-
Notifications
You must be signed in to change notification settings - Fork 4
fix: model-aware sentinels, cache optimization, session alignment, and image model support #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
73db541
aca6181
de62914
c798189
1d61562
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -158,6 +158,10 @@ export interface ManagedAccount { | |
| verificationRequiredAt?: number; | ||
| verificationRequiredReason?: string; | ||
| verificationUrl?: string; | ||
| /** Account permanently ineligible for Antigravity */ | ||
| ineligible?: boolean; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Ineligibility metadata is lost on the first disk save, so after restart Prompt for AI agents |
||
| ineligibleAt?: number; | ||
| ineligibleReason?: string; | ||
| /** Daily request counts per model family */ | ||
| dailyRequestCounts?: { | ||
| date: string | ||
|
|
@@ -956,6 +960,30 @@ export class AccountManager { | |
| return true; | ||
| } | ||
|
|
||
| markAccountIneligible(accountIndex: number, reason?: string): boolean { | ||
| const account = this.accounts[accountIndex]; | ||
| if (!account) { | ||
| return false; | ||
| } | ||
|
|
||
| account.ineligible = true; | ||
| account.ineligibleAt = nowMs(); | ||
| account.ineligibleReason = reason?.trim() || undefined; | ||
|
|
||
| if (account.enabled !== false) { | ||
| this.setAccountEnabled(accountIndex, false); | ||
| } else { | ||
| this.requestSaveToDisk(); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| isAccountIneligible(accountIndex: number): boolean { | ||
| const account = this.accounts[accountIndex]; | ||
| return account?.ineligible === true; | ||
| } | ||
|
|
||
| removeAccountByIndex(accountIndex: number): boolean { | ||
| if (accountIndex < 0 || accountIndex >= this.accounts.length) { | ||
| return false; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,6 +118,56 @@ function mergeConfigs( | |
| * @param directory - The project directory (for project-level config) | ||
| * @returns Fully resolved configuration | ||
| */ | ||
| function stripJsonCommentsAndTrailingCommas(json: string): string { | ||
| return json | ||
| .replace( | ||
| /\\"|"(?:\\"|[^"])*"|(\/{2}.*|\/\*[\s\S]*?\*\/)/g, | ||
| (match: string, group: string | undefined) => (group ? "" : match) | ||
| ) | ||
| .replace(/,(\s*[}\]])/g, "$1"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Model metadata containing Prompt for AI agents |
||
| } | ||
|
|
||
| function loadModelsFile(path: string): Record<string, unknown> | null { | ||
| try { | ||
| if (!existsSync(path)) { | ||
| return null; | ||
| } | ||
| const content = readFileSync(path, "utf-8"); | ||
| const parsed = JSON.parse(stripJsonCommentsAndTrailingCommas(content)); | ||
| if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { | ||
| return parsed as Record<string, unknown>; | ||
| } | ||
| return null; | ||
| } catch (error) { | ||
| log.warn("Failed to load decoupled models file", { path, error: String(error) }); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export function loadDecoupledModels(directory: string): Record<string, unknown> { | ||
| let mergedModels: Record<string, unknown> = {}; | ||
|
|
||
| const configDir = getConfigDir(); | ||
| const userJsonPath = join(configDir, "antigravity-models.json"); | ||
| const userJsoncPath = join(configDir, "antigravity-models.jsonc"); | ||
| const projectJsonPath = join(directory, ".opencode", "antigravity-models.json"); | ||
| const projectJsoncPath = join(directory, ".opencode", "antigravity-models.jsonc"); | ||
|
|
||
| // User level (prefer jsonc if both exist) | ||
| const userModels = loadModelsFile(existsSync(userJsoncPath) ? userJsoncPath : userJsonPath); | ||
| if (userModels) { | ||
| mergedModels = { ...mergedModels, ...userModels }; | ||
| } | ||
|
|
||
| // Project level (prefer jsonc if both exist) - overrides user level | ||
| const projectModels = loadModelsFile(existsSync(projectJsoncPath) ? projectJsoncPath : projectJsonPath); | ||
| if (projectModels) { | ||
| mergedModels = { ...mergedModels, ...projectModels }; | ||
| } | ||
|
|
||
| return mergedModels; | ||
| } | ||
|
|
||
| export function loadConfig(directory: string): AntigravityConfig { | ||
| // Start with defaults | ||
| let config: AntigravityConfig = { ...DEFAULT_CONFIG }; | ||
|
|
@@ -136,6 +186,15 @@ export function loadConfig(directory: string): AntigravityConfig { | |
| config = mergeConfigs(config, projectConfig); | ||
| } | ||
|
|
||
| // Load decoupled models from antigravity-models.json(c) and merge into config.models | ||
| const decoupledModels = loadDecoupledModels(directory); | ||
| if (Object.keys(decoupledModels).length > 0) { | ||
| config.models = { | ||
| ...(config.models ?? {}), | ||
| ...decoupledModels, | ||
| }; | ||
| } | ||
|
|
||
| return config; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Some recoverable 403 responses can now permanently disable an account because the ineligibility matcher includes the generic phrase
access denied. Tightening that check to explicit ineligibility phrases avoids false permanent disablement and preserves normal retry/fallback behavior.Prompt for AI agents