dsh-agy 0.2.9 → 0.2.11

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 (36) hide show
  1. package/README.md +1 -0
  2. package/lib/{accounts-Um8N9I56.mjs → accounts-C0Oj3tQA.mjs} +260 -233
  3. package/lib/accounts-C0Oj3tQA.mjs.map +1 -0
  4. package/lib/auth-DPPVFwio.mjs +32 -0
  5. package/lib/auth-DPPVFwio.mjs.map +1 -0
  6. package/lib/{catalog-DVHPJjfc.mjs → catalog-bfGIyoba.mjs} +3 -3
  7. package/lib/catalog-bfGIyoba.mjs.map +1 -0
  8. package/lib/cli/index.d.mts.map +1 -1
  9. package/lib/cli/index.mjs +8 -7
  10. package/lib/cli/index.mjs.map +1 -1
  11. package/lib/constants-BFKYjOmh.mjs +173 -0
  12. package/lib/constants-BFKYjOmh.mjs.map +1 -0
  13. package/lib/{exchange-Bdql4kf3.mjs → exchange-dQm2mjJ5.mjs} +15 -14
  14. package/lib/exchange-dQm2mjJ5.mjs.map +1 -0
  15. package/lib/identity-DsftHTJC.mjs +35 -0
  16. package/lib/identity-DsftHTJC.mjs.map +1 -0
  17. package/lib/{import-BkqCMX7m.mjs → import-D7R0GpRr.mjs} +14 -12
  18. package/lib/import-D7R0GpRr.mjs.map +1 -0
  19. package/lib/index.mjs +2 -2
  20. package/lib/{models-F-1cWO0f.mjs → models-ClGTQBLY.mjs} +4 -4
  21. package/lib/{models-F-1cWO0f.mjs.map → models-ClGTQBLY.mjs.map} +1 -1
  22. package/lib/{plugin-common-Di4n-1eR.mjs → plugin-common-DtF_PyxH.mjs} +16 -129
  23. package/lib/plugin-common-DtF_PyxH.mjs.map +1 -0
  24. package/lib/{proxy-DQPIwUov.mjs → proxy-DLvX8yxv.mjs} +63 -11
  25. package/lib/proxy-DLvX8yxv.mjs.map +1 -0
  26. package/lib/{translate-CE9tNXiV.mjs → translate-pdeVGFpT.mjs} +29 -4
  27. package/lib/{translate-CE9tNXiV.mjs.map → translate-pdeVGFpT.mjs.map} +1 -1
  28. package/lib/web/plugin.mjs +9 -9
  29. package/lib/web/plugin.mjs.map +1 -1
  30. package/package.json +4 -2
  31. package/lib/accounts-Um8N9I56.mjs.map +0 -1
  32. package/lib/catalog-DVHPJjfc.mjs.map +0 -1
  33. package/lib/exchange-Bdql4kf3.mjs.map +0 -1
  34. package/lib/import-BkqCMX7m.mjs.map +0 -1
  35. package/lib/plugin-common-Di4n-1eR.mjs.map +0 -1
  36. package/lib/proxy-DQPIwUov.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
- import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, S as OAUTH_AUTHORIZE_URL, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./accounts-Um8N9I56.mjs";
2
- import { i as proxiedFetch, r as normalizeProxyUrl } from "./proxy-DQPIwUov.mjs";
3
- import { i as generatePkcePair, r as encodeState } from "./exchange-Bdql4kf3.mjs";
1
+ import { d as getAgyBootstrapClientMetadata, f as getAgyBootstrapUserAgent, i as AGY_SCOPES, o as OAUTH_AUTHORIZE_URL, p as resolveAgyClientCredentials, r as AGY_ENDPOINT_FALLBACKS } from "./constants-BFKYjOmh.mjs";
2
+ import { a as proxiedFetch, i as normalizeProxyUrl } from "./proxy-DLvX8yxv.mjs";
3
+ import { i as generatePkcePair, r as encodeState } from "./exchange-dQm2mjJ5.mjs";
4
4
  import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  //#region src/oauth/authorize.ts
@@ -88,8 +88,10 @@ function parseAndValidateAgyToken(raw) {
88
88
  clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId)
89
89
  };
90
90
  }
91
- /** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */
92
- async function enrichWithAntigravityBackend(parsed) {
91
+ /** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed.
92
+ * `proxyUrl` is the account proxy being imported: enrichment talks to Google as
93
+ * this account, so it must take the same egress route the account will use. */
94
+ async function enrichWithAntigravityBackend(parsed, options = {}) {
93
95
  let email = null;
94
96
  let projectId = null;
95
97
  const controller = new AbortController();
@@ -98,7 +100,7 @@ async function enrichWithAntigravityBackend(parsed) {
98
100
  const res = await proxiedFetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
99
101
  headers: { Authorization: `Bearer ${parsed.accessToken}` },
100
102
  signal: controller.signal
101
- });
103
+ }, { proxyUrl: options.proxyUrl });
102
104
  if (res.ok) email = toNonEmptyString(toRecord(await res.json()).email);
103
105
  } catch {} finally {
104
106
  clearTimeout(timer);
@@ -118,7 +120,7 @@ async function enrichWithAntigravityBackend(parsed) {
118
120
  headers,
119
121
  body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
120
122
  signal: loadController.signal
121
- });
123
+ }, { proxyUrl: options.proxyUrl });
122
124
  if (!res.ok) continue;
123
125
  const project = toRecord(await res.json()).cloudaicompanionProject;
124
126
  projectId = (typeof project === "string" ? toNonEmptyString(project) : null) ?? toNonEmptyString(toRecord(project).id);
@@ -135,15 +137,15 @@ async function enrichWithAntigravityBackend(parsed) {
135
137
  };
136
138
  }
137
139
  /** Parse either a raw token document or a paste blob into an enriched account. */
138
- async function parseImportSource(source, kind) {
140
+ async function parseImportSource(source, kind, options = {}) {
139
141
  if (kind === "blob") {
140
142
  if (typeof source !== "string") throw new AgyAuthFileError("blob must be a string", 400, "invalid_blob");
141
143
  return enrichWithAntigravityBackend(parseAndValidateAgyToken({
142
144
  token: decodeCredentialBlob(source).tokens,
143
145
  auth_method: "paste-blob"
144
- }));
146
+ }), options);
145
147
  }
146
- return enrichWithAntigravityBackend(parseAndValidateAgyToken(source));
148
+ return enrichWithAntigravityBackend(parseAndValidateAgyToken(source), options);
147
149
  }
148
150
  /** Batch-import many sources (CLI multi-file / web multi-line paste). Each item
149
151
  * is independent: a failure is collected, the rest still import. */
@@ -154,7 +156,7 @@ async function importManySources(items, store, options = {}) {
154
156
  errors: []
155
157
  };
156
158
  for (const item of items) try {
157
- const { created } = await upsertImportedAccount(store, await parseImportSource(item.source, item.kind), options);
159
+ const { created } = await upsertImportedAccount(store, await parseImportSource(item.source, item.kind, { ...options.proxy ? { proxyUrl: normalizeProxyUrl(options.proxy) } : {} }), options);
158
160
  if (created) result.imported++;
159
161
  else result.replaced++;
160
162
  } catch (error) {
@@ -223,4 +225,4 @@ async function upsertImportedAccount(store, enriched, options = {}) {
223
225
  //#endregion
224
226
  export { upsertImportedAccount as n, authorizeAntigravity as r, importManySources as t };
225
227
 
226
- //# sourceMappingURL=import-BkqCMX7m.mjs.map
228
+ //# sourceMappingURL=import-D7R0GpRr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-D7R0GpRr.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 { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } 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 'Client-Metadata': getAgyBootstrapClientMetadata(),\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: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\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 { d as isAgyDisabled } from "./accounts-Um8N9I56.mjs";
2
- import { t as createAgyRuntime } from "./plugin-common-Di4n-1eR.mjs";
1
+ import { d as isAgyDisabled } from "./accounts-C0Oj3tQA.mjs";
2
+ import { t as createAgyRuntime } from "./plugin-common-DtF_PyxH.mjs";
3
3
  //#region src/index.ts
4
4
  const name = "dsh-agy";
5
5
  const inject = ["llm"];
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./accounts-Um8N9I56.mjs";
3
- import { i as proxiedFetch } from "./proxy-DQPIwUov.mjs";
4
- import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-DVHPJjfc.mjs";
2
+ import { f as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-BFKYjOmh.mjs";
3
+ import { a as proxiedFetch } from "./proxy-DLvX8yxv.mjs";
4
+ import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-bfGIyoba.mjs";
5
5
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
6
6
  //#region src/adapter/models.ts
7
7
  /**
@@ -204,4 +204,4 @@ function resolveAgyModel(provider, model) {
204
204
  //#endregion
205
205
  export { resolveAgyModel as i, listAgyModels as n, models_exports as r, catalogModelList as t };
206
206
 
207
- //# sourceMappingURL=models-F-1cWO0f.mjs.map
207
+ //# sourceMappingURL=models-ClGTQBLY.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"models-F-1cWO0f.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/** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */\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 defaultEffort: ReasoningEffortId('medium'),\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/** 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-ClGTQBLY.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/** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */\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 defaultEffort: ReasoningEffortId('medium'),\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/** 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":";;;;;;;;;;;;;;;;;;;;AAcA,MAAM,kBAAyC,OAAO,OAAO;CAC3D,SAAS,OAAO,OAAO;EACrB;GAAE,IAAI,kBAAkB,KAAK;GAAG,MAAM;EAAM;EAC5C;GAAE,IAAI,kBAAkB,QAAQ;GAAG,MAAM;EAAS;EAClD;GAAE,IAAI,kBAAkB,MAAM;GAAG,MAAM;EAAO;CAChD,CAAU;CACV,eAAe,kBAAkB,QAAQ;AAC3C,CAAU;;;;;;;;AASV,MAAM,uBAAuB,CAAC,QAAQ,OAAO;AAC7C,MAAM,2BAA2B,CAAC,MAAM;AAExC,SAAS,mBAAmB,MAAiE;CAC3F,OAAO,CAAC,IAAI,OAAO,KAAK,mBAAmB,OAAO,QAAQ,uBAAuB,wBAAwB;AAC3G;;AAqCA,MAAM,qBAAqB;CAAC;CAAe;CAA2B;AAA4B;;AAGlG,SAAS,YAAY,OAA0B;CAC7C,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,OAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,CAAC;AAC3F;;;;;;;;AASA,SAAS,mBAAmB,SAAwC;CAClE,MAAM,6BAAa,IAAI,IAAY;CACnC,IAAI,OAAO,QAAQ,wBAAwB,YAAY,QAAQ,oBAAoB,SAAS,GAC1F,WAAW,IAAI,QAAQ,mBAAmB;CAE5C,KAAK,MAAM,QAAQ,MAAM,QAAQ,QAAQ,eAAe,IAAI,QAAQ,kBAAkB,CAAC,GACrF,KAAK,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,IAAI,KAAK,SAAS,CAAC,GAC/D,KAAK,MAAM,MAAM,YAAY,OAAO,QAAQ,GAAG,WAAW,IAAI,EAAE;CAGpE,MAAM,SAAS,QAAQ;CACvB,IAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC/D,KAAK,MAAM,UAAU,OAAO,OAAO,MAAM,GACvC,KAAK,MAAM,MAAM,YAAY,MAAM,GAAG,WAAW,IAAI,EAAE;CAG3D,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAS,oBAAoB,SAAwC;CACnE,MAAM,SAAS,QAAQ,UAAU,CAAC;CAClC,MAAM,aAAa,mBAAmB,OAAO;CAC7C,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,OAAO,oBAChB,KAAK,MAAM,MAAM,YAAY,QAAQ,IAAI,GACvC,IAAI,CAAC,WAAW,IAAI,EAAE,GAAG,OAAO,IAAI,EAAE;CAG1C,MAAM,aAAa,IAAI,IAAI,MAAM;CACjC,MAAM,aAAa,QAAQ;CAC3B,IAAI,cAAc,OAAO,eAAe,YAAY,CAAC,MAAM,QAAQ,UAAU,GAC3E,KAAK,MAAM,CAAC,WAAW,SAAS,OAAO,QAAQ,UAAU,GAAG;EAC1D,IAAI,WAAW,IAAI,SAAS,GAAG;EAC/B,MAAM,cAAc,MAAM;EAC1B,IAAI,OAAO,gBAAgB,YAAY,YAAY,WAAW,KAAK,gBAAgB,WAAW;EAC9F,IAAI,CAAC,OAAO,OAAO,QAAQ,WAAW,GAAG;EACzC,IAAI,CAAC,sBAAsB,WAAW,KAAK,WAAW,IAAI,WAAW,GAAG;EACxE,OAAO,IAAI,SAAS;CACtB;CAEF,OAAO;AACT;;AAGA,eAAsB,qBACpB,aACA,WACA,YAA0B,cACC;CAC3B,IAAI,YAAqB;CACzB,MAAM,OAAO,YAAY,EAAE,SAAS,UAAU,IAAI,CAAC;CACnD,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,aAAa,mCAAmC;GAClF,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;IAChB,cAAc,yBAAyB;GACzC;GACA,MAAM,KAAK,UAAU,IAAI;EAC3B,CAAC;EACD,IAAI,SAAS,IACX,OAAQ,MAAM,SAAS,KAAK;EAE9B,4BAAY,IAAI,MAAM,wBAAwB,SAAS,OAAO,MAAM,cAAc;CACpF,SAAS,OAAO;EACd,YAAY;CACd;CAEF,MAAM,qBAAqB,QAAQ,4BAAY,IAAI,MAAM,4CAA4C;AACvG;;AAGA,SAAgB,kBAAkB,SAA2C;CAC3E,MAAM,UAA0B,CAAC;CACjC,MAAM,SAAS,oBAAoB,OAAO;CAC1C,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,QAAQ,UAAU,CAAC,CAAC,GAAG;EAC9D,IAAI,CAAC,sBAAsB,EAAE,KAAK,OAAO,IAAI,EAAE,GAAG;EAClD,MAAM,OAAO,aAAa,EAAE;EAE5B,MAAM,eADiB,MAAM,eAAe,MAAM,gBAAgB,KAAK,MAAM,cAAc,KAAA,MACrD,MAAM,QAAQ,MAAM,eAAe,MAAM,aAAa;EAC5F,QAAQ,KAAK;GACX,UAAA;GACA;GACA,MAAM;GACN,iBAAiB,mBAAmB,IAAI;GACxC,GAAI,OAAO,EAAE,SAAS,EAAE,eAAe,KAAK,cAAc,EAAE,IAAI,CAAC;EACnE,CAAC;CACH;CACA,OAAO;AACT;;AAGA,SAAgB,mBAAmC;CACjD,OAAO,kBAAkB,KAAK,WAAW;EACvC,UAAA;EACA,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,iBAAiB,mBAAmB,KAAK;EACzC,SAAS,EAAE,eAAe,MAAM,cAAc;CAChD,EAAE;AACJ;;AAGA,eAAsB,cACpB,aACA,WACA,YAA0B,cACQ;CAClC,IAAI,CAAC,aAAa,OAAO,iBAAiB;CAC1C,IAAI;EAEF,MAAM,SAAS,kBAAkB,MADX,qBAAqB,aAAa,WAAW,SAAS,CACpC;EACxC,OAAO,OAAO,SAAS,IAAI,SAAS,iBAAiB;CACvD,QAAQ;EACN,OAAO,iBAAiB;CAC1B;AACF;;AAGA,SAAgB,gBAAgB,UAAkB,OAAqC;CACrF,MAAM,OAAO,aAAa,KAAK;CAC/B,IAAI,qBAAqB,KAAK,GAC5B,OAAO;EACL;EACA,IAAI;EACJ,MAAM,MAAM,QAAQ;EACpB,iBAAiB,mBAAmB,IAAI;EACxC,SAAS,EAAE,eAAe,MAAM,iBAAiB,QAAQ;EACzD,kBAAkB,MAAM,mBAAmB;EAE3C,WAAW;GAAE,GAAG;GAAiB,SAAS,CAAC,GAAG,gBAAgB,OAAO;EAAE;CACzE;CAEF,OAAO;EACL;EACA,IAAI;EACJ,MAAM,MAAM,QAAQ;EACpB,iBAAiB,mBAAmB,IAAI;EACxC,GAAI,OAAO;GAAE,SAAS,EAAE,eAAe,KAAK,cAAc;GAAG,kBAAkB,KAAK;EAAgB,IAAI,CAAC;CAC3G;AACF"}
@@ -1,130 +1,12 @@
1
- import { A as AgyPoolBlockedError, T as fetchAgyFirstOk, _ as deriveAntigravitySessionId, a as deriveKey, c as resolveDshHome, h as resolveAntigravityVersion, i as createAesGcmCodec, k as AgyAuthError, l as resolveMasterKeyCodec, o as loadMasterKey, r as MASTER_KEY_REF, s as persistMasterKey, t as JsonAccountStore, u as AgySessionManager, v as generateAntigravityRequestId } from "./accounts-Um8N9I56.mjs";
2
- import { n as isProxyUnreachableError } from "./proxy-DQPIwUov.mjs";
3
- import { i as setThoughtSignature, r as resolveMultimodalFiles, t as toAgyRequestBody } from "./translate-CE9tNXiV.mjs";
1
+ import { h as AgyPoolBlockedError, m as AgyAuthError, u as fetchAgyFirstOk } from "./constants-BFKYjOmh.mjs";
2
+ import { t as accountFetch } from "./proxy-DLvX8yxv.mjs";
3
+ import { _ as classifyHttpError, a as deriveKey, c as resolveDshHome, g as classifyFetchError, i as createAesGcmCodec, l as resolveMasterKeyCodec, m as resolveAntigravityVersion, o as loadMasterKey, r as MASTER_KEY_REF, s as persistMasterKey, t as JsonAccountStore, u as AgySessionManager, v as describeFetchError } from "./accounts-C0Oj3tQA.mjs";
4
+ import { n as generateAntigravityRequestId, t as deriveAntigravitySessionId } from "./identity-DsftHTJC.mjs";
5
+ import { i as setThoughtSignature, r as resolveMultimodalFiles, t as toAgyRequestBody } from "./translate-pdeVGFpT.mjs";
4
6
  import { t as parseAgySse } from "./parse-Bs4rPkHA.mjs";
5
- import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-F-1cWO0f.mjs";
7
+ import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-ClGTQBLY.mjs";
6
8
  import { randomBytes } from "node:crypto";
7
9
  import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
8
- //#region src/runtime/classify.ts
9
- const QUOTA_EXHAUSTED_KEYWORDS = [
10
- "quota_exhausted",
11
- "quota exhausted",
12
- "quota reached",
13
- "enable overages",
14
- "individual quota"
15
- ];
16
- /** Classify a 429 body into the four upstream categories. */
17
- function classifyRateLimit(bodyText, retryAfterMs) {
18
- const text = (bodyText ?? "").toLowerCase();
19
- if (QUOTA_EXHAUSTED_KEYWORDS.some((keyword) => text.includes(keyword))) return "quota_exhausted";
20
- if (retryAfterMs !== void 0 && retryAfterMs < 3e3) return "soft_rate_limit";
21
- if (retryAfterMs !== void 0) return "rate_limited";
22
- return text.includes("quota") || text.includes("resource_exhausted") ? "quota_exhausted" : "unknown";
23
- }
24
- const RATE_LIMIT_RESET_FIELDS = [
25
- "resetTime",
26
- "reset_time",
27
- "resetAt",
28
- "quotaResetTime"
29
- ];
30
- function extractResetTime(bodyText) {
31
- if (!bodyText) return void 0;
32
- try {
33
- const data = JSON.parse(bodyText);
34
- for (const field of RATE_LIMIT_RESET_FIELDS) {
35
- const value = data[field];
36
- if (typeof value === "string" && value) return value;
37
- if (typeof value === "number" && Number.isFinite(value)) return value > 0xe8d4a51000 ? new Date(value).toISOString() : new Date(Date.now() + value * 1e3).toISOString();
38
- }
39
- const quotaInfo = data.quotaInfo;
40
- if (quotaInfo && typeof quotaInfo.resetTime === "string") return quotaInfo.resetTime;
41
- } catch {}
42
- }
43
- function parseRetryAfter(header) {
44
- if (!header) return void 0;
45
- const seconds = Number(header);
46
- if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
47
- const date = Date.parse(header);
48
- if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
49
- }
50
- /** Classify a completed HTTP response (non-2xx). */
51
- function classifyHttpError(status, headers, bodyText) {
52
- const retryAfterMs = parseRetryAfter(headers.get("retry-after"));
53
- const resetTime = extractResetTime(bodyText);
54
- if (status === 429) return {
55
- kind: "rate-limit",
56
- rateLimitCategory: classifyRateLimit(bodyText, retryAfterMs),
57
- status,
58
- retryAfterMs,
59
- resetTime,
60
- message: bodyText ? bodyText.slice(0, 200) : void 0
61
- };
62
- if (status === 401) return {
63
- kind: "auth-failure",
64
- status,
65
- message: bodyText ? bodyText.slice(0, 200) : void 0
66
- };
67
- if (status === 403) {
68
- const category = classifyRateLimit(bodyText, void 0);
69
- if (category === "quota_exhausted") return {
70
- kind: "rate-limit",
71
- rateLimitCategory: category,
72
- status,
73
- message: bodyText ? bodyText.slice(0, 200) : void 0
74
- };
75
- return {
76
- kind: "auth-failure",
77
- status,
78
- message: bodyText ? bodyText.slice(0, 200) : void 0
79
- };
80
- }
81
- if (status === 404) return {
82
- kind: "transient",
83
- status,
84
- message: bodyText ? bodyText.slice(0, 200) : void 0
85
- };
86
- if (status >= 500) return {
87
- kind: "transient",
88
- status,
89
- retryAfterMs,
90
- message: bodyText ? bodyText.slice(0, 200) : void 0
91
- };
92
- if (status === 400) {
93
- const text = (bodyText ?? "").toLowerCase();
94
- if (text.includes("context") && (text.includes("overflow") || text.includes("too long") || text.includes("exceeded")) || text.includes("model") && (text.includes("not found") || text.includes("unavailable") || text.includes("not supported"))) return {
95
- kind: "transient",
96
- status,
97
- message: bodyText ? bodyText.slice(0, 200) : void 0
98
- };
99
- return {
100
- kind: "request-error",
101
- status,
102
- message: bodyText ? bodyText.slice(0, 200) : void 0
103
- };
104
- }
105
- return {
106
- kind: "transient",
107
- status,
108
- message: bodyText ? bodyText.slice(0, 200) : void 0
109
- };
110
- }
111
- /** Classify a fetch-level failure (DNS, refused, timeout, abort). */
112
- function classifyFetchError(error) {
113
- const message = error instanceof Error ? error.message : String(error);
114
- if (error instanceof DOMException && error.name === "AbortError") return {
115
- kind: "network-error",
116
- message
117
- };
118
- if (isProxyUnreachableError(error)) return {
119
- kind: "proxy-unreachable",
120
- message
121
- };
122
- return {
123
- kind: "network-error",
124
- message
125
- };
126
- }
127
- //#endregion
128
10
  //#region src/adapter/adapter.ts
129
11
  /**
130
12
  * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —
@@ -170,7 +52,8 @@ var AgyAdapter = class extends LlmAdapter {
170
52
  async listModels(_provider) {
171
53
  try {
172
54
  const session = await this.options.getSession();
173
- return await listAgyModels(session?.auth.access, session?.account.projectId);
55
+ const routing = { proxyUrl: session?.account.proxy };
56
+ return await listAgyModels(session?.auth.access, session?.account.projectId, accountFetch(routing));
174
57
  } catch (error) {
175
58
  if (error instanceof AgyPoolBlockedError || error instanceof AgyAuthError) return catalogModelList();
176
59
  throw error;
@@ -243,14 +126,18 @@ var AgyAdapter = class extends LlmAdapter {
243
126
  const headers = buildRequestHeaders(session);
244
127
  let response;
245
128
  try {
129
+ const routing = {
130
+ proxyUrl: session.account.proxy,
131
+ streaming: true
132
+ };
246
133
  response = await fetchAgyFirstOk("/v1internal:streamGenerateContent?alt=sse", {
247
134
  method: "POST",
248
135
  headers,
249
136
  body: JSON.stringify(body),
250
137
  signal: options.signal
251
- });
138
+ }, accountFetch(routing), routing);
252
139
  } catch (error) {
253
- const classified = classifyFetchError(error);
140
+ const classified = classifyFetchError(error, { proxyUrl: session.account.proxy });
254
141
  await this.options.reportFailure(classified.kind, session);
255
142
  throw new LlmError(classified.message ?? "agy fetch failed", "TRANSPORT", { cause: error });
256
143
  }
@@ -290,7 +177,7 @@ var AgyAdapter = class extends LlmAdapter {
290
177
  } catch (error) {
291
178
  if (error instanceof DOMException && error.name === "AbortError") throw new LlmError("agy stream aborted", "ABORTED", { cause: error });
292
179
  await this.options.reportFailure("network-error", session);
293
- throw new LlmError(error instanceof Error ? error.message : "agy stream parse failed", UPSTREAM_ERROR_CODE, { cause: error });
180
+ throw new LlmError(error instanceof Error ? describeFetchError(error) : "agy stream parse failed", UPSTREAM_ERROR_CODE, { cause: error });
294
181
  }
295
182
  }
296
183
  };
@@ -364,4 +251,4 @@ async function createAgyRuntime(ctx) {
364
251
  //#endregion
365
252
  export { createAgyRuntime as t };
366
253
 
367
- //# sourceMappingURL=plugin-common-Di4n-1eR.mjs.map
254
+ //# sourceMappingURL=plugin-common-DtF_PyxH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-common-DtF_PyxH.mjs","names":[],"sources":["../src/adapter/adapter.ts","../src/plugin-common.ts"],"sourcesContent":["/**\n * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —\n * account session resolution (shell-provided), request translation, SSE\n * parsing, failure classification, and rotation reporting. All wire details\n * live in translate.ts / parse.ts / models.ts.\n */\n\nimport {\n LlmAdapter,\n LlmError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n attributionHeaders,\n} from '@deepseek-ai/dsh-llm'\nimport type {\n GenerateOptions,\n LlmModelInfo,\n LlmProviderInfo,\n LlmResolvedModelInfo,\n StreamChunk,\n ToolSchema,\n} from '@deepseek-ai/dsh-llm'\n\nimport { AgyAuthError, AgyPoolBlockedError } from '../types.ts'\nimport type { AgyAccountSession, FailureKind, ManagedAccount, OAuthAuthDetails } from '../types.ts'\nimport type { RateLimitCategory } from '../runtime/classify.ts'\nimport { fetchAgyFirstOk } from '../oauth/constants.ts'\nimport { classifyFetchError, classifyHttpError, describeFetchError } from '../runtime/classify.ts'\nimport { accountFetch } from '../proxy.ts'\nimport { deriveAntigravitySessionId, generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { setThoughtSignature } from '../runtime/signature-cache.ts'\nimport { toAgyRequestBody } from './translate.ts'\nimport type { AgyResolvedImage } from './translate.ts'\nimport { resolveMultimodalFiles } from './multimodal.ts'\nimport { parseAgySse } from './parse.ts'\nimport { AGY_PROVIDER, catalogModelList, listAgyModels, resolveAgyModel } from './models.ts'\n\nexport type { AgyAccountSession }\n\n/**\n * Structural view of the harness attachment service (ctx.attachments).\n * Deliberately not an import of @deepseek-ai/dsh-attachment: the CLI bundle\n * must stay free of harness runtime dependencies, and the real store\n * satisfies this shape.\n */\nexport interface AgyAttachmentStore {\n readImage(ref: {\n attachmentId: string\n mediaType: string\n }): Promise<{ ref: { mediaType: string }; data: Uint8Array }>\n}\n\n/** Collect image refs from user-message content only (spec scope: user images; tool-result nesting out of scope). */\nfunction collectImageRefs(options: GenerateOptions): Array<{ attachmentId: string; mediaType: string }> {\n const refs: Array<{ attachmentId: string; mediaType: string }> = []\n for (const message of options.messages) {\n if (message.role !== 'user') continue\n for (const block of message.content) {\n if (block.type === 'image') refs.push(block.attachment)\n }\n }\n return refs\n}\n\nexport interface AgyAdapterOptions {\n /** Resolve the active account for a request (model-aware: family-scoped quota ranking). */\n getSession(model?: string): Promise<AgyAccountSession | undefined>\n /** Report a classified upstream failure so the shell can cool/rotate/revoke. */\n reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: {\n retryAfterMs?: number\n status?: number\n rateLimitCategory?: RateLimitCategory\n /** Server-reported absolute reset time; drives precise cooldowns. */\n resetTime?: string\n /** Requested model id; drives family-scoped rate-limit bookkeeping. */\n model?: string\n },\n ): Promise<void>\n /** Report a clean stream completion (resets the failure counter). */\n markSuccess?(session: AgyAccountSession): Promise<void>\n /** Resolve the harness attachment store; undefined outside the harness (standalone CLI). */\n resolveAttachments?(): AgyAttachmentStore | undefined\n}\n\nconst UPSTREAM_ERROR_CODE = 'UPSTREAM'\n/** First-class DSH retryable code: the default retry policy honors SERVER (5xx), not UPSTREAM. */\nconst SERVER_ERROR_CODE = 'SERVER'\n\n/** Build the impersonation headers for one request (per-request randomization applied by the shell). */\nexport function buildRequestHeaders(session: AgyAccountSession): Record<string, string> {\n return {\n authorization: `Bearer ${session.auth.access}`,\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'x-goog-request-id': generateAntigravityRequestId(),\n ...attributionHeaders(),\n ...session.impersonation,\n }\n}\n\nexport class AgyAdapter extends LlmAdapter {\n private readonly options: AgyAdapterOptions\n\n constructor(options: AgyAdapterOptions) {\n super()\n this.options = options\n }\n\n override providerInfo(_provider: string): LlmProviderInfo {\n return { id: AGY_PROVIDER, name: 'Antigravity (agy)' }\n }\n\n override async listModels(_provider: string): Promise<readonly LlmModelInfo[]> {\n try {\n const session = await this.options.getSession()\n // Model discovery is account-scoped: route it through the account's proxy\n // (control-plane class, so the standard timeouts apply).\n const routing = { proxyUrl: session?.account.proxy }\n return await listAgyModels(session?.auth.access, session?.account.projectId, accountFetch(routing))\n } catch (error) {\n if (error instanceof AgyPoolBlockedError || error instanceof AgyAuthError) {\n return catalogModelList()\n }\n throw error\n }\n }\n\n override async resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {\n return resolveAgyModel(provider, model)\n }\n\n // `prepareCall` is deliberately NOT overridden: the LlmAdapter base class\n // (dsh-llm 0.1.1-rc.2+) already binds the resolved model to the stream, which\n // is exactly what this adapter would do. The base implementation also\n // forwards the cancellation signal, which the previous hand-written copy\n // dropped. Only dynamic adapters — those needing different capabilities per\n // generation — should override it.\n\n /**\n * Pre-resolve every image attachment into base64 bytes before translation.\n * Image input hard-fails with UNSUPPORTED_CONTENT (terminal, never retried)\n * when the store is missing or a read fails — silently dropping images and\n * sending text-only is the exact failure mode this path exists to prevent.\n */\n private async resolveRequestImages(options: GenerateOptions): Promise<Map<string, AgyResolvedImage>> {\n const refs = collectImageRefs(options)\n const images = new Map<string, AgyResolvedImage>()\n if (refs.length === 0) return images\n const store = this.options.resolveAttachments?.()\n if (!store) {\n throw new LlmError(\n 'agy image input requires the durable attachment service (in-harness plugin only)',\n 'UNSUPPORTED_CONTENT',\n )\n }\n // Read every attachment concurrently (N images cost one round-trip, not N).\n // allSettled rather than all: more than one read may reject, and the\n // surfaced error must be deterministic (first failure in ref order) instead\n // of whichever concurrent read happened to reject first — and no rejection\n // may escape as unhandled.\n const settled = await Promise.allSettled(\n refs.map(async (ref) => {\n const stored = await store.readImage(ref)\n return {\n attachmentId: ref.attachmentId,\n image: {\n mediaType: stored.ref.mediaType,\n data: Buffer.from(stored.data).toString('base64'),\n },\n }\n }),\n )\n const failedIndex = settled.findIndex((outcome) => outcome.status === 'rejected')\n if (failedIndex !== -1) {\n const ref = refs[failedIndex]!\n const cause: unknown = (settled[failedIndex] as PromiseRejectedResult).reason\n throw new LlmError(\n `agy image attachment \"${ref.attachmentId}\" could not be loaded: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'UNSUPPORTED_CONTENT',\n { cause: cause instanceof Error ? cause : undefined },\n )\n }\n for (const outcome of settled) {\n if (outcome.status === 'fulfilled') {\n images.set(outcome.value.attachmentId, outcome.value.image)\n }\n }\n return images\n }\n\n override async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {\n // Spec D1 sequence: resolve images first — a locally-failing image request\n // must surface UNSUPPORTED_CONTENT (user story 8) instead of being masked\n // by account-pool errors, and must not touch pool state at all.\n const images = await this.resolveRequestImages(options)\n let session: AgyAccountSession | undefined\n try {\n session = await this.options.getSession(options.model)\n } catch (error) {\n if (error instanceof AgyAuthError) {\n if (error.kind === 'transport') {\n throw new LlmError(error.message, 'TRANSPORT', { cause: error })\n }\n if (error.kind === 'rate-limit') {\n throw new LlmError(error.message, 'RATE_LIMIT', {\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n })\n }\n throw new LlmError(error.message, 'INVALID_CREDENTIAL', { cause: error })\n }\n if (error instanceof AgyPoolBlockedError) {\n if (error.kind === 'quota-exhausted') {\n throw new LlmError(error.message, QUOTA_EXCEEDED_CODE)\n }\n const delta = Math.ceil(error.blockedUntil - Date.now())\n const providerRetryAfterMs = Number.isFinite(delta) && delta > 0 ? delta : 1\n throw new LlmError(error.message, 'RATE_LIMIT', {\n providerRetryAfterMs,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n })\n }\n throw error\n }\n if (!session) {\n throw new LlmError(\n 'No agy account configured — run `dsh-agy login` to authenticate.',\n 'NO_CREDENTIAL',\n )\n }\n\n const multimodalFiles = await resolveMultimodalFiles(options)\n const body = toAgyRequestBody(options, {\n projectId: session.account.projectId,\n sessionId: deriveAntigravitySessionId(session.account.email) ?? undefined,\n ...(images.size > 0 ? { images } : {}),\n ...(multimodalFiles.size > 0 ? { multimodalFiles } : {}),\n })\n const headers = buildRequestHeaders(session)\n\n let response: Response\n try {\n // Streaming dispatch: the account proxy MUST carry the generation request\n // (it carried only the control-plane calls before, so a proxied account\n // silently generated from the host's real IP), and the streaming\n // dispatcher drops the per-gap body timeout a reasoning pause would trip.\n const routing = { proxyUrl: session.account.proxy, streaming: true }\n response = await fetchAgyFirstOk(\n '/v1internal:streamGenerateContent?alt=sse',\n {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: options.signal,\n },\n accountFetch(routing),\n routing,\n )\n } catch (error) {\n const classified = classifyFetchError(error, { proxyUrl: session.account.proxy })\n await this.options.reportFailure(classified.kind, session)\n throw new LlmError(classified.message ?? 'agy fetch failed', 'TRANSPORT', { cause: error })\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n const classified = classifyHttpError(response.status, response.headers, bodyText)\n await this.options.reportFailure(classified.kind, session, {\n retryAfterMs: classified.retryAfterMs,\n status: response.status,\n rateLimitCategory: classified.rateLimitCategory,\n resetTime: classified.resetTime,\n model: options.model,\n })\n if (classified.kind === 'rate-limit') {\n // soft/rate limits are retryable by the harness (RATE_LIMIT + delay);\n // daily quota exhaustion is terminal (QUOTA, 24h cooldown already set).\n if (classified.rateLimitCategory === 'quota_exhausted') {\n throw new LlmError(\n `agy daily quota exhausted (${response.status}): ${classified.message ?? ''}`,\n QUOTA_EXCEEDED_CODE,\n )\n }\n throw new LlmError(\n `agy rate-limited (${response.status}): ${classified.message ?? ''}`,\n 'RATE_LIMIT',\n {\n providerRetryAfterMs: classified.retryAfterMs ?? undefined,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n },\n )\n }\n if (classified.kind === 'auth-failure') {\n throw new LlmError(\n `agy authentication failed (${response.status}) — run \\`dsh-agy login\\``,\n 'INVALID_CREDENTIAL',\n )\n }\n // 5xx upstream failures (e.g. 503 \"No capacity available\") are transient:\n // the DSH retry policy honors SERVER but treats UPSTREAM as terminal, so\n // classifying 5xx as UPSTREAM kills the turn with zero retries. Non-5xx\n // transient/request errors (404, generic 400, other 4xx) stay terminal.\n if (classified.status !== undefined && classified.status >= 500) {\n throw new LlmError(\n `agy upstream error (${response.status}): ${classified.message ?? ''}`,\n SERVER_ERROR_CODE,\n {\n providerRetryAfterMs: classified.retryAfterMs ?? undefined,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n },\n )\n }\n throw new LlmError(\n `agy upstream error (${response.status}): ${classified.message ?? ''}`,\n UPSTREAM_ERROR_CODE,\n )\n }\n\n if (!response.body) {\n throw new LlmError('agy stream returned no body', UPSTREAM_ERROR_CODE)\n }\n\n try {\n yield* parseAgySse(response.body, {\n signal: options.signal,\n onToolSignature: (toolCallId, signature) => {\n setThoughtSignature(toolCallId, signature)\n },\n })\n await this.options.markSuccess?.(session)\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new LlmError('agy stream aborted', 'ABORTED', { cause: error })\n }\n await this.options.reportFailure('network-error', session)\n // Deliberately UPSTREAM (terminal), not TRANSPORT: content may already\n // have been emitted, and DSH's retry policy honours TRANSPORT, so retrying\n // here would replay a partially-delivered turn. The account-level report\n // above already absorbs the transient case by cooling/rotating. The cause\n // code is still surfaced so the socket failure is legible in session events.\n throw new LlmError(\n error instanceof Error ? describeFetchError(error) : 'agy stream parse failed',\n UPSTREAM_ERROR_CODE,\n { cause: error },\n )\n }\n }\n}\n\nexport type { ToolSchema }\n","/**\n * Shared runtime construction for the in-harness plugin entries: master-key\n * codec resolution (credentials seam first, credentials document fallback),\n * account store, session manager, and adapter. Used by the main plugin\n * (adapter registration) and the web plugin (route registration) so both\n * entries operate on the same store.\n */\n\nimport { randomBytes } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { AgyAdapter } from './adapter/adapter.ts'\nimport type { AgyAttachmentStore } from './adapter/adapter.ts'\nimport { AGY_PROVIDER } from './adapter/models.ts'\nimport { resolveAntigravityVersion } from './runtime/version.ts'\nimport { AgySessionManager } from './session.ts'\nimport { JsonAccountStore } from './store/accounts.ts'\nimport type { AccountStore } from './store/accounts.ts'\nimport {\n MASTER_KEY_REF,\n createAesGcmCodec,\n deriveKey,\n loadMasterKey,\n persistMasterKey,\n resolveDshHome,\n resolveMasterKeyCodec,\n} from './store/keyring.ts'\nimport type { SecretCodec } from './store/keyring.ts'\n\nexport interface CredentialsSeam {\n resolve(ref: string): Promise<{ value: string } | undefined>\n set(ref: string, value: string): Promise<void>\n}\n\nfunction codecFrom(masterKey: string): SecretCodec {\n return createAesGcmCodec(deriveKey(masterKey))\n}\n\n/** Resolve or create the master key, preferring the credentials seam when available. */\nexport async function resolveCodec(ctx: Context): Promise<{ codec: SecretCodec; created: boolean }> {\n const dshHome = resolveDshHome()\n const credentials = ctx.get('credentials') as CredentialsSeam | undefined\n\n if (credentials) {\n const resolved = await credentials.resolve(MASTER_KEY_REF)\n if (resolved) return { codec: codecFrom(resolved.value), created: false }\n const fileKey = loadMasterKey(dshHome)\n if (fileKey) return { codec: codecFrom(fileKey), created: false }\n const fresh = randomBytes(32).toString('hex')\n try {\n await credentials.set(MASTER_KEY_REF, fresh)\n return { codec: codecFrom(fresh), created: true }\n } catch {\n // Read-only shadowing: persist to the credentials document directly.\n persistMasterKey(dshHome, fresh)\n return { codec: codecFrom(fresh), created: true }\n }\n }\n\n return resolveMasterKeyCodec(dshHome)\n}\n\n/** Build the store, session manager, and adapter for one plugin entry. */\nexport async function createAgyRuntime(ctx: Context): Promise<{\n store: AccountStore\n sessions: AgySessionManager\n adapter: AgyAdapter\n}> {\n // Warm the version cache (non-blocking) so fingerprint generation inside\n // the rate-limit path never waits on a cold feed.\n void resolveAntigravityVersion().catch(() => {})\n const { codec } = await resolveCodec(ctx)\n const dshHome = resolveDshHome()\n const store = new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n const sessions = new AgySessionManager({ store })\n // Optional background health probe (DSH_AGY_HEALTH_INTERVAL_MS), off by default.\n const healthIntervalMs = Number(process.env.DSH_AGY_HEALTH_INTERVAL_MS ?? 0)\n if (Number.isFinite(healthIntervalMs) && healthIntervalMs > 0) {\n sessions.startHealthProbe(healthIntervalMs)\n }\n const adapter = new AgyAdapter({\n getSession: (model) => sessions.getSession(model),\n reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),\n markSuccess: (session) => sessions.markSuccess(session),\n resolveAttachments: () => ctx.get('attachments') as AgyAttachmentStore | undefined,\n })\n return { store, sessions, adapter }\n}\n\nexport { AGY_PROVIDER }\n"],"mappings":";;;;;;;;;;;;;;;;;AAqDA,SAAS,iBAAiB,SAA8E;CACtG,MAAM,OAA2D,CAAC;CAClE,KAAK,MAAM,WAAW,QAAQ,UAAU;EACtC,IAAI,QAAQ,SAAS,QAAQ;EAC7B,KAAK,MAAM,SAAS,QAAQ,SAC1B,IAAI,MAAM,SAAS,SAAS,KAAK,KAAK,MAAM,UAAU;CAE1D;CACA,OAAO;AACT;AAyBA,MAAM,sBAAsB;;AAE5B,MAAM,oBAAoB;;AAG1B,SAAgB,oBAAoB,SAAoD;CACtF,OAAO;EACL,eAAe,UAAU,QAAQ,KAAK;EACtC,gBAAgB;EAChB,QAAQ;EACR,qBAAqB,6BAA6B;EAClD,GAAG,mBAAmB;EACtB,GAAG,QAAQ;CACb;AACF;AAEA,IAAa,aAAb,cAAgC,WAAW;CACzC;CAEA,YAAY,SAA4B;EACtC,MAAM;EACN,KAAK,UAAU;CACjB;CAEA,aAAsB,WAAoC;EACxD,OAAO;GAAE,IAAA;GAAkB,MAAM;EAAoB;CACvD;CAEA,MAAe,WAAW,WAAqD;EAC7E,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,QAAQ,WAAW;GAG9C,MAAM,UAAU,EAAE,UAAU,SAAS,QAAQ,MAAM;GACnD,OAAO,MAAM,cAAc,SAAS,KAAK,QAAQ,SAAS,QAAQ,WAAW,aAAa,OAAO,CAAC;EACpG,SAAS,OAAO;GACd,IAAI,iBAAiB,uBAAuB,iBAAiB,cAC3D,OAAO,iBAAiB;GAE1B,MAAM;EACR;CACF;CAEA,MAAe,aAAa,UAAkB,OAA8C;EAC1F,OAAO,gBAAgB,UAAU,KAAK;CACxC;;;;;;;CAeA,MAAc,qBAAqB,SAAkE;EACnG,MAAM,OAAO,iBAAiB,OAAO;EACrC,MAAM,yBAAS,IAAI,IAA8B;EACjD,IAAI,KAAK,WAAW,GAAG,OAAO;EAC9B,MAAM,QAAQ,KAAK,QAAQ,qBAAqB;EAChD,IAAI,CAAC,OACH,MAAM,IAAI,SACR,oFACA,qBACF;EAOF,MAAM,UAAU,MAAM,QAAQ,WAC5B,KAAK,IAAI,OAAO,QAAQ;GACtB,MAAM,SAAS,MAAM,MAAM,UAAU,GAAG;GACxC,OAAO;IACL,cAAc,IAAI;IAClB,OAAO;KACL,WAAW,OAAO,IAAI;KACtB,MAAM,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,SAAS,QAAQ;IAClD;GACF;EACF,CAAC,CACH;EACA,MAAM,cAAc,QAAQ,WAAW,YAAY,QAAQ,WAAW,UAAU;EAChF,IAAI,gBAAgB,IAAI;GACtB,MAAM,MAAM,KAAK;GACjB,MAAM,QAAkB,QAAQ,YAAY,CAA2B;GACvE,MAAM,IAAI,SACR,yBAAyB,IAAI,aAAa,yBAAyB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KACxH,uBACA,EAAE,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAAU,CACtD;EACF;EACA,KAAK,MAAM,WAAW,SACpB,IAAI,QAAQ,WAAW,aACrB,OAAO,IAAI,QAAQ,MAAM,cAAc,QAAQ,MAAM,KAAK;EAG9D,OAAO;CACT;CAEA,OAAgB,OAAO,SAAsD;EAI3E,MAAM,SAAS,MAAM,KAAK,qBAAqB,OAAO;EACtD,IAAI;EACJ,IAAI;GACF,UAAU,MAAM,KAAK,QAAQ,WAAW,QAAQ,KAAK;EACvD,SAAS,OAAO;GACd,IAAI,iBAAiB,cAAc;IACjC,IAAI,MAAM,SAAS,aACjB,MAAM,IAAI,SAAS,MAAM,SAAS,aAAa,EAAE,OAAO,MAAM,CAAC;IAEjE,IAAI,MAAM,SAAS,cACjB,MAAM,IAAI,SAAS,MAAM,SAAS,cAAc,EAC9C,WAAW,kBAAkB,6BAA6B,CAAC,EAC7D,CAAC;IAEH,MAAM,IAAI,SAAS,MAAM,SAAS,sBAAsB,EAAE,OAAO,MAAM,CAAC;GAC1E;GACA,IAAI,iBAAiB,qBAAqB;IACxC,IAAI,MAAM,SAAS,mBACjB,MAAM,IAAI,SAAS,MAAM,SAAS,mBAAmB;IAEvD,MAAM,QAAQ,KAAK,KAAK,MAAM,eAAe,KAAK,IAAI,CAAC;IACvD,MAAM,uBAAuB,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;IAC3E,MAAM,IAAI,SAAS,MAAM,SAAS,cAAc;KAC9C;KACA,WAAW,kBAAkB,6BAA6B,CAAC;IAC7D,CAAC;GACH;GACA,MAAM;EACR;EACA,IAAI,CAAC,SACH,MAAM,IAAI,SACR,oEACA,eACF;EAGF,MAAM,kBAAkB,MAAM,uBAAuB,OAAO;EAC5D,MAAM,OAAO,iBAAiB,SAAS;GACrC,WAAW,QAAQ,QAAQ;GAC3B,WAAW,2BAA2B,QAAQ,QAAQ,KAAK,KAAK,KAAA;GAChE,GAAI,OAAO,OAAO,IAAI,EAAE,OAAO,IAAI,CAAC;GACpC,GAAI,gBAAgB,OAAO,IAAI,EAAE,gBAAgB,IAAI,CAAC;EACxD,CAAC;EACD,MAAM,UAAU,oBAAoB,OAAO;EAE3C,IAAI;EACJ,IAAI;GAKF,MAAM,UAAU;IAAE,UAAU,QAAQ,QAAQ;IAAO,WAAW;GAAK;GACnE,WAAW,MAAM,gBACf,6CACA;IACE,QAAQ;IACR;IACA,MAAM,KAAK,UAAU,IAAI;IACzB,QAAQ,QAAQ;GAClB,GACA,aAAa,OAAO,GACpB,OACF;EACF,SAAS,OAAO;GACd,MAAM,aAAa,mBAAmB,OAAO,EAAE,UAAU,QAAQ,QAAQ,MAAM,CAAC;GAChF,MAAM,KAAK,QAAQ,cAAc,WAAW,MAAM,OAAO;GACzD,MAAM,IAAI,SAAS,WAAW,WAAW,oBAAoB,aAAa,EAAE,OAAO,MAAM,CAAC;EAC5F;EAEA,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,WAAW,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;GAC5D,MAAM,aAAa,kBAAkB,SAAS,QAAQ,SAAS,SAAS,QAAQ;GAChF,MAAM,KAAK,QAAQ,cAAc,WAAW,MAAM,SAAS;IACzD,cAAc,WAAW;IACzB,QAAQ,SAAS;IACjB,mBAAmB,WAAW;IAC9B,WAAW,WAAW;IACtB,OAAO,QAAQ;GACjB,CAAC;GACD,IAAI,WAAW,SAAS,cAAc;IAGpC,IAAI,WAAW,sBAAsB,mBACnC,MAAM,IAAI,SACR,8BAA8B,SAAS,OAAO,KAAK,WAAW,WAAW,MACzE,mBACF;IAEF,MAAM,IAAI,SACR,qBAAqB,SAAS,OAAO,KAAK,WAAW,WAAW,MAChE,cACA;KACE,sBAAsB,WAAW,gBAAgB,KAAA;KACjD,WAAW,kBAAkB,6BAA6B,CAAC;IAC7D,CACF;GACF;GACA,IAAI,WAAW,SAAS,gBACtB,MAAM,IAAI,SACR,8BAA8B,SAAS,OAAO,4BAC9C,oBACF;GAMF,IAAI,WAAW,WAAW,KAAA,KAAa,WAAW,UAAU,KAC1D,MAAM,IAAI,SACR,uBAAuB,SAAS,OAAO,KAAK,WAAW,WAAW,MAClE,mBACA;IACE,sBAAsB,WAAW,gBAAgB,KAAA;IACjD,WAAW,kBAAkB,6BAA6B,CAAC;GAC7D,CACF;GAEF,MAAM,IAAI,SACR,uBAAuB,SAAS,OAAO,KAAK,WAAW,WAAW,MAClE,mBACF;EACF;EAEA,IAAI,CAAC,SAAS,MACZ,MAAM,IAAI,SAAS,+BAA+B,mBAAmB;EAGvE,IAAI;GACF,OAAO,YAAY,SAAS,MAAM;IAChC,QAAQ,QAAQ;IAChB,kBAAkB,YAAY,cAAc;KAC1C,oBAAoB,YAAY,SAAS;IAC3C;GACF,CAAC;GACD,MAAM,KAAK,QAAQ,cAAc,OAAO;EAC1C,SAAS,OAAO;GACd,IAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAClD,MAAM,IAAI,SAAS,sBAAsB,WAAW,EAAE,OAAO,MAAM,CAAC;GAEtE,MAAM,KAAK,QAAQ,cAAc,iBAAiB,OAAO;GAMzD,MAAM,IAAI,SACR,iBAAiB,QAAQ,mBAAmB,KAAK,IAAI,2BACrD,qBACA,EAAE,OAAO,MAAM,CACjB;EACF;CACF;AACF;;;;;;;;;;AC5TA,SAAS,UAAU,WAAgC;CACjD,OAAO,kBAAkB,UAAU,SAAS,CAAC;AAC/C;;AAGA,eAAsB,aAAa,KAAiE;CAClG,MAAM,UAAU,eAAe;CAC/B,MAAM,cAAc,IAAI,IAAI,aAAa;CAEzC,IAAI,aAAa;EACf,MAAM,WAAW,MAAM,YAAY,QAAQ,cAAc;EACzD,IAAI,UAAU,OAAO;GAAE,OAAO,UAAU,SAAS,KAAK;GAAG,SAAS;EAAM;EACxE,MAAM,UAAU,cAAc,OAAO;EACrC,IAAI,SAAS,OAAO;GAAE,OAAO,UAAU,OAAO;GAAG,SAAS;EAAM;EAChE,MAAM,QAAQ,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;EAC5C,IAAI;GACF,MAAM,YAAY,IAAI,gBAAgB,KAAK;GAC3C,OAAO;IAAE,OAAO,UAAU,KAAK;IAAG,SAAS;GAAK;EAClD,QAAQ;GAEN,iBAAiB,SAAS,KAAK;GAC/B,OAAO;IAAE,OAAO,UAAU,KAAK;IAAG,SAAS;GAAK;EAClD;CACF;CAEA,OAAO,sBAAsB,OAAO;AACtC;;AAGA,eAAsB,iBAAiB,KAIpC;CAGD,0BAA+B,CAAC,CAAC,YAAY,CAAC,CAAC;CAC/C,MAAM,EAAE,UAAU,MAAM,aAAa,GAAG;CACxC,MAAM,UAAU,eAAe;CAC/B,MAAM,QAAQ,IAAI,iBAAiB;EAAE,MAAM,GAAG,QAAQ;EAAqB;CAAM,CAAC;CAClF,MAAM,WAAW,IAAI,kBAAkB,EAAE,MAAM,CAAC;CAEhD,MAAM,mBAAmB,OAAO,QAAQ,IAAI,8BAA8B,CAAC;CAC3E,IAAI,OAAO,SAAS,gBAAgB,KAAK,mBAAmB,GAC1D,SAAS,iBAAiB,gBAAgB;CAQ5C,OAAO;EAAE;EAAO;EAAU,SAAA,IANN,WAAW;GAC7B,aAAa,UAAU,SAAS,WAAW,KAAK;GAChD,gBAAgB,MAAM,SAAS,SAAS,SAAS,cAAc,MAAM,SAAS,IAAI;GAClF,cAAc,YAAY,SAAS,YAAY,OAAO;GACtD,0BAA0B,IAAI,IAAI,aAAa;EACjD,CACgC;CAAE;AACpC"}
@@ -10,7 +10,9 @@ import { createConnection } from "node:net";
10
10
  */
11
11
  var proxy_exports = /* @__PURE__ */ __exportAll({
12
12
  _envAgentForTest: () => envAgent,
13
+ accountFetch: () => accountFetch,
13
14
  dispatcherForAsync: () => dispatcherForAsync,
15
+ dispatcherOptsFor: () => dispatcherOptsFor,
14
16
  isProxyReachable: () => isProxyReachable,
15
17
  isProxyUnreachableError: () => isProxyUnreachableError,
16
18
  normalizeProxyUrl: () => normalizeProxyUrl,
@@ -19,6 +21,13 @@ var proxy_exports = /* @__PURE__ */ __exportAll({
19
21
  tagProxyUnreachable: () => tagProxyUnreachable
20
22
  });
21
23
  const envAgent = new EnvHttpProxyAgent();
24
+ /**
25
+ * Streaming variant of the env agent. A generation stream may legitimately stay
26
+ * silent for minutes mid-turn (reasoning), and undici's `bodyTimeout` is a
27
+ * per-gap inactivity timer — the 30s control-plane value kills such a turn.
28
+ * Only generation uses this agent; every other call keeps the short bound.
29
+ */
30
+ const envStreamingAgent = new EnvHttpProxyAgent({ bodyTimeout: 0 });
22
31
  const dispatcherCache = /* @__PURE__ */ new Map();
23
32
  const SUPPORTED = /* @__PURE__ */ new Set([
24
33
  "http:",
@@ -59,7 +68,7 @@ function normalizeProxyUrl(proxyUrl) {
59
68
  if (!port) port = defaultPort(protocol);
60
69
  const portNum = Number(port);
61
70
  if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) throw new Error("[proxy] invalid port");
62
- const auth = parsed.username ? `${encodeURIComponent(parsed.username)}${parsed.password ? `:${encodeURIComponent(parsed.password)}` : ""}@` : "";
71
+ const auth = parsed.username ? `${encodeURIComponent(decodeURIComponent(parsed.username))}${parsed.password ? `:${encodeURIComponent(decodeURIComponent(parsed.password))}` : ""}@` : "";
63
72
  const normalizedBase = `${protocol}//${auth}${parsed.hostname}:${port}`;
64
73
  return familySuffix ? `${normalizedBase}${familySuffix}` : normalizedBase;
65
74
  }
@@ -197,6 +206,19 @@ function createHttpDispatcher(proxyUrl, dispatcherOpts) {
197
206
  ...dispatcherOpts
198
207
  });
199
208
  }
209
+ /**
210
+ * Dispatcher options. `bodyTimeout` is undici's per-gap inactivity timer, not a
211
+ * total transfer budget: it fires when NO bytes arrive for that long. The
212
+ * control-plane calls want the short bound, but a generation stream may
213
+ * legitimately stay silent for minutes mid-turn (reasoning), so streaming runs
214
+ * with the timer disabled — the caller's own AbortSignal bounds it instead.
215
+ *
216
+ * `keepAliveTimeout`/`keepAliveMaxTimeout` deliberately stay at 1ms and are NOT
217
+ * loosened for streaming: undici applies them only when `pipelining` is
218
+ * non-zero, and `pipelining: 0` forces every connection to reset on completion,
219
+ * so they are inert here. They are kept at the conservative value spec #8 asks
220
+ * for rather than being replaced by a setting that would never take effect.
221
+ */
200
222
  const DISPATCHER_OPTS = {
201
223
  headersTimeout: 3e4,
202
224
  bodyTimeout: 3e4,
@@ -205,16 +227,30 @@ const DISPATCHER_OPTS = {
205
227
  keepAliveMaxTimeout: 1,
206
228
  pipelining: 0
207
229
  };
208
- async function dispatcherForAsync(proxyUrl) {
209
- if (!proxyUrl) return envAgent;
230
+ /** Streaming dispatcher options: same bounds, minus the body inactivity timer. */
231
+ const STREAMING_DISPATCHER_OPTS = {
232
+ ...DISPATCHER_OPTS,
233
+ bodyTimeout: 0
234
+ };
235
+ /** Resolved dispatcher options for a call class (exported as the source of truth). */
236
+ function dispatcherOptsFor(streaming) {
237
+ return streaming ? STREAMING_DISPATCHER_OPTS : DISPATCHER_OPTS;
238
+ }
239
+ function dispatcherCacheKey(normalized, streaming) {
240
+ return streaming ? `${normalized}|stream` : normalized;
241
+ }
242
+ async function dispatcherForAsync(proxyUrl, options = {}) {
243
+ const streaming = options.streaming === true;
244
+ if (!proxyUrl) return streaming ? envStreamingAgent : envAgent;
210
245
  const normalized = normalizeProxyUrl(proxyUrl);
211
- const cached = dispatcherCache.get(normalized);
246
+ const key = dispatcherCacheKey(normalized, streaming);
247
+ const cached = dispatcherCache.get(key);
212
248
  if (cached) return cached;
213
249
  const famClean = normalized.replace(/\?family=(ipv4|ipv6)$/, "");
214
250
  let dispatcher;
215
- if (famClean.startsWith("socks5:")) dispatcher = await createSocksDispatcher(normalized, DISPATCHER_OPTS);
216
- else dispatcher = createHttpDispatcher(normalized, DISPATCHER_OPTS);
217
- dispatcherCache.set(normalized, dispatcher);
251
+ if (famClean.startsWith("socks5:")) dispatcher = await createSocksDispatcher(normalized, dispatcherOptsFor(streaming));
252
+ else dispatcher = createHttpDispatcher(normalized, dispatcherOptsFor(streaming));
253
+ dispatcherCache.set(key, dispatcher);
218
254
  return dispatcher;
219
255
  }
220
256
  function getTargetUrlString(input) {
@@ -228,11 +264,12 @@ function getTargetUrlString(input) {
228
264
  /** fetch() that respects per-account proxyUrl (when given) otherwise env. */
229
265
  const proxiedFetch = async (input, init, opts) => {
230
266
  const proxyUrl = opts?.proxyUrl ?? init?.proxyUrl;
267
+ const streaming = opts?.streaming === true;
231
268
  const cleanInit = { ...init };
232
269
  if (cleanInit.proxyUrl) delete cleanInit.proxyUrl;
233
270
  if (!proxyUrl) return fetch(input, {
234
271
  ...cleanInit,
235
- dispatcher: envAgent
272
+ dispatcher: streaming ? envStreamingAgent : envAgent
236
273
  });
237
274
  const targetStr = getTargetUrlString(input);
238
275
  if (targetStr && isLoopbackUrl(targetStr)) return fetch(input, cleanInit);
@@ -250,7 +287,7 @@ const proxiedFetch = async (input, init, opts) => {
250
287
  }
251
288
  let dispatcher;
252
289
  try {
253
- dispatcher = await dispatcherForAsync(proxyUrl);
290
+ dispatcher = await dispatcherForAsync(proxyUrl, { streaming });
254
291
  } catch (e) {
255
292
  throw tagProxyUnreachable(e);
256
293
  }
@@ -263,7 +300,22 @@ const proxiedFetch = async (input, init, opts) => {
263
300
  throw tagProxyUnreachable(err);
264
301
  }
265
302
  };
303
+ /**
304
+ * Bind one account's routing to a fetch implementation.
305
+ *
306
+ * The single entry point every account-scoped call uses (generation, test call,
307
+ * model/quota discovery, project healing, import enrichment). Threading a bare
308
+ * `proxyUrl` at each site is what let six call sites drift and silently route a
309
+ * proxied account over the host's real IP, so the invariant is enforced here
310
+ * instead of by review (AGENTS.md "Proxy Routing").
311
+ */
312
+ function accountFetch(routing) {
313
+ return ((input, init) => proxiedFetch(input, init, {
314
+ ...routing?.proxyUrl ? { proxyUrl: routing.proxyUrl } : {},
315
+ ...routing?.streaming ? { streaming: true } : {}
316
+ }));
317
+ }
266
318
  //#endregion
267
- export { proxyUrlForLogs as a, proxiedFetch as i, isProxyUnreachableError as n, proxy_exports as o, normalizeProxyUrl as r, isProxyReachable as t };
319
+ export { proxiedFetch as a, normalizeProxyUrl as i, isProxyReachable as n, proxyUrlForLogs as o, isProxyUnreachableError as r, proxy_exports as s, accountFetch as t };
268
320
 
269
- //# sourceMappingURL=proxy-DQPIwUov.mjs.map
321
+ //# sourceMappingURL=proxy-DLvX8yxv.mjs.map