billion-context 0.1.31 → 0.1.33
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 +1372 -72
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -43930,14 +43930,49 @@ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, opt
|
|
|
43930
43930
|
return { messages: updated, truncatedCount, savedTokens };
|
|
43931
43931
|
}
|
|
43932
43932
|
var KEEP_LAST_ORPHANED = 0;
|
|
43933
|
+
function rangeKey(startRef, endRef) {
|
|
43934
|
+
return `${startRef}::${endRef}`;
|
|
43935
|
+
}
|
|
43936
|
+
function rewriteCompressText(text, liveKeys) {
|
|
43937
|
+
let parsed;
|
|
43938
|
+
try {
|
|
43939
|
+
parsed = JSON.parse(text ?? "");
|
|
43940
|
+
} catch {
|
|
43941
|
+
return null;
|
|
43942
|
+
}
|
|
43943
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
43944
|
+
const obj = parsed;
|
|
43945
|
+
const content = obj.content;
|
|
43946
|
+
if (!Array.isArray(content) || content.length === 0) return null;
|
|
43947
|
+
const kept = content.filter((entry) => {
|
|
43948
|
+
if (!entry || typeof entry !== "object") return false;
|
|
43949
|
+
const s3 = typeof entry.startId === "string" ? entry.startId : typeof entry.messageId === "string" ? entry.messageId : "";
|
|
43950
|
+
const e = typeof entry.endId === "string" ? entry.endId : typeof entry.messageId === "string" ? entry.messageId : "";
|
|
43951
|
+
return liveKeys.has(rangeKey(s3, e));
|
|
43952
|
+
});
|
|
43953
|
+
if (kept.length === content.length || kept.length === 0) return null;
|
|
43954
|
+
return JSON.stringify({ ...obj, content: kept });
|
|
43955
|
+
}
|
|
43933
43956
|
function hideConsumedCompressCalls(state, messages) {
|
|
43934
|
-
const activeCallIds = /* @__PURE__ */ new Set();
|
|
43935
43957
|
const allBlockCallIds = /* @__PURE__ */ new Set();
|
|
43958
|
+
const activeCallIds = /* @__PURE__ */ new Set();
|
|
43959
|
+
const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
|
|
43960
|
+
const legacyLiveByCallId = /* @__PURE__ */ new Set();
|
|
43936
43961
|
for (const block of state.blocks) {
|
|
43937
|
-
if (block.compressCallId)
|
|
43938
|
-
|
|
43939
|
-
|
|
43962
|
+
if (!block.compressCallId) continue;
|
|
43963
|
+
allBlockCallIds.add(block.compressCallId);
|
|
43964
|
+
if (!block.active) continue;
|
|
43965
|
+
activeCallIds.add(block.compressCallId);
|
|
43966
|
+
if (block.startRef === void 0 || block.endRef === void 0) {
|
|
43967
|
+
legacyLiveByCallId.add(block.compressCallId);
|
|
43968
|
+
continue;
|
|
43940
43969
|
}
|
|
43970
|
+
let keys = liveRangeKeysByCallId.get(block.compressCallId);
|
|
43971
|
+
if (!keys) {
|
|
43972
|
+
keys = /* @__PURE__ */ new Set();
|
|
43973
|
+
liveRangeKeysByCallId.set(block.compressCallId, keys);
|
|
43974
|
+
}
|
|
43975
|
+
keys.add(rangeKey(block.startRef, block.endRef));
|
|
43941
43976
|
}
|
|
43942
43977
|
const lastOrphanedCallIds = [];
|
|
43943
43978
|
for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
|
|
@@ -43966,6 +44001,16 @@ function hideConsumedCompressCalls(state, messages) {
|
|
|
43966
44001
|
hidden++;
|
|
43967
44002
|
continue;
|
|
43968
44003
|
}
|
|
44004
|
+
if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
|
|
44005
|
+
const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);
|
|
44006
|
+
if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {
|
|
44007
|
+
const rewritten = rewriteCompressText(message.text, liveKeys);
|
|
44008
|
+
if (rewritten !== null) {
|
|
44009
|
+
result.push({ ...message, text: rewritten });
|
|
44010
|
+
continue;
|
|
44011
|
+
}
|
|
44012
|
+
}
|
|
44013
|
+
}
|
|
43969
44014
|
result.push(message);
|
|
43970
44015
|
}
|
|
43971
44016
|
return { messages: result, hidden };
|
|
@@ -44199,8 +44244,8 @@ function refNum(ref) {
|
|
|
44199
44244
|
const n = parseInt(ref.slice(1), 10);
|
|
44200
44245
|
return Number.isNaN(n) ? -1 : n;
|
|
44201
44246
|
}
|
|
44202
|
-
function
|
|
44203
|
-
return Math.ceil(
|
|
44247
|
+
function estimateTextTokens(text) {
|
|
44248
|
+
return Math.ceil(text.length / 4);
|
|
44204
44249
|
}
|
|
44205
44250
|
function isToolMessage(message) {
|
|
44206
44251
|
return message.contentType === "tool-call" || message.contentType === "tool-result";
|
|
@@ -44212,7 +44257,7 @@ function isSyntheticOrPruned(message, state) {
|
|
|
44212
44257
|
}
|
|
44213
44258
|
return false;
|
|
44214
44259
|
}
|
|
44215
|
-
function computeProtectedRefs(messages, state, config) {
|
|
44260
|
+
function computeProtectedRefs(messages, state, config, countTokens = estimateTextTokens) {
|
|
44216
44261
|
const preserveN = config.preserveRecentMessages;
|
|
44217
44262
|
const preserveTokens = config.preserveRecentTokens;
|
|
44218
44263
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -44222,7 +44267,7 @@ function computeProtectedRefs(messages, state, config) {
|
|
|
44222
44267
|
if (isNeverPreserveRecent(msg2)) continue;
|
|
44223
44268
|
const ref = state.messageRefs.byRaw[msg2.id];
|
|
44224
44269
|
if (!ref || ref === "BLOCKED") continue;
|
|
44225
|
-
visible.push({ ref, tokens:
|
|
44270
|
+
visible.push({ ref, tokens: countTokens(msg2.text ?? "") });
|
|
44226
44271
|
}
|
|
44227
44272
|
if (preserveN > 0) {
|
|
44228
44273
|
for (const m2 of visible.slice(-preserveN)) {
|
|
@@ -44247,7 +44292,7 @@ function computeProtectedRefs(messages, state, config) {
|
|
|
44247
44292
|
}
|
|
44248
44293
|
return result;
|
|
44249
44294
|
}
|
|
44250
|
-
function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
|
|
44295
|
+
function buildCompressibleRanges(messages, state, config, protectedZoneRefs, countTokens = estimateTextTokens) {
|
|
44251
44296
|
const compressibleMsgs = [];
|
|
44252
44297
|
const protectedMsgs = [];
|
|
44253
44298
|
const protectedCallIds = collectProtectedToolCallIds(messages, config);
|
|
@@ -44260,7 +44305,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
|
|
|
44260
44305
|
protectedMsgs.push({
|
|
44261
44306
|
ref,
|
|
44262
44307
|
refNum: rn2,
|
|
44263
|
-
tokens:
|
|
44308
|
+
tokens: countTokens(msg2.text ?? ""),
|
|
44264
44309
|
tools: msg2.toolName ? [msg2.toolName] : []
|
|
44265
44310
|
});
|
|
44266
44311
|
continue;
|
|
@@ -44271,7 +44316,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
|
|
|
44271
44316
|
compressibleMsgs.push({
|
|
44272
44317
|
ref,
|
|
44273
44318
|
refNum: rn2,
|
|
44274
|
-
tokens:
|
|
44319
|
+
tokens: countTokens(msg2.text ?? ""),
|
|
44275
44320
|
isTool: isToolMessage(msg2),
|
|
44276
44321
|
isUser: msg2.role === "user"
|
|
44277
44322
|
});
|
|
@@ -44358,7 +44403,7 @@ function createCore(ports = {}) {
|
|
|
44358
44403
|
let tokensCompressed = 0;
|
|
44359
44404
|
const errors = [];
|
|
44360
44405
|
const warnings = [];
|
|
44361
|
-
const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config);
|
|
44406
|
+
const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config, countTokens);
|
|
44362
44407
|
const preExistingCoverage = collectCoverage(state);
|
|
44363
44408
|
const rangeIndexSets = [];
|
|
44364
44409
|
for (const spec of input.ranges) {
|
|
@@ -44383,29 +44428,25 @@ function createCore(ports = {}) {
|
|
|
44383
44428
|
const bMin = b2.indices.length > 0 ? Math.min(...b2.indices) : Infinity;
|
|
44384
44429
|
return aMin - bMin;
|
|
44385
44430
|
});
|
|
44386
|
-
|
|
44387
|
-
|
|
44388
|
-
|
|
44389
|
-
const
|
|
44390
|
-
const
|
|
44391
|
-
if (
|
|
44392
|
-
|
|
44393
|
-
|
|
44394
|
-
|
|
44395
|
-
|
|
44396
|
-
|
|
44397
|
-
errors: [
|
|
44398
|
-
`content: range (${prev.spec.startRef}..${prev.spec.endRef}) overlaps (${curr.spec.startRef}..${curr.spec.endRef}). Overlapping ranges cannot be compressed in the same batch.`
|
|
44399
|
-
],
|
|
44400
|
-
warnings: []
|
|
44401
|
-
}
|
|
44402
|
-
};
|
|
44431
|
+
const skipSpecs = /* @__PURE__ */ new Set();
|
|
44432
|
+
let acceptedMaxIndex = -1;
|
|
44433
|
+
for (const entry of sortedRanges) {
|
|
44434
|
+
const entryMax = entry.indices.length > 0 ? Math.max(...entry.indices) : -1;
|
|
44435
|
+
const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;
|
|
44436
|
+
if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {
|
|
44437
|
+
skipSpecs.add(entry.spec);
|
|
44438
|
+
warnings.push(
|
|
44439
|
+
`Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) \u2014 overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`
|
|
44440
|
+
);
|
|
44441
|
+
continue;
|
|
44403
44442
|
}
|
|
44443
|
+
if (entryMax > acceptedMaxIndex) acceptedMaxIndex = entryMax;
|
|
44404
44444
|
}
|
|
44405
44445
|
if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
|
|
44406
44446
|
let totalRangeChars = 0;
|
|
44407
44447
|
let hasBlockBoundaryRange = false;
|
|
44408
44448
|
for (const spec of input.ranges) {
|
|
44449
|
+
if (skipSpecs.has(spec)) continue;
|
|
44409
44450
|
let resolved;
|
|
44410
44451
|
try {
|
|
44411
44452
|
resolved = resolveBoundaries({
|
|
@@ -44441,6 +44482,7 @@ function createCore(ports = {}) {
|
|
|
44441
44482
|
}
|
|
44442
44483
|
}
|
|
44443
44484
|
for (const spec of input.ranges) {
|
|
44485
|
+
if (skipSpecs.has(spec)) continue;
|
|
44444
44486
|
try {
|
|
44445
44487
|
const outcome = applySingleRange({
|
|
44446
44488
|
spec,
|
|
@@ -44581,13 +44623,15 @@ var recommendNode = {
|
|
|
44581
44623
|
const protectedRefs = computeProtectedRefs(
|
|
44582
44624
|
io2.messages,
|
|
44583
44625
|
io2.state,
|
|
44584
|
-
ctx.config
|
|
44626
|
+
ctx.config,
|
|
44627
|
+
ctx.countTokens
|
|
44585
44628
|
);
|
|
44586
44629
|
const contextRanges = buildCompressibleRanges(
|
|
44587
44630
|
io2.messages,
|
|
44588
44631
|
io2.state,
|
|
44589
44632
|
ctx.config,
|
|
44590
|
-
protectedRefs
|
|
44633
|
+
protectedRefs,
|
|
44634
|
+
ctx.countTokens
|
|
44591
44635
|
);
|
|
44592
44636
|
const nothingToCompress = contextRanges.compressible.length === 0;
|
|
44593
44637
|
const recommendation = {
|
|
@@ -44765,7 +44809,9 @@ function applySingleRange(input) {
|
|
|
44765
44809
|
survivedCount: 0,
|
|
44766
44810
|
generation: "young",
|
|
44767
44811
|
active: true,
|
|
44768
|
-
compressCallId: input.spec.compressCallId
|
|
44812
|
+
compressCallId: input.spec.compressCallId,
|
|
44813
|
+
startRef: input.spec.startRef,
|
|
44814
|
+
endRef: input.spec.endRef
|
|
44769
44815
|
};
|
|
44770
44816
|
input.state.blocks.push(block);
|
|
44771
44817
|
for (const consumedId of consumedBlockIds) {
|
|
@@ -45256,7 +45302,7 @@ function renderNudgeText(decision) {
|
|
|
45256
45302
|
breakdownStr,
|
|
45257
45303
|
"",
|
|
45258
45304
|
`[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`,
|
|
45259
|
-
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries.`,
|
|
45305
|
+
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
|
|
45260
45306
|
blockList,
|
|
45261
45307
|
`Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
|
|
45262
45308
|
"",
|
|
@@ -46420,7 +46466,6 @@ function rejectLegacyRoute(key, value) {
|
|
|
46420
46466
|
// src/server.ts
|
|
46421
46467
|
import http from "http";
|
|
46422
46468
|
import fs3 from "fs";
|
|
46423
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
46424
46469
|
|
|
46425
46470
|
// src/registry.ts
|
|
46426
46471
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -47698,9 +47743,15 @@ async function flushAllSessions() {
|
|
|
47698
47743
|
var COMPRESS_TOOL_NAME = "compress";
|
|
47699
47744
|
var ACP_TEXT_OPEN = "<acp_compress>";
|
|
47700
47745
|
var ACP_TEXT_CLOSE = "</acp_compress>";
|
|
47746
|
+
var ACP_STATUS_OPEN = "<acp_status>";
|
|
47747
|
+
var ACP_STATUS_CLOSE = "</acp_status>";
|
|
47748
|
+
var ACP_SEARCH_OPEN = "<acp_search>";
|
|
47749
|
+
var ACP_SEARCH_CLOSE = "</acp_search>";
|
|
47750
|
+
var ACP_DECOMPRESS_OPEN = "<acp_decompress>";
|
|
47751
|
+
var ACP_DECOMPRESS_CLOSE = "</acp_decompress>";
|
|
47701
47752
|
var COMPRESS_TOOL = {
|
|
47702
47753
|
name: COMPRESS_TOOL_NAME,
|
|
47703
|
-
description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}].",
|
|
47754
|
+
description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}]. REQUIRED \u2014 compress without content is invalid.",
|
|
47704
47755
|
input_schema: {
|
|
47705
47756
|
type: "object",
|
|
47706
47757
|
properties: {
|
|
@@ -47719,23 +47770,23 @@ var COMPRESS_TOOL = {
|
|
|
47719
47770
|
required: ["startId", "endId", "summary"]
|
|
47720
47771
|
}
|
|
47721
47772
|
}
|
|
47722
|
-
}
|
|
47773
|
+
},
|
|
47774
|
+
required: ["content"]
|
|
47723
47775
|
}
|
|
47724
47776
|
};
|
|
47725
|
-
function parseCompressInput(input) {
|
|
47777
|
+
function parseCompressInput(input, callId) {
|
|
47726
47778
|
if (!input || typeof input !== "object") {
|
|
47727
47779
|
log("warn", `[acp-compress-input] rejected: not object (${typeof input})`);
|
|
47728
47780
|
return [];
|
|
47729
47781
|
}
|
|
47730
47782
|
const obj = input;
|
|
47731
|
-
if (Array.isArray(obj.content)) {
|
|
47732
|
-
const out = obj.content.map((r) => toRange(r)).filter((r) => r !== null);
|
|
47733
|
-
if (out.length === 0) log("warn", `[acp-compress-input] content array but 0 valid ranges. keys per item: ${obj.content.map((c) => Object.keys(c ?? {}).join(",")).join(" | ")}`);
|
|
47734
|
-
return out;
|
|
47735
|
-
}
|
|
47736
47783
|
const single = toRange(obj);
|
|
47737
|
-
|
|
47738
|
-
|
|
47784
|
+
const ranges = Array.isArray(obj.content) ? obj.content.map((r) => toRange(r)).filter((r) => r !== null) : single ? [single] : [];
|
|
47785
|
+
if (ranges.length === 0) {
|
|
47786
|
+
log("warn", `[acp-compress-input] parsed 0 valid ranges. top keys: ${Object.keys(obj).join(",")}`);
|
|
47787
|
+
}
|
|
47788
|
+
if (callId) for (const r of ranges) r.compressCallId = callId;
|
|
47789
|
+
return ranges;
|
|
47739
47790
|
}
|
|
47740
47791
|
function toRange(r) {
|
|
47741
47792
|
const startRef = pick(r, "startId", "startRef");
|
|
@@ -47764,7 +47815,7 @@ var COMPRESS_TOOL_OPENAI = {
|
|
|
47764
47815
|
topic: { type: "string", description: "Optional short title for the compressed range" },
|
|
47765
47816
|
content: {
|
|
47766
47817
|
type: "array",
|
|
47767
|
-
description: "One or more ranges to compress into separate summary blocks",
|
|
47818
|
+
description: "One or more ranges to compress into separate summary blocks. REQUIRED \u2014 compress without content is invalid.",
|
|
47768
47819
|
items: {
|
|
47769
47820
|
type: "object",
|
|
47770
47821
|
properties: {
|
|
@@ -47776,7 +47827,8 @@ var COMPRESS_TOOL_OPENAI = {
|
|
|
47776
47827
|
required: ["startId", "endId", "summary"]
|
|
47777
47828
|
}
|
|
47778
47829
|
}
|
|
47779
|
-
}
|
|
47830
|
+
},
|
|
47831
|
+
required: ["content"]
|
|
47780
47832
|
}
|
|
47781
47833
|
}
|
|
47782
47834
|
};
|
|
@@ -47826,7 +47878,29 @@ Rules for the trigger:
|
|
|
47826
47878
|
- JSON shape matches the compress tool: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
|
|
47827
47879
|
- After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
|
|
47828
47880
|
- Do NOT wrap the marker in code fences, quotes, or commentary.
|
|
47829
|
-
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large
|
|
47881
|
+
- NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.
|
|
47882
|
+
|
|
47883
|
+
ACP TOOLS (TEXT TRIGGERS)
|
|
47884
|
+
|
|
47885
|
+
Since host tools cannot coexist with a declared tools field, ALL ACP tools use text triggers. Emit the marker; the proxy intercepts and executes it; the marker is stripped from what the user sees.
|
|
47886
|
+
|
|
47887
|
+
1. acp_status \u2014 view context usage, compression state, and compressible ranges:
|
|
47888
|
+
${ACP_STATUS_OPEN}${ACP_STATUS_CLOSE}
|
|
47889
|
+
No payload needed. Use this FIRST when unsure about context state.
|
|
47890
|
+
|
|
47891
|
+
2. search_context \u2014 search compressed block summaries by keyword:
|
|
47892
|
+
${ACP_SEARCH_OPEN}{"query":"auth token refresh"}${ACP_SEARCH_CLOSE}
|
|
47893
|
+
Use when you need details that may have been compressed away.
|
|
47894
|
+
|
|
47895
|
+
3. decompress \u2014 restore compressed content for exact details:
|
|
47896
|
+
${ACP_DECOMPRESS_OPEN}{"blockId":"b5"}${ACP_DECOMPRESS_CLOSE}
|
|
47897
|
+
Optional: {"blockId":"b5","toFile":"/tmp/b5.txt"} to write to file instead.
|
|
47898
|
+
Optional: {"blockId":"b5","full":true} to restore all the way to original messages.
|
|
47899
|
+
|
|
47900
|
+
Rules for ALL triggers:
|
|
47901
|
+
- Output on its own, NO surrounding prose. Just the raw marker.
|
|
47902
|
+
- After emitting, STOP your turn. The proxy executes and returns the result.
|
|
47903
|
+
- Do NOT wrap in code fences, quotes, or commentary.`;
|
|
47830
47904
|
}
|
|
47831
47905
|
var DECOMPRESS_TOOL_NAME = "decompress";
|
|
47832
47906
|
var DECOMPRESS_TOOL_OPENAI = {
|
|
@@ -47936,6 +48010,14 @@ var PROXY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
47936
48010
|
SEARCH_CONTEXT_TOOL_NAME,
|
|
47937
48011
|
ACP_STATUS_TOOL_NAME
|
|
47938
48012
|
]);
|
|
48013
|
+
var MUTATING_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
48014
|
+
COMPRESS_TOOL_NAME,
|
|
48015
|
+
DECOMPRESS_TOOL_NAME
|
|
48016
|
+
]);
|
|
48017
|
+
var READONLY_PROXY_TOOLS = /* @__PURE__ */ new Set([
|
|
48018
|
+
SEARCH_CONTEXT_TOOL_NAME,
|
|
48019
|
+
ACP_STATUS_TOOL_NAME
|
|
48020
|
+
]);
|
|
47939
48021
|
|
|
47940
48022
|
// src/decompress-shared.ts
|
|
47941
48023
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -48595,7 +48677,9 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
|
|
|
48595
48677
|
}).filter((tc) => tc.name.length > 0);
|
|
48596
48678
|
const proxyCalls = toolCalls.filter((tc) => PROXY_TOOL_NAMES.has(tc.name));
|
|
48597
48679
|
const realCalls = toolCalls.filter((tc) => !PROXY_TOOL_NAMES.has(tc.name));
|
|
48598
|
-
const
|
|
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;
|
|
48599
48683
|
if (usage) {
|
|
48600
48684
|
const prompt = usage.prompt_tokens ?? usage.input_tokens;
|
|
48601
48685
|
const det = usage.prompt_tokens_details ?? usage.prompt_cache_hit_tokens;
|
|
@@ -48611,7 +48695,27 @@ async function* compressLoopStream(initialUpstream, ctx, requestBody, requestOpt
|
|
|
48611
48695
|
ctx.session.stats.cacheSamples += 1;
|
|
48612
48696
|
}
|
|
48613
48697
|
}
|
|
48614
|
-
if (!
|
|
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
|
+
}
|
|
48615
48719
|
for (const tc of realCalls) {
|
|
48616
48720
|
yield Buffer.from(buildToolCallSse(makeBase(), tc), "utf8");
|
|
48617
48721
|
}
|
|
@@ -48867,8 +48971,23 @@ async function* compressLoopAnthropicStream(initialUpstream, ctx, requestBody, r
|
|
|
48867
48971
|
}
|
|
48868
48972
|
clientIndex = state.clientIndex;
|
|
48869
48973
|
const proxyCalls = [...state.toolBlocks.values()].filter((b2) => PROXY_TOOL_NAMES.has(b2.name));
|
|
48870
|
-
const
|
|
48871
|
-
|
|
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
|
+
}
|
|
48872
48991
|
const stop = hasRealToolUse ? "tool_use" : roundStopReason ?? "end_turn";
|
|
48873
48992
|
yield Buffer.from(buildTerminalSse(stop, totalOutputTokens, totalInputTokens, totalCachedTokens, messageId, model), "utf8");
|
|
48874
48993
|
return;
|
|
@@ -49281,6 +49400,31 @@ function replaceResponsesJsonText(parts, text) {
|
|
|
49281
49400
|
part.text = index === 0 ? text : "";
|
|
49282
49401
|
});
|
|
49283
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
|
+
}
|
|
49284
49428
|
async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requestOptions) {
|
|
49285
49429
|
let current = initialResponse;
|
|
49286
49430
|
for (let loopCount = 1; loopCount <= 5; loopCount++) {
|
|
@@ -49289,8 +49433,12 @@ async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requ
|
|
|
49289
49433
|
const allCalls = [...output.calls, ...extracted.calls].filter((call) => call.name.length > 0);
|
|
49290
49434
|
const proxyCalls = allCalls.filter((call) => PROXY_TOOL_NAMES.has(call.name));
|
|
49291
49435
|
const realCalls = allCalls.filter((call) => !PROXY_TOOL_NAMES.has(call.name));
|
|
49292
|
-
|
|
49293
|
-
|
|
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
|
+
}
|
|
49294
49442
|
return current;
|
|
49295
49443
|
}
|
|
49296
49444
|
const inputItems = Array.isArray(requestBody.input) ? [...requestBody.input] : [];
|
|
@@ -49437,9 +49585,30 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
|
|
|
49437
49585
|
const allCalls = [...fcByItemId.values()].filter((c) => c.name.length > 0);
|
|
49438
49586
|
const proxyCalls = allCalls.filter((c) => PROXY_TOOL_NAMES.has(c.name));
|
|
49439
49587
|
const realCalls = allCalls.filter((c) => !PROXY_TOOL_NAMES.has(c.name));
|
|
49588
|
+
const readonlyProxy = proxyCalls.filter((c) => READONLY_PROXY_TOOLS.has(c.name));
|
|
49440
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))}`);
|
|
49441
|
-
const
|
|
49442
|
-
if (!
|
|
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
|
+
}
|
|
49443
49612
|
let oi2 = nextOutputIndex;
|
|
49444
49613
|
for (const fc of realCalls) {
|
|
49445
49614
|
yield Buffer.from(buildFunctionCallEvents(fc, oi2), "utf8");
|
|
@@ -49451,7 +49620,8 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
|
|
|
49451
49620
|
return;
|
|
49452
49621
|
}
|
|
49453
49622
|
const hasUsage = !!responseObj?.usage;
|
|
49454
|
-
|
|
49623
|
+
const emittedReadonly = readonlyProxy.length > 0;
|
|
49624
|
+
if (contentText.length === 0 && realCalls.length === 0 && customToolCalls === 0 && !emittedReadonly && !hasUsage) {
|
|
49455
49625
|
ctx.log("[acp-proxy: empty upstream response (no content/usage) \u2014 injecting response.failed for client retry]");
|
|
49456
49626
|
yield Buffer.from(buildFailed(responseObj), "utf8");
|
|
49457
49627
|
return;
|
|
@@ -49532,6 +49702,1070 @@ async function* compressLoopResponsesStream(initialUpstream, ctx, requestBody, r
|
|
|
49532
49702
|
}
|
|
49533
49703
|
}
|
|
49534
49704
|
|
|
49705
|
+
// src/loop/core.ts
|
|
49706
|
+
var MAX_LOOP_ROUNDS = 10;
|
|
49707
|
+
function executeProxyTool4(toolName, args, ctx, callId) {
|
|
49708
|
+
if (toolName === "compress") {
|
|
49709
|
+
return applyRanges(parseCompressInput(args, callId), ctx);
|
|
49710
|
+
}
|
|
49711
|
+
if (toolName === "decompress") {
|
|
49712
|
+
return resolveDecompress(args, ctx);
|
|
49713
|
+
}
|
|
49714
|
+
if (toolName === "search_context") {
|
|
49715
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
49716
|
+
if (query.length === 0) return "[search_context FAILED: query is required]";
|
|
49717
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 5;
|
|
49718
|
+
const blocks = ctx.core.search(query, ctx.session.state).slice(0, limit);
|
|
49719
|
+
if (blocks.length === 0) return `[No blocks matched "${query}"]`;
|
|
49720
|
+
const lines = blocks.map((b2) => {
|
|
49721
|
+
const topic = b2.topic ?? "(no topic)";
|
|
49722
|
+
const preview = b2.summary.length > 200 ? b2.summary.slice(0, 200) + "..." : b2.summary;
|
|
49723
|
+
return `${b2.blockId} (T${b2.tier}) "${topic}"
|
|
49724
|
+
${preview}`;
|
|
49725
|
+
});
|
|
49726
|
+
return `Found ${blocks.length} block(s) for "${query}":
|
|
49727
|
+
|
|
49728
|
+
${lines.join("\n\n")}`;
|
|
49729
|
+
}
|
|
49730
|
+
if (toolName === "acp_status") {
|
|
49731
|
+
return buildStatusReport(ctx.session.state, ctx.messages, estimateTokensFast);
|
|
49732
|
+
}
|
|
49733
|
+
return `[Unknown proxy tool: ${toolName}]`;
|
|
49734
|
+
}
|
|
49735
|
+
function recordUsage(ctx, usage, round) {
|
|
49736
|
+
const prompt = usage.inputTokens;
|
|
49737
|
+
const cached = usage.cachedTokens;
|
|
49738
|
+
const out = usage.outputTokens;
|
|
49739
|
+
if (typeof prompt === "number") ctx.session.stats.inputTokens += prompt;
|
|
49740
|
+
ctx.session.stats.lastInputTokens = (typeof prompt === "number" ? prompt : 0) + (typeof cached === "number" ? cached : 0);
|
|
49741
|
+
if (typeof cached === "number") ctx.session.stats.cachedTokens += cached;
|
|
49742
|
+
if (typeof out === "number") ctx.session.stats.outputTokens += out;
|
|
49743
|
+
ctx.session.stats.cacheSamples += 1;
|
|
49744
|
+
const hitPct = typeof prompt === "number" && typeof cached === "number" && prompt + cached > 0 ? Math.round(cached / (prompt + cached) * 100) : 0;
|
|
49745
|
+
ctx.log(
|
|
49746
|
+
`[acp-usage] round ${round} input=${ctx.session.stats.lastInputTokens} cached=${cached ?? 0} (cache hit ${hitPct}%)`
|
|
49747
|
+
);
|
|
49748
|
+
}
|
|
49749
|
+
async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adapter, systemPrompt) {
|
|
49750
|
+
let activeClearTimer = null;
|
|
49751
|
+
let currentUpstream = upstream;
|
|
49752
|
+
const coreMessages = [...ctx.messages];
|
|
49753
|
+
try {
|
|
49754
|
+
for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
|
|
49755
|
+
let assistantText = "";
|
|
49756
|
+
const calls = [];
|
|
49757
|
+
let usage = {};
|
|
49758
|
+
let finishReason;
|
|
49759
|
+
for await (const ev of adapter.parseStream(currentUpstream, round)) {
|
|
49760
|
+
if (ev.kind === "text") {
|
|
49761
|
+
assistantText += ev.delta;
|
|
49762
|
+
if (!ctx.textProtocol && round === 1 && ev.raw) {
|
|
49763
|
+
yield ev.raw;
|
|
49764
|
+
}
|
|
49765
|
+
} else if (ev.kind === "tool_call") {
|
|
49766
|
+
calls.push({ name: ev.name, callId: ev.callId, arguments: ev.arguments });
|
|
49767
|
+
} else if (ev.kind === "usage") {
|
|
49768
|
+
usage = {
|
|
49769
|
+
inputTokens: ev.inputTokens,
|
|
49770
|
+
outputTokens: ev.outputTokens,
|
|
49771
|
+
cachedTokens: ev.cachedTokens
|
|
49772
|
+
};
|
|
49773
|
+
} else if (ev.kind === "done") {
|
|
49774
|
+
finishReason = ev.finishReason;
|
|
49775
|
+
} else if (ev.kind === "meta") {
|
|
49776
|
+
if (round === 1 || !ev.firstRoundOnly) {
|
|
49777
|
+
yield ev.chunk;
|
|
49778
|
+
}
|
|
49779
|
+
}
|
|
49780
|
+
}
|
|
49781
|
+
if (usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cachedTokens !== void 0) {
|
|
49782
|
+
recordUsage(ctx, usage, round);
|
|
49783
|
+
}
|
|
49784
|
+
let resolvedText = assistantText;
|
|
49785
|
+
let allCalls = calls;
|
|
49786
|
+
if (ctx.textProtocol && assistantText.length > 0 && adapter.extractTextTriggers) {
|
|
49787
|
+
const extracted = adapter.extractTextTriggers(assistantText);
|
|
49788
|
+
resolvedText = extracted.clean;
|
|
49789
|
+
allCalls = [...calls, ...extracted.calls];
|
|
49790
|
+
}
|
|
49791
|
+
if (ctx.textProtocol && resolvedText.length > 0) {
|
|
49792
|
+
yield adapter.emitText(resolvedText);
|
|
49793
|
+
} else if (!ctx.textProtocol && round > 1 && resolvedText.length > 0) {
|
|
49794
|
+
yield adapter.emitText(resolvedText);
|
|
49795
|
+
}
|
|
49796
|
+
let realCalls = 0;
|
|
49797
|
+
const realToolCalls = [];
|
|
49798
|
+
const proxyResults = [];
|
|
49799
|
+
for (const call of allCalls) {
|
|
49800
|
+
if (PROXY_TOOL_NAMES.has(call.name)) {
|
|
49801
|
+
let parsedArgs;
|
|
49802
|
+
try {
|
|
49803
|
+
parsedArgs = call.arguments.length > 0 ? JSON.parse(call.arguments) : {};
|
|
49804
|
+
} catch {
|
|
49805
|
+
parsedArgs = {};
|
|
49806
|
+
}
|
|
49807
|
+
const result = executeProxyTool4(call.name, parsedArgs, ctx, call.callId);
|
|
49808
|
+
proxyResults.push({ name: call.name, callId: call.callId, result, arguments: call.arguments });
|
|
49809
|
+
yield adapter.emitMarker(call.name, result);
|
|
49810
|
+
} else {
|
|
49811
|
+
realToolCalls.push(call);
|
|
49812
|
+
realCalls += 1;
|
|
49813
|
+
}
|
|
49814
|
+
}
|
|
49815
|
+
if (ctx.debug) {
|
|
49816
|
+
const callSummary = allCalls.map((c) => {
|
|
49817
|
+
const argSnippet = c.arguments.length > 200 ? c.arguments.slice(0, 200) + "..." : c.arguments;
|
|
49818
|
+
return `${c.name}(${argSnippet})`;
|
|
49819
|
+
}).join(" | ");
|
|
49820
|
+
ctx.log(`[acp-loop] round ${round}: ${allCalls.length} call(s): ${callSummary || "(none)"}`);
|
|
49821
|
+
for (const pr2 of proxyResults) {
|
|
49822
|
+
const resSnippet = pr2.result.length > 300 ? pr2.result.slice(0, 300) + "..." : pr2.result;
|
|
49823
|
+
ctx.log(`[acp-loop] \u2192 ${pr2.name} result: ${resSnippet}`);
|
|
49824
|
+
}
|
|
49825
|
+
if (realCalls > 0) ctx.log(`[acp-loop] round ${round}: ${realCalls} real tool call(s) forwarded to client`);
|
|
49826
|
+
}
|
|
49827
|
+
if (proxyResults.length > 0) {
|
|
49828
|
+
if (resolvedText.length > 0) {
|
|
49829
|
+
coreMessages.push({
|
|
49830
|
+
id: `acp_loop_r${round}_asst`,
|
|
49831
|
+
role: "assistant",
|
|
49832
|
+
contentType: "text",
|
|
49833
|
+
text: resolvedText
|
|
49834
|
+
});
|
|
49835
|
+
}
|
|
49836
|
+
for (const pr2 of proxyResults) {
|
|
49837
|
+
if (ctx.textProtocol) {
|
|
49838
|
+
coreMessages.push({
|
|
49839
|
+
id: `acp_loop_r${round}_marker_${pr2.callId}`,
|
|
49840
|
+
role: "user",
|
|
49841
|
+
contentType: "text",
|
|
49842
|
+
text: buildVisibilityMarker(pr2.name, pr2.result)
|
|
49843
|
+
});
|
|
49844
|
+
} else {
|
|
49845
|
+
coreMessages.push({
|
|
49846
|
+
id: `acp_loop_r${round}_asst_tc_${pr2.callId}`,
|
|
49847
|
+
role: "assistant",
|
|
49848
|
+
contentType: "tool-call",
|
|
49849
|
+
toolName: pr2.name,
|
|
49850
|
+
toolCallId: pr2.callId,
|
|
49851
|
+
text: pr2.arguments
|
|
49852
|
+
});
|
|
49853
|
+
coreMessages.push({
|
|
49854
|
+
id: `acp_loop_r${round}_tool_${pr2.callId}`,
|
|
49855
|
+
role: "tool",
|
|
49856
|
+
contentType: "tool-result",
|
|
49857
|
+
toolCallId: pr2.callId,
|
|
49858
|
+
text: pr2.result
|
|
49859
|
+
});
|
|
49860
|
+
}
|
|
49861
|
+
}
|
|
49862
|
+
if (!ctx.textProtocol) {
|
|
49863
|
+
const hidden = hideConsumedCompressCalls(ctx.session.state, coreMessages);
|
|
49864
|
+
if (hidden.hidden > 0) {
|
|
49865
|
+
ctx.log(`[acp-loop] round ${round} hideConsumed hid ${hidden.hidden} compress record(s)`);
|
|
49866
|
+
coreMessages.length = 0;
|
|
49867
|
+
coreMessages.push(...hidden.messages);
|
|
49868
|
+
}
|
|
49869
|
+
}
|
|
49870
|
+
}
|
|
49871
|
+
for (const tc of realToolCalls) {
|
|
49872
|
+
yield adapter.emitToolCall(tc);
|
|
49873
|
+
}
|
|
49874
|
+
const reRequest = proxyResults.length > 0 && realCalls === 0;
|
|
49875
|
+
if (!reRequest) {
|
|
49876
|
+
yield adapter.emitCompletion({ finishReason, usage });
|
|
49877
|
+
return;
|
|
49878
|
+
}
|
|
49879
|
+
if (round >= MAX_LOOP_ROUNDS) {
|
|
49880
|
+
ctx.log(`[acp-loop] round ${round} hit MAX_LOOP_ROUNDS; completing gracefully`);
|
|
49881
|
+
log("warn", `[acp-loop] loop limit (${MAX_LOOP_ROUNDS}) reached; completing gracefully`);
|
|
49882
|
+
yield adapter.emitCompletion({ finishReason: "length", usage });
|
|
49883
|
+
return;
|
|
49884
|
+
}
|
|
49885
|
+
ctx.log(`[acp-loop] round ${round} saw mutating proxy tool; re-requesting`);
|
|
49886
|
+
const newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
|
|
49887
|
+
if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
|
|
49888
|
+
try {
|
|
49889
|
+
const fs5 = await import("fs");
|
|
49890
|
+
const dumpDir = process.env.ACP_DUMP_DIR || `${process.env.HOME}/.local/state/billion-context/dumps`;
|
|
49891
|
+
fs5.mkdirSync(dumpDir, { recursive: true });
|
|
49892
|
+
const sid = ctx.session.id ?? "unknown";
|
|
49893
|
+
fs5.writeFileSync(`${dumpDir}/req-${Date.now()}-${sid}-REREQUEST.json`, JSON.stringify(newBody, null, 2));
|
|
49894
|
+
} catch {
|
|
49895
|
+
}
|
|
49896
|
+
}
|
|
49897
|
+
const { response: resp, clearTimer } = await fetchWithTimeout(requestOptions.url, {
|
|
49898
|
+
method: "POST",
|
|
49899
|
+
headers: requestOptions.headers,
|
|
49900
|
+
body: JSON.stringify(newBody),
|
|
49901
|
+
...ctx.proxyUrl ? { dispatcher: proxyDispatcher(ctx.proxyUrl) } : {}
|
|
49902
|
+
});
|
|
49903
|
+
if (!resp.ok || !resp.body) {
|
|
49904
|
+
clearTimer();
|
|
49905
|
+
const errText = await resp.text().catch(() => "upstream error");
|
|
49906
|
+
ctx.log(`[acp-proxy: compress loop upstream error ${resp.status}: ${errText.slice(0, 200)}]`);
|
|
49907
|
+
log("error", `[acp-loop] upstream error ${resp.status}: ${errText.slice(0, 200)}`);
|
|
49908
|
+
yield adapter.emitError(`upstream error ${resp.status}: ${errText.slice(0, 200)}`);
|
|
49909
|
+
return;
|
|
49910
|
+
}
|
|
49911
|
+
currentUpstream = resp.body;
|
|
49912
|
+
if (activeClearTimer) activeClearTimer();
|
|
49913
|
+
activeClearTimer = clearTimer;
|
|
49914
|
+
}
|
|
49915
|
+
} finally {
|
|
49916
|
+
if (activeClearTimer) {
|
|
49917
|
+
activeClearTimer();
|
|
49918
|
+
activeClearTimer = null;
|
|
49919
|
+
}
|
|
49920
|
+
}
|
|
49921
|
+
}
|
|
49922
|
+
|
|
49923
|
+
// src/loop/adapter-responses.ts
|
|
49924
|
+
async function* iterSseEvents(stream2) {
|
|
49925
|
+
const reader = stream2.getReader();
|
|
49926
|
+
let buf = "";
|
|
49927
|
+
try {
|
|
49928
|
+
while (true) {
|
|
49929
|
+
let done;
|
|
49930
|
+
let value;
|
|
49931
|
+
try {
|
|
49932
|
+
({ done, value } = await reader.read());
|
|
49933
|
+
} catch {
|
|
49934
|
+
break;
|
|
49935
|
+
}
|
|
49936
|
+
if (done) break;
|
|
49937
|
+
buf += new TextDecoder().decode(value, { stream: true });
|
|
49938
|
+
buf = buf.replace(/\r\n|\r/g, "\n");
|
|
49939
|
+
let idx;
|
|
49940
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
49941
|
+
const raw = buf.slice(0, idx);
|
|
49942
|
+
buf = buf.slice(idx + 2);
|
|
49943
|
+
if (raw.trim().length > 0) yield raw;
|
|
49944
|
+
}
|
|
49945
|
+
}
|
|
49946
|
+
if (buf.trim().length > 0) yield buf;
|
|
49947
|
+
} finally {
|
|
49948
|
+
reader.releaseLock();
|
|
49949
|
+
}
|
|
49950
|
+
}
|
|
49951
|
+
function extractEventType2(rawEvent) {
|
|
49952
|
+
for (const l of rawEvent.split("\n")) {
|
|
49953
|
+
if (l.startsWith("event:")) return l.slice(6).trim();
|
|
49954
|
+
}
|
|
49955
|
+
return null;
|
|
49956
|
+
}
|
|
49957
|
+
function extractDataLine2(rawEvent) {
|
|
49958
|
+
const parts = [];
|
|
49959
|
+
for (const l of rawEvent.split("\n")) {
|
|
49960
|
+
if (l.startsWith("data:")) {
|
|
49961
|
+
let v2 = l.slice(5);
|
|
49962
|
+
if (v2.startsWith(" ")) v2 = v2.slice(1);
|
|
49963
|
+
parts.push(v2);
|
|
49964
|
+
}
|
|
49965
|
+
}
|
|
49966
|
+
return parts.length ? parts.join("\n") : null;
|
|
49967
|
+
}
|
|
49968
|
+
function buildMessageItemSequence2(itemId, outputIndex, text) {
|
|
49969
|
+
const item = { type: "message", id: itemId, role: "assistant", content: [] };
|
|
49970
|
+
const part = { type: "output_text", text: "" };
|
|
49971
|
+
const doneItem = {
|
|
49972
|
+
type: "message",
|
|
49973
|
+
id: itemId,
|
|
49974
|
+
role: "assistant",
|
|
49975
|
+
content: [{ type: "output_text", text }]
|
|
49976
|
+
};
|
|
49977
|
+
return Buffer.from(
|
|
49978
|
+
[
|
|
49979
|
+
`event: response.output_item.added
|
|
49980
|
+
data: ${JSON.stringify({ type: "response.output_item.added", output_index: outputIndex, item })}
|
|
49981
|
+
|
|
49982
|
+
`,
|
|
49983
|
+
`event: response.content_part.added
|
|
49984
|
+
data: ${JSON.stringify({ type: "response.content_part.added", item_id: itemId, output_index: outputIndex, part })}
|
|
49985
|
+
|
|
49986
|
+
`,
|
|
49987
|
+
`event: response.output_text.delta
|
|
49988
|
+
data: ${JSON.stringify({ type: "response.output_text.delta", item_id: itemId, output_index: outputIndex, delta: text })}
|
|
49989
|
+
|
|
49990
|
+
`,
|
|
49991
|
+
`event: response.output_text.done
|
|
49992
|
+
data: ${JSON.stringify({ type: "response.output_text.done", item_id: itemId, output_index: outputIndex, text })}
|
|
49993
|
+
|
|
49994
|
+
`,
|
|
49995
|
+
`event: response.content_part.done
|
|
49996
|
+
data: ${JSON.stringify({ type: "response.content_part.done", item_id: itemId, output_index: outputIndex, part: { type: "output_text", text } })}
|
|
49997
|
+
|
|
49998
|
+
`,
|
|
49999
|
+
`event: response.output_item.done
|
|
50000
|
+
data: ${JSON.stringify({ type: "response.output_item.done", output_index: outputIndex, item: doneItem })}
|
|
50001
|
+
|
|
50002
|
+
`
|
|
50003
|
+
].join(""),
|
|
50004
|
+
"utf8"
|
|
50005
|
+
);
|
|
50006
|
+
}
|
|
50007
|
+
function buildFunctionCallEvents2(fc, itemId, outputIndex) {
|
|
50008
|
+
return Buffer.from(
|
|
50009
|
+
[
|
|
50010
|
+
`event: response.output_item.added
|
|
50011
|
+
data: ${JSON.stringify({
|
|
50012
|
+
type: "response.output_item.added",
|
|
50013
|
+
output_index: outputIndex,
|
|
50014
|
+
item: { type: "function_call", id: itemId, call_id: fc.callId, name: fc.name, arguments: "" }
|
|
50015
|
+
})}
|
|
50016
|
+
|
|
50017
|
+
`,
|
|
50018
|
+
`event: response.function_call_arguments.delta
|
|
50019
|
+
data: ${JSON.stringify({
|
|
50020
|
+
type: "response.function_call_arguments.delta",
|
|
50021
|
+
item_id: itemId,
|
|
50022
|
+
delta: fc.arguments
|
|
50023
|
+
})}
|
|
50024
|
+
|
|
50025
|
+
`,
|
|
50026
|
+
`event: response.function_call_arguments.done
|
|
50027
|
+
data: ${JSON.stringify({
|
|
50028
|
+
type: "response.function_call_arguments.done",
|
|
50029
|
+
item_id: itemId,
|
|
50030
|
+
arguments: fc.arguments
|
|
50031
|
+
})}
|
|
50032
|
+
|
|
50033
|
+
`,
|
|
50034
|
+
`event: response.output_item.done
|
|
50035
|
+
data: ${JSON.stringify({
|
|
50036
|
+
type: "response.output_item.done",
|
|
50037
|
+
output_index: outputIndex,
|
|
50038
|
+
item: { type: "function_call", id: itemId, call_id: fc.callId, name: fc.name, arguments: fc.arguments }
|
|
50039
|
+
})}
|
|
50040
|
+
|
|
50041
|
+
`
|
|
50042
|
+
].join(""),
|
|
50043
|
+
"utf8"
|
|
50044
|
+
);
|
|
50045
|
+
}
|
|
50046
|
+
function buildCompleted2(responseObj) {
|
|
50047
|
+
const resp = responseObj ?? { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
50048
|
+
return Buffer.from(
|
|
50049
|
+
`event: response.completed
|
|
50050
|
+
data: ${JSON.stringify({ type: "response.completed", response: resp })}
|
|
50051
|
+
|
|
50052
|
+
`,
|
|
50053
|
+
"utf8"
|
|
50054
|
+
);
|
|
50055
|
+
}
|
|
50056
|
+
function createResponsesAdapter(textProtocol, projection) {
|
|
50057
|
+
const suppressTextLifecycle = !!textProtocol;
|
|
50058
|
+
let outputIndex = 0;
|
|
50059
|
+
let responseObj = null;
|
|
50060
|
+
let terminalRaw = null;
|
|
50061
|
+
let terminalKind = null;
|
|
50062
|
+
return {
|
|
50063
|
+
buildRequest(coreMessages, systemPrompt, requestBody) {
|
|
50064
|
+
const customToolCallIds = /* @__PURE__ */ new Set();
|
|
50065
|
+
for (const m2 of coreMessages) {
|
|
50066
|
+
const bm = m2;
|
|
50067
|
+
const raw = bm?.rawResponsesItem;
|
|
50068
|
+
if (raw && (raw.type === "custom_tool_call" || raw.type === "custom_tool_call_output")) {
|
|
50069
|
+
const id = typeof raw.call_id === "string" ? raw.call_id : typeof raw.id === "string" ? raw.id : "";
|
|
50070
|
+
if (id) customToolCallIds.add(id);
|
|
50071
|
+
}
|
|
50072
|
+
}
|
|
50073
|
+
let inputItems;
|
|
50074
|
+
if (projection) {
|
|
50075
|
+
const rebuiltInput = patchResponsesInput(projection, coreMessages);
|
|
50076
|
+
inputItems = typeof rebuiltInput === "string" ? [{ type: "message", role: "user", content: rebuiltInput }] : rebuiltInput;
|
|
50077
|
+
} else {
|
|
50078
|
+
inputItems = coreToResponses(coreMessages, customToolCallIds);
|
|
50079
|
+
}
|
|
50080
|
+
const devParts = projection && projection.systemParts.length > 0 ? [...projection.systemParts, systemPrompt] : [systemPrompt];
|
|
50081
|
+
const withDev = injectResponsesDeveloperMessage(inputItems, devParts.join("\n\n---\n\n"));
|
|
50082
|
+
const rebuilt = { ...requestBody, input: withDev };
|
|
50083
|
+
delete rebuilt.previous_response_id;
|
|
50084
|
+
delete rebuilt.instructions;
|
|
50085
|
+
return rebuilt;
|
|
50086
|
+
},
|
|
50087
|
+
async *parseStream(upstream, round) {
|
|
50088
|
+
const pending = /* @__PURE__ */ new Map();
|
|
50089
|
+
for await (const eventStr of iterSseEvents(upstream)) {
|
|
50090
|
+
const type = extractEventType2(eventStr);
|
|
50091
|
+
const dataLine = extractDataLine2(eventStr);
|
|
50092
|
+
if (!type || !dataLine) continue;
|
|
50093
|
+
let obj;
|
|
50094
|
+
try {
|
|
50095
|
+
obj = JSON.parse(dataLine);
|
|
50096
|
+
} catch {
|
|
50097
|
+
continue;
|
|
50098
|
+
}
|
|
50099
|
+
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
50100
|
+
if (round === 1 && typeof obj.output_index === "number") {
|
|
50101
|
+
outputIndex = Math.max(outputIndex, obj.output_index + 1);
|
|
50102
|
+
}
|
|
50103
|
+
if (type === "response.created" || type === "response.in_progress") {
|
|
50104
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50105
|
+
} else if (type === "response.output_item.added") {
|
|
50106
|
+
const item = obj.item;
|
|
50107
|
+
if (item?.type === "function_call") {
|
|
50108
|
+
const itemId = typeof item.id === "string" ? item.id : "";
|
|
50109
|
+
pending.set(itemId, {
|
|
50110
|
+
itemId,
|
|
50111
|
+
callId: typeof item.call_id === "string" ? item.call_id : "",
|
|
50112
|
+
name: typeof item.name === "string" ? item.name : "",
|
|
50113
|
+
arguments: ""
|
|
50114
|
+
});
|
|
50115
|
+
} else if (item?.type === "custom_tool_call") {
|
|
50116
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
|
|
50117
|
+
} else if (!suppressTextLifecycle) {
|
|
50118
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50119
|
+
}
|
|
50120
|
+
} else if (type === "response.content_part.added" || type === "response.content_part.done" || type === "response.output_text.done") {
|
|
50121
|
+
if (!suppressTextLifecycle) {
|
|
50122
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50123
|
+
}
|
|
50124
|
+
} else if (type === "response.output_text.delta") {
|
|
50125
|
+
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
50126
|
+
if (delta.length > 0) {
|
|
50127
|
+
yield { kind: "text", delta, raw: rawBuf };
|
|
50128
|
+
}
|
|
50129
|
+
} else if (type === "response.function_call_arguments.delta") {
|
|
50130
|
+
const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
|
|
50131
|
+
const delta = typeof obj.delta === "string" ? obj.delta : "";
|
|
50132
|
+
const fc = pending.get(itemId);
|
|
50133
|
+
if (fc) fc.arguments += delta;
|
|
50134
|
+
} else if (type === "response.function_call_arguments.done") {
|
|
50135
|
+
const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
|
|
50136
|
+
const args = typeof obj.arguments === "string" ? obj.arguments : "";
|
|
50137
|
+
const fc = pending.get(itemId);
|
|
50138
|
+
if (fc && args) fc.arguments = args;
|
|
50139
|
+
} else if (type === "response.output_item.done") {
|
|
50140
|
+
const item = obj.item;
|
|
50141
|
+
if (item?.type === "function_call") {
|
|
50142
|
+
const itemId = typeof item.id === "string" ? item.id : "";
|
|
50143
|
+
const fc = pending.get(itemId);
|
|
50144
|
+
if (fc) {
|
|
50145
|
+
if (typeof item.arguments === "string" && item.arguments) fc.arguments = item.arguments;
|
|
50146
|
+
pending.delete(itemId);
|
|
50147
|
+
yield {
|
|
50148
|
+
kind: "tool_call",
|
|
50149
|
+
name: fc.name,
|
|
50150
|
+
callId: fc.callId,
|
|
50151
|
+
arguments: fc.arguments
|
|
50152
|
+
};
|
|
50153
|
+
}
|
|
50154
|
+
} else if (item?.type === "custom_tool_call") {
|
|
50155
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
|
|
50156
|
+
} else if (!suppressTextLifecycle) {
|
|
50157
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50158
|
+
}
|
|
50159
|
+
} else if (type === "response.completed") {
|
|
50160
|
+
responseObj = obj.response ?? null;
|
|
50161
|
+
terminalKind = "completed";
|
|
50162
|
+
terminalRaw = null;
|
|
50163
|
+
const respUsage = responseObj?.usage;
|
|
50164
|
+
const pd = respUsage?.input_tokens_details;
|
|
50165
|
+
yield {
|
|
50166
|
+
kind: "usage",
|
|
50167
|
+
inputTokens: typeof respUsage?.input_tokens === "number" ? respUsage.input_tokens : void 0,
|
|
50168
|
+
outputTokens: typeof respUsage?.output_tokens === "number" ? respUsage.output_tokens : void 0,
|
|
50169
|
+
cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
|
|
50170
|
+
};
|
|
50171
|
+
yield { kind: "done", finishReason: "completed" };
|
|
50172
|
+
} else if (type === "response.incomplete") {
|
|
50173
|
+
terminalKind = "incomplete";
|
|
50174
|
+
terminalRaw = rawBuf;
|
|
50175
|
+
yield { kind: "done", finishReason: "incomplete" };
|
|
50176
|
+
} else if (type === "response.failed" || type === "response.error") {
|
|
50177
|
+
terminalKind = "failed";
|
|
50178
|
+
terminalRaw = rawBuf;
|
|
50179
|
+
yield { kind: "done", finishReason: "failed" };
|
|
50180
|
+
} else {
|
|
50181
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50182
|
+
}
|
|
50183
|
+
}
|
|
50184
|
+
if (!terminalKind) {
|
|
50185
|
+
yield { kind: "done", finishReason: "failed" };
|
|
50186
|
+
}
|
|
50187
|
+
},
|
|
50188
|
+
emitText(delta) {
|
|
50189
|
+
return buildMessageItemSequence2(`msg-proxy-${Date.now()}-${outputIndex}`, outputIndex++, delta);
|
|
50190
|
+
},
|
|
50191
|
+
emitToolCall(call) {
|
|
50192
|
+
const buf = buildFunctionCallEvents2(call, `fc-proxy-${Date.now()}-${outputIndex}`, outputIndex);
|
|
50193
|
+
outputIndex += 1;
|
|
50194
|
+
return buf;
|
|
50195
|
+
},
|
|
50196
|
+
emitMarker(toolName, result) {
|
|
50197
|
+
return buildMessageItemSequence2(
|
|
50198
|
+
`marker-${Date.now()}-${outputIndex}`,
|
|
50199
|
+
outputIndex++,
|
|
50200
|
+
buildVisibilityMarker(toolName, result)
|
|
50201
|
+
);
|
|
50202
|
+
},
|
|
50203
|
+
emitCompletion(opts) {
|
|
50204
|
+
if (terminalRaw && (terminalKind === "failed" || terminalKind === "incomplete")) {
|
|
50205
|
+
return terminalRaw;
|
|
50206
|
+
}
|
|
50207
|
+
if (!responseObj && opts?.finishReason === "failed") {
|
|
50208
|
+
const failed = {
|
|
50209
|
+
id: `resp-error-${Date.now()}`,
|
|
50210
|
+
status: "failed",
|
|
50211
|
+
error: { code: "server_error", message: "upstream returned no response" }
|
|
50212
|
+
};
|
|
50213
|
+
return Buffer.from(
|
|
50214
|
+
`event: response.failed
|
|
50215
|
+
data: ${JSON.stringify({ type: "response.failed", response: failed })}
|
|
50216
|
+
|
|
50217
|
+
`,
|
|
50218
|
+
"utf8"
|
|
50219
|
+
);
|
|
50220
|
+
}
|
|
50221
|
+
let resp = responseObj ? { ...responseObj } : { id: `resp-proxy-${Date.now()}`, status: "completed", output: [] };
|
|
50222
|
+
if (opts?.usage) {
|
|
50223
|
+
const usage = {
|
|
50224
|
+
...typeof resp.usage === "object" ? resp.usage : {}
|
|
50225
|
+
};
|
|
50226
|
+
if (typeof opts.usage.inputTokens === "number") usage.input_tokens = opts.usage.inputTokens;
|
|
50227
|
+
if (typeof opts.usage.outputTokens === "number") usage.output_tokens = opts.usage.outputTokens;
|
|
50228
|
+
if (typeof opts.usage.cachedTokens === "number") {
|
|
50229
|
+
usage.input_tokens_details = {
|
|
50230
|
+
...usage.input_tokens_details ?? {},
|
|
50231
|
+
cached_tokens: opts.usage.cachedTokens
|
|
50232
|
+
};
|
|
50233
|
+
}
|
|
50234
|
+
resp = { ...resp, usage };
|
|
50235
|
+
}
|
|
50236
|
+
return buildCompleted2(resp);
|
|
50237
|
+
},
|
|
50238
|
+
emitError(message) {
|
|
50239
|
+
const resp = {
|
|
50240
|
+
id: `resp-error-${Date.now()}`,
|
|
50241
|
+
status: "failed",
|
|
50242
|
+
error: { code: "server_error", message }
|
|
50243
|
+
};
|
|
50244
|
+
return Buffer.from(
|
|
50245
|
+
`event: response.failed
|
|
50246
|
+
data: ${JSON.stringify({ type: "response.failed", response: resp })}
|
|
50247
|
+
|
|
50248
|
+
`,
|
|
50249
|
+
"utf8"
|
|
50250
|
+
);
|
|
50251
|
+
},
|
|
50252
|
+
extractTextTriggers(text) {
|
|
50253
|
+
const calls = [];
|
|
50254
|
+
let clean = text;
|
|
50255
|
+
let hadTrigger = false;
|
|
50256
|
+
const triggers = [
|
|
50257
|
+
{ name: "compress", open: ACP_TEXT_OPEN, close: ACP_TEXT_CLOSE, requirePayload: true },
|
|
50258
|
+
{ name: "acp_status", open: ACP_STATUS_OPEN, close: ACP_STATUS_CLOSE, requirePayload: false },
|
|
50259
|
+
{ name: "search_context", open: ACP_SEARCH_OPEN, close: ACP_SEARCH_CLOSE, requirePayload: true },
|
|
50260
|
+
{ name: "decompress", open: ACP_DECOMPRESS_OPEN, close: ACP_DECOMPRESS_CLOSE, requirePayload: true }
|
|
50261
|
+
];
|
|
50262
|
+
for (const t of triggers) {
|
|
50263
|
+
let start = clean.indexOf(t.open);
|
|
50264
|
+
while (start >= 0) {
|
|
50265
|
+
const end = clean.indexOf(t.close, start + t.open.length);
|
|
50266
|
+
if (end < 0) break;
|
|
50267
|
+
hadTrigger = true;
|
|
50268
|
+
const payload = clean.slice(start + t.open.length, end).trim();
|
|
50269
|
+
if (payload.length > 0 || !t.requirePayload) {
|
|
50270
|
+
const stamp = `${Date.now()}-${calls.length}`;
|
|
50271
|
+
calls.push({
|
|
50272
|
+
name: t.name,
|
|
50273
|
+
callId: `call_text_${stamp}`,
|
|
50274
|
+
arguments: payload.length > 0 ? payload : "{}"
|
|
50275
|
+
});
|
|
50276
|
+
}
|
|
50277
|
+
clean = clean.slice(0, start) + clean.slice(end + t.close.length);
|
|
50278
|
+
start = clean.indexOf(t.open);
|
|
50279
|
+
}
|
|
50280
|
+
}
|
|
50281
|
+
return { clean: hadTrigger ? clean : text, calls };
|
|
50282
|
+
}
|
|
50283
|
+
};
|
|
50284
|
+
}
|
|
50285
|
+
|
|
50286
|
+
// src/loop/adapter-openai.ts
|
|
50287
|
+
async function* iterSseChunks(stream2) {
|
|
50288
|
+
const reader = stream2.getReader();
|
|
50289
|
+
let buf = "";
|
|
50290
|
+
try {
|
|
50291
|
+
while (true) {
|
|
50292
|
+
let done;
|
|
50293
|
+
let value;
|
|
50294
|
+
try {
|
|
50295
|
+
({ done, value } = await reader.read());
|
|
50296
|
+
} catch {
|
|
50297
|
+
break;
|
|
50298
|
+
}
|
|
50299
|
+
if (done) break;
|
|
50300
|
+
buf += new TextDecoder().decode(value, { stream: true });
|
|
50301
|
+
buf = buf.replace(/\r\n|\r/g, "\n");
|
|
50302
|
+
let idx;
|
|
50303
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
50304
|
+
const raw = buf.slice(0, idx);
|
|
50305
|
+
buf = buf.slice(idx + 2);
|
|
50306
|
+
if (raw.trim().length > 0) yield raw;
|
|
50307
|
+
}
|
|
50308
|
+
}
|
|
50309
|
+
if (buf.trim().length > 0) yield buf;
|
|
50310
|
+
} finally {
|
|
50311
|
+
reader.releaseLock();
|
|
50312
|
+
}
|
|
50313
|
+
}
|
|
50314
|
+
function createOpenaiAdapter(requestBody) {
|
|
50315
|
+
const model = requestBody.model ?? "unknown";
|
|
50316
|
+
let responseId = `chatcmpl-proxy-${Date.now()}`;
|
|
50317
|
+
let toolIndex = 0;
|
|
50318
|
+
const makeBase = () => ({
|
|
50319
|
+
id: responseId,
|
|
50320
|
+
object: "chat.completion.chunk",
|
|
50321
|
+
created: Date.now(),
|
|
50322
|
+
model
|
|
50323
|
+
});
|
|
50324
|
+
const buildContent = (content) => Buffer.from(
|
|
50325
|
+
`data: ${JSON.stringify({
|
|
50326
|
+
id: responseId,
|
|
50327
|
+
object: "chat.completion.chunk",
|
|
50328
|
+
created: Date.now(),
|
|
50329
|
+
model,
|
|
50330
|
+
choices: [{ index: 0, delta: { content }, finish_reason: null }]
|
|
50331
|
+
})}
|
|
50332
|
+
|
|
50333
|
+
`,
|
|
50334
|
+
"utf8"
|
|
50335
|
+
);
|
|
50336
|
+
const buildToolCall = (call) => {
|
|
50337
|
+
const idx = toolIndex++;
|
|
50338
|
+
return Buffer.from(
|
|
50339
|
+
`data: ${JSON.stringify({
|
|
50340
|
+
...makeBase(),
|
|
50341
|
+
choices: [{
|
|
50342
|
+
index: 0,
|
|
50343
|
+
delta: {
|
|
50344
|
+
tool_calls: [{
|
|
50345
|
+
index: idx,
|
|
50346
|
+
id: call.callId,
|
|
50347
|
+
type: "function",
|
|
50348
|
+
function: { name: call.name, arguments: call.arguments }
|
|
50349
|
+
}]
|
|
50350
|
+
},
|
|
50351
|
+
finish_reason: null
|
|
50352
|
+
}]
|
|
50353
|
+
})}
|
|
50354
|
+
|
|
50355
|
+
`,
|
|
50356
|
+
"utf8"
|
|
50357
|
+
);
|
|
50358
|
+
};
|
|
50359
|
+
const buildFinish = (finishReason, usage) => Buffer.from(
|
|
50360
|
+
`data: ${JSON.stringify({
|
|
50361
|
+
...makeBase(),
|
|
50362
|
+
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
50363
|
+
...usage ? { usage } : {}
|
|
50364
|
+
})}
|
|
50365
|
+
|
|
50366
|
+
`,
|
|
50367
|
+
"utf8"
|
|
50368
|
+
);
|
|
50369
|
+
return {
|
|
50370
|
+
buildRequest(coreMessages, systemPrompt, body) {
|
|
50371
|
+
const messages = coreToOpenai(coreMessages);
|
|
50372
|
+
const withSys = injectOpenaiSystem(messages, [systemPrompt]);
|
|
50373
|
+
return { ...body, messages: withSys };
|
|
50374
|
+
},
|
|
50375
|
+
async *parseStream(upstream, _round) {
|
|
50376
|
+
const pending = /* @__PURE__ */ new Map();
|
|
50377
|
+
for await (const eventStr of iterSseChunks(upstream)) {
|
|
50378
|
+
const dataLine = eventStr.split("\n").find((l) => l.startsWith("data:"));
|
|
50379
|
+
if (!dataLine) continue;
|
|
50380
|
+
const jsonStr = dataLine.slice(5).trim();
|
|
50381
|
+
if (jsonStr === "[DONE]") {
|
|
50382
|
+
for (const [, tc] of pending) {
|
|
50383
|
+
if (tc.name.length > 0 || tc.id.length > 0) {
|
|
50384
|
+
yield {
|
|
50385
|
+
kind: "tool_call",
|
|
50386
|
+
name: tc.name,
|
|
50387
|
+
callId: tc.id,
|
|
50388
|
+
arguments: tc.arguments
|
|
50389
|
+
};
|
|
50390
|
+
}
|
|
50391
|
+
}
|
|
50392
|
+
pending.clear();
|
|
50393
|
+
yield { kind: "done", finishReason: "stop" };
|
|
50394
|
+
continue;
|
|
50395
|
+
}
|
|
50396
|
+
let parsed;
|
|
50397
|
+
try {
|
|
50398
|
+
parsed = JSON.parse(jsonStr);
|
|
50399
|
+
} catch {
|
|
50400
|
+
continue;
|
|
50401
|
+
}
|
|
50402
|
+
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
50403
|
+
const choices = parsed.choices;
|
|
50404
|
+
const choice = choices?.[0];
|
|
50405
|
+
if (!choice) {
|
|
50406
|
+
if (parsed.usage) {
|
|
50407
|
+
const u2 = parsed.usage;
|
|
50408
|
+
const pd = u2.prompt_tokens_details;
|
|
50409
|
+
yield {
|
|
50410
|
+
kind: "usage",
|
|
50411
|
+
inputTokens: typeof u2.prompt_tokens === "number" ? u2.prompt_tokens : void 0,
|
|
50412
|
+
outputTokens: typeof u2.completion_tokens === "number" ? u2.completion_tokens : void 0,
|
|
50413
|
+
cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
|
|
50414
|
+
};
|
|
50415
|
+
}
|
|
50416
|
+
continue;
|
|
50417
|
+
}
|
|
50418
|
+
const delta = choice.delta;
|
|
50419
|
+
const finishReason = typeof choice.finish_reason === "string" ? choice.finish_reason : void 0;
|
|
50420
|
+
if (finishReason) {
|
|
50421
|
+
for (const [, tc] of pending) {
|
|
50422
|
+
if (tc.name.length > 0 || tc.id.length > 0) {
|
|
50423
|
+
yield {
|
|
50424
|
+
kind: "tool_call",
|
|
50425
|
+
name: tc.name,
|
|
50426
|
+
callId: tc.id,
|
|
50427
|
+
arguments: tc.arguments
|
|
50428
|
+
};
|
|
50429
|
+
}
|
|
50430
|
+
}
|
|
50431
|
+
pending.clear();
|
|
50432
|
+
const u2 = parsed.usage;
|
|
50433
|
+
const pd = u2?.prompt_tokens_details;
|
|
50434
|
+
yield {
|
|
50435
|
+
kind: "usage",
|
|
50436
|
+
inputTokens: typeof u2?.prompt_tokens === "number" ? u2.prompt_tokens : void 0,
|
|
50437
|
+
outputTokens: typeof u2?.completion_tokens === "number" ? u2.completion_tokens : void 0,
|
|
50438
|
+
cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
|
|
50439
|
+
};
|
|
50440
|
+
yield { kind: "done", finishReason };
|
|
50441
|
+
}
|
|
50442
|
+
if (!delta) continue;
|
|
50443
|
+
if (delta.tool_calls) {
|
|
50444
|
+
const tcs = delta.tool_calls;
|
|
50445
|
+
for (const tc of tcs) {
|
|
50446
|
+
const idx = typeof tc.index === "number" ? tc.index : 0;
|
|
50447
|
+
const fn = tc.function;
|
|
50448
|
+
const name = typeof fn?.name === "string" ? fn.name : "";
|
|
50449
|
+
const id = typeof tc.id === "string" ? tc.id : "";
|
|
50450
|
+
const args = typeof fn?.arguments === "string" ? fn.arguments : "";
|
|
50451
|
+
let buf = pending.get(idx);
|
|
50452
|
+
if (!buf) {
|
|
50453
|
+
buf = { index: idx, id, name, arguments: args };
|
|
50454
|
+
pending.set(idx, buf);
|
|
50455
|
+
} else {
|
|
50456
|
+
if (id) buf.id = id;
|
|
50457
|
+
if (name) buf.name = name;
|
|
50458
|
+
buf.arguments += args;
|
|
50459
|
+
}
|
|
50460
|
+
}
|
|
50461
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
50462
|
+
yield { kind: "text", delta: delta.content };
|
|
50463
|
+
}
|
|
50464
|
+
continue;
|
|
50465
|
+
}
|
|
50466
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
50467
|
+
yield { kind: "text", delta: delta.content, raw: rawBuf };
|
|
50468
|
+
} else if (delta.role || Object.keys(delta).length === 0 && !finishReason) {
|
|
50469
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50470
|
+
}
|
|
50471
|
+
}
|
|
50472
|
+
},
|
|
50473
|
+
emitText(delta) {
|
|
50474
|
+
return buildContent(delta);
|
|
50475
|
+
},
|
|
50476
|
+
emitToolCall(call) {
|
|
50477
|
+
return buildToolCall(call);
|
|
50478
|
+
},
|
|
50479
|
+
emitMarker(toolName, result) {
|
|
50480
|
+
return buildContent(buildVisibilityMarker(toolName, result));
|
|
50481
|
+
},
|
|
50482
|
+
emitCompletion(opts) {
|
|
50483
|
+
const finishReason = opts?.finishReason ?? "stop";
|
|
50484
|
+
const usage = opts?.usage ? {
|
|
50485
|
+
prompt_tokens: opts.usage.inputTokens,
|
|
50486
|
+
completion_tokens: opts.usage.outputTokens,
|
|
50487
|
+
...typeof opts.usage.cachedTokens === "number" ? { prompt_tokens_details: { cached_tokens: opts.usage.cachedTokens } } : {}
|
|
50488
|
+
} : null;
|
|
50489
|
+
return Buffer.concat([buildFinish(finishReason, usage), Buffer.from("data: [DONE]\n\n", "utf8")]);
|
|
50490
|
+
},
|
|
50491
|
+
emitError(message) {
|
|
50492
|
+
return Buffer.concat([
|
|
50493
|
+
buildContent(`
|
|
50494
|
+
[acp-proxy: ${message}]
|
|
50495
|
+
`),
|
|
50496
|
+
buildFinish("stop", null),
|
|
50497
|
+
Buffer.from("data: [DONE]\n\n", "utf8")
|
|
50498
|
+
]);
|
|
50499
|
+
}
|
|
50500
|
+
};
|
|
50501
|
+
}
|
|
50502
|
+
|
|
50503
|
+
// src/loop/adapter-anthropic.ts
|
|
50504
|
+
async function* iterSseEvents2(stream2) {
|
|
50505
|
+
const reader = stream2.getReader();
|
|
50506
|
+
let buf = "";
|
|
50507
|
+
try {
|
|
50508
|
+
while (true) {
|
|
50509
|
+
let done;
|
|
50510
|
+
let value;
|
|
50511
|
+
try {
|
|
50512
|
+
({ done, value } = await reader.read());
|
|
50513
|
+
} catch {
|
|
50514
|
+
break;
|
|
50515
|
+
}
|
|
50516
|
+
if (done) break;
|
|
50517
|
+
buf += new TextDecoder().decode(value, { stream: true });
|
|
50518
|
+
buf = buf.replace(/\r\n|\r/g, "\n");
|
|
50519
|
+
let idx;
|
|
50520
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
50521
|
+
const raw = buf.slice(0, idx);
|
|
50522
|
+
buf = buf.slice(idx + 2);
|
|
50523
|
+
if (raw.trim().length > 0) yield raw;
|
|
50524
|
+
}
|
|
50525
|
+
}
|
|
50526
|
+
if (buf.trim().length > 0) yield buf;
|
|
50527
|
+
} finally {
|
|
50528
|
+
reader.releaseLock();
|
|
50529
|
+
}
|
|
50530
|
+
}
|
|
50531
|
+
function parseAnthropicSse2(eventStr) {
|
|
50532
|
+
const lines = eventStr.split("\n");
|
|
50533
|
+
let type = "";
|
|
50534
|
+
const dataLines = [];
|
|
50535
|
+
for (const l of lines) {
|
|
50536
|
+
if (l.startsWith("event:")) type = l.slice(6).trim();
|
|
50537
|
+
else if (l.startsWith("data:")) dataLines.push(l.slice(5).replace(/^ /, ""));
|
|
50538
|
+
}
|
|
50539
|
+
if (!type) return null;
|
|
50540
|
+
const jsonStr = dataLines.join("\n").trim();
|
|
50541
|
+
if (!jsonStr) return { type, data: {} };
|
|
50542
|
+
try {
|
|
50543
|
+
return { type, data: JSON.parse(jsonStr) };
|
|
50544
|
+
} catch {
|
|
50545
|
+
return { type, data: {} };
|
|
50546
|
+
}
|
|
50547
|
+
}
|
|
50548
|
+
function remapIndexInEvent(eventStr, newIndex) {
|
|
50549
|
+
const lines = eventStr.split("\n");
|
|
50550
|
+
const rebuilt = [];
|
|
50551
|
+
let touched = false;
|
|
50552
|
+
for (const l of lines) {
|
|
50553
|
+
if (!touched && l.startsWith("data:")) {
|
|
50554
|
+
const jsonStr = l.slice(5).replace(/^ /, "");
|
|
50555
|
+
try {
|
|
50556
|
+
const obj = JSON.parse(jsonStr);
|
|
50557
|
+
if (typeof obj === "object" && obj !== null && typeof obj.index === "number") {
|
|
50558
|
+
obj.index = newIndex;
|
|
50559
|
+
rebuilt.push(`data: ${JSON.stringify(obj)}`);
|
|
50560
|
+
touched = true;
|
|
50561
|
+
continue;
|
|
50562
|
+
}
|
|
50563
|
+
} catch {
|
|
50564
|
+
}
|
|
50565
|
+
}
|
|
50566
|
+
rebuilt.push(l);
|
|
50567
|
+
}
|
|
50568
|
+
return Buffer.from(rebuilt.join("\n") + "\n\n", "utf8");
|
|
50569
|
+
}
|
|
50570
|
+
function createAnthropicAdapter(requestBody, originalSystem) {
|
|
50571
|
+
const model = requestBody.model ?? void 0;
|
|
50572
|
+
let messageId;
|
|
50573
|
+
let clientIndex = 0;
|
|
50574
|
+
const buildTextBlock = (index, text) => Buffer.from(
|
|
50575
|
+
`event: content_block_start
|
|
50576
|
+
data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "text", text: "" } })}
|
|
50577
|
+
|
|
50578
|
+
event: content_block_delta
|
|
50579
|
+
data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "text_delta", text } })}
|
|
50580
|
+
|
|
50581
|
+
event: content_block_stop
|
|
50582
|
+
data: ${JSON.stringify({ type: "content_block_stop", index })}
|
|
50583
|
+
|
|
50584
|
+
`,
|
|
50585
|
+
"utf8"
|
|
50586
|
+
);
|
|
50587
|
+
const buildToolUseBlock = (index, call) => Buffer.from(
|
|
50588
|
+
`event: content_block_start
|
|
50589
|
+
data: ${JSON.stringify({ type: "content_block_start", index, content_block: { type: "tool_use", id: call.callId, name: call.name, input: {} } })}
|
|
50590
|
+
|
|
50591
|
+
event: content_block_delta
|
|
50592
|
+
data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: "input_json_delta", partial_json: call.arguments } })}
|
|
50593
|
+
|
|
50594
|
+
event: content_block_stop
|
|
50595
|
+
data: ${JSON.stringify({ type: "content_block_stop", index })}
|
|
50596
|
+
|
|
50597
|
+
`,
|
|
50598
|
+
"utf8"
|
|
50599
|
+
);
|
|
50600
|
+
const buildTerminal = (stopReason, outputTokens, inputTokens, cachedTokens) => {
|
|
50601
|
+
const usage = {
|
|
50602
|
+
input_tokens: inputTokens,
|
|
50603
|
+
output_tokens: outputTokens,
|
|
50604
|
+
cache_read_input_tokens: cachedTokens
|
|
50605
|
+
};
|
|
50606
|
+
const extra = {};
|
|
50607
|
+
if (messageId) extra.id = messageId;
|
|
50608
|
+
if (model) extra.model = model;
|
|
50609
|
+
return Buffer.from(
|
|
50610
|
+
`event: message_delta
|
|
50611
|
+
data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason, stop_sequence: null }, usage, ...extra })}
|
|
50612
|
+
|
|
50613
|
+
event: message_stop
|
|
50614
|
+
data: ${JSON.stringify({ type: "message_stop" })}
|
|
50615
|
+
|
|
50616
|
+
`,
|
|
50617
|
+
"utf8"
|
|
50618
|
+
);
|
|
50619
|
+
};
|
|
50620
|
+
return {
|
|
50621
|
+
buildRequest(coreMessages, systemPrompt, body) {
|
|
50622
|
+
const messages = coreToAnthropic(coreMessages);
|
|
50623
|
+
const baseText = originalSystem !== void 0 ? extractSystem(originalSystem) : "";
|
|
50624
|
+
const full = baseText ? `${baseText}
|
|
50625
|
+
|
|
50626
|
+
---
|
|
50627
|
+
|
|
50628
|
+
${systemPrompt}` : systemPrompt;
|
|
50629
|
+
const system = originalSystem !== void 0 ? buildSystem(full, originalSystem) : full;
|
|
50630
|
+
return { ...body, system, messages };
|
|
50631
|
+
},
|
|
50632
|
+
async *parseStream(upstream, round) {
|
|
50633
|
+
const pending = /* @__PURE__ */ new Map();
|
|
50634
|
+
let roundInput;
|
|
50635
|
+
let roundCached;
|
|
50636
|
+
let roundOutput;
|
|
50637
|
+
let stopReason;
|
|
50638
|
+
let usageYielded = false;
|
|
50639
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
50640
|
+
for await (const eventStr of iterSseEvents2(upstream)) {
|
|
50641
|
+
const parsed = parseAnthropicSse2(eventStr);
|
|
50642
|
+
if (!parsed) continue;
|
|
50643
|
+
const { type, data } = parsed;
|
|
50644
|
+
const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
|
|
50645
|
+
if (type === "message_start") {
|
|
50646
|
+
const msg2 = data.message ?? {};
|
|
50647
|
+
if (typeof msg2.id === "string" && !messageId) messageId = msg2.id;
|
|
50648
|
+
const u2 = msg2.usage ?? {};
|
|
50649
|
+
if (typeof u2.input_tokens === "number") roundInput = u2.input_tokens;
|
|
50650
|
+
if (typeof u2.cache_read_input_tokens === "number") roundCached = u2.cache_read_input_tokens;
|
|
50651
|
+
if (round === 1) {
|
|
50652
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50653
|
+
}
|
|
50654
|
+
} else if (type === "ping") {
|
|
50655
|
+
yield { kind: "meta", chunk: rawBuf };
|
|
50656
|
+
} else if (type === "content_block_start") {
|
|
50657
|
+
const upstreamIndex = data.index ?? 0;
|
|
50658
|
+
const block = data.content_block ?? {};
|
|
50659
|
+
if (block.type === "tool_use") {
|
|
50660
|
+
const name = typeof block.name === "string" ? block.name : "";
|
|
50661
|
+
const id = typeof block.id === "string" ? block.id : `toolu_${upstreamIndex}`;
|
|
50662
|
+
pending.set(upstreamIndex, { id, name, json: "" });
|
|
50663
|
+
} else if (round === 1) {
|
|
50664
|
+
const ci2 = clientIndex++;
|
|
50665
|
+
indexMap.set(upstreamIndex, ci2);
|
|
50666
|
+
yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
|
|
50667
|
+
}
|
|
50668
|
+
} else if (type === "content_block_delta") {
|
|
50669
|
+
const upstreamIndex = data.index ?? 0;
|
|
50670
|
+
const delta = data.delta ?? {};
|
|
50671
|
+
if (pending.has(upstreamIndex)) {
|
|
50672
|
+
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
50673
|
+
pending.get(upstreamIndex).json += delta.partial_json;
|
|
50674
|
+
}
|
|
50675
|
+
} else if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
50676
|
+
if (round === 1) {
|
|
50677
|
+
const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
50678
|
+
yield { kind: "text", delta: delta.text, raw: remapIndexInEvent(eventStr, ci2) };
|
|
50679
|
+
} else {
|
|
50680
|
+
yield { kind: "text", delta: delta.text };
|
|
50681
|
+
}
|
|
50682
|
+
} else if (round === 1) {
|
|
50683
|
+
const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
50684
|
+
yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
|
|
50685
|
+
}
|
|
50686
|
+
} else if (type === "content_block_stop") {
|
|
50687
|
+
const upstreamIndex = data.index ?? 0;
|
|
50688
|
+
const tb = pending.get(upstreamIndex);
|
|
50689
|
+
if (tb) {
|
|
50690
|
+
pending.delete(upstreamIndex);
|
|
50691
|
+
yield {
|
|
50692
|
+
kind: "tool_call",
|
|
50693
|
+
name: tb.name,
|
|
50694
|
+
callId: tb.id,
|
|
50695
|
+
arguments: tb.json
|
|
50696
|
+
};
|
|
50697
|
+
} else if (round === 1) {
|
|
50698
|
+
const ci2 = indexMap.get(upstreamIndex) ?? upstreamIndex;
|
|
50699
|
+
yield { kind: "meta", chunk: remapIndexInEvent(eventStr, ci2), firstRoundOnly: true };
|
|
50700
|
+
}
|
|
50701
|
+
} else if (type === "message_delta") {
|
|
50702
|
+
const u2 = data.usage ?? {};
|
|
50703
|
+
if (typeof u2.output_tokens === "number") roundOutput = u2.output_tokens;
|
|
50704
|
+
if (typeof u2.input_tokens === "number") roundInput = u2.input_tokens;
|
|
50705
|
+
if (typeof u2.cache_read_input_tokens === "number") roundCached = u2.cache_read_input_tokens;
|
|
50706
|
+
const d = data.delta ?? {};
|
|
50707
|
+
if (typeof d.stop_reason === "string") stopReason = d.stop_reason;
|
|
50708
|
+
if (!usageYielded) {
|
|
50709
|
+
usageYielded = true;
|
|
50710
|
+
yield {
|
|
50711
|
+
kind: "usage",
|
|
50712
|
+
inputTokens: roundInput,
|
|
50713
|
+
outputTokens: roundOutput,
|
|
50714
|
+
cachedTokens: roundCached
|
|
50715
|
+
};
|
|
50716
|
+
}
|
|
50717
|
+
yield { kind: "done", finishReason: stopReason };
|
|
50718
|
+
} else if (type === "message_stop") {
|
|
50719
|
+
if (!usageYielded) {
|
|
50720
|
+
usageYielded = true;
|
|
50721
|
+
yield {
|
|
50722
|
+
kind: "usage",
|
|
50723
|
+
inputTokens: roundInput,
|
|
50724
|
+
outputTokens: roundOutput,
|
|
50725
|
+
cachedTokens: roundCached
|
|
50726
|
+
};
|
|
50727
|
+
}
|
|
50728
|
+
yield { kind: "done", finishReason: stopReason ?? "end_turn" };
|
|
50729
|
+
} else if (round === 1) {
|
|
50730
|
+
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
50731
|
+
}
|
|
50732
|
+
}
|
|
50733
|
+
},
|
|
50734
|
+
emitText(delta) {
|
|
50735
|
+
return buildTextBlock(clientIndex++, delta);
|
|
50736
|
+
},
|
|
50737
|
+
emitToolCall(call) {
|
|
50738
|
+
return buildToolUseBlock(clientIndex++, call);
|
|
50739
|
+
},
|
|
50740
|
+
emitMarker(toolName, result) {
|
|
50741
|
+
return buildTextBlock(clientIndex++, buildVisibilityMarker(toolName, result));
|
|
50742
|
+
},
|
|
50743
|
+
emitCompletion(opts) {
|
|
50744
|
+
const stopReason = opts?.finishReason ?? "end_turn";
|
|
50745
|
+
return buildTerminal(
|
|
50746
|
+
stopReason,
|
|
50747
|
+
opts?.usage?.outputTokens ?? 0,
|
|
50748
|
+
opts?.usage?.inputTokens ?? 0,
|
|
50749
|
+
opts?.usage?.cachedTokens ?? 0
|
|
50750
|
+
);
|
|
50751
|
+
},
|
|
50752
|
+
emitError(message) {
|
|
50753
|
+
const errBlock = buildTextBlock(clientIndex++, `
|
|
50754
|
+
[acp-proxy: ${message}]
|
|
50755
|
+
`);
|
|
50756
|
+
return Buffer.concat([errBlock, buildTerminal("end_turn", 0, 0, 0)]);
|
|
50757
|
+
}
|
|
50758
|
+
};
|
|
50759
|
+
}
|
|
50760
|
+
|
|
50761
|
+
// src/loop/index.ts
|
|
50762
|
+
function pickAdapter(protocol, requestBody, textProtocol, responsesProjection, anthropicSystem) {
|
|
50763
|
+
if (protocol === "responses") return createResponsesAdapter(textProtocol, responsesProjection);
|
|
50764
|
+
if (protocol === "openai") return createOpenaiAdapter(requestBody);
|
|
50765
|
+
if (protocol === "anthropic") return createAnthropicAdapter(requestBody, anthropicSystem);
|
|
50766
|
+
throw new Error(`[acp-loop] unknown protocol: ${protocol}`);
|
|
50767
|
+
}
|
|
50768
|
+
|
|
49535
50769
|
// src/stream-openai.ts
|
|
49536
50770
|
function rewriteOpenaiJsonResponse(body, ctx) {
|
|
49537
50771
|
if (!body || typeof body !== "object") return body;
|
|
@@ -49668,7 +50902,7 @@ function extractKey(headers) {
|
|
|
49668
50902
|
return "(no-key)";
|
|
49669
50903
|
}
|
|
49670
50904
|
function clientConversationHeader(headers) {
|
|
49671
|
-
const names = ["x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
|
|
50905
|
+
const names = ["x-claude-code-session-id", "x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
|
|
49672
50906
|
for (const name of names) {
|
|
49673
50907
|
const v2 = headers[name];
|
|
49674
50908
|
if (typeof v2 === "string" && v2.trim().length > 0) return v2.trim();
|
|
@@ -50830,11 +52064,24 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
50830
52064
|
return;
|
|
50831
52065
|
}
|
|
50832
52066
|
let bodyBuffer;
|
|
52067
|
+
let urlPath;
|
|
52068
|
+
let responsesCompact;
|
|
52069
|
+
let route;
|
|
52070
|
+
let upstreamOrigin;
|
|
52071
|
+
let protocol;
|
|
50833
52072
|
try {
|
|
50834
52073
|
bodyBuffer = await readBody(req);
|
|
50835
|
-
const
|
|
50836
|
-
|
|
50837
|
-
|
|
52074
|
+
const url = req.url ?? "";
|
|
52075
|
+
urlPath = url.split("?", 2)[0];
|
|
52076
|
+
responsesCompact = urlPath.endsWith("/responses/compact");
|
|
52077
|
+
route = resolveUpstream(opts, req.url ?? "", req);
|
|
52078
|
+
upstreamOrigin = route ? route.upstream : opts.upstream;
|
|
52079
|
+
protocol = route?.explicitProtocol ?? (req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null);
|
|
52080
|
+
if (protocol !== null && bodyBuffer.length > 0) {
|
|
52081
|
+
const decoded = await decodeRequestBody(headerValue(req, "content-encoding"), bodyBuffer, MAX_REQUEST_BYTES);
|
|
52082
|
+
bodyBuffer = decoded.body;
|
|
52083
|
+
if (decoded.decoded) delete req.headers["content-encoding"];
|
|
52084
|
+
}
|
|
50838
52085
|
} catch (err2) {
|
|
50839
52086
|
if (err2 instanceof BodyTooLargeError) {
|
|
50840
52087
|
log2("warn", `413: request body exceeds ${err2.limit} bytes`);
|
|
@@ -50847,13 +52094,7 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
50847
52094
|
res.end(JSON.stringify({ error: { type: "invalid_request", message: String(err2) } }));
|
|
50848
52095
|
return;
|
|
50849
52096
|
}
|
|
50850
|
-
const url = req.url ?? "";
|
|
50851
|
-
const urlPath = url.split("?", 2)[0];
|
|
50852
|
-
const responsesCompact = urlPath.endsWith("/responses/compact");
|
|
50853
52097
|
const countTokens = isCountTokensRequest(req.method ?? "GET", urlPath, bodyBuffer.length > 0);
|
|
50854
|
-
const route = resolveUpstream(opts, req.url ?? "", req);
|
|
50855
|
-
const upstreamOrigin = route ? route.upstream : opts.upstream;
|
|
50856
|
-
const protocol = route?.explicitProtocol ?? (req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null);
|
|
50857
52098
|
let parsed = null;
|
|
50858
52099
|
if (protocol && bodyBuffer.length > 0) {
|
|
50859
52100
|
try {
|
|
@@ -50910,7 +52151,7 @@ async function handle(req, res, opts, core, config, log2) {
|
|
|
50910
52151
|
}
|
|
50911
52152
|
if (!prepared) {
|
|
50912
52153
|
if (protocol === null && !opts.passthrough) {
|
|
50913
|
-
log2("warn", `unrecognized path ${url} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
|
|
52154
|
+
log2("warn", `unrecognized path ${req.url ?? ""} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
|
|
50914
52155
|
}
|
|
50915
52156
|
await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, void 0);
|
|
50916
52157
|
}
|
|
@@ -50985,7 +52226,7 @@ function prepareAnthropic(parsed, req, opts, core, config, log2, session) {
|
|
|
50985
52226
|
}
|
|
50986
52227
|
const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
|
|
50987
52228
|
markDirty(session);
|
|
50988
|
-
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
|
|
52229
|
+
return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: opts.compress.injectTool };
|
|
50989
52230
|
}
|
|
50990
52231
|
function prepareOpenai(parsed, req, opts, core, config, log2, session) {
|
|
50991
52232
|
const sessionId = session.id;
|
|
@@ -51049,12 +52290,14 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
51049
52290
|
}
|
|
51050
52291
|
let processedMessages = [];
|
|
51051
52292
|
let originalMessages = [];
|
|
52293
|
+
let responsesProjection;
|
|
51052
52294
|
let rebuiltInput = parsed.input;
|
|
51053
52295
|
let toolsOut = parsed.tools;
|
|
51054
52296
|
const shouldInject = opts.compress.injectTool;
|
|
51055
52297
|
const responsesTextProtocol = FORCE_TEXT_PROTOCOL || isChatGptCodexUpstream(session.meta.upstreamOrigin) || isCodexResponsesLite(req.headers, parsed);
|
|
51056
52298
|
try {
|
|
51057
52299
|
const projection = responsesToCore(parsed);
|
|
52300
|
+
responsesProjection = projection;
|
|
51058
52301
|
const { msgs } = projection;
|
|
51059
52302
|
originalMessages = msgs;
|
|
51060
52303
|
if (process.env.ACP_DEBUG) {
|
|
@@ -51120,6 +52363,7 @@ function prepareResponses(parsed, req, opts, core, config, log2, session, identi
|
|
|
51120
52363
|
session,
|
|
51121
52364
|
processedMessages,
|
|
51122
52365
|
originalMessages,
|
|
52366
|
+
responsesProjection,
|
|
51123
52367
|
protocol: "responses",
|
|
51124
52368
|
stream: stream2,
|
|
51125
52369
|
compressInjected: shouldInject,
|
|
@@ -51258,9 +52502,20 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51258
52502
|
return fn?.name ?? t.name ?? "?";
|
|
51259
52503
|
});
|
|
51260
52504
|
log2("info", `[debug] tools=[${toolNames.join(",")}] msgs=${parsed.messages?.length ?? 0} stream=${parsed.stream ?? false} system_len=${JSON.stringify(parsed.messages?.find((m2) => m2.role === "system")?.content ?? "").length}`);
|
|
51261
|
-
if (process.env.ACP_DUMP_REQ
|
|
51262
|
-
const
|
|
51263
|
-
|
|
52505
|
+
if (process.env.ACP_DUMP_REQ !== "0") {
|
|
52506
|
+
const dumpDir = process.env.ACP_DUMP_DIR || `${stateDir()}/dumps`;
|
|
52507
|
+
try {
|
|
52508
|
+
fs3.mkdirSync(dumpDir, { recursive: true });
|
|
52509
|
+
} catch {
|
|
52510
|
+
}
|
|
52511
|
+
const sid = prepared?.session.id ?? "unknown";
|
|
52512
|
+
const out = `${dumpDir}/req-${Date.now()}-${sid}.json`;
|
|
52513
|
+
try {
|
|
52514
|
+
const pretty = JSON.stringify(JSON.parse(body), null, 2);
|
|
52515
|
+
fs3.writeFileSync(out, pretty);
|
|
52516
|
+
} catch {
|
|
52517
|
+
fs3.writeFileSync(out, body);
|
|
52518
|
+
}
|
|
51264
52519
|
log2("info", `[debug] forwarded body written to ${out}`);
|
|
51265
52520
|
}
|
|
51266
52521
|
} catch {
|
|
@@ -51276,6 +52531,13 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51276
52531
|
headers["x-session-id"] = affinity;
|
|
51277
52532
|
}
|
|
51278
52533
|
const proxyUrl = resolveProxy(opts.routes, opts.proxy, route?.rewrittenUrl ?? upstreamUrl, opts.proxyFallback);
|
|
52534
|
+
if (opts.debug) {
|
|
52535
|
+
const hdrLog = {};
|
|
52536
|
+
for (const [hk, hv] of Object.entries(headers)) {
|
|
52537
|
+
if (typeof hv === "string") hdrLog[hk] = hv.length > 200 ? hv.slice(0, 200) + "..." : hv;
|
|
52538
|
+
}
|
|
52539
|
+
log2("info", `[${prepared?.session.id ?? "unknown"}] \u2192 upstream headers: ${JSON.stringify(hdrLog)}`);
|
|
52540
|
+
}
|
|
51279
52541
|
const dispatcher = proxyDispatcher(proxyUrl);
|
|
51280
52542
|
const init = {
|
|
51281
52543
|
method: req.method ?? "GET",
|
|
@@ -51297,6 +52559,14 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51297
52559
|
if (UPSTREAM_HOP_HEADERS.has(k2.toLowerCase())) return;
|
|
51298
52560
|
respHeaders[k2] = v2;
|
|
51299
52561
|
});
|
|
52562
|
+
if (opts.debug) {
|
|
52563
|
+
const respLog = {};
|
|
52564
|
+
upstream.headers.forEach((v2, k2) => {
|
|
52565
|
+
if (UPSTREAM_HOP_HEADERS.has(k2.toLowerCase())) return;
|
|
52566
|
+
respLog[k2] = v2.length > 300 ? v2.slice(0, 300) + "..." : v2;
|
|
52567
|
+
});
|
|
52568
|
+
log2("info", `[${prepared?.session.id ?? "unknown"}] \u2190 upstream response headers: ${JSON.stringify(respLog)}`);
|
|
52569
|
+
}
|
|
51300
52570
|
if (!upstream.ok) {
|
|
51301
52571
|
res.writeHead(upstream.status, respHeaders);
|
|
51302
52572
|
if (upstream.body) await pipeThrough(upstream.body, res);
|
|
@@ -51340,7 +52610,37 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
|
|
|
51340
52610
|
dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
|
|
51341
52611
|
}
|
|
51342
52612
|
try {
|
|
51343
|
-
if (
|
|
52613
|
+
if (process.env.ACP_LOOP_V2 !== "0") {
|
|
52614
|
+
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
52615
|
+
const reqHeaders = {};
|
|
52616
|
+
for (const [k2, v2] of Object.entries(headers)) {
|
|
52617
|
+
if (k2.toLowerCase() === "content-length" || k2.toLowerCase() === "host") continue;
|
|
52618
|
+
reqHeaders[k2] = v2;
|
|
52619
|
+
}
|
|
52620
|
+
reqHeaders["content-type"] = "application/json";
|
|
52621
|
+
const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
|
|
52622
|
+
const systemPrompt = textProtocol ? buildCompressTextSystemPrompt() : buildCompressSystemPrompt();
|
|
52623
|
+
const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem);
|
|
52624
|
+
const loop = runCompressLoop(
|
|
52625
|
+
streamToRead,
|
|
52626
|
+
{ core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol, debug: opts.debug },
|
|
52627
|
+
parsedReq,
|
|
52628
|
+
{ url: upstreamUrl, headers: reqHeaders },
|
|
52629
|
+
adapter,
|
|
52630
|
+
systemPrompt
|
|
52631
|
+
);
|
|
52632
|
+
for await (const chunk of loop) {
|
|
52633
|
+
{
|
|
52634
|
+
const s3 = chunk.toString("utf8");
|
|
52635
|
+
if (s3.includes("<acp ") || s3.includes("</acp")) {
|
|
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") {
|
|
51344
52644
|
const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
|
|
51345
52645
|
const reqHeaders = {};
|
|
51346
52646
|
for (const [k2, v2] of Object.entries(headers)) {
|