dsh-agy 0.2.6 → 0.2.8
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.
- package/lib/{accounts-CjOuS29o.mjs → accounts-Um8N9I56.mjs} +162 -36
- package/lib/accounts-Um8N9I56.mjs.map +1 -0
- package/lib/cli/index.mjs +4 -4
- package/lib/{exchange-DNrUFqeJ.mjs → exchange-Bdql4kf3.mjs} +2 -2
- package/lib/{exchange-DNrUFqeJ.mjs.map → exchange-Bdql4kf3.mjs.map} +1 -1
- package/lib/{import-CL05Tqhk.mjs → import-BkqCMX7m.mjs} +3 -3
- package/lib/{import-CL05Tqhk.mjs.map → import-BkqCMX7m.mjs.map} +1 -1
- package/lib/index.mjs +2 -2
- package/lib/{models-BTMqI4Md.mjs → models-F-1cWO0f.mjs} +74 -4
- package/lib/models-F-1cWO0f.mjs.map +1 -0
- package/lib/{plugin-common-DWSiyQG-.mjs → plugin-common-DHQVJtXd.mjs} +27 -11
- package/lib/plugin-common-DHQVJtXd.mjs.map +1 -0
- package/lib/{translate-CfeRpHDe.mjs → translate-CE9tNXiV.mjs} +187 -14
- package/lib/translate-CE9tNXiV.mjs.map +1 -0
- package/lib/web/plugin.mjs +6 -6
- package/package.json +3 -2
- package/lib/accounts-CjOuS29o.mjs.map +0 -1
- package/lib/models-BTMqI4Md.mjs.map +0 -1
- package/lib/plugin-common-DWSiyQG-.mjs.map +0 -1
- package/lib/translate-CfeRpHDe.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./accounts-
|
|
2
|
+
import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./accounts-Um8N9I56.mjs";
|
|
3
3
|
import { i as proxiedFetch } from "./proxy-DQPIwUov.mjs";
|
|
4
4
|
import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-DVHPJjfc.mjs";
|
|
5
5
|
import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
@@ -47,6 +47,75 @@ const AGY_TEXT_ONLY_MODALITIES = ["text"];
|
|
|
47
47
|
function inputModalitiesFor(meta) {
|
|
48
48
|
return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES];
|
|
49
49
|
}
|
|
50
|
+
/** Role keys whose members are not agent chat models. */
|
|
51
|
+
const NON_CHAT_ROLE_KEYS = [
|
|
52
|
+
"tabModelIds",
|
|
53
|
+
"imageGenerationModelIds",
|
|
54
|
+
"audioTranscriptionModelIds"
|
|
55
|
+
];
|
|
56
|
+
/** The payload arrives via an unvalidated cast, so every shape is re-checked. */
|
|
57
|
+
function stringsFrom(value) {
|
|
58
|
+
if (!Array.isArray(value)) return [];
|
|
59
|
+
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Ids the payload itself advertises: the default agent model, anything in an
|
|
63
|
+
* `agentModelSorts` group, and every tiered family member. These are never
|
|
64
|
+
* hidden. If upstream both recommends an id and files it under a non-chat
|
|
65
|
+
* role, the recommendation is the more recent statement of intent, and this
|
|
66
|
+
* keeps a labelling mistake from removing a working model.
|
|
67
|
+
*/
|
|
68
|
+
function advertisedModelIds(dynamic) {
|
|
69
|
+
const advertised = /* @__PURE__ */ new Set();
|
|
70
|
+
if (typeof dynamic.defaultAgentModelId === "string" && dynamic.defaultAgentModelId.length > 0) advertised.add(dynamic.defaultAgentModelId);
|
|
71
|
+
for (const sort of Array.isArray(dynamic.agentModelSorts) ? dynamic.agentModelSorts : []) for (const group of Array.isArray(sort?.groups) ? sort.groups : []) for (const id of stringsFrom(group?.modelIds)) advertised.add(id);
|
|
72
|
+
const tiered = dynamic.tieredModelIds;
|
|
73
|
+
if (tiered && typeof tiered === "object" && !Array.isArray(tiered)) for (const family of Object.values(tiered)) for (const id of stringsFrom(family)) advertised.add(id);
|
|
74
|
+
return advertised;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Discovered ids that should not appear in a chat model list, according to the
|
|
78
|
+
* payload's own role assignments.
|
|
79
|
+
*
|
|
80
|
+
* This is the judgement `isChatCallableModelId` already makes with the `tab_`
|
|
81
|
+
* prefix, but taken from upstream's list instead of a name guess. That matters:
|
|
82
|
+
* `tabModelIds` members are not required to start with `tab_` (live accounts
|
|
83
|
+
* return ids such as `chat_20706`), so those ids would otherwise reach the
|
|
84
|
+
* model list, where they render as a raw id with no metadata.
|
|
85
|
+
*
|
|
86
|
+
* Only roles that mean "different output modality" or "superseded" are
|
|
87
|
+
* consumed. The utility roles (`commandModelIds`, `mqueryModelIds`,
|
|
88
|
+
* `webSearchModelIds`, `commitMessageModelIds`) name ordinary chat models doing
|
|
89
|
+
* a side job - on a live account all three of the latter name
|
|
90
|
+
* `gemini-3.1-flash-lite`, which is a pinned catalog model.
|
|
91
|
+
*
|
|
92
|
+
* A deprecated id is hidden only when its `newModelId` is present in `models`,
|
|
93
|
+
* chat-callable, and not role-hidden; otherwise hiding it would remove the only
|
|
94
|
+
* route to that capability on an account whose tier does not carry the
|
|
95
|
+
* replacement. Replacement availability is judged against role hiding alone and
|
|
96
|
+
* never against other deprecations, so a chain A -> B -> C resolves the same
|
|
97
|
+
* way whatever order the payload happens to serialize its keys in.
|
|
98
|
+
*
|
|
99
|
+
* Not the complete hiding rule: the `tab_` prefix check stays in
|
|
100
|
+
* `mergeModelCatalog`, because the catalog-only fallback has no payload.
|
|
101
|
+
*/
|
|
102
|
+
function hiddenDiscoveredIds(dynamic) {
|
|
103
|
+
const models = dynamic.models ?? {};
|
|
104
|
+
const advertised = advertisedModelIds(dynamic);
|
|
105
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
106
|
+
for (const key of NON_CHAT_ROLE_KEYS) for (const id of stringsFrom(dynamic[key])) if (!advertised.has(id)) hidden.add(id);
|
|
107
|
+
const roleHidden = new Set(hidden);
|
|
108
|
+
const deprecated = dynamic.deprecatedModelIds;
|
|
109
|
+
if (deprecated && typeof deprecated === "object" && !Array.isArray(deprecated)) for (const [retiredId, info] of Object.entries(deprecated)) {
|
|
110
|
+
if (advertised.has(retiredId)) continue;
|
|
111
|
+
const replacement = info?.newModelId;
|
|
112
|
+
if (typeof replacement !== "string" || replacement.length === 0 || replacement === retiredId) continue;
|
|
113
|
+
if (!Object.hasOwn(models, replacement)) continue;
|
|
114
|
+
if (!isChatCallableModelId(replacement) || roleHidden.has(replacement)) continue;
|
|
115
|
+
hidden.add(retiredId);
|
|
116
|
+
}
|
|
117
|
+
return hidden;
|
|
118
|
+
}
|
|
50
119
|
/** Fetch the account's available models from the first reachable endpoint. */
|
|
51
120
|
async function fetchAvailableModels(accessToken, projectId, fetchImpl = proxiedFetch) {
|
|
52
121
|
let lastError = null;
|
|
@@ -68,11 +137,12 @@ async function fetchAvailableModels(accessToken, projectId, fetchImpl = proxiedF
|
|
|
68
137
|
}
|
|
69
138
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("fetchAvailableModels: all endpoints failed");
|
|
70
139
|
}
|
|
71
|
-
/** Merge dynamic ids with catalog metadata; non-chat
|
|
140
|
+
/** Merge dynamic ids with catalog metadata; non-chat and superseded ids are dropped, unknown ids keep minimal info. */
|
|
72
141
|
function mergeModelCatalog(dynamic) {
|
|
73
142
|
const entries = [];
|
|
143
|
+
const hidden = hiddenDiscoveredIds(dynamic);
|
|
74
144
|
for (const [id, entry] of Object.entries(dynamic.models ?? {})) {
|
|
75
|
-
if (!isChatCallableModelId(id)) continue;
|
|
145
|
+
if (!isChatCallableModelId(id) || hidden.has(id)) continue;
|
|
76
146
|
const meta = catalogModel(id);
|
|
77
147
|
const displayName = (entry.displayName && entry.displayName !== id ? entry.displayName : void 0) ?? meta?.name ?? entry.displayName ?? entry.modelName ?? id;
|
|
78
148
|
entries.push({
|
|
@@ -134,4 +204,4 @@ function resolveAgyModel(provider, model) {
|
|
|
134
204
|
//#endregion
|
|
135
205
|
export { resolveAgyModel as i, listAgyModels as n, models_exports as r, catalogModelList as t };
|
|
136
206
|
|
|
137
|
-
//# sourceMappingURL=models-
|
|
207
|
+
//# sourceMappingURL=models-F-1cWO0f.mjs.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;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,8 +1,8 @@
|
|
|
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-
|
|
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
2
|
import { n as isProxyUnreachableError } from "./proxy-DQPIwUov.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { i as setThoughtSignature, r as resolveMultimodalFiles, t as toAgyRequestBody } from "./translate-CE9tNXiV.mjs";
|
|
4
4
|
import { t as parseAgySse } from "./parse-Bs4rPkHA.mjs";
|
|
5
|
-
import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-
|
|
5
|
+
import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-F-1cWO0f.mjs";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
|
|
8
8
|
//#region src/runtime/classify.ts
|
|
@@ -142,6 +142,8 @@ function collectImageRefs(options) {
|
|
|
142
142
|
return refs;
|
|
143
143
|
}
|
|
144
144
|
const UPSTREAM_ERROR_CODE = "UPSTREAM";
|
|
145
|
+
/** First-class DSH retryable code: the default retry policy honors SERVER (5xx), not UPSTREAM. */
|
|
146
|
+
const SERVER_ERROR_CODE = "SERVER";
|
|
145
147
|
/** Build the impersonation headers for one request (per-request randomization applied by the shell). */
|
|
146
148
|
function buildRequestHeaders(session) {
|
|
147
149
|
return {
|
|
@@ -195,15 +197,23 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
195
197
|
if (refs.length === 0) return images;
|
|
196
198
|
const store = this.options.resolveAttachments?.();
|
|
197
199
|
if (!store) throw new LlmError("agy image input requires the durable attachment service (in-harness plugin only)", "UNSUPPORTED_CONTENT");
|
|
198
|
-
|
|
200
|
+
const settled = await Promise.allSettled(refs.map(async (ref) => {
|
|
199
201
|
const stored = await store.readImage(ref);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
return {
|
|
203
|
+
attachmentId: ref.attachmentId,
|
|
204
|
+
image: {
|
|
205
|
+
mediaType: stored.ref.mediaType,
|
|
206
|
+
data: Buffer.from(stored.data).toString("base64")
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}));
|
|
210
|
+
const failedIndex = settled.findIndex((outcome) => outcome.status === "rejected");
|
|
211
|
+
if (failedIndex !== -1) {
|
|
212
|
+
const ref = refs[failedIndex];
|
|
213
|
+
const cause = settled[failedIndex].reason;
|
|
205
214
|
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 });
|
|
206
215
|
}
|
|
216
|
+
for (const outcome of settled) if (outcome.status === "fulfilled") images.set(outcome.value.attachmentId, outcome.value.image);
|
|
207
217
|
return images;
|
|
208
218
|
}
|
|
209
219
|
async *stream(options) {
|
|
@@ -229,10 +239,12 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
229
239
|
throw error;
|
|
230
240
|
}
|
|
231
241
|
if (!session) throw new LlmError("No agy account configured — run `dsh-agy login` to authenticate.", "NO_CREDENTIAL");
|
|
242
|
+
const multimodalFiles = await resolveMultimodalFiles(options);
|
|
232
243
|
const body = toAgyRequestBody(options, {
|
|
233
244
|
projectId: session.account.projectId,
|
|
234
245
|
sessionId: deriveAntigravitySessionId(session.account.email) ?? void 0,
|
|
235
|
-
...images.size > 0 ? { images } : {}
|
|
246
|
+
...images.size > 0 ? { images } : {},
|
|
247
|
+
...multimodalFiles.size > 0 ? { multimodalFiles } : {}
|
|
236
248
|
});
|
|
237
249
|
const headers = buildRequestHeaders(session);
|
|
238
250
|
let response;
|
|
@@ -266,6 +278,10 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
266
278
|
});
|
|
267
279
|
}
|
|
268
280
|
if (classified.kind === "auth-failure") throw new LlmError(`agy authentication failed (${response.status}) — run \`dsh-agy login\``, "INVALID_CREDENTIAL");
|
|
281
|
+
if (classified.status !== void 0 && classified.status >= 500) throw new LlmError(`agy upstream error (${response.status}): ${classified.message ?? ""}`, SERVER_ERROR_CODE, {
|
|
282
|
+
providerRetryAfterMs: classified.retryAfterMs ?? void 0,
|
|
283
|
+
requestId: ProviderRequestId(generateAntigravityRequestId())
|
|
284
|
+
});
|
|
269
285
|
throw new LlmError(`agy upstream error (${response.status}): ${classified.message ?? ""}`, UPSTREAM_ERROR_CODE);
|
|
270
286
|
}
|
|
271
287
|
if (!response.body) throw new LlmError("agy stream returned no body", UPSTREAM_ERROR_CODE);
|
|
@@ -354,4 +370,4 @@ async function createAgyRuntime(ctx) {
|
|
|
354
370
|
//#endregion
|
|
355
371
|
export { createAgyRuntime as t };
|
|
356
372
|
|
|
357
|
-
//# sourceMappingURL=plugin-common-
|
|
373
|
+
//# sourceMappingURL=plugin-common-DHQVJtXd.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-common-DHQVJtXd.mjs","names":[],"sources":["../src/runtime/classify.ts","../src/adapter/adapter.ts","../src/plugin-common.ts"],"sourcesContent":["/**\n * Upstream failure classification: HTTP status + headers + body → FailureKind.\n * Conservative by design: only auth-failure may revoke an account; everything\n * else cools, rotates, or retries.\n */\n\nimport type { FailureKind } from '../types.ts'\nimport { isProxyUnreachableError } from '../proxy.ts'\n\nexport interface ClassifiedError {\n kind: FailureKind\n status?: number\n /** Server-provided retry delay (ms), when the response carried it. */\n retryAfterMs?: number\n /** Server-provided absolute reset time, when the response carried it. */\n resetTime?: string\n message?: string\n /** 429 sub-category, present when kind is rate-limit. */\n rateLimitCategory?: RateLimitCategory\n}\n\n/**\n * 429 sub-category (mirrors CLIProxyAPI/OmniRoute's classify429):\n * - soft_rate_limit: transient burst, retry immediately on the same account\n * - rate_limited: per-minute limit, short cooldown then retry (same account\n * when there is no other)\n * - quota_exhausted: daily/plan quota gone, long cooldown (24h)\n * - unknown: exponential backoff\n */\nexport type RateLimitCategory = 'soft_rate_limit' | 'rate_limited' | 'quota_exhausted' | 'unknown'\n\nconst QUOTA_EXHAUSTED_KEYWORDS = [\n 'quota_exhausted',\n 'quota exhausted',\n 'quota reached',\n 'enable overages',\n 'individual quota',\n]\n\n/** Classify a 429 body into the four upstream categories. */\nexport function classifyRateLimit(\n bodyText: string | undefined,\n retryAfterMs: number | undefined,\n): RateLimitCategory {\n const text = (bodyText ?? '').toLowerCase()\n if (QUOTA_EXHAUSTED_KEYWORDS.some((keyword) => text.includes(keyword))) {\n return 'quota_exhausted'\n }\n if (retryAfterMs !== undefined && retryAfterMs < 3000) {\n return 'soft_rate_limit'\n }\n if (retryAfterMs !== undefined) {\n return 'rate_limited'\n }\n // No explicit retry hint: \"quota\" wording → daily quota, otherwise unknown.\n return text.includes('quota') || text.includes('resource_exhausted') ? 'quota_exhausted' : 'unknown'\n}\n\nconst RATE_LIMIT_RESET_FIELDS = ['resetTime', 'reset_time', 'resetAt', 'quotaResetTime'] as const\n\nfunction extractResetTime(bodyText: string | undefined): string | undefined {\n if (!bodyText) return undefined\n try {\n const data = JSON.parse(bodyText) as Record<string, unknown>\n for (const field of RATE_LIMIT_RESET_FIELDS) {\n const value = data[field]\n if (typeof value === 'string' && value) return value\n if (typeof value === 'number' && Number.isFinite(value)) {\n // Unix ms when large, seconds when small (relative to now).\n return value > 1_000_000_000_000 ? new Date(value).toISOString() : new Date(Date.now() + value * 1000).toISOString()\n }\n }\n const quotaInfo = data.quotaInfo as Record<string, unknown> | undefined\n if (quotaInfo && typeof quotaInfo.resetTime === 'string') return quotaInfo.resetTime\n } catch {\n // not JSON — no reset info\n }\n return undefined\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000\n const date = Date.parse(header)\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now())\n return undefined\n}\n\n/** Classify a completed HTTP response (non-2xx). */\nexport function classifyHttpError(\n status: number,\n headers: Headers,\n bodyText?: string,\n): ClassifiedError {\n const retryAfterMs = parseRetryAfter(headers.get('retry-after'))\n const resetTime = extractResetTime(bodyText)\n\n if (status === 429) {\n const category = classifyRateLimit(bodyText, retryAfterMs)\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n retryAfterMs,\n resetTime,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n if (status === 401) {\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 403) {\n // Google also reports quota walls as 403 RESOURCE_EXHAUSTED, and the\n // endpoint fallback chain ends on hosts answering 403 for \"no license\"\n // (not bad auth). Only treat a 403 as auth-failure when the body carries\n // no quota wording — a false revoke permanently disables the account.\n const category = classifyRateLimit(bodyText, undefined)\n if (category === 'quota_exhausted') {\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 404) {\n // Model/route missing — nothing to rotate for; surface as transient request error.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status >= 500) {\n return { kind: 'transient', status, retryAfterMs, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 400) {\n // A 400 is a request-construction error (permanent — retrying sends the\n // same broken payload). Only capacity-style phrases are recoverable\n // (context overflow / model unavailable), mirroring OmniRoute's\n // classifyProviderError: everything else surfaces as a terminal\n // request-error so rotation does not waste attempts.\n const text = (bodyText ?? '').toLowerCase()\n const recoverable = (\n (text.includes('context') && (text.includes('overflow') || text.includes('too long') || text.includes('exceeded')))\n || (text.includes('model') && (text.includes('not found') || text.includes('unavailable') || text.includes('not supported')))\n )\n if (recoverable) {\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n return { kind: 'request-error', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n // 400-range others (401 handled above): request-level errors, no rotation semantics.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n}\n\n/** Classify a fetch-level failure (DNS, refused, timeout, abort). */\nexport function classifyFetchError(error: unknown): ClassifiedError {\n const message = error instanceof Error ? error.message : String(error)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return { kind: 'network-error', message }\n }\n if (isProxyUnreachableError(error)) {\n return { kind: 'proxy-unreachable', message }\n }\n return { kind: 'network-error', message }\n}\n\n/** Classify the result of a refresh attempt (shared by adapter + verify paths). */\nexport function classifyRefreshFailure(status: number, code?: string): ClassifiedError {\n if (code === 'invalid_grant') return { kind: 'auth-failure', status, message: code }\n if (status === 429) return { kind: 'rate-limit', status, message: code }\n if (status >= 500) return { kind: 'transient', status, message: code }\n return { kind: 'transient', status, message: code }\n}\n\nexport type { FailureKind }\n","/**\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\nexport interface PreparedAdapterCall {\n model: LlmResolvedModelInfo\n stream: (options: GenerateOptions) => AsyncIterable<StreamChunk>\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 } from '../runtime/classify.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 return await listAgyModels(session?.auth.access, session?.account.projectId)\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 // ponytail: DSH 0.1.1-rc.2+ calls prepareCall instead of stream directly — keep compatible with old base without override\n async prepareCall(provider: string, model: string, _signal?: AbortSignal): Promise<PreparedAdapterCall> {\n return {\n model: await this.resolveModel(provider, model),\n stream: (options: GenerateOptions) => this.stream(options),\n }\n }\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 response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: options.signal,\n })\n } catch (error) {\n const classified = classifyFetchError(error)\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 throw new LlmError(\n error instanceof Error ? error.message : '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,7 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { v as generateAntigravityRequestId } from "./accounts-
|
|
3
|
-
import { i as isLevelThinkingModel } from "./catalog-DVHPJjfc.mjs";
|
|
2
|
+
import { v as generateAntigravityRequestId } from "./accounts-Um8N9I56.mjs";
|
|
3
|
+
import { i as isLevelThinkingModel, n as catalogModel } from "./catalog-DVHPJjfc.mjs";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
5
6
|
const DEFAULT_TTL_MS = 36e5;
|
|
6
7
|
const MAX_ENTRIES = 2e3;
|
|
7
8
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -39,6 +40,170 @@ function getThoughtSignature(toolCallId) {
|
|
|
39
40
|
return entry.signature;
|
|
40
41
|
}
|
|
41
42
|
//#endregion
|
|
43
|
+
//#region src/adapter/multimodal.ts
|
|
44
|
+
/**
|
|
45
|
+
* Multimodal file resolution for Google Gemini on Antigravity.
|
|
46
|
+
*
|
|
47
|
+
* In DeepSeek Harness (DSH), non-image files (PDF, audio, video, etc.)
|
|
48
|
+
* uploaded by users are converted by DSH's LLM runtime into deterministic
|
|
49
|
+
* text handles:
|
|
50
|
+
*
|
|
51
|
+
* `[File "${name}" (${bytes} bytes, sha256:${digest}): verbatim read-only copy saved at "${readonlyPath}". ...]`
|
|
52
|
+
*
|
|
53
|
+
* Gemini models natively support these files as base64-encoded `inlineData`
|
|
54
|
+
* parts alongside text. Claude models running behind the Antigravity Vertex
|
|
55
|
+
* proxy reject non-image `inlineData` with 500, so injection is scoped strictly
|
|
56
|
+
* to Gemini (non-Claude, vision-capable) models.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* MIME type mapping for Gemini supported multimodal formats.
|
|
60
|
+
*
|
|
61
|
+
* Covered formats (PDF observed live end-to-end; the rest follow Gemini's
|
|
62
|
+
* documented multimodal support and are not yet live-verified):
|
|
63
|
+
* - Document: .pdf -> 'application/pdf'
|
|
64
|
+
* - Audio: .mp3 -> 'audio/mp3', .wav -> 'audio/wav', .m4a -> 'audio/m4a', .aac -> 'audio/aac', .ogg -> 'audio/ogg', .flac -> 'audio/flac'
|
|
65
|
+
* - Video: .mp4 -> 'video/mp4', .mov -> 'video/quicktime', .webm -> 'video/webm'
|
|
66
|
+
* - Extended image: .bmp -> 'image/bmp', .heic -> 'image/heic', .heif -> 'image/heif'
|
|
67
|
+
*/
|
|
68
|
+
const GEMINI_MULTIMODAL_MIMES = Object.freeze({
|
|
69
|
+
pdf: "application/pdf",
|
|
70
|
+
mp3: "audio/mp3",
|
|
71
|
+
wav: "audio/wav",
|
|
72
|
+
m4a: "audio/m4a",
|
|
73
|
+
aac: "audio/aac",
|
|
74
|
+
ogg: "audio/ogg",
|
|
75
|
+
flac: "audio/flac",
|
|
76
|
+
mp4: "video/mp4",
|
|
77
|
+
mov: "video/quicktime",
|
|
78
|
+
webm: "video/webm",
|
|
79
|
+
bmp: "image/bmp",
|
|
80
|
+
heic: "image/heic",
|
|
81
|
+
heif: "image/heif",
|
|
82
|
+
".pdf": "application/pdf",
|
|
83
|
+
".mp3": "audio/mp3",
|
|
84
|
+
".wav": "audio/wav",
|
|
85
|
+
".m4a": "audio/m4a",
|
|
86
|
+
".aac": "audio/aac",
|
|
87
|
+
".ogg": "audio/ogg",
|
|
88
|
+
".flac": "audio/flac",
|
|
89
|
+
".mp4": "video/mp4",
|
|
90
|
+
".mov": "video/quicktime",
|
|
91
|
+
".webm": "video/webm",
|
|
92
|
+
".bmp": "image/bmp",
|
|
93
|
+
".heic": "image/heic",
|
|
94
|
+
".heif": "image/heif"
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Regex matching DSH's deterministic read-only file handle blocks.
|
|
98
|
+
* Capture groups:
|
|
99
|
+
* 1: file name
|
|
100
|
+
* 2: file size in bytes
|
|
101
|
+
* 3: local read-only filesystem path
|
|
102
|
+
*/
|
|
103
|
+
const DSH_FILE_HANDLE_REGEX = /\[File "([^"]+)" \((\d+) bytes, sha256:[a-f0-9]+\): verbatim read-only copy saved at "([^"]+)"\./g;
|
|
104
|
+
/** Extract file handle descriptors from text content. */
|
|
105
|
+
function extractFileHandles(text) {
|
|
106
|
+
const regex = new RegExp(DSH_FILE_HANDLE_REGEX.source, "g");
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const match of text.matchAll(regex)) {
|
|
109
|
+
const [, name, bytesStr, readonlyPath] = match;
|
|
110
|
+
if (name && bytesStr && readonlyPath) {
|
|
111
|
+
const bytes = parseInt(bytesStr, 10);
|
|
112
|
+
if (!Number.isNaN(bytes)) results.push({
|
|
113
|
+
name,
|
|
114
|
+
bytes,
|
|
115
|
+
readonlyPath
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
/** Resolve MIME type for a file name or path based on extension. */
|
|
122
|
+
function getMultimodalMimeType(filenameOrPath) {
|
|
123
|
+
const dotIndex = filenameOrPath.lastIndexOf(".");
|
|
124
|
+
if (dotIndex === -1) return void 0;
|
|
125
|
+
const ext = filenameOrPath.slice(dotIndex + 1).toLowerCase();
|
|
126
|
+
return GEMINI_MULTIMODAL_MIMES[ext];
|
|
127
|
+
}
|
|
128
|
+
/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */
|
|
129
|
+
function isClaudeModel(model) {
|
|
130
|
+
return model.startsWith("claude-") || model.includes("/claude");
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check whether a model supports Gemini multimodal file inlineData.
|
|
134
|
+
* Deny-by-default: Claude models are always excluded (Vertex 500 on
|
|
135
|
+
* non-image inlineData), catalog models must be vision-capable, and ids
|
|
136
|
+
* unknown to the catalog are only allowed when they carry the `gemini-`
|
|
137
|
+
* prefix (so future tiered ids keep working without a catalog bump).
|
|
138
|
+
*/
|
|
139
|
+
function supportsMultimodalFiles(model) {
|
|
140
|
+
if (isClaudeModel(model)) return false;
|
|
141
|
+
const meta = catalogModel(model);
|
|
142
|
+
if (meta) return meta.supportsVision === true;
|
|
143
|
+
return model.startsWith("gemini-");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Resolve multimodal files referenced in user messages.
|
|
147
|
+
*
|
|
148
|
+
* Reads local files up to 20MB into base64 strings. Silently ignores
|
|
149
|
+
* missing/unreadable files or files exceeding the size limit so the original
|
|
150
|
+
* text handle block remains intact in the prompt.
|
|
151
|
+
*/
|
|
152
|
+
async function resolveMultimodalFiles(optionsOrMessages, modelOrOptions, extraOptions) {
|
|
153
|
+
let messages;
|
|
154
|
+
let model;
|
|
155
|
+
let customOptions;
|
|
156
|
+
if ("messages" in optionsOrMessages) {
|
|
157
|
+
messages = optionsOrMessages.messages ?? [];
|
|
158
|
+
model = optionsOrMessages.model ?? "";
|
|
159
|
+
customOptions = typeof modelOrOptions === "object" ? modelOrOptions : extraOptions;
|
|
160
|
+
} else {
|
|
161
|
+
messages = optionsOrMessages;
|
|
162
|
+
if (typeof modelOrOptions === "string") {
|
|
163
|
+
model = modelOrOptions;
|
|
164
|
+
customOptions = extraOptions;
|
|
165
|
+
} else {
|
|
166
|
+
model = "";
|
|
167
|
+
customOptions = modelOrOptions;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const result = /* @__PURE__ */ new Map();
|
|
171
|
+
if (!supportsMultimodalFiles(model)) return result;
|
|
172
|
+
const readFn = customOptions?.readFile ?? readFile;
|
|
173
|
+
for (let i = 0; i < messages.length; i++) {
|
|
174
|
+
const message = messages[i];
|
|
175
|
+
if (!message || message.role !== "user") continue;
|
|
176
|
+
const resolvedForMessage = [];
|
|
177
|
+
for (const block of message.content) {
|
|
178
|
+
if (block.type !== "text" || typeof block.text !== "string") continue;
|
|
179
|
+
const handles = extractFileHandles(block.text);
|
|
180
|
+
for (const handle of handles) {
|
|
181
|
+
if (handle.bytes > 20971520) continue;
|
|
182
|
+
const mimeType = getMultimodalMimeType(handle.name) ?? getMultimodalMimeType(handle.readonlyPath);
|
|
183
|
+
if (!mimeType) continue;
|
|
184
|
+
try {
|
|
185
|
+
const fileBuffer = await readFn(handle.readonlyPath);
|
|
186
|
+
if (fileBuffer.length > 20971520) continue;
|
|
187
|
+
resolvedForMessage.push({
|
|
188
|
+
mimeType,
|
|
189
|
+
data: Buffer.from(fileBuffer).toString("base64"),
|
|
190
|
+
name: handle.name,
|
|
191
|
+
path: handle.readonlyPath,
|
|
192
|
+
bytes: fileBuffer.length
|
|
193
|
+
});
|
|
194
|
+
} catch {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (resolvedForMessage.length > 0) {
|
|
200
|
+
const key = message.id ?? `msg-${i}`;
|
|
201
|
+
result.set(key, resolvedForMessage);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
42
207
|
//#region src/adapter/translate.ts
|
|
43
208
|
/**
|
|
44
209
|
* Translate a DSH GenerateOptions into the Antigravity wrapped request.
|
|
@@ -59,12 +224,9 @@ var translate_exports = /* @__PURE__ */ __exportAll({
|
|
|
59
224
|
AGY_SCHEMA_ALLOWLIST: () => AGY_SCHEMA_ALLOWLIST,
|
|
60
225
|
isClaudeModel: () => isClaudeModel,
|
|
61
226
|
stripTrailingModelTurn: () => stripTrailingModelTurn,
|
|
227
|
+
supportsMultimodalFiles: () => supportsMultimodalFiles,
|
|
62
228
|
toAgyRequestBody: () => toAgyRequestBody
|
|
63
229
|
});
|
|
64
|
-
/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */
|
|
65
|
-
function isClaudeModel(model) {
|
|
66
|
-
return model.startsWith("claude-") || model.includes("/claude");
|
|
67
|
-
}
|
|
68
230
|
/**
|
|
69
231
|
* Vertex (the Antigravity Claude backend) rejects conversations ending on an
|
|
70
232
|
* assistant/model turn ("assistant message prefill"); never strip to empty.
|
|
@@ -187,8 +349,15 @@ function blockToParts(block, toolNames, images) {
|
|
|
187
349
|
default: return [];
|
|
188
350
|
}
|
|
189
351
|
}
|
|
190
|
-
function messageToContent(message, toolNames, images) {
|
|
352
|
+
function messageToContent(message, toolNames, images, multimodalFiles, messageIndex) {
|
|
191
353
|
const parts = message.content.flatMap((block) => block.type === "image" && message.role !== "user" ? [] : blockToParts(block, toolNames, images));
|
|
354
|
+
if (message.role === "user" && multimodalFiles) {
|
|
355
|
+
const files = (message.id ? multimodalFiles.get(message.id) : void 0) ?? multimodalFiles.get(message) ?? (messageIndex !== void 0 ? multimodalFiles.get(`msg-${messageIndex}`) : void 0) ?? (messageIndex !== void 0 ? multimodalFiles.get(String(messageIndex)) : void 0);
|
|
356
|
+
if (files) for (const file of files) parts.push({ inlineData: {
|
|
357
|
+
mimeType: file.mimeType,
|
|
358
|
+
data: file.data
|
|
359
|
+
} });
|
|
360
|
+
}
|
|
192
361
|
if (parts.length === 0) return null;
|
|
193
362
|
return {
|
|
194
363
|
role: message.role === "assistant" ? "model" : "user",
|
|
@@ -245,7 +414,8 @@ function toolsToDeclarations(tools) {
|
|
|
245
414
|
function toAgyRequestBody(options, context) {
|
|
246
415
|
const toolNames = buildToolNameIndex(options.messages);
|
|
247
416
|
const images = context.images ?? /* @__PURE__ */ new Map();
|
|
248
|
-
|
|
417
|
+
const multimodalFiles = supportsMultimodalFiles(options.model) ? context.multimodalFiles : void 0;
|
|
418
|
+
let contents = options.messages.map((message, index) => messageToContent(message, toolNames, images, multimodalFiles, index)).filter((c) => c !== null);
|
|
249
419
|
if (isClaudeModel(options.model)) contents = stripTrailingModelTurn(contents);
|
|
250
420
|
const tools = toolsToDeclarations(options.tools);
|
|
251
421
|
const generationConfig = {};
|
|
@@ -253,10 +423,13 @@ function toAgyRequestBody(options, context) {
|
|
|
253
423
|
if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
|
|
254
424
|
if (options.stop !== void 0 && options.stop.length > 0) generationConfig.stopSequences = options.stop;
|
|
255
425
|
const effort = options.reasoningEffort?.toLowerCase();
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
426
|
+
if (isLevelThinkingModel(options.model)) {
|
|
427
|
+
if (options.purpose === "session-title" || effort === "none" || effort === "off") generationConfig.thinkingConfig = { thinkingBudget: 0 };
|
|
428
|
+
else if (effort && LEVEL_THINKING_LEVELS.has(effort)) generationConfig.thinkingConfig = {
|
|
429
|
+
thinkingLevel: effort,
|
|
430
|
+
includeThoughts: true
|
|
431
|
+
};
|
|
432
|
+
}
|
|
260
433
|
return {
|
|
261
434
|
project: context.projectId || void 0,
|
|
262
435
|
requestId: generateAntigravityRequestId(),
|
|
@@ -274,6 +447,6 @@ function toAgyRequestBody(options, context) {
|
|
|
274
447
|
};
|
|
275
448
|
}
|
|
276
449
|
//#endregion
|
|
277
|
-
export { translate_exports as n,
|
|
450
|
+
export { setThoughtSignature as i, translate_exports as n, resolveMultimodalFiles as r, toAgyRequestBody as t };
|
|
278
451
|
|
|
279
|
-
//# sourceMappingURL=translate-
|
|
452
|
+
//# sourceMappingURL=translate-CE9tNXiV.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate-CE9tNXiV.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 (Gemini `thought` parts); nothing is\n * stripped or re-signed — that signature dance was an artifact of the\n * reference plugin's interception architecture (see docs/ARCHITECTURE.md).\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: { 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): AgyPart[] {\n switch (block.type) {\n case 'text':\n return [{ text: block.text }]\n case 'reasoning':\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 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): 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),\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/** 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 let contents = options.messages\n .map((message, index) => messageToContent(message, toolNames, images, multimodalFiles, index))\n .filter((c): c is AgyContent => c !== null)\n if (isClaudeModel(options.model)) {\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) generationConfig.maxOutputTokens = options.maxTokens\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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"}
|