dsh-agy 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +8 -3
  2. package/lib/{accounts-BOuz6_Sh.mjs → accounts-DtIsWCeF.mjs} +15 -11
  3. package/lib/{accounts-BOuz6_Sh.mjs.map → accounts-DtIsWCeF.mjs.map} +1 -1
  4. package/lib/cli/index.mjs +6 -6
  5. package/lib/client.js +181 -23
  6. package/lib/{constants-CmA9wXNe.mjs → constants-AdsD4sKh.mjs} +3 -3
  7. package/lib/{constants-CmA9wXNe.mjs.map → constants-AdsD4sKh.mjs.map} +1 -1
  8. package/lib/{exchange-C-tndv7j.mjs → exchange-B_rHhU8K.mjs} +3 -3
  9. package/lib/{exchange-C-tndv7j.mjs.map → exchange-B_rHhU8K.mjs.map} +1 -1
  10. package/lib/{import-SxPDjH3u.mjs → import-BqyA9tzt.mjs} +4 -4
  11. package/lib/{import-SxPDjH3u.mjs.map → import-BqyA9tzt.mjs.map} +1 -1
  12. package/lib/index.mjs +2 -2
  13. package/lib/{models-CjB0rXtI.mjs → models-D_54Ro5z.mjs} +3 -3
  14. package/lib/{models-CjB0rXtI.mjs.map → models-D_54Ro5z.mjs.map} +1 -1
  15. package/lib/{parse-D3InNyUx.mjs → parse-CeJc7cHo.mjs} +108 -8
  16. package/lib/parse-CeJc7cHo.mjs.map +1 -0
  17. package/lib/{plugin-common-Dwa1K13O.mjs → plugin-common-BlGbNHxX.mjs} +267 -10
  18. package/lib/plugin-common-BlGbNHxX.mjs.map +1 -0
  19. package/lib/{proxy-BbuQX5U9.mjs → proxy-B7VnbAfO.mjs} +5 -2
  20. package/lib/proxy-B7VnbAfO.mjs.map +1 -0
  21. package/lib/{quota-summary-DEA3P3Zh.mjs → quota-summary-DarIZkmo.mjs} +3 -3
  22. package/lib/{quota-summary-DEA3P3Zh.mjs.map → quota-summary-DarIZkmo.mjs.map} +1 -1
  23. package/lib/{translate-Cq6PGO4b.mjs → translate-vh3M5H_9.mjs} +7 -6
  24. package/lib/translate-vh3M5H_9.mjs.map +1 -0
  25. package/lib/web/plugin.d.mts +23 -1
  26. package/lib/web/plugin.d.mts.map +1 -1
  27. package/lib/web/plugin.mjs +86 -19
  28. package/lib/web/plugin.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/lib/parse-D3InNyUx.mjs.map +0 -1
  31. package/lib/plugin-common-Dwa1K13O.mjs.map +0 -1
  32. package/lib/proxy-BbuQX5U9.mjs.map +0 -1
  33. package/lib/translate-Cq6PGO4b.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"import-SxPDjH3u.mjs","names":[],"sources":["../src/oauth/authorize.ts","../src/cli/import.ts"],"sourcesContent":["/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */\n\nimport {\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { encodeState, generatePkcePair } from './pkce.ts'\n\nexport interface AntigravityAuthorization {\n url: string\n verifier: string\n projectId: string\n /** Raw state payload embedded in the URL (for server-side binding). */\n state: string\n}\n\n/**\n * Build the Google OAuth authorization URL for the Antigravity client.\n * The `state` payload carries the PKCE verifier and an optional project id so a\n * headless callback can be pasted back verbatim.\n * @param redirectUri - loopback callback URI (harness webServer route or CLI listener).\n * @param projectId - optional Antigravity project id to bind this login to.\n */\nexport async function authorizeAntigravity(\n redirectUri: string,\n projectId = '',\n): Promise<AntigravityAuthorization> {\n const { verifier, challenge } = generatePkcePair()\n\n const url = new URL(OAUTH_AUTHORIZE_URL)\n const { clientId } = resolveAgyClientCredentials()\n url.searchParams.set('client_id', clientId)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('redirect_uri', redirectUri)\n url.searchParams.set('scope', AGY_SCOPES.join(' '))\n url.searchParams.set('code_challenge', challenge)\n url.searchParams.set('code_challenge_method', 'S256')\n const state = encodeState({ verifier, projectId: projectId || '' })\n url.searchParams.set('state', state)\n url.searchParams.set('access_type', 'offline')\n url.searchParams.set('prompt', 'consent')\n\n return { url: url.toString(), verifier, projectId: projectId || '', state }\n}\n","/**\n * Import agy credentials: parse the `agy` CLI auth.json token file or a\n * paste credential blob, enrich with the Code Assist backend (email/project),\n * and persist into the account store. Adapted from OmniRoute's agyAuthImport\n * (MIT, see NOTICE.md).\n */\nimport { randomUUID } from 'node:crypto'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n AGY_IDE_TYPE,\n AGY_PLATFORM_ENUM,\n currentAgyVersion,\n getAgyBootstrapUserAgent,\n} from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport { normalizeProxyUrl, proxiedFetch } from '../proxy.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n clientId?: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n clientId?: string | null\n}\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed.\n * `proxyUrl` is the account proxy being imported: enrichment talks to Google as\n * this account, so it must take the same egress route the account will use. */\nexport async function enrichWithAntigravityBackend(\n parsed: ParsedAgyAuth,\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n }, { proxyUrl: options.proxyUrl })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n // The identity rides in the BODY `metadata` message, matching\n // `LoadCodeAssistRequest.metadata` in the official descriptor; there is no\n // `Client-Metadata` header (neither official binary contains that name).\n const loadBody = JSON.stringify({\n metadata: {\n ideType: AGY_IDE_TYPE,\n ideVersion: currentAgyVersion(),\n platform: AGY_PLATFORM_ENUM,\n },\n })\n for (const endpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const res = await proxiedFetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: loadBody,\n signal: loadController.signal,\n }, { proxyUrl: options.proxyUrl })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return {\n ...parsed,\n email,\n projectId,\n clientId: parsed.clientId ?? null,\n }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(\n source: unknown,\n kind: 'json' | 'blob',\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n options,\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source), options)\n}\n\n/** Batch-import many sources (CLI multi-file / web multi-line paste). Each item\n * is independent: a failure is collected, the rest still import. */\nexport async function importManySources(\n items: Array<{ source: unknown; kind: 'json' | 'blob' }>,\n store: AccountStore,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ imported: number; replaced: number; errors: string[] }> {\n const result = { imported: 0, replaced: 0, errors: [] as string[] }\n for (const item of items) {\n try {\n // Enrich over the proxy this account is being imported with, so the\n // verification calls leave from the same egress as the account itself.\n const enriched = await parseImportSource(item.source, item.kind, {\n ...(options.proxy ? { proxyUrl: normalizeProxyUrl(options.proxy) } : {}),\n })\n const { created } = await upsertImportedAccount(store, enriched, options)\n if (created) result.imported++\n else result.replaced++\n } catch (error) {\n result.errors.push(error instanceof Error ? error.message : String(error))\n }\n }\n return result\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n // Normalize proxy if explicitly provided (empty -> clear -> undefined)\n let normalizedProxy: string | undefined\n let proxyProvided = options.proxy !== undefined\n if (proxyProvided) {\n const raw = options.proxy ?? ''\n if (!raw.trim()) normalizedProxy = undefined\n else normalizedProxy = normalizeProxyUrl(raw)\n }\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n const updated: ManagedAccount = {\n ...existing,\n id: existing.id || randomUUID(),\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n clientId: enriched.clientId ? enriched.clientId : existing.clientId,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n if (proxyProvided) {\n if (normalizedProxy) updated.proxy = normalizedProxy\n else delete (updated as { proxy?: string }).proxy\n }\n storage.accounts[existingIndex] = updated\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n id: randomUUID(),\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n clientId: enriched.clientId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n ...(normalizedProxy ? { proxy: normalizedProxy } : {}),\n }\n // When proxy was explicitly cleared (empty string) for new account, just omit proxy field\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"import-BqyA9tzt.mjs","names":[],"sources":["../src/oauth/authorize.ts","../src/cli/import.ts"],"sourcesContent":["/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */\n\nimport {\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { encodeState, generatePkcePair } from './pkce.ts'\n\nexport interface AntigravityAuthorization {\n url: string\n verifier: string\n projectId: string\n /** Raw state payload embedded in the URL (for server-side binding). */\n state: string\n}\n\n/**\n * Build the Google OAuth authorization URL for the Antigravity client.\n * The `state` payload carries the PKCE verifier and an optional project id so a\n * headless callback can be pasted back verbatim.\n * @param redirectUri - loopback callback URI (harness webServer route or CLI listener).\n * @param projectId - optional Antigravity project id to bind this login to.\n */\nexport async function authorizeAntigravity(\n redirectUri: string,\n projectId = '',\n): Promise<AntigravityAuthorization> {\n const { verifier, challenge } = generatePkcePair()\n\n const url = new URL(OAUTH_AUTHORIZE_URL)\n const { clientId } = resolveAgyClientCredentials()\n url.searchParams.set('client_id', clientId)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('redirect_uri', redirectUri)\n url.searchParams.set('scope', AGY_SCOPES.join(' '))\n url.searchParams.set('code_challenge', challenge)\n url.searchParams.set('code_challenge_method', 'S256')\n const state = encodeState({ verifier, projectId: projectId || '' })\n url.searchParams.set('state', state)\n url.searchParams.set('access_type', 'offline')\n url.searchParams.set('prompt', 'consent')\n\n return { url: url.toString(), verifier, projectId: projectId || '', state }\n}\n","/**\n * Import agy credentials: parse the `agy` CLI auth.json token file or a\n * paste credential blob, enrich with the Code Assist backend (email/project),\n * and persist into the account store. Adapted from OmniRoute's agyAuthImport\n * (MIT, see NOTICE.md).\n */\nimport { randomUUID } from 'node:crypto'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n AGY_IDE_TYPE,\n AGY_PLATFORM_ENUM,\n currentAgyVersion,\n getAgyBootstrapUserAgent,\n} from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport { normalizeProxyUrl, proxiedFetch } from '../proxy.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n clientId?: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n clientId?: string | null\n}\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed.\n * `proxyUrl` is the account proxy being imported: enrichment talks to Google as\n * this account, so it must take the same egress route the account will use. */\nexport async function enrichWithAntigravityBackend(\n parsed: ParsedAgyAuth,\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n }, { proxyUrl: options.proxyUrl })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n // The identity rides in the BODY `metadata` message, matching\n // `LoadCodeAssistRequest.metadata` in the official descriptor; there is no\n // `Client-Metadata` header (neither official binary contains that name).\n const loadBody = JSON.stringify({\n metadata: {\n ideType: AGY_IDE_TYPE,\n ideVersion: currentAgyVersion(),\n platform: AGY_PLATFORM_ENUM,\n },\n })\n for (const endpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const res = await proxiedFetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: loadBody,\n signal: loadController.signal,\n }, { proxyUrl: options.proxyUrl })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return {\n ...parsed,\n email,\n projectId,\n clientId: parsed.clientId ?? null,\n }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(\n source: unknown,\n kind: 'json' | 'blob',\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n options,\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source), options)\n}\n\n/** Batch-import many sources (CLI multi-file / web multi-line paste). Each item\n * is independent: a failure is collected, the rest still import. */\nexport async function importManySources(\n items: Array<{ source: unknown; kind: 'json' | 'blob' }>,\n store: AccountStore,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ imported: number; replaced: number; errors: string[] }> {\n const result = { imported: 0, replaced: 0, errors: [] as string[] }\n for (const item of items) {\n try {\n // Enrich over the proxy this account is being imported with, so the\n // verification calls leave from the same egress as the account itself.\n const enriched = await parseImportSource(item.source, item.kind, {\n ...(options.proxy ? { proxyUrl: normalizeProxyUrl(options.proxy) } : {}),\n })\n const { created } = await upsertImportedAccount(store, enriched, options)\n if (created) result.imported++\n else result.replaced++\n } catch (error) {\n result.errors.push(error instanceof Error ? error.message : String(error))\n }\n }\n return result\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n // Normalize proxy if explicitly provided (empty -> clear -> undefined)\n let normalizedProxy: string | undefined\n let proxyProvided = options.proxy !== undefined\n if (proxyProvided) {\n const raw = options.proxy ?? ''\n if (!raw.trim()) normalizedProxy = undefined\n else normalizedProxy = normalizeProxyUrl(raw)\n }\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n const updated: ManagedAccount = {\n ...existing,\n id: existing.id || randomUUID(),\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n clientId: enriched.clientId ? enriched.clientId : existing.clientId,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n if (proxyProvided) {\n if (normalizedProxy) updated.proxy = normalizedProxy\n else delete (updated as { proxy?: string }).proxy\n }\n storage.accounts[existingIndex] = updated\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n id: randomUUID(),\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n clientId: enriched.clientId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n ...(normalizedProxy ? { proxy: normalizedProxy } : {}),\n }\n // When proxy was explicitly cleared (empty string) for new account, just omit proxy field\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
package/lib/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as isAgyDisabled } from "./accounts-BOuz6_Sh.mjs";
2
- import { t as createAgyRuntime } from "./plugin-common-Dwa1K13O.mjs";
1
+ import { i as isAgyDisabled } from "./accounts-DtIsWCeF.mjs";
2
+ import { t as createAgyRuntime } from "./plugin-common-BlGbNHxX.mjs";
3
3
  //#region src/index.ts
4
4
  const name = "dsh-agy";
5
5
  const inject = ["llm"];
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { h as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-CmA9wXNe.mjs";
3
- import { o as proxiedFetch } from "./proxy-BbuQX5U9.mjs";
2
+ import { h as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-AdsD4sKh.mjs";
3
+ import { o as proxiedFetch } from "./proxy-B7VnbAfO.mjs";
4
4
  import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-BRHHLLd5.mjs";
5
5
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
6
6
  //#region src/adapter/models.ts
@@ -217,4 +217,4 @@ function resolveAgyModel(provider, model) {
217
217
  //#endregion
218
218
  export { resolveAgyModel as i, listAgyModels as n, models_exports as r, catalogModelList as t };
219
219
 
220
- //# sourceMappingURL=models-CjB0rXtI.mjs.map
220
+ //# sourceMappingURL=models-D_54Ro5z.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"models-CjB0rXtI.mjs","names":[],"sources":["../src/adapter/models.ts"],"sourcesContent":["/**\n * Model discovery: dynamic `v1internal:fetchAvailableModels` as the primary\n * source (fresh ids + per-model quotaInfo), the pinned catalog merged in for\n * capability metadata, and catalog fallback when the endpoint is unreachable.\n */\n\nimport { ReasoningEffortId, type LlmModelInfo, type LlmModelReasoningInfo, type LlmResolvedModelInfo, type ModelModality } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId, isLevelThinkingModel } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\n/**\n * Selectable thinking levels for a tiered model. Deliberately NO `defaultEffort`.\n *\n * `defaultEffort` is not a display hint: the harness reads it as\n * `effective = requested ?? reasoning.defaultEffort`, and it also gates the\n * selector's own \"provider default\" entry\n * (`...reasoning.defaultEffort === void 0 ? [providerDefault] : []`). Setting it\n * therefore did two things at once — it forced an effort onto every request, so\n * `translate.ts` emitted `thinkingConfig` even when the user never chose a level,\n * and it REMOVED the only option that expresses \"let the model decide\".\n *\n * Leaving it unset restores that option (host label \"Default\"): choosing it\n * sends no `reasoningEffort`, `translate.ts` omits `thinkingConfig` entirely, and\n * the upstream runs its own adaptive budget — measured on this channel as\n * `thinkingBudget: -1` + `minThinkingBudget: 32` for every `*-tiered` model.\n * The three explicit levels remain selectable alongside it.\n */\nconst LEVEL_REASONING: LlmModelReasoningInfo = Object.freeze({\n efforts: Object.freeze([\n { id: ReasoningEffortId('low'), name: 'Low' },\n { id: ReasoningEffortId('medium'), name: 'Medium' },\n { id: ReasoningEffortId('high'), name: 'High' },\n ] as const),\n} as const)\n\n/**\n * Input modalities per model. Image support follows the catalog's own\n * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only\n * there); unknown dynamic ids default to vision-capable — the upstream schema\n * accepts inlineData across the board, and a wrong guess surfaces as a clear\n * upstream 400 instead of a silent drop.\n */\nconst AGY_INPUT_MODALITIES = ['text', 'image'] as const\nconst AGY_TEXT_ONLY_MODALITIES = ['text'] as const\n\nfunction inputModalitiesFor(meta: { supportsVision?: boolean } | undefined): ModelModality[] {\n return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES]\n}\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DeprecatedModelInfo {\n newModelId?: string\n oldModelEnum?: string\n newModelEnum?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\n /**\n * Role assignments the endpoint returns alongside `models`. Upstream uses\n * them to decide which id drives which IDE feature; only the ones that mean\n * \"this is not an agent chat model\" are consumed here, plus the three\n * positive signals below.\n */\n tabModelIds?: string[]\n imageGenerationModelIds?: string[]\n audioTranscriptionModelIds?: string[]\n /** Keyed by the RETIRED id; an object, not an array. */\n deprecatedModelIds?: Record<string, DeprecatedModelInfo>\n defaultAgentModelId?: string\n agentModelSorts?: { displayName?: string; groups?: { modelIds?: string[] }[] }[]\n /** Selectable-thinking families, e.g. `{flash: ['gemini-3.8-flash-tiered']}`. */\n tieredModelIds?: Record<string, string[]>\n}\n\n/** Role keys whose members are not agent chat models. */\nconst NON_CHAT_ROLE_KEYS = ['tabModelIds', 'imageGenerationModelIds', 'audioTranscriptionModelIds'] as const\n\n/** The payload arrives via an unvalidated cast, so every shape is re-checked. */\nfunction stringsFrom(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return value.filter((item): item is string => typeof item === 'string' && item.length > 0)\n}\n\n/**\n * Ids the payload itself advertises: the default agent model, anything in an\n * `agentModelSorts` group, and every tiered family member. These are never\n * hidden. If upstream both recommends an id and files it under a non-chat\n * role, the recommendation is the more recent statement of intent, and this\n * keeps a labelling mistake from removing a working model.\n */\nfunction advertisedModelIds(dynamic: DiscoveredModels): Set<string> {\n const advertised = new Set<string>()\n if (typeof dynamic.defaultAgentModelId === 'string' && dynamic.defaultAgentModelId.length > 0) {\n advertised.add(dynamic.defaultAgentModelId)\n }\n for (const sort of Array.isArray(dynamic.agentModelSorts) ? dynamic.agentModelSorts : []) {\n for (const group of Array.isArray(sort?.groups) ? sort.groups : []) {\n for (const id of stringsFrom(group?.modelIds)) advertised.add(id)\n }\n }\n const tiered = dynamic.tieredModelIds\n if (tiered && typeof tiered === 'object' && !Array.isArray(tiered)) {\n for (const family of Object.values(tiered)) {\n for (const id of stringsFrom(family)) advertised.add(id)\n }\n }\n return advertised\n}\n\n/**\n * Discovered ids that should not appear in a chat model list, according to the\n * payload's own role assignments.\n *\n * This is the judgement `isChatCallableModelId` already makes with the `tab_`\n * prefix, but taken from upstream's list instead of a name guess. That matters:\n * `tabModelIds` members are not required to start with `tab_` (live accounts\n * return ids such as `chat_20706`), so those ids would otherwise reach the\n * model list, where they render as a raw id with no metadata.\n *\n * Only roles that mean \"different output modality\" or \"superseded\" are\n * consumed. The utility roles (`commandModelIds`, `mqueryModelIds`,\n * `webSearchModelIds`, `commitMessageModelIds`) name ordinary chat models doing\n * a side job - on a live account all three of the latter name\n * `gemini-3.1-flash-lite`, which is a pinned catalog model.\n *\n * A deprecated id is hidden only when its `newModelId` is present in `models`,\n * chat-callable, and not role-hidden; otherwise hiding it would remove the only\n * route to that capability on an account whose tier does not carry the\n * replacement. Replacement availability is judged against role hiding alone and\n * never against other deprecations, so a chain A -> B -> C resolves the same\n * way whatever order the payload happens to serialize its keys in.\n *\n * Not the complete hiding rule: the `tab_` prefix check stays in\n * `mergeModelCatalog`, because the catalog-only fallback has no payload.\n */\nfunction hiddenDiscoveredIds(dynamic: DiscoveredModels): Set<string> {\n const models = dynamic.models ?? {}\n const advertised = advertisedModelIds(dynamic)\n const hidden = new Set<string>()\n for (const key of NON_CHAT_ROLE_KEYS) {\n for (const id of stringsFrom(dynamic[key])) {\n if (!advertised.has(id)) hidden.add(id)\n }\n }\n const roleHidden = new Set(hidden)\n const deprecated = dynamic.deprecatedModelIds\n if (deprecated && typeof deprecated === 'object' && !Array.isArray(deprecated)) {\n for (const [retiredId, info] of Object.entries(deprecated)) {\n if (advertised.has(retiredId)) continue\n const replacement = info?.newModelId\n if (typeof replacement !== 'string' || replacement.length === 0 || replacement === retiredId) continue\n if (!Object.hasOwn(models, replacement)) continue\n if (!isChatCallableModelId(replacement) || roleHidden.has(replacement)) continue\n hidden.add(retiredId)\n }\n }\n return hidden\n}\n\n/** Fetch the account's available models from the first reachable endpoint. */\nexport async function fetchAvailableModels(\n accessToken: string,\n projectId?: string,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<DiscoveredModels> {\n let lastError: unknown = null\n const body = projectId ? { project: projectId } : {}\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: JSON.stringify(body),\n })\n if (response.ok) {\n return (await response.json()) as DiscoveredModels\n }\n lastError = new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`)\n } catch (error) {\n lastError = error\n }\n }\n throw lastError instanceof Error ? lastError : new Error('fetchAvailableModels: all endpoints failed')\n}\n\n/**\n * The discovered ids a chat session may actually use: upstream's `models` minus\n * the `tab_`/role/deprecated set (see `hiddenDiscoveredIds`).\n *\n * Exported so surfaces other than the selector (the quota panel) present the\n * same list. `mergeModelCatalog` consumes it too, so the two cannot drift.\n */\nexport function chatCallableDiscoveredIds(dynamic: DiscoveredModels): string[] {\n const hidden = hiddenDiscoveredIds(dynamic)\n return Object.keys(dynamic.models ?? {}).filter((id) => isChatCallableModelId(id) && !hidden.has(id))\n}\n\n/** Merge dynamic ids with catalog metadata; non-chat and superseded ids are dropped, unknown ids keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n const hidden = hiddenDiscoveredIds(dynamic)\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id) || hidden.has(id)) continue\n const meta = catalogModel(id)\n const rawDisplayName = entry.displayName && entry.displayName !== id ? entry.displayName : undefined\n const displayName = rawDisplayName ?? meta?.name ?? entry.displayName ?? entry.modelName ?? id\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: displayName,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength } } : {}),\n })\n }\n return entries\n}\n\n/** Catalog-only model list used when the endpoint is unreachable. */\nexport function catalogModelList(): LlmModelInfo[] {\n return AGY_PUBLIC_MODELS.map((model) => ({\n provider: AGY_PROVIDER,\n id: model.id,\n name: model.name,\n inputModalities: inputModalitiesFor(model),\n context: { contextWindow: model.contextLength },\n }))\n}\n\n/** Adapter-facing listing: dynamic first, catalog fallback. */\nexport async function listAgyModels(\n accessToken: string | undefined,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<readonly LlmModelInfo[]> {\n if (!accessToken) return catalogModelList()\n try {\n const dynamic = await fetchAvailableModels(accessToken, projectId, fetchImpl)\n const merged = mergeModelCatalog(dynamic)\n return merged.length > 0 ? merged : catalogModelList()\n } catch {\n return catalogModelList()\n }\n}\n\n/** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */\nexport function resolveAgyModel(provider: string, model: string): LlmResolvedModelInfo {\n const meta = catalogModel(model)\n if (isLevelThinkingModel(model)) {\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n context: { contextWindow: meta?.contextLength ?? 1048576 },\n defaultMaxTokens: meta?.maxOutputTokens ?? 65536,\n // Return a shallow copy so callers cannot mutate the frozen singleton.\n reasoning: { ...LEVEL_REASONING, efforts: [...LEVEL_REASONING.efforts] },\n }\n }\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"models-D_54Ro5z.mjs","names":[],"sources":["../src/adapter/models.ts"],"sourcesContent":["/**\n * Model discovery: dynamic `v1internal:fetchAvailableModels` as the primary\n * source (fresh ids + per-model quotaInfo), the pinned catalog merged in for\n * capability metadata, and catalog fallback when the endpoint is unreachable.\n */\n\nimport { ReasoningEffortId, type LlmModelInfo, type LlmModelReasoningInfo, type LlmResolvedModelInfo, type ModelModality } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId, isLevelThinkingModel } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\n/**\n * Selectable thinking levels for a tiered model. Deliberately NO `defaultEffort`.\n *\n * `defaultEffort` is not a display hint: the harness reads it as\n * `effective = requested ?? reasoning.defaultEffort`, and it also gates the\n * selector's own \"provider default\" entry\n * (`...reasoning.defaultEffort === void 0 ? [providerDefault] : []`). Setting it\n * therefore did two things at once — it forced an effort onto every request, so\n * `translate.ts` emitted `thinkingConfig` even when the user never chose a level,\n * and it REMOVED the only option that expresses \"let the model decide\".\n *\n * Leaving it unset restores that option (host label \"Default\"): choosing it\n * sends no `reasoningEffort`, `translate.ts` omits `thinkingConfig` entirely, and\n * the upstream runs its own adaptive budget — measured on this channel as\n * `thinkingBudget: -1` + `minThinkingBudget: 32` for every `*-tiered` model.\n * The three explicit levels remain selectable alongside it.\n */\nconst LEVEL_REASONING: LlmModelReasoningInfo = Object.freeze({\n efforts: Object.freeze([\n { id: ReasoningEffortId('low'), name: 'Low' },\n { id: ReasoningEffortId('medium'), name: 'Medium' },\n { id: ReasoningEffortId('high'), name: 'High' },\n ] as const),\n} as const)\n\n/**\n * Input modalities per model. Image support follows the catalog's own\n * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only\n * there); unknown dynamic ids default to vision-capable — the upstream schema\n * accepts inlineData across the board, and a wrong guess surfaces as a clear\n * upstream 400 instead of a silent drop.\n */\nconst AGY_INPUT_MODALITIES = ['text', 'image'] as const\nconst AGY_TEXT_ONLY_MODALITIES = ['text'] as const\n\nfunction inputModalitiesFor(meta: { supportsVision?: boolean } | undefined): ModelModality[] {\n return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES]\n}\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DeprecatedModelInfo {\n newModelId?: string\n oldModelEnum?: string\n newModelEnum?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\n /**\n * Role assignments the endpoint returns alongside `models`. Upstream uses\n * them to decide which id drives which IDE feature; only the ones that mean\n * \"this is not an agent chat model\" are consumed here, plus the three\n * positive signals below.\n */\n tabModelIds?: string[]\n imageGenerationModelIds?: string[]\n audioTranscriptionModelIds?: string[]\n /** Keyed by the RETIRED id; an object, not an array. */\n deprecatedModelIds?: Record<string, DeprecatedModelInfo>\n defaultAgentModelId?: string\n agentModelSorts?: { displayName?: string; groups?: { modelIds?: string[] }[] }[]\n /** Selectable-thinking families, e.g. `{flash: ['gemini-3.8-flash-tiered']}`. */\n tieredModelIds?: Record<string, string[]>\n}\n\n/** Role keys whose members are not agent chat models. */\nconst NON_CHAT_ROLE_KEYS = ['tabModelIds', 'imageGenerationModelIds', 'audioTranscriptionModelIds'] as const\n\n/** The payload arrives via an unvalidated cast, so every shape is re-checked. */\nfunction stringsFrom(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return value.filter((item): item is string => typeof item === 'string' && item.length > 0)\n}\n\n/**\n * Ids the payload itself advertises: the default agent model, anything in an\n * `agentModelSorts` group, and every tiered family member. These are never\n * hidden. If upstream both recommends an id and files it under a non-chat\n * role, the recommendation is the more recent statement of intent, and this\n * keeps a labelling mistake from removing a working model.\n */\nfunction advertisedModelIds(dynamic: DiscoveredModels): Set<string> {\n const advertised = new Set<string>()\n if (typeof dynamic.defaultAgentModelId === 'string' && dynamic.defaultAgentModelId.length > 0) {\n advertised.add(dynamic.defaultAgentModelId)\n }\n for (const sort of Array.isArray(dynamic.agentModelSorts) ? dynamic.agentModelSorts : []) {\n for (const group of Array.isArray(sort?.groups) ? sort.groups : []) {\n for (const id of stringsFrom(group?.modelIds)) advertised.add(id)\n }\n }\n const tiered = dynamic.tieredModelIds\n if (tiered && typeof tiered === 'object' && !Array.isArray(tiered)) {\n for (const family of Object.values(tiered)) {\n for (const id of stringsFrom(family)) advertised.add(id)\n }\n }\n return advertised\n}\n\n/**\n * Discovered ids that should not appear in a chat model list, according to the\n * payload's own role assignments.\n *\n * This is the judgement `isChatCallableModelId` already makes with the `tab_`\n * prefix, but taken from upstream's list instead of a name guess. That matters:\n * `tabModelIds` members are not required to start with `tab_` (live accounts\n * return ids such as `chat_20706`), so those ids would otherwise reach the\n * model list, where they render as a raw id with no metadata.\n *\n * Only roles that mean \"different output modality\" or \"superseded\" are\n * consumed. The utility roles (`commandModelIds`, `mqueryModelIds`,\n * `webSearchModelIds`, `commitMessageModelIds`) name ordinary chat models doing\n * a side job - on a live account all three of the latter name\n * `gemini-3.1-flash-lite`, which is a pinned catalog model.\n *\n * A deprecated id is hidden only when its `newModelId` is present in `models`,\n * chat-callable, and not role-hidden; otherwise hiding it would remove the only\n * route to that capability on an account whose tier does not carry the\n * replacement. Replacement availability is judged against role hiding alone and\n * never against other deprecations, so a chain A -> B -> C resolves the same\n * way whatever order the payload happens to serialize its keys in.\n *\n * Not the complete hiding rule: the `tab_` prefix check stays in\n * `mergeModelCatalog`, because the catalog-only fallback has no payload.\n */\nfunction hiddenDiscoveredIds(dynamic: DiscoveredModels): Set<string> {\n const models = dynamic.models ?? {}\n const advertised = advertisedModelIds(dynamic)\n const hidden = new Set<string>()\n for (const key of NON_CHAT_ROLE_KEYS) {\n for (const id of stringsFrom(dynamic[key])) {\n if (!advertised.has(id)) hidden.add(id)\n }\n }\n const roleHidden = new Set(hidden)\n const deprecated = dynamic.deprecatedModelIds\n if (deprecated && typeof deprecated === 'object' && !Array.isArray(deprecated)) {\n for (const [retiredId, info] of Object.entries(deprecated)) {\n if (advertised.has(retiredId)) continue\n const replacement = info?.newModelId\n if (typeof replacement !== 'string' || replacement.length === 0 || replacement === retiredId) continue\n if (!Object.hasOwn(models, replacement)) continue\n if (!isChatCallableModelId(replacement) || roleHidden.has(replacement)) continue\n hidden.add(retiredId)\n }\n }\n return hidden\n}\n\n/** Fetch the account's available models from the first reachable endpoint. */\nexport async function fetchAvailableModels(\n accessToken: string,\n projectId?: string,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<DiscoveredModels> {\n let lastError: unknown = null\n const body = projectId ? { project: projectId } : {}\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: JSON.stringify(body),\n })\n if (response.ok) {\n return (await response.json()) as DiscoveredModels\n }\n lastError = new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`)\n } catch (error) {\n lastError = error\n }\n }\n throw lastError instanceof Error ? lastError : new Error('fetchAvailableModels: all endpoints failed')\n}\n\n/**\n * The discovered ids a chat session may actually use: upstream's `models` minus\n * the `tab_`/role/deprecated set (see `hiddenDiscoveredIds`).\n *\n * Exported so surfaces other than the selector (the quota panel) present the\n * same list. `mergeModelCatalog` consumes it too, so the two cannot drift.\n */\nexport function chatCallableDiscoveredIds(dynamic: DiscoveredModels): string[] {\n const hidden = hiddenDiscoveredIds(dynamic)\n return Object.keys(dynamic.models ?? {}).filter((id) => isChatCallableModelId(id) && !hidden.has(id))\n}\n\n/** Merge dynamic ids with catalog metadata; non-chat and superseded ids are dropped, unknown ids keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n const hidden = hiddenDiscoveredIds(dynamic)\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id) || hidden.has(id)) continue\n const meta = catalogModel(id)\n const rawDisplayName = entry.displayName && entry.displayName !== id ? entry.displayName : undefined\n const displayName = rawDisplayName ?? meta?.name ?? entry.displayName ?? entry.modelName ?? id\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: displayName,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength } } : {}),\n })\n }\n return entries\n}\n\n/** Catalog-only model list used when the endpoint is unreachable. */\nexport function catalogModelList(): LlmModelInfo[] {\n return AGY_PUBLIC_MODELS.map((model) => ({\n provider: AGY_PROVIDER,\n id: model.id,\n name: model.name,\n inputModalities: inputModalitiesFor(model),\n context: { contextWindow: model.contextLength },\n }))\n}\n\n/** Adapter-facing listing: dynamic first, catalog fallback. */\nexport async function listAgyModels(\n accessToken: string | undefined,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<readonly LlmModelInfo[]> {\n if (!accessToken) return catalogModelList()\n try {\n const dynamic = await fetchAvailableModels(accessToken, projectId, fetchImpl)\n const merged = mergeModelCatalog(dynamic)\n return merged.length > 0 ? merged : catalogModelList()\n } catch {\n return catalogModelList()\n }\n}\n\n/** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */\nexport function resolveAgyModel(provider: string, model: string): LlmResolvedModelInfo {\n const meta = catalogModel(model)\n if (isLevelThinkingModel(model)) {\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n context: { contextWindow: meta?.contextLength ?? 1048576 },\n defaultMaxTokens: meta?.maxOutputTokens ?? 65536,\n // Return a shallow copy so callers cannot mutate the frozen singleton.\n reasoning: { ...LEVEL_REASONING, efforts: [...LEVEL_REASONING.efforts] },\n }\n }\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":"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"}
@@ -2,9 +2,12 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import * as DshLlm from "@deepseek-ai/dsh-llm";
3
3
  //#region src/adapter/parse.ts
4
4
  var parse_exports = /* @__PURE__ */ __exportAll({
5
+ AgyStreamIdleTimeoutError: () => AgyStreamIdleTimeoutError,
6
+ DEFAULT_STREAM_IDLE_TIMEOUT_MS: () => DEFAULT_STREAM_IDLE_TIMEOUT_MS,
5
7
  UnmappedFinishReasonError: () => UnmappedFinishReasonError,
6
8
  parseAgySse: () => parseAgySse,
7
- parseSseDataLine: () => parseSseDataLine
9
+ parseSseDataLine: () => parseSseDataLine,
10
+ resolveStreamIdleTimeoutMs: () => resolveStreamIdleTimeoutMs
8
11
  });
9
12
  const CallId = DshLlm.ToolCallId ?? DshLlm.CallId;
10
13
  if (!CallId) throw new Error("dsh-llm: neither ToolCallId nor CallId found");
@@ -56,6 +59,40 @@ var UnmappedFinishReasonError = class extends Error {
56
59
  }
57
60
  };
58
61
  /**
62
+ * Thrown when the SSE body produces no bytes for longer than the idle timeout.
63
+ *
64
+ * The streaming dispatcher deliberately runs with `bodyTimeout: 0` (a reasoning
65
+ * pause must not be killed mid-turn), so a connection that stops producing
66
+ * bytes without ever closing has no transport-level reclaim: the read simply
67
+ * never settles and the turn hangs until the user cancels it. The watchdog
68
+ * exists only for that shape.
69
+ */
70
+ var AgyStreamIdleTimeoutError = class extends Error {
71
+ idleTimeoutMs;
72
+ constructor(idleTimeoutMs) {
73
+ super(idleTimeoutMs >= 1e3 ? `agy stream idle timeout: no data received for ${Math.round(idleTimeoutMs / 1e3)}s` : `agy stream idle timeout: no data received for ${idleTimeoutMs}ms`);
74
+ this.idleTimeoutMs = idleTimeoutMs;
75
+ this.name = "AgyStreamIdleTimeoutError";
76
+ }
77
+ };
78
+ /** Silence budget for one body read before the stream is declared stalled. */
79
+ const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 18e4;
80
+ /**
81
+ * Resolve the idle timeout from `DSH_AGY_IDLE_TIMEOUT_MS` (or the `AGY_IDLE_TIMEOUT_MS` alias).
82
+ * Deliberately generous: measured reasoning gaps on a real tunnel reach ~35s, so the default is
83
+ * ~5x that and only a connection that has genuinely stopped trips it. `0` disables the watchdog;
84
+ * negative or non-numeric values fall back to the default rather than silently disabling it.
85
+ */
86
+ function resolveStreamIdleTimeoutMs() {
87
+ const dsh = process.env.DSH_AGY_IDLE_TIMEOUT_MS?.trim();
88
+ const agy = process.env.AGY_IDLE_TIMEOUT_MS?.trim();
89
+ const raw = dsh && dsh.length > 0 ? dsh : agy && agy.length > 0 ? agy : void 0;
90
+ if (raw === void 0) return DEFAULT_STREAM_IDLE_TIMEOUT_MS;
91
+ const parsed = Number(raw);
92
+ if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_STREAM_IDLE_TIMEOUT_MS;
93
+ return parsed;
94
+ }
95
+ /**
59
96
  * Map the upstream `finishReason` vocabulary onto DSH's. WHITELIST, not
60
97
  * blacklist: the completable reasons map to their kinds and every other
61
98
  * explicit reason throws. `FINISH_REASON_UNSPECIFIED` is granted `stop` — it
@@ -71,8 +108,54 @@ function mapFinishReason(reason) {
71
108
  default: throw new UnmappedFinishReasonError(reason);
72
109
  }
73
110
  }
111
+ /**
112
+ * Await one body read, failing after `timeoutMs` of silence (0 = no watchdog)
113
+ * or as soon as `signal` aborts.
114
+ *
115
+ * The timer measures the GAP between reads, so any byte resets it: a slow but
116
+ * live generation is untouched, while a connection that died without closing —
117
+ * or an upstream that accepted the request and then went away — fails instead
118
+ * of hanging forever. The abort leg exists for the same reason: without it a
119
+ * cancelled turn keeps waiting out the whole idle window, because the read loop
120
+ * only checks `signal.aborted` BETWEEN reads.
121
+ *
122
+ * Each abandonment rejects BEFORE cancelling the reader, and the order is
123
+ * load-bearing (measured on undici 7 and synthetic streams): the rejection is
124
+ * what settles this race — `reader.cancel()` settles the outstanding read only
125
+ * on a later turn — while the cancel reclaims the connection the streaming
126
+ * dispatcher (`bodyTimeout: 0`) would otherwise leave pinned. The abandoned
127
+ * read rejects with the release error once the caller's `finally` drops the
128
+ * lock, and `Promise.race` is what keeps that rejection handled.
129
+ */
130
+ async function readWithIdleTimeout(reader, timeoutMs, signal) {
131
+ const pending = reader.read();
132
+ if (timeoutMs <= 0 && signal === void 0) return pending;
133
+ let timer;
134
+ let onAbort;
135
+ const watchdog = new Promise((_resolve, reject) => {
136
+ const abandon = (error) => {
137
+ reject(error);
138
+ reader.cancel(error).catch(() => {});
139
+ };
140
+ if (signal?.aborted) {
141
+ abandon(new DOMException("aborted", "AbortError"));
142
+ return;
143
+ }
144
+ if (timeoutMs > 0) timer = setTimeout(() => abandon(new AgyStreamIdleTimeoutError(timeoutMs)), timeoutMs);
145
+ if (signal) {
146
+ onAbort = () => abandon(new DOMException("aborted", "AbortError"));
147
+ signal.addEventListener("abort", onAbort, { once: true });
148
+ }
149
+ });
150
+ try {
151
+ return await Promise.race([pending, watchdog]);
152
+ } finally {
153
+ if (timer !== void 0) clearTimeout(timer);
154
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
155
+ }
156
+ }
74
157
  async function* parseAgySse(body, options = {}) {
75
- const { signal } = options;
158
+ const { signal, idleTimeoutMs = resolveStreamIdleTimeoutMs() } = options;
76
159
  const reader = body.getReader();
77
160
  const decoder = new TextDecoder();
78
161
  let buffer = "";
@@ -247,10 +330,27 @@ async function* parseAgySse(body, options = {}) {
247
330
  };
248
331
  try {
249
332
  while (true) {
250
- if (signal?.aborted) throw new DOMException("aborted", "AbortError");
251
- const { done, value } = await reader.read();
252
- if (done) break;
253
- buffer += decoder.decode(value, { stream: true });
333
+ if (signal?.aborted) {
334
+ if (lastUsage !== null) yield {
335
+ type: "usage",
336
+ usage: lastUsage
337
+ };
338
+ throw new DOMException("aborted", "AbortError");
339
+ }
340
+ let read;
341
+ try {
342
+ read = await readWithIdleTimeout(reader, idleTimeoutMs, signal);
343
+ } catch (error) {
344
+ const isTimeout = error instanceof AgyStreamIdleTimeoutError;
345
+ const isAbort = error instanceof DOMException && error.name === "AbortError";
346
+ if ((isTimeout || isAbort) && lastUsage !== null) yield {
347
+ type: "usage",
348
+ usage: lastUsage
349
+ };
350
+ throw error;
351
+ }
352
+ if (read.done) break;
353
+ buffer += decoder.decode(read.value, { stream: true });
254
354
  let newlineIndex;
255
355
  while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
256
356
  const line = buffer.slice(0, newlineIndex);
@@ -287,6 +387,6 @@ async function* parseAgySse(body, options = {}) {
287
387
  }
288
388
  }
289
389
  //#endregion
290
- export { parseAgySse as n, parse_exports as r, UnmappedFinishReasonError as t };
390
+ export { parse_exports as i, UnmappedFinishReasonError as n, parseAgySse as r, AgyStreamIdleTimeoutError as t };
291
391
 
292
- //# sourceMappingURL=parse-D3InNyUx.mjs.map
392
+ //# sourceMappingURL=parse-CeJc7cHo.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-CeJc7cHo.mjs","names":[],"sources":["../src/adapter/parse.ts"],"sourcesContent":["/**\n * Parse Antigravity SSE responses (Gemini-style `candidates[]` events) into the\n * DSH StreamChunk protocol: block-start / text-delta / reasoning-delta /\n * tool-call-delta / block-end / usage / finish.\n *\n * Wire shape: each `data:` payload is a JSON array of candidate objects with\n * `content.parts[]` (text / {thought:true} / functionCall), `usageMetadata`,\n * and `finishReason`. MEASURED (2026-10-03, daily endpoint, 3/3): a normal\n * stream terminates via the candidate `finishReason` alone — the endpoint\n * sends NO `data: [DONE]`; the SSE `[DONE]` convention is accepted but never\n * observed on the wire.\n *\n * Robustness contract: a cut-short stream must FAIL, never masquerade as a\n * completed turn. Three cases throw:\n * - an error body in-band (`data:` prefixed) or BARE (the upstream emits its\n * `{\"error\":{...}}` vocabulary without the prefix right before dropping the\n * connection), including in the final unterminated line;\n * - a clean EOF carrying neither `data: [DONE]` nor any `finishReason`\n * (either signal alone completes the stream);\n * - an explicit `finishReason` this parser does not map\n * ({@link UnmappedFinishReasonError}).\n */\n\nimport type { FinishReason, StreamChunk } from '@deepseek-ai/dsh-llm'\nimport * as DshLlm from '@deepseek-ai/dsh-llm'\n\n// ponytail: DSH 0.0.1-rc.1 exports CallId, 0.1.x renames to ToolCallId — pick whichever exists at runtime\nconst CallId = ((DshLlm as unknown as { ToolCallId?: (id: string) => unknown }).ToolCallId\n ?? (DshLlm as unknown as { CallId?: (id: string) => unknown }).CallId) as (id: string) => never\nif (!CallId) throw new Error('dsh-llm: neither ToolCallId nor CallId found')\n\nexport interface SsePart {\n text?: string\n thought?: boolean\n thoughtSignature?: string\n functionCall?: { id?: string; name?: string; args?: unknown }\n}\n\nexport interface SseCandidate {\n index?: number\n content?: {\n role?: string\n parts?: SsePart[]\n }\n finishReason?: string\n}\n\n/** The upstream error-vocabulary shape, in-band or bare. */\nexport interface SseError {\n code?: number\n status?: string\n message?: string\n}\n\nexport interface SsePayload {\n candidates?: SseCandidate[]\n usageMetadata?: {\n promptTokenCount?: number\n candidatesTokenCount?: number\n totalTokenCount?: number\n cachedContentTokenCount?: number\n }\n error?: SseError\n}\n\n/**\n * Parse one SSE `data:` line; returns null for `[DONE]` or empty lines.\n * Accepts the `{\"response\": {...}}` envelope (daily endpoint wire shape) and\n * the bare array/object shapes older clients emitted.\n */\nexport function parseSseDataLine(line: string): SsePayload | null {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) return null\n const data = trimmed.slice(5).trim()\n if (data === '' || data === '[DONE]') return null\n const parsed = JSON.parse(data) as unknown\n const root = (parsed as { response?: unknown })?.response ?? parsed\n const payload = (Array.isArray(root) ? root[0] : root) as SsePayload | undefined\n return payload ?? null\n}\n\n/**\n * Extract an error body from a line that does not carry the SSE `data:`\n * prefix. The upstream occasionally drops its `{\"error\":{...}}` vocabulary as\n * a bare JSON line right before terminating the stream — the socket closes\n * cleanly and nothing else surfaces the failure. Returns null for anything\n * that is not a JSON object carrying an `error` object; SSE comments and\n * event/id/retry fields stay skipped.\n */\nfunction extractBareJsonError(line: string): SseError | null {\n const trimmed = line.trim()\n if (!trimmed.startsWith('{')) return null\n try {\n const parsed = JSON.parse(trimmed) as { error?: SseError }\n if (parsed.error && typeof parsed.error === 'object') return parsed.error\n } catch {\n // not JSON (or truncated) — nothing to extract\n }\n return null\n}\n\n/**\n * Thrown when the upstream ends a stream with a `finishReason` this parser\n * does not map (SAFETY, RECITATION, MALFORMED_FUNCTION_CALL, ...). A silent\n * default to `stop` presented policy-blocked and malformed-call turns as\n * completed. This is a CONTENT-level verdict, not account health: the adapter\n * reports it as `request-error` (no cooldown, no rotation), never\n * `network-error`.\n */\nexport class UnmappedFinishReasonError extends Error {\n constructor(readonly reason: string) {\n super(`agy upstream ended the stream with unrecognized finishReason: ${reason}`)\n this.name = 'UnmappedFinishReasonError'\n }\n}\n\n/**\n * Thrown when the SSE body produces no bytes for longer than the idle timeout.\n *\n * The streaming dispatcher deliberately runs with `bodyTimeout: 0` (a reasoning\n * pause must not be killed mid-turn), so a connection that stops producing\n * bytes without ever closing has no transport-level reclaim: the read simply\n * never settles and the turn hangs until the user cancels it. The watchdog\n * exists only for that shape.\n */\nexport class AgyStreamIdleTimeoutError extends Error {\n constructor(readonly idleTimeoutMs: number) {\n super(idleTimeoutMs >= 1000\n ? `agy stream idle timeout: no data received for ${Math.round(idleTimeoutMs / 1000)}s`\n : `agy stream idle timeout: no data received for ${idleTimeoutMs}ms`)\n this.name = 'AgyStreamIdleTimeoutError'\n }\n}\n\n/** Silence budget for one body read before the stream is declared stalled. */\nexport const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 180_000\n\n/**\n * Resolve the idle timeout from `DSH_AGY_IDLE_TIMEOUT_MS` (or the `AGY_IDLE_TIMEOUT_MS` alias).\n * Deliberately generous: measured reasoning gaps on a real tunnel reach ~35s, so the default is\n * ~5x that and only a connection that has genuinely stopped trips it. `0` disables the watchdog;\n * negative or non-numeric values fall back to the default rather than silently disabling it.\n */\nexport function resolveStreamIdleTimeoutMs(): number {\n const dsh = process.env.DSH_AGY_IDLE_TIMEOUT_MS?.trim()\n const agy = process.env.AGY_IDLE_TIMEOUT_MS?.trim()\n const raw = dsh && dsh.length > 0 ? dsh : agy && agy.length > 0 ? agy : undefined\n if (raw === undefined) return DEFAULT_STREAM_IDLE_TIMEOUT_MS\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_STREAM_IDLE_TIMEOUT_MS\n return parsed\n}\n\n/**\n * Map the upstream `finishReason` vocabulary onto DSH's. WHITELIST, not\n * blacklist: the completable reasons map to their kinds and every other\n * explicit reason throws. `FINISH_REASON_UNSPECIFIED` is granted `stop` — it\n * carries no information, exactly like the absent field.\n */\nfunction mapFinishReason(reason: string): FinishReason {\n switch (reason) {\n case 'MAX_TOKENS':\n return { kind: 'max-tokens' }\n case 'STOP':\n case 'FINISH_REASON_UNSPECIFIED':\n return { kind: 'stop' }\n case 'TOOL_CALLS':\n case 'FUNCTION_CALL':\n return { kind: 'tool-calls' }\n default:\n throw new UnmappedFinishReasonError(reason)\n }\n}\n\ntype StreamReadResult = Awaited<ReturnType<ReadableStreamDefaultReader<Uint8Array>['read']>>\n\n/**\n * Await one body read, failing after `timeoutMs` of silence (0 = no watchdog)\n * or as soon as `signal` aborts.\n *\n * The timer measures the GAP between reads, so any byte resets it: a slow but\n * live generation is untouched, while a connection that died without closing —\n * or an upstream that accepted the request and then went away — fails instead\n * of hanging forever. The abort leg exists for the same reason: without it a\n * cancelled turn keeps waiting out the whole idle window, because the read loop\n * only checks `signal.aborted` BETWEEN reads.\n *\n * Each abandonment rejects BEFORE cancelling the reader, and the order is\n * load-bearing (measured on undici 7 and synthetic streams): the rejection is\n * what settles this race — `reader.cancel()` settles the outstanding read only\n * on a later turn — while the cancel reclaims the connection the streaming\n * dispatcher (`bodyTimeout: 0`) would otherwise leave pinned. The abandoned\n * read rejects with the release error once the caller's `finally` drops the\n * lock, and `Promise.race` is what keeps that rejection handled.\n */\nasync function readWithIdleTimeout(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<StreamReadResult> {\n const pending = reader.read()\n if (timeoutMs <= 0 && signal === undefined) return pending\n let timer: ReturnType<typeof setTimeout> | undefined\n let onAbort: (() => void) | undefined\n const watchdog = new Promise<never>((_resolve, reject) => {\n const abandon = (error: Error): void => {\n reject(error)\n void reader.cancel(error).catch(() => {})\n }\n if (signal?.aborted) {\n abandon(new DOMException('aborted', 'AbortError'))\n return\n }\n if (timeoutMs > 0) {\n timer = setTimeout(() => abandon(new AgyStreamIdleTimeoutError(timeoutMs)), timeoutMs)\n }\n if (signal) {\n onAbort = () => abandon(new DOMException('aborted', 'AbortError'))\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n try {\n return await Promise.race([pending, watchdog])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n if (signal && onAbort) signal.removeEventListener('abort', onAbort)\n }\n}\n\n/**\n * Consume an SSE text stream and yield StreamChunks. One accumulating block is\n * kept open at a time; tool-call argument deltas accumulate until a different\n * part kind, usage metadata, or stream end closes it.\n */\nexport interface ParseAgySseOptions {\n signal?: AbortSignal\n /**\n * Milliseconds of silence on the body before the read is abandoned. Defaults\n * to `resolveStreamIdleTimeoutMs()`; 0 disables the watchdog.\n */\n idleTimeoutMs?: number\n /**\n * Invoked when a functionCall part carries a sibling thoughtSignature, and\n * when a thought part carries one, keyed by the functionCall id (or the\n * thought signature is ignored unless paired). The adapter persists these\n * for replay on the next turn (see signature-cache.ts).\n */\n onToolSignature?(toolCallId: string, signature: string): void\n}\n\nexport async function* parseAgySse(\n body: ReadableStream<Uint8Array>,\n options: ParseAgySseOptions = {},\n): AsyncGenerator<StreamChunk> {\n const { signal, idleTimeoutMs = resolveStreamIdleTimeoutMs() } = options\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let blockIndex = 0\n let finishReason: FinishReason = { kind: 'stop' }\n let sawDone = false\n let sawFinishReason = false\n let sawUsage = false\n let lastUsage: { inputTokens: number; outputTokens: number; cacheReadTokens?: number } | null = null\n\n interface OpenBlock {\n kind: 'text' | 'reasoning' | 'tool-call'\n id?: string\n name?: string\n arguments: string\n text: string\n }\n let open: OpenBlock | null = null\n\n const closeBlock = (): StreamChunk | null => {\n if (!open) return null\n const block: StreamChunk = open.kind === 'tool-call'\n ? {\n type: 'block-end',\n index: blockIndex,\n block: {\n type: 'tool-call',\n id: CallId(open.id ?? `call-${blockIndex}`),\n name: open.name ?? '',\n arguments: open.arguments,\n },\n }\n : {\n type: 'block-end',\n index: blockIndex,\n block: { type: open.kind, text: open.text },\n }\n open = null\n blockIndex += 1\n return block\n }\n\n /**\n * Ensure a block of the given kind is open, switching when needed.\n * Returns chunks to yield (a closed block's end, then the new block's start).\n * Callers MUST yield everything returned — dropping the end silently corrupts\n * the block stream for DSH (verified: multi-tool turns and text→tool\n * transitions lost their block-end).\n */\n const ensureBlock = (kind: OpenBlock['kind'], meta: { id?: string; name?: string } = {}): StreamChunk[] => {\n const out: StreamChunk[] = []\n if (open && open.kind !== kind) {\n const end = closeBlock()\n if (end) out.push(end)\n }\n if (!open) {\n open = { kind, arguments: '', text: '', id: meta.id, name: meta.name }\n const blockType = kind === 'tool-call' ? 'tool-call' : kind\n out.push({ type: 'block-start', index: blockIndex, blockType })\n }\n return out\n }\n\n /**\n * Handle one wire line and return the chunks to yield. Shared by the read\n * loop and the EOF residual so a final line without a trailing newline is\n * processed exactly like a newline-terminated one — dropping it would lose\n * content AND, since the completeness guard landed, falsify a premature\n * termination on streams that end bare.\n */\n const handleLine = (line: string): StreamChunk[] => {\n const out: StreamChunk[] = []\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) {\n const bare = extractBareJsonError(trimmed)\n if (bare) {\n const message = bare.message ?? bare.status ?? 'upstream error'\n throw new Error(`agy stream error (${bare.code ?? 'unknown'}): ${message}`)\n }\n return out\n }\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') {\n sawDone = true\n return out\n }\n const payload = parseSseDataLine(trimmed)\n if (!payload) return out\n if (payload.error) {\n const message = payload.error.message ?? payload.error.status ?? 'upstream error'\n throw new Error(`agy stream error (${payload.error.code ?? 'unknown'}): ${message}`)\n }\n if (payload.usageMetadata) {\n // The upstream sends usageMetadata on EVERY SSE event (cumulative).\n // Do NOT close the block here: closing per event would split one\n // continuous text stream into a block per chunk (frontend renders\n // block boundaries as line breaks). Stash the last (full) totals\n // and emit one usage chunk at stream end.\n //\n // Stashed BEFORE the candidates loop below, because that loop is where an\n // unmapped `finishReason` throws: the totals ride on the same event that\n // ends the stream, and `drainLine` can only flush a stash that exists.\n sawUsage = true\n // DSH TokenUsage buckets are DISJOINT: inputTokens must be the\n // uncached portion only; cache reads are reported separately.\n // Reporting the full promptTokenCount here double-counts cached\n // tokens in the stats line's cache-hit percentage (it divides by\n // uncached + cacheRead + cacheWrite).\n const promptTokens = payload.usageMetadata.promptTokenCount ?? 0\n const cachedTokens = payload.usageMetadata.cachedContentTokenCount ?? 0\n lastUsage = {\n inputTokens: Math.max(0, promptTokens - cachedTokens),\n outputTokens: payload.usageMetadata.candidatesTokenCount ?? 0,\n cacheReadTokens: cachedTokens,\n }\n }\n for (const candidate of payload.candidates ?? []) {\n if (candidate.finishReason) {\n sawFinishReason = true\n finishReason = mapFinishReason(candidate.finishReason)\n }\n for (const part of candidate.content?.parts ?? []) {\n // A zero-length text part is a routine wire shape (the daily endpoint\n // ends turns with `{thoughtSignature, text: \"\"}`) — fabricating an\n // empty block from it poisoned session history for stricter\n // downstream serializers (issue #77). Skip it without touching an\n // open block, so \"Hel\" + \"\" + \"lo\" stays one block; `!part.functionCall`\n // keeps a text+functionCall part on its current branch. Signatures\n // ride functionCall parts only, so the skip loses nothing.\n if (part.text === '' && !part.functionCall) continue\n if (part.text !== undefined && part.thought !== true) {\n out.push(...ensureBlock('text'))\n open!.text += part.text\n out.push({ type: 'text-delta', index: blockIndex, text: part.text })\n } else if (part.text !== undefined && part.thought === true) {\n out.push(...ensureBlock('reasoning'))\n open!.text += part.text\n out.push({ type: 'reasoning-delta', index: blockIndex, text: part.text })\n } else if (part.functionCall) {\n // Use the upstream functionCall id when present so the signature\n // captured on this part can be replayed for the same id next turn.\n const upstreamId = part.functionCall.id || String(blockIndex)\n // Each functionCall part is an ATOMIC block: a stream can carry\n // several functionCall parts in one turn (multi-tool responses),\n // and they share kind \"tool-call\" — ensureBlock alone would not\n // switch between them, concatenating their args JSON into one\n // invalid string. Close any open block (yielding its end) first.\n if (open) {\n const end = closeBlock()\n if (end) out.push(end)\n }\n const start = ensureBlock('tool-call', {\n id: upstreamId,\n name: part.functionCall.name,\n })\n out.push(...start)\n if (part.thoughtSignature) {\n options.onToolSignature?.(upstreamId, part.thoughtSignature)\n }\n const argsJson = typeof part.functionCall.args === 'string'\n ? part.functionCall.args\n : JSON.stringify(part.functionCall.args ?? {})\n open!.arguments += argsJson\n out.push({\n type: 'tool-call-delta',\n index: blockIndex,\n id: CallId(open!.id ?? ''),\n name: open!.name,\n argumentsDelta: argsJson,\n })\n }\n }\n }\n return out\n }\n\n /**\n * Yield one line's chunks, but flush the stashed usage first if the line\n * aborts the stream.\n *\n * An unmapped `finishReason` throws from inside `handleLine`, i.e. BEFORE the\n * usageMetadata stash on that same line AND before the end-of-stream usage\n * chunk below. Without this the caller sees no usage at all for an attempt\n * that the upstream really did bill — and the adapter's `MALFORMED_FUNCTION_CALL`\n * re-sample discards exactly such an attempt, so its tokens would silently\n * never reach the ledger (AGENTS.md: the ledger accumulates per attempt).\n */\n const drainLine = function* (line: string): Generator<StreamChunk> {\n try {\n yield* handleLine(line)\n } catch (error) {\n if (error instanceof UnmappedFinishReasonError && lastUsage !== null) {\n yield { type: 'usage', usage: lastUsage }\n }\n throw error\n }\n }\n\n try {\n while (true) {\n if (signal?.aborted) {\n if (lastUsage !== null) {\n yield { type: 'usage', usage: lastUsage }\n }\n throw new DOMException('aborted', 'AbortError')\n }\n let read: StreamReadResult\n try {\n read = await readWithIdleTimeout(reader, idleTimeoutMs, signal)\n } catch (error) {\n // Same rescue `drainLine` performs for an unmapped finishReason, for the\n // same reason: this attempt really did consume quota (the ledger\n // accumulates per attempt), and the totals the upstream already\n // reported would otherwise be dropped with the stalled or aborted stream.\n const isTimeout = error instanceof AgyStreamIdleTimeoutError\n const isAbort = error instanceof DOMException && error.name === 'AbortError'\n if ((isTimeout || isAbort) && lastUsage !== null) {\n yield { type: 'usage', usage: lastUsage }\n }\n throw error\n }\n if (read.done) break\n buffer += decoder.decode(read.value, { stream: true })\n let newlineIndex: number\n while ((newlineIndex = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newlineIndex)\n buffer = buffer.slice(newlineIndex + 1)\n for (const chunk of drainLine(line)) yield chunk\n }\n }\n // Flush the decoder and process a final line that arrived without its\n // trailing newline: content, usage, finishReason, `[DONE]`, and bare\n // error bodies all ride it.\n buffer += decoder.decode()\n if (buffer.trim() !== '') {\n const line = buffer\n buffer = ''\n for (const chunk of drainLine(line)) yield chunk\n }\n // Completeness guard: a CLEAN close carrying neither `[DONE]` nor any\n // `finishReason` is a cut-short stream, not a completed turn — yielding\n // the default `stop` here presented truncated replies as successful\n // (issue #85). Either signal alone completes the stream.\n if (!sawDone && !sawFinishReason) {\n throw new Error('agy stream terminated prematurely without [DONE] or finishReason')\n }\n const closed = closeBlock()\n if (closed) yield closed\n if (lastUsage) {\n yield { type: 'usage', usage: lastUsage }\n } else if (sawUsage) {\n yield { type: 'usage', usage: { inputTokens: 0, outputTokens: 0 } }\n }\n yield { type: 'finish', reason: finishReason }\n } finally {\n reader.releaseLock()\n }\n}\n"],"mappings":"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"}