billion-context 0.1.31 → 0.1.32
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/index.js +1276 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -46420,7 +46420,6 @@ function rejectLegacyRoute(key, value) {
|
|
|
46420
46420
|
// src/server.ts
|
|
46421
46421
|
import http from "http";
|
|
46422
46422
|
import fs3 from "fs";
|
|
46423
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
46424
46423
|
|
|
46425
46424
|
// src/registry.ts
|
|
46426
46425
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -47698,9 +47697,15 @@ async function flushAllSessions() {
|
|
|
47698
47697
|
var COMPRESS_TOOL_NAME = "compress";
|
|
47699
47698
|
var ACP_TEXT_OPEN = "<acp_compress>";
|
|
47700
47699
|
var ACP_TEXT_CLOSE = "</acp_compress>";
|
|
47700
|
+
var ACP_STATUS_OPEN = "<acp_status>";
|
|
47701
|
+
var ACP_STATUS_CLOSE = "</acp_status>";
|
|
47702
|
+
var ACP_SEARCH_OPEN = "<acp_search>";
|
|
47703
|
+
var ACP_SEARCH_CLOSE = "</acp_search>";
|
|
47704
|
+
var ACP_DECOMPRESS_OPEN = "<acp_decompress>";
|
|
47705
|
+
var ACP_DECOMPRESS_CLOSE = "</acp_decompress>";
|
|
47701
47706
|
var COMPRESS_TOOL = {
|
|
47702
47707
|
name: COMPRESS_TOOL_NAME,
|
|
47703
|
-
description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}].",
|
|
47708
|
+
description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}]. REQUIRED \u2014 compress without content is invalid.",
|
|
47704
47709
|
input_schema: {
|
|
47705
47710
|
type: "object",
|
|
47706
47711
|
properties: {
|
|
@@ -47719,23 +47724,23 @@ var COMPRESS_TOOL = {
|
|
|
47719
47724
|
required: ["startId", "endId", "summary"]
|
|
47720
47725
|
}
|
|
47721
47726
|
}
|
|
47722
|
-
}
|
|
47727
|
+
},
|
|
47728
|
+
required: ["content"]
|
|
47723
47729
|
}
|
|
47724
47730
|
};
|
|
47725
|
-
function parseCompressInput(input) {
|
|
47731
|
+
function parseCompressInput(input, callId) {
|
|
47726
47732
|
if (!input || typeof input !== "object") {
|
|
47727
47733
|
log("warn", `[acp-compress-input] rejected: not object (${typeof input})`);
|
|
47728
47734
|
return [];
|
|
47729
47735
|
}
|
|
47730
47736
|
const obj = input;
|
|
47731
|
-
if (Array.isArray(obj.content)) {
|
|
47732
|
-
const out = obj.content.map((r) => toRange(r)).filter((r) => r !== null);
|
|
47733
|
-
if (out.length === 0) log("warn", `[acp-compress-input] content array but 0 valid ranges. keys per item: ${obj.content.map((c) => Object.keys(c ?? {}).join(",")).join(" | ")}`);
|
|
47734
|
-
return out;
|
|
47735
|
-
}
|
|
47736
47737
|
const single = toRange(obj);
|
|
47737
|
-
|
|
47738
|
-
|
|
47738
|
+
const ranges = Array.isArray(obj.content) ? obj.content.map((r) => toRange(r)).filter((r) => r !== null) : single ? [single] : [];
|
|
47739
|
+
if (ranges.length === 0) {
|
|
47740
|
+
log("warn", `[acp-compress-input] parsed 0 valid ranges. top keys: ${Object.keys(obj).join(",")}`);
|
|
47741
|
+
}
|
|
47742
|
+
if (callId) for (const r of ranges) r.compressCallId = callId;
|
|
47743
|
+
return ranges;
|
|
47739
47744
|
}
|
|
47740
47745
|
function toRange(r) {
|
|
47741
47746
|
const startRef = pick(r, "startId", "startRef");
|
|
@@ -47764,7 +47769,7 @@ var COMPRESS_TOOL_OPENAI = {
|
|
|
47764
47769
|
topic: { type: "string", description: "Optional short title for the compressed range" },
|
|
47765
47770
|
content: {
|
|
47766
47771
|
type: "array",
|
|
47767
|
-
description: "One or more ranges to compress into separate summary blocks",
|
|
47772
|
+
description: "One or more ranges to compress into separate summary blocks. REQUIRED \u2014 compress without content is invalid.",
|
|
47768
47773
|
items: {
|
|
47769
47774
|
type: "object",
|
|
47770
47775
|
properties: {
|
|
@@ -47776,7 +47781,8 @@ var COMPRESS_TOOL_OPENAI = {
|
|
|
47776
47781
|
required: ["startId", "endId", "summary"]
|
|
47777
47782
|
}
|
|
47778
47783
|
}
|
|
47779
|
-
}
|
|
47784
|
+
},
|
|
47785
|
+
required: ["content"]
|
|
47780
47786
|
}
|
|
47781
47787
|
}
|
|
47782
47788
|
};
|
|
@@ -47826,7 +47832,29 @@ Rules for the trigger:
|
|
|
47826
47832
|
- JSON shape matches the compress tool: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
|
|
47827
47833
|
- After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
|
|
47828
47834
|
- Do NOT wrap the marker in code fences, quotes, or commentary.
|
|
47829
|
-
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large
|
|
47835
|
+
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.
|
|
47836
|
+
|
|
47837
|
+
ACP TOOLS (TEXT TRIGGERS)
|
|
47838
|
+
|
|
47839
|
+
Since host tools cannot coexist with a declared tools field, ALL ACP tools use text triggers. Emit the marker; the proxy intercepts and executes it; the marker is stripped from what the user sees.
|
|
47840
|
+
|
|
47841
|
+
1. acp_status \u2014 view context usage, compression state, and compressible ranges:
|
|
47842
|
+
${ACP_STATUS_OPEN}${ACP_STATUS_CLOSE}
|
|
47843
|
+
No payload needed. Use this FIRST when unsure about context state.
|
|
47844
|
+
|
|
47845
|
+
2. search_context \u2014 search compressed block summaries by keyword:
|
|
47846
|
+
${ACP_SEARCH_OPEN}{"query":"auth token refresh"}${ACP_SEARCH_CLOSE}
|
|
47847
|
+
Use when you need details that may have been compressed away.
|
|
47848
|
+
|
|
47849
|
+
3. decompress \u2014 restore compressed content for exact details:
|
|
47850
|
+
${ACP_DECOMPRESS_OPEN}{"blockId":"b5"}${ACP_DECOMPRESS_CLOSE}
|
|
47851
|
+
Optional: {"blockId":"b5","toFile":"/tmp/b5.txt"} to write to file instead.
|
|
47852
|
+
Optional: {"blockId":"b5","full":true} to restore all the way to original messages.
|
|
47853
|
+
|
|
47854
|
+
Rules for ALL triggers:
|
|
47855
|
+
- Output on its own, NO surrounding prose. Just the raw marker.
|
|
47856
|
+
- After emitting, STOP your turn. The proxy executes and returns the result.
|
|
47857
|
+
- Do NOT wrap in code fences, quotes, or commentary.`;
|
|
47830
47858
|
}
|
|
47831
47859
|
var DECOMPRESS_TOOL_NAME = "decompress";
|
|
47832
47860
|
var DECOMPRESS_TOOL_OPENAI = {
|
|
@@ -47936,6 +47964,14 @@ var PROXY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
47936
47964
|
SEARCH_CONTEXT_TOOL_NAME,
|
|
47937
47965
|
ACP_STATUS_TOOL_NAME
|
|
47938
47966
|
]);
|
|
47967
|
+
var MUTATING_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
47968
|
+
COMPRESS_TOOL_NAME,
|
|
47969
|
+
DECOMPRESS_TOOL_NAME
|
|
47970
|
+
]);
|
|
47971
|
+
var READONLY_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
47972
|
+
SEARCH_CONTEXT_TOOL_NAME,
|
|
47973
|
+
ACP_STATUS_TOOL_NAME
|
|
47974
|
+
]);
|
|
47939
47975
|
|
|
47940
47976
|
// src/decompress-shared.ts
|
|
47941
47977
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -48595,7 +48631,9 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
|
|
|
48595
48631
|
}).filter((tc) => tc.name.length > 0);
|
|
48596
48632
|
const proxyCalls = toolCalls.filter((tc) => PROXY_TOOL_NAMES.has(tc.name));
|
|
48597
48633
|
const realCalls = toolCalls.filter((tc) => !PROXY_TOOL_NAMES.has(tc.name));
|
|
48598
|
-
const
|
|
48634
|
+
const mutatingProxy = proxyCalls.filter((tc) => MUTATING_PROXY_TOOLS.has(tc.name));
|
|
48635
|
+
const readonlyProxy = proxyCalls.filter((tc) => READONLY_PROXY_TOOLS.has(tc.name));
|
|
48636
|
+
const hasMutatingOnly = mutatingProxy.length > 0 && realCalls.length === 0;
|
|
48599
48637
|
if (usage) {
|
|
48600
48638
|
const prompt = usage.prompt_tokens ?? usage.input_tokens;
|
|
48601
48639
|
const det = usage.prompt_tokens_details ?? usage.prompt_cache_hit_tokens;
|
|
@@ -48611,7 +48649,27 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
|
|
|
48611
48649
|
ctx.session.stats.cacheSamples += 1;
|
|
48612
48650
|
}
|
|
48613
48651
|
}
|
|
48614
|
-
if (!
|
|
48652
|
+
if (!hasMutatingOnly) {
|
|
48653
|
+
for (const tc of readonlyProxy) {
|
|
48654
|
+
let args = {};
|
|
48655
|
+
try {
|
|
48656
|
+
args = JSON.parse(tc.arguments);
|
|
48657
|
+
} catch {
|
|
48658
|
+
args = {};
|
|
48659
|
+
}
|
|
48660
|
+
let result;
|
|
48661
|
+
try {
|
|
48662
|
+
result = executeProxyTool(tc.name, args, ctx);
|
|
48663
|
+
} catch (e) {
|
|
48664
|
+
result = `[${tc.name} FAILED: ${e instanceof Error ? e.message : String(e)}]`;
|
|
48665
|
+
}
|
|
48666
|
+
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48667
|
+
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48668
|
+
yield Buffer.from(
|
|
48669
|
+
buildContentSse(responseId, model, buildVisibilityMarker(tc.name, result)),
|
|
48670
|
+
"utf8"
|
|
48671
|
+
);
|
|
48672
|
+
}
|
|
48615
48673
|
for (const tc of realCalls) {
|
|
48616
48674
|
yield Buffer.from(buildToolCallSse(makeBase(), tc), "utf8");
|
|
48617
48675
|
}
|
|
@@ -48867,8 +48925,23 @@ async function* compressLoopAnthropicStream(initialUpstream, ctx, requestBody, r
|
|
|
48867
48925
|
}
|
|
48868
48926
|
clientIndex = state.clientIndex;
|
|
48869
48927
|
const proxyCalls = [...state.toolBlocks.values()].filter((b2) => PROXY_TOOL_NAMES.has(b2.name));
|
|
48870
|
-
const
|
|
48871
|
-
|
|
48928
|
+
const mutatingProxy = proxyCalls.filter((b2) => MUTATING_PROXY_TOOLS.has(b2.name));
|
|
48929
|
+
const readonlyProxy = proxyCalls.filter((b2) => READONLY_PROXY_TOOLS.has(b2.name));
|
|
48930
|
+
const hasMutatingOnly = mutatingProxy.length > 0 && !hasRealToolUse;
|
|
48931
|
+
if (!hasMutatingOnly) {
|
|
48932
|
+
for (const tc of readonlyProxy) {
|
|
48933
|
+
const args = safeParse2(tc.json);
|
|
48934
|
+
let result;
|
|
48935
|
+
try {
|
|
48936
|
+
result = executeProxyTool2(tc.name, args, ctx);
|
|
48937
|
+
} catch (e) {
|
|
48938
|
+
result = `[${tc.name} FAILED: ${e instanceof Error ? e.message : String(e)}]`;
|
|
48939
|
+
}
|
|
48940
|
+
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48941
|
+
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48942
|
+
yield Buffer.from(buildTextBlockSse(clientIndex, buildVisibilityMarker(tc.name, result)), "utf8");
|
|
48943
|
+
clientIndex++;
|
|
48944
|
+
}
|
|
48872
48945
|
const stop = hasRealToolUse ? "tool_use" : roundStopReason ?? "end_turn";
|
|
48873
48946
|
yield Buffer.from(buildTerminalSse(stop, totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
48874
48947
|
return;
|
|
@@ -49281,6 +49354,31 @@ function replaceResponsesJsonText(parts, text) {
|
|
|
49281
49354
|
part.text = index === 0 ? text : "";
|
|
49282
49355
|
});
|
|
49283
49356
|
}
|
|
49357
|
+
function surfaceReadonlyJson(current, proxyCalls, ctx) {
|
|
49358
|
+
const markers = [];
|
|
49359
|
+
for (const call of proxyCalls) {
|
|
49360
|
+
if (MUTATING_PROXY_TOOLS.has(call.name)) continue;
|
|
49361
|
+
let args = {};
|
|
49362
|
+
try {
|
|
49363
|
+
args = JSON.parse(call.arguments);
|
|
49364
|
+
} catch {
|
|
49365
|
+
args = {};
|
|
49366
|
+
}
|
|
49367
|
+
let result;
|
|
49368
|
+
try {
|
|
49369
|
+
result = executeProxyTool3(call.name, args, ctx);
|
|
49370
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49371
|
+
} catch (e) {
|
|
49372
|
+
result = `\u274C [ACP] ${call.name} FAILED: ${String(e)}`;
|
|
49373
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) FAILED: ${String(e)}]`);
|
|
49374
|
+
}
|
|
49375
|
+
markers.push(buildVisibilityMarker(call.name, result));
|
|
49376
|
+
}
|
|
49377
|
+
if (markers.length === 0) return current;
|
|
49378
|
+
const out = Array.isArray(current.output) ? [...current.output] : [];
|
|
49379
|
+
out.push({ type: "message", id: `msg_acp_ro_${Date.now()}_${markers.length}`, role: "assistant", content: [{ type: "output_text", text: markers.join("\n") }] });
|
|
49380
|
+
return { ...current, output: out };
|
|
49381
|
+
}
|
|
49284
49382
|
async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
|
|
49285
49383
|
let current = initialResponse;
|
|
49286
49384
|
for (let loopCount = 1; loopCount <= 5; loopCount++) {
|
|
@@ -49289,8 +49387,12 @@ async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requ
|
|
|
49289
49387
|
const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
|
|
49290
49388
|
const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
|
|
49291
49389
|
const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
|
|
49292
|
-
|
|
49293
|
-
|
|
49390
|
+
const mutatingProxy = proxyCalls.filter((call) => MUTATING_PROXY_TOOLS.has(call.name));
|
|
49391
|
+
if (mutatingProxy.length === 0 || realCalls.length > 0) {
|
|
49392
|
+
if (proxyCalls.length > 0) {
|
|
49393
|
+
replaceResponsesJsonText(output.textParts, extracted.clean);
|
|
49394
|
+
current = surfaceReadonlyJson(current, proxyCalls, ctx);
|
|
49395
|
+
}
|
|
49294
49396
|
return current;
|
|
49295
49397
|
}
|
|
49296
49398
|
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
@@ -49437,9 +49539,30 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
|
|
|
49437
49539
|
const allCalls = [...fcByItemId.values()].filter((c) => c.name.length > 0);
|
|
49438
49540
|
const proxyCalls = allCalls.filter((c) => PROXY_TOOL_NAMES.has(c.name));
|
|
49439
49541
|
const realCalls = allCalls.filter((c) => !PROXY_TOOL_NAMES.has(c.name));
|
|
49542
|
+
const readonlyProxy = proxyCalls.filter((c) => READONLY_PROXY_TOOLS.has(c.name));
|
|
49440
49543
|
log("debug", `[acp-diag] round ${loopCount} allCalls=[${allCalls.map((c) => c.name).join(",")}] realCalls=[${realCalls.map((c) => c.name).join(",")}] customToolCalls=${customToolCalls} text=${JSON.stringify(contentText.slice(0, 120))}`);
|
|
49441
|
-
const
|
|
49442
|
-
if (!
|
|
49544
|
+
const hasMutatingOnly = proxyCalls.some((c) => MUTATING_PROXY_TOOLS.has(c.name)) && realCalls.length === 0;
|
|
49545
|
+
if (!hasMutatingOnly) {
|
|
49546
|
+
for (const fc of readonlyProxy) {
|
|
49547
|
+
let args = {};
|
|
49548
|
+
try {
|
|
49549
|
+
args = JSON.parse(fc.arguments);
|
|
49550
|
+
} catch (e) {
|
|
49551
|
+
log("warn", `[acp-compress-args] ${fc.name} JSON.parse failed: ${String(e)}`);
|
|
49552
|
+
args = {};
|
|
49553
|
+
}
|
|
49554
|
+
let result;
|
|
49555
|
+
try {
|
|
49556
|
+
result = executeProxyTool3(fc.name, args, ctx);
|
|
49557
|
+
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49558
|
+
ctx.log(`[acp-proxy: responses ${fc.name} (read-only) (${fc.callId}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49559
|
+
} catch (e) {
|
|
49560
|
+
result = `\u274C [ACP] ${fc.name} FAILED: ${String(e)}`;
|
|
49561
|
+
ctx.log(`[acp-proxy: responses ${fc.name} (read-only) (${fc.callId}) FAILED: ${String(e)}]`);
|
|
49562
|
+
}
|
|
49563
|
+
const markerItemId = `msg_acp_ro_${Date.now()}_${nextOutputIndex}`;
|
|
49564
|
+
yield Buffer.from(buildMessageItemSequence(markerItemId, nextOutputIndex++, buildVisibilityMarker(fc.name, result)), "utf8");
|
|
49565
|
+
}
|
|
49443
49566
|
let oi2 = nextOutputIndex;
|
|
49444
49567
|
for (const fc of realCalls) {
|
|
49445
49568
|
yield Buffer.from(buildFunctionCallEvents(fc, oi2), "utf8");
|
|
@@ -49451,7 +49574,8 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
|
|
|
49451
49574
|
return;
|
|
49452
49575
|
}
|
|
49453
49576
|
const hasUsage = !!responseObj?.usage;
|
|
49454
|
-
|
|
49577
|
+
const emittedReadonly = readonlyProxy.length > 0;
|
|
49578
|
+
if (contentText.length === 0 && realCalls.length === 0 && customToolCalls === 0 && !emittedReadonly && !hasUsage) {
|
|
49455
49579
|
ctx.log("[acp-proxy: empty upstream response (no content/usage) \u2014 injecting response.failed for client retry]");
|
|
49456
49580
|
yield Buffer.from(buildFailed(responseObj), "utf8");
|
|
49457
49581
|
return;
|
|
@@ -49532,6 +49656,1070 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
|
|
|
49532
49656
|
}
|
|
49533
49657
|
}
|
|
49534
49658
|
|
|
49659
|
+
// src/loop/core.ts
|
|
49660
|
+
var MAX_LOOP_ROUNDS = 10;
|
|
49661
|
+
function executeProxyTool4(toolName, args, ctx, callId) {
|
|
49662
|
+
if (toolName === "compress") {
|
|
49663
|
+
return applyRanges(parseCompressInput(args, callId), ctx);
|
|
49664
|
+
}
|
|
49665
|
+
if (toolName === "decompress") {
|
|
49666
|
+
return resolveDecompress(args, ctx);
|
|
49667
|
+
}
|
|
49668
|
+
if (toolName === "search_context") {
|
|
49669
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
49670
|
+
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
49671
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
49672
|
+
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
49673
|
+
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
49674
|
+
const lines = blocks.map((b2) => {
|
|
49675
|
+
const topic = b2.topic ?? "(no topic)";
|
|
49676
|
+
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
49677
|
+
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
49678
|
+
${preview}`;
|
|
49679
|
+
});
|
|
49680
|
+
return `Found ${blocks.length} block(s) for "${query}":
|
|
49681
|
+
|
|
49682
|
+
${lines.join("\n\n")}`;
|
|
49683
|
+
}
|
|
49684
|
+
if (toolName === "acp_status") {
|
|
49685
|
+
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
49686
|
+
}
|
|
49687
|
+
return `[Unknown proxy tool: ${toolName}]`;
|
|
49688
|
+
}
|
|
49689
|
+
function recordUsage(ctx, usage, round) {
|
|
49690
|
+
const prompt = usage.inputTokens;
|
|
49691
|
+
const cached = usage.cachedTokens;
|
|
49692
|
+
const out = usage.outputTokens;
|
|
49693
|
+
if (typeof prompt === "number") ctx.session.stats.inputTokens += prompt;
|
|
49694
|
+
ctx.session.stats.lastInputTokens = (typeof prompt === "number" ? prompt : 0) + (typeof cached === "number" ? cached : 0);
|
|
49695
|
+
if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
|
|
49696
|
+
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
49697
|
+
ctx.session.stats.cacheSamples += 1;
|
|
49698
|
+
const hitPct = typeof prompt === "number" && typeof cached === "number" && prompt + cached > 0 ? Math.round(cached / (prompt + cached) * 100) : 0;
|
|
49699
|
+
ctx.log(
|
|
49700
|
+
`[acp-usage] round ${round} input=${ctx.session.stats.lastInputTokens} cached=${cached ?? 0} (cache hit ${hitPct}%)`
|
|
49701
|
+
);
|
|
49702
|
+
}
|
|
49703
|
+
async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt) {
|
|
49704
|
+
let activeClearTimer = null;
|
|
49705
|
+
let currentUpstream = upstream;
|
|
49706
|
+
const coreMessages = [...ctx.messages];
|
|
49707
|
+
try {
|
|
49708
|
+
for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
|
|
49709
|
+
let assistantText = "";
|
|
49710
|
+
const calls = [];
|
|
49711
|
+
let usage = {};
|
|
49712
|
+
let finishReason;
|
|
49713
|
+
for await (const ev of adapter.parseStream(currentUpstream, round)) {
|
|
49714
|
+
if (ev.kind === "text") {
|
|
49715
|
+
assistantText += ev.delta;
|
|
49716
|
+
if (!ctx.textProtocol && round === 1 && ev.raw) {
|
|
49717
|
+
yield ev.raw;
|
|
49718
|
+
}
|
|
49719
|
+
} else if (ev.kind === "tool_call") {
|
|
49720
|
+
calls.push({ name: ev.name, callId: ev.callId, arguments: ev.arguments });
|
|
49721
|
+
} else if (ev.kind === "usage") {
|
|
49722
|
+
usage = {
|
|
49723
|
+
inputTokens: ev.inputTokens,
|
|
49724
|
+
outputTokens: ev.outputTokens,
|
|
49725
|
+
cachedTokens: ev.cachedTokens
|
|
49726
|
+
};
|
|
49727
|
+
} else if (ev.kind === "done") {
|
|
49728
|
+
finishReason = ev.finishReason;
|
|
49729
|
+
} else if (ev.kind === "meta") {
|
|
49730
|
+
if (round === 1 || !ev.firstRoundOnly) {
|
|
49731
|
+
yield ev.chunk;
|
|
49732
|
+
}
|
|
49733
|
+
}
|
|
49734
|
+
}
|
|
49735
|
+
if (usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cachedTokens !== void 0) {
|
|
49736
|
+
recordUsage(ctx, usage, round);
|
|
49737
|
+
}
|
|
49738
|
+
let resolvedText = assistantText;
|
|
49739
|
+
let allCalls = calls;
|
|
49740
|
+
if (ctx.textProtocol && assistantText.length > 0 && adapter.extractTextTriggers) {
|
|
49741
|
+
const extracted = adapter.extractTextTriggers(assistantText);
|
|
49742
|
+
resolvedText = extracted.clean;
|
|
49743
|
+
allCalls = [...calls, ...extracted.calls];
|
|
49744
|
+
}
|
|
49745
|
+
if (ctx.textProtocol && resolvedText.length > 0) {
|
|
49746
|
+
yield adapter.emitText(resolvedText);
|
|
49747
|
+
} else if (!ctx.textProtocol && round > 1 && resolvedText.length > 0) {
|
|
49748
|
+
yield adapter.emitText(resolvedText);
|
|
49749
|
+
}
|
|
49750
|
+
let realCalls = 0;
|
|
49751
|
+
const realToolCalls = [];
|
|
49752
|
+
const proxyResults = [];
|
|
49753
|
+
for (const call of allCalls) {
|
|
49754
|
+
if (PROXY_TOOL_NAMES.has(call.name)) {
|
|
49755
|
+
let parsedArgs;
|
|
49756
|
+
try {
|
|
49757
|
+
parsedArgs = call.arguments.length > 0 ? JSON.parse(call.arguments) : {};
|
|
49758
|
+
} catch {
|
|
49759
|
+
parsedArgs = {};
|
|
49760
|
+
}
|
|
49761
|
+
const result = executeProxyTool4(call.name, parsedArgs, ctx, call.callId);
|
|
49762
|
+
proxyResults.push({ name: call.name, callId: call.callId, result, arguments: call.arguments });
|
|
49763
|
+
yield adapter.emitMarker(call.name, result);
|
|
49764
|
+
} else {
|
|
49765
|
+
realToolCalls.push(call);
|
|
49766
|
+
realCalls += 1;
|
|
49767
|
+
}
|
|
49768
|
+
}
|
|
49769
|
+
if (ctx.debug) {
|
|
49770
|
+
const callSummary = allCalls.map((c) => {
|
|
49771
|
+
const argSnippet = c.arguments.length > 200 ? c.arguments.slice(0, 200) + "..." : c.arguments;
|
|
49772
|
+
return `${c.name}(${argSnippet})`;
|
|
49773
|
+
}).join(" | ");
|
|
49774
|
+
ctx.log(`[acp-loop] round ${round}: ${allCalls.length} call(s): ${callSummary || "(none)"}`);
|
|
49775
|
+
for (const pr2 of proxyResults) {
|
|
49776
|
+
const resSnippet = pr2.result.length > 300 ? pr2.result.slice(0, 300) + "..." : pr2.result;
|
|
49777
|
+
ctx.log(`[acp-loop] \u2192 ${pr2.name} result: ${resSnippet}`);
|
|
49778
|
+
}
|
|
49779
|
+
if (realCalls > 0) ctx.log(`[acp-loop] round ${round}: ${realCalls} real tool call(s) forwarded to client`);
|
|
49780
|
+
}
|
|
49781
|
+
if (proxyResults.length > 0) {
|
|
49782
|
+
if (resolvedText.length > 0) {
|
|
49783
|
+
coreMessages.push({
|
|
49784
|
+
id: `acp_loop_r${round}_asst`,
|
|
49785
|
+
role: "assistant",
|
|
49786
|
+
contentType: "text",
|
|
49787
|
+
text: resolvedText
|
|
49788
|
+
});
|
|
49789
|
+
}
|
|
49790
|
+
for (const pr2 of proxyResults) {
|
|
49791
|
+
if (ctx.textProtocol) {
|
|
49792
|
+
coreMessages.push({
|
|
49793
|
+
id: `acp_loop_r${round}_marker_${pr2.callId}`,
|
|
49794
|
+
role: "user",
|
|
49795
|
+
contentType: "text",
|
|
49796
|
+
text: buildVisibilityMarker(pr2.name, pr2.result)
|
|
49797
|
+
});
|
|
49798
|
+
} else {
|
|
49799
|
+
coreMessages.push({
|
|
49800
|
+
id: `acp_loop_r${round}_asst_tc_${pr2.callId}`,
|
|
49801
|
+
role: "assistant",
|
|
49802
|
+
contentType: "tool-call",
|
|
49803
|
+
toolName: pr2.name,
|
|
49804
|
+
toolCallId: pr2.callId,
|
|
49805
|
+
text: pr2.arguments
|
|
49806
|
+
});
|
|
49807
|
+
coreMessages.push({
|
|
49808
|
+
id: `acp_loop_r${round}_tool_${pr2.callId}`,
|
|
49809
|
+
role: "tool",
|
|
49810
|
+
contentType: "tool-result",
|
|
49811
|
+
toolCallId: pr2.callId,
|
|
49812
|
+
text: pr2.result
|
|
49813
|
+
});
|
|
49814
|
+
}
|
|
49815
|
+
}
|
|
49816
|
+
if (!ctx.textProtocol) {
|
|
49817
|
+
const hidden = hideConsumedCompressCalls(ctx.session.state, coreMessages);
|
|
49818
|
+
if (hidden.hidden > 0) {
|
|
49819
|
+
ctx.log(`[acp-loop] round ${round} hideConsumed hid ${hidden.hidden} compress record(s)`);
|
|
49820
|
+
coreMessages.length = 0;
|
|
49821
|
+
coreMessages.push(...hidden.messages);
|
|
49822
|
+
}
|
|
49823
|
+
}
|
|
49824
|
+
}
|
|
49825
|
+
for (const tc of realToolCalls) {
|
|
49826
|
+
yield adapter.emitToolCall(tc);
|
|
49827
|
+
}
|
|
49828
|
+
const reRequest = proxyResults.length > 0 && realCalls === 0;
|
|
49829
|
+
if (!reRequest) {
|
|
49830
|
+
yield adapter.emitCompletion({ finishReason, usage });
|
|
49831
|
+
return;
|
|
49832
|
+
}
|
|
49833
|
+
if (round >= MAX_LOOP_ROUNDS) {
|
|
49834
|
+
ctx.log(`[acp-loop] round ${round} hit MAX_LOOP_ROUNDS; completing gracefully`);
|
|
49835
|
+
log("warn", `[acp-loop] loop limit (${MAX_LOOP_ROUNDS}) reached; completing gracefully`);
|
|
49836
|
+
yield adapter.emitCompletion({ finishReason: "length", usage });
|
|
49837
|
+
return;
|
|
49838
|
+
}
|
|
49839
|
+
ctx.log(`[acp-loop] round ${round} saw mutating proxy tool; re-requesting`);
|
|
49840
|
+
const newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
|
|
49841
|
+
if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
|
|
49842
|
+
try {
|
|
49843
|
+
const fs5 = await import("fs");
|
|
49844
|
+
const dumpDir = process.env.ACP_DUMP_DIR || `${process.env.HOME}/.local/state/billion-context/dumps`;
|
|
49845
|
+
fs5.mkdirSync(dumpDir, { recursive: true });
|
|
49846
|
+
const sid = ctx.session.id ?? "unknown";
|
|
49847
|
+
fs5.writeFileSync(`${dumpDir}/req-${Date.now()}-${sid}-REREQUEST.json`, JSON.stringify(newBody, null, 2));
|
|
49848
|
+
} catch {
|
|
49849
|
+
}
|
|
49850
|
+
}
|
|
49851
|
+
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49852
|
+
method: "POST",
|
|
49853
|
+
headers: requestOptions.headers,
|
|
49854
|
+
body: JSON.stringify(newBody),
|
|
49855
|
+
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49856
|
+
});
|
|
49857
|
+
if (!resp.ok || !resp.body) {
|
|
49858
|
+
clearTimer();
|
|
49859
|
+
const errText = await resp.text().catch(() => "upstream error");
|
|
49860
|
+
ctx.log(`[acp-proxy: compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
49861
|
+
log("error", `[acp-loop] upstream error ${resp.status}: ${errText.slice(0, 200)}`);
|
|
49862
|
+
yield adapter.emitError(`upstream error ${resp.status}: ${errText.slice(0, 200)}`);
|
|
49863
|
+
return;
|
|
49864
|
+
}
|
|
49865
|
+
currentUpstream = resp.body;
|
|
49866
|
+
if (activeClearTimer) activeClearTimer();
|
|
49867
|
+
activeClearTimer = clearTimer;
|
|
49868
|
+
}
|
|
49869
|
+
} finally {
|
|
49870
|
+
if (activeClearTimer) {
|
|
49871
|
+
activeClearTimer();
|
|
49872
|
+
activeClearTimer = null;
|
|
49873
|
+
}
|
|
49874
|
+
}
|
|
49875
|
+
}
|
|
49876
|
+
|
|
49877
|
+
// src/loop/adapter-responses.ts
|
|
49878
|
+
async function* iterSseEvents(stream2) {
|
|
49879
|
+
const reader = stream2.getReader();
|
|
49880
|
+
let buf = "";
|
|
49881
|
+
try {
|
|
49882
|
+
while (true) {
|
|
49883
|
+
let done;
|
|
49884
|
+
let value;
|
|
49885
|
+
try {
|
|
49886
|
+
({ done, value } = await reader.read());
|
|
49887
|
+
} catch {
|
|
49888
|
+
break;
|
|
49889
|
+
}
|
|
49890
|
+
if (done) break;
|
|
49891
|
+
buf += new TextDecoder().decode(value, { stream: true });
|
|
49892
|
+
buf = buf.replace(/\r\n|\r/g, "\n");
|
|
49893
|
+
let idx;
|
|
49894
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
49895
|
+
const raw = buf.slice(0, idx);
|
|
49896
|
+
buf = buf.slice(idx + 2);
|
|
49897
|
+
if (raw.trim().length > 0) yield raw;
|
|
49898
|
+
}
|
|
49899
|
+
}
|
|
49900
|
+
if (buf.trim().length > 0) yield buf;
|
|
49901
|
+
} finally {
|
|
49902
|
+
reader.releaseLock();
|
|
49903
|
+
}
|
|
49904
|
+
}
|
|
49905
|
+
function extractEventType2(rawEvent) {
|
|
49906
|
+
for (const l of rawEvent.split("\n")) {
|
|
49907
|
+
if (l.startsWith("event:")) return l.slice(6).trim();
|
|
49908
|
+
}
|
|
49909
|
+
return null;
|
|
49910
|
+
}
|
|
49911
|
+
function extractDataLine2(rawEvent) {
|
|
49912
|
+
const parts = [];
|
|
49913
|
+
for (const l of rawEvent.split("\n")) {
|
|
49914
|
+
if (l.startsWith("data:")) {
|
|
49915
|
+
let v2 = l.slice(5);
|
|
49916
|
+
if (v2.startsWith(" ")) v2 = v2.slice(1);
|
|
49917
|
+
parts.push(v2);
|
|
49918
|
+
}
|
|
49919
|
+
}
|
|
49920
|
+
return parts.length ? parts.join("\n") : null;
|
|
49921
|
+
}
|
|
49922
|
+
function buildMessageItemSequence2(itemId, outputIndex, text) {
|
|
49923
|
+
const item = { type: "message", id: itemId, role: "assistant", content: [] };
|
|
49924
|
+
const part = { type: "output_text", text: "" };
|
|
49925
|
+
const doneItem = {
|
|
49926
|
+
type: "message",
|
|
49927
|
+
id: itemId,
|
|
49928
|
+
role: "assistant",
|
|
49929
|
+
content: [{ type: "output_text", text }]
|
|
49930
|
+
};
|
|
49931
|
+
return Buffer.from(
|
|
49932
|
+
[
|
|
49933
|
+
`event: response.output_item.added
|
|
49934
|
+
data: ${JSON.stringify({ type: "response.output_item.added", output_index: outputIndex, item })}
|
|
49935
|
+
|
|
49936
|
+
`,
|
|
49937
|
+
`event: response.content_part.added
|
|
49938
|
+
data: ${JSON.stringify({ type: "response.content_part.added", item_id: itemId, output_index: outputIndex, part })}
|
|
49939
|
+
|
|
49940
|
+
`,
|
|
49941
|
+
`event: response.output_text.delta
|
|
49942
|
+
data: ${JSON.stringify({ type: "response.output_text.delta", item_id: itemId, output_index: outputIndex, delta: text })}
|
|
49943
|
+
|
|
49944
|
+
`,
|
|
49945
|
+
`event: response.output_text.done
|
|
49946
|
+
data: ${JSON.stringify({ type: "response.output_text.done", item_id: itemId, output_index: outputIndex, text })}
|
|
49947
|
+
|
|
49948
|
+
`,
|
|
49949
|
+
`event: response.content_part.done
|
|
49950
|
+
data: ${JSON.stringify({ type: "response.content_part.done", item_id: itemId, output_index: outputIndex, part: { type: "output_text", text } })}
|
|
49951
|
+
|
|
49952
|
+
`,
|
|
49953
|
+
`event: response.output_item.done
|
|
49954
|
+
data: ${JSON.stringify({ type: "response.output_item.done", output_index: outputIndex, item: doneItem })}
|
|
49955
|
+
|
|
49956
|
+
`
|
|
49957
|
+
].join(""),
|
|
49958
|
+
"utf8"
|
|
49959
|
+
);
|
|
49960
|
+
}
|
|
49961
|
+
function buildFunctionCallEvents2(fc, itemId, outputIndex) {
|
|
49962
|
+
return Buffer.from(
|
|
49963
|
+
[
|
|
49964
|
+
`event: response.output_item.added
|
|
49965
|
+
data: ${JSON.stringify({
|
|
49966
|
+
type: "response.output_item.added",
|
|
49967
|
+
output_index: outputIndex,
|
|
49968
|
+
item: { type: "function_call", id: itemId, call_id: fc.callId, name: fc.name, arguments: "" }
|
|
49969
|
+
})}
|
|
49970
|
+
|
|
49971
|
+
`,
|
|
49972
|
+
`event: response.function_call_arguments.delta
|
|
49973
|
+
data: ${JSON.stringify({
|
|
49974
|
+
type: "response.function_call_arguments.delta",
|
|
49975
|
+
item_id: itemId,
|
|
49976
|
+
delta: fc.arguments
|
|
49977
|
+
})}
|
|
49978
|
+
|
|
49979
|
+
`,
|
|
49980
|
+
`event: response.function_call_arguments.done
|
|
49981
|
+
data: ${JSON.stringify({
|
|
49982
|
+
type: "response.function_call_arguments.done",
|
|
49983
|
+
item_id: itemId,
|
|
49984
|
+
arguments: fc.arguments
|
|
49985
|
+
})}
|
|
49986
|
+
|
|
49987
|
+
`,
|
|
49988
|
+
`event: response.output_item.done
|
|
49989
|
+
data: ${JSON.stringify({
|
|
49990
|
+
type: "response.output_item.done",
|
|
49991
|
+
output_index: outputIndex,
|
|
49992
|
+
item: { type: "function_call", id: itemId, call_id: fc.callId, name: fc.name, arguments: fc.arguments }
|
|
49993
|
+
})}
|
|
49994
|
+
|
|
49995
|
+
`
|
|
49996
|
+
].join(""),
|
|
49997
|
+
"utf8"
|
|
49998
|
+
);
|
|
49999
|
+
}
|
|
50000
|
+
function buildCompleted2(responseObj) {
|
|
50001
|
+
const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
50002
|
+
return Buffer.from(
|
|
50003
|
+
`event: response.completed
|
|
50004
|
+
data: ${JSON.stringify({ type: "response.completed", response: resp })}
|
|
50005
|
+
|
|
50006
|
+
`,
|
|
50007
|
+
"utf8"
|
|
50008
|
+
);
|
|
50009
|
+
}
|
|
50010
|
+
function createResponsesAdapter(textProtocol, projection) {
|
|
50011
|
+
const suppressTextLifecycle = !!textProtocol;
|
|
50012
|
+
let outputIndex = 0;
|
|
50013
|
+
let responseObj = null;
|
|
50014
|
+
let terminalRaw = null;
|
|
50015
|
+
let terminalKind = null;
|
|
50016
|
+
return {
|
|
50017
|
+
buildRequest(coreMessages, systemPrompt, requestBody) {
|
|
50018
|
+
const customToolCallIds = /* @__PURE__ */ new Set();
|
|
50019
|
+
for (const m2 of coreMessages) {
|
|
50020
|
+
const bm = m2;
|
|
50021
|
+
const raw = bm?.rawResponsesItem;
|
|
50022
|
+
if (raw && (raw.type === "custom_tool_call" || raw.type === "custom_tool_call_output")) {
|
|
50023
|
+
const id = typeof raw.call_id === "string" ? raw.call_id : typeof raw.id === "string" ? raw.id : "";
|
|
50024
|
+
if (id) customToolCallIds.add(id);
|
|
50025
|
+
}
|
|
50026
|
+
}
|
|
50027
|
+
let inputItems;
|
|
50028
|
+
if (projection) {
|
|
50029
|
+
const rebuiltInput = patchResponsesInput(projection, coreMessages);
|
|
50030
|
+
inputItems = typeof rebuiltInput === "string" ? [{ type: "message", role: "user", content: rebuiltInput }] : rebuiltInput;
|
|
50031
|
+
} else {
|
|
50032
|
+
inputItems = coreToResponses(coreMessages, customToolCallIds);
|
|
50033
|
+
}
|
|
50034
|
+
const devParts = projection && projection.systemParts.length > 0 ? [...projection.systemParts, systemPrompt] : [systemPrompt];
|
|
50035
|
+
const withDev = injectResponsesDeveloperMessage(inputItems, devParts.join("\n\n---\n\n"));
|
|
50036
|
+
const rebuilt = { ...requestBody, input: withDev };
|
|
50037
|
+
delete rebuilt.previous_response_id;
|
|
50038
|
+
delete rebuilt.instructions;
|
|
50039
|
+
return rebuilt;
|
|
50040
|
+
},
|
|
50041
|
+
async *parseStream(upstream, round) {
|
|
50042
|
+
const pending = /* @__PURE__ */ new Map();
|
|
50043
|
+
for await (const eventStr of iterSseEvents(upstream)) {
|
|
50044
|
+
const type = extractEventType2(eventStr);
|
|
50045
|
+
const dataLine = extractDataLine2(eventStr);
|
|
50046
|
+
if (!type || !dataLine) continue;
|
|
50047
|
+
let obj;
|
|
50048
|
+
try {
|
|
50049
|
+
obj = JSON.parse(dataLine);
|
|
50050
|
+
} catch {
|
|
50051
|
+
continue;
|
|
50052
|
+
}
|
|
50053
|
+
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
50054
|
+
if (round === 1 && typeof obj.output_index === "number") {
|
|
50055
|
+
outputIndex = Math.max(outputIndex, obj.output_index + 1);
|
|
50056
|
+
}
|
|
50057
|
+
if (type === "response.created" || type === "response.in_progress") {
|
|
50058
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50059
|
+
} else if (type === "response.output_item.added") {
|
|
50060
|
+
const item = obj.item;
|
|
50061
|
+
if (item?.type === "function_call") {
|
|
50062
|
+
const itemId = typeof item.id === "string" ? item.id : "";
|
|
50063
|
+
pending.set(itemId, {
|
|
50064
|
+
itemId,
|
|
50065
|
+
callId: typeof item.call_id === "string" ? item.call_id : "",
|
|
50066
|
+
name: typeof item.name === "string" ? item.name : "",
|
|
50067
|
+
arguments: ""
|
|
50068
|
+
});
|
|
50069
|
+
} else if (item?.type === "custom_tool_call") {
|
|
50070
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
|
|
50071
|
+
} else if (!suppressTextLifecycle) {
|
|
50072
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50073
|
+
}
|
|
50074
|
+
} else if (type === "response.content_part.added" || type === "response.content_part.done" || type === "response.output_text.done") {
|
|
50075
|
+
if (!suppressTextLifecycle) {
|
|
50076
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50077
|
+
}
|
|
50078
|
+
} else if (type === "response.output_text.delta") {
|
|
50079
|
+
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
50080
|
+
if (delta.length > 0) {
|
|
50081
|
+
yield { kind: "text", delta, raw: rawBuf };
|
|
50082
|
+
}
|
|
50083
|
+
} else if (type === "response.function_call_arguments.delta") {
|
|
50084
|
+
const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
|
|
50085
|
+
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
50086
|
+
const fc = pending.get(itemId);
|
|
50087
|
+
if (fc) fc.arguments += delta;
|
|
50088
|
+
} else if (type === "response.function_call_arguments.done") {
|
|
50089
|
+
const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
|
|
50090
|
+
const args = typeof obj.arguments === "string" ? obj.arguments : "";
|
|
50091
|
+
const fc = pending.get(itemId);
|
|
50092
|
+
if (fc && args) fc.arguments = args;
|
|
50093
|
+
} else if (type === "response.output_item.done") {
|
|
50094
|
+
const item = obj.item;
|
|
50095
|
+
if (item?.type === "function_call") {
|
|
50096
|
+
const itemId = typeof item.id === "string" ? item.id : "";
|
|
50097
|
+
const fc = pending.get(itemId);
|
|
50098
|
+
if (fc) {
|
|
50099
|
+
if (typeof item.arguments === "string" && item.arguments) fc.arguments = item.arguments;
|
|
50100
|
+
pending.delete(itemId);
|
|
50101
|
+
yield {
|
|
50102
|
+
kind: "tool_call",
|
|
50103
|
+
name: fc.name,
|
|
50104
|
+
callId: fc.callId,
|
|
50105
|
+
arguments: fc.arguments
|
|
50106
|
+
};
|
|
50107
|
+
}
|
|
50108
|
+
} else if (item?.type === "custom_tool_call") {
|
|
50109
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
|
|
50110
|
+
} else if (!suppressTextLifecycle) {
|
|
50111
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50112
|
+
}
|
|
50113
|
+
} else if (type === "response.completed") {
|
|
50114
|
+
responseObj = obj.response ?? null;
|
|
50115
|
+
terminalKind = "completed";
|
|
50116
|
+
terminalRaw = null;
|
|
50117
|
+
const respUsage = responseObj?.usage;
|
|
50118
|
+
const pd = respUsage?.input_tokens_details;
|
|
50119
|
+
yield {
|
|
50120
|
+
kind: "usage",
|
|
50121
|
+
inputTokens: typeof respUsage?.input_tokens === "number" ? respUsage.input_tokens : void 0,
|
|
50122
|
+
outputTokens: typeof respUsage?.output_tokens === "number" ? respUsage.output_tokens : void 0,
|
|
50123
|
+
cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
|
|
50124
|
+
};
|
|
50125
|
+
yield { kind: "done", finishReason: "completed" };
|
|
50126
|
+
} else if (type === "response.incomplete") {
|
|
50127
|
+
terminalKind = "incomplete";
|
|
50128
|
+
terminalRaw = rawBuf;
|
|
50129
|
+
yield { kind: "done", finishReason: "incomplete" };
|
|
50130
|
+
} else if (type === "response.failed" || type === "response.error") {
|
|
50131
|
+
terminalKind = "failed";
|
|
50132
|
+
terminalRaw = rawBuf;
|
|
50133
|
+
yield { kind: "done", finishReason: "failed" };
|
|
50134
|
+
} else {
|
|
50135
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50136
|
+
}
|
|
50137
|
+
}
|
|
50138
|
+
if (!terminalKind) {
|
|
50139
|
+
yield { kind: "done", finishReason: "failed" };
|
|
50140
|
+
}
|
|
50141
|
+
},
|
|
50142
|
+
emitText(delta) {
|
|
50143
|
+
return buildMessageItemSequence2(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
|
|
50144
|
+
},
|
|
50145
|
+
emitToolCall(call) {
|
|
50146
|
+
const buf = buildFunctionCallEvents2(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
|
|
50147
|
+
outputIndex += 1;
|
|
50148
|
+
return buf;
|
|
50149
|
+
},
|
|
50150
|
+
emitMarker(toolName, result) {
|
|
50151
|
+
return buildMessageItemSequence2(
|
|
50152
|
+
`marker-${Date.now()}-${outputIndex}`,
|
|
50153
|
+
outputIndex++,
|
|
50154
|
+
buildVisibilityMarker(toolName, result)
|
|
50155
|
+
);
|
|
50156
|
+
},
|
|
50157
|
+
emitCompletion(opts) {
|
|
50158
|
+
if (terminalRaw && (terminalKind === "failed" || terminalKind === "incomplete")) {
|
|
50159
|
+
return terminalRaw;
|
|
50160
|
+
}
|
|
50161
|
+
if (!responseObj && opts?.finishReason === "failed") {
|
|
50162
|
+
const failed = {
|
|
50163
|
+
id: `resp-error-${Date.now()}`,
|
|
50164
|
+
status: "failed",
|
|
50165
|
+
error: { code: "server_error", message: "upstream returned no response" }
|
|
50166
|
+
};
|
|
50167
|
+
return Buffer.from(
|
|
50168
|
+
`event: response.failed
|
|
50169
|
+
data: ${JSON.stringify({ type: "response.failed", response: failed })}
|
|
50170
|
+
|
|
50171
|
+
`,
|
|
50172
|
+
"utf8"
|
|
50173
|
+
);
|
|
50174
|
+
}
|
|
50175
|
+
let resp = responseObj ? { ...responseObj } : { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
50176
|
+
if (opts?.usage) {
|
|
50177
|
+
const usage = {
|
|
50178
|
+
...typeof resp.usage === "object" ? resp.usage : {}
|
|
50179
|
+
};
|
|
50180
|
+
if (typeof opts.usage.inputTokens === "number") usage.input_tokens = opts.usage.inputTokens;
|
|
50181
|
+
if (typeof opts.usage.outputTokens === "number") usage.output_tokens = opts.usage.outputTokens;
|
|
50182
|
+
if (typeof opts.usage.cachedTokens === "number") {
|
|
50183
|
+
usage.input_tokens_details = {
|
|
50184
|
+
...usage.input_tokens_details ?? {},
|
|
50185
|
+
cached_tokens: opts.usage.cachedTokens
|
|
50186
|
+
};
|
|
50187
|
+
}
|
|
50188
|
+
resp = { ...resp, usage };
|
|
50189
|
+
}
|
|
50190
|
+
return buildCompleted2(resp);
|
|
50191
|
+
},
|
|
50192
|
+
emitError(message) {
|
|
50193
|
+
const resp = {
|
|
50194
|
+
id: `resp-error-${Date.now()}`,
|
|
50195
|
+
status: "failed",
|
|
50196
|
+
error: { code: "server_error", message }
|
|
50197
|
+
};
|
|
50198
|
+
return Buffer.from(
|
|
50199
|
+
`event: response.failed
|
|
50200
|
+
data: ${JSON.stringify({ type: "response.failed", response: resp })}
|
|
50201
|
+
|
|
50202
|
+
`,
|
|
50203
|
+
"utf8"
|
|
50204
|
+
);
|
|
50205
|
+
},
|
|
50206
|
+
extractTextTriggers(text) {
|
|
50207
|
+
const calls = [];
|
|
50208
|
+
let clean = text;
|
|
50209
|
+
let hadTrigger = false;
|
|
50210
|
+
const triggers = [
|
|
50211
|
+
{ name: "compress", open: ACP_TEXT_OPEN, close: ACP_TEXT_CLOSE, requirePayload: true },
|
|
50212
|
+
{ name: "acp_status", open: ACP_STATUS_OPEN, close: ACP_STATUS_CLOSE, requirePayload: false },
|
|
50213
|
+
{ name: "search_context", open: ACP_SEARCH_OPEN, close: ACP_SEARCH_CLOSE, requirePayload: true },
|
|
50214
|
+
{ name: "decompress", open: ACP_DECOMPRESS_OPEN, close: ACP_DECOMPRESS_CLOSE, requirePayload: true }
|
|
50215
|
+
];
|
|
50216
|
+
for (const t of triggers) {
|
|
50217
|
+
let start = clean.indexOf(t.open);
|
|
50218
|
+
while (start >= 0) {
|
|
50219
|
+
const end = clean.indexOf(t.close, start + t.open.length);
|
|
50220
|
+
if (end < 0) break;
|
|
50221
|
+
hadTrigger = true;
|
|
50222
|
+
const payload = clean.slice(start + t.open.length, end).trim();
|
|
50223
|
+
if (payload.length > 0 || !t.requirePayload) {
|
|
50224
|
+
const stamp = `${Date.now()}-${calls.length}`;
|
|
50225
|
+
calls.push({
|
|
50226
|
+
name: t.name,
|
|
50227
|
+
callId: `call_text_${stamp}`,
|
|
50228
|
+
arguments: payload.length > 0 ? payload : "{}"
|
|
50229
|
+
});
|
|
50230
|
+
}
|
|
50231
|
+
clean = clean.slice(0, start) + clean.slice(end + t.close.length);
|
|
50232
|
+
start = clean.indexOf(t.open);
|
|
50233
|
+
}
|
|
50234
|
+
}
|
|
50235
|
+
return { clean: hadTrigger ? clean : text, calls };
|
|
50236
|
+
}
|
|
50237
|
+
};
|
|
50238
|
+
}
|
|
50239
|
+
|
|
50240
|
+
// src/loop/adapter-openai.ts
|
|
50241
|
+
async function* iterSseChunks(stream2) {
|
|
50242
|
+
const reader = stream2.getReader();
|
|
50243
|
+
let buf = "";
|
|
50244
|
+
try {
|
|
50245
|
+
while (true) {
|
|
50246
|
+
let done;
|
|
50247
|
+
let value;
|
|
50248
|
+
try {
|
|
50249
|
+
({ done, value } = await reader.read());
|
|
50250
|
+
} catch {
|
|
50251
|
+
break;
|
|
50252
|
+
}
|
|
50253
|
+
if (done) break;
|
|
50254
|
+
buf += new TextDecoder().decode(value, { stream: true });
|
|
50255
|
+
buf = buf.replace(/\r\n|\r/g, "\n");
|
|
50256
|
+
let idx;
|
|
50257
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
50258
|
+
const raw = buf.slice(0, idx);
|
|
50259
|
+
buf = buf.slice(idx + 2);
|
|
50260
|
+
if (raw.trim().length > 0) yield raw;
|
|
50261
|
+
}
|
|
50262
|
+
}
|
|
50263
|
+
if (buf.trim().length > 0) yield buf;
|
|
50264
|
+
} finally {
|
|
50265
|
+
reader.releaseLock();
|
|
50266
|
+
}
|
|
50267
|
+
}
|
|
50268
|
+
function createOpenaiAdapter(requestBody) {
|
|
50269
|
+
const model = requestBody.model ?? "unknown";
|
|
50270
|
+
let responseId = `chatcmpl-proxy-${Date.now()}`;
|
|
50271
|
+
let toolIndex = 0;
|
|
50272
|
+
const makeBase = () => ({
|
|
50273
|
+
id: responseId,
|
|
50274
|
+
object: "chat.completion.chunk",
|
|
50275
|
+
created: Date.now(),
|
|
50276
|
+
model
|
|
50277
|
+
});
|
|
50278
|
+
const buildContent = (content) => Buffer.from(
|
|
50279
|
+
`data: ${JSON.stringify({
|
|
50280
|
+
id: responseId,
|
|
50281
|
+
object: "chat.completion.chunk",
|
|
50282
|
+
created: Date.now(),
|
|
50283
|
+
model,
|
|
50284
|
+
choices: [{ index: 0, delta: { content }, finish_reason: null }]
|
|
50285
|
+
})}
|
|
50286
|
+
|
|
50287
|
+
`,
|
|
50288
|
+
"utf8"
|
|
50289
|
+
);
|
|
50290
|
+
const buildToolCall = (call) => {
|
|
50291
|
+
const idx = toolIndex++;
|
|
50292
|
+
return Buffer.from(
|
|
50293
|
+
`data: ${JSON.stringify({
|
|
50294
|
+
...makeBase(),
|
|
50295
|
+
choices: [{
|
|
50296
|
+
index: 0,
|
|
50297
|
+
delta: {
|
|
50298
|
+
tool_calls: [{
|
|
50299
|
+
index: idx,
|
|
50300
|
+
id: call.callId,
|
|
50301
|
+
type: "function",
|
|
50302
|
+
function: { name: call.name, arguments: call.arguments }
|
|
50303
|
+
}]
|
|
50304
|
+
},
|
|
50305
|
+
finish_reason: null
|
|
50306
|
+
}]
|
|
50307
|
+
})}
|
|
50308
|
+
|
|
50309
|
+
`,
|
|
50310
|
+
"utf8"
|
|
50311
|
+
);
|
|
50312
|
+
};
|
|
50313
|
+
const buildFinish = (finishReason, usage) => Buffer.from(
|
|
50314
|
+
`data: ${JSON.stringify({
|
|
50315
|
+
...makeBase(),
|
|
50316
|
+
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
50317
|
+
...usage ? { usage } : {}
|
|
50318
|
+
})}
|
|
50319
|
+
|
|
50320
|
+
`,
|
|
50321
|
+
"utf8"
|
|
50322
|
+
);
|
|
50323
|
+
return {
|
|
50324
|
+
buildRequest(coreMessages, systemPrompt, body) {
|
|
50325
|
+
const messages = coreToOpenai(coreMessages);
|
|
50326
|
+
const withSys = injectOpenaiSystem(messages, [systemPrompt]);
|
|
50327
|
+
return { ...body, messages: withSys };
|
|
50328
|
+
},
|
|
50329
|
+
async *parseStream(upstream, _round) {
|
|
50330
|
+
const pending = /* @__PURE__ */ new Map();
|
|
50331
|
+
for await (const eventStr of iterSseChunks(upstream)) {
|
|
50332
|
+
const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
|
|
50333
|
+
if (!dataLine) continue;
|
|
50334
|
+
const jsonStr = dataLine.slice(5).trim();
|
|
50335
|
+
if (jsonStr === "[DONE]") {
|
|
50336
|
+
for (const [, tc] of pending) {
|
|
50337
|
+
if (tc.name.length > 0 || tc.id.length > 0) {
|
|
50338
|
+
yield {
|
|
50339
|
+
kind: "tool_call",
|
|
50340
|
+
name: tc.name,
|
|
50341
|
+
callId: tc.id,
|
|
50342
|
+
arguments: tc.arguments
|
|
50343
|
+
};
|
|
50344
|
+
}
|
|
50345
|
+
}
|
|
50346
|
+
pending.clear();
|
|
50347
|
+
yield { kind: "done", finishReason: "stop" };
|
|
50348
|
+
continue;
|
|
50349
|
+
}
|
|
50350
|
+
let parsed;
|
|
50351
|
+
try {
|
|
50352
|
+
parsed = JSON.parse(jsonStr);
|
|
50353
|
+
} catch {
|
|
50354
|
+
continue;
|
|
50355
|
+
}
|
|
50356
|
+
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
50357
|
+
const choices = parsed.choices;
|
|
50358
|
+
const choice = choices?.[0];
|
|
50359
|
+
if (!choice) {
|
|
50360
|
+
if (parsed.usage) {
|
|
50361
|
+
const u2 = parsed.usage;
|
|
50362
|
+
const pd = u2.prompt_tokens_details;
|
|
50363
|
+
yield {
|
|
50364
|
+
kind: "usage",
|
|
50365
|
+
inputTokens: typeof u2.prompt_tokens === "number" ? u2.prompt_tokens : void 0,
|
|
50366
|
+
outputTokens: typeof u2.completion_tokens === "number" ? u2.completion_tokens : void 0,
|
|
50367
|
+
cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
|
|
50368
|
+
};
|
|
50369
|
+
}
|
|
50370
|
+
continue;
|
|
50371
|
+
}
|
|
50372
|
+
const delta = choice.delta;
|
|
50373
|
+
const finishReason = typeof choice.finish_reason === "string" ? choice.finish_reason : void 0;
|
|
50374
|
+
if (finishReason) {
|
|
50375
|
+
for (const [, tc] of pending) {
|
|
50376
|
+
if (tc.name.length > 0 || tc.id.length > 0) {
|
|
50377
|
+
yield {
|
|
50378
|
+
kind: "tool_call",
|
|
50379
|
+
name: tc.name,
|
|
50380
|
+
callId: tc.id,
|
|
50381
|
+
arguments: tc.arguments
|
|
50382
|
+
};
|
|
50383
|
+
}
|
|
50384
|
+
}
|
|
50385
|
+
pending.clear();
|
|
50386
|
+
const u2 = parsed.usage;
|
|
50387
|
+
const pd = u2?.prompt_tokens_details;
|
|
50388
|
+
yield {
|
|
50389
|
+
kind: "usage",
|
|
50390
|
+
inputTokens: typeof u2?.prompt_tokens === "number" ? u2.prompt_tokens : void 0,
|
|
50391
|
+
outputTokens: typeof u2?.completion_tokens === "number" ? u2.completion_tokens : void 0,
|
|
50392
|
+
cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
|
|
50393
|
+
};
|
|
50394
|
+
yield { kind: "done", finishReason };
|
|
50395
|
+
}
|
|
50396
|
+
if (!delta) continue;
|
|
50397
|
+
if (delta.tool_calls) {
|
|
50398
|
+
const tcs = delta.tool_calls;
|
|
50399
|
+
for (const tc of tcs) {
|
|
50400
|
+
const idx = typeof tc.index === "number" ? tc.index : 0;
|
|
50401
|
+
const fn = tc.function;
|
|
50402
|
+
const name = typeof fn?.name === "string" ? fn.name : "";
|
|
50403
|
+
const id = typeof tc.id === "string" ? tc.id : "";
|
|
50404
|
+
const args = typeof fn?.arguments === "string" ? fn.arguments : "";
|
|
50405
|
+
let buf = pending.get(idx);
|
|
50406
|
+
if (!buf) {
|
|
50407
|
+
buf = { index: idx, id, name, arguments: args };
|
|
50408
|
+
pending.set(idx, buf);
|
|
50409
|
+
} else {
|
|
50410
|
+
if (id) buf.id = id;
|
|
50411
|
+
if (name) buf.name = name;
|
|
50412
|
+
buf.arguments += args;
|
|
50413
|
+
}
|
|
50414
|
+
}
|
|
50415
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
50416
|
+
yield { kind: "text", delta: delta.content };
|
|
50417
|
+
}
|
|
50418
|
+
continue;
|
|
50419
|
+
}
|
|
50420
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
50421
|
+
yield { kind: "text", delta: delta.content, raw: rawBuf };
|
|
50422
|
+
} else if (delta.role || Object.keys(delta).length === 0 && !finishReason) {
|
|
50423
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50424
|
+
}
|
|
50425
|
+
}
|
|
50426
|
+
},
|
|
50427
|
+
emitText(delta) {
|
|
50428
|
+
return buildContent(delta);
|
|
50429
|
+
},
|
|
50430
|
+
emitToolCall(call) {
|
|
50431
|
+
return buildToolCall(call);
|
|
50432
|
+
},
|
|
50433
|
+
emitMarker(toolName, result) {
|
|
50434
|
+
return buildContent(buildVisibilityMarker(toolName, result));
|
|
50435
|
+
},
|
|
50436
|
+
emitCompletion(opts) {
|
|
50437
|
+
const finishReason = opts?.finishReason ?? "stop";
|
|
50438
|
+
const usage = opts?.usage ? {
|
|
50439
|
+
prompt_tokens: opts.usage.inputTokens,
|
|
50440
|
+
completion_tokens: opts.usage.outputTokens,
|
|
50441
|
+
...typeof opts.usage.cachedTokens === "number" ? { prompt_tokens_details: { cached_tokens: opts.usage.cachedTokens } } : {}
|
|
50442
|
+
} : null;
|
|
50443
|
+
return Buffer.concat([buildFinish(finishReason, usage), Buffer.from("data: [DONE]\n\n", "utf8")]);
|
|
50444
|
+
},
|
|
50445
|
+
emitError(message) {
|
|
50446
|
+
return Buffer.concat([
|
|
50447
|
+
buildContent(`
|
|
50448
|
+
[acp-proxy: ${message}]
|
|
50449
|
+
`),
|
|
50450
|
+
buildFinish("stop", null),
|
|
50451
|
+
Buffer.from("data: [DONE]\n\n", "utf8")
|
|
50452
|
+
]);
|
|
50453
|
+
}
|
|
50454
|
+
};
|
|
50455
|
+
}
|
|
50456
|
+
|
|
50457
|
+
// src/loop/adapter-anthropic.ts
|
|
50458
|
+
async function* iterSseEvents2(stream2) {
|
|
50459
|
+
const reader = stream2.getReader();
|
|
50460
|
+
let buf = "";
|
|
50461
|
+
try {
|
|
50462
|
+
while (true) {
|
|
50463
|
+
let done;
|
|
50464
|
+
let value;
|
|
50465
|
+
try {
|
|
50466
|
+
({ done, value } = await reader.read());
|
|
50467
|
+
} catch {
|
|
50468
|
+
break;
|
|
50469
|
+
}
|
|
50470
|
+
if (done) break;
|
|
50471
|
+
buf += new TextDecoder().decode(value, { stream: true });
|
|
50472
|
+
buf = buf.replace(/\r\n|\r/g, "\n");
|
|
50473
|
+
let idx;
|
|
50474
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
50475
|
+
const raw = buf.slice(0, idx);
|
|
50476
|
+
buf = buf.slice(idx + 2);
|
|
50477
|
+
if (raw.trim().length > 0) yield raw;
|
|
50478
|
+
}
|
|
50479
|
+
}
|
|
50480
|
+
if (buf.trim().length > 0) yield buf;
|
|
50481
|
+
} finally {
|
|
50482
|
+
reader.releaseLock();
|
|
50483
|
+
}
|
|
50484
|
+
}
|
|
50485
|
+
function parseAnthropicSse2(eventStr) {
|
|
50486
|
+
const lines = eventStr.split("\n");
|
|
50487
|
+
let type = "";
|
|
50488
|
+
const dataLines = [];
|
|
50489
|
+
for (const l of lines) {
|
|
50490
|
+
if (l.startsWith("event:")) type = l.slice(6).trim();
|
|
50491
|
+
else if (l.startsWith("data:")) dataLines.push(l.slice(5).replace(/^ /, ""));
|
|
50492
|
+
}
|
|
50493
|
+
if (!type) return null;
|
|
50494
|
+
const jsonStr = dataLines.join("\n").trim();
|
|
50495
|
+
if (!jsonStr) return { type, data: {} };
|
|
50496
|
+
try {
|
|
50497
|
+
return { type, data: JSON.parse(jsonStr) };
|
|
50498
|
+
} catch {
|
|
50499
|
+
return { type, data: {} };
|
|
50500
|
+
}
|
|
50501
|
+
}
|
|
50502
|
+
function remapIndexInEvent(eventStr, newIndex) {
|
|
50503
|
+
const lines = eventStr.split("\n");
|
|
50504
|
+
const rebuilt = [];
|
|
50505
|
+
let touched = false;
|
|
50506
|
+
for (const l of lines) {
|
|
50507
|
+
if (!touched && l.startsWith("data:")) {
|
|
50508
|
+
const jsonStr = l.slice(5).replace(/^ /, "");
|
|
50509
|
+
try {
|
|
50510
|
+
const obj = JSON.parse(jsonStr);
|
|
50511
|
+
if (typeof obj === "object" && obj !== null && typeof obj.index === "number") {
|
|
50512
|
+
obj.index = newIndex;
|
|
50513
|
+
rebuilt.push(`data: ${JSON.stringify(obj)}`);
|
|
50514
|
+
touched = true;
|
|
50515
|
+
continue;
|
|
50516
|
+
}
|
|
50517
|
+
} catch {
|
|
50518
|
+
}
|
|
50519
|
+
}
|
|
50520
|
+
rebuilt.push(l);
|
|
50521
|
+
}
|
|
50522
|
+
return Buffer.from(rebuilt.join("\n") + "\n\n", "utf8");
|
|
50523
|
+
}
|
|
50524
|
+
function createAnthropicAdapter(requestBody, originalSystem) {
|
|
50525
|
+
const model = requestBody.model ?? void 0;
|
|
50526
|
+
let messageId;
|
|
50527
|
+
let clientIndex = 0;
|
|
50528
|
+
const buildTextBlock = (index, text) => Buffer.from(
|
|
50529
|
+
`event: content_block_start
|
|
50530
|
+
data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "text", text: "" } })}
|
|
50531
|
+
|
|
50532
|
+
event: content_block_delta
|
|
50533
|
+
data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "text_delta", text } })}
|
|
50534
|
+
|
|
50535
|
+
event: content_block_stop
|
|
50536
|
+
data: ${JSON.stringify({ type: "content_block_stop", index })}
|
|
50537
|
+
|
|
50538
|
+
`,
|
|
50539
|
+
"utf8"
|
|
50540
|
+
);
|
|
50541
|
+
const buildToolUseBlock = (index, call) => Buffer.from(
|
|
50542
|
+
`event: content_block_start
|
|
50543
|
+
data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "tool_use", id: call.callId, name: call.name, input: {} } })}
|
|
50544
|
+
|
|
50545
|
+
event: content_block_delta
|
|
50546
|
+
data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "input_json_delta", partial_json: call.arguments } })}
|
|
50547
|
+
|
|
50548
|
+
event: content_block_stop
|
|
50549
|
+
data: ${JSON.stringify({ type: "content_block_stop", index })}
|
|
50550
|
+
|
|
50551
|
+
`,
|
|
50552
|
+
"utf8"
|
|
50553
|
+
);
|
|
50554
|
+
const buildTerminal = (stopReason, outputTokens, inputTokens, cachedTokens) => {
|
|
50555
|
+
const usage = {
|
|
50556
|
+
input_tokens: inputTokens,
|
|
50557
|
+
output_tokens: outputTokens,
|
|
50558
|
+
cache_read_input_tokens: cachedTokens
|
|
50559
|
+
};
|
|
50560
|
+
const extra = {};
|
|
50561
|
+
if (messageId) extra.id = messageId;
|
|
50562
|
+
if (model) extra.model = model;
|
|
50563
|
+
return Buffer.from(
|
|
50564
|
+
`event: message_delta
|
|
50565
|
+
data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason, stop_sequence: null }, usage, ...extra })}
|
|
50566
|
+
|
|
50567
|
+
event: message_stop
|
|
50568
|
+
data: ${JSON.stringify({ type: "message_stop" })}
|
|
50569
|
+
|
|
50570
|
+
`,
|
|
50571
|
+
"utf8"
|
|
50572
|
+
);
|
|
50573
|
+
};
|
|
50574
|
+
return {
|
|
50575
|
+
buildRequest(coreMessages, systemPrompt, body) {
|
|
50576
|
+
const messages = coreToAnthropic(coreMessages);
|
|
50577
|
+
const baseText = originalSystem !== void 0 ? extractSystem(originalSystem) : "";
|
|
50578
|
+
const full = baseText ? `${baseText}
|
|
50579
|
+
|
|
50580
|
+
---
|
|
50581
|
+
|
|
50582
|
+
${systemPrompt}` : systemPrompt;
|
|
50583
|
+
const system = originalSystem !== void 0 ? buildSystem(full, originalSystem) : full;
|
|
50584
|
+
return { ...body, system, messages };
|
|
50585
|
+
},
|
|
50586
|
+
async *parseStream(upstream, round) {
|
|
50587
|
+
const pending = /* @__PURE__ */ new Map();
|
|
50588
|
+
let roundInput;
|
|
50589
|
+
let roundCached;
|
|
50590
|
+
let roundOutput;
|
|
50591
|
+
let stopReason;
|
|
50592
|
+
let usageYielded = false;
|
|
50593
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
50594
|
+
for await (const eventStr of iterSseEvents2(upstream)) {
|
|
50595
|
+
const parsed = parseAnthropicSse2(eventStr);
|
|
50596
|
+
if (!parsed) continue;
|
|
50597
|
+
const { type, data } = parsed;
|
|
50598
|
+
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
50599
|
+
if (type === "message_start") {
|
|
50600
|
+
const msg2 = data.message ?? {};
|
|
50601
|
+
if (typeof msg2.id === "string" && !messageId) messageId = msg2.id;
|
|
50602
|
+
const u2 = msg2.usage ?? {};
|
|
50603
|
+
if (typeof u2.input_tokens === "number") roundInput = u2.input_tokens;
|
|
50604
|
+
if (typeof u2.cache_read_input_tokens === "number") roundCached = u2.cache_read_input_tokens;
|
|
50605
|
+
if (round === 1) {
|
|
50606
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50607
|
+
}
|
|
50608
|
+
} else if (type === "ping") {
|
|
50609
|
+
yield { kind: "meta", chunk: rawBuf };
|
|
50610
|
+
} else if (type === "content_block_start") {
|
|
50611
|
+
const upstreamIndex = data.index ?? 0;
|
|
50612
|
+
const block = data.content_block ?? {};
|
|
50613
|
+
if (block.type === "tool_use") {
|
|
50614
|
+
const name = typeof block.name === "string" ? block.name : "";
|
|
50615
|
+
const id = typeof block.id === "string" ? block.id : `toolu_${upstreamIndex}`;
|
|
50616
|
+
pending.set(upstreamIndex, { id, name, json: "" });
|
|
50617
|
+
} else if (round === 1) {
|
|
50618
|
+
const ci2 = clientIndex++;
|
|
50619
|
+
indexMap.set(upstreamIndex, ci2);
|
|
50620
|
+
yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
|
|
50621
|
+
}
|
|
50622
|
+
} else if (type === "content_block_delta") {
|
|
50623
|
+
const upstreamIndex = data.index ?? 0;
|
|
50624
|
+
const delta = data.delta ?? {};
|
|
50625
|
+
if (pending.has(upstreamIndex)) {
|
|
50626
|
+
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
50627
|
+
pending.get(upstreamIndex).json += delta.partial_json;
|
|
50628
|
+
}
|
|
50629
|
+
} else if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
50630
|
+
if (round === 1) {
|
|
50631
|
+
const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
50632
|
+
yield { kind: "text", delta: delta.text, raw: remapIndexInEvent(eventStr, ci2) };
|
|
50633
|
+
} else {
|
|
50634
|
+
yield { kind: "text", delta: delta.text };
|
|
50635
|
+
}
|
|
50636
|
+
} else if (round === 1) {
|
|
50637
|
+
const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
50638
|
+
yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
|
|
50639
|
+
}
|
|
50640
|
+
} else if (type === "content_block_stop") {
|
|
50641
|
+
const upstreamIndex = data.index ?? 0;
|
|
50642
|
+
const tb = pending.get(upstreamIndex);
|
|
50643
|
+
if (tb) {
|
|
50644
|
+
pending.delete(upstreamIndex);
|
|
50645
|
+
yield {
|
|
50646
|
+
kind: "tool_call",
|
|
50647
|
+
name: tb.name,
|
|
50648
|
+
callId: tb.id,
|
|
50649
|
+
arguments: tb.json
|
|
50650
|
+
};
|
|
50651
|
+
} else if (round === 1) {
|
|
50652
|
+
const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
50653
|
+
yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
|
|
50654
|
+
}
|
|
50655
|
+
} else if (type === "message_delta") {
|
|
50656
|
+
const u2 = data.usage ?? {};
|
|
50657
|
+
if (typeof u2.output_tokens === "number") roundOutput = u2.output_tokens;
|
|
50658
|
+
if (typeof u2.input_tokens === "number") roundInput = u2.input_tokens;
|
|
50659
|
+
if (typeof u2.cache_read_input_tokens === "number") roundCached = u2.cache_read_input_tokens;
|
|
50660
|
+
const d = data.delta ?? {};
|
|
50661
|
+
if (typeof d.stop_reason === "string") stopReason = d.stop_reason;
|
|
50662
|
+
if (!usageYielded) {
|
|
50663
|
+
usageYielded = true;
|
|
50664
|
+
yield {
|
|
50665
|
+
kind: "usage",
|
|
50666
|
+
inputTokens: roundInput,
|
|
50667
|
+
outputTokens: roundOutput,
|
|
50668
|
+
cachedTokens: roundCached
|
|
50669
|
+
};
|
|
50670
|
+
}
|
|
50671
|
+
yield { kind: "done", finishReason: stopReason };
|
|
50672
|
+
} else if (type === "message_stop") {
|
|
50673
|
+
if (!usageYielded) {
|
|
50674
|
+
usageYielded = true;
|
|
50675
|
+
yield {
|
|
50676
|
+
kind: "usage",
|
|
50677
|
+
inputTokens: roundInput,
|
|
50678
|
+
outputTokens: roundOutput,
|
|
50679
|
+
cachedTokens: roundCached
|
|
50680
|
+
};
|
|
50681
|
+
}
|
|
50682
|
+
yield { kind: "done", finishReason: stopReason ?? "end_turn" };
|
|
50683
|
+
} else if (round === 1) {
|
|
50684
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50685
|
+
}
|
|
50686
|
+
}
|
|
50687
|
+
},
|
|
50688
|
+
emitText(delta) {
|
|
50689
|
+
return buildTextBlock(clientIndex++, delta);
|
|
50690
|
+
},
|
|
50691
|
+
emitToolCall(call) {
|
|
50692
|
+
return buildToolUseBlock(clientIndex++, call);
|
|
50693
|
+
},
|
|
50694
|
+
emitMarker(toolName, result) {
|
|
50695
|
+
return buildTextBlock(clientIndex++, buildVisibilityMarker(toolName, result));
|
|
50696
|
+
},
|
|
50697
|
+
emitCompletion(opts) {
|
|
50698
|
+
const stopReason = opts?.finishReason ?? "end_turn";
|
|
50699
|
+
return buildTerminal(
|
|
50700
|
+
stopReason,
|
|
50701
|
+
opts?.usage?.outputTokens ?? 0,
|
|
50702
|
+
opts?.usage?.inputTokens ?? 0,
|
|
50703
|
+
opts?.usage?.cachedTokens ?? 0
|
|
50704
|
+
);
|
|
50705
|
+
},
|
|
50706
|
+
emitError(message) {
|
|
50707
|
+
const errBlock = buildTextBlock(clientIndex++, `
|
|
50708
|
+
[acp-proxy: ${message}]
|
|
50709
|
+
`);
|
|
50710
|
+
return Buffer.concat([errBlock, buildTerminal("end_turn", 0, 0, 0)]);
|
|
50711
|
+
}
|
|
50712
|
+
};
|
|
50713
|
+
}
|
|
50714
|
+
|
|
50715
|
+
// src/loop/index.ts
|
|
50716
|
+
function pickAdapter(protocol, requestBody, textProtocol, responsesProjection, anthropicSystem) {
|
|
50717
|
+
if (protocol === "responses") return createResponsesAdapter(textProtocol, responsesProjection);
|
|
50718
|
+
if (protocol === "openai") return createOpenaiAdapter(requestBody);
|
|
50719
|
+
if (protocol === "anthropic") return createAnthropicAdapter(requestBody, anthropicSystem);
|
|
50720
|
+
throw new Error(`[acp-loop] unknown protocol: ${protocol}`);
|
|
50721
|
+
}
|
|
50722
|
+
|
|
49535
50723
|
// src/stream-openai.ts
|
|
49536
50724
|
function rewriteOpenaiJsonResponse(body, ctx) {
|
|
49537
50725
|
if (!body || typeof body !== "object") return body;
|
|
@@ -49668,7 +50856,7 @@ function extractKey(headers) {
|
|
|
49668
50856
|
return "(no-key)";
|
|
49669
50857
|
}
|
|
49670
50858
|
function clientConversationHeader(headers) {
|
|
49671
|
-
const names = ["x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
|
|
50859
|
+
const names = ["x-claude-code-session-id", "x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
|
|
49672
50860
|
for (const name of names) {
|
|
49673
50861
|
const v2 = headers[name];
|
|
49674
50862
|
if (typeof v2 === "string" && v2.trim().length > 0) return v2.trim();
|
|
@@ -50985,7 +52173,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
50985
52173
|
}
|
|
50986
52174
|
const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
|
|
50987
52175
|
markDirty(session);
|
|
50988
|
-
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
|
|
52176
|
+
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
|
|
50989
52177
|
}
|
|
50990
52178
|
function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
50991
52179
|
const sessionId = session.id;
|
|
@@ -51049,12 +52237,14 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
51049
52237
|
}
|
|
51050
52238
|
let processedMessages = [];
|
|
51051
52239
|
let originalMessages = [];
|
|
52240
|
+
let responsesProjection;
|
|
51052
52241
|
let rebuiltInput = parsed.input;
|
|
51053
52242
|
let toolsOut = parsed.tools;
|
|
51054
52243
|
const shouldInject = opts.compress.injectTool;
|
|
51055
52244
|
const responsesTextProtocol = FORCE_TEXT_PROTOCOL || isChatGptCodexUpstream(session.meta.upstreamOrigin) || isCodexResponsesLite(req.headers, parsed);
|
|
51056
52245
|
try {
|
|
51057
52246
|
const projection = responsesToCore(parsed);
|
|
52247
|
+
responsesProjection = projection;
|
|
51058
52248
|
const { msgs } = projection;
|
|
51059
52249
|
originalMessages = msgs;
|
|
51060
52250
|
if (process.env.ACP_DEBUG) {
|
|
@@ -51120,6 +52310,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
51120
52310
|
session,
|
|
51121
52311
|
processedMessages,
|
|
51122
52312
|
originalMessages,
|
|
52313
|
+
responsesProjection,
|
|
51123
52314
|
protocol: "responses",
|
|
51124
52315
|
stream: stream2,
|
|
51125
52316
|
compressInjected: shouldInject,
|
|
@@ -51258,9 +52449,20 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51258
52449
|
return fn?.name ?? t.name ?? "?";
|
|
51259
52450
|
});
|
|
51260
52451
|
log2("info", `[debug] tools=[${toolNames.join(",")}] msgs=${parsed.messages?.length ?? 0} stream=${parsed.stream ?? false} system_len=${JSON.stringify(parsed.messages?.find((m2) => m2.role === "system")?.content ?? "").length}`);
|
|
51261
|
-
if (process.env.ACP_DUMP_REQ
|
|
51262
|
-
const
|
|
51263
|
-
|
|
52452
|
+
if (process.env.ACP_DUMP_REQ !== "0") {
|
|
52453
|
+
const dumpDir = process.env.ACP_DUMP_DIR || `${stateDir()}/dumps`;
|
|
52454
|
+
try {
|
|
52455
|
+
fs3.mkdirSync(dumpDir, { recursive: true });
|
|
52456
|
+
} catch {
|
|
52457
|
+
}
|
|
52458
|
+
const sid = prepared?.session.id ?? "unknown";
|
|
52459
|
+
const out = `${dumpDir}/req-${Date.now()}-${sid}.json`;
|
|
52460
|
+
try {
|
|
52461
|
+
const pretty = JSON.stringify(JSON.parse(body), null, 2);
|
|
52462
|
+
fs3.writeFileSync(out, pretty);
|
|
52463
|
+
} catch {
|
|
52464
|
+
fs3.writeFileSync(out, body);
|
|
52465
|
+
}
|
|
51264
52466
|
log2("info", `[debug] forwarded body written to ${out}`);
|
|
51265
52467
|
}
|
|
51266
52468
|
} catch {
|
|
@@ -51276,6 +52478,13 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51276
52478
|
headers["x-session-id"] = affinity;
|
|
51277
52479
|
}
|
|
51278
52480
|
const proxyUrl = resolveProxy(opts.routes, opts.proxy, route?.rewrittenUrl ?? upstreamUrl, opts.proxyFallback);
|
|
52481
|
+
if (opts.debug) {
|
|
52482
|
+
const hdrLog = {};
|
|
52483
|
+
for (const [hk, hv] of Object.entries(headers)) {
|
|
52484
|
+
if (typeof hv === "string") hdrLog[hk] = hv.length > 200 ? hv.slice(0, 200) + "..." : hv;
|
|
52485
|
+
}
|
|
52486
|
+
log2("info", `[${prepared?.session.id ?? "unknown"}] \u2192 upstream headers: ${JSON.stringify(hdrLog)}`);
|
|
52487
|
+
}
|
|
51279
52488
|
const dispatcher = proxyDispatcher(proxyUrl);
|
|
51280
52489
|
const init = {
|
|
51281
52490
|
method: req.method ?? "GET",
|
|
@@ -51297,6 +52506,14 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51297
52506
|
if (UPSTREAM_HOP_HEADERS.has(k2.toLowerCase())) return;
|
|
51298
52507
|
respHeaders[k2] = v2;
|
|
51299
52508
|
});
|
|
52509
|
+
if (opts.debug) {
|
|
52510
|
+
const respLog = {};
|
|
52511
|
+
upstream.headers.forEach((v2, k2) => {
|
|
52512
|
+
if (UPSTREAM_HOP_HEADERS.has(k2.toLowerCase())) return;
|
|
52513
|
+
respLog[k2] = v2.length > 300 ? v2.slice(0, 300) + "..." : v2;
|
|
52514
|
+
});
|
|
52515
|
+
log2("info", `[${prepared?.session.id ?? "unknown"}] \u2190 upstream response headers: ${JSON.stringify(respLog)}`);
|
|
52516
|
+
}
|
|
51300
52517
|
if (!upstream.ok) {
|
|
51301
52518
|
res.writeHead(upstream.status, respHeaders);
|
|
51302
52519
|
if (upstream.body) await pipeThrough(upstream.body, res);
|
|
@@ -51340,7 +52557,37 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51340
52557
|
dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
|
|
51341
52558
|
}
|
|
51342
52559
|
try {
|
|
51343
|
-
if (
|
|
52560
|
+
if (process.env.ACP_LOOP_V2 !== "0") {
|
|
52561
|
+
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52562
|
+
const reqHeaders = {};
|
|
52563
|
+
for (const [k2, v2] of Object.entries(headers)) {
|
|
52564
|
+
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
52565
|
+
reqHeaders[k2] = v2;
|
|
52566
|
+
}
|
|
52567
|
+
reqHeaders["content-type"] = "application/json";
|
|
52568
|
+
const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
|
|
52569
|
+
const systemPrompt = textProtocol ? buildCompressTextSystemPrompt() : buildCompressSystemPrompt();
|
|
52570
|
+
const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem);
|
|
52571
|
+
const loop = runCompressLoop(
|
|
52572
|
+
streamToRead,
|
|
52573
|
+
{ core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol, debug: opts.debug },
|
|
52574
|
+
parsedReq,
|
|
52575
|
+
{ url: upstreamUrl, headers: reqHeaders },
|
|
52576
|
+
adapter,
|
|
52577
|
+
systemPrompt
|
|
52578
|
+
);
|
|
52579
|
+
for await (const chunk of loop) {
|
|
52580
|
+
{
|
|
52581
|
+
const s3 = chunk.toString("utf8");
|
|
52582
|
+
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52583
|
+
log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
|
|
52584
|
+
}
|
|
52585
|
+
}
|
|
52586
|
+
res.write(chunk);
|
|
52587
|
+
if (res.writableNeedDrain) await new Promise((r) => res.once("drain", () => r()));
|
|
52588
|
+
}
|
|
52589
|
+
res.end();
|
|
52590
|
+
} else if (prepared.protocol === "openai") {
|
|
51344
52591
|
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
51345
52592
|
const reqHeaders = {};
|
|
51346
52593
|
for (const [k2, v2] of Object.entries(headers)) {
|