dsh-agy 0.2.12 → 0.3.1

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 (46) hide show
  1. package/README.md +67 -11
  2. package/cordis.patch.yml +4 -2
  3. package/lib/{accounts-DB1tT9Gy.mjs → accounts-CkaHhb5H.mjs} +1155 -646
  4. package/lib/accounts-CkaHhb5H.mjs.map +1 -0
  5. package/lib/{catalog-bfGIyoba.mjs → catalog-BgdDVJEB.mjs} +17 -2
  6. package/lib/{catalog-bfGIyoba.mjs.map → catalog-BgdDVJEB.mjs.map} +1 -1
  7. package/lib/cli/index.d.mts +10 -1
  8. package/lib/cli/index.d.mts.map +1 -1
  9. package/lib/cli/index.mjs +21 -8
  10. package/lib/cli/index.mjs.map +1 -1
  11. package/lib/client.js +2184 -14
  12. package/lib/{constants-BFKYjOmh.mjs → constants-Db4tmyfb.mjs} +79 -14
  13. package/lib/constants-Db4tmyfb.mjs.map +1 -0
  14. package/lib/{exchange-dQm2mjJ5.mjs → exchange-yZPIZumk.mjs} +27 -12
  15. package/lib/exchange-yZPIZumk.mjs.map +1 -0
  16. package/lib/identity-Ci8EGc1F.mjs +96 -0
  17. package/lib/identity-Ci8EGc1F.mjs.map +1 -0
  18. package/lib/{import-D7R0GpRr.mjs → import-BaFULQnH.mjs} +11 -7
  19. package/lib/import-BaFULQnH.mjs.map +1 -0
  20. package/lib/index.mjs +2 -2
  21. package/lib/{models-ClGTQBLY.mjs → models-BGuwl50d.mjs} +35 -22
  22. package/lib/models-BGuwl50d.mjs.map +1 -0
  23. package/lib/plugin-common-B1MG00JT.mjs +1313 -0
  24. package/lib/plugin-common-B1MG00JT.mjs.map +1 -0
  25. package/lib/{proxy-DLvX8yxv.mjs → proxy-DfaL73mL.mjs} +52 -3
  26. package/lib/proxy-DfaL73mL.mjs.map +1 -0
  27. package/lib/quota-summary-5CypVffd.mjs +127 -0
  28. package/lib/quota-summary-5CypVffd.mjs.map +1 -0
  29. package/lib/{translate-D3GFUpw4.mjs → translate-BQu8HLvB.mjs} +25 -6
  30. package/lib/translate-BQu8HLvB.mjs.map +1 -0
  31. package/lib/web/plugin.d.mts +1 -0
  32. package/lib/web/plugin.d.mts.map +1 -1
  33. package/lib/web/plugin.mjs +651 -1701
  34. package/lib/web/plugin.mjs.map +1 -1
  35. package/package.json +16 -4
  36. package/lib/accounts-DB1tT9Gy.mjs.map +0 -1
  37. package/lib/constants-BFKYjOmh.mjs.map +0 -1
  38. package/lib/exchange-dQm2mjJ5.mjs.map +0 -1
  39. package/lib/identity-DsftHTJC.mjs +0 -35
  40. package/lib/identity-DsftHTJC.mjs.map +0 -1
  41. package/lib/import-D7R0GpRr.mjs.map +0 -1
  42. package/lib/models-ClGTQBLY.mjs.map +0 -1
  43. package/lib/plugin-common-B63uavj7.mjs +0 -254
  44. package/lib/plugin-common-B63uavj7.mjs.map +0 -1
  45. package/lib/proxy-DLvX8yxv.mjs.map +0 -1
  46. package/lib/translate-D3GFUpw4.mjs.map +0 -1
@@ -1,254 +0,0 @@
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-DB1tT9Gy.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-D3GFUpw4.mjs";
6
- import { t as parseAgySse } from "./parse-Bs4rPkHA.mjs";
7
- import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-ClGTQBLY.mjs";
8
- import { randomBytes } from "node:crypto";
9
- import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
10
- //#region src/adapter/adapter.ts
11
- /**
12
- * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —
13
- * account session resolution (shell-provided), request translation, SSE
14
- * parsing, failure classification, and rotation reporting. All wire details
15
- * live in translate.ts / parse.ts / models.ts.
16
- */
17
- /** Collect image refs from user-message content only (spec scope: user images; tool-result nesting out of scope). */
18
- function collectImageRefs(options) {
19
- const refs = [];
20
- for (const message of options.messages) {
21
- if (message.role !== "user") continue;
22
- for (const block of message.content) if (block.type === "image") refs.push(block.attachment);
23
- }
24
- return refs;
25
- }
26
- const UPSTREAM_ERROR_CODE = "UPSTREAM";
27
- /** First-class DSH retryable code: the default retry policy honors SERVER (5xx), not UPSTREAM. */
28
- const SERVER_ERROR_CODE = "SERVER";
29
- /** Build the impersonation headers for one request (per-request randomization applied by the shell). */
30
- function buildRequestHeaders(session) {
31
- return {
32
- authorization: `Bearer ${session.auth.access}`,
33
- "content-type": "application/json",
34
- accept: "text/event-stream",
35
- "x-goog-request-id": generateAntigravityRequestId(),
36
- ...attributionHeaders(),
37
- ...session.impersonation
38
- };
39
- }
40
- var AgyAdapter = class extends LlmAdapter {
41
- options;
42
- constructor(options) {
43
- super();
44
- this.options = options;
45
- }
46
- providerInfo(_provider) {
47
- return {
48
- id: "agy",
49
- name: "Antigravity (agy)"
50
- };
51
- }
52
- async listModels(_provider) {
53
- try {
54
- const session = await this.options.getSession();
55
- const routing = { proxyUrl: session?.account.proxy };
56
- return await listAgyModels(session?.auth.access, session?.account.projectId, accountFetch(routing));
57
- } catch (error) {
58
- if (error instanceof AgyPoolBlockedError || error instanceof AgyAuthError) return catalogModelList();
59
- throw error;
60
- }
61
- }
62
- async resolveModel(provider, model) {
63
- return resolveAgyModel(provider, model);
64
- }
65
- /**
66
- * Pre-resolve every image attachment into base64 bytes before translation.
67
- * Image input hard-fails with UNSUPPORTED_CONTENT (terminal, never retried)
68
- * when the store is missing or a read fails — silently dropping images and
69
- * sending text-only is the exact failure mode this path exists to prevent.
70
- */
71
- async resolveRequestImages(options) {
72
- const refs = collectImageRefs(options);
73
- const images = /* @__PURE__ */ new Map();
74
- if (refs.length === 0) return images;
75
- const store = this.options.resolveAttachments?.();
76
- if (!store) throw new LlmError("agy image input requires the durable attachment service (in-harness plugin only)", "UNSUPPORTED_CONTENT");
77
- const settled = await Promise.allSettled(refs.map(async (ref) => {
78
- const stored = await store.readImage(ref);
79
- return {
80
- attachmentId: ref.attachmentId,
81
- image: {
82
- mediaType: stored.ref.mediaType,
83
- data: Buffer.from(stored.data).toString("base64")
84
- }
85
- };
86
- }));
87
- const failedIndex = settled.findIndex((outcome) => outcome.status === "rejected");
88
- if (failedIndex !== -1) {
89
- const ref = refs[failedIndex];
90
- const cause = settled[failedIndex].reason;
91
- throw new LlmError(`agy image attachment "${ref.attachmentId}" could not be loaded: ${cause instanceof Error ? cause.message : String(cause)}`, "UNSUPPORTED_CONTENT", { cause: cause instanceof Error ? cause : void 0 });
92
- }
93
- for (const outcome of settled) if (outcome.status === "fulfilled") images.set(outcome.value.attachmentId, outcome.value.image);
94
- return images;
95
- }
96
- async *stream(options) {
97
- const images = await this.resolveRequestImages(options);
98
- let session;
99
- try {
100
- session = await this.options.getSession(options.model);
101
- } catch (error) {
102
- if (error instanceof AgyAuthError) {
103
- if (error.kind === "transport") throw new LlmError(error.message, "TRANSPORT", { cause: error });
104
- if (error.kind === "rate-limit") throw new LlmError(error.message, "RATE_LIMIT", { requestId: ProviderRequestId(generateAntigravityRequestId()) });
105
- throw new LlmError(error.message, "INVALID_CREDENTIAL", { cause: error });
106
- }
107
- if (error instanceof AgyPoolBlockedError) {
108
- if (error.kind === "quota-exhausted") throw new LlmError(error.message, QUOTA_EXCEEDED_CODE);
109
- const delta = Math.ceil(error.blockedUntil - Date.now());
110
- const providerRetryAfterMs = Number.isFinite(delta) && delta > 0 ? delta : 1;
111
- throw new LlmError(error.message, "RATE_LIMIT", {
112
- providerRetryAfterMs,
113
- requestId: ProviderRequestId(generateAntigravityRequestId())
114
- });
115
- }
116
- throw error;
117
- }
118
- if (!session) throw new LlmError("No agy account configured — run `dsh-agy login` to authenticate.", "NO_CREDENTIAL");
119
- const multimodalFiles = await resolveMultimodalFiles(options);
120
- const body = toAgyRequestBody(options, {
121
- projectId: session.account.projectId,
122
- sessionId: deriveAntigravitySessionId(session.account.email) ?? void 0,
123
- ...images.size > 0 ? { images } : {},
124
- ...multimodalFiles.size > 0 ? { multimodalFiles } : {}
125
- });
126
- const headers = buildRequestHeaders(session);
127
- let response;
128
- try {
129
- const routing = {
130
- proxyUrl: session.account.proxy,
131
- streaming: true
132
- };
133
- response = await fetchAgyFirstOk("/v1internal:streamGenerateContent?alt=sse", {
134
- method: "POST",
135
- headers,
136
- body: JSON.stringify(body),
137
- signal: options.signal
138
- }, accountFetch(routing), routing);
139
- } catch (error) {
140
- const classified = classifyFetchError(error, { proxyUrl: session.account.proxy });
141
- await this.options.reportFailure(classified.kind, session);
142
- throw new LlmError(classified.message ?? "agy fetch failed", "TRANSPORT", { cause: error });
143
- }
144
- if (!response.ok) {
145
- const bodyText = await response.text().catch(() => void 0);
146
- const classified = classifyHttpError(response.status, response.headers, bodyText);
147
- await this.options.reportFailure(classified.kind, session, {
148
- retryAfterMs: classified.retryAfterMs,
149
- status: response.status,
150
- rateLimitCategory: classified.rateLimitCategory,
151
- resetTime: classified.resetTime,
152
- model: options.model
153
- });
154
- if (classified.kind === "rate-limit") {
155
- if (classified.rateLimitCategory === "quota_exhausted") throw new LlmError(`agy daily quota exhausted (${response.status}): ${classified.message ?? ""}`, QUOTA_EXCEEDED_CODE);
156
- throw new LlmError(`agy rate-limited (${response.status}): ${classified.message ?? ""}`, "RATE_LIMIT", {
157
- providerRetryAfterMs: classified.retryAfterMs ?? void 0,
158
- requestId: ProviderRequestId(generateAntigravityRequestId())
159
- });
160
- }
161
- if (classified.kind === "auth-failure") throw new LlmError(`agy authentication failed (${response.status}) — run \`dsh-agy login\``, "INVALID_CREDENTIAL");
162
- if (classified.status !== void 0 && classified.status >= 500) throw new LlmError(`agy upstream error (${response.status}): ${classified.message ?? ""}`, SERVER_ERROR_CODE, {
163
- providerRetryAfterMs: classified.retryAfterMs ?? void 0,
164
- requestId: ProviderRequestId(generateAntigravityRequestId())
165
- });
166
- throw new LlmError(`agy upstream error (${response.status}): ${classified.message ?? ""}`, UPSTREAM_ERROR_CODE);
167
- }
168
- if (!response.body) throw new LlmError("agy stream returned no body", UPSTREAM_ERROR_CODE);
169
- try {
170
- yield* parseAgySse(response.body, {
171
- signal: options.signal,
172
- onToolSignature: (toolCallId, signature) => {
173
- setThoughtSignature(toolCallId, signature);
174
- }
175
- });
176
- await this.options.markSuccess?.(session);
177
- } catch (error) {
178
- if (error instanceof DOMException && error.name === "AbortError") throw new LlmError("agy stream aborted", "ABORTED", { cause: error });
179
- await this.options.reportFailure("network-error", session);
180
- throw new LlmError(error instanceof Error ? describeFetchError(error) : "agy stream parse failed", UPSTREAM_ERROR_CODE, { cause: error });
181
- }
182
- }
183
- };
184
- //#endregion
185
- //#region src/plugin-common.ts
186
- /**
187
- * Shared runtime construction for the in-harness plugin entries: master-key
188
- * codec resolution (credentials seam first, credentials document fallback),
189
- * account store, session manager, and adapter. Used by the main plugin
190
- * (adapter registration) and the web plugin (route registration) so both
191
- * entries operate on the same store.
192
- */
193
- function codecFrom(masterKey) {
194
- return createAesGcmCodec(deriveKey(masterKey));
195
- }
196
- /** Resolve or create the master key, preferring the credentials seam when available. */
197
- async function resolveCodec(ctx) {
198
- const dshHome = resolveDshHome();
199
- const credentials = ctx.get("credentials");
200
- if (credentials) {
201
- const resolved = await credentials.resolve(MASTER_KEY_REF);
202
- if (resolved) return {
203
- codec: codecFrom(resolved.value),
204
- created: false
205
- };
206
- const fileKey = loadMasterKey(dshHome);
207
- if (fileKey) return {
208
- codec: codecFrom(fileKey),
209
- created: false
210
- };
211
- const fresh = randomBytes(32).toString("hex");
212
- try {
213
- await credentials.set(MASTER_KEY_REF, fresh);
214
- return {
215
- codec: codecFrom(fresh),
216
- created: true
217
- };
218
- } catch {
219
- persistMasterKey(dshHome, fresh);
220
- return {
221
- codec: codecFrom(fresh),
222
- created: true
223
- };
224
- }
225
- }
226
- return resolveMasterKeyCodec(dshHome);
227
- }
228
- /** Build the store, session manager, and adapter for one plugin entry. */
229
- async function createAgyRuntime(ctx) {
230
- resolveAntigravityVersion().catch(() => {});
231
- const { codec } = await resolveCodec(ctx);
232
- const dshHome = resolveDshHome();
233
- const store = new JsonAccountStore({
234
- file: `${dshHome}/agy-accounts.json`,
235
- codec
236
- });
237
- const sessions = new AgySessionManager({ store });
238
- const healthIntervalMs = Number(process.env.DSH_AGY_HEALTH_INTERVAL_MS ?? 0);
239
- if (Number.isFinite(healthIntervalMs) && healthIntervalMs > 0) sessions.startHealthProbe(healthIntervalMs);
240
- return {
241
- store,
242
- sessions,
243
- adapter: new AgyAdapter({
244
- getSession: (model) => sessions.getSession(model),
245
- reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),
246
- markSuccess: (session) => sessions.markSuccess(session),
247
- resolveAttachments: () => ctx.get("attachments")
248
- })
249
- };
250
- }
251
- //#endregion
252
- export { createAgyRuntime as t };
253
-
254
- //# sourceMappingURL=plugin-common-B63uavj7.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin-common-B63uavj7.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":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"proxy-DLvX8yxv.mjs","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["/**\n * Proxy-aware fetch: per-account proxy (http/https/socks5) + env fallback.\n * - account.proxy present => per-account dispatcher (fail-closed, not affected by NO_PROXY)\n * - otherwise => EnvHttpProxyAgent (honours HTTP_PROXY/HTTPS_PROXY/NO_PROXY)\n * Applied per-request via dispatcher option so the host's global dispatcher stays untouched.\n */\n\nimport { EnvHttpProxyAgent, ProxyAgent } from 'undici'\nimport { createConnection } from 'node:net'\nimport { createRequire } from 'node:module'\nimport { isProxyRouted } from './types.ts'\nimport type { AccountRouting } from './types.ts'\n\nconst envAgent = new EnvHttpProxyAgent()\n\n/**\n * Streaming variant of the env agent. A generation stream may legitimately stay\n * silent for minutes mid-turn (reasoning), and undici's `bodyTimeout` is a\n * per-gap inactivity timer — the 30s control-plane value kills such a turn.\n * Only generation uses this agent; every other call keeps the short bound.\n */\nconst envStreamingAgent = new EnvHttpProxyAgent({ bodyTimeout: 0 })\n\n/** The env proxy agent (exported for tests). */\nexport const proxyAgent = envAgent\n\n/** The streaming env proxy agent (exported for tests). */\nexport const proxyStreamingAgent = envStreamingAgent\n\n// ── Dispatcher cache (bounded by MAX_ACCOUNTS=10, no leak concern) ──\nconst dispatcherCache = new Map<string, any>()\n\n// ── Proxy URL normalization ──\nconst SUPPORTED = new Set(['http:', 'https:', 'socks5:', 'socks5h:'])\n\nfunction defaultPort(protocol: string): string {\n if (protocol === 'https:') return '443'\n if (protocol === 'socks5:' || protocol === 'socks5h:') return '1080'\n return '8080'\n}\n\nexport function normalizeProxyUrl(proxyUrl: string): string {\n let raw = proxyUrl.trim()\n if (!raw) throw new Error('[proxy] empty proxy URL')\n // alias socks:// -> socks5://\n if (raw.toLowerCase().startsWith('socks://')) raw = 'socks5://' + raw.slice('socks://'.length)\n // strip family marker before parse, re-append after\n const familyMatch = raw.match(/\\?family=(ipv4|ipv6)$/)\n const familySuffix = familyMatch ? familyMatch[0] : ''\n const baseRaw = familySuffix ? raw.slice(0, -familySuffix.length) : raw\n\n let parsed: URL\n try {\n parsed = new URL(baseRaw)\n } catch {\n throw new Error(`[proxy] invalid proxy URL: ${proxyUrlForLogs(proxyUrl)}`)\n }\n // normalize socks5h -> socks5 (remote DNS, same agent)\n let protocol = parsed.protocol.toLowerCase()\n if (protocol === 'socks5h:') protocol = 'socks5:'\n if (!SUPPORTED.has(protocol) && protocol !== 'socks5:') {\n // allow socks5h already normalized\n if (!SUPPORTED.has(parsed.protocol.toLowerCase())) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n }\n // ensure protocol is one of http/https/socks5\n if (!['http:', 'https:', 'socks5:'].includes(protocol)) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n if (!parsed.hostname) throw new Error('[proxy] missing host')\n\n let port = parsed.port\n if (!port) port = defaultPort(protocol)\n const portNum = Number(port)\n if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error('[proxy] invalid port')\n }\n // Build auth from the DECODED credentials. `URL.username`/`password` return the\n // percent-ENCODED substrings (not decoded, despite what the older comment here\n // claimed), so encoding them again double-encodes: `p@ss` became `p%2540ss`,\n // which the proxy decodes to the literal `p%40ss` and rejects. Decoding first\n // also makes this function idempotent, which matters because a stored proxy URL\n // is normalized again on every request.\n const auth = parsed.username\n ? `${encodeURIComponent(decodeURIComponent(parsed.username))}${parsed.password ? `:${encodeURIComponent(decodeURIComponent(parsed.password))}` : ''}@`\n : ''\n const normalizedBase = `${protocol}//${auth}${parsed.hostname}:${port}`\n return familySuffix ? `${normalizedBase}${familySuffix}` : normalizedBase\n}\n\nexport function proxyUrlForLogs(proxyUrl: string): string {\n try {\n // handle family suffix\n const fam = proxyUrl.match(/\\?family=(ipv4|ipv6)$/)\n const base = fam ? proxyUrl.slice(0, -fam[0].length) : proxyUrl\n const u = new URL(base)\n const port = u.port || defaultPort(u.protocol)\n return `${u.protocol}//${u.hostname}:${port}`\n } catch {\n return proxyUrl\n }\n}\n\n// ── Proxy unreachable detection (mirrors OmniRoute proxyFetch.ts:30) ──\nconst PROXY_UNREACHABLE_CODES = new Set([\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'ENETUNREACH',\n 'EHOSTUNREACH',\n 'EPIPE',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n])\n\nfunction isLoopbackUrl(url: string | URL): boolean {\n try {\n const u = typeof url === 'string' ? new URL(url as string) : (url as URL)\n const h = u.hostname.toLowerCase()\n return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '::ffff:127.0.0.1'\n } catch {\n return false\n }\n}\n\nexport function isProxyUnreachableError(err: unknown): boolean {\n const seen = new Set<unknown>()\n let cur: unknown = err\n for (let depth = 0; cur && depth < 5 && !seen.has(cur); depth++) {\n seen.add(cur)\n if (cur && typeof cur === 'object') {\n const code = (cur as { code?: unknown }).code\n if (typeof code === 'string' && PROXY_UNREACHABLE_CODES.has(code)) return true\n const errorCode = (cur as { errorCode?: unknown }).errorCode\n if (errorCode === 'proxy_unreachable' || errorCode === 'PROXY_UNREACHABLE') return true\n const statusCode = (cur as { statusCode?: unknown }).statusCode\n if (statusCode === 503) {\n const m = (cur as { message?: unknown }).message\n if (typeof m === 'string' && m.includes('Proxy unreachable')) return true\n }\n const msg = (cur as { message?: unknown }).message\n if (typeof msg === 'string') {\n if (msg.includes('proxy_unreachable') || msg.includes('PROXY_UNREACHABLE')) return true\n for (const c of PROXY_UNREACHABLE_CODES) {\n if (msg.includes(c)) return true\n }\n }\n }\n cur = (cur as { cause?: unknown })?.cause\n // Also handle AggregateError.errors (undici Happy Eyeballs)\n if (!cur && (err as { errors?: unknown })?.errors && depth === 0) {\n const errs = (err as { errors?: unknown[] }).errors\n if (Array.isArray(errs)) {\n for (const e of errs) if (isProxyUnreachableError(e)) return true\n }\n }\n }\n return false\n}\n\nexport function tagProxyUnreachable<T>(err: T): T {\n if (isProxyUnreachableError(err)) {\n const e = err as unknown as Error & { code?: string; errorCode?: string }\n e.code = 'PROXY_UNREACHABLE'\n e.errorCode = 'proxy_unreachable'\n }\n return err\n}\n\n// ── Fast-fail TCP reachability (2s, 30s healthy / 2s unhealthy cache) ──\nconst FAST_FAIL_TIMEOUT_MS = 2000\nconst HEALTHY_TTL_MS = 30_000\nconst UNHEALTHY_TTL_MS = 2000\ntype HealthEntry = { healthy: boolean; at: number; ttl: number }\nconst healthCache = new Map<string, HealthEntry>()\nconst healthInflight = new Map<string, Promise<boolean>>()\n\nfunction tcpCheck(host: string, port: number, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ host, port }, () => {\n socket.destroy()\n resolve(true)\n })\n socket.setTimeout(timeoutMs)\n socket.on('error', () => resolve(false))\n socket.on('timeout', () => {\n socket.destroy()\n resolve(false)\n })\n })\n}\n\nexport async function isProxyReachable(proxyUrl: string, timeoutMs = FAST_FAIL_TIMEOUT_MS): Promise<boolean> {\n const key = normalizeProxyUrl(proxyUrl)\n const cached = healthCache.get(key)\n if (cached && Date.now() - cached.at < cached.ttl) return cached.healthy\n const existing = healthInflight.get(key)\n if (existing) return existing\n let url: URL\n try {\n const base = key.replace(/\\?family=(ipv4|ipv6)$/, '')\n url = new URL(base)\n } catch {\n healthCache.set(key, { healthy: false, at: Date.now(), ttl: UNHEALTHY_TTL_MS })\n return false\n }\n const host = url.hostname.replace(/^\\[/, '').replace(/\\]$/, '')\n const port = Number(url.port || defaultPort(url.protocol))\n const probe = tcpCheck(host, port, timeoutMs).then((healthy) => {\n healthCache.set(key, { healthy, at: Date.now(), ttl: healthy ? HEALTHY_TTL_MS : UNHEALTHY_TTL_MS })\n return healthy\n })\n healthInflight.set(key, probe)\n try {\n return await probe\n } finally {\n if (healthInflight.get(key) === probe) healthInflight.delete(key)\n }\n}\n\nexport function _clearProxyHealthCacheForTest(): void {\n healthCache.clear()\n healthInflight.clear()\n}\n\n// ── Dispatcher creation (cached) ──\nasync function createSocksDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): Promise<any> {\n // @ts-ignore — optional dep, only required for socks5\n const { SocksProxyAgent } = await import('socks-proxy-agent')\n // socks-proxy-agent is compatible with undici dispatcher interface via fetch\n return new (SocksProxyAgent as any)(proxyUrl) as any\n}\n\nfunction createHttpDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): any {\n const clean = proxyUrl.replace(/\\?family=(ipv4|ipv6)$/, '')\n return new ProxyAgent({\n uri: clean,\n // tunnel all (http+https) via CONNECT, same as OmniRoute\n proxyTunnel: true,\n ...dispatcherOpts,\n } as any)\n}\n\n/**\n * Dispatcher options. `bodyTimeout` is undici's per-gap inactivity timer, not a\n * total transfer budget: it fires when NO bytes arrive for that long. The\n * control-plane calls want the short bound, but a generation stream may\n * legitimately stay silent for minutes mid-turn (reasoning), so streaming runs\n * with the timer disabled — the caller's own AbortSignal bounds it instead.\n *\n * `keepAliveTimeout`/`keepAliveMaxTimeout` deliberately stay at 1ms and are NOT\n * loosened for streaming: undici applies them only when `pipelining` is\n * non-zero, and `pipelining: 0` forces every connection to reset on completion,\n * so they are inert here. They are kept at the conservative value spec #8 asks\n * for rather than being replaced by a setting that would never take effect.\n */\nconst DISPATCHER_OPTS = {\n headersTimeout: 30_000,\n bodyTimeout: 30_000,\n connectTimeout: 10_000,\n keepAliveTimeout: 1,\n keepAliveMaxTimeout: 1,\n pipelining: 0,\n} as const\n\n/** Streaming dispatcher options: same bounds, minus the body inactivity timer. */\nconst STREAMING_DISPATCHER_OPTS = { ...DISPATCHER_OPTS, bodyTimeout: 0 } as const\n\n/** Shape shared by both dispatcher option sets. */\nexport type DispatcherOpts = { -readonly [K in keyof typeof DISPATCHER_OPTS]: number }\n\n/** Resolved dispatcher options for a call class (exported as the source of truth). */\nexport function dispatcherOptsFor(streaming: boolean): DispatcherOpts {\n return streaming ? STREAMING_DISPATCHER_OPTS : DISPATCHER_OPTS\n}\n\nfunction dispatcherCacheKey(normalized: string, streaming: boolean): string {\n return streaming ? `${normalized}|stream` : normalized\n}\n\n/**\n * Synchronous dispatcher for a proxy URL (control-plane class only).\n *\n * Streaming must NOT use this: it needs `bodyTimeout: 0`, and the socks path\n * already requires the async variant. Kept sync for callers that cannot await.\n */\nexport function dispatcherFor(proxyUrl?: string): any | undefined {\n if (!proxyUrl) return envAgent as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const cached = dispatcherCache.get(dispatcherCacheKey(normalized, false))\n if (cached) return cached\n const famClean = normalized.replace(/\\?family=(ipv4|ipv6)$/, '')\n let dispatcher: any\n if (famClean.startsWith('socks5:')) {\n // SOCKS needs SocksProxyAgent. Try sync require for cached case; otherwise prefer async path (proxiedFetch uses dispatcherForAsync).\n try {\n const rq = createRequire(import.meta.url)\n const mod = rq('socks-proxy-agent') as { SocksProxyAgent?: new (u: string) => unknown }\n const Cls = mod.SocksProxyAgent\n if (Cls) {\n dispatcher = new (Cls as unknown as new (u: string) => unknown)(normalized) as unknown\n } else {\n throw new Error('no SocksProxyAgent')\n }\n } catch {\n throw new Error('[proxy] socks dispatcher requires async creation — use dispatcherForAsync or proxiedFetch with proxyUrl')\n }\n } else {\n dispatcher = createHttpDispatcher(normalized, DISPATCHER_OPTS as any)\n }\n dispatcherCache.set(dispatcherCacheKey(normalized, false), dispatcher)\n return dispatcher\n}\n\nexport async function dispatcherForAsync(\n proxyUrl?: string,\n options: { streaming?: boolean } = {},\n): Promise<any | undefined> {\n const streaming = options.streaming === true\n if (!proxyUrl) return (streaming ? envStreamingAgent : envAgent) as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const key = dispatcherCacheKey(normalized, streaming)\n const cached = dispatcherCache.get(key)\n if (cached) return cached\n const famClean = normalized.replace(/\\?family=(ipv4|ipv6)$/, '')\n let dispatcher: any\n if (famClean.startsWith('socks5:')) {\n dispatcher = await createSocksDispatcher(normalized, dispatcherOptsFor(streaming) as any)\n } else {\n dispatcher = createHttpDispatcher(normalized, dispatcherOptsFor(streaming) as any)\n }\n dispatcherCache.set(key, dispatcher)\n return dispatcher\n}\n\nexport function _clearDispatcherCacheForTest(): void {\n dispatcherCache.clear()\n}\n\nfunction getTargetUrlString(input: Parameters<typeof fetch>[0]): string {\n if (typeof input === 'string') return input\n try {\n if (input instanceof URL) return input.toString()\n if (typeof (input as Request).url === 'string') return (input as Request).url\n } catch {\n // ignore\n }\n return ''\n}\n\n/** Options accepted by proxiedFetch; `streaming` selects the long-silence dispatcher. */\nexport interface ProxiedFetchOptions {\n proxyUrl?: string\n /**\n * True for a generation stream (long model silences are normal). Selects a\n * dispatcher without the per-gap body inactivity timer.\n */\n streaming?: boolean\n}\n\n/** fetch() that respects per-account proxyUrl (when given) otherwise env. */\nexport const proxiedFetch = async (\n input: Parameters<typeof fetch>[0],\n init?: Parameters<typeof fetch>[1] & { proxyUrl?: string },\n opts?: ProxiedFetchOptions,\n): Promise<Response> => {\n const proxyUrl = (opts as any)?.proxyUrl ?? (init as any)?.proxyUrl\n const streaming = opts?.streaming === true\n // Normalize init without proxyUrl leakage\n const cleanInit = { ...init } as any\n if (cleanInit.proxyUrl) delete cleanInit.proxyUrl\n if (!proxyUrl) {\n return fetch(input, { ...cleanInit, dispatcher: (streaming ? envStreamingAgent : envAgent) as any })\n }\n // Loopback bypass: per-account proxy must never intercept OAuth loopback callback (spec 1)\n const targetStr = getTargetUrlString(input)\n if (targetStr && isLoopbackUrl(targetStr)) {\n return fetch(input, cleanInit as RequestInit)\n }\n // Fast-fail pre-check (skip for env path)\n try {\n const reachable = await isProxyReachable(proxyUrl)\n if (!reachable) {\n const err = new Error(`[Proxy Fast-Fail] Proxy unreachable: ${proxyUrlForLogs(proxyUrl)}`) as Error & { code?: string; errorCode?: string; statusCode?: number }\n err.code = 'PROXY_UNREACHABLE'\n err.errorCode = 'proxy_unreachable'\n ;(err as any).statusCode = 503\n throw err\n }\n } catch (e) {\n if ((e as any)?.errorCode === 'proxy_unreachable') throw e\n // normalization failure -> throw\n if (e instanceof Error && e.message.startsWith('[proxy]')) throw e\n }\n\n let dispatcher: any\n try {\n dispatcher = await dispatcherForAsync(proxyUrl, { streaming })\n } catch (e) {\n throw tagProxyUnreachable(e)\n }\n try {\n return await fetch(input, { ...cleanInit, dispatcher })\n } catch (err) {\n // Tag proxy unreachable so caller can classify\n throw tagProxyUnreachable(err)\n }\n}\n\n// Keep named export for tests that assert instanceof\nexport { envAgent as _envAgentForTest }\n\n/**\n * Bind one account's routing to a fetch implementation.\n *\n * The single entry point every account-scoped call uses (generation, test call,\n * model/quota discovery, project healing, import enrichment). Threading a bare\n * `proxyUrl` at each site is what let six call sites drift and silently route a\n * proxied account over the host's real IP, so the invariant is enforced here\n * instead of by review (AGENTS.md \"Proxy Routing\").\n */\nexport function accountFetch(routing: AccountRouting | undefined): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) =>\n proxiedFetch(input, init, {\n ...(routing?.proxyUrl ? { proxyUrl: routing.proxyUrl } : {}),\n ...(routing?.streaming ? { streaming: true } : {}),\n })) as typeof fetch\n}\n\n/** Whether these requests are pinned to an explicit per-account proxy. */\nexport { isProxyRouted }\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"translate-D3GFUpw4.mjs","names":[],"sources":["../src/runtime/signature-cache.ts","../src/adapter/multimodal.ts","../src/adapter/translate.ts"],"sourcesContent":["/**\n * Gemini thought-signature cache for Antigravity tool calls.\n *\n * The Antigravity backend requires every outbound `functionCall` part to carry\n * a `thoughtSignature` (400 \"Function call is missing a thought_signature\"\n * otherwise). Signatures arrive on the *response* side — as a sibling field of\n * functionCall parts, and on thought parts — and must be replayed on the next\n * turn for the same tool call id. When nothing is cached, the translator\n * injects the `skip_thought_signature_validator` sentinel (the established\n * bypass both reference implementations use by default).\n *\n * Process-local with TTL; a cold process falls back to the sentinel, which the\n * upstream accepts, so cross-process persistence is not required for\n * correctness.\n */\n\nexport const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator'\n\ninterface CacheEntry {\n signature: string\n expiresAt: number\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\nconst MAX_ENTRIES = 2000\n\nconst store = new Map<string, CacheEntry>()\n\nfunction pruneExpired(now = Date.now()): void {\n if (store.size < MAX_ENTRIES / 2) return\n for (const [key, entry] of store) {\n if (entry.expiresAt <= now) store.delete(key)\n }\n}\n\n/** Store the signature observed for one tool call id (response side). */\nexport function setThoughtSignature(toolCallId: string, signature: string, ttlMs = DEFAULT_TTL_MS): void {\n if (!toolCallId || !signature) return\n pruneExpired()\n if (store.size >= MAX_ENTRIES) {\n // Evict the oldest entry to keep the cache bounded.\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of store) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey) store.delete(oldestKey)\n }\n store.set(toolCallId, { signature, expiresAt: Date.now() + ttlMs })\n}\n\n/** Resolve the signature for one tool call id, or null when unknown/expired. */\nexport function getThoughtSignature(toolCallId: string): string | null {\n if (!toolCallId) return null\n const entry = store.get(toolCallId)\n if (!entry) return null\n if (entry.expiresAt <= Date.now()) {\n store.delete(toolCallId)\n return null\n }\n return entry.signature\n}\n","/**\n * Multimodal file resolution for Google Gemini on Antigravity.\n *\n * In DeepSeek Harness (DSH), non-image files (PDF, audio, video, etc.)\n * uploaded by users are converted by DSH's LLM runtime into deterministic\n * text handles:\n *\n * `[File \"${name}\" (${bytes} bytes, sha256:${digest}): verbatim read-only copy saved at \"${readonlyPath}\". ...]`\n *\n * Gemini models natively support these files as base64-encoded `inlineData`\n * parts alongside text. Claude models running behind the Antigravity Vertex\n * proxy reject non-image `inlineData` with 500, so injection is scoped strictly\n * to Gemini (non-Claude, vision-capable) models.\n */\n\nimport { readFile } from 'node:fs/promises'\nimport type { GenerateOptions, Message } from '@deepseek-ai/dsh-llm'\nimport { catalogModel } from './catalog.ts'\n\n/**\n * MIME type mapping for Gemini supported multimodal formats.\n *\n * Covered formats (PDF observed live end-to-end; the rest follow Gemini's\n * documented multimodal support and are not yet live-verified):\n * - Document: .pdf -> 'application/pdf'\n * - Audio: .mp3 -> 'audio/mp3', .wav -> 'audio/wav', .m4a -> 'audio/m4a', .aac -> 'audio/aac', .ogg -> 'audio/ogg', .flac -> 'audio/flac'\n * - Video: .mp4 -> 'video/mp4', .mov -> 'video/quicktime', .webm -> 'video/webm'\n * - Extended image: .bmp -> 'image/bmp', .heic -> 'image/heic', .heif -> 'image/heif'\n */\nexport const GEMINI_MULTIMODAL_MIMES: Readonly<Record<string, string>> = Object.freeze({\n pdf: 'application/pdf',\n mp3: 'audio/mp3',\n wav: 'audio/wav',\n m4a: 'audio/m4a',\n aac: 'audio/aac',\n ogg: 'audio/ogg',\n flac: 'audio/flac',\n mp4: 'video/mp4',\n mov: 'video/quicktime',\n webm: 'video/webm',\n bmp: 'image/bmp',\n heic: 'image/heic',\n heif: 'image/heif',\n '.pdf': 'application/pdf',\n '.mp3': 'audio/mp3',\n '.wav': 'audio/wav',\n '.m4a': 'audio/m4a',\n '.aac': 'audio/aac',\n '.ogg': 'audio/ogg',\n '.flac': 'audio/flac',\n '.mp4': 'video/mp4',\n '.mov': 'video/quicktime',\n '.webm': 'video/webm',\n '.bmp': 'image/bmp',\n '.heic': 'image/heic',\n '.heif': 'image/heif',\n})\n\n/**\n * Regex matching DSH's deterministic read-only file handle blocks.\n * Capture groups:\n * 1: file name\n * 2: file size in bytes\n * 3: local read-only filesystem path\n */\nexport const DSH_FILE_HANDLE_REGEX =\n /\\[File \"([^\"]+)\" \\((\\d+) bytes, sha256:[a-f0-9]+\\): verbatim read-only copy saved at \"([^\"]+)\"\\./g\n\nexport const FILE_HANDLE_REGEX = DSH_FILE_HANDLE_REGEX\n\n/** Maximum file size for inlining (20MB). */\nexport const MAX_MULTIMODAL_FILE_BYTES = 20 * 1024 * 1024\nexport const MAX_MULTIMODAL_FILE_SIZE = MAX_MULTIMODAL_FILE_BYTES\n\nexport interface ExtractedFileHandle {\n name: string\n bytes: number\n readonlyPath: string\n}\n\n/** Extract file handle descriptors from text content. */\nexport function extractFileHandles(text: string): ExtractedFileHandle[] {\n const regex = new RegExp(DSH_FILE_HANDLE_REGEX.source, 'g')\n const results: ExtractedFileHandle[] = []\n for (const match of text.matchAll(regex)) {\n const [, name, bytesStr, readonlyPath] = match\n if (name && bytesStr && readonlyPath) {\n const bytes = parseInt(bytesStr, 10)\n if (!Number.isNaN(bytes)) {\n results.push({ name, bytes, readonlyPath })\n }\n }\n }\n return results\n}\n\n/** Resolve MIME type for a file name or path based on extension. */\nexport function getMultimodalMimeType(filenameOrPath: string): string | undefined {\n const dotIndex = filenameOrPath.lastIndexOf('.')\n if (dotIndex === -1) return undefined\n const ext = filenameOrPath.slice(dotIndex + 1).toLowerCase()\n return GEMINI_MULTIMODAL_MIMES[ext]\n}\n\n/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */\nexport function isClaudeModel(model: string): boolean {\n return model.startsWith('claude-') || model.includes('/claude')\n}\n\n/**\n * Check whether a model supports Gemini multimodal file inlineData.\n * Deny-by-default: Claude models are always excluded (Vertex 500 on\n * non-image inlineData), catalog models must be vision-capable, and ids\n * unknown to the catalog are only allowed when they carry the `gemini-`\n * prefix (so future tiered ids keep working without a catalog bump).\n */\nexport function supportsMultimodalFiles(model: string): boolean {\n if (isClaudeModel(model)) return false\n const meta = catalogModel(model)\n if (meta) return meta.supportsVision === true\n return model.startsWith('gemini-')\n}\n\nexport const isMultimodalSupported = supportsMultimodalFiles\nexport const isMultimodalModel = supportsMultimodalFiles\n\nexport interface AgyResolvedMultimodalFile {\n mimeType: string\n /** Pure base64 (no data: prefix). */\n data: string\n name?: string\n path?: string\n bytes?: number\n}\n\nexport interface ResolveMultimodalOptions {\n readFile?: (path: string) => Promise<Buffer | Uint8Array>\n}\n\n/**\n * Resolve multimodal files referenced in user messages.\n *\n * Reads local files up to 20MB into base64 strings. Silently ignores\n * missing/unreadable files or files exceeding the size limit so the original\n * text handle block remains intact in the prompt.\n */\nexport async function resolveMultimodalFiles(\n optionsOrMessages: GenerateOptions | readonly Message[],\n modelOrOptions?: string | ResolveMultimodalOptions,\n extraOptions?: ResolveMultimodalOptions,\n): Promise<Map<string, AgyResolvedMultimodalFile[]>> {\n let messages: readonly Message[]\n let model: string\n let customOptions: ResolveMultimodalOptions | undefined\n\n if ('messages' in optionsOrMessages) {\n messages = optionsOrMessages.messages ?? []\n model = optionsOrMessages.model ?? ''\n customOptions = typeof modelOrOptions === 'object' ? modelOrOptions : extraOptions\n } else {\n messages = optionsOrMessages\n if (typeof modelOrOptions === 'string') {\n model = modelOrOptions\n customOptions = extraOptions\n } else {\n model = ''\n customOptions = modelOrOptions\n }\n }\n\n const result = new Map<string, AgyResolvedMultimodalFile[]>()\n if (!supportsMultimodalFiles(model)) {\n return result\n }\n\n const readFn = customOptions?.readFile ?? readFile\n\n for (let i = 0; i < messages.length; i++) {\n const message = messages[i]\n if (!message || message.role !== 'user') continue\n\n const resolvedForMessage: AgyResolvedMultimodalFile[] = []\n for (const block of message.content) {\n if (block.type !== 'text' || typeof block.text !== 'string') continue\n\n const handles = extractFileHandles(block.text)\n for (const handle of handles) {\n if (handle.bytes > MAX_MULTIMODAL_FILE_BYTES) continue\n\n const mimeType = getMultimodalMimeType(handle.name) ?? getMultimodalMimeType(handle.readonlyPath)\n if (!mimeType) continue\n\n try {\n const fileBuffer = await readFn(handle.readonlyPath)\n if (fileBuffer.length > MAX_MULTIMODAL_FILE_BYTES) continue\n resolvedForMessage.push({\n mimeType,\n data: Buffer.from(fileBuffer).toString('base64'),\n name: handle.name,\n path: handle.readonlyPath,\n bytes: fileBuffer.length,\n })\n } catch {\n // Error resilience: catch error silently and do not throw\n continue\n }\n }\n }\n\n if (resolvedForMessage.length > 0) {\n const key = message.id ?? `msg-${i}`\n result.set(key, resolvedForMessage)\n }\n }\n\n return result\n}\n","/**\n * Translate a DSH GenerateOptions into the Antigravity wrapped request.\n *\n * Envelope shape follows the actively-maintained OmniRoute wire format\n * (the archived opencode reference predates it): top-level `project`,\n * `requestId`, `model`, `userAgent`, `requestType`, with the Gemini-style\n * body under `request` (contents/systemInstruction/tools/generationConfig/\n * sessionId). `toolConfig` VALIDATED is attached when tools are present, and\n * Claude-path requests strip trailing model turns (Vertex rejects \"assistant\n * message prefill\").\n *\n * Thinking blocks are carried as-is on the Gemini path (Gemini `thought`\n * parts); no thought is ever re-signed — that signature dance was an artifact\n * of the reference plugin's interception architecture (see\n * docs/ARCHITECTURE.md). The Claude path is stricter and drops thought parts\n * entirely, because its validator demands a real thinking signature that only\n * the originating model can produce (docs/ANTIGRAVITY-API.md §3.3).\n */\n\nimport { createHash } from 'node:crypto'\nimport type { ContentBlock, GenerateOptions, Message, ToolSchema } from '@deepseek-ai/dsh-llm'\nimport { generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { getThoughtSignature, THOUGHT_SIGNATURE_SENTINEL } from '../runtime/signature-cache.ts'\nimport { catalogModel, isLevelThinkingModel } from './catalog.ts'\nimport { isClaudeModel, supportsMultimodalFiles, type AgyResolvedMultimodalFile } from './multimodal.ts'\n\nexport { isClaudeModel, supportsMultimodalFiles }\nexport type { AgyResolvedMultimodalFile }\n\nexport type AgyPart =\n | { text: string }\n | { thought: true; text: string }\n | { thoughtSignature: string; functionCall: { id: string; name: string; args: unknown } }\n | { functionResponse: { id: string; name: string; response: unknown } }\n | { inlineData: { mimeType: string; data: string } }\n\n/** Image bytes pre-resolved from the durable attachment store, keyed by attachment id. */\nexport interface AgyResolvedImage {\n mediaType: string\n /** Pure base64 (no data: prefix). */\n data: string\n}\n\nexport interface AgyContent {\n role: 'user' | 'model'\n parts: AgyPart[]\n}\n\nexport interface AgyRequestBody {\n project?: string\n requestId?: string\n model: string\n userAgent?: string\n requestType?: 'agent'\n request: {\n contents: AgyContent[]\n systemInstruction?: { parts: Array<{ text: string }> }\n tools?: Array<{ functionDeclarations: Array<{ name: string; description: string; parameters: unknown }> }>\n toolConfig?: { functionCallingConfig: { mode: 'VALIDATED' } }\n generationConfig?: {\n temperature?: number\n maxOutputTokens?: number\n stopSequences?: string[]\n thinkingConfig?: { thinkingLevel?: string; includeThoughts?: boolean; thinkingBudget?: number }\n }\n sessionId?: string\n }\n}\n\n// isClaudeModel is imported from ./multimodal.ts and re-exported above\n\n/**\n * Vertex (the Antigravity Claude backend) rejects conversations ending on an\n * assistant/model turn (\"assistant message prefill\"); never strip to empty.\n */\nexport function stripTrailingModelTurn(contents: AgyContent[]): AgyContent[] {\n while (contents.length > 1 && contents[contents.length - 1]?.role === 'model') {\n contents.pop()\n }\n return contents\n}\n\n/**\n * The Antigravity backend parses tool `parameters` as a strict protobuf\n * schema and rejects ANY unknown keyword with 400 (verified empirically:\n * `$schema`, `propertyNames`, `pattern`, `minLength`, ... each fail in turn).\n * Denylisting is whack-a-mole, so keep only the keywords the upstream\n * accepts. Container shapes are handled distinctly: `properties` is a\n * name->schema map (keys preserved), `items`/`additionalProperties` are\n * nested schemas (additionalProperties also accepts a boolean — live-verified\n * against the Antigravity upstream), `required`/`enum` are plain arrays.\n *\n * Keyword VALUES are also constrained by the protobuf shape (verified\n * empirically): `type` must be a single enum string (union arrays like\n * `[\"string\",\"number\"]` are rejected) and every `enum` item must be a\n * string (booleans/numbers are rejected). Values are normalized to the\n * nearest valid form instead of being dropped wholesale.\n */\n// Exported for the contract invariant test (tests/adapter.test.ts); not part of\n// the package public API (translate.ts is an internal module).\nexport const AGY_SCHEMA_ALLOWLIST = new Set([\n 'type', 'format', 'title', 'description', 'nullable',\n 'items', 'enum', 'default', 'properties', 'required', 'additionalProperties',\n])\nconst AGY_SCHEMA_MAP_KEYS = new Set(['properties'])\nconst AGY_SCHEMA_NESTED_KEYS = new Set(['items', 'additionalProperties'])\nconst AGY_SCHEMA_LIST_KEYS = new Set(['required', 'enum'])\n\nfunction sanitizeToolSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map((entry) => sanitizeToolSchema(entry))\n\n // Normalize union types: upstream `type` is a single enum string. Pick the\n // first non-null string type (`\"null\"` maps to the `nullable` keyword);\n // fall back to `string` when no usable type remains.\n let normalized = schema as Record<string, unknown>\n if (Array.isArray(normalized.type)) {\n const types = normalized.type.filter((t): t is string => typeof t === 'string' && t !== 'null')\n normalized = { ...normalized, type: types[0] ?? 'string' }\n }\n\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(normalized)) {\n if (!AGY_SCHEMA_ALLOWLIST.has(key)) continue\n if (AGY_SCHEMA_MAP_KEYS.has(key)) {\n const map: Record<string, unknown> = {}\n for (const [name, child] of Object.entries(value as Record<string, unknown>)) {\n map[name] = sanitizeToolSchema(child)\n }\n result[key] = map\n continue\n }\n if (AGY_SCHEMA_NESTED_KEYS.has(key)) {\n result[key] = sanitizeToolSchema(value)\n continue\n }\n if (AGY_SCHEMA_LIST_KEYS.has(key)) {\n // Upstream `enum` items must be strings; filter the rest and omit an\n // empty enum entirely (an empty array would be rejected too).\n if (key === 'enum' && Array.isArray(value)) {\n const filtered = value.filter((v): v is string => typeof v === 'string')\n if (filtered.length > 0) result[key] = filtered\n } else {\n result[key] = value\n }\n continue\n }\n result[key] = value\n }\n return result\n}\n\n/** Collect tool-call names by id so tool-result blocks can name their function. */\nfunction buildToolNameIndex(messages: readonly Message[]): Map<string, string> {\n const index = new Map<string, string>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'tool-call') {\n index.set(block.id, block.name)\n }\n }\n }\n return index\n}\n\nfunction blockToParts(\n block: ContentBlock,\n toolNames: Map<string, string>,\n images: Map<string, AgyResolvedImage>,\n /** Claude path: replayed thought blocks are rejected outright (see below). */\n dropThoughts = false,\n): AgyPart[] {\n switch (block.type) {\n case 'text':\n // An empty text part is rejected by the Anthropic-backed Claude path\n // (\"messages.N.content.M.text.text: Field required\") while the Gemini\n // path tolerates it. Upstream's own parts normalization drops empty\n // text, so drop it here for every family (live-verified). DSH emits\n // these as trailing zero-length blocks after a tool call.\n return block.text === '' ? [] : [{ text: block.text }]\n case 'reasoning':\n // Empty thought: same rejection class as `text` (\"thinking.thinking:\n // Field required\").\n if (block.text === '') return []\n // A replayed thought block cannot be sent to the Claude path at all: the\n // backend demands a thinking `signature`, and the\n // `skip_thought_signature_validator` sentinel that works for functionCall\n // parts is rejected here as an invalid signature (live-verified). Only\n // the model that produced the thought could re-sign it, so a thought that\n // came from another family (e.g. a mid-session switch from a Gemini\n // tiered model) has no valid form — drop it rather than 400. Gemini\n // accepts replayed thoughts, so this is Claude-only.\n if (dropThoughts) return []\n return [{ thought: true, text: block.text }]\n case 'tool-call': {\n // Upstream parses functionCall.args as google.protobuf.Struct and\n // rejects a raw string with 400. Guarantee an object: parse the string\n // form, fall back to {} when it is truncated/malformed (the failure\n // mode for long multi-turn histories).\n let args: unknown = {}\n if (typeof block.arguments === 'object' && block.arguments !== null && !Array.isArray(block.arguments)) {\n args = block.arguments\n } else if (typeof block.arguments === 'string') {\n try {\n const parsed: unknown = JSON.parse(block.arguments)\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n args = parsed\n }\n } catch {\n // truncated/malformed JSON -> empty object\n }\n }\n // Antigravity rejects functionCall parts without a thoughtSignature\n // (400). Replay the signature captured for this tool call id on the\n // previous turn; the sentinel is the established bypass when nothing is\n // cached (both reference implementations default to it).\n const signature = getThoughtSignature(block.id) ?? THOUGHT_SIGNATURE_SENTINEL\n return [{\n thoughtSignature: signature,\n functionCall: { id: block.id, name: block.name, args },\n }]\n }\n case 'tool-result': {\n const name = toolNames.get(block.toolCallId) ?? block.toolCallId\n const text = block.content\n .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')\n .map((b) => b.text)\n .join('\\n')\n return [{\n functionResponse: {\n // The Anthropic-backed Claude path requires tool_result.tool_use_id\n // and 400s without it (\"messages.N.content.M.tool_result.tool_use_id:\n // Field required\"); the Gemini path accepts the id too, so it is\n // always carried rather than branched per family (live-verified).\n id: block.toolCallId,\n name,\n response: { result: text, is_error: block.isError === true },\n },\n }]\n }\n case 'image': {\n // Bytes are pre-resolved by the adapter before translation; a missing\n // entry breaks that invariant and must fail loudly, never silently drop.\n const resolved = images.get(block.attachment.attachmentId)\n if (!resolved) {\n throw new Error(`agy translate: unresolved image attachment \"${block.attachment.attachmentId}\"`)\n }\n return [{ inlineData: { mimeType: resolved.mediaType, data: resolved.data } }]\n }\n default:\n return [] // unknown block types (merge-extensible) are skipped\n }\n}\n\nfunction messageToContent(\n message: Message,\n toolNames: Map<string, string>,\n images: Map<string, AgyResolvedImage>,\n multimodalFiles?: Map<string, AgyResolvedMultimodalFile[]>,\n messageIndex?: number,\n dropThoughts = false,\n): AgyContent | null {\n const parts = message.content.flatMap((block) =>\n // Non-user images are out of scope by policy (docs ANTIGRAVITY-API §3.2):\n // skip them like any other untranslatable block instead of tripping the\n // unresolved-map guard, which protects only the user-image invariant.\n block.type === 'image' && message.role !== 'user'\n ? []\n : blockToParts(block, toolNames, images, dropThoughts),\n )\n\n if (message.role === 'user' && multimodalFiles) {\n const files =\n (message.id ? multimodalFiles.get(message.id) : undefined) ??\n (multimodalFiles as Map<unknown, AgyResolvedMultimodalFile[]>).get(message) ??\n (messageIndex !== undefined ? multimodalFiles.get(`msg-${messageIndex}`) : undefined) ??\n (messageIndex !== undefined ? multimodalFiles.get(String(messageIndex)) : undefined)\n\n if (files) {\n for (const file of files) {\n parts.push({ inlineData: { mimeType: file.mimeType, data: file.data } })\n }\n }\n }\n\n if (parts.length === 0) return null\n const role = message.role === 'assistant' ? 'model' : 'user'\n return { role, parts }\n}\n\n/**\n * Builtin Gemini tools must not shadow functionDeclarations names (upstream\n * treats them as native tools; verified by OmniRoute's GEMINI_BUILTIN_TOOL_NAMES).\n */\nconst AGY_BUILTIN_TOOL_NAMES = new Set(['google_search', 'web_search', 'search_web', 'googleSearch'])\n\n/** Level-thinking: single id + selectable low/medium/high via thinkingLevel (catalog thinking:'level'). */\nconst LEVEL_THINKING_LEVELS = new Set(['low', 'medium', 'high'])\n\n/**\n * Claude-family output ceiling on the Antigravity channel (live-measured).\n *\n * The Claude models are served through the Gemini-style\n * `generationConfig.maxOutputTokens` field here, and this channel rejects the\n * Claude family above 64000: 64000 answers 200, 64001 answers 400\n * `INVALID_ARGUMENT: Request contains an invalid argument` (deterministic\n * across both Claude ids, with and without the full 87-tool payload). The\n * Gemini family accepts 65536 on the same endpoint, so the limit is\n * model-family-specific, not endpoint-wide.\n *\n * Do NOT reason about this number from Anthropic's public API limits. Agy may\n * front a self-hosted or otherwise gated Claude deployment whose capacity and\n * validation rules are its own; the only authority is what this channel\n * accepts, which is what the probe measures. The value here is that\n * measurement, nothing more.\n *\n * The catalog's `maxOutputTokens` is the harness-injected default\n * (`LlmResolvedModelInfo.defaultMaxTokens`), so a wrong value there makes every\n * Claude request fail. This clamp is the second line of defense: it also covers\n * an explicit `maxTokens` (agent preset / call config) and a dynamically\n * discovered Claude id absent from the pinned catalog.\n */\nexport const AGY_CLAUDE_MAX_OUTPUT_TOKENS = 64_000\n\n/** Upstream functionDeclarations names are `[a-zA-Z0-9_]` and ≤64 chars (OmniRoute-verified). */\nconst AGY_TOOL_NAME_MAX_LENGTH = 64\n\n/** Sanitize a tool name to the upstream charset/length; dedupe via a short hash. */\nfunction sanitizeToolName(name: string, seen: Set<string>): string {\n let candidate = name.replace(/[^a-zA-Z0-9_]/g, '_') || 'tool'\n if (candidate.length > AGY_TOOL_NAME_MAX_LENGTH || seen.has(candidate)) {\n const hash = createHash('sha256').update(candidate).digest('hex').slice(0, 8)\n const prefix = candidate.slice(0, AGY_TOOL_NAME_MAX_LENGTH - hash.length - 1)\n candidate = `${prefix}_${hash}`\n let i = 2\n while (seen.has(candidate)) candidate = `${prefix}_${i++}_${hash}`\n }\n seen.add(candidate)\n return candidate\n}\n\nfunction toolsToDeclarations(tools: ToolSchema[] | undefined): AgyRequestBody['request']['tools'] {\n if (!tools || tools.length === 0) return undefined\n const seenNames = new Set<string>()\n const declarations = []\n for (const tool of tools) {\n if (AGY_BUILTIN_TOOL_NAMES.has(tool.name)) continue\n declarations.push({\n name: sanitizeToolName(tool.name, seenNames),\n description: tool.description,\n parameters: sanitizeToolSchema(tool.parameters),\n })\n }\n if (declarations.length === 0) return undefined\n return [{ functionDeclarations: declarations }]\n}\n\n/** Build the wrapped Antigravity request body for one call. */\nexport function toAgyRequestBody(\n options: GenerateOptions,\n context: {\n projectId?: string\n sessionId?: string\n images?: Map<string, AgyResolvedImage>\n multimodalFiles?: Map<string, AgyResolvedMultimodalFile[]>\n },\n): AgyRequestBody {\n const toolNames = buildToolNameIndex(options.messages)\n const images = context.images ?? new Map<string, AgyResolvedImage>()\n const multimodalFiles = supportsMultimodalFiles(options.model) ? context.multimodalFiles : undefined\n const claude = isClaudeModel(options.model)\n let contents = options.messages\n .map((message, index) => messageToContent(message, toolNames, images, multimodalFiles, index, claude))\n .filter((c): c is AgyContent => c !== null)\n if (claude) {\n contents = stripTrailingModelTurn(contents)\n }\n\n const tools = toolsToDeclarations(options.tools)\n const generationConfig: NonNullable<AgyRequestBody['request']['generationConfig']> = {}\n if (options.temperature !== undefined) generationConfig.temperature = options.temperature\n if (options.maxTokens !== undefined) {\n // Claude family: the platform rejects >64000 with 400, and the value the\n // harness injects comes from the catalog default, so clamp before the wire.\n generationConfig.maxOutputTokens = isClaudeModel(options.model)\n ? Math.min(options.maxTokens, AGY_CLAUDE_MAX_OUTPUT_TOKENS)\n : options.maxTokens\n }\n if (options.stop !== undefined && options.stop.length > 0) generationConfig.stopSequences = options.stop\n // Level-thinking: map the DSH reasoning effort to thinkingConfig.\n // Id-bound models (thinking !== 'level') never emit it — default is UI hint, not wire default.\n // When purpose is 'session-title' or reasoning is off, thinkingBudget: 0 prevents\n // default thinking tokens from exhausting tight output caps (e.g. maxTokens: 64).\n const effort = options.reasoningEffort?.toLowerCase()\n if (isLevelThinkingModel(options.model)) {\n if (options.purpose === 'session-title' || effort === 'none' || effort === 'off') {\n generationConfig.thinkingConfig = { thinkingBudget: 0 }\n } else if (effort && LEVEL_THINKING_LEVELS.has(effort)) {\n generationConfig.thinkingConfig = { thinkingLevel: effort, includeThoughts: true }\n }\n }\n\n return {\n project: context.projectId || undefined,\n requestId: generateAntigravityRequestId(),\n model: options.model,\n userAgent: 'antigravity',\n requestType: 'agent',\n request: {\n contents,\n ...(options.system ? { systemInstruction: { parts: [{ text: options.system }] } } : {}),\n ...(tools ? { tools } : {}),\n ...(tools ? { toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } } } : {}),\n ...(Object.keys(generationConfig).length > 0 ? { generationConfig } : {}),\n ...(context.sessionId ? { sessionId: context.sessionId } : {}),\n },\n }\n}\n"],"mappings":"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"}