@agent-proxy/agy 0.11.3 → 0.12.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.
package/dist/catalog.d.ts CHANGED
@@ -46,7 +46,7 @@ export declare function parseAgyCatalogData(value: unknown): AgyCatalogData;
46
46
  export declare function agyEffortTier(effort: unknown): AgyTier;
47
47
  /** 家族内按档位解析上游模型 id;目标档缺失时按「就近偏高」回退,家族必有成员。 */
48
48
  export declare function resolveAgyFamilyModel(family: AgyModelFamily, effort: unknown): string;
49
- /** Codex 行为字段沿用 gpt-5.5 快照基底;模型名与能力仅由当前账号目录决定。
49
+ /** Codex 行为字段沿用快照基底(优先 gpt-5.5,缺失回退首条);模型名与能力仅由当前账号目录决定。
50
50
  * 多档位家族合并为一个 `agy/<base>` 条目(能力/窗口取成员并集或最大值),
51
51
  * `supported_reasoning_levels` 只列家族实际存在的档位;无档位后缀的模型保持
52
52
  * 独立条目且不暴露档位选择。 */
package/dist/catalog.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
- import { getCodexModelSnapshot } from "./resources/models.js";
4
+ import { getCodexBaselineEntry } from "./resources/models.js";
5
5
  import { invalidateModelsCache, withAgentSystemPrompt } from "@agent-proxy/plugin-api";
6
6
  import { atomicWrite } from "@agent-proxy/plugin-api";
7
7
  import { AGY_AGENT_SYSTEM_PROMPT, AGY_ENDPOINT } from "./transport.js";
@@ -166,14 +166,12 @@ const baseLevelDescriptions = (base) => new Map((Array.isArray(base.supported_re
166
166
  ? base.supported_reasoning_levels
167
167
  : []).filter((level) => typeof level.effort === "string" && typeof level.description === "string")
168
168
  .map((level) => [level.effort, level.description]));
169
- /** Codex 行为字段沿用 gpt-5.5 快照基底;模型名与能力仅由当前账号目录决定。
169
+ /** Codex 行为字段沿用快照基底(优先 gpt-5.5,缺失回退首条);模型名与能力仅由当前账号目录决定。
170
170
  * 多档位家族合并为一个 `agy/<base>` 条目(能力/窗口取成员并集或最大值),
171
171
  * `supported_reasoning_levels` 只列家族实际存在的档位;无档位后缀的模型保持
172
172
  * 独立条目且不暴露档位选择。 */
173
173
  export function buildAgyCatalog(data) {
174
- const BASE = getCodexModelSnapshot().models.find((entry) => entry.slug === "gpt-5.5");
175
- if (!BASE)
176
- throw new Error("Codex 模型快照缺少 gpt-5.5 基底条目");
174
+ const BASE = getCodexBaselineEntry();
177
175
  const BASE_LEVEL_DESCRIPTIONS = baseLevelDescriptions(BASE);
178
176
  const familyByMember = new Map();
179
177
  for (const family of data.families) {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,16 @@
1
- import type { HostServices, GatewayPlugin } from "@agent-proxy/plugin-api";
1
+ import type { HostServices, GatewayPlugin, ClientProtocol } from "@agent-proxy/plugin-api";
2
2
  import type { AgyCredentials } from "./credentials.ts";
3
3
  import { isAgyModel, mergeAgyCatalog } from "./catalog.ts";
4
4
  import type { AgentConfig, ModelCatalog, ProcessLogTarget } from "@agent-proxy/plugin-api";
5
+ declare module "@agent-proxy/plugin-api" {
6
+ interface AgentConfig {
7
+ /**
8
+ * 是否开启调试转储:上游错误时把完整请求体落盘到日志目录(当前覆盖 agy 上游 400 的 agy-debug-400.json)。
9
+ * 与 requestLogging 互相独立,默认关闭。
10
+ */
11
+ debug?: boolean;
12
+ }
13
+ }
5
14
  /**
6
15
  * Antigravity(agy)入口门面:凭据只读消费(过期即失败,绝不刷新)、fetchAvailableModels
7
16
  * 动态目录、Responses ↔ v1internal streamGenerateContent 双向转换。`catalog()` 供
@@ -29,8 +38,7 @@ export interface AgyDependencies {
29
38
  /** 进程日志目标(gateway.log)。 */
30
39
  processLog?: ProcessLogTarget;
31
40
  }
32
- export declare function agyEnabled<Config extends AgentConfig>(config: Config): boolean;
33
- export declare function validateAgyConfig<Config extends AgentConfig>(config: Config): void;
41
+ export declare function validateAgyConfig(config: AgentConfig): void;
34
42
  export declare function agyError(status: number, message: string, type?: string): Response;
35
43
  export interface SafeAgyError {
36
44
  code: string;
@@ -42,12 +50,12 @@ export interface SafeAgyError {
42
50
  export declare function safeAgyUpstreamError(status: number, body: string): SafeAgyError;
43
51
  export { isAgyModel, mergeAgyCatalog };
44
52
  export { AGY_RUNTIME_ARTIFACTS } from "./catalog.ts";
45
- export declare function createAgyAdapter<Config extends AgentConfig>(config: Config, dependencies?: AgyDependencies): {
53
+ export declare function createAgyAdapter(config: AgentConfig, dependencies?: AgyDependencies): {
46
54
  catalog(): Promise<ModelCatalog>;
47
55
  /** 显式刷新不吞错;无 last-good 时由命令报告失败。 */
48
56
  refreshCatalog(): Promise<ModelCatalog>;
49
57
  reloadCatalog(): Promise<ModelCatalog>;
50
- forward(request: Request, input: Record<string, unknown>, mapResult?: (payload: Record<string, unknown>) => Response): Promise<Response>;
58
+ forward(request: Request, input: Record<string, unknown>, to?: ClientProtocol, mapResult?: (payload: Record<string, unknown>) => Response): Promise<Response>;
51
59
  close(): void;
52
60
  };
53
61
  export declare const AGY_MODEL_PATTERNS: readonly string[];
package/dist/index.js CHANGED
@@ -7,26 +7,16 @@ import { agyTokenStale, loadAgyCredentials, AgyCredentialError, defaultAgyCreden
7
7
  import { createAgyTransport, AgyTransportError } from "./transport.js";
8
8
  import { AGY_PREFIX, agyUpstreamModel, createAgyCatalogStore, isAgyModel, mergeAgyCatalog, resolveAgyFamilyModel, } from "./catalog.js";
9
9
  import { translateAgyRequest, AgyRequestError } from "./request.js";
10
- import { createAgyResponse } from "./response.js";
10
+ import { createAgyClientResponse } from "./response.js";
11
11
  import { localTime, logExchange, logGroupFromPath, requestLogDir } from "@agent-proxy/plugin-api";
12
12
  import { logGatewayError, logRequestSummary } from "@agent-proxy/plugin-api";
13
13
  /** CLI 的模型轮询周期是 360 秒(见调研文档 §5.5),网关按同一节奏刷新目录。 */
14
14
  const DEFAULT_CATALOG_REFRESH_INTERVAL_MS = 360_000;
15
- export function agyEnabled(config) {
16
- return config.agy === undefined || config.agy === true;
17
- }
18
15
  export function validateAgyConfig(config) {
19
- if (config.agy !== undefined && typeof config.agy !== "boolean")
20
- throw new Error("agy 必须为 boolean");
21
- if (!agyEnabled(config))
22
- return;
23
16
  const host = config.host;
24
17
  if (!(host === "localhost" || host === "::1" || host === "[::1]" || (isIP(host) === 4 && host.startsWith("127.")))) {
25
18
  throw new Error("启用 agy 时网关只能监听环回地址");
26
19
  }
27
- if (config.prefix && (AGY_PREFIX.startsWith(config.prefix) || config.prefix.startsWith(AGY_PREFIX))) {
28
- throw new Error(`启用 agy 时 ${AGY_PREFIX} 前缀保留给 Antigravity,请调整第三方 prefix`);
29
- }
30
20
  }
31
21
  export function agyError(status, message, type = "invalid_request_error") {
32
22
  return Response.json({ error: { type, message } }, { status });
@@ -54,7 +44,6 @@ export function safeAgyUpstreamError(status, body) {
54
44
  }
55
45
  function buildAgyAdapter(config, dependencies = {}) {
56
46
  validateAgyConfig(config);
57
- const enabled = agyEnabled(config);
58
47
  const credentialFile = dependencies.credentialFile ?? defaultAgyCredentialFile();
59
48
  const loadCredentials = async () => {
60
49
  if (dependencies.credentials)
@@ -68,35 +57,31 @@ function buildAgyAdapter(config, dependencies = {}) {
68
57
  };
69
58
  const transport = createAgyTransport({ endpoint: dependencies.endpoint, fetch: dependencies.fetch });
70
59
  // 请求日志与调试转储共用同一目录解析规则;两者开关(requestLogging / debug)互相独立。
71
- const logDir = requestLogDir(config);
60
+ const logDir = requestLogDir();
72
61
  const sink = config.requestLogging === true ? {
73
62
  dir: logDir,
74
63
  maxLogs: Math.max(0, Math.trunc(config.maxRequestLogs ?? 0)),
75
64
  processLog: dependencies.processLog,
76
65
  } : undefined;
77
- const store = enabled
78
- ? createAgyCatalogStore({
79
- cacheDirectory: dependencies.cacheDirectory ?? resolvePaths().runtimeHome,
80
- credentials: loadCredentials,
81
- codexModelsCacheFile: dependencies.codexModelsCacheFile,
82
- endpoint: dependencies.endpoint,
83
- fetchCatalog: (credential) => transport.fetchModels(credential, AbortSignal.timeout(30_000)),
84
- catalogMode: dependencies.catalogMode,
85
- })
86
- : undefined;
66
+ const store = createAgyCatalogStore({
67
+ cacheDirectory: dependencies.cacheDirectory ?? resolvePaths().runtimeHome,
68
+ credentials: loadCredentials,
69
+ codexModelsCacheFile: dependencies.codexModelsCacheFile,
70
+ endpoint: dependencies.endpoint,
71
+ fetchCatalog: (credential) => transport.fetchModels(credential, AbortSignal.timeout(30_000)),
72
+ catalogMode: dependencies.catalogMode,
73
+ });
87
74
  const schedule = dependencies.setInterval ?? setInterval;
88
75
  const cancel = dependencies.clearInterval ?? clearInterval;
89
76
  let timer;
90
77
  let startupRefresh;
91
- if (store) {
92
- if (dependencies.refreshCatalogOnStart !== false) {
93
- startupRefresh = store.refresh();
94
- void startupRefresh.catch(() => { });
95
- }
96
- if (dependencies.catalogMode !== "manual") {
97
- timer = schedule(() => { void store.refresh().catch(() => { }); }, Math.max(1_000, dependencies.catalogRefreshIntervalMs ?? DEFAULT_CATALOG_REFRESH_INTERVAL_MS));
98
- timer.unref?.();
99
- }
78
+ if (dependencies.refreshCatalogOnStart !== false) {
79
+ startupRefresh = store.refresh();
80
+ void startupRefresh.catch(() => { });
81
+ }
82
+ if (dependencies.catalogMode !== "manual") {
83
+ timer = schedule(() => { void store.refresh().catch(() => { }); }, Math.max(1_000, dependencies.catalogRefreshIntervalMs ?? DEFAULT_CATALOG_REFRESH_INTERVAL_MS));
84
+ timer.unref?.();
100
85
  }
101
86
  const activeRequests = new Set();
102
87
  /** 已知模型集合(含重定向前的旧 id);空目录视为不可校验,透传由上游判定。 */
@@ -116,8 +101,6 @@ function buildAgyAdapter(config, dependencies = {}) {
116
101
  };
117
102
  /** 尽力加载目录元数据(含磁盘缓存回退);失败时保持未加载状态,由上游判定。 */
118
103
  const loadCatalogMeta = async () => {
119
- if (!store)
120
- return undefined;
121
104
  try {
122
105
  await startupRefresh?.catch(() => { });
123
106
  return updateCatalogMeta(await store.catalog()).models;
@@ -127,24 +110,24 @@ function buildAgyAdapter(config, dependencies = {}) {
127
110
  let closed = false;
128
111
  return {
129
112
  async catalog() {
130
- if (!store || closed)
113
+ if (closed)
131
114
  return { models: [] };
132
115
  // 系统提示词已在 buildAgyCatalog 合成时替换(含 model_messages 模板),缓存即成品。
133
116
  return { models: (await loadCatalogMeta()) ?? [] };
134
117
  },
135
118
  /** 显式刷新不吞错;无 last-good 时由命令报告失败。 */
136
119
  async refreshCatalog() {
137
- if (!store || closed)
120
+ if (closed)
138
121
  return { models: [] };
139
122
  await store.refresh();
140
123
  return updateCatalogMeta(await store.catalog());
141
124
  },
142
125
  async reloadCatalog() {
143
- if (!store || closed)
126
+ if (closed)
144
127
  return { models: [] };
145
128
  return updateCatalogMeta(await store.reload());
146
129
  },
147
- async forward(request, input, mapResult) {
130
+ async forward(request, input, to = "responses", mapResult) {
148
131
  const start = Date.now();
149
132
  const requestTime = localTime();
150
133
  const incoming = new URL(request.url);
@@ -175,8 +158,8 @@ function buildAgyAdapter(config, dependencies = {}) {
175
158
  log(status, JSON.stringify({ error: { type, message } }), response.headers);
176
159
  return response;
177
160
  };
178
- if (!store || closed)
179
- return fail(503, "Antigravity 入口未启用或网关已关闭", "configuration_error");
161
+ if (closed)
162
+ return fail(503, "Antigravity 入口已关闭", "configuration_error");
180
163
  const abort = new AbortController();
181
164
  const onAbort = () => abort.abort(request.signal.reason);
182
165
  let cleaned = false;
@@ -261,10 +244,10 @@ function buildAgyAdapter(config, dependencies = {}) {
261
244
  }
262
245
  }
263
246
  }
264
- const translated = translateAgyRequest(input, resolvedModel);
247
+ const translated = translateAgyRequest(input, resolvedModel, to);
265
248
  abort.signal.throwIfAborted();
266
249
  const upstream = await transport.streamInfer(credential, translated.wrapper, abort.signal);
267
- const response = await createAgyResponse(upstream, {
250
+ const response = await createAgyClientResponse(to, upstream, {
268
251
  model, tools: translated.tools, stream, signal: abort.signal,
269
252
  sanitizeError: (error) => {
270
253
  // 上游流内错误的原始对象只进本机网关日志(截断),供排查;客户端只见预定义分类。
@@ -375,9 +358,7 @@ export function createAgyPlugin(host, dependencies = {}) {
375
358
  const input = await context.body.json();
376
359
  if (!input || typeof input !== "object" || Array.isArray(input))
377
360
  return Response.json({ error: { type: "invalid_request_error", message: "请求正文必须是 JSON 对象" } }, { status: 400 });
378
- if (context.from !== "responses")
379
- return Response.json({ error: { type: "invalid_request_error", message: "Agy 模型仅支持 Responses API" } }, { status: 400 });
380
- return adapter.forward(context.request, input);
361
+ return adapter.forward(context.request, input, context.to);
381
362
  },
382
363
  getCatalog: () => adapter.catalog(),
383
364
  refreshCatalog: () => adapter.refreshCatalog(),
package/dist/request.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ClientProtocol } from "@agent-proxy/plugin-api";
1
2
  /**
2
3
  * Codex Responses → Antigravity v1internal generateContent 请求转换。
3
4
  *
@@ -25,4 +26,4 @@ export interface AgyTranslatedRequest {
25
26
  /** reasoning.effort → Gemini thinkingLevel;unknown 档位映射到最近的低/高档。
26
27
  * 档位名同时是多档位家族的上游变体后缀,catalog.ts 复用该映射做 effort → 变体解析。 */
27
28
  export declare const THINKING_LEVELS: Record<string, string>;
28
- export declare function translateAgyRequest(body: Record<string, unknown>, upstreamModel: string): AgyTranslatedRequest;
29
+ export declare function translateAgyRequest(body: Record<string, unknown>, upstreamModel: string, to?: ClientProtocol): AgyTranslatedRequest;
package/dist/request.js CHANGED
@@ -268,9 +268,215 @@ function translateTools(value) {
268
268
  visit(value);
269
269
  return { tools, map, dropped };
270
270
  }
271
- export function translateAgyRequest(body, upstreamModel) {
271
+ /**
272
+ * Chat Completions / Anthropic 源归一化为 Responses 形状的 history 项与参数,
273
+ * 复用既有 Responses → CloudCodeAssist 构建路径;三种来源共用同一工具身份表。
274
+ */
275
+ function normalizeSourceItems(body, to) {
276
+ if (to === "responses")
277
+ return body;
278
+ if (to === "gemini") {
279
+ if (!Array.isArray(body.contents))
280
+ fail("contents 必须是数组");
281
+ const items = [];
282
+ const systemTexts = [];
283
+ const instruction = body.systemInstruction;
284
+ if (typeof instruction === "string" && instruction)
285
+ systemTexts.push(instruction);
286
+ else if (record(instruction) && Array.isArray(instruction.parts)) {
287
+ for (const part of instruction.parts) {
288
+ if (record(part) && typeof part.text === "string" && part.text)
289
+ systemTexts.push(part.text);
290
+ }
291
+ }
292
+ let callSeq = 0;
293
+ const nameToOpenCall = new Map();
294
+ const idByCallId = new Map();
295
+ for (const message of body.contents) {
296
+ if (!record(message) || (message.role !== "user" && message.role !== "model")) {
297
+ fail("contents 必须是 user/model 角色条目");
298
+ }
299
+ for (const raw of Array.isArray(message.parts) ? message.parts : []) {
300
+ if (!record(raw))
301
+ fail("parts 包含非对象块");
302
+ if (typeof raw.text === "string" && raw.text) {
303
+ if (raw.thought === true)
304
+ continue; // 思考块不进入 CloudCodeAssist 回放。
305
+ items.push({ type: "message", role: message.role === "model" ? "assistant" : "user", content: raw.text });
306
+ }
307
+ else if (record(raw.functionCall)) {
308
+ const name = typeof raw.functionCall.name === "string" && raw.functionCall.name ? raw.functionCall.name : fail("functionCall.name 必须是非空字符串");
309
+ const id = typeof raw.functionCall.id === "string" && raw.functionCall.id ? raw.functionCall.id : `agy_gemini_call_${callSeq++}`;
310
+ idByCallId.set(id, id);
311
+ if (!(typeof raw.functionCall.id === "string" && raw.functionCall.id))
312
+ nameToOpenCall.set(name, id);
313
+ items.push({ type: "function_call", call_id: id, name, arguments: JSON.stringify(record(raw.functionCall.args) ? raw.functionCall.args : {}) });
314
+ }
315
+ else if (record(raw.functionResponse)) {
316
+ const name = typeof raw.functionResponse.name === "string" ? raw.functionResponse.name : "";
317
+ let id = typeof raw.functionResponse.id === "string" && raw.functionResponse.id ? raw.functionResponse.id : nameToOpenCall.get(name);
318
+ if (!id)
319
+ fail(`functionResponse 无法配对到 functionCall(${name || "未命名"})`);
320
+ nameToOpenCall.delete(name);
321
+ items.push({ type: "function_call_output", call_id: id, output: JSON.stringify(record(raw.functionResponse.response) ? raw.functionResponse.response : raw.functionResponse.response ?? "") });
322
+ }
323
+ else if (record(raw.inlineData) || record(raw.fileData)) {
324
+ fail("Antigravity gemini 入口暂不支持图片 parts,请改用 responses/chat/anthropic 入口");
325
+ }
326
+ else {
327
+ fail("parts 包含不支持的块");
328
+ }
329
+ }
330
+ }
331
+ return {
332
+ ...body,
333
+ instructions: [typeof body.instructions === "string" ? body.instructions : "", ...systemTexts].filter(Boolean).join("\n\n") || undefined,
334
+ input: items,
335
+ };
336
+ }
337
+ if (to === "chat-completions") {
338
+ if (!Array.isArray(body.messages))
339
+ fail("messages 必须是数组");
340
+ const items = [];
341
+ for (const raw of body.messages) {
342
+ if (!record(raw))
343
+ fail("messages 包含非对象项");
344
+ if (raw.role === "system" || raw.role === "developer") {
345
+ items.push({ type: "message", role: raw.role, content: raw.content });
346
+ continue;
347
+ }
348
+ if (raw.role === "tool") {
349
+ items.push({ type: "function_call_output", call_id: raw.tool_call_id ?? "", output: raw.content });
350
+ continue;
351
+ }
352
+ if (raw.role === "assistant" && Array.isArray(raw.tool_calls)) {
353
+ if (typeof raw.content === "string" && raw.content)
354
+ items.push({ type: "message", role: "assistant", content: raw.content });
355
+ for (const call of raw.tool_calls) {
356
+ if (!record(call) || !record(call.function))
357
+ fail("assistant.tool_calls 包含无效调用");
358
+ items.push({ type: "function_call", call_id: typeof call.id === "string" ? call.id : "", name: call.function.name ?? "", arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments ?? {}) });
359
+ }
360
+ continue;
361
+ }
362
+ if (raw.role === "user" || raw.role === "assistant") {
363
+ items.push({ type: "message", role: raw.role, content: raw.content });
364
+ continue;
365
+ }
366
+ fail(`不支持的消息 role ${String(raw.role)}`);
367
+ }
368
+ const normalized = {
369
+ ...body,
370
+ input: items,
371
+ ...(typeof body.max_tokens === "number" ? { max_output_tokens: body.max_tokens } : typeof body.max_completion_tokens === "number" ? { max_output_tokens: body.max_completion_tokens } : {}),
372
+ };
373
+ delete normalized.messages;
374
+ delete normalized.max_tokens;
375
+ delete normalized.max_completion_tokens;
376
+ if (typeof body.reasoning_effort === "string" && body.reasoning === undefined) {
377
+ normalized.reasoning = { effort: body.reasoning_effort };
378
+ delete normalized.reasoning_effort;
379
+ }
380
+ if (record(body.tool_choice) && body.tool_choice.type === "function" && record(body.tool_choice.function)) {
381
+ normalized.tool_choice = { type: "function", name: body.tool_choice.function.name };
382
+ }
383
+ if (Array.isArray(body.tools)) {
384
+ normalized.tools = body.tools.map((raw) => {
385
+ if (record(raw) && raw.type === "function" && record(raw.function)) {
386
+ return { type: "function", name: raw.function.name, ...(typeof raw.function.description === "string" ? { description: raw.function.description } : {}), parameters: record(raw.function.parameters) ? raw.function.parameters : { type: "object" } };
387
+ }
388
+ return raw;
389
+ });
390
+ }
391
+ return normalized;
392
+ }
393
+ // anthropic 源
394
+ if (!Array.isArray(body.messages))
395
+ fail("messages 必须是数组");
396
+ const items = [];
397
+ for (const raw of body.messages) {
398
+ if (!record(raw) || !Array.isArray(raw.content))
399
+ fail("messages 必须是 user/assistant 与内容块数组");
400
+ const calls = [];
401
+ const results = [];
402
+ const parts = [];
403
+ for (const block of raw.content) {
404
+ if (!record(block))
405
+ fail("messages 包含无效内容块");
406
+ if (block.type === "tool_use") {
407
+ calls.push({ type: "function_call", call_id: typeof block.id === "string" ? block.id : "", name: typeof block.name === "string" ? block.name : "", arguments: JSON.stringify(block.input ?? {}) });
408
+ }
409
+ else if (block.type === "tool_result") {
410
+ results.push({ type: "function_call_output", call_id: typeof block.tool_use_id === "string" ? block.tool_use_id : "", output: block.content });
411
+ }
412
+ else if (block.type === "text" || block.type === "image" || block.type === "thinking") {
413
+ if (block.type === "text") {
414
+ parts.push({ type: "input_text", text: typeof block.text === "string" ? block.text : "" });
415
+ }
416
+ else if (block.type === "image") {
417
+ const source = record(block.source) ? block.source : undefined;
418
+ const url = source && source.type === "url" && typeof source.url === "string"
419
+ ? source.url
420
+ : `data:${String(source?.media_type ?? "image/png")};base64,${String(source?.data ?? "")}`;
421
+ parts.push({ type: "input_image", image_url: url });
422
+ }
423
+ else {
424
+ parts.push({ type: "input_text", text: `[thinking] ${typeof block.thinking === "string" ? block.thinking : ""}` });
425
+ }
426
+ }
427
+ }
428
+ if (results.length > 0)
429
+ items.push(...results);
430
+ else if (parts.length > 0)
431
+ items.push({ type: "message", role: raw.role, content: parts });
432
+ if (calls.length > 0)
433
+ items.push(...calls);
434
+ }
435
+ // Anthropic 的 system(字符串或块数组)语义等同 Responses instructions。
436
+ let systemInstruction;
437
+ if (typeof body.system === "string")
438
+ systemInstruction = body.system;
439
+ else if (Array.isArray(body.system)) {
440
+ const texts = [];
441
+ for (const part of body.system) {
442
+ if (record(part) && typeof part.text === "string")
443
+ texts.push(part.text);
444
+ }
445
+ if (texts.length > 0)
446
+ systemInstruction = texts.join("\n\n");
447
+ }
448
+ const normalized = {
449
+ ...body,
450
+ input: items,
451
+ ...(systemInstruction !== undefined ? { instructions: systemInstruction } : {}),
452
+ ...(typeof body.max_tokens === "number" ? { max_output_tokens: body.max_tokens } : {}),
453
+ };
454
+ delete normalized.messages;
455
+ delete normalized.max_tokens;
456
+ delete normalized.system;
457
+ if (record(body.tool_choice)) {
458
+ const choice = body.tool_choice;
459
+ if (choice.type === "tool" && typeof choice.name === "string")
460
+ normalized.tool_choice = { type: "function", name: choice.name };
461
+ else if (choice.type === "any")
462
+ normalized.tool_choice = "required";
463
+ else if (choice.type === "auto" || choice.type === "none")
464
+ normalized.tool_choice = choice.type;
465
+ }
466
+ if (Array.isArray(body.tools)) {
467
+ normalized.tools = body.tools.map((raw) => {
468
+ if (record(raw) && typeof raw.name === "string") {
469
+ return { type: "function", name: raw.name, ...(typeof raw.description === "string" ? { description: raw.description } : {}), parameters: record(raw.input_schema) ? raw.input_schema : { type: "object" } };
470
+ }
471
+ return raw;
472
+ });
473
+ }
474
+ return normalized;
475
+ }
476
+ export function translateAgyRequest(body, upstreamModel, to = "responses") {
272
477
  if (typeof upstreamModel !== "string" || !upstreamModel)
273
478
  fail("上游模型不能为空");
479
+ body = normalizeSourceItems(body, to);
274
480
  if (body.previous_response_id !== undefined && body.previous_response_id !== null && body.previous_response_id !== "") {
275
481
  fail("Antigravity 不支持 previous_response_id;请提供完整 input 历史");
276
482
  }