@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/response.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClientProtocol } from "@agent-proxy/plugin-api";
|
|
1
2
|
import type { AgyToolMap } from "./request.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Antigravity v1internal streamGenerateContent(SSE)→ Codex Responses 事件流。
|
|
@@ -19,4 +20,9 @@ export interface AgyResponseOptions {
|
|
|
19
20
|
onChunk?: (chunk: string) => void;
|
|
20
21
|
sanitizeError?: (error: Record<string, unknown>) => Record<string, unknown>;
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* 四种客户端协议统一入口:responses 源沿用既有 Responses 输出;chat-completions /
|
|
25
|
+
* anthropic / gemini 源由 AGY 自产 Responses SSE 独立渲染为对应协议外壳(零桥接依赖)。
|
|
26
|
+
*/
|
|
27
|
+
export declare function createAgyClientResponse(to: ClientProtocol, upstream: Response, options: AgyResponseOptions): Promise<Response>;
|
|
22
28
|
export declare function createAgyResponse(upstream: Response, options: AgyResponseOptions): Promise<Response>;
|
package/dist/response.js
CHANGED
|
@@ -93,6 +93,384 @@ function finishDisposition(finishReason) {
|
|
|
93
93
|
return { status: "failed", failure: "Antigravity 上游报告函数调用格式无效,请检查工具声明后重试" };
|
|
94
94
|
return { status: "failed", failure: `Antigravity 上游返回不支持的 finishReason:${finishReason}` };
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* AGY 自产 Responses SSE → Chat Completions chunks(含 [DONE])。本渲染器属于
|
|
98
|
+
* agy 插件包(不依赖桥接包):解析自身产出的受信事件流并重组为客户端协议外壳。
|
|
99
|
+
*/
|
|
100
|
+
async function renderChatClient(responsesSse, state) {
|
|
101
|
+
const id = `chatcmpl-${crypto.randomUUID().replaceAll("-", "")}`;
|
|
102
|
+
const created = Math.floor(Date.now() / 1000);
|
|
103
|
+
let usage;
|
|
104
|
+
let finish = "stop";
|
|
105
|
+
let model = state.model;
|
|
106
|
+
const frames = [];
|
|
107
|
+
const push = (delta) => {
|
|
108
|
+
frames.push(`data: ${JSON.stringify({ id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta, finish_reason: null }] })}\n\n`);
|
|
109
|
+
};
|
|
110
|
+
let contentStarted = false;
|
|
111
|
+
for await (const frame of iterateOwnSse(responsesSse)) {
|
|
112
|
+
if (frame.type === "response.output_text.delta" && typeof frame.delta === "string") {
|
|
113
|
+
contentStarted = true;
|
|
114
|
+
push({ content: frame.delta, role: contentStarted && !frames.length ? "assistant" : undefined });
|
|
115
|
+
}
|
|
116
|
+
else if (frame.type === "response.reasoning_summary_text.delta" && typeof frame.delta === "string") {
|
|
117
|
+
push({ reasoning_content: frame.delta });
|
|
118
|
+
}
|
|
119
|
+
else if (frame.type === "response.refusal.delta" && typeof frame.delta === "string") {
|
|
120
|
+
push({ refusal: frame.delta });
|
|
121
|
+
}
|
|
122
|
+
else if (frame.type === "response.output_item.added" && record(frame.item, "item").type === "function_call") {
|
|
123
|
+
const item = record(frame.item, "item");
|
|
124
|
+
push({ role: "assistant", tool_calls: [{ index: Number(frame.output_index ?? 0), id: item.call_id ?? "", type: "function", function: { name: item.name ?? "", arguments: "" } }] });
|
|
125
|
+
}
|
|
126
|
+
else if (frame.type === "response.function_call_arguments.delta" && typeof frame.delta === "string") {
|
|
127
|
+
push({ tool_calls: [{ index: Number(frame.output_index ?? 0), function: { arguments: frame.delta } }] });
|
|
128
|
+
}
|
|
129
|
+
else if (frame.type === "response.completed" || frame.type === "response.incomplete" || frame.type === "response.failed") {
|
|
130
|
+
const response = record(frame.response, "response");
|
|
131
|
+
if (typeof response.model === "string")
|
|
132
|
+
model = state.model;
|
|
133
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
134
|
+
finish = response.status === "incomplete" ? "length" : output.some((entry) => record(entry, "output").type === "function_call") ? "tool_calls" : "stop";
|
|
135
|
+
if (record(response.usage ?? {}, "usage") && response.usage !== null) {
|
|
136
|
+
const value = record(response.usage, "usage");
|
|
137
|
+
usage = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
frames.push(`data: ${JSON.stringify({ id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta: {}, finish_reason: finish }] })}\n\n`);
|
|
142
|
+
if (usage) {
|
|
143
|
+
frames.push(`data: ${JSON.stringify({ id, object: "chat.completion.chunk", created, model, choices: [], usage })}\n\n`);
|
|
144
|
+
}
|
|
145
|
+
frames.push("data: [DONE]\n\n");
|
|
146
|
+
const text = frames.join("");
|
|
147
|
+
safely(() => state.onChunk?.(text));
|
|
148
|
+
if (!state.stream) {
|
|
149
|
+
// 非流式:聚合为完整 completion JSON。
|
|
150
|
+
let content = "";
|
|
151
|
+
const toolCalls = new Map();
|
|
152
|
+
let reasoning = "";
|
|
153
|
+
let refusal = "";
|
|
154
|
+
for (const frame of frames) {
|
|
155
|
+
if (!frame.startsWith("data: ") || frame.includes("[DONE]"))
|
|
156
|
+
continue;
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(frame.slice(6));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const choice = Array.isArray(parsed.choices) ? record(parsed.choices[0] ?? {}, "choice") : {};
|
|
165
|
+
const delta = record(choice.delta ?? {}, "delta");
|
|
166
|
+
if (typeof delta.content === "string")
|
|
167
|
+
content += delta.content;
|
|
168
|
+
if (typeof delta.reasoning_content === "string")
|
|
169
|
+
reasoning += delta.reasoning_content;
|
|
170
|
+
if (typeof delta.refusal === "string")
|
|
171
|
+
refusal += delta.refusal;
|
|
172
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
173
|
+
for (const raw of delta.tool_calls) {
|
|
174
|
+
const call = record(raw, "tool_call");
|
|
175
|
+
const index = typeof call.index === "number" ? call.index : 0;
|
|
176
|
+
const existing = toolCalls.get(index) ?? { id: "", function: { name: "", arguments: "" } };
|
|
177
|
+
if (typeof call.id === "string" && call.id)
|
|
178
|
+
existing.id = call.id;
|
|
179
|
+
const fn = record(call.function ?? {}, "function");
|
|
180
|
+
if (typeof fn.name === "string" && fn.name)
|
|
181
|
+
existing.function.name = fn.name;
|
|
182
|
+
if (typeof fn.arguments === "string")
|
|
183
|
+
existing.function.arguments += fn.arguments;
|
|
184
|
+
toolCalls.set(index, existing);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const completion = {
|
|
189
|
+
id, object: "chat.completion", created, model,
|
|
190
|
+
choices: [{ index: 0, message: { role: "assistant", ...(content === "" && toolCalls.size ? { content: null } : { content }), ...(reasoning ? { reasoning_content: reasoning } : {}), ...(refusal ? { refusal } : {}), ...(toolCalls.size ? { tool_calls: [...toolCalls.entries()].sort(([a], [b]) => a - b).map(([, call]) => ({ id: call.id, type: "function", function: call.function })) } : {}) }, finish_reason: finish }],
|
|
191
|
+
...(usage ? { usage } : {}),
|
|
192
|
+
};
|
|
193
|
+
return Response.json(completion);
|
|
194
|
+
}
|
|
195
|
+
return new Response(strBytes(text), { headers: sseHeaders() });
|
|
196
|
+
}
|
|
197
|
+
/** AGY 自产 Responses SSE → Gemini generateContent SSE / 完整 JSON(零桥接依赖)。 */
|
|
198
|
+
async function renderGeminiClient(responsesSse, state) {
|
|
199
|
+
let finish = "STOP";
|
|
200
|
+
let model = state.model;
|
|
201
|
+
const parts = [];
|
|
202
|
+
const toolArgs = new Map();
|
|
203
|
+
const toolNames = new Map();
|
|
204
|
+
let toolCalled = false;
|
|
205
|
+
let usage;
|
|
206
|
+
for await (const frame of iterateOwnSse(responsesSse)) {
|
|
207
|
+
if (frame.type === "response.output_text.delta" && typeof frame.delta === "string") {
|
|
208
|
+
parts.push({ text: frame.delta });
|
|
209
|
+
}
|
|
210
|
+
else if (frame.type === "response.reasoning_summary_text.delta" && typeof frame.delta === "string") {
|
|
211
|
+
parts.push({ text: frame.delta, thought: true });
|
|
212
|
+
}
|
|
213
|
+
else if (frame.type === "response.output_item.added" && record(frame.item, "item").type === "function_call") {
|
|
214
|
+
const item = record(frame.item, "item");
|
|
215
|
+
const callId = typeof item.call_id === "string" ? item.call_id : "";
|
|
216
|
+
toolArgs.set(callId, "");
|
|
217
|
+
toolNames.set(callId, typeof item.name === "string" ? item.name : "");
|
|
218
|
+
}
|
|
219
|
+
else if (frame.type === "response.function_call_arguments.delta" && typeof frame.delta === "string") {
|
|
220
|
+
const callId = [...toolArgs.keys()].at(-1) ?? "";
|
|
221
|
+
toolArgs.set(callId, (toolArgs.get(callId) ?? "") + frame.delta);
|
|
222
|
+
}
|
|
223
|
+
else if (frame.type === "response.completed" || frame.type === "response.incomplete" || frame.type === "response.failed") {
|
|
224
|
+
const response = record(frame.response, "response");
|
|
225
|
+
if (typeof response.model === "string")
|
|
226
|
+
model = state.model;
|
|
227
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
228
|
+
finish = response.status === "incomplete" ? "MAX_TOKENS" : "STOP";
|
|
229
|
+
if (response.status === "failed")
|
|
230
|
+
finish = "OTHER";
|
|
231
|
+
if (record(response.usage ?? {}, "usage") && response.usage !== null)
|
|
232
|
+
usage = record(response.usage, "usage");
|
|
233
|
+
void output;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// functionCall parts 依到达顺序附加在正文之后(Gemini 的候选内容是单序列)。
|
|
237
|
+
for (const [callId, argsText] of toolArgs) {
|
|
238
|
+
let args = {};
|
|
239
|
+
try {
|
|
240
|
+
args = record(JSON.parse(argsText || "{}"), "args");
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
args = {};
|
|
244
|
+
}
|
|
245
|
+
parts.push({ functionCall: { name: toolNames.get(callId) ?? "", args } });
|
|
246
|
+
toolCalled = true;
|
|
247
|
+
}
|
|
248
|
+
void toolCalled;
|
|
249
|
+
const inputTokens = typeof usage?.input_tokens === "number" ? usage.input_tokens : 0;
|
|
250
|
+
const outputTokens = typeof usage?.output_tokens === "number" ? usage.output_tokens : 0;
|
|
251
|
+
const usageMetadata = usage
|
|
252
|
+
? {
|
|
253
|
+
promptTokenCount: inputTokens,
|
|
254
|
+
candidatesTokenCount: outputTokens,
|
|
255
|
+
totalTokenCount: typeof usage.total_tokens === "number" ? usage.total_tokens : inputTokens + outputTokens,
|
|
256
|
+
}
|
|
257
|
+
: undefined;
|
|
258
|
+
if (state.stream) {
|
|
259
|
+
const frames = [];
|
|
260
|
+
for (const part of parts) {
|
|
261
|
+
const chunk = { candidates: [{ content: { parts: [part], role: "model" } }], modelVersion: model };
|
|
262
|
+
frames.push(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
263
|
+
}
|
|
264
|
+
const terminal = { candidates: [{ content: { role: "model" }, finishReason: finish }], modelVersion: model, ...(usageMetadata ? { usageMetadata } : {}) };
|
|
265
|
+
frames.push(`data: ${JSON.stringify(terminal)}\n\n`);
|
|
266
|
+
const text = frames.join("");
|
|
267
|
+
safely(() => state.onChunk?.(text));
|
|
268
|
+
return new Response(strBytes(text), { headers: sseHeaders() });
|
|
269
|
+
}
|
|
270
|
+
const completion = {
|
|
271
|
+
candidates: [{ content: { ...(parts.length > 0 ? { parts } : { parts: [{ text: "" }] }), role: "model" }, finishReason: finish }],
|
|
272
|
+
modelVersion: model,
|
|
273
|
+
...(usageMetadata ? { usageMetadata } : {}),
|
|
274
|
+
};
|
|
275
|
+
safely(() => state.onChunk?.(JSON.stringify(completion)));
|
|
276
|
+
return Response.json(completion);
|
|
277
|
+
}
|
|
278
|
+
/** AGY 自产 Responses SSE → Anthropic Messages 事件(message_start/块生命周期/终态)。 */
|
|
279
|
+
async function renderAnthropicClient(responsesSse, state) {
|
|
280
|
+
const id = `msg_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
281
|
+
let model = state.model;
|
|
282
|
+
const frames = [];
|
|
283
|
+
let started = false;
|
|
284
|
+
let blockIndex = -1;
|
|
285
|
+
let blockKind;
|
|
286
|
+
let textValue = "";
|
|
287
|
+
let thinkingValue = "";
|
|
288
|
+
const openTool = () => {
|
|
289
|
+
blockIndex += 1;
|
|
290
|
+
blockKind = "tool";
|
|
291
|
+
frames.push(`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: blockIndex, content_block: { type: "tool_use", id: "", name: "", input: {} } })}\n\n`);
|
|
292
|
+
};
|
|
293
|
+
const closeBlock = () => {
|
|
294
|
+
if (blockKind === undefined)
|
|
295
|
+
return;
|
|
296
|
+
frames.push(`event: content_block_stop\ndata: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
297
|
+
blockKind = undefined;
|
|
298
|
+
};
|
|
299
|
+
let usage = {};
|
|
300
|
+
let stopReason = "end_turn";
|
|
301
|
+
for await (const frame of iterateOwnSse(responsesSse)) {
|
|
302
|
+
if (frame.type === "response.created" || frame.type === "response.in_progress") {
|
|
303
|
+
if (started)
|
|
304
|
+
continue;
|
|
305
|
+
started = true;
|
|
306
|
+
const response = record(frame.response, "response");
|
|
307
|
+
if (typeof response.model === "string")
|
|
308
|
+
model = state.model;
|
|
309
|
+
frames.push(`event: message_start\ndata: ${JSON.stringify({ type: "message_start", message: { id, type: "message", role: "assistant", model, content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } } })}\n\n`);
|
|
310
|
+
frames.push('event: ping\ndata: {"type": "ping"}\n\n');
|
|
311
|
+
}
|
|
312
|
+
else if (frame.type === "response.output_text.delta" && typeof frame.delta === "string") {
|
|
313
|
+
if (blockKind !== "text") {
|
|
314
|
+
closeBlock();
|
|
315
|
+
blockIndex += 1;
|
|
316
|
+
blockKind = "text";
|
|
317
|
+
textValue = "";
|
|
318
|
+
frames.push(`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: blockIndex, content_block: { type: "text", text: "" } })}\n\n`);
|
|
319
|
+
}
|
|
320
|
+
textValue += frame.delta;
|
|
321
|
+
frames.push(`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: blockIndex, delta: { type: "text_delta", text: frame.delta } })}\n\n`);
|
|
322
|
+
}
|
|
323
|
+
else if (frame.type === "response.reasoning_summary_text.delta" && typeof frame.delta === "string") {
|
|
324
|
+
if (blockKind !== "thinking") {
|
|
325
|
+
closeBlock();
|
|
326
|
+
blockIndex += 1;
|
|
327
|
+
blockKind = "thinking";
|
|
328
|
+
thinkingValue = "";
|
|
329
|
+
frames.push(`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: blockIndex, content_block: { type: "thinking", thinking: "" } })}\n\n`);
|
|
330
|
+
}
|
|
331
|
+
thinkingValue += frame.delta;
|
|
332
|
+
frames.push(`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: blockIndex, delta: { type: "thinking_delta", thinking: frame.delta } })}\n\n`);
|
|
333
|
+
}
|
|
334
|
+
else if (frame.type === "response.output_item.added" && record(frame.item, "item").type === "function_call") {
|
|
335
|
+
closeBlock();
|
|
336
|
+
openTool();
|
|
337
|
+
const item = record(frame.item, "item");
|
|
338
|
+
frames.push(`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: blockIndex, content_block: { type: "tool_use", id: item.call_id ?? "", name: item.name ?? "", input: {} } })}\n\n`);
|
|
339
|
+
}
|
|
340
|
+
else if (frame.type === "response.function_call_arguments.delta" && typeof frame.delta === "string") {
|
|
341
|
+
frames.push(`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: blockIndex, delta: { type: "input_json_delta", partial_json: frame.delta } })}\n\n`);
|
|
342
|
+
}
|
|
343
|
+
else if (frame.type === "response.output_item.done" && record(frame.item, "item").type === "function_call") {
|
|
344
|
+
closeBlock();
|
|
345
|
+
}
|
|
346
|
+
else if (frame.type === "response.completed" || frame.type === "response.incomplete" || frame.type === "response.failed") {
|
|
347
|
+
const response = record(frame.response, "response");
|
|
348
|
+
closeBlock();
|
|
349
|
+
if (record(response.usage ?? {}, "usage") && response.usage !== null) {
|
|
350
|
+
const value = record(response.usage, "usage");
|
|
351
|
+
usage = { input_tokens: typeof value.input_tokens === "number" ? value.input_tokens : 0, output_tokens: typeof value.output_tokens === "number" ? value.output_tokens : 0 };
|
|
352
|
+
}
|
|
353
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
354
|
+
stopReason = response.status === "incomplete" ? "max_tokens" : output.some((entry) => record(entry, "output").type === "function_call") ? "tool_use" : "end_turn";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
frames.push(`event: message_delta\ndata: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason, stop_sequence: null }, usage: { output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0 } })}\n\n`);
|
|
358
|
+
frames.push('event: message_stop\ndata: {"type": "message_stop"}\n\n');
|
|
359
|
+
const text = frames.join("");
|
|
360
|
+
safely(() => state.onChunk?.(text));
|
|
361
|
+
if (!state.stream) {
|
|
362
|
+
// 非流式:聚合为完整 Message JSON。
|
|
363
|
+
const content = [];
|
|
364
|
+
let current;
|
|
365
|
+
for (const frame of frames) {
|
|
366
|
+
if (!frame.startsWith("event: content_block_start"))
|
|
367
|
+
continue;
|
|
368
|
+
const data = JSON.parse(frame.split("\ndata: ")[1]);
|
|
369
|
+
const block = record(data.content_block, "content_block");
|
|
370
|
+
if (block.type === "text")
|
|
371
|
+
current = { type: "text", text: "" };
|
|
372
|
+
else if (block.type === "thinking")
|
|
373
|
+
current = { type: "thinking", thinking: "", signature: "" };
|
|
374
|
+
else
|
|
375
|
+
current = { type: "tool_use", id: block.id ?? "", name: block.name ?? "", input: {} };
|
|
376
|
+
content.push(current);
|
|
377
|
+
}
|
|
378
|
+
for (const frame of frames) {
|
|
379
|
+
if (!frame.startsWith("event: content_block_delta"))
|
|
380
|
+
continue;
|
|
381
|
+
const data = JSON.parse(frame.split("\ndata: ")[1]);
|
|
382
|
+
const index = typeof data.index === "number" ? data.index : 0;
|
|
383
|
+
const delta = record(data.delta, "delta");
|
|
384
|
+
const target = content[index];
|
|
385
|
+
if (!target)
|
|
386
|
+
continue;
|
|
387
|
+
if (delta.type === "text_delta" && typeof delta.text === "string")
|
|
388
|
+
target.text = `${target.text}${delta.text}`;
|
|
389
|
+
else if (delta.type === "thinking_delta" && typeof delta.thinking === "string")
|
|
390
|
+
target.thinking = `${target.thinking}${delta.thinking}`;
|
|
391
|
+
else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
392
|
+
// 参数按块整段缓存后一次性解析(上游 functionCall 本就整帧到达)。
|
|
393
|
+
try {
|
|
394
|
+
const parsed = JSON.parse(delta.partial_json);
|
|
395
|
+
if (record(parsed, "tool input"))
|
|
396
|
+
target.input = parsed;
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
target.input = {};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const message = {
|
|
404
|
+
id, type: "message", role: "assistant", model,
|
|
405
|
+
content: content.length ? content : [{ type: "text", text: "" }],
|
|
406
|
+
stop_reason: stopReason, stop_sequence: null,
|
|
407
|
+
usage: { input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0, output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0 },
|
|
408
|
+
};
|
|
409
|
+
return Response.json(message);
|
|
410
|
+
}
|
|
411
|
+
return new Response(strBytes(text), { headers: sseHeaders() });
|
|
412
|
+
}
|
|
413
|
+
/** 解析 AGY 自产的 Responses SSE(受信输入:严格 JSON,忽略 event 行)。 */
|
|
414
|
+
async function* iterateOwnSse(body) {
|
|
415
|
+
const reader = body.getReader();
|
|
416
|
+
const decoder = new TextDecoder();
|
|
417
|
+
let buffer = "";
|
|
418
|
+
try {
|
|
419
|
+
while (true) {
|
|
420
|
+
const { done, value } = await reader.read();
|
|
421
|
+
if (done)
|
|
422
|
+
break;
|
|
423
|
+
buffer += decoder.decode(value, { stream: true });
|
|
424
|
+
let boundary = buffer.indexOf("\n\n");
|
|
425
|
+
while (boundary !== -1) {
|
|
426
|
+
const frame = buffer.slice(0, boundary);
|
|
427
|
+
buffer = buffer.slice(boundary + 2);
|
|
428
|
+
const data = frame.split("\n").filter((line) => line.startsWith("data: ")).map((line) => line.slice(6)).join("\n");
|
|
429
|
+
if (!data || data === "[DONE]")
|
|
430
|
+
continue;
|
|
431
|
+
try {
|
|
432
|
+
yield JSON.parse(data);
|
|
433
|
+
}
|
|
434
|
+
catch { /* 自产流不应出现坏帧;忽略。 */ }
|
|
435
|
+
boundary = buffer.indexOf("\n\n");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
finally {
|
|
440
|
+
try {
|
|
441
|
+
reader.releaseLock();
|
|
442
|
+
}
|
|
443
|
+
catch { /* 读取已由取消收尾。 */ }
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function strBytes(text) {
|
|
447
|
+
const bytes = encoder.encode(text);
|
|
448
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
449
|
+
new Uint8Array(buffer).set(bytes);
|
|
450
|
+
return buffer;
|
|
451
|
+
}
|
|
452
|
+
function sseHeaders() {
|
|
453
|
+
return new Headers({ "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache" });
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* 四种客户端协议统一入口:responses 源沿用既有 Responses 输出;chat-completions /
|
|
457
|
+
* anthropic / gemini 源由 AGY 自产 Responses SSE 独立渲染为对应协议外壳(零桥接依赖)。
|
|
458
|
+
*/
|
|
459
|
+
export async function createAgyClientResponse(to, upstream, options) {
|
|
460
|
+
if (to === "responses")
|
|
461
|
+
return createAgyResponse(upstream, options);
|
|
462
|
+
// 非 responses 客户端始终先取 Responses SSE 流(非流式客户端由渲染器聚合),
|
|
463
|
+
// 否则非流式时 createAgyResponse 直接返回聚合 JSON,渲染层无帧可解。
|
|
464
|
+
const responsesOutput = await createAgyResponse(upstream, { ...options, stream: true });
|
|
465
|
+
if (!responsesOutput.body)
|
|
466
|
+
throw new Error("Antigravity 响应缺少 body");
|
|
467
|
+
const state = { model: options.model, stream: options.stream, onChunk: options.onChunk };
|
|
468
|
+
if (to === "chat-completions")
|
|
469
|
+
return renderChatClient(responsesOutput.body, state);
|
|
470
|
+
if (to === "gemini")
|
|
471
|
+
return renderGeminiClient(responsesOutput.body, state);
|
|
472
|
+
return renderAnthropicClient(responsesOutput.body, state);
|
|
473
|
+
}
|
|
96
474
|
export async function createAgyResponse(upstream, options) {
|
|
97
475
|
const contentType = upstream.headers.get("content-type") ?? "";
|
|
98
476
|
if (!contentType.includes("text/event-stream")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-proxy/agy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Agent Proxy agy 插件;保留基线转换行为。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"url": "git+https://github.com/aceHubert/codex-cliproxy-gateway.git"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@agent-proxy/models": "0.
|
|
37
|
+
"@agent-proxy/models": "0.12.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@agent-proxy/plugin-api": "0.
|
|
40
|
+
"@agent-proxy/plugin-api": "0.12.1"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "50a77d2e514a6237b097361fd4eb794acaa582d5"
|
|
43
43
|
}
|