@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 +1 -1
- package/dist/catalog.js +3 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +26 -45
- package/dist/request.d.ts +2 -1
- package/dist/request.js +207 -1
- package/dist/resources/models.js +3260 -910
- package/dist/response.d.ts +6 -0
- package/dist/response.js +378 -0
- package/package.json +4 -4
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
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 {
|
|
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(
|
|
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 =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
179
|
-
return fail(503, "Antigravity
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|