billion-context 0.1.33 → 0.1.34
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 +403 -1480
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -43736,7 +43736,7 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
43736
43736
|
iterationThreshold: 15,
|
|
43737
43737
|
force: "soft",
|
|
43738
43738
|
growthRatio: 0.05,
|
|
43739
|
-
growthFloor:
|
|
43739
|
+
growthFloor: 5e4,
|
|
43740
43740
|
growthCap: 5e4,
|
|
43741
43741
|
minGrowthFloor: 2e4,
|
|
43742
43742
|
minGrowthRatio: 0.45,
|
|
@@ -46091,7 +46091,7 @@ function resolveProxyDecision(routes, globalProxy, upstreamUrl, fallback = {}) {
|
|
|
46091
46091
|
return { proxy: parsed.url, source: "provider" };
|
|
46092
46092
|
}
|
|
46093
46093
|
}
|
|
46094
|
-
if (globalProxy === "") return { source: "direct" };
|
|
46094
|
+
if (globalProxy === "" && fallback.explicitDirect) return { source: "direct" };
|
|
46095
46095
|
const explicit = parseHttpProxy(globalProxy, fallback.biliPort)?.url;
|
|
46096
46096
|
if (explicit) return { proxy: explicit, source: fallback.globalSource ?? "global" };
|
|
46097
46097
|
if (target && matchesNoProxy(target, fallback.noProxy)) return { source: "no-proxy" };
|
|
@@ -46329,34 +46329,19 @@ function loadRoutes(env = process.env) {
|
|
|
46329
46329
|
function loadOptions(env = process.env) {
|
|
46330
46330
|
const fileConfig = loadConfigFile();
|
|
46331
46331
|
const port = parseInt(env.ACP_PORT ?? env.PORT ?? `${fileConfig.port ?? 8787}`, 10);
|
|
46332
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
46333
|
+
throw new Error(`Invalid port ${Number.isNaN(port) ? "(not a number)" : port}; must be 1-65535`);
|
|
46334
|
+
}
|
|
46332
46335
|
const host = env.ACP_HOST ?? fileConfig.host ?? "127.0.0.1";
|
|
46333
46336
|
const upstream = (env.ACP_UPSTREAM ?? fileConfig.upstream ?? "https://api.anthropic.com").replace(/\/$/, "");
|
|
46334
|
-
|
|
46335
|
-
const routesPath = env.ACP_PROVIDERS ?? fileConfig.providersPath ?? "";
|
|
46336
|
-
if (routesPath) {
|
|
46337
|
-
const parsed = safeReadJson(routesPath);
|
|
46338
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
46339
|
-
for (const [k2, v2] of Object.entries(parsed)) {
|
|
46340
|
-
rejectLegacyRoute(k2, v2);
|
|
46341
|
-
const route = parseRouteEntry(v2);
|
|
46342
|
-
if (route) routes[normalizeUrlKey(k2)] = route;
|
|
46343
|
-
}
|
|
46344
|
-
}
|
|
46345
|
-
}
|
|
46346
|
-
if (fileConfig.providers) {
|
|
46347
|
-
for (const [k2, v2] of Object.entries(fileConfig.providers)) {
|
|
46348
|
-
rejectLegacyRoute(k2, v2);
|
|
46349
|
-
const route = parseRouteEntry(v2);
|
|
46350
|
-
if (route && !routes[normalizeUrlKey(k2)]) routes[normalizeUrlKey(k2)] = route;
|
|
46351
|
-
}
|
|
46352
|
-
}
|
|
46337
|
+
const routes = loadRoutes(env);
|
|
46353
46338
|
const modelContextLimit = parseInt(env.ACP_MODEL_CONTEXT_LIMIT ?? `${fileConfig.modelContextLimit ?? 2e5}`, 10);
|
|
46354
46339
|
const biliProxy = nonEmpty(env.BILI_UPSTREAM_PROXY);
|
|
46355
46340
|
const webProxy = nonEmpty(fileConfig.upstreamProxy);
|
|
46356
46341
|
const configProxy = nonEmpty(fileConfig.proxy);
|
|
46357
|
-
const
|
|
46358
|
-
|
|
46359
|
-
|
|
46342
|
+
const rawProxyMode = env.BILI_UPSTREAM_PROXY_MODE ?? fileConfig.upstreamProxyMode ?? (webProxy ? "manual" : void 0);
|
|
46343
|
+
const proxyMode = parseUpstreamProxyMode(rawProxyMode);
|
|
46344
|
+
const explicitDirect = proxyMode === "direct" && rawProxyMode === "direct";
|
|
46360
46345
|
const proxy = biliProxy ?? (proxyMode === "direct" ? "" : proxyMode === "manual" ? webProxy ?? configProxy : configProxy);
|
|
46361
46346
|
const proxySource = biliProxy ? "bili-env" : proxyMode === "direct" ? "direct" : proxyMode === "manual" && webProxy ? "web-manual" : configProxy ? "config" : "auto";
|
|
46362
46347
|
const httpProxy = nonEmpty(env.HTTP_PROXY ?? env.http_proxy);
|
|
@@ -46368,8 +46353,9 @@ function loadOptions(env = process.env) {
|
|
|
46368
46353
|
...httpsProxy ? { httpsProxy } : {},
|
|
46369
46354
|
...allProxy ? { allProxy } : {},
|
|
46370
46355
|
...noProxy ? { noProxy } : {},
|
|
46371
|
-
biliPort:
|
|
46372
|
-
globalSource: proxySource
|
|
46356
|
+
biliPort: port,
|
|
46357
|
+
globalSource: proxySource,
|
|
46358
|
+
explicitDirect
|
|
46373
46359
|
};
|
|
46374
46360
|
validateHttpProxy(proxy, proxyFallback.biliPort);
|
|
46375
46361
|
for (const [url, route] of Object.entries(routes)) {
|
|
@@ -46380,7 +46366,7 @@ function loadOptions(env = process.env) {
|
|
|
46380
46366
|
}
|
|
46381
46367
|
}
|
|
46382
46368
|
return {
|
|
46383
|
-
port
|
|
46369
|
+
port,
|
|
46384
46370
|
host,
|
|
46385
46371
|
upstream,
|
|
46386
46372
|
routes,
|
|
@@ -46590,15 +46576,30 @@ async function contextFromRegistry(model, host) {
|
|
|
46590
46576
|
// src/fetch-util.ts
|
|
46591
46577
|
var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
|
|
46592
46578
|
var UPSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
46593
|
-
async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS) {
|
|
46579
|
+
async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, externalSignal) {
|
|
46594
46580
|
const controller = new AbortController();
|
|
46595
46581
|
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
46582
|
+
let onExternalAbort = null;
|
|
46583
|
+
if (externalSignal) {
|
|
46584
|
+
if (externalSignal.aborted) controller.abort();
|
|
46585
|
+
else {
|
|
46586
|
+
onExternalAbort = () => controller.abort();
|
|
46587
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
46588
|
+
}
|
|
46589
|
+
}
|
|
46596
46590
|
try {
|
|
46597
46591
|
const finalOpts = { ...opts, signal: controller.signal };
|
|
46598
46592
|
const response = await fetch(url, finalOpts);
|
|
46599
|
-
return {
|
|
46593
|
+
return {
|
|
46594
|
+
response,
|
|
46595
|
+
clearTimer: () => {
|
|
46596
|
+
clearTimeout(timer2);
|
|
46597
|
+
if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
46598
|
+
}
|
|
46599
|
+
};
|
|
46600
46600
|
} catch (e) {
|
|
46601
46601
|
clearTimeout(timer2);
|
|
46602
|
+
if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
46602
46603
|
throw e;
|
|
46603
46604
|
}
|
|
46604
46605
|
}
|
|
@@ -47608,7 +47609,7 @@ function getStore() {
|
|
|
47608
47609
|
|
|
47609
47610
|
// src/session.ts
|
|
47610
47611
|
var sessions = /* @__PURE__ */ new Map();
|
|
47611
|
-
var MAX_SESSIONS = Number.parseInt(process.env.BILI_MAX_SESSIONS ?? "256", 10) || 256;
|
|
47612
|
+
var MAX_SESSIONS = Math.max(1, Number.parseInt(process.env.BILI_MAX_SESSIONS ?? "256", 10) || 256);
|
|
47612
47613
|
var initialized = false;
|
|
47613
47614
|
async function initSessions() {
|
|
47614
47615
|
if (initialized) return;
|
|
@@ -47643,7 +47644,12 @@ function getSession(id, meta) {
|
|
|
47643
47644
|
sessions.set(id, reloaded);
|
|
47644
47645
|
return reloaded;
|
|
47645
47646
|
}
|
|
47646
|
-
if (sessions.size >= MAX_SESSIONS)
|
|
47647
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
47648
|
+
const evicted = evictOldest();
|
|
47649
|
+
if (!evicted) {
|
|
47650
|
+
throw new Error(`session pool exhausted (MAX_SESSIONS=${MAX_SESSIONS}; all in-flight)`);
|
|
47651
|
+
}
|
|
47652
|
+
}
|
|
47647
47653
|
const session = {
|
|
47648
47654
|
id,
|
|
47649
47655
|
meta: { protocol: meta?.protocol, upstreamOrigin: meta?.upstreamOrigin, label: meta?.label },
|
|
@@ -47727,13 +47733,14 @@ function evictOldest() {
|
|
|
47727
47733
|
oldestId = id;
|
|
47728
47734
|
}
|
|
47729
47735
|
}
|
|
47730
|
-
if (!oldestId) return;
|
|
47736
|
+
if (!oldestId) return false;
|
|
47731
47737
|
const s3 = sessions.get(oldestId);
|
|
47732
47738
|
const ok = getStore().flushSync(s3);
|
|
47733
47739
|
if (!ok && !s3.persisted) {
|
|
47734
|
-
return;
|
|
47740
|
+
return false;
|
|
47735
47741
|
}
|
|
47736
47742
|
sessions.delete(oldestId);
|
|
47743
|
+
return true;
|
|
47737
47744
|
}
|
|
47738
47745
|
async function flushAllSessions() {
|
|
47739
47746
|
await getStore().flushAll(sessions.values());
|
|
@@ -48014,15 +48021,38 @@ var MUTATING_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
48014
48021
|
COMPRESS_TOOL_NAME,
|
|
48015
48022
|
DECOMPRESS_TOOL_NAME
|
|
48016
48023
|
]);
|
|
48017
|
-
var READONLY_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
48018
|
-
SEARCH_CONTEXT_TOOL_NAME,
|
|
48019
|
-
ACP_STATUS_TOOL_NAME
|
|
48020
|
-
]);
|
|
48021
48024
|
|
|
48022
48025
|
// src/decompress-shared.ts
|
|
48023
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
48026
|
+
import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
48024
48027
|
import { dirname as dirname3, join as join2 } from "path";
|
|
48025
48028
|
import { tmpdir } from "os";
|
|
48029
|
+
var trackedTempFiles = [];
|
|
48030
|
+
function getDecompressTmpCap() {
|
|
48031
|
+
const raw = process.env.BILI_DECOMPRESS_TMP_CAP;
|
|
48032
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
48033
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 50;
|
|
48034
|
+
}
|
|
48035
|
+
function reapTempFiles() {
|
|
48036
|
+
const cap = getDecompressTmpCap();
|
|
48037
|
+
while (trackedTempFiles.length > cap) {
|
|
48038
|
+
trackedTempFiles.sort((a, b2) => a.mtimeMs - b2.mtimeMs);
|
|
48039
|
+
const oldest = trackedTempFiles.shift();
|
|
48040
|
+
if (!oldest) break;
|
|
48041
|
+
try {
|
|
48042
|
+
unlinkSync2(oldest.path);
|
|
48043
|
+
} catch {
|
|
48044
|
+
}
|
|
48045
|
+
}
|
|
48046
|
+
}
|
|
48047
|
+
process.on("beforeExit", () => {
|
|
48048
|
+
for (const f2 of trackedTempFiles) {
|
|
48049
|
+
try {
|
|
48050
|
+
unlinkSync2(f2.path);
|
|
48051
|
+
} catch {
|
|
48052
|
+
}
|
|
48053
|
+
}
|
|
48054
|
+
trackedTempFiles.length = 0;
|
|
48055
|
+
});
|
|
48026
48056
|
function resolveDecompress(args, ctx) {
|
|
48027
48057
|
const rawBlockId = args.blockId;
|
|
48028
48058
|
if (typeof rawBlockId !== "string" || rawBlockId.length === 0) {
|
|
@@ -48055,6 +48085,8 @@ function resolveDecompress(args, ctx) {
|
|
|
48055
48085
|
try {
|
|
48056
48086
|
mkdirSync4(dirname3(outPath), { recursive: true });
|
|
48057
48087
|
writeFileSync3(outPath, body, "utf8");
|
|
48088
|
+
trackedTempFiles.push({ path: outPath, mtimeMs: Date.now() });
|
|
48089
|
+
reapTempFiles();
|
|
48058
48090
|
return `${header}
|
|
48059
48091
|
Content (${body.length} chars) written to: ${outPath}
|
|
48060
48092
|
Use the read tool to access it.`;
|
|
@@ -48068,12 +48100,6 @@ ${body.slice(0, 4e3)}...`;
|
|
|
48068
48100
|
${body}`;
|
|
48069
48101
|
}
|
|
48070
48102
|
|
|
48071
|
-
// src/sse-util.ts
|
|
48072
|
-
function normalizeSseLineEndings(buf) {
|
|
48073
|
-
if (buf.indexOf("\r") === -1) return buf;
|
|
48074
|
-
return buf.replace(/\r\n|\r/g, "\n");
|
|
48075
|
-
}
|
|
48076
|
-
|
|
48077
48103
|
// src/stream.ts
|
|
48078
48104
|
function executeAnthropicProxyTool(toolName, args, ctx) {
|
|
48079
48105
|
if (toolName === COMPRESS_TOOL_NAME) {
|
|
@@ -48237,7 +48263,7 @@ function renderPage(origin, version2) {
|
|
|
48237
48263
|
}
|
|
48238
48264
|
|
|
48239
48265
|
// src/web/api.ts
|
|
48240
|
-
import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as
|
|
48266
|
+
import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
48241
48267
|
import { dirname as dirname4 } from "path";
|
|
48242
48268
|
import { randomUUID } from "crypto";
|
|
48243
48269
|
function readConfig() {
|
|
@@ -48270,7 +48296,7 @@ function atomicWriteConfig(config) {
|
|
|
48270
48296
|
} catch (error) {
|
|
48271
48297
|
if (descriptor !== void 0) closeSync(descriptor);
|
|
48272
48298
|
try {
|
|
48273
|
-
|
|
48299
|
+
unlinkSync3(tempPath);
|
|
48274
48300
|
} catch {
|
|
48275
48301
|
}
|
|
48276
48302
|
throw error;
|
|
@@ -48403,1308 +48429,48 @@ function reapOrphanBlocks(session, visible, deactivate) {
|
|
|
48403
48429
|
const hasHit = block.effectiveMessageIds.some((id) => presentIds.has(id));
|
|
48404
48430
|
if (hasHit) {
|
|
48405
48431
|
orphanStreaks.delete(block);
|
|
48406
|
-
continue;
|
|
48407
|
-
}
|
|
48408
|
-
const streak = (orphanStreaks.get(block) ?? 0) + 1;
|
|
48409
|
-
orphanStreaks.set(block, streak);
|
|
48410
|
-
if (streak >= ORPHAN_THRESHOLD) {
|
|
48411
|
-
reaped.push(block.blockId);
|
|
48412
|
-
}
|
|
48413
|
-
}
|
|
48414
|
-
if (reaped.length === 0) return { reaped: [] };
|
|
48415
|
-
session.state = deactivate(session.state, reaped);
|
|
48416
|
-
for (const id of reaped) session.blockContents.delete(id);
|
|
48417
|
-
return { reaped };
|
|
48418
|
-
}
|
|
48419
|
-
|
|
48420
|
-
// src/compress-loop.ts
|
|
48421
|
-
function executeProxyTool(toolName, args, ctx) {
|
|
48422
|
-
if (toolName === "compress") {
|
|
48423
|
-
return applyRanges(parseCompressInput(args), ctx);
|
|
48424
|
-
}
|
|
48425
|
-
if (toolName === "decompress") {
|
|
48426
|
-
return resolveDecompress(args, ctx);
|
|
48427
|
-
}
|
|
48428
|
-
if (toolName === "search_context") {
|
|
48429
|
-
const query = typeof args.query === "string" ? args.query : "";
|
|
48430
|
-
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
48431
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
48432
|
-
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
48433
|
-
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
48434
|
-
const lines = blocks.map((b2) => {
|
|
48435
|
-
const topic = b2.topic ?? "(no topic)";
|
|
48436
|
-
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
48437
|
-
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
48438
|
-
${preview}`;
|
|
48439
|
-
});
|
|
48440
|
-
return `Found ${blocks.length} block(s) for "${query}":
|
|
48441
|
-
|
|
48442
|
-
${lines.join("\n\n")}`;
|
|
48443
|
-
}
|
|
48444
|
-
if (toolName === "acp_status") {
|
|
48445
|
-
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
48446
|
-
}
|
|
48447
|
-
return `[Unknown proxy tool: ${toolName}]`;
|
|
48448
|
-
}
|
|
48449
|
-
function classifySseEvent(eventStr) {
|
|
48450
|
-
const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
|
|
48451
|
-
if (!dataLine) return {};
|
|
48452
|
-
const jsonStr = dataLine.slice(5).trim();
|
|
48453
|
-
if (jsonStr === "[DONE]") return { done: true };
|
|
48454
|
-
let parsed;
|
|
48455
|
-
try {
|
|
48456
|
-
parsed = JSON.parse(jsonStr);
|
|
48457
|
-
} catch {
|
|
48458
|
-
return {};
|
|
48459
|
-
}
|
|
48460
|
-
const choices = parsed.choices;
|
|
48461
|
-
const choice = choices?.[0];
|
|
48462
|
-
if (!choice) return {};
|
|
48463
|
-
const delta = choice.delta;
|
|
48464
|
-
const finishReason = choice.finish_reason;
|
|
48465
|
-
const out = {};
|
|
48466
|
-
if (finishReason) {
|
|
48467
|
-
out.finishReason = finishReason;
|
|
48468
|
-
out.usage = parsed.usage ?? null;
|
|
48469
|
-
}
|
|
48470
|
-
if (!delta) return out;
|
|
48471
|
-
if (delta.tool_calls) {
|
|
48472
|
-
const tcs = delta.tool_calls;
|
|
48473
|
-
const toolCalls = [];
|
|
48474
|
-
for (const tc of tcs) {
|
|
48475
|
-
const idx = typeof tc.index === "number" ? tc.index : 0;
|
|
48476
|
-
const fn = tc.function;
|
|
48477
|
-
const name = typeof fn?.name === "string" ? fn.name : "";
|
|
48478
|
-
const id = typeof tc.id === "string" ? tc.id : "";
|
|
48479
|
-
const args = typeof fn?.arguments === "string" ? fn.arguments : "";
|
|
48480
|
-
toolCalls.push({ index: idx, id, name, arguments: args });
|
|
48481
|
-
}
|
|
48482
|
-
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
48483
|
-
out.contentDelta = delta.content;
|
|
48484
|
-
}
|
|
48485
|
-
out.toolCalls = toolCalls;
|
|
48486
|
-
return out;
|
|
48487
|
-
}
|
|
48488
|
-
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
48489
|
-
out.contentDelta = delta.content;
|
|
48490
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
48491
|
-
return out;
|
|
48492
|
-
}
|
|
48493
|
-
if (delta.role || Object.keys(delta).length === 0 && !finishReason) {
|
|
48494
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
48495
|
-
}
|
|
48496
|
-
return out;
|
|
48497
|
-
}
|
|
48498
|
-
function buildToolCallSse(base, tc) {
|
|
48499
|
-
return `data: ${JSON.stringify({
|
|
48500
|
-
...base,
|
|
48501
|
-
choices: [{
|
|
48502
|
-
index: 0,
|
|
48503
|
-
delta: {
|
|
48504
|
-
tool_calls: [{
|
|
48505
|
-
index: tc.index,
|
|
48506
|
-
id: tc.id,
|
|
48507
|
-
type: "function",
|
|
48508
|
-
function: { name: tc.name, arguments: tc.arguments }
|
|
48509
|
-
}]
|
|
48510
|
-
},
|
|
48511
|
-
finish_reason: null
|
|
48512
|
-
}]
|
|
48513
|
-
})}
|
|
48514
|
-
|
|
48515
|
-
`;
|
|
48516
|
-
}
|
|
48517
|
-
function buildFinishSse(base, finishReason, usage) {
|
|
48518
|
-
return `data: ${JSON.stringify({
|
|
48519
|
-
...base,
|
|
48520
|
-
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
48521
|
-
...usage ? { usage } : {}
|
|
48522
|
-
})}
|
|
48523
|
-
|
|
48524
|
-
`;
|
|
48525
|
-
}
|
|
48526
|
-
function buildContentSse(id, model, content) {
|
|
48527
|
-
return `data: ${JSON.stringify({
|
|
48528
|
-
id,
|
|
48529
|
-
object: "chat.completion.chunk",
|
|
48530
|
-
created: Date.now(),
|
|
48531
|
-
model,
|
|
48532
|
-
choices: [{ index: 0, delta: { content }, finish_reason: null }]
|
|
48533
|
-
})}
|
|
48534
|
-
|
|
48535
|
-
`;
|
|
48536
|
-
}
|
|
48537
|
-
function buildVisibilityMarker(toolName, result) {
|
|
48538
|
-
const lines = result.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
48539
|
-
const failed = lines.some(
|
|
48540
|
-
(l) => l.includes("FAILED") || l.includes("not found") || l.includes("is required") || l.includes("No blocks matched")
|
|
48541
|
-
);
|
|
48542
|
-
const icons = {
|
|
48543
|
-
compress: "\u{1F4E6}",
|
|
48544
|
-
decompress: "\u{1F4E4}",
|
|
48545
|
-
search_context: "\u{1F50D}",
|
|
48546
|
-
acp_status: "\u{1F4CA}"
|
|
48547
|
-
};
|
|
48548
|
-
const icon = failed ? "\u274C" : icons[toolName] ?? "\u{1F4E6}";
|
|
48549
|
-
if (toolName === "acp_status" && lines.length >= 2) {
|
|
48550
|
-
const dataLine = lines.slice(0, 3).join(" | ").replace(/\s+/g, " ");
|
|
48551
|
-
return `
|
|
48552
|
-
${icon} [ACP] ${dataLine}
|
|
48553
|
-
`;
|
|
48554
|
-
}
|
|
48555
|
-
const inner = (lines[0] ?? "").replace(/^\[/, "").replace(/\]$/, "").trim();
|
|
48556
|
-
return `
|
|
48557
|
-
${icon} [ACP] ${inner}
|
|
48558
|
-
`;
|
|
48559
|
-
}
|
|
48560
|
-
async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOptions) {
|
|
48561
|
-
let upstream = initialUpstream;
|
|
48562
|
-
let activeClearTimer = null;
|
|
48563
|
-
try {
|
|
48564
|
-
const model = requestBody.model ?? "unknown";
|
|
48565
|
-
let responseId = `chatcmpl-proxy-${Date.now()}`;
|
|
48566
|
-
const makeBase = () => ({
|
|
48567
|
-
id: responseId,
|
|
48568
|
-
object: "chat.completion.chunk",
|
|
48569
|
-
created: Date.now(),
|
|
48570
|
-
model
|
|
48571
|
-
});
|
|
48572
|
-
let loopCount = 0;
|
|
48573
|
-
for (; ; ) {
|
|
48574
|
-
loopCount++;
|
|
48575
|
-
if (loopCount > 10) {
|
|
48576
|
-
ctx.log("[acp-proxy: compress loop limit (10) reached, forwarding as-is]");
|
|
48577
|
-
yield Buffer.from(buildFinishSse(makeBase(), "stop", null), "utf8");
|
|
48578
|
-
yield Buffer.from("data: [DONE]\n\n", "utf8");
|
|
48579
|
-
return;
|
|
48580
|
-
}
|
|
48581
|
-
const toolCallByIndex = /* @__PURE__ */ new Map();
|
|
48582
|
-
let contentText = "";
|
|
48583
|
-
let finishReason = null;
|
|
48584
|
-
let usage = null;
|
|
48585
|
-
const isFirstRound = loopCount === 1;
|
|
48586
|
-
const reader = upstream.getReader();
|
|
48587
|
-
const decoder = new TextDecoder("utf-8");
|
|
48588
|
-
let sseBuffer = "";
|
|
48589
|
-
try {
|
|
48590
|
-
for (; ; ) {
|
|
48591
|
-
const { done, value } = await reader.read();
|
|
48592
|
-
if (done) break;
|
|
48593
|
-
sseBuffer += decoder.decode(value, { stream: true });
|
|
48594
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48595
|
-
let sep;
|
|
48596
|
-
while ((sep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48597
|
-
const eventStr = sseBuffer.slice(0, sep);
|
|
48598
|
-
sseBuffer = sseBuffer.slice(sep + 2);
|
|
48599
|
-
if (!eventStr.trim()) continue;
|
|
48600
|
-
const d = classifySseEvent(eventStr);
|
|
48601
|
-
if (d.done) {
|
|
48602
|
-
continue;
|
|
48603
|
-
}
|
|
48604
|
-
if (isFirstRound) {
|
|
48605
|
-
if (d.yieldChunk) {
|
|
48606
|
-
if (!responseId) {
|
|
48607
|
-
const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
|
|
48608
|
-
if (dataLine) {
|
|
48609
|
-
try {
|
|
48610
|
-
const p2 = JSON.parse(dataLine.slice(5).trim());
|
|
48611
|
-
if (typeof p2.id === "string") responseId = p2.id;
|
|
48612
|
-
} catch {
|
|
48613
|
-
}
|
|
48614
|
-
}
|
|
48615
|
-
}
|
|
48616
|
-
yield d.yieldChunk;
|
|
48617
|
-
}
|
|
48618
|
-
} else {
|
|
48619
|
-
if (d.contentDelta) {
|
|
48620
|
-
yield Buffer.from(buildContentSse(responseId, model, d.contentDelta), "utf8");
|
|
48621
|
-
}
|
|
48622
|
-
}
|
|
48623
|
-
if (d.contentDelta) contentText += d.contentDelta;
|
|
48624
|
-
if (d.finishReason) finishReason = d.finishReason;
|
|
48625
|
-
if (d.usage !== void 0) usage = d.usage;
|
|
48626
|
-
if (d.toolCalls) {
|
|
48627
|
-
for (const tc of d.toolCalls) {
|
|
48628
|
-
const existing = toolCallByIndex.get(tc.index);
|
|
48629
|
-
if (existing) {
|
|
48630
|
-
if (tc.name) existing.name = tc.name;
|
|
48631
|
-
if (tc.id) existing.id = tc.id;
|
|
48632
|
-
existing.arguments += tc.arguments;
|
|
48633
|
-
} else {
|
|
48634
|
-
toolCallByIndex.set(tc.index, tc);
|
|
48635
|
-
}
|
|
48636
|
-
}
|
|
48637
|
-
}
|
|
48638
|
-
}
|
|
48639
|
-
}
|
|
48640
|
-
sseBuffer += decoder.decode();
|
|
48641
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48642
|
-
let resSep;
|
|
48643
|
-
while ((resSep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48644
|
-
const eventStr = sseBuffer.slice(0, resSep);
|
|
48645
|
-
sseBuffer = sseBuffer.slice(resSep + 2);
|
|
48646
|
-
if (!eventStr.trim()) continue;
|
|
48647
|
-
const d = classifySseEvent(eventStr);
|
|
48648
|
-
if (d.done) continue;
|
|
48649
|
-
if (isFirstRound) {
|
|
48650
|
-
if (d.yieldChunk) yield d.yieldChunk;
|
|
48651
|
-
} else {
|
|
48652
|
-
if (d.contentDelta) yield Buffer.from(buildContentSse(responseId, model, d.contentDelta), "utf8");
|
|
48653
|
-
}
|
|
48654
|
-
if (d.contentDelta) contentText += d.contentDelta;
|
|
48655
|
-
if (d.finishReason) finishReason = d.finishReason;
|
|
48656
|
-
if (d.usage !== void 0) usage = d.usage;
|
|
48657
|
-
if (d.toolCalls) {
|
|
48658
|
-
for (const tc of d.toolCalls) {
|
|
48659
|
-
const existing = toolCallByIndex.get(tc.index);
|
|
48660
|
-
if (existing) {
|
|
48661
|
-
if (tc.name) existing.name = tc.name;
|
|
48662
|
-
if (tc.id) existing.id = tc.id;
|
|
48663
|
-
existing.arguments += tc.arguments;
|
|
48664
|
-
} else {
|
|
48665
|
-
toolCallByIndex.set(tc.index, tc);
|
|
48666
|
-
}
|
|
48667
|
-
}
|
|
48668
|
-
}
|
|
48669
|
-
}
|
|
48670
|
-
} finally {
|
|
48671
|
-
reader.releaseLock();
|
|
48672
|
-
}
|
|
48673
|
-
const sortedIndices = [...toolCallByIndex.keys()].sort((a, b2) => a - b2);
|
|
48674
|
-
const toolCalls = sortedIndices.map((i) => {
|
|
48675
|
-
const tc = toolCallByIndex.get(i);
|
|
48676
|
-
return { ...tc, id: tc.id || `call_${tc.index}` };
|
|
48677
|
-
}).filter((tc) => tc.name.length > 0);
|
|
48678
|
-
const proxyCalls = toolCalls.filter((tc) => PROXY_TOOL_NAMES.has(tc.name));
|
|
48679
|
-
const realCalls = toolCalls.filter((tc) => !PROXY_TOOL_NAMES.has(tc.name));
|
|
48680
|
-
const mutatingProxy = proxyCalls.filter((tc) => MUTATING_PROXY_TOOLS.has(tc.name));
|
|
48681
|
-
const readonlyProxy = proxyCalls.filter((tc) => READONLY_PROXY_TOOLS.has(tc.name));
|
|
48682
|
-
const hasMutatingOnly = mutatingProxy.length > 0 && realCalls.length === 0;
|
|
48683
|
-
if (usage) {
|
|
48684
|
-
const prompt = usage.prompt_tokens ?? usage.input_tokens;
|
|
48685
|
-
const det = usage.prompt_tokens_details ?? usage.prompt_cache_hit_tokens;
|
|
48686
|
-
const cached = det?.cached_tokens ?? usage.prompt_cache_hit_tokens;
|
|
48687
|
-
const out = usage.completion_tokens ?? usage.output_tokens;
|
|
48688
|
-
if (typeof prompt === "number") {
|
|
48689
|
-
const ch = typeof cached === "number" ? cached : 0;
|
|
48690
|
-
log("info", `[acp-usage] round ${loopCount} input=${prompt} cached=${typeof cached === "number" ? cached : "?"} output=${out ?? "?"}${ch > 0 ? ` (cache hit ${Math.round(ch / prompt * 100)}%)` : ""}`);
|
|
48691
|
-
ctx.session.stats.inputTokens += prompt;
|
|
48692
|
-
ctx.session.stats.lastInputTokens = prompt + (typeof cached === "number" ? cached : 0);
|
|
48693
|
-
if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
|
|
48694
|
-
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
48695
|
-
ctx.session.stats.cacheSamples += 1;
|
|
48696
|
-
}
|
|
48697
|
-
}
|
|
48698
|
-
if (!hasMutatingOnly) {
|
|
48699
|
-
for (const tc of readonlyProxy) {
|
|
48700
|
-
let args = {};
|
|
48701
|
-
try {
|
|
48702
|
-
args = JSON.parse(tc.arguments);
|
|
48703
|
-
} catch {
|
|
48704
|
-
args = {};
|
|
48705
|
-
}
|
|
48706
|
-
let result;
|
|
48707
|
-
try {
|
|
48708
|
-
result = executeProxyTool(tc.name, args, ctx);
|
|
48709
|
-
} catch (e) {
|
|
48710
|
-
result = `[${tc.name} FAILED: ${e instanceof Error ? e.message : String(e)}]`;
|
|
48711
|
-
}
|
|
48712
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48713
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48714
|
-
yield Buffer.from(
|
|
48715
|
-
buildContentSse(responseId, model, buildVisibilityMarker(tc.name, result)),
|
|
48716
|
-
"utf8"
|
|
48717
|
-
);
|
|
48718
|
-
}
|
|
48719
|
-
for (const tc of realCalls) {
|
|
48720
|
-
yield Buffer.from(buildToolCallSse(makeBase(), tc), "utf8");
|
|
48721
|
-
}
|
|
48722
|
-
const fr2 = realCalls.length > 0 ? "tool_calls" : finishReason ?? "stop";
|
|
48723
|
-
yield Buffer.from(buildFinishSse(makeBase(), fr2, usage), "utf8");
|
|
48724
|
-
yield Buffer.from("data: [DONE]\n\n", "utf8");
|
|
48725
|
-
return;
|
|
48726
|
-
}
|
|
48727
|
-
const names = proxyCalls.map((c) => c.name).join(", ");
|
|
48728
|
-
ctx.log(`[acp-proxy: round ${loopCount} \u2014 ${proxyCalls.length} proxy call(s): ${names}]`);
|
|
48729
|
-
const messages = requestBody.messages ?? [];
|
|
48730
|
-
messages.push({
|
|
48731
|
-
role: "assistant",
|
|
48732
|
-
content: contentText || null,
|
|
48733
|
-
tool_calls: proxyCalls.map((tc) => ({
|
|
48734
|
-
id: tc.id,
|
|
48735
|
-
type: "function",
|
|
48736
|
-
function: { name: tc.name, arguments: tc.arguments }
|
|
48737
|
-
}))
|
|
48738
|
-
});
|
|
48739
|
-
for (const tc of proxyCalls) {
|
|
48740
|
-
let args = {};
|
|
48741
|
-
try {
|
|
48742
|
-
args = JSON.parse(tc.arguments);
|
|
48743
|
-
} catch {
|
|
48744
|
-
args = {};
|
|
48745
|
-
}
|
|
48746
|
-
const result = executeProxyTool(tc.name, args, ctx);
|
|
48747
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48748
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48749
|
-
yield Buffer.from(
|
|
48750
|
-
buildContentSse(responseId, model, buildVisibilityMarker(tc.name, result)),
|
|
48751
|
-
"utf8"
|
|
48752
|
-
);
|
|
48753
|
-
messages.push({
|
|
48754
|
-
role: "tool",
|
|
48755
|
-
tool_call_id: tc.id,
|
|
48756
|
-
content: result
|
|
48757
|
-
});
|
|
48758
|
-
}
|
|
48759
|
-
requestBody.messages = messages;
|
|
48760
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
48761
|
-
method: "POST",
|
|
48762
|
-
headers: requestOptions.headers,
|
|
48763
|
-
body: JSON.stringify(requestBody),
|
|
48764
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
48765
|
-
});
|
|
48766
|
-
if (!resp.ok || !resp.body) {
|
|
48767
|
-
const errText = await resp.text().catch(() => "upstream error");
|
|
48768
|
-
ctx.log(`[acp-proxy: compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
48769
|
-
yield Buffer.from(
|
|
48770
|
-
`data: ${JSON.stringify({
|
|
48771
|
-
...makeBase(),
|
|
48772
|
-
choices: [{
|
|
48773
|
-
index: 0,
|
|
48774
|
-
delta: { content: `
|
|
48775
|
-
[acp-proxy: upstream error ${resp.status}: ${errText.slice(0, 200)}]
|
|
48776
|
-
` },
|
|
48777
|
-
finish_reason: null
|
|
48778
|
-
}]
|
|
48779
|
-
})}
|
|
48780
|
-
|
|
48781
|
-
`,
|
|
48782
|
-
"utf8"
|
|
48783
|
-
);
|
|
48784
|
-
yield Buffer.from(buildFinishSse(makeBase(), "stop", null), "utf8");
|
|
48785
|
-
yield Buffer.from("data: [DONE]\n\n", "utf8");
|
|
48786
|
-
return;
|
|
48787
|
-
}
|
|
48788
|
-
upstream = resp.body;
|
|
48789
|
-
if (activeClearTimer) activeClearTimer();
|
|
48790
|
-
activeClearTimer = clearTimer;
|
|
48791
|
-
}
|
|
48792
|
-
} finally {
|
|
48793
|
-
if (activeClearTimer) {
|
|
48794
|
-
activeClearTimer();
|
|
48795
|
-
activeClearTimer = null;
|
|
48796
|
-
}
|
|
48797
|
-
}
|
|
48798
|
-
}
|
|
48799
|
-
|
|
48800
|
-
// src/compress-loop-anthropic.ts
|
|
48801
|
-
function executeProxyTool2(toolName, args, ctx) {
|
|
48802
|
-
if (toolName === "compress") {
|
|
48803
|
-
return applyRanges(parseCompressInput(args), ctx);
|
|
48804
|
-
}
|
|
48805
|
-
if (toolName === "decompress") {
|
|
48806
|
-
return resolveDecompress(args, ctx);
|
|
48807
|
-
}
|
|
48808
|
-
if (toolName === "search_context") {
|
|
48809
|
-
const query = typeof args.query === "string" ? args.query : "";
|
|
48810
|
-
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
48811
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
48812
|
-
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
48813
|
-
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
48814
|
-
const lines = blocks.map((b2) => {
|
|
48815
|
-
const topic = b2.topic ?? "(no topic)";
|
|
48816
|
-
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
48817
|
-
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
48818
|
-
${preview}`;
|
|
48819
|
-
});
|
|
48820
|
-
return `Found ${blocks.length} block(s) for "${query}":
|
|
48821
|
-
|
|
48822
|
-
${lines.join("\n\n")}`;
|
|
48823
|
-
}
|
|
48824
|
-
if (toolName === "acp_status") {
|
|
48825
|
-
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
48826
|
-
}
|
|
48827
|
-
return `[Unknown proxy tool: ${toolName}]`;
|
|
48828
|
-
}
|
|
48829
|
-
function parseAnthropicSse(eventStr) {
|
|
48830
|
-
const lines = eventStr.split("\n");
|
|
48831
|
-
let type = "";
|
|
48832
|
-
const dataLines = [];
|
|
48833
|
-
for (const l of lines) {
|
|
48834
|
-
if (l.startsWith("event:")) {
|
|
48835
|
-
type = l.slice(6).trim();
|
|
48836
|
-
} else if (l.startsWith("data:")) {
|
|
48837
|
-
dataLines.push(l.slice(5).replace(/^ /, ""));
|
|
48838
|
-
}
|
|
48839
|
-
}
|
|
48840
|
-
if (!type) return null;
|
|
48841
|
-
const jsonStr = dataLines.join("\n").trim();
|
|
48842
|
-
if (!jsonStr) return { type, data: {} };
|
|
48843
|
-
try {
|
|
48844
|
-
return { type, data: JSON.parse(jsonStr) };
|
|
48845
|
-
} catch {
|
|
48846
|
-
return { type, data: {} };
|
|
48847
|
-
}
|
|
48848
|
-
}
|
|
48849
|
-
function buildTextBlockSse(index, text) {
|
|
48850
|
-
return `event: content_block_start
|
|
48851
|
-
data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "text", text: "" } })}
|
|
48852
|
-
|
|
48853
|
-
event: content_block_delta
|
|
48854
|
-
data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "text_delta", text } })}
|
|
48855
|
-
|
|
48856
|
-
event: content_block_stop
|
|
48857
|
-
data: ${JSON.stringify({ type: "content_block_stop", index })}
|
|
48858
|
-
|
|
48859
|
-
`;
|
|
48860
|
-
}
|
|
48861
|
-
function buildTerminalSse(stopReason, outputTokens, inputTokens, cachedTokens, messageId, model) {
|
|
48862
|
-
const usage = {
|
|
48863
|
-
input_tokens: inputTokens,
|
|
48864
|
-
output_tokens: outputTokens,
|
|
48865
|
-
cache_read_input_tokens: cachedTokens
|
|
48866
|
-
};
|
|
48867
|
-
const extra = {};
|
|
48868
|
-
if (messageId) extra.id = messageId;
|
|
48869
|
-
if (model) extra.model = model;
|
|
48870
|
-
return `event: message_delta
|
|
48871
|
-
data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason, stop_sequence: null }, usage, ...extra })}
|
|
48872
|
-
|
|
48873
|
-
event: message_stop
|
|
48874
|
-
data: ${JSON.stringify({ type: "message_stop" })}
|
|
48875
|
-
|
|
48876
|
-
`;
|
|
48877
|
-
}
|
|
48878
|
-
function remapIndex(json, oldIndex, newIndex) {
|
|
48879
|
-
return json.replaceAll(`"index":${oldIndex}`, `"index":${newIndex}`).replaceAll(`"index": ${oldIndex}`, `"index": ${newIndex}`);
|
|
48880
|
-
}
|
|
48881
|
-
function safeParse2(s3) {
|
|
48882
|
-
try {
|
|
48883
|
-
const v2 = JSON.parse(s3);
|
|
48884
|
-
return typeof v2 === "object" && v2 ? v2 : {};
|
|
48885
|
-
} catch {
|
|
48886
|
-
return {};
|
|
48887
|
-
}
|
|
48888
|
-
}
|
|
48889
|
-
async function* compressLoopAnthropicStream(initialUpstream, ctx, requestBody, requestOptions) {
|
|
48890
|
-
let upstream = initialUpstream;
|
|
48891
|
-
let activeClearTimer = null;
|
|
48892
|
-
try {
|
|
48893
|
-
const model = requestBody.model ?? void 0;
|
|
48894
|
-
let messageId;
|
|
48895
|
-
let clientIndex = 0;
|
|
48896
|
-
let totalOutputTokens = 0;
|
|
48897
|
-
let totalInputTokens = 0;
|
|
48898
|
-
let totalCachedTokens = 0;
|
|
48899
|
-
for (let loopCount = 1; ; loopCount++) {
|
|
48900
|
-
if (loopCount > 10) {
|
|
48901
|
-
ctx.log("[acp-proxy: anthropic compress loop limit (10) reached, finishing]");
|
|
48902
|
-
yield Buffer.from(buildTerminalSse("end_turn", totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
48903
|
-
return;
|
|
48904
|
-
}
|
|
48905
|
-
const isFirstRound = loopCount === 1;
|
|
48906
|
-
const state = { clientIndex, toolBlocks: /* @__PURE__ */ new Map(), indexMap: /* @__PURE__ */ new Map() };
|
|
48907
|
-
let hasRealToolUse = false;
|
|
48908
|
-
let roundText = "";
|
|
48909
|
-
let roundStopReason;
|
|
48910
|
-
const reader = upstream.getReader();
|
|
48911
|
-
const decoder = new TextDecoder("utf-8");
|
|
48912
|
-
let sseBuffer = "";
|
|
48913
|
-
const cbs = {
|
|
48914
|
-
onRealToolUse: () => {
|
|
48915
|
-
hasRealToolUse = true;
|
|
48916
|
-
},
|
|
48917
|
-
onText: (t) => {
|
|
48918
|
-
roundText += t;
|
|
48919
|
-
},
|
|
48920
|
-
onOutputTokens: (n) => {
|
|
48921
|
-
totalOutputTokens += n;
|
|
48922
|
-
},
|
|
48923
|
-
onMessageId: (id) => {
|
|
48924
|
-
if (!messageId) messageId = id;
|
|
48925
|
-
},
|
|
48926
|
-
onStopReason: (r) => {
|
|
48927
|
-
roundStopReason = r;
|
|
48928
|
-
},
|
|
48929
|
-
onCacheUsage: (input, cached) => {
|
|
48930
|
-
if (typeof input === "number") {
|
|
48931
|
-
ctx.session.stats.inputTokens += input;
|
|
48932
|
-
ctx.session.stats.lastInputTokens = input + (typeof cached === "number" ? cached : 0);
|
|
48933
|
-
totalInputTokens += input;
|
|
48934
|
-
}
|
|
48935
|
-
if (typeof cached === "number") {
|
|
48936
|
-
ctx.session.stats.cachedTokens += cached;
|
|
48937
|
-
ctx.session.stats.cacheSamples += 1;
|
|
48938
|
-
totalCachedTokens += cached;
|
|
48939
|
-
}
|
|
48940
|
-
}
|
|
48941
|
-
};
|
|
48942
|
-
try {
|
|
48943
|
-
for (; ; ) {
|
|
48944
|
-
const { done, value } = await reader.read();
|
|
48945
|
-
if (done) break;
|
|
48946
|
-
sseBuffer += decoder.decode(value, { stream: true });
|
|
48947
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48948
|
-
let sep;
|
|
48949
|
-
while ((sep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48950
|
-
const eventStr = sseBuffer.slice(0, sep);
|
|
48951
|
-
sseBuffer = sseBuffer.slice(sep + 2);
|
|
48952
|
-
if (!eventStr.trim()) continue;
|
|
48953
|
-
for (const b2 of routeAnthropicEvent(eventStr, isFirstRound, state, cbs)) {
|
|
48954
|
-
yield b2;
|
|
48955
|
-
}
|
|
48956
|
-
}
|
|
48957
|
-
}
|
|
48958
|
-
sseBuffer += decoder.decode();
|
|
48959
|
-
sseBuffer = normalizeSseLineEndings(sseBuffer);
|
|
48960
|
-
let resSep;
|
|
48961
|
-
while ((resSep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
48962
|
-
const eventStr = sseBuffer.slice(0, resSep);
|
|
48963
|
-
sseBuffer = sseBuffer.slice(resSep + 2);
|
|
48964
|
-
if (!eventStr.trim()) continue;
|
|
48965
|
-
for (const b2 of routeAnthropicEvent(eventStr, isFirstRound, state, cbs)) {
|
|
48966
|
-
yield b2;
|
|
48967
|
-
}
|
|
48968
|
-
}
|
|
48969
|
-
} finally {
|
|
48970
|
-
reader.releaseLock();
|
|
48971
|
-
}
|
|
48972
|
-
clientIndex = state.clientIndex;
|
|
48973
|
-
const proxyCalls = [...state.toolBlocks.values()].filter((b2) => PROXY_TOOL_NAMES.has(b2.name));
|
|
48974
|
-
const mutatingProxy = proxyCalls.filter((b2) => MUTATING_PROXY_TOOLS.has(b2.name));
|
|
48975
|
-
const readonlyProxy = proxyCalls.filter((b2) => READONLY_PROXY_TOOLS.has(b2.name));
|
|
48976
|
-
const hasMutatingOnly = mutatingProxy.length > 0 && !hasRealToolUse;
|
|
48977
|
-
if (!hasMutatingOnly) {
|
|
48978
|
-
for (const tc of readonlyProxy) {
|
|
48979
|
-
const args = safeParse2(tc.json);
|
|
48980
|
-
let result;
|
|
48981
|
-
try {
|
|
48982
|
-
result = executeProxyTool2(tc.name, args, ctx);
|
|
48983
|
-
} catch (e) {
|
|
48984
|
-
result = `[${tc.name} FAILED: ${e instanceof Error ? e.message : String(e)}]`;
|
|
48985
|
-
}
|
|
48986
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
48987
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
48988
|
-
yield Buffer.from(buildTextBlockSse(clientIndex, buildVisibilityMarker(tc.name, result)), "utf8");
|
|
48989
|
-
clientIndex++;
|
|
48990
|
-
}
|
|
48991
|
-
const stop = hasRealToolUse ? "tool_use" : roundStopReason ?? "end_turn";
|
|
48992
|
-
yield Buffer.from(buildTerminalSse(stop, totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
48993
|
-
return;
|
|
48994
|
-
}
|
|
48995
|
-
const names = proxyCalls.map((c) => c.name).join(", ");
|
|
48996
|
-
ctx.log(`[acp-proxy: anthropic round ${loopCount} \u2014 ${proxyCalls.length} proxy call(s): ${names}]`);
|
|
48997
|
-
const messages = requestBody.messages ?? [];
|
|
48998
|
-
const assistantContent = [];
|
|
48999
|
-
if (roundText.length > 0) {
|
|
49000
|
-
assistantContent.push({ type: "text", text: roundText });
|
|
49001
|
-
}
|
|
49002
|
-
for (const tc of proxyCalls) {
|
|
49003
|
-
assistantContent.push({ type: "tool_use", id: tc.id, name: tc.name, input: safeParse2(tc.json) });
|
|
49004
|
-
}
|
|
49005
|
-
messages.push({ role: "assistant", content: assistantContent });
|
|
49006
|
-
for (const tc of proxyCalls) {
|
|
49007
|
-
const args = safeParse2(tc.json);
|
|
49008
|
-
const result = executeProxyTool2(tc.name, args, ctx);
|
|
49009
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49010
|
-
ctx.log(`[acp-proxy: ${tc.name} (${tc.id}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49011
|
-
yield Buffer.from(buildTextBlockSse(clientIndex, buildVisibilityMarker(tc.name, result)), "utf8");
|
|
49012
|
-
clientIndex++;
|
|
49013
|
-
messages.push({
|
|
49014
|
-
role: "user",
|
|
49015
|
-
content: [{ type: "tool_result", tool_use_id: tc.id, content: result }]
|
|
49016
|
-
});
|
|
49017
|
-
}
|
|
49018
|
-
requestBody.messages = messages;
|
|
49019
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49020
|
-
method: "POST",
|
|
49021
|
-
headers: requestOptions.headers,
|
|
49022
|
-
body: JSON.stringify(requestBody),
|
|
49023
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49024
|
-
});
|
|
49025
|
-
if (!resp.ok || !resp.body) {
|
|
49026
|
-
const errText = await resp.text().catch(() => "upstream error");
|
|
49027
|
-
ctx.log(`[acp-proxy: anthropic compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
49028
|
-
yield Buffer.from(buildTextBlockSse(clientIndex, `
|
|
49029
|
-
[acp-proxy: upstream error ${resp.status}: ${errText.slice(0, 200)}]
|
|
49030
|
-
`), "utf8");
|
|
49031
|
-
yield Buffer.from(buildTerminalSse("end_turn", totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
49032
|
-
return;
|
|
49033
|
-
}
|
|
49034
|
-
upstream = resp.body;
|
|
49035
|
-
if (activeClearTimer) activeClearTimer();
|
|
49036
|
-
activeClearTimer = clearTimer;
|
|
49037
|
-
}
|
|
49038
|
-
} finally {
|
|
49039
|
-
if (activeClearTimer) {
|
|
49040
|
-
activeClearTimer();
|
|
49041
|
-
activeClearTimer = null;
|
|
49042
|
-
}
|
|
49043
|
-
}
|
|
49044
|
-
}
|
|
49045
|
-
function routeAnthropicEvent(eventStr, isFirstRound, state, cb) {
|
|
49046
|
-
const parsed = parseAnthropicSse(eventStr);
|
|
49047
|
-
if (!parsed) return [];
|
|
49048
|
-
const { type, data } = parsed;
|
|
49049
|
-
if (type === "message_start") {
|
|
49050
|
-
const msg2 = data.message ?? {};
|
|
49051
|
-
if (typeof msg2.id === "string") cb.onMessageId(msg2.id);
|
|
49052
|
-
const u2 = msg2.usage ?? {};
|
|
49053
|
-
cb.onCacheUsage(u2.input_tokens, u2.cache_read_input_tokens);
|
|
49054
|
-
return isFirstRound ? [Buffer.from(eventStr + "\n\n", "utf8")] : [];
|
|
49055
|
-
}
|
|
49056
|
-
if (type === "ping") {
|
|
49057
|
-
return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49058
|
-
}
|
|
49059
|
-
if (type === "content_block_start") {
|
|
49060
|
-
const upstreamIndex = data.index ?? 0;
|
|
49061
|
-
const block = data.content_block ?? {};
|
|
49062
|
-
if (block.type === "tool_use") {
|
|
49063
|
-
const name = typeof block.name === "string" ? block.name : "";
|
|
49064
|
-
const id = typeof block.id === "string" ? block.id : `toolu_${upstreamIndex}`;
|
|
49065
|
-
if (PROXY_TOOL_NAMES.has(name)) {
|
|
49066
|
-
state.toolBlocks.set(upstreamIndex, { id, name, json: "" });
|
|
49067
|
-
return [];
|
|
49068
|
-
}
|
|
49069
|
-
cb.onRealToolUse();
|
|
49070
|
-
}
|
|
49071
|
-
const ci2 = state.clientIndex++;
|
|
49072
|
-
state.indexMap.set(upstreamIndex, ci2);
|
|
49073
|
-
if (isFirstRound) return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49074
|
-
return [Buffer.from(remapIndex(eventStr + "\n\n", upstreamIndex, ci2), "utf8")];
|
|
49075
|
-
}
|
|
49076
|
-
if (type === "content_block_delta") {
|
|
49077
|
-
const upstreamIndex = data.index ?? 0;
|
|
49078
|
-
const delta = data.delta ?? {};
|
|
49079
|
-
if (state.toolBlocks.has(upstreamIndex)) {
|
|
49080
|
-
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
49081
|
-
state.toolBlocks.get(upstreamIndex).json += delta.partial_json;
|
|
49082
|
-
}
|
|
49083
|
-
return [];
|
|
49084
|
-
}
|
|
49085
|
-
if (delta.type === "text_delta" && typeof delta.text === "string") cb.onText(delta.text);
|
|
49086
|
-
if (isFirstRound) return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49087
|
-
const ci2 = state.indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
49088
|
-
return [Buffer.from(remapIndex(eventStr + "\n\n", upstreamIndex, ci2), "utf8")];
|
|
49089
|
-
}
|
|
49090
|
-
if (type === "content_block_stop") {
|
|
49091
|
-
const upstreamIndex = data.index ?? 0;
|
|
49092
|
-
if (state.toolBlocks.has(upstreamIndex)) return [];
|
|
49093
|
-
if (isFirstRound) return [Buffer.from(eventStr + "\n\n", "utf8")];
|
|
49094
|
-
const ci2 = state.indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
49095
|
-
return [Buffer.from(remapIndex(eventStr + "\n\n", upstreamIndex, ci2), "utf8")];
|
|
49096
|
-
}
|
|
49097
|
-
if (type === "message_delta") {
|
|
49098
|
-
const u2 = data.usage ?? {};
|
|
49099
|
-
const out = u2.output_tokens;
|
|
49100
|
-
if (typeof out === "number") cb.onOutputTokens(out);
|
|
49101
|
-
cb.onCacheUsage(
|
|
49102
|
-
u2.input_tokens,
|
|
49103
|
-
u2.cache_read_input_tokens
|
|
49104
|
-
);
|
|
49105
|
-
const d = data.delta ?? {};
|
|
49106
|
-
if (typeof d.stop_reason === "string") cb.onStopReason(d.stop_reason);
|
|
49107
|
-
return [];
|
|
49108
|
-
}
|
|
49109
|
-
if (type === "message_stop") {
|
|
49110
|
-
return [];
|
|
49111
|
-
}
|
|
49112
|
-
return isFirstRound ? [Buffer.from(eventStr + "\n\n", "utf8")] : [];
|
|
49113
|
-
}
|
|
49114
|
-
|
|
49115
|
-
// src/compress-loop-responses.ts
|
|
49116
|
-
var TEXT_PROTOCOL = process.env.ACP_COMPRESS_PROTOCOL === "text";
|
|
49117
|
-
function extractTextTriggers(text) {
|
|
49118
|
-
const calls = [];
|
|
49119
|
-
let clean = "";
|
|
49120
|
-
let i = 0;
|
|
49121
|
-
let n = 0;
|
|
49122
|
-
while (i < text.length) {
|
|
49123
|
-
const open = text.indexOf(ACP_TEXT_OPEN, i);
|
|
49124
|
-
if (open === -1) {
|
|
49125
|
-
clean += text.slice(i);
|
|
49126
|
-
break;
|
|
49127
|
-
}
|
|
49128
|
-
clean += text.slice(i, open);
|
|
49129
|
-
const after = open + ACP_TEXT_OPEN.length;
|
|
49130
|
-
const close = text.indexOf(ACP_TEXT_CLOSE, after);
|
|
49131
|
-
if (close === -1) {
|
|
49132
|
-
clean += text.slice(open);
|
|
49133
|
-
break;
|
|
49134
|
-
}
|
|
49135
|
-
const payload = text.slice(after, close).trim();
|
|
49136
|
-
if (payload) {
|
|
49137
|
-
const stamp = `${Date.now()}_${n++}`;
|
|
49138
|
-
calls.push({
|
|
49139
|
-
itemId: `fc_text_${stamp}`,
|
|
49140
|
-
callId: `call_text_${stamp}`,
|
|
49141
|
-
name: COMPRESS_TOOL_NAME,
|
|
49142
|
-
arguments: payload
|
|
49143
|
-
});
|
|
49144
|
-
}
|
|
49145
|
-
i = close + ACP_TEXT_CLOSE.length;
|
|
49146
|
-
}
|
|
49147
|
-
return { clean, calls };
|
|
49148
|
-
}
|
|
49149
|
-
function executeProxyTool3(toolName, args, ctx) {
|
|
49150
|
-
if (toolName === "compress") {
|
|
49151
|
-
return applyRanges(parseCompressInput(args), ctx);
|
|
49152
|
-
}
|
|
49153
|
-
if (toolName === "decompress") {
|
|
49154
|
-
return resolveDecompress(args, ctx);
|
|
49155
|
-
}
|
|
49156
|
-
if (toolName === "search_context") {
|
|
49157
|
-
const query = typeof args.query === "string" ? args.query : "";
|
|
49158
|
-
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
49159
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
49160
|
-
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
49161
|
-
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
49162
|
-
const lines = blocks.map((b2) => {
|
|
49163
|
-
const topic = b2.topic ?? "(no topic)";
|
|
49164
|
-
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
49165
|
-
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
49166
|
-
${preview}`;
|
|
49167
|
-
});
|
|
49168
|
-
return `Found ${blocks.length} block(s) for "${query}":
|
|
49169
|
-
|
|
49170
|
-
${lines.join("\n\n")}`;
|
|
49171
|
-
}
|
|
49172
|
-
if (toolName === "acp_status") {
|
|
49173
|
-
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
49174
|
-
}
|
|
49175
|
-
return `[Unknown proxy tool: ${toolName}]`;
|
|
49176
|
-
}
|
|
49177
|
-
function extractEventType(rawEvent) {
|
|
49178
|
-
for (const l of rawEvent.split("\n")) {
|
|
49179
|
-
if (l.startsWith("event:")) return l.slice(6).trim();
|
|
49180
|
-
}
|
|
49181
|
-
return null;
|
|
49182
|
-
}
|
|
49183
|
-
function extractDataLine(rawEvent) {
|
|
49184
|
-
const parts = [];
|
|
49185
|
-
for (const l of rawEvent.split("\n")) {
|
|
49186
|
-
if (l.startsWith("data:")) {
|
|
49187
|
-
let v2 = l.slice(5);
|
|
49188
|
-
if (v2.startsWith(" ")) v2 = v2.slice(1);
|
|
49189
|
-
parts.push(v2);
|
|
49190
|
-
}
|
|
49191
|
-
}
|
|
49192
|
-
return parts.length ? parts.join("\n") : null;
|
|
49193
|
-
}
|
|
49194
|
-
function classifyResponsesSseEvent(eventStr) {
|
|
49195
|
-
const type = extractEventType(eventStr);
|
|
49196
|
-
const dataLine = extractDataLine(eventStr);
|
|
49197
|
-
if (!type || !dataLine) return {};
|
|
49198
|
-
let obj;
|
|
49199
|
-
try {
|
|
49200
|
-
obj = JSON.parse(dataLine);
|
|
49201
|
-
} catch {
|
|
49202
|
-
return {};
|
|
49203
|
-
}
|
|
49204
|
-
const out = {};
|
|
49205
|
-
switch (type) {
|
|
49206
|
-
case "response.created":
|
|
49207
|
-
case "response.in_progress":
|
|
49208
|
-
out.isMeta = true;
|
|
49209
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49210
|
-
return out;
|
|
49211
|
-
case "response.output_item.added": {
|
|
49212
|
-
const item = obj.item;
|
|
49213
|
-
if (item?.type === "function_call") {
|
|
49214
|
-
const name = typeof item.name === "string" ? item.name : "";
|
|
49215
|
-
out.fcStart = {
|
|
49216
|
-
itemId: typeof item.id === "string" ? item.id : "",
|
|
49217
|
-
callId: typeof item.call_id === "string" ? item.call_id : "",
|
|
49218
|
-
name
|
|
49219
|
-
};
|
|
49220
|
-
return out;
|
|
49221
|
-
}
|
|
49222
|
-
if (item?.type === "custom_tool_call") out.noBuffer = true;
|
|
49223
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49224
|
-
return out;
|
|
49225
|
-
}
|
|
49226
|
-
case "response.content_part.added":
|
|
49227
|
-
case "response.content_part.done":
|
|
49228
|
-
case "response.output_text.done":
|
|
49229
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49230
|
-
return out;
|
|
49231
|
-
case "response.output_text.delta": {
|
|
49232
|
-
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
49233
|
-
if (delta) {
|
|
49234
|
-
out.contentDelta = delta;
|
|
49235
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49236
|
-
}
|
|
49237
|
-
return out;
|
|
49238
|
-
}
|
|
49239
|
-
case "response.function_call_arguments.delta": {
|
|
49240
|
-
const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
|
|
49241
|
-
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
49242
|
-
out.fcArgs = { itemId, delta };
|
|
49243
|
-
return out;
|
|
49244
|
-
}
|
|
49245
|
-
case "response.output_item.done": {
|
|
49246
|
-
const item = obj.item;
|
|
49247
|
-
if (item?.type === "function_call") {
|
|
49248
|
-
out.fcDone = { itemId: typeof item.id === "string" ? item.id : "" };
|
|
49249
|
-
return out;
|
|
49250
|
-
}
|
|
49251
|
-
if (item?.type === "custom_tool_call") {
|
|
49252
|
-
out.noBuffer = true;
|
|
49253
|
-
out.customToolCallDone = true;
|
|
49254
|
-
}
|
|
49255
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49256
|
-
return out;
|
|
49257
|
-
}
|
|
49258
|
-
case "response.completed":
|
|
49259
|
-
out.isMeta = true;
|
|
49260
|
-
out.terminal = true;
|
|
49261
|
-
out.terminalKind = "completed";
|
|
49262
|
-
out.responseObj = obj.response ?? null;
|
|
49263
|
-
return out;
|
|
49264
|
-
case "response.incomplete":
|
|
49265
|
-
out.isMeta = true;
|
|
49266
|
-
out.terminal = true;
|
|
49267
|
-
out.terminalKind = "incomplete";
|
|
49268
|
-
out.terminalRaw = eventStr;
|
|
49269
|
-
return out;
|
|
49270
|
-
case "response.failed":
|
|
49271
|
-
case "response.error":
|
|
49272
|
-
out.isMeta = true;
|
|
49273
|
-
out.terminal = true;
|
|
49274
|
-
out.terminalKind = "failed";
|
|
49275
|
-
out.terminalRaw = eventStr;
|
|
49276
|
-
return out;
|
|
49277
|
-
default:
|
|
49278
|
-
if (type.startsWith("response.custom_tool_call.")) out.noBuffer = true;
|
|
49279
|
-
out.yieldChunk = Buffer.from(eventStr + "\n\n", "utf8");
|
|
49280
|
-
return out;
|
|
49281
|
-
}
|
|
49282
|
-
}
|
|
49283
|
-
function buildMessageItemSequence(itemId, outputIndex, text) {
|
|
49284
|
-
const item = { type: "message", id: itemId, role: "assistant", content: [] };
|
|
49285
|
-
const part = { type: "output_text", text: "" };
|
|
49286
|
-
const doneItem = { type: "message", id: itemId, role: "assistant", content: [{ type: "output_text", text }] };
|
|
49287
|
-
return [
|
|
49288
|
-
`event: response.output_item.added
|
|
49289
|
-
data: ${JSON.stringify({ type: "response.output_item.added", output_index: outputIndex, item })}
|
|
49290
|
-
|
|
49291
|
-
`,
|
|
49292
|
-
`event: response.content_part.added
|
|
49293
|
-
data: ${JSON.stringify({ type: "response.content_part.added", item_id: itemId, output_index: outputIndex, part })}
|
|
49294
|
-
|
|
49295
|
-
`,
|
|
49296
|
-
`event: response.output_text.delta
|
|
49297
|
-
data: ${JSON.stringify({ type: "response.output_text.delta", item_id: itemId, output_index: outputIndex, delta: text })}
|
|
49298
|
-
|
|
49299
|
-
`,
|
|
49300
|
-
`event: response.output_text.done
|
|
49301
|
-
data: ${JSON.stringify({ type: "response.output_text.done", item_id: itemId, output_index: outputIndex, text })}
|
|
49302
|
-
|
|
49303
|
-
`,
|
|
49304
|
-
`event: response.content_part.done
|
|
49305
|
-
data: ${JSON.stringify({ type: "response.content_part.done", item_id: itemId, output_index: outputIndex, part: { type: "output_text", text } })}
|
|
49306
|
-
|
|
49307
|
-
`,
|
|
49308
|
-
`event: response.output_item.done
|
|
49309
|
-
data: ${JSON.stringify({ type: "response.output_item.done", output_index: outputIndex, item: doneItem })}
|
|
49310
|
-
|
|
49311
|
-
`
|
|
49312
|
-
].join("");
|
|
49313
|
-
}
|
|
49314
|
-
function buildFunctionCallEvents(fc, outputIndex) {
|
|
49315
|
-
return [
|
|
49316
|
-
`event: response.output_item.added
|
|
49317
|
-
data: ${JSON.stringify({
|
|
49318
|
-
type: "response.output_item.added",
|
|
49319
|
-
output_index: outputIndex,
|
|
49320
|
-
item: { type: "function_call", id: fc.itemId, call_id: fc.callId, name: fc.name, arguments: "" }
|
|
49321
|
-
})}
|
|
49322
|
-
|
|
49323
|
-
`,
|
|
49324
|
-
`event: response.function_call_arguments.delta
|
|
49325
|
-
data: ${JSON.stringify({
|
|
49326
|
-
type: "response.function_call_arguments.delta",
|
|
49327
|
-
item_id: fc.itemId,
|
|
49328
|
-
delta: fc.arguments
|
|
49329
|
-
})}
|
|
49330
|
-
|
|
49331
|
-
`,
|
|
49332
|
-
`event: response.function_call_arguments.done
|
|
49333
|
-
data: ${JSON.stringify({
|
|
49334
|
-
type: "response.function_call_arguments.done",
|
|
49335
|
-
item_id: fc.itemId,
|
|
49336
|
-
arguments: fc.arguments
|
|
49337
|
-
})}
|
|
49338
|
-
|
|
49339
|
-
`,
|
|
49340
|
-
`event: response.output_item.done
|
|
49341
|
-
data: ${JSON.stringify({
|
|
49342
|
-
type: "response.output_item.done",
|
|
49343
|
-
output_index: outputIndex,
|
|
49344
|
-
item: { type: "function_call", id: fc.itemId, call_id: fc.callId, name: fc.name, arguments: fc.arguments }
|
|
49345
|
-
})}
|
|
49346
|
-
|
|
49347
|
-
`
|
|
49348
|
-
].join("");
|
|
49349
|
-
}
|
|
49350
|
-
function buildCompleted(responseObj) {
|
|
49351
|
-
const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
49352
|
-
return `event: response.completed
|
|
49353
|
-
data: ${JSON.stringify({
|
|
49354
|
-
type: "response.completed",
|
|
49355
|
-
response: resp
|
|
49356
|
-
})}
|
|
49357
|
-
|
|
49358
|
-
`;
|
|
49359
|
-
}
|
|
49360
|
-
function buildFailed(responseObj) {
|
|
49361
|
-
const id = responseObj?.id ?? `resp-failed-${Date.now()}`;
|
|
49362
|
-
const resp = { ...responseObj ?? {}, id, status: "failed", error: { code: "server_error", message: "upstream returned empty response" } };
|
|
49363
|
-
return `event: response.failed
|
|
49364
|
-
data: ${JSON.stringify({
|
|
49365
|
-
type: "response.failed",
|
|
49366
|
-
response: resp
|
|
49367
|
-
})}
|
|
49368
|
-
|
|
49369
|
-
`;
|
|
49370
|
-
}
|
|
49371
|
-
function responsesJsonOutput(response) {
|
|
49372
|
-
const textParts = [];
|
|
49373
|
-
const calls = [];
|
|
49374
|
-
for (const item of Array.isArray(response.output) ? response.output : []) {
|
|
49375
|
-
if (!item || typeof item !== "object") continue;
|
|
49376
|
-
const value = item;
|
|
49377
|
-
if (value.type === "message") {
|
|
49378
|
-
for (const part of Array.isArray(value.content) ? value.content : []) {
|
|
49379
|
-
if (part && typeof part === "object" && part.type === "output_text") {
|
|
49380
|
-
textParts.push(part);
|
|
49381
|
-
}
|
|
49382
|
-
}
|
|
49383
|
-
} else if (value.type === "function_call") {
|
|
49384
|
-
calls.push({
|
|
49385
|
-
itemId: typeof value.id === "string" ? value.id : "",
|
|
49386
|
-
callId: typeof value.call_id === "string" ? value.call_id : "",
|
|
49387
|
-
name: typeof value.name === "string" ? value.name : "",
|
|
49388
|
-
arguments: typeof value.arguments === "string" ? value.arguments : ""
|
|
49389
|
-
});
|
|
49390
|
-
}
|
|
49391
|
-
}
|
|
49392
|
-
return {
|
|
49393
|
-
text: textParts.map((part) => typeof part.text === "string" ? part.text : "").join(""),
|
|
49394
|
-
textParts,
|
|
49395
|
-
calls
|
|
49396
|
-
};
|
|
49397
|
-
}
|
|
49398
|
-
function replaceResponsesJsonText(parts, text) {
|
|
49399
|
-
parts.forEach((part, index) => {
|
|
49400
|
-
part.text = index === 0 ? text : "";
|
|
49401
|
-
});
|
|
49402
|
-
}
|
|
49403
|
-
function surfaceReadonlyJson(current, proxyCalls, ctx) {
|
|
49404
|
-
const markers = [];
|
|
49405
|
-
for (const call of proxyCalls) {
|
|
49406
|
-
if (MUTATING_PROXY_TOOLS.has(call.name)) continue;
|
|
49407
|
-
let args = {};
|
|
49408
|
-
try {
|
|
49409
|
-
args = JSON.parse(call.arguments);
|
|
49410
|
-
} catch {
|
|
49411
|
-
args = {};
|
|
49412
|
-
}
|
|
49413
|
-
let result;
|
|
49414
|
-
try {
|
|
49415
|
-
result = executeProxyTool3(call.name, args, ctx);
|
|
49416
|
-
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49417
|
-
} catch (e) {
|
|
49418
|
-
result = `\u274C [ACP] ${call.name} FAILED: ${String(e)}`;
|
|
49419
|
-
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) FAILED: ${String(e)}]`);
|
|
49420
|
-
}
|
|
49421
|
-
markers.push(buildVisibilityMarker(call.name, result));
|
|
49422
|
-
}
|
|
49423
|
-
if (markers.length === 0) return current;
|
|
49424
|
-
const out = Array.isArray(current.output) ? [...current.output] : [];
|
|
49425
|
-
out.push({ type: "message", id: `msg_acp_ro_${Date.now()}_${markers.length}`, role: "assistant", content: [{ type: "output_text", text: markers.join("\n") }] });
|
|
49426
|
-
return { ...current, output: out };
|
|
49427
|
-
}
|
|
49428
|
-
async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
|
|
49429
|
-
let current = initialResponse;
|
|
49430
|
-
for (let loopCount = 1; loopCount <= 5; loopCount++) {
|
|
49431
|
-
const output = responsesJsonOutput(current);
|
|
49432
|
-
const extracted = extractTextTriggers(output.text);
|
|
49433
|
-
const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
|
|
49434
|
-
const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
|
|
49435
|
-
const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
|
|
49436
|
-
const mutatingProxy = proxyCalls.filter((call) => MUTATING_PROXY_TOOLS.has(call.name));
|
|
49437
|
-
if (mutatingProxy.length === 0 || realCalls.length > 0) {
|
|
49438
|
-
if (proxyCalls.length > 0) {
|
|
49439
|
-
replaceResponsesJsonText(output.textParts, extracted.clean);
|
|
49440
|
-
current = surfaceReadonlyJson(current, proxyCalls, ctx);
|
|
49441
|
-
}
|
|
49442
|
-
return current;
|
|
49443
|
-
}
|
|
49444
|
-
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
49445
|
-
if (extracted.clean.trim()) {
|
|
49446
|
-
inputItems.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: extracted.clean }] });
|
|
49447
|
-
}
|
|
49448
|
-
for (const call of proxyCalls) {
|
|
49449
|
-
let args = {};
|
|
49450
|
-
try {
|
|
49451
|
-
args = JSON.parse(call.arguments);
|
|
49452
|
-
} catch (error) {
|
|
49453
|
-
log("warn", `[acp-compress-args] ${call.name} JSON.parse failed: ${String(error)}`);
|
|
49454
|
-
}
|
|
49455
|
-
const result = executeProxyTool3(call.name, args, ctx);
|
|
49456
|
-
ctx.log(`[acp-proxy: responses JSON ${call.name} \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49457
|
-
inputItems.push({ type: "message", role: "user", content: buildVisibilityMarker(call.name, result) });
|
|
49458
|
-
}
|
|
49459
|
-
requestBody.input = inputItems;
|
|
49460
|
-
const { response, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49461
|
-
method: "POST",
|
|
49462
|
-
headers: requestOptions.headers,
|
|
49463
|
-
body: JSON.stringify(requestBody),
|
|
49464
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49465
|
-
});
|
|
49466
|
-
try {
|
|
49467
|
-
if (!response.ok) {
|
|
49468
|
-
const detail = await response.text().catch(() => "upstream error");
|
|
49469
|
-
throw new Error(`responses compress loop upstream error ${response.status}: ${detail.slice(0, 200)}`);
|
|
49470
|
-
}
|
|
49471
|
-
current = await response.json();
|
|
49472
|
-
} finally {
|
|
49473
|
-
clearTimer();
|
|
49474
|
-
}
|
|
49475
|
-
}
|
|
49476
|
-
ctx.log("[acp-proxy: responses JSON compress loop limit (5) reached]");
|
|
49477
|
-
return current;
|
|
49478
|
-
}
|
|
49479
|
-
async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, requestOptions) {
|
|
49480
|
-
const textProtocol = ctx.textProtocol ?? TEXT_PROTOCOL;
|
|
49481
|
-
let upstream = initialUpstream;
|
|
49482
|
-
let loopCount = 0;
|
|
49483
|
-
let responseObj = null;
|
|
49484
|
-
let activeClearTimer = null;
|
|
49485
|
-
let nextOutputIndex = 0;
|
|
49486
|
-
for (; ; ) {
|
|
49487
|
-
loopCount++;
|
|
49488
|
-
if (loopCount > 5) {
|
|
49489
|
-
ctx.log("[acp-proxy: responses compress loop limit (5) reached, forwarding completion as-is]");
|
|
49490
|
-
const limItemId = `msg_acp_limit_${Date.now()}`;
|
|
49491
|
-
yield Buffer.from(buildMessageItemSequence(limItemId, nextOutputIndex++, "\n[acp-proxy: compress loop limit reached]\n"), "utf8");
|
|
49492
|
-
yield Buffer.from(buildCompleted(responseObj), "utf8");
|
|
49493
|
-
return;
|
|
49494
|
-
}
|
|
49495
|
-
const fcByItemId = /* @__PURE__ */ new Map();
|
|
49496
|
-
let contentText = "";
|
|
49497
|
-
let customToolCalls = 0;
|
|
49498
|
-
let completed = false;
|
|
49499
|
-
let terminalKind = null;
|
|
49500
|
-
let terminalRaw = null;
|
|
49501
|
-
const isFirstRound = loopCount === 1;
|
|
49502
|
-
const reader = upstream.getReader();
|
|
49503
|
-
const decoder = new TextDecoder("utf-8");
|
|
49504
|
-
let sseBuffer = "";
|
|
49505
|
-
try {
|
|
49506
|
-
for (; ; ) {
|
|
49507
|
-
const { done, value } = await reader.read();
|
|
49508
|
-
if (done) break;
|
|
49509
|
-
sseBuffer += decoder.decode(value, { stream: true });
|
|
49510
|
-
if (sseBuffer.indexOf("\r") !== -1) sseBuffer = sseBuffer.replace(/\r\n|\r/g, "\n");
|
|
49511
|
-
let sep;
|
|
49512
|
-
while ((sep = sseBuffer.indexOf("\n\n")) >= 0) {
|
|
49513
|
-
const eventStr = sseBuffer.slice(0, sep);
|
|
49514
|
-
sseBuffer = sseBuffer.slice(sep + 2);
|
|
49515
|
-
if (!eventStr.trim()) continue;
|
|
49516
|
-
const d = classifyResponsesSseEvent(eventStr);
|
|
49517
|
-
if (d.yieldChunk && (isFirstRound || !d.isMeta) && !(textProtocol && !d.isMeta && !d.noBuffer)) {
|
|
49518
|
-
yield d.yieldChunk;
|
|
49519
|
-
}
|
|
49520
|
-
if (d.contentDelta) contentText += d.contentDelta;
|
|
49521
|
-
if (d.fcStart) {
|
|
49522
|
-
fcByItemId.set(d.fcStart.itemId, {
|
|
49523
|
-
itemId: d.fcStart.itemId,
|
|
49524
|
-
callId: d.fcStart.callId,
|
|
49525
|
-
name: d.fcStart.name,
|
|
49526
|
-
arguments: ""
|
|
49527
|
-
});
|
|
49528
|
-
}
|
|
49529
|
-
if (d.fcArgs) {
|
|
49530
|
-
const existing = fcByItemId.get(d.fcArgs.itemId);
|
|
49531
|
-
if (existing) existing.arguments += d.fcArgs.delta;
|
|
49532
|
-
}
|
|
49533
|
-
if (d.fcDone) {
|
|
49534
|
-
const existing = fcByItemId.get(d.fcDone.itemId);
|
|
49535
|
-
if (existing && !existing.arguments) {
|
|
49536
|
-
const item = JSON.parse(extractDataLine(eventStr) ?? "{}").item;
|
|
49537
|
-
const args = typeof item?.arguments === "string" ? item.arguments : "";
|
|
49538
|
-
existing.arguments = args;
|
|
49539
|
-
}
|
|
49540
|
-
}
|
|
49541
|
-
if (d.customToolCallDone) customToolCalls++;
|
|
49542
|
-
if (d.terminal) {
|
|
49543
|
-
completed = true;
|
|
49544
|
-
terminalKind = d.terminalKind ?? null;
|
|
49545
|
-
terminalRaw = d.terminalRaw ?? null;
|
|
49546
|
-
responseObj = d.responseObj ?? responseObj;
|
|
49547
|
-
const resp2 = d.responseObj ?? {};
|
|
49548
|
-
const usage = resp2.usage;
|
|
49549
|
-
if (usage && d.terminalKind === "completed") {
|
|
49550
|
-
const prompt = usage.input_tokens ?? usage.prompt_tokens ?? "?";
|
|
49551
|
-
const inDet = usage.input_tokens_details;
|
|
49552
|
-
const prDet = usage.prompt_tokens_details;
|
|
49553
|
-
const cached = inDet?.cached_tokens ?? prDet?.cached_tokens ?? "?";
|
|
49554
|
-
const out = usage.output_tokens ?? "?";
|
|
49555
|
-
log("info", `[acp-usage] round ${loopCount} input=${prompt} cached=${cached} output=${out}${cached !== "?" && cached !== 0 && prompt !== "?" ? ` (cache hit ${Math.round(Number(cached) / Number(prompt) * 100)}%)` : ""}`);
|
|
49556
|
-
if (typeof prompt === "number") {
|
|
49557
|
-
ctx.session.stats.inputTokens += prompt;
|
|
49558
|
-
ctx.session.stats.lastInputTokens = prompt + (typeof cached === "number" ? cached : 0);
|
|
49559
|
-
if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
|
|
49560
|
-
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
49561
|
-
ctx.session.stats.cacheSamples += 1;
|
|
49562
|
-
}
|
|
49563
|
-
}
|
|
49564
|
-
}
|
|
49565
|
-
}
|
|
49566
|
-
}
|
|
49567
|
-
} finally {
|
|
49568
|
-
reader.releaseLock();
|
|
49569
|
-
if (activeClearTimer) {
|
|
49570
|
-
activeClearTimer();
|
|
49571
|
-
activeClearTimer = null;
|
|
49572
|
-
}
|
|
49573
|
-
}
|
|
49574
|
-
if (textProtocol) {
|
|
49575
|
-
const extracted = extractTextTriggers(contentText);
|
|
49576
|
-
contentText = extracted.clean;
|
|
49577
|
-
for (const c of extracted.calls) {
|
|
49578
|
-
fcByItemId.set(c.itemId, c);
|
|
49579
|
-
}
|
|
49580
|
-
if (contentText.trim()) {
|
|
49581
|
-
const textItemId = `msg_acp_text_r${loopCount}_${Date.now()}`;
|
|
49582
|
-
yield Buffer.from(buildMessageItemSequence(textItemId, nextOutputIndex++, contentText), "utf8");
|
|
49583
|
-
}
|
|
49584
|
-
}
|
|
49585
|
-
const allCalls = [...fcByItemId.values()].filter((c) => c.name.length > 0);
|
|
49586
|
-
const proxyCalls = allCalls.filter((c) => PROXY_TOOL_NAMES.has(c.name));
|
|
49587
|
-
const realCalls = allCalls.filter((c) => !PROXY_TOOL_NAMES.has(c.name));
|
|
49588
|
-
const readonlyProxy = proxyCalls.filter((c) => READONLY_PROXY_TOOLS.has(c.name));
|
|
49589
|
-
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))}`);
|
|
49590
|
-
const hasMutatingOnly = proxyCalls.some((c) => MUTATING_PROXY_TOOLS.has(c.name)) && realCalls.length === 0;
|
|
49591
|
-
if (!hasMutatingOnly) {
|
|
49592
|
-
for (const fc of readonlyProxy) {
|
|
49593
|
-
let args = {};
|
|
49594
|
-
try {
|
|
49595
|
-
args = JSON.parse(fc.arguments);
|
|
49596
|
-
} catch (e) {
|
|
49597
|
-
log("warn", `[acp-compress-args] ${fc.name} JSON.parse failed: ${String(e)}`);
|
|
49598
|
-
args = {};
|
|
49599
|
-
}
|
|
49600
|
-
let result;
|
|
49601
|
-
try {
|
|
49602
|
-
result = executeProxyTool3(fc.name, args, ctx);
|
|
49603
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49604
|
-
ctx.log(`[acp-proxy: responses ${fc.name} (read-only) (${fc.callId}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49605
|
-
} catch (e) {
|
|
49606
|
-
result = `\u274C [ACP] ${fc.name} FAILED: ${String(e)}`;
|
|
49607
|
-
ctx.log(`[acp-proxy: responses ${fc.name} (read-only) (${fc.callId}) FAILED: ${String(e)}]`);
|
|
49608
|
-
}
|
|
49609
|
-
const markerItemId = `msg_acp_ro_${Date.now()}_${nextOutputIndex}`;
|
|
49610
|
-
yield Buffer.from(buildMessageItemSequence(markerItemId, nextOutputIndex++, buildVisibilityMarker(fc.name, result)), "utf8");
|
|
49611
|
-
}
|
|
49612
|
-
let oi2 = nextOutputIndex;
|
|
49613
|
-
for (const fc of realCalls) {
|
|
49614
|
-
yield Buffer.from(buildFunctionCallEvents(fc, oi2), "utf8");
|
|
49615
|
-
oi2++;
|
|
49616
|
-
}
|
|
49617
|
-
nextOutputIndex = oi2;
|
|
49618
|
-
if (terminalKind && terminalKind !== "completed" && terminalRaw) {
|
|
49619
|
-
yield Buffer.from(terminalRaw + "\n\n", "utf8");
|
|
49620
|
-
return;
|
|
49621
|
-
}
|
|
49622
|
-
const hasUsage = !!responseObj?.usage;
|
|
49623
|
-
const emittedReadonly = readonlyProxy.length > 0;
|
|
49624
|
-
if (contentText.length === 0 && realCalls.length === 0 && customToolCalls === 0 && !emittedReadonly && !hasUsage) {
|
|
49625
|
-
ctx.log("[acp-proxy: empty upstream response (no content/usage) \u2014 injecting response.failed for client retry]");
|
|
49626
|
-
yield Buffer.from(buildFailed(responseObj), "utf8");
|
|
49627
|
-
return;
|
|
49628
|
-
}
|
|
49629
|
-
if (!completed) {
|
|
49630
|
-
ctx.log("[acp-proxy: responses stream ended without completion]");
|
|
49631
|
-
}
|
|
49632
|
-
yield Buffer.from(buildCompleted(responseObj), "utf8");
|
|
49633
|
-
return;
|
|
49634
|
-
}
|
|
49635
|
-
const names = proxyCalls.map((c) => c.name).join(", ");
|
|
49636
|
-
ctx.log(`[acp-proxy: responses round ${loopCount} \u2014 ${proxyCalls.length} proxy call(s): ${names}]`);
|
|
49637
|
-
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
49638
|
-
if (contentText) {
|
|
49639
|
-
inputItems.push({
|
|
49640
|
-
type: "message",
|
|
49641
|
-
role: "assistant",
|
|
49642
|
-
content: [{ type: "output_text", text: contentText }]
|
|
49643
|
-
});
|
|
49644
|
-
}
|
|
49645
|
-
if (!textProtocol) {
|
|
49646
|
-
for (const fc of proxyCalls) {
|
|
49647
|
-
inputItems.push({
|
|
49648
|
-
type: "function_call",
|
|
49649
|
-
id: fc.itemId || `fc_${Date.now()}`,
|
|
49650
|
-
call_id: fc.callId || `call_${Date.now()}`,
|
|
49651
|
-
name: fc.name,
|
|
49652
|
-
arguments: fc.arguments
|
|
49653
|
-
});
|
|
49654
|
-
}
|
|
49655
|
-
}
|
|
49656
|
-
for (const fc of proxyCalls) {
|
|
49657
|
-
let args = {};
|
|
49658
|
-
try {
|
|
49659
|
-
args = JSON.parse(fc.arguments);
|
|
49660
|
-
} catch (e) {
|
|
49661
|
-
log("warn", `[acp-compress-args] ${fc.name} JSON.parse failed: ${String(e)}. raw arguments (len=${fc.arguments.length}): ${fc.arguments.slice(0, 300)}`);
|
|
49662
|
-
args = {};
|
|
49663
|
-
}
|
|
49664
|
-
if (fc.name === "compress") {
|
|
49665
|
-
log("debug", `[acp-compress-args] compress args parsed: ${JSON.stringify(args).slice(0, 400)}`);
|
|
49666
|
-
}
|
|
49667
|
-
const result = executeProxyTool3(fc.name, args, ctx);
|
|
49668
|
-
const preview = result.length > 120 ? result.slice(0, 120) + "..." : result;
|
|
49669
|
-
ctx.log(`[acp-proxy: responses ${fc.name} (${fc.callId}) \u2192 ${preview.replace(/\n/g, " ")}]`);
|
|
49670
|
-
const markerItemId = `msg_acp_${Date.now()}_${nextOutputIndex}`;
|
|
49671
|
-
yield Buffer.from(
|
|
49672
|
-
buildMessageItemSequence(markerItemId, nextOutputIndex++, buildVisibilityMarker(fc.name, result)),
|
|
49673
|
-
"utf8"
|
|
49674
|
-
);
|
|
49675
|
-
inputItems.push(textProtocol ? { type: "message", role: "user", content: buildVisibilityMarker(fc.name, result) } : { type: "function_call_output", call_id: fc.callId || `call_${Date.now()}`, output: result });
|
|
48432
|
+
continue;
|
|
49676
48433
|
}
|
|
49677
|
-
|
|
49678
|
-
|
|
49679
|
-
|
|
49680
|
-
|
|
49681
|
-
headers: requestOptions.headers,
|
|
49682
|
-
body: JSON.stringify(requestBody),
|
|
49683
|
-
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49684
|
-
});
|
|
49685
|
-
if (!resp.ok || !resp.body) {
|
|
49686
|
-
clearTimer();
|
|
49687
|
-
const errText = await resp.text().catch(() => "upstream error");
|
|
49688
|
-
ctx.log(`[acp-proxy: responses compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
49689
|
-
const errItemId = `msg_acp_err_${Date.now()}`;
|
|
49690
|
-
yield Buffer.from(
|
|
49691
|
-
buildMessageItemSequence(errItemId, nextOutputIndex++, `
|
|
49692
|
-
[acp-proxy: upstream error ${resp.status}: ${errText.slice(0, 200)}]
|
|
49693
|
-
`),
|
|
49694
|
-
"utf8"
|
|
49695
|
-
);
|
|
49696
|
-
yield Buffer.from(buildCompleted(responseObj), "utf8");
|
|
49697
|
-
return;
|
|
48434
|
+
const streak = (orphanStreaks.get(block) ?? 0) + 1;
|
|
48435
|
+
orphanStreaks.set(block, streak);
|
|
48436
|
+
if (streak >= ORPHAN_THRESHOLD) {
|
|
48437
|
+
reaped.push(block.blockId);
|
|
49698
48438
|
}
|
|
49699
|
-
upstream = resp.body;
|
|
49700
|
-
if (activeClearTimer) activeClearTimer();
|
|
49701
|
-
activeClearTimer = clearTimer;
|
|
49702
48439
|
}
|
|
48440
|
+
if (reaped.length === 0) return { reaped: [] };
|
|
48441
|
+
session.state = deactivate(session.state, reaped);
|
|
48442
|
+
for (const id of reaped) session.blockContents.delete(id);
|
|
48443
|
+
return { reaped };
|
|
48444
|
+
}
|
|
48445
|
+
|
|
48446
|
+
// src/compress-loop.ts
|
|
48447
|
+
function buildVisibilityMarker(toolName, result) {
|
|
48448
|
+
const lines = result.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
48449
|
+
const failed = lines.some(
|
|
48450
|
+
(l) => l.includes("FAILED") || l.includes("not found") || l.includes("is required") || l.includes("No blocks matched")
|
|
48451
|
+
);
|
|
48452
|
+
const icons = {
|
|
48453
|
+
compress: "\u{1F4E6}",
|
|
48454
|
+
decompress: "\u{1F4E4}",
|
|
48455
|
+
search_context: "\u{1F50D}",
|
|
48456
|
+
acp_status: "\u{1F4CA}"
|
|
48457
|
+
};
|
|
48458
|
+
const icon = failed ? "\u274C" : icons[toolName] ?? "\u{1F4E6}";
|
|
48459
|
+
if (toolName === "acp_status") {
|
|
48460
|
+
return `
|
|
48461
|
+
${icon} [ACP] acp_status result:
|
|
48462
|
+
${result.trim()}
|
|
48463
|
+
`;
|
|
48464
|
+
}
|
|
48465
|
+
const inner = (lines[0] ?? "").replace(/^\[/, "").replace(/\]$/, "").trim();
|
|
48466
|
+
return `
|
|
48467
|
+
${icon} [ACP] ${inner}
|
|
48468
|
+
`;
|
|
49703
48469
|
}
|
|
49704
48470
|
|
|
49705
48471
|
// src/loop/core.ts
|
|
49706
48472
|
var MAX_LOOP_ROUNDS = 10;
|
|
49707
|
-
function
|
|
48473
|
+
function executeProxyTool(toolName, args, ctx, callId) {
|
|
49708
48474
|
if (toolName === "compress") {
|
|
49709
48475
|
return applyRanges(parseCompressInput(args, callId), ctx);
|
|
49710
48476
|
}
|
|
@@ -49728,35 +48494,62 @@ function executeProxyTool4(toolName, args, ctx, callId) {
|
|
|
49728
48494
|
${lines.join("\n\n")}`;
|
|
49729
48495
|
}
|
|
49730
48496
|
if (toolName === "acp_status") {
|
|
49731
|
-
return
|
|
48497
|
+
return handleAcpStatus(args, ctx);
|
|
49732
48498
|
}
|
|
49733
48499
|
return `[Unknown proxy tool: ${toolName}]`;
|
|
49734
48500
|
}
|
|
48501
|
+
function handleAcpStatus(args, ctx) {
|
|
48502
|
+
const scope = typeof args.scope === "string" ? args.scope : void 0;
|
|
48503
|
+
const view = typeof args.view === "string" ? args.view : void 0;
|
|
48504
|
+
const tool = typeof args.tool === "string" ? args.tool : void 0;
|
|
48505
|
+
const sort = typeof args.sort === "string" ? args.sort : void 0;
|
|
48506
|
+
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
48507
|
+
const base = buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast, { scope, view, tool, sort, limit });
|
|
48508
|
+
if (scope) return base;
|
|
48509
|
+
const nudge = ctx.nudge;
|
|
48510
|
+
const ranges = nudge?.compressibleRanges ?? [];
|
|
48511
|
+
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
48512
|
+
const extra = [];
|
|
48513
|
+
if (nudge) {
|
|
48514
|
+
extra.push("");
|
|
48515
|
+
extra.push(nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`);
|
|
48516
|
+
}
|
|
48517
|
+
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
48518
|
+
extra.push("");
|
|
48519
|
+
extra.push(formatRanges(ranges, protectedRanges));
|
|
48520
|
+
}
|
|
48521
|
+
return extra.length > 0 ? `${base}
|
|
48522
|
+
${extra.join("\n")}` : base;
|
|
48523
|
+
}
|
|
49735
48524
|
function recordUsage(ctx, usage, round) {
|
|
49736
48525
|
const prompt = usage.inputTokens;
|
|
49737
48526
|
const cached = usage.cachedTokens;
|
|
49738
48527
|
const out = usage.outputTokens;
|
|
49739
48528
|
if (typeof prompt === "number") ctx.session.stats.inputTokens += prompt;
|
|
49740
48529
|
ctx.session.stats.lastInputTokens = (typeof prompt === "number" ? prompt : 0) + (typeof cached === "number" ? cached : 0);
|
|
49741
|
-
if (typeof cached === "number")
|
|
48530
|
+
if (typeof cached === "number") {
|
|
48531
|
+
ctx.session.stats.cachedTokens += cached;
|
|
48532
|
+
ctx.session.stats.cacheSamples += 1;
|
|
48533
|
+
}
|
|
49742
48534
|
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
49743
|
-
ctx.session.stats.cacheSamples += 1;
|
|
49744
48535
|
const hitPct = typeof prompt === "number" && typeof cached === "number" && prompt + cached > 0 ? Math.round(cached / (prompt + cached) * 100) : 0;
|
|
49745
48536
|
ctx.log(
|
|
49746
48537
|
`[acp-usage] round ${round} input=${ctx.session.stats.lastInputTokens} cached=${cached ?? 0} (cache hit ${hitPct}%)`
|
|
49747
48538
|
);
|
|
49748
48539
|
}
|
|
49749
|
-
async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt) {
|
|
48540
|
+
async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt, signal) {
|
|
49750
48541
|
let activeClearTimer = null;
|
|
49751
48542
|
let currentUpstream = upstream;
|
|
49752
48543
|
const coreMessages = [...ctx.messages];
|
|
49753
48544
|
try {
|
|
49754
48545
|
for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
|
|
48546
|
+
if (signal?.aborted) break;
|
|
49755
48547
|
let assistantText = "";
|
|
49756
48548
|
const calls = [];
|
|
49757
48549
|
let usage = {};
|
|
49758
48550
|
let finishReason;
|
|
49759
48551
|
for await (const ev of adapter.parseStream(currentUpstream, round)) {
|
|
48552
|
+
if (signal?.aborted) break;
|
|
49760
48553
|
if (ev.kind === "text") {
|
|
49761
48554
|
assistantText += ev.delta;
|
|
49762
48555
|
if (!ctx.textProtocol && round === 1 && ev.raw) {
|
|
@@ -49804,7 +48597,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49804
48597
|
} catch {
|
|
49805
48598
|
parsedArgs = {};
|
|
49806
48599
|
}
|
|
49807
|
-
const result =
|
|
48600
|
+
const result = executeProxyTool(call.name, parsedArgs, ctx, call.callId);
|
|
49808
48601
|
proxyResults.push({ name: call.name, callId: call.callId, result, arguments: call.arguments });
|
|
49809
48602
|
yield adapter.emitMarker(call.name, result);
|
|
49810
48603
|
} else {
|
|
@@ -49837,7 +48630,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49837
48630
|
if (ctx.textProtocol) {
|
|
49838
48631
|
coreMessages.push({
|
|
49839
48632
|
id: `acp_loop_r${round}_marker_${pr2.callId}`,
|
|
49840
|
-
role: "
|
|
48633
|
+
role: "system",
|
|
49841
48634
|
contentType: "text",
|
|
49842
48635
|
text: buildVisibilityMarker(pr2.name, pr2.result)
|
|
49843
48636
|
});
|
|
@@ -49883,6 +48676,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49883
48676
|
return;
|
|
49884
48677
|
}
|
|
49885
48678
|
ctx.log(`[acp-loop] round ${round} saw mutating proxy tool; re-requesting`);
|
|
48679
|
+
if (signal?.aborted) break;
|
|
49886
48680
|
const newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
|
|
49887
48681
|
if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
|
|
49888
48682
|
try {
|
|
@@ -49894,12 +48688,17 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
49894
48688
|
} catch {
|
|
49895
48689
|
}
|
|
49896
48690
|
}
|
|
49897
|
-
const { response: resp, clearTimer } = await fetchWithTimeout(
|
|
49898
|
-
|
|
49899
|
-
|
|
49900
|
-
|
|
49901
|
-
|
|
49902
|
-
|
|
48691
|
+
const { response: resp, clearTimer } = await fetchWithTimeout(
|
|
48692
|
+
requestOptions.url,
|
|
48693
|
+
{
|
|
48694
|
+
method: "POST",
|
|
48695
|
+
headers: requestOptions.headers,
|
|
48696
|
+
body: JSON.stringify(newBody),
|
|
48697
|
+
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
48698
|
+
},
|
|
48699
|
+
void 0,
|
|
48700
|
+
signal
|
|
48701
|
+
);
|
|
49903
48702
|
if (!resp.ok || !resp.body) {
|
|
49904
48703
|
clearTimer();
|
|
49905
48704
|
const errText = await resp.text().catch(() => "upstream error");
|
|
@@ -49948,13 +48747,13 @@ async function* iterSseEvents(stream2) {
|
|
|
49948
48747
|
reader.releaseLock();
|
|
49949
48748
|
}
|
|
49950
48749
|
}
|
|
49951
|
-
function
|
|
48750
|
+
function extractEventType(rawEvent) {
|
|
49952
48751
|
for (const l of rawEvent.split("\n")) {
|
|
49953
48752
|
if (l.startsWith("event:")) return l.slice(6).trim();
|
|
49954
48753
|
}
|
|
49955
48754
|
return null;
|
|
49956
48755
|
}
|
|
49957
|
-
function
|
|
48756
|
+
function extractDataLine(rawEvent) {
|
|
49958
48757
|
const parts = [];
|
|
49959
48758
|
for (const l of rawEvent.split("\n")) {
|
|
49960
48759
|
if (l.startsWith("data:")) {
|
|
@@ -49965,7 +48764,7 @@ function extractDataLine2(rawEvent) {
|
|
|
49965
48764
|
}
|
|
49966
48765
|
return parts.length ? parts.join("\n") : null;
|
|
49967
48766
|
}
|
|
49968
|
-
function
|
|
48767
|
+
function buildMessageItemSequence(itemId, outputIndex, text) {
|
|
49969
48768
|
const item = { type: "message", id: itemId, role: "assistant", content: [] };
|
|
49970
48769
|
const part = { type: "output_text", text: "" };
|
|
49971
48770
|
const doneItem = {
|
|
@@ -50004,7 +48803,7 @@ data: ${JSON.stringify({ type: "response.output_item.done", output_index: output
|
|
|
50004
48803
|
"utf8"
|
|
50005
48804
|
);
|
|
50006
48805
|
}
|
|
50007
|
-
function
|
|
48806
|
+
function buildFunctionCallEvents(fc, itemId, outputIndex) {
|
|
50008
48807
|
return Buffer.from(
|
|
50009
48808
|
[
|
|
50010
48809
|
`event: response.output_item.added
|
|
@@ -50043,7 +48842,7 @@ data: ${JSON.stringify({
|
|
|
50043
48842
|
"utf8"
|
|
50044
48843
|
);
|
|
50045
48844
|
}
|
|
50046
|
-
function
|
|
48845
|
+
function buildCompleted(responseObj) {
|
|
50047
48846
|
const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
50048
48847
|
return Buffer.from(
|
|
50049
48848
|
`event: response.completed
|
|
@@ -50087,8 +48886,8 @@ function createResponsesAdapter(textProtocol, projection) {
|
|
|
50087
48886
|
async *parseStream(upstream, round) {
|
|
50088
48887
|
const pending = /* @__PURE__ */ new Map();
|
|
50089
48888
|
for await (const eventStr of iterSseEvents(upstream)) {
|
|
50090
|
-
const type =
|
|
50091
|
-
const dataLine =
|
|
48889
|
+
const type = extractEventType(eventStr);
|
|
48890
|
+
const dataLine = extractDataLine(eventStr);
|
|
50092
48891
|
if (!type || !dataLine) continue;
|
|
50093
48892
|
let obj;
|
|
50094
48893
|
try {
|
|
@@ -50186,15 +48985,15 @@ function createResponsesAdapter(textProtocol, projection) {
|
|
|
50186
48985
|
}
|
|
50187
48986
|
},
|
|
50188
48987
|
emitText(delta) {
|
|
50189
|
-
return
|
|
48988
|
+
return buildMessageItemSequence(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
|
|
50190
48989
|
},
|
|
50191
48990
|
emitToolCall(call) {
|
|
50192
|
-
const buf =
|
|
48991
|
+
const buf = buildFunctionCallEvents(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
|
|
50193
48992
|
outputIndex += 1;
|
|
50194
48993
|
return buf;
|
|
50195
48994
|
},
|
|
50196
48995
|
emitMarker(toolName, result) {
|
|
50197
|
-
return
|
|
48996
|
+
return buildMessageItemSequence(
|
|
50198
48997
|
`marker-${Date.now()}-${outputIndex}`,
|
|
50199
48998
|
outputIndex++,
|
|
50200
48999
|
buildVisibilityMarker(toolName, result)
|
|
@@ -50233,7 +49032,7 @@ data: ${JSON.stringify({ type: "response.failed", response: failed })}
|
|
|
50233
49032
|
}
|
|
50234
49033
|
resp = { ...resp, usage };
|
|
50235
49034
|
}
|
|
50236
|
-
return
|
|
49035
|
+
return buildCompleted(resp);
|
|
50237
49036
|
},
|
|
50238
49037
|
emitError(message) {
|
|
50239
49038
|
const resp = {
|
|
@@ -50528,7 +49327,7 @@ async function* iterSseEvents2(stream2) {
|
|
|
50528
49327
|
reader.releaseLock();
|
|
50529
49328
|
}
|
|
50530
49329
|
}
|
|
50531
|
-
function
|
|
49330
|
+
function parseAnthropicSse(eventStr) {
|
|
50532
49331
|
const lines = eventStr.split("\n");
|
|
50533
49332
|
let type = "";
|
|
50534
49333
|
const dataLines = [];
|
|
@@ -50638,7 +49437,7 @@ ${systemPrompt}` : systemPrompt;
|
|
|
50638
49437
|
let usageYielded = false;
|
|
50639
49438
|
const indexMap = /* @__PURE__ */ new Map();
|
|
50640
49439
|
for await (const eventStr of iterSseEvents2(upstream)) {
|
|
50641
|
-
const parsed =
|
|
49440
|
+
const parsed = parseAnthropicSse(eventStr);
|
|
50642
49441
|
if (!parsed) continue;
|
|
50643
49442
|
const { type, data } = parsed;
|
|
50644
49443
|
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
@@ -50766,6 +49565,184 @@ function pickAdapter(protocol, requestBody, textProtocol, responsesProjection, a
|
|
|
50766
49565
|
throw new Error(`[acp-loop] unknown protocol: ${protocol}`);
|
|
50767
49566
|
}
|
|
50768
49567
|
|
|
49568
|
+
// src/compress-loop-responses.ts
|
|
49569
|
+
function extractTextTriggers(text) {
|
|
49570
|
+
const calls = [];
|
|
49571
|
+
let clean = "";
|
|
49572
|
+
let i = 0;
|
|
49573
|
+
let n = 0;
|
|
49574
|
+
while (i < text.length) {
|
|
49575
|
+
const open = text.indexOf(ACP_TEXT_OPEN, i);
|
|
49576
|
+
if (open === -1) {
|
|
49577
|
+
clean += text.slice(i);
|
|
49578
|
+
break;
|
|
49579
|
+
}
|
|
49580
|
+
clean += text.slice(i, open);
|
|
49581
|
+
const after = open + ACP_TEXT_OPEN.length;
|
|
49582
|
+
const close = text.indexOf(ACP_TEXT_CLOSE, after);
|
|
49583
|
+
if (close === -1) {
|
|
49584
|
+
clean += text.slice(open);
|
|
49585
|
+
break;
|
|
49586
|
+
}
|
|
49587
|
+
const payload = text.slice(after, close).trim();
|
|
49588
|
+
if (payload) {
|
|
49589
|
+
const stamp = `${Date.now()}_${n++}`;
|
|
49590
|
+
calls.push({
|
|
49591
|
+
itemId: `fc_text_${stamp}`,
|
|
49592
|
+
callId: `call_text_${stamp}`,
|
|
49593
|
+
name: COMPRESS_TOOL_NAME,
|
|
49594
|
+
arguments: payload
|
|
49595
|
+
});
|
|
49596
|
+
}
|
|
49597
|
+
i = close + ACP_TEXT_CLOSE.length;
|
|
49598
|
+
}
|
|
49599
|
+
return { clean, calls };
|
|
49600
|
+
}
|
|
49601
|
+
function executeProxyTool2(toolName, args, ctx) {
|
|
49602
|
+
if (toolName === "compress") {
|
|
49603
|
+
return applyRanges(parseCompressInput(args), ctx);
|
|
49604
|
+
}
|
|
49605
|
+
if (toolName === "decompress") {
|
|
49606
|
+
return resolveDecompress(args, ctx);
|
|
49607
|
+
}
|
|
49608
|
+
if (toolName === "search_context") {
|
|
49609
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
49610
|
+
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
49611
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
49612
|
+
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
49613
|
+
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
49614
|
+
const lines = blocks.map((b2) => {
|
|
49615
|
+
const topic = b2.topic ?? "(no topic)";
|
|
49616
|
+
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
49617
|
+
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
49618
|
+
${preview}`;
|
|
49619
|
+
});
|
|
49620
|
+
return `Found ${blocks.length} block(s) for "${query}":
|
|
49621
|
+
|
|
49622
|
+
${lines.join("\n\n")}`;
|
|
49623
|
+
}
|
|
49624
|
+
if (toolName === "acp_status") {
|
|
49625
|
+
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
49626
|
+
}
|
|
49627
|
+
return `[Unknown proxy tool: ${toolName}]`;
|
|
49628
|
+
}
|
|
49629
|
+
function responsesJsonOutput(response) {
|
|
49630
|
+
const textParts = [];
|
|
49631
|
+
const calls = [];
|
|
49632
|
+
for (const item of Array.isArray(response.output) ? response.output : []) {
|
|
49633
|
+
if (!item || typeof item !== "object") continue;
|
|
49634
|
+
const value = item;
|
|
49635
|
+
if (value.type === "message") {
|
|
49636
|
+
for (const part of Array.isArray(value.content) ? value.content : []) {
|
|
49637
|
+
if (part && typeof part === "object" && part.type === "output_text") {
|
|
49638
|
+
textParts.push(part);
|
|
49639
|
+
}
|
|
49640
|
+
}
|
|
49641
|
+
} else if (value.type === "function_call") {
|
|
49642
|
+
calls.push({
|
|
49643
|
+
itemId: typeof value.id === "string" ? value.id : "",
|
|
49644
|
+
callId: typeof value.call_id === "string" ? value.call_id : "",
|
|
49645
|
+
name: typeof value.name === "string" ? value.name : "",
|
|
49646
|
+
arguments: typeof value.arguments === "string" ? value.arguments : ""
|
|
49647
|
+
});
|
|
49648
|
+
}
|
|
49649
|
+
}
|
|
49650
|
+
return {
|
|
49651
|
+
text: textParts.map((part) => typeof part.text === "string" ? part.text : "").join(""),
|
|
49652
|
+
textParts,
|
|
49653
|
+
calls
|
|
49654
|
+
};
|
|
49655
|
+
}
|
|
49656
|
+
function replaceResponsesJsonText(parts, text) {
|
|
49657
|
+
parts.forEach((part, index) => {
|
|
49658
|
+
part.text = index === 0 ? text : "";
|
|
49659
|
+
});
|
|
49660
|
+
}
|
|
49661
|
+
function surfaceReadonlyJson(current, proxyCalls, ctx) {
|
|
49662
|
+
const markers = [];
|
|
49663
|
+
for (const call of proxyCalls) {
|
|
49664
|
+
if (MUTATING_PROXY_TOOLS.has(call.name)) continue;
|
|
49665
|
+
let args = {};
|
|
49666
|
+
try {
|
|
49667
|
+
args = JSON.parse(call.arguments);
|
|
49668
|
+
} catch {
|
|
49669
|
+
args = {};
|
|
49670
|
+
}
|
|
49671
|
+
let result;
|
|
49672
|
+
try {
|
|
49673
|
+
result = executeProxyTool2(call.name, args, ctx);
|
|
49674
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49675
|
+
} catch (e) {
|
|
49676
|
+
result = `\u274C [ACP] ${call.name} FAILED: ${String(e)}`;
|
|
49677
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} (read-only) FAILED: ${String(e)}]`);
|
|
49678
|
+
}
|
|
49679
|
+
markers.push(buildVisibilityMarker(call.name, result));
|
|
49680
|
+
}
|
|
49681
|
+
if (markers.length === 0) return current;
|
|
49682
|
+
const out = Array.isArray(current.output) ? [...current.output] : [];
|
|
49683
|
+
out.push({ type: "message", id: `msg_acp_ro_${Date.now()}_${markers.length}`, role: "assistant", content: [{ type: "output_text", text: markers.join("\n") }] });
|
|
49684
|
+
return { ...current, output: out };
|
|
49685
|
+
}
|
|
49686
|
+
async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
|
|
49687
|
+
let current = initialResponse;
|
|
49688
|
+
for (let loopCount = 1; loopCount <= MAX_LOOP_ROUNDS; loopCount++) {
|
|
49689
|
+
const output = responsesJsonOutput(current);
|
|
49690
|
+
const extracted = extractTextTriggers(output.text);
|
|
49691
|
+
const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
|
|
49692
|
+
const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
|
|
49693
|
+
const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
|
|
49694
|
+
const mutatingProxy = proxyCalls.filter((call) => MUTATING_PROXY_TOOLS.has(call.name));
|
|
49695
|
+
if (mutatingProxy.length === 0 || realCalls.length > 0) {
|
|
49696
|
+
if (proxyCalls.length > 0) {
|
|
49697
|
+
replaceResponsesJsonText(output.textParts, extracted.clean);
|
|
49698
|
+
current = surfaceReadonlyJson(current, proxyCalls, ctx);
|
|
49699
|
+
}
|
|
49700
|
+
return current;
|
|
49701
|
+
}
|
|
49702
|
+
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
49703
|
+
if (extracted.clean.trim()) {
|
|
49704
|
+
inputItems.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: extracted.clean }] });
|
|
49705
|
+
}
|
|
49706
|
+
let mutatedThisTurn = false;
|
|
49707
|
+
for (const call of proxyCalls) {
|
|
49708
|
+
let args = {};
|
|
49709
|
+
try {
|
|
49710
|
+
args = JSON.parse(call.arguments);
|
|
49711
|
+
} catch (error) {
|
|
49712
|
+
log("warn", `[acp-compress-args] ${call.name} JSON.parse failed: ${String(error)}`);
|
|
49713
|
+
}
|
|
49714
|
+
let result;
|
|
49715
|
+
if (MUTATING_PROXY_TOOLS.has(call.name) && mutatedThisTurn) {
|
|
49716
|
+
result = `Already ${call.name}ed once this turn. Do not ${call.name} again; generate your normal response now.`;
|
|
49717
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} skipped (state already mutated this turn)]`);
|
|
49718
|
+
} else {
|
|
49719
|
+
result = executeProxyTool2(call.name, args, ctx);
|
|
49720
|
+
if (MUTATING_PROXY_TOOLS.has(call.name)) mutatedThisTurn = true;
|
|
49721
|
+
ctx.log(`[acp-proxy: responses JSON ${call.name} \u2192 ${result.slice(0, 120).replace(/\n/g, " ")}]`);
|
|
49722
|
+
}
|
|
49723
|
+
inputItems.push({ type: "message", role: "developer", content: buildVisibilityMarker(call.name, result) });
|
|
49724
|
+
}
|
|
49725
|
+
requestBody.input = inputItems;
|
|
49726
|
+
const { response, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49727
|
+
method: "POST",
|
|
49728
|
+
headers: requestOptions.headers,
|
|
49729
|
+
body: JSON.stringify(requestBody),
|
|
49730
|
+
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49731
|
+
});
|
|
49732
|
+
try {
|
|
49733
|
+
if (!response.ok) {
|
|
49734
|
+
const detail = await response.text().catch(() => "upstream error");
|
|
49735
|
+
throw new Error(`responses compress loop upstream error ${response.status}: ${detail.slice(0, 200)}`);
|
|
49736
|
+
}
|
|
49737
|
+
current = await response.json();
|
|
49738
|
+
} finally {
|
|
49739
|
+
clearTimer();
|
|
49740
|
+
}
|
|
49741
|
+
}
|
|
49742
|
+
ctx.log(`[acp-proxy: responses JSON compress loop limit (${MAX_LOOP_ROUNDS}) reached]`);
|
|
49743
|
+
return current;
|
|
49744
|
+
}
|
|
49745
|
+
|
|
50769
49746
|
// src/stream-openai.ts
|
|
50770
49747
|
function rewriteOpenaiJsonResponse(body, ctx) {
|
|
50771
49748
|
if (!body || typeof body !== "object") return body;
|
|
@@ -50809,7 +49786,7 @@ ${note}` : note;
|
|
|
50809
49786
|
}
|
|
50810
49787
|
|
|
50811
49788
|
// src/stream-responses.ts
|
|
50812
|
-
var
|
|
49789
|
+
var TEXT_PROTOCOL = process.env.ACP_COMPRESS_PROTOCOL === "text";
|
|
50813
49790
|
function rewriteResponsesJsonResponse(body, ctx) {
|
|
50814
49791
|
if (!body || typeof body !== "object") return body;
|
|
50815
49792
|
const b2 = body;
|
|
@@ -51841,6 +50818,15 @@ var UPSTREAM_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
|
51841
50818
|
// streamed from fetch, otherwise clients try to decompress plain bytes.
|
|
51842
50819
|
"content-encoding"
|
|
51843
50820
|
]);
|
|
50821
|
+
function buildForwardHeaders(headers) {
|
|
50822
|
+
const out = {};
|
|
50823
|
+
for (const [k2, v2] of Object.entries(headers)) {
|
|
50824
|
+
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
50825
|
+
out[k2] = v2;
|
|
50826
|
+
}
|
|
50827
|
+
out["content-type"] = "application/json";
|
|
50828
|
+
return out;
|
|
50829
|
+
}
|
|
51844
50830
|
function resolveUpstream(_opts, reqUrl, req) {
|
|
51845
50831
|
const mitmUpstream = readMitmUpstream(req?.socket);
|
|
51846
50832
|
if (mitmUpstream) {
|
|
@@ -52139,15 +51125,15 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
52139
51125
|
});
|
|
52140
51126
|
const clientLabel = responsesIdentity?.clientProvided ? responsesIdentity.value : clientConversationHeader(req.headers);
|
|
52141
51127
|
const session = getSession(sessionId, { protocol, upstreamOrigin, label: clientLabel ?? void 0 });
|
|
52142
|
-
|
|
52143
|
-
|
|
52144
|
-
|
|
52145
|
-
|
|
51128
|
+
acquireInFlight(session);
|
|
51129
|
+
try {
|
|
51130
|
+
await withSessionLock(session, async () => {
|
|
51131
|
+
prepared = countTokens ? prepareCountTokens(parsed, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(parsed, req, opts, core, reqConfig, log2, session) : protocol === "openai" ? prepareOpenai(parsed, req, opts, core, reqConfig, log2, session) : responsesCompact ? prepareResponsesCompact(bodyBuffer, parsed, session) : prepareResponses(parsed, req, opts, core, reqConfig, log2, session, responsesIdentity);
|
|
52146
51132
|
await forward(req, res, opts, prepared.body, prepared, core, reqConfig, log2, route, affinity);
|
|
52147
|
-
}
|
|
52148
|
-
|
|
52149
|
-
|
|
52150
|
-
}
|
|
51133
|
+
});
|
|
51134
|
+
} finally {
|
|
51135
|
+
releaseInFlight(session);
|
|
51136
|
+
}
|
|
52151
51137
|
}
|
|
52152
51138
|
if (!prepared) {
|
|
52153
51139
|
if (protocol === null && !opts.passthrough) {
|
|
@@ -52187,6 +51173,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52187
51173
|
++session.stats.requests;
|
|
52188
51174
|
let processedMessages = [];
|
|
52189
51175
|
let originalMessages = [];
|
|
51176
|
+
let nudge;
|
|
52190
51177
|
let rebuiltMessages = parsed.messages;
|
|
52191
51178
|
let systemOut = parsed.system;
|
|
52192
51179
|
let toolsOut = parsed.tools;
|
|
@@ -52197,6 +51184,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52197
51184
|
const tokenCount = session.stats.lastInputTokens;
|
|
52198
51185
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
|
|
52199
51186
|
session.state = turn.state;
|
|
51187
|
+
nudge = turn.nudge;
|
|
52200
51188
|
session.stats.contextTokens = tokenCount;
|
|
52201
51189
|
if (!session.meta.title) {
|
|
52202
51190
|
const t = deriveTitle(msgs);
|
|
@@ -52226,7 +51214,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
52226
51214
|
}
|
|
52227
51215
|
const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
|
|
52228
51216
|
markDirty(session);
|
|
52229
|
-
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
|
|
51217
|
+
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool, nudge };
|
|
52230
51218
|
}
|
|
52231
51219
|
function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
52232
51220
|
const sessionId = session.id;
|
|
@@ -52234,6 +51222,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52234
51222
|
++session.stats.requests;
|
|
52235
51223
|
let processedMessages = [];
|
|
52236
51224
|
let originalMessages = [];
|
|
51225
|
+
let nudge;
|
|
52237
51226
|
let rebuiltMessages = parsed.messages;
|
|
52238
51227
|
let toolsOut = parsed.tools;
|
|
52239
51228
|
const maxTokens = typeof parsed.max_tokens === "number" ? parsed.max_tokens : 8192;
|
|
@@ -52245,6 +51234,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52245
51234
|
const tokenCount = session.stats.lastInputTokens;
|
|
52246
51235
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: "text-only" });
|
|
52247
51236
|
session.state = turn.state;
|
|
51237
|
+
nudge = turn.nudge;
|
|
52248
51238
|
session.stats.contextTokens = tokenCount;
|
|
52249
51239
|
if (!session.meta.title) {
|
|
52250
51240
|
const t = deriveTitle(msgs);
|
|
@@ -52279,7 +51269,7 @@ function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
|
52279
51269
|
rebuilt.stream_options = { include_usage: true };
|
|
52280
51270
|
}
|
|
52281
51271
|
markDirty(session);
|
|
52282
|
-
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: shouldInject };
|
|
51272
|
+
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: shouldInject, nudge };
|
|
52283
51273
|
}
|
|
52284
51274
|
function prepareResponses(parsed, req, opts, core, config, log2, session, identity) {
|
|
52285
51275
|
const sessionId = session.id;
|
|
@@ -52290,6 +51280,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52290
51280
|
}
|
|
52291
51281
|
let processedMessages = [];
|
|
52292
51282
|
let originalMessages = [];
|
|
51283
|
+
let nudge;
|
|
52293
51284
|
let responsesProjection;
|
|
52294
51285
|
let rebuiltInput = parsed.input;
|
|
52295
51286
|
let toolsOut = parsed.tools;
|
|
@@ -52306,6 +51297,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52306
51297
|
const tokenCount = session.stats.lastInputTokens;
|
|
52307
51298
|
const turn = core.processTurn({ messages: msgs, state: session.state, config, tokenCount, renderTags: process.env.ACP_RENDER_NONE ? "none" : "text-only" });
|
|
52308
51299
|
session.state = turn.state;
|
|
51300
|
+
nudge = turn.nudge;
|
|
52309
51301
|
session.stats.contextTokens = tokenCount;
|
|
52310
51302
|
if (!session.meta.title) {
|
|
52311
51303
|
const t = deriveTitle(msgs);
|
|
@@ -52367,7 +51359,8 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
52367
51359
|
protocol: "responses",
|
|
52368
51360
|
stream: stream2,
|
|
52369
51361
|
compressInjected: shouldInject,
|
|
52370
|
-
responsesTextProtocol
|
|
51362
|
+
responsesTextProtocol,
|
|
51363
|
+
nudge
|
|
52371
51364
|
};
|
|
52372
51365
|
}
|
|
52373
51366
|
function isCountTokensRequest(method, urlPath, hasBody) {
|
|
@@ -52610,105 +51603,39 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52610
51603
|
dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
|
|
52611
51604
|
}
|
|
52612
51605
|
try {
|
|
52613
|
-
|
|
52614
|
-
|
|
52615
|
-
|
|
52616
|
-
|
|
52617
|
-
|
|
52618
|
-
|
|
52619
|
-
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52624
|
-
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52628
|
-
|
|
52629
|
-
|
|
52630
|
-
|
|
52631
|
-
|
|
52632
|
-
|
|
52633
|
-
|
|
52634
|
-
|
|
52635
|
-
|
|
52636
|
-
log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
|
|
52637
|
-
}
|
|
52638
|
-
}
|
|
52639
|
-
res.write(chunk);
|
|
52640
|
-
if (res.writableNeedDrain) await new Promise((r) => res.once("drain", () => r()));
|
|
52641
|
-
}
|
|
52642
|
-
res.end();
|
|
52643
|
-
} else if (prepared.protocol === "openai") {
|
|
52644
|
-
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52645
|
-
const reqHeaders = {};
|
|
52646
|
-
for (const [k2, v2] of Object.entries(headers)) {
|
|
52647
|
-
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
52648
|
-
reqHeaders[k2] = v2;
|
|
52649
|
-
}
|
|
52650
|
-
reqHeaders["content-type"] = "application/json";
|
|
52651
|
-
const loop = compressLoopStream(
|
|
52652
|
-
streamToRead,
|
|
52653
|
-
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl },
|
|
52654
|
-
parsedReq,
|
|
52655
|
-
{ url: upstreamUrl, headers: reqHeaders }
|
|
52656
|
-
);
|
|
52657
|
-
for await (const chunk of loop) {
|
|
52658
|
-
{
|
|
52659
|
-
const s3 = chunk.toString("utf8");
|
|
52660
|
-
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52661
|
-
log2("warn", `[${prepared.session.id}] tag echo: openai response stream contains <acp tag`);
|
|
52662
|
-
}
|
|
52663
|
-
}
|
|
52664
|
-
if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
|
|
52665
|
-
}
|
|
52666
|
-
} else if (prepared.protocol === "responses") {
|
|
52667
|
-
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52668
|
-
const reqHeaders = {};
|
|
52669
|
-
for (const [k2, v2] of Object.entries(headers)) {
|
|
52670
|
-
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
52671
|
-
reqHeaders[k2] = v2;
|
|
52672
|
-
}
|
|
52673
|
-
reqHeaders["content-type"] = "application/json";
|
|
52674
|
-
const loop = compressLoopResponsesStream(
|
|
52675
|
-
streamToRead,
|
|
52676
|
-
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: prepared.responsesTextProtocol },
|
|
52677
|
-
parsedReq,
|
|
52678
|
-
{ url: upstreamUrl, headers: reqHeaders }
|
|
52679
|
-
);
|
|
52680
|
-
for await (const chunk of loop) {
|
|
52681
|
-
{
|
|
52682
|
-
const s3 = chunk.toString("utf8");
|
|
52683
|
-
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52684
|
-
log2("warn", `[${prepared.session.id}] tag echo: responses response stream contains <acp tag`);
|
|
52685
|
-
}
|
|
51606
|
+
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
51607
|
+
const reqHeaders = buildForwardHeaders(headers);
|
|
51608
|
+
const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
|
|
51609
|
+
const systemPrompt = textProtocol ? buildCompressTextSystemPrompt() : buildCompressSystemPrompt();
|
|
51610
|
+
const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem);
|
|
51611
|
+
const abortCtrl = new AbortController();
|
|
51612
|
+
req.on("close", () => {
|
|
51613
|
+
if (!res.writableEnded) abortCtrl.abort();
|
|
51614
|
+
});
|
|
51615
|
+
const loop = runCompressLoop(
|
|
51616
|
+
streamToRead,
|
|
51617
|
+
{ core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol, debug: opts.debug, nudge: prepared.nudge },
|
|
51618
|
+
parsedReq,
|
|
51619
|
+
{ url: upstreamUrl, headers: reqHeaders },
|
|
51620
|
+
adapter,
|
|
51621
|
+
systemPrompt,
|
|
51622
|
+
abortCtrl.signal
|
|
51623
|
+
);
|
|
51624
|
+
for await (const chunk of loop) {
|
|
51625
|
+
{
|
|
51626
|
+
const s3 = chunk.toString("utf8");
|
|
51627
|
+
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
51628
|
+
log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
|
|
52686
51629
|
}
|
|
52687
|
-
if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
|
|
52688
51630
|
}
|
|
52689
|
-
|
|
52690
|
-
|
|
52691
|
-
|
|
52692
|
-
|
|
52693
|
-
|
|
52694
|
-
|
|
52695
|
-
}
|
|
52696
|
-
reqHeaders["content-type"] = "application/json";
|
|
52697
|
-
const loop = compressLoopAnthropicStream(
|
|
52698
|
-
streamToRead,
|
|
52699
|
-
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl },
|
|
52700
|
-
parsedReq,
|
|
52701
|
-
{ url: upstreamUrl, headers: reqHeaders }
|
|
52702
|
-
);
|
|
52703
|
-
for await (const chunk of loop) {
|
|
52704
|
-
{
|
|
52705
|
-
const s3 = chunk.toString("utf8");
|
|
52706
|
-
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
52707
|
-
log2("warn", `[${prepared.session.id}] tag echo: anthropic response stream contains <acp tag`);
|
|
52708
|
-
}
|
|
52709
|
-
}
|
|
52710
|
-
if (!res.write(chunk)) await new Promise((r) => res.once("drain", () => r()));
|
|
51631
|
+
res.write(chunk);
|
|
51632
|
+
if (res.writableNeedDrain) {
|
|
51633
|
+
await Promise.race([
|
|
51634
|
+
new Promise((r) => res.once("drain", () => r())),
|
|
51635
|
+
new Promise((r) => res.once("close", () => r()))
|
|
51636
|
+
]);
|
|
52711
51637
|
}
|
|
51638
|
+
if (res.destroyed || res.writableEnded) break;
|
|
52712
51639
|
}
|
|
52713
51640
|
res.end();
|
|
52714
51641
|
} catch (e) {
|
|
@@ -52725,12 +51652,7 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
52725
51652
|
let json = JSON.parse(text);
|
|
52726
51653
|
if (prepared.protocol === "responses" && prepared.responsesTextProtocol) {
|
|
52727
51654
|
const requestBody = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52728
|
-
const requestHeaders =
|
|
52729
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
52730
|
-
if (key.toLowerCase() === "content-length" || key.toLowerCase() === "host") continue;
|
|
52731
|
-
requestHeaders[key] = value;
|
|
52732
|
-
}
|
|
52733
|
-
requestHeaders["content-type"] = "application/json";
|
|
51655
|
+
const requestHeaders = buildForwardHeaders(headers);
|
|
52734
51656
|
json = await compressLoopResponsesJson(
|
|
52735
51657
|
json,
|
|
52736
51658
|
{ core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: true },
|
|
@@ -52867,6 +51789,7 @@ function readBody(req) {
|
|
|
52867
51789
|
size += c.length;
|
|
52868
51790
|
if (size > MAX_REQUEST_BYTES) {
|
|
52869
51791
|
aborted = true;
|
|
51792
|
+
req.destroy();
|
|
52870
51793
|
reject(new BodyTooLargeError(MAX_REQUEST_BYTES));
|
|
52871
51794
|
return;
|
|
52872
51795
|
}
|