billion-context 0.1.99 → 0.1.100-pr.647.509

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 CHANGED
@@ -47777,7 +47777,14 @@ async function fetchWithTimeout(url, opts, timeoutMs, externalSignal) {
47777
47777
  const finalOpts = {
47778
47778
  ...opts,
47779
47779
  signal: controller.signal,
47780
- dispatcher: opts.dispatcher ?? directDispatcher(effective)
47780
+ dispatcher: opts.dispatcher ?? directDispatcher(effective),
47781
+ // Forward-proxy correctness: never silently follow a redirect.
47782
+ // undici's default (follow) downgrades POST→GET and drops the body
47783
+ // on 301/302/303, so a redirecting upstream (CDN/WAF) turns a valid
47784
+ // POST into a 405 at the redirect target. Pass the 3xx through to
47785
+ // the client, which follows it with its own policy. Internal bili
47786
+ // fetches that want to follow (registry, upstream test) opt in.
47787
+ redirect: opts.redirect ?? "manual"
47781
47788
  };
47782
47789
  const raw = await fetch(url, finalOpts);
47783
47790
  if (raw.body) {
@@ -48514,6 +48521,7 @@ function loadOptions(env = process.env) {
48514
48521
  passthrough: passthrough.enabled,
48515
48522
  passthroughSource: passthrough.source,
48516
48523
  autoUpdate: (env.ACP_AUTO_UPDATE ?? (fileConfig.autoUpdate === false ? "0" : "1")) !== "0",
48524
+ hostUsageCredit: parseHostUsageCredit(env.BILI_HOST_USAGE_CREDIT ?? fileConfig.hostUsageCredit),
48517
48525
  updateTag: (env.ACP_UPDATE_TAG ?? fileConfig.updateTag ?? "latest").trim() || "latest",
48518
48526
  logFile: env.ACP_LOG_FILE !== void 0 ? env.ACP_LOG_FILE || void 0 : fileConfig.logFile,
48519
48527
  mitm: {
@@ -48579,6 +48587,7 @@ function parseRouteEntry(v2) {
48579
48587
  if (obj.compress) route.compress = obj.compress;
48580
48588
  const compatRoles = parseCompatRoles(obj.compat?.roles);
48581
48589
  if (compatRoles) route.compat = { roles: compatRoles };
48590
+ if (typeof obj.passthrough === "boolean") route.passthrough = obj.passthrough;
48582
48591
  return route;
48583
48592
  }
48584
48593
  if (v2 === null) return {};
@@ -48590,6 +48599,9 @@ function parsePromptCacheRouting(value) {
48590
48599
  function parseUpstreamProxyMode(value) {
48591
48600
  return value === "manual" || value === "auto" ? value : "direct";
48592
48601
  }
48602
+ function parseHostUsageCredit(value) {
48603
+ return value === "off" ? "off" : "auto";
48604
+ }
48593
48605
  function parseCompressSettings(v2) {
48594
48606
  if (!v2 || typeof v2 !== "object" || Array.isArray(v2)) return void 0;
48595
48607
  const obj = v2;
@@ -49005,7 +49017,8 @@ async function fetchFresh() {
49005
49017
  try {
49006
49018
  const { response, clearTimer } = await fetchWithTimeout(REGISTRY_URL, {
49007
49019
  ...opts,
49008
- headers: { Accept: "application/json" }
49020
+ headers: { Accept: "application/json" },
49021
+ redirect: "follow"
49009
49022
  }, 15e3);
49010
49023
  const text = await response.text();
49011
49024
  clearTimer();
@@ -49244,7 +49257,9 @@ function isCodexClient(headers) {
49244
49257
  const ua = headers["user-agent"];
49245
49258
  if (!ua) return false;
49246
49259
  const s3 = Array.isArray(ua) ? ua[0] : ua;
49247
- return typeof s3 === "string" && CODEX_UA_PREFIXES.some((p2) => s3.startsWith(p2));
49260
+ if (typeof s3 !== "string") return false;
49261
+ if (CODEX_UA_PREFIXES.some((p2) => s3.startsWith(p2))) return true;
49262
+ return s3.includes("codex");
49248
49263
  }
49249
49264
  function hasCompactionTrigger(input) {
49250
49265
  if (!Array.isArray(input)) return false;
@@ -51498,20 +51513,44 @@ ${body}`;
51498
51513
  }
51499
51514
 
51500
51515
  // src/loop/tag-echo-filter.ts
51501
- var PAIRED = /\x3cacp\s[^<>]{0,256}>([^<>]{0,64})<\/acp>/;
51502
- var LONE_OPEN = /\x3cacp(?:\s[^<>]{0,256})?>/;
51503
- var LONE_CLOSE = /<\/acp(?=[\s>])[^<>]{0,32}>/;
51504
- var PARTIAL_TAIL = /(\x3cacp\s[^<>]*|\x3c\/acp(?:\s[^<>]{0,32})?|<\/?a?c?p?)$/;
51505
- var TRUNC_OPEN = /\x3cacp\s[^<>]*$/;
51506
- var TRUNC_CLOSE = /\x3c\/acp(?:\s[^<>]{0,32})?$/;
51507
- var CLOSE_TAG = "</acp";
51516
+ function buildAcplikeName() {
51517
+ const cores = ["acp", "apc", "cap", "cpa", "pac", "pca"];
51518
+ const names = new Set(cores);
51519
+ for (const c of cores) {
51520
+ for (const ch of ["a", "c", "p", "i"]) {
51521
+ for (let pos = 0; pos <= c.length; pos++) names.add(c.slice(0, pos) + ch + c.slice(pos));
51522
+ }
51523
+ }
51524
+ return [...names].sort((a, b2) => b2.length - a.length).join("|");
51525
+ }
51526
+ var ACP_NAME_ALT = `(?:${buildAcplikeName()})`;
51527
+ var NAME = ACP_NAME_ALT;
51528
+ var PAIRED = new RegExp("<" + NAME + "\\s[^<>]{0,256}>([^<>]{0,64})<\\/" + NAME + ">");
51529
+ var LONE_OPEN = new RegExp("<" + NAME + "(?:\\s[^<>]{0,256})?>");
51530
+ var LONE_CLOSE = new RegExp("<\\/" + NAME + "(?=[\\s>])[^<>]{0,32}>");
51531
+ var PARTIAL_TAIL = new RegExp("(<" + NAME + "\\s[^<>]*|<\\/" + NAME + "(?:\\s[^<>]{0,32})?|<\\/?[acip]*)$");
51532
+ var TRUNC_OPEN = new RegExp("<" + NAME + "\\s[^<>]*$");
51533
+ var TRUNC_CLOSE = new RegExp("<\\/" + NAME + "(?:\\s[^<>]{0,32})?$");
51534
+ var DEFINITE_TAIL = new RegExp("^<" + NAME + "\\s|^<\\/" + NAME);
51535
+ var OPEN_WITH_ATTRS = new RegExp("^<" + NAME + "\\s");
51536
+ var CLOSE_HEAD = "</";
51537
+ var CLOSE_NAME_ANCHORED = new RegExp("^" + NAME);
51508
51538
  var HOLD_LIMIT = 128;
51509
51539
  var TAG_OPEN_CAP = 4096;
51510
51540
  var SWALLOW_CAP = 80;
51541
+ function looseCloseEnd(s3) {
51542
+ let idx = s3.indexOf(CLOSE_HEAD);
51543
+ while (idx >= 0) {
51544
+ const m2 = CLOSE_NAME_ANCHORED.exec(s3.slice(idx + 2));
51545
+ if (m2 && s3[idx + 2 + m2[0].length] === ">") return idx + 2 + m2[0].length + 1;
51546
+ idx = s3.indexOf(CLOSE_HEAD, idx + 1);
51547
+ }
51548
+ return -1;
51549
+ }
51511
51550
  function stripAcpTags(text) {
51512
51551
  return text.replace(new RegExp(PAIRED.source, "g"), "").replace(new RegExp(LONE_OPEN.source, "g"), "").replace(new RegExp(LONE_CLOSE.source, "g"), "").replace(new RegExp(TRUNC_OPEN.source), "").replace(new RegExp(TRUNC_CLOSE.source), "");
51513
51552
  }
51514
- var RENDER_TAG_DETECT = /\x3c\/?acp(?=[\s>])|\\u003c\/?acp(?=[\s>\\])/;
51553
+ var RENDER_TAG_DETECT = new RegExp("<\\/?" + NAME + "(?=[\\s>])|\\\\u003c\\/?" + NAME + "(?=[\\s>\\\\])");
51515
51554
  function containsRenderTagText(s3) {
51516
51555
  return RENDER_TAG_DETECT.test(s3);
51517
51556
  }
@@ -51528,6 +51567,8 @@ function createTagEchoFilter(onDrop) {
51528
51567
  let swallowed = "";
51529
51568
  let droppedAny = false;
51530
51569
  let notified = false;
51570
+ let inputChars = 0;
51571
+ let outputChars = 0;
51531
51572
  const drop = (snippet) => {
51532
51573
  droppedAny = true;
51533
51574
  if (onDrop && !notified) {
@@ -51541,10 +51582,9 @@ function createTagEchoFilter(onDrop) {
51541
51582
  for (; ; ) {
51542
51583
  if (swallowUntilClose) {
51543
51584
  const combined = swallowed + buf;
51544
- const closeIdx = combined.indexOf(CLOSE_TAG);
51545
- if (closeIdx >= 0 && combined[closeIdx + CLOSE_TAG.length] === ">") {
51546
- const end = closeIdx + CLOSE_TAG.length + 1;
51547
- drop(swallowed + combined.slice(0, end));
51585
+ const end = looseCloseEnd(combined);
51586
+ if (end >= 0) {
51587
+ drop(combined.slice(0, end));
51548
51588
  swallowed = "";
51549
51589
  swallowUntilClose = false;
51550
51590
  buf = combined.slice(end);
@@ -51569,7 +51609,7 @@ function createTagEchoFilter(onDrop) {
51569
51609
  if (!m2) {
51570
51610
  const t = PARTIAL_TAIL.exec(buf);
51571
51611
  if (t) {
51572
- const definite = /^\x3cacp\s/.test(t[0]) || /^\x3c\/acp/.test(t[0]);
51612
+ const definite = DEFINITE_TAIL.test(t[0]);
51573
51613
  const cap = definite ? TAG_OPEN_CAP : HOLD_LIMIT;
51574
51614
  if (t[0].length <= cap) {
51575
51615
  held = t[0];
@@ -51588,7 +51628,7 @@ function createTagEchoFilter(onDrop) {
51588
51628
  drop(m2[0]);
51589
51629
  out += buf.slice(0, m2.index);
51590
51630
  buf = buf.slice(m2.index + m2[0].length);
51591
- if (m2 === o && /^\x3cacp\s/.test(m2[0])) {
51631
+ if (m2 === o && OPEN_WITH_ATTRS.test(m2[0])) {
51592
51632
  swallowUntilClose = true;
51593
51633
  swallowed = "";
51594
51634
  }
@@ -51597,9 +51637,12 @@ function createTagEchoFilter(onDrop) {
51597
51637
  };
51598
51638
  return {
51599
51639
  push(delta) {
51640
+ inputChars += delta.length;
51600
51641
  const chunk = held + delta;
51601
51642
  held = "";
51602
- return process2(chunk);
51643
+ const r = process2(chunk);
51644
+ outputChars += r.length;
51645
+ return r;
51603
51646
  },
51604
51647
  flush() {
51605
51648
  const rest = swallowed + held;
@@ -51607,27 +51650,36 @@ function createTagEchoFilter(onDrop) {
51607
51650
  swallowed = "";
51608
51651
  held = "";
51609
51652
  swallowUntilClose = false;
51653
+ let result;
51610
51654
  if (wasSwallowing) {
51611
51655
  if (rest.length > 0) drop(rest);
51612
- return "";
51613
- }
51614
- const t = new RegExp(TRUNC_OPEN.source).exec(rest);
51615
- if (t) {
51616
- drop(t[0]);
51617
- return rest.slice(0, t.index);
51618
- }
51619
- const tc = new RegExp(TRUNC_CLOSE.source).exec(rest);
51620
- if (tc) {
51621
- drop(tc[0]);
51622
- return rest.slice(0, tc.index);
51656
+ result = "";
51657
+ } else {
51658
+ const t = new RegExp(TRUNC_OPEN.source).exec(rest);
51659
+ if (t) {
51660
+ drop(t[0]);
51661
+ result = rest.slice(0, t.index);
51662
+ } else {
51663
+ const tc = new RegExp(TRUNC_CLOSE.source).exec(rest);
51664
+ if (tc) {
51665
+ drop(tc[0]);
51666
+ result = rest.slice(0, tc.index);
51667
+ } else {
51668
+ result = rest;
51669
+ }
51670
+ }
51623
51671
  }
51624
- return rest;
51672
+ outputChars += result.length;
51673
+ return result;
51625
51674
  },
51626
51675
  dropped() {
51627
51676
  return droppedAny;
51628
51677
  },
51629
51678
  pending() {
51630
51679
  return held.length > 0 || swallowUntilClose;
51680
+ },
51681
+ stats() {
51682
+ return { inputChars, outputChars, dropped: droppedAny };
51631
51683
  }
51632
51684
  };
51633
51685
  }
@@ -51932,16 +51984,41 @@ function splitChunks(messages, startIdx, endIdx, budget, minUnits, countText = d
51932
51984
  }
51933
51985
  return chunks;
51934
51986
  }
51935
- function summaryPayload(protocol, model, system, content, stream2) {
51987
+ function summaryPayload(protocol, model, system, content, stream2, includeMaxOutputTokens) {
51936
51988
  if (protocol === "anthropic") {
51937
51989
  return { model, max_tokens: MAX_SUMMARY_OUTPUT_TOKENS, system, messages: [{ role: "user", content }], stream: stream2 };
51938
51990
  }
51939
51991
  if (protocol === "openai") {
51940
51992
  return { model, max_tokens: MAX_SUMMARY_OUTPUT_TOKENS, messages: [{ role: "system", content: system }, { role: "user", content }], stream: stream2 };
51941
51993
  }
51942
- return { model, max_output_tokens: MAX_SUMMARY_OUTPUT_TOKENS, instructions: system, input: [{ role: "user", content }], stream: stream2, store: false };
51994
+ const payload = { model, instructions: system, input: [{ role: "user", content }], stream: stream2, store: false };
51995
+ if (includeMaxOutputTokens) payload.max_output_tokens = MAX_SUMMARY_OUTPUT_TOKENS;
51996
+ return payload;
51943
51997
  }
51944
51998
  var STREAM_REQUIRED_RE = /\bstream\b[^\n]{0,60}\btrue\b/i;
51999
+ var MAX_OUTPUT_TOKENS_REJECTED_RE = /\bmax_output_tokens\b/i;
52000
+ function noMaxOutputTokensKey(deps) {
52001
+ return `${deps.url}\0${deps.model}`;
52002
+ }
52003
+ function hasLearnedNoMaxOutputTokens(deps) {
52004
+ const learned = deps.session.metadata.preflightNoMaxOutputTokens;
52005
+ return typeof learned === "object" && learned !== null && learned[noMaxOutputTokensKey(deps)] === true;
52006
+ }
52007
+ function rememberNoMaxOutputTokens(deps) {
52008
+ const learned = deps.session.metadata.preflightNoMaxOutputTokens;
52009
+ const map = typeof learned === "object" && learned !== null ? learned : {};
52010
+ map[noMaxOutputTokensKey(deps)] = true;
52011
+ deps.session.metadata.preflightNoMaxOutputTokens = map;
52012
+ }
52013
+ var SUMMARY_STRIP_HEADERS = /* @__PURE__ */ new Set(["x-openai-internal-codex-responses-lite"]);
52014
+ function summaryHeaders(deps) {
52015
+ const headers = {};
52016
+ for (const [k2, v2] of Object.entries(deps.headers)) {
52017
+ if (SUMMARY_STRIP_HEADERS.has(k2.toLowerCase())) continue;
52018
+ headers[k2] = v2;
52019
+ }
52020
+ return headers;
52021
+ }
51945
52022
  function extractSummaryFromSse(protocol, text) {
51946
52023
  let out = "";
51947
52024
  for (const line of text.split("\n")) {
@@ -52005,25 +52082,39 @@ async function summarizeRange(deps, content, startRef, endRef) {
52005
52082
  const system = buildCompressSystemPrompt(deps.prompts) + `
52006
52083
 
52007
52084
  TASK: The conversation segment below (messages ${startRef}\u2013${endRef}) must be compressed because the session context exceeds the current model's window. Write a tier-1 compression summary of the segment following every rule above. Output ONLY the summary text \u2014 no preamble, no closing remarks, no tool calls.`;
52008
- const learned = deps.session.metadata.preflightStreamSummary === true;
52009
- try {
52010
- return await requestSummary(deps, system, content, learned);
52011
- } catch (err2) {
52012
- if (err2 instanceof UpstreamHttpError && err2.status === 400 && !learned && STREAM_REQUIRED_RE.test(err2.body)) {
52013
- deps.session.metadata.preflightStreamSummary = true;
52014
- deps.log("info", "[preflight] upstream requires stream for summaries; retrying with SSE (learned for this session)");
52015
- return await requestSummary(deps, system, content, true);
52085
+ let stream2 = deps.session.metadata.preflightStreamSummary === true;
52086
+ let includeMaxOutputTokens = !(deps.protocol === "responses" && hasLearnedNoMaxOutputTokens(deps));
52087
+ for (; ; ) {
52088
+ try {
52089
+ return await requestSummary(deps, system, content, stream2, includeMaxOutputTokens);
52090
+ } catch (err2) {
52091
+ if (err2 instanceof UpstreamHttpError && err2.status === 400) {
52092
+ let adapted = false;
52093
+ if (!stream2 && STREAM_REQUIRED_RE.test(err2.body)) {
52094
+ deps.session.metadata.preflightStreamSummary = true;
52095
+ stream2 = true;
52096
+ adapted = true;
52097
+ deps.log("info", "[preflight] upstream requires stream for summaries; retrying with SSE (learned for this session)");
52098
+ }
52099
+ if (deps.protocol === "responses" && includeMaxOutputTokens && MAX_OUTPUT_TOKENS_REJECTED_RE.test(err2.body)) {
52100
+ rememberNoMaxOutputTokens(deps);
52101
+ includeMaxOutputTokens = false;
52102
+ adapted = true;
52103
+ deps.log("info", `[preflight] upstream rejects max_output_tokens for summaries (model=${deps.model}); retrying without it (learned for this session+upstream+model)`);
52104
+ }
52105
+ if (adapted) continue;
52106
+ }
52107
+ throw err2;
52016
52108
  }
52017
- throw err2;
52018
52109
  }
52019
52110
  }
52020
- async function requestSummary(deps, system, content, stream2) {
52111
+ async function requestSummary(deps, system, content, stream2, includeMaxOutputTokens) {
52021
52112
  const { response, clearTimer } = await fetchWithRetry(
52022
52113
  deps.url,
52023
52114
  {
52024
52115
  method: "POST",
52025
- headers: { "content-type": "application/json", ...deps.headers },
52026
- body: JSON.stringify(summaryPayload(deps.protocol, deps.model, system, content, stream2)),
52116
+ headers: { "content-type": "application/json", ...summaryHeaders(deps) },
52117
+ body: JSON.stringify(summaryPayload(deps.protocol, deps.model, system, content, stream2, includeMaxOutputTokens)),
52027
52118
  dispatcher: proxyDispatcher(deps.proxyUrl)
52028
52119
  },
52029
52120
  void 0,
@@ -53660,6 +53751,18 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
53660
53751
  }
53661
53752
  }
53662
53753
 
53754
+ // src/degenerate-turn.ts
53755
+ function degenerateTurnWarning(o) {
53756
+ if ((o.reason ?? o.terminalReason) !== o.terminalReason) return null;
53757
+ if (o.toolCalls > 0) return null;
53758
+ if (o.text.outputChars > 0) return null;
53759
+ const bits = [];
53760
+ if (o.sawThinking) bits.push("thinking present");
53761
+ if (o.text.dropped && o.text.inputChars > 0) bits.push(`${o.text.inputChars} chars of emitted text stripped as render-tag echo`);
53762
+ else bits.push("no visible text emitted");
53763
+ return `[degenerate-turn] ${o.wire}: turn ended ${o.terminalReason} with zero visible text and zero tool calls (${bits.join("; ")}) \u2014 the agent receives an empty turn and may stall until nudged (#673)`;
53764
+ }
53765
+
53663
53766
  // src/loop/adapter-responses.ts
53664
53767
  var RESPONSES_ITEM_ID_MAX = 64;
53665
53768
  function normalizeResponsesMessageItems(input) {
@@ -53723,7 +53826,7 @@ function dropWhitespaceResponsesMessages(input) {
53723
53826
  }
53724
53827
  return dropped;
53725
53828
  }
53726
- var RENDER_TAG_RE = /\x3cacp\s[^>]*\x3e[^<]*\x3c\/acp\x3e|\x3cacp\s[^>]*\/\x3e/g;
53829
+ var RENDER_TAG_RE = new RegExp("<" + ACP_NAME_ALT + "\\s[^>]*>[^<]*<\\/" + ACP_NAME_ALT + ">|<" + ACP_NAME_ALT + "\\s[^>]*\\/>", "g");
53727
53830
  function stripRenderTags(text) {
53728
53831
  return text.replace(RENDER_TAG_RE, "");
53729
53832
  }
@@ -53938,6 +54041,17 @@ data: ${JSON.stringify({ type: "response.created", response: { id: createdRespId
53938
54041
  yield { kind: "text", delta: tail, ...raw ? { raw } : {} };
53939
54042
  }
53940
54043
  };
54044
+ let sawReasoning = false;
54045
+ let toolCallsEmitted = 0;
54046
+ let degenerateWarned = false;
54047
+ const maybeWarnDegenerate = (reason) => {
54048
+ if (degenerateWarned) return;
54049
+ const msg = degenerateTurnWarning({ reason, terminalReason: "completed", toolCalls: toolCallsEmitted, text: tagFilter.stats(), sawThinking: sawReasoning, wire: "responses" });
54050
+ if (msg) {
54051
+ degenerateWarned = true;
54052
+ log("warn", msg);
54053
+ }
54054
+ };
53941
54055
  for await (const eventStr of iterSseEvents(upstream)) {
53942
54056
  const explicitType = extractEventType(eventStr);
53943
54057
  const dataLine = extractDataLine(eventStr);
@@ -53950,6 +54064,7 @@ data: ${JSON.stringify({ type: "response.created", response: { id: createdRespId
53950
54064
  }
53951
54065
  const type = explicitType ?? (typeof obj.type === "string" ? obj.type : null);
53952
54066
  if (!type) continue;
54067
+ if (type.startsWith("response.reasoning")) sawReasoning = true;
53953
54068
  const rawBuf = Buffer.from(eventStr + "\n\n", "utf8");
53954
54069
  if (round === 1 && typeof obj.output_index === "number") {
53955
54070
  outputIndex = Math.max(outputIndex, obj.output_index + 1);
@@ -54033,6 +54148,7 @@ data: ${JSON.stringify({ type: "response.created", response: { id: createdRespId
54033
54148
  if (fc) {
54034
54149
  if (typeof item.arguments === "string" && item.arguments) fc.arguments = item.arguments;
54035
54150
  pending.delete(itemId);
54151
+ toolCallsEmitted++;
54036
54152
  yield {
54037
54153
  kind: "tool_call",
54038
54154
  name: fc.name,
@@ -54041,6 +54157,7 @@ data: ${JSON.stringify({ type: "response.created", response: { id: createdRespId
54041
54157
  };
54042
54158
  } else {
54043
54159
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
54160
+ toolCallsEmitted++;
54044
54161
  yield {
54045
54162
  kind: "tool_call",
54046
54163
  name: typeof item.name === "string" ? item.name : "",
@@ -54077,6 +54194,7 @@ data: ${JSON.stringify({ type: "response.created", response: { id: createdRespId
54077
54194
  outputTokens: typeof respUsage?.output_tokens === "number" ? respUsage.output_tokens : void 0,
54078
54195
  cachedTokens: typeof pd?.cached_tokens === "number" ? pd.cached_tokens : void 0
54079
54196
  };
54197
+ maybeWarnDegenerate("completed");
54080
54198
  yield { kind: "done", finishReason: "completed" };
54081
54199
  } else if (type === "response.incomplete") {
54082
54200
  yield* flushFilter();
@@ -54394,11 +54512,23 @@ function createOpenaiAdapter(requestBody, clientSystem, hostCredit = 0, absorbNa
54394
54512
  yield { kind: "text", delta: tail, raw: buildContent(tail) };
54395
54513
  }
54396
54514
  };
54515
+ let sawReasoning = false;
54516
+ let toolCallsEmitted = 0;
54517
+ let degenerateWarned = false;
54518
+ const maybeWarnDegenerate = (reason) => {
54519
+ if (degenerateWarned) return;
54520
+ const msg = degenerateTurnWarning({ reason, terminalReason: "stop", toolCalls: toolCallsEmitted, text: tagFilter.stats(), sawThinking: sawReasoning, wire: "openai" });
54521
+ if (msg) {
54522
+ degenerateWarned = true;
54523
+ log("warn", msg);
54524
+ }
54525
+ };
54397
54526
  const rawToolChunks = [];
54398
54527
  let sawRealToolCall = false;
54399
54528
  const flushPendingAsStructured = function* () {
54400
54529
  for (const [, tc] of pending) {
54401
54530
  if (tc.name.length > 0 || tc.id.length > 0) {
54531
+ toolCallsEmitted++;
54402
54532
  yield {
54403
54533
  kind: "tool_call",
54404
54534
  name: tc.name,
@@ -54421,6 +54551,7 @@ function createOpenaiAdapter(requestBody, clientSystem, hostCredit = 0, absorbNa
54421
54551
  }
54422
54552
  for (const [idx, tc] of pending) {
54423
54553
  if (realIndexes.has(idx) && (tc.name.length > 0 || tc.id.length > 0)) {
54554
+ toolCallsEmitted++;
54424
54555
  yield {
54425
54556
  kind: "tool_call",
54426
54557
  name: tc.name,
@@ -54440,6 +54571,7 @@ function createOpenaiAdapter(requestBody, clientSystem, hostCredit = 0, absorbNa
54440
54571
  }
54441
54572
  for (const [idx, tc] of pending) {
54442
54573
  if (!realIndexes.has(idx) && tc.name.length > 0) {
54574
+ toolCallsEmitted++;
54443
54575
  yield {
54444
54576
  kind: "tool_call",
54445
54577
  name: tc.name,
@@ -54464,6 +54596,7 @@ function createOpenaiAdapter(requestBody, clientSystem, hostCredit = 0, absorbNa
54464
54596
  if (sawRealToolCall) {
54465
54597
  yield { kind: "meta", chunk: Buffer.from(eventStr + "\n\n", "utf8") };
54466
54598
  }
54599
+ maybeWarnDegenerate("stop");
54467
54600
  yield { kind: "done", finishReason: "stop", ...sawRealToolCall ? { suppressCompletion: true } : {} };
54468
54601
  continue;
54469
54602
  }
@@ -54509,9 +54642,11 @@ function createOpenaiAdapter(requestBody, clientSystem, hostCredit = 0, absorbNa
54509
54642
  if (sawRealToolCall) {
54510
54643
  const chunk = patchUsageChunk(eventStr, parsed, u2 ?? {}, hostCredit);
54511
54644
  yield { kind: "meta", chunk };
54645
+ maybeWarnDegenerate(finishReason);
54512
54646
  yield { kind: "done", finishReason, suppressCompletion: true };
54513
54647
  continue;
54514
54648
  } else {
54649
+ maybeWarnDegenerate(finishReason);
54515
54650
  yield {
54516
54651
  kind: "done",
54517
54652
  finishReason: hadToolCalls && finishReason === "stop" ? "tool_calls" : finishReason
@@ -54520,6 +54655,7 @@ function createOpenaiAdapter(requestBody, clientSystem, hostCredit = 0, absorbNa
54520
54655
  }
54521
54656
  if (!delta) continue;
54522
54657
  if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
54658
+ sawReasoning = true;
54523
54659
  yield { kind: "reasoning", delta: delta.reasoning_content, raw: finishReason ? stripFinishReasonChunk(rawBuf) : rawBuf };
54524
54660
  }
54525
54661
  if (delta.tool_calls) {
@@ -54797,6 +54933,17 @@ ${systemPrompt}` : systemPrompt;
54797
54933
  log("warn", `[tag-echo] stripped model-emitted render tag: ${snippet.slice(0, 80).replace(/\n/g, " ")}`);
54798
54934
  });
54799
54935
  let lastTextIndex = null;
54936
+ let sawThinking = false;
54937
+ let toolCallsEmitted = 0;
54938
+ let degenerateWarned = false;
54939
+ const maybeWarnDegenerate = (reason) => {
54940
+ if (degenerateWarned) return;
54941
+ const msg = degenerateTurnWarning({ reason, terminalReason: "end_turn", toolCalls: toolCallsEmitted, text: tagFilter.stats(), sawThinking, wire: "anthropic" });
54942
+ if (msg) {
54943
+ degenerateWarned = true;
54944
+ log("warn", msg);
54945
+ }
54946
+ };
54800
54947
  for await (const eventStr of iterSseEvents2(upstream)) {
54801
54948
  const parsed = parseAnthropicSse(eventStr);
54802
54949
  if (!parsed) continue;
@@ -54833,7 +54980,10 @@ ${systemPrompt}` : systemPrompt;
54833
54980
  const id = typeof block.id === "string" ? block.id : `toolu_${upstreamIndex}`;
54834
54981
  pending.set(upstreamIndex, { id, name, json: "" });
54835
54982
  } else {
54836
- if (block.type === "thinking" || block.type === "redacted_thinking") thinkingIndexes.add(upstreamIndex);
54983
+ if (block.type === "thinking" || block.type === "redacted_thinking") {
54984
+ thinkingIndexes.add(upstreamIndex);
54985
+ sawThinking = true;
54986
+ }
54837
54987
  const ci2 = clientIndex++;
54838
54988
  indexMap.set(upstreamIndex, ci2);
54839
54989
  openBlocks.push(ci2);
@@ -54878,6 +55028,7 @@ ${systemPrompt}` : systemPrompt;
54878
55028
  const tb = pending.get(upstreamIndex);
54879
55029
  if (tb) {
54880
55030
  pending.delete(upstreamIndex);
55031
+ toolCallsEmitted++;
54881
55032
  yield {
54882
55033
  kind: "tool_call",
54883
55034
  name: tb.name,
@@ -54924,6 +55075,7 @@ ${systemPrompt}` : systemPrompt;
54924
55075
  cachedTokens: roundCached
54925
55076
  };
54926
55077
  }
55078
+ maybeWarnDegenerate(stopReason);
54927
55079
  yield { kind: "done", finishReason: stopReason };
54928
55080
  } else if (type === "message_stop") {
54929
55081
  if (lastTextIndex !== null) {
@@ -54942,6 +55094,7 @@ ${systemPrompt}` : systemPrompt;
54942
55094
  cachedTokens: roundCached
54943
55095
  };
54944
55096
  }
55097
+ maybeWarnDegenerate(stopReason);
54945
55098
  yield { kind: "done", finishReason: stopReason ?? "end_turn" };
54946
55099
  } else if (round === 1) {
54947
55100
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
@@ -56552,11 +56705,18 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56552
56705
 
56553
56706
  `;
56554
56707
  };
56708
+ let sawToolUse = false;
56709
+ let sawThinking = false;
56710
+ let visibleTextChars = 0;
56711
+ let finalFinishReason;
56555
56712
  const flushTails = () => {
56556
56713
  let out = "";
56557
56714
  for (const s3 of streams.values()) {
56558
56715
  const tail = s3.filter.flush();
56559
- if (tail.length > 0) out += syntheticTail(s3.field, s3.index, tail);
56716
+ if (tail.length > 0) {
56717
+ out += syntheticTail(s3.field, s3.index, tail);
56718
+ if (s3.field === "content" || s3.field === "text") visibleTextChars += tail.length;
56719
+ }
56560
56720
  }
56561
56721
  return out;
56562
56722
  };
@@ -56579,6 +56739,28 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56579
56739
  reason: "plugin chat passthrough stream ended without a completion event"
56580
56740
  });
56581
56741
  };
56742
+ const maybeWarnDegenerate = () => {
56743
+ if (!sawTerminal || res.destroyed || res.writableEnded) return;
56744
+ let inputChars = 0;
56745
+ let dropped = false;
56746
+ for (const s3 of streams.values()) {
56747
+ const st2 = s3.filter.stats();
56748
+ inputChars += st2.inputChars;
56749
+ dropped = dropped || st2.dropped;
56750
+ }
56751
+ const msg = degenerateTurnWarning({
56752
+ reason: finalFinishReason,
56753
+ terminalReason: protocol === "anthropic" ? "end_turn" : "stop",
56754
+ toolCalls: sawToolUse ? 1 : 0,
56755
+ text: { inputChars, outputChars: visibleTextChars, dropped },
56756
+ sawThinking,
56757
+ wire: `plugin-passthrough-${protocol}`
56758
+ });
56759
+ if (msg) {
56760
+ log("warn", msg);
56761
+ log2?.(msg);
56762
+ }
56763
+ };
56582
56764
  const pushField = (field, index, text) => {
56583
56765
  const s3 = filterFor(field, index);
56584
56766
  const clean = s3.filter.push(text);
@@ -56598,21 +56780,28 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56598
56780
  let hadText = false;
56599
56781
  for (let ci2 = 0; ci2 < choices.length; ci2++) {
56600
56782
  const ch = choices[ci2];
56783
+ if (ch && typeof ch["finish_reason"] === "string") finalFinishReason = ch["finish_reason"];
56601
56784
  const d = ch?.["delta"];
56602
56785
  if (!d || typeof d !== "object") continue;
56603
56786
  const dd = d;
56787
+ if (dd["tool_calls"] !== void 0) sawToolUse = true;
56604
56788
  for (const field of ["content", "reasoning_content", "reasoning"]) {
56605
56789
  const v2 = dd[field];
56606
56790
  if (typeof v2 !== "string") continue;
56607
56791
  hadText = true;
56792
+ if (field !== "content" && v2.length > 0) sawThinking = true;
56608
56793
  if (!mayStartRenderTag(v2) && !anyPending()) {
56609
56794
  if (v2.length > 0) keptText = true;
56795
+ if (field === "content") visibleTextChars += v2.length;
56610
56796
  continue;
56611
56797
  }
56612
56798
  const index = typeof ch?.["index"] === "number" ? ch["index"] : ci2;
56613
56799
  const [clean, changed] = pushField(field, index, v2);
56614
56800
  if (clean.length === 0) droppedText = true;
56615
- else keptText = true;
56801
+ else {
56802
+ keptText = true;
56803
+ if (field === "content") visibleTextChars += clean.length;
56804
+ }
56616
56805
  if (changed) {
56617
56806
  if (!rebuilt) {
56618
56807
  rebuilt = { ...ev, choices: choices.map((c) => ({ ...c, delta: { ...c["delta"] } })) };
@@ -56631,6 +56820,13 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56631
56820
  return rawEvent + "\n\n";
56632
56821
  };
56633
56822
  const processAnthropic = (ev, rawEvent) => {
56823
+ if (ev["type"] === "content_block_start") {
56824
+ const cb = ev["content_block"];
56825
+ const bt2 = cb && typeof cb === "object" ? cb["type"] : void 0;
56826
+ if (bt2 === "tool_use") sawToolUse = true;
56827
+ else if (bt2 === "thinking" || bt2 === "redacted_thinking") sawThinking = true;
56828
+ return anyPending() ? flushTails() + rawEvent + "\n\n" : rawEvent + "\n\n";
56829
+ }
56634
56830
  if (ev["type"] !== "content_block_delta") {
56635
56831
  return anyPending() ? flushTails() + rawEvent + "\n\n" : rawEvent + "\n\n";
56636
56832
  }
@@ -56641,11 +56837,17 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56641
56837
  return rawEvent + "\n\n";
56642
56838
  }
56643
56839
  const raw = d[field];
56840
+ if (field === "thinking" && raw.length > 0) sawThinking = true;
56644
56841
  if (!mayStartRenderTag(raw) && !anyPending()) {
56842
+ if (field === "text" && raw.length > 0) visibleTextChars += raw.length;
56645
56843
  return rawEvent + "\n\n";
56646
56844
  }
56647
56845
  const [clean, changed] = pushField(field, index, raw);
56648
- if (!changed) return rawEvent + "\n\n";
56846
+ if (!changed) {
56847
+ if (field === "text" && raw.length > 0) visibleTextChars += raw.length;
56848
+ return rawEvent + "\n\n";
56849
+ }
56850
+ if (field === "text" && clean.length > 0) visibleTextChars += clean.length;
56649
56851
  if (clean.length === 0 && Object.keys(d ?? {}).length <= 2) return "";
56650
56852
  return rebuildEvent(rawEvent, { ...ev, delta: { ...d, [field]: clean } });
56651
56853
  };
@@ -56676,6 +56878,10 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56676
56878
  continue;
56677
56879
  }
56678
56880
  if (ev["type"] === "message_stop") sawTerminal = true;
56881
+ if (ev["type"] === "message_delta") {
56882
+ const d = ev["delta"];
56883
+ if (d && typeof d["stop_reason"] === "string") finalFinishReason = d["stop_reason"];
56884
+ }
56679
56885
  const sample = usageFromSseEvent(ev);
56680
56886
  if (sample) mergeUsageSample(acc, sample);
56681
56887
  let backfilled = false;
@@ -56699,6 +56905,7 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
56699
56905
  if (rest.length > 0 && !res.destroyed && !res.writableEnded) await write(rest);
56700
56906
  settleUsage();
56701
56907
  maybeNoteTruncated();
56908
+ maybeWarnDegenerate();
56702
56909
  } catch (e) {
56703
56910
  settleUsage();
56704
56911
  maybeNoteTruncated();
@@ -56735,6 +56942,9 @@ async function pipePluginResponsesWithStrip(stream2, res, session, log2) {
56735
56942
  log("warn", `[tag-echo] stripped model-emitted render tag (plugin passthrough): ${snippet.slice(0, 80).replace(/\n/g, " ")}`);
56736
56943
  log2?.(`[tag-echo] stripped model-emitted render tag from plugin passthrough text`);
56737
56944
  });
56945
+ let sawFunctionCall = false;
56946
+ let sawReasoning = false;
56947
+ let responseStatus;
56738
56948
  const write = (s3) => {
56739
56949
  if (!res.write(Buffer.from(s3, "utf8"))) {
56740
56950
  return new Promise((r) => res.once("drain", () => r()));
@@ -56755,6 +56965,22 @@ async function pipePluginResponsesWithStrip(stream2, res, session, log2) {
56755
56965
  reason: "plugin responses passthrough stream ended without a completion event"
56756
56966
  });
56757
56967
  };
56968
+ const maybeWarnDegenerate = () => {
56969
+ if (!sawTerminal || res.destroyed || res.writableEnded) return;
56970
+ const st2 = tagFilter.stats();
56971
+ const msg = degenerateTurnWarning({
56972
+ reason: responseStatus,
56973
+ terminalReason: "completed",
56974
+ toolCalls: sawFunctionCall ? 1 : 0,
56975
+ text: st2,
56976
+ sawThinking: sawReasoning,
56977
+ wire: "plugin-passthrough-responses"
56978
+ });
56979
+ if (msg) {
56980
+ log("warn", msg);
56981
+ log2?.(msg);
56982
+ }
56983
+ };
56758
56984
  let lastDeltaMeta = null;
56759
56985
  const flushTail = (after) => {
56760
56986
  const tail = tagFilter.flush();
@@ -56794,6 +57020,16 @@ async function pipePluginResponsesWithStrip(stream2, res, session, log2) {
56794
57020
  const sample = usageFromSseEvent(ev);
56795
57021
  if (sample) mergeUsageSample(acc, sample);
56796
57022
  const type = ev["type"];
57023
+ if (typeof type === "string") {
57024
+ if (type.startsWith("response.reasoning")) sawReasoning = true;
57025
+ if (type === "response.output_item.added" || type === "response.output_item.done") {
57026
+ const item = ev["item"];
57027
+ const it2 = item?.["type"];
57028
+ if (it2 === "function_call" || it2 === "custom_tool_call") sawFunctionCall = true;
57029
+ }
57030
+ const resp = ev["response"];
57031
+ if (resp && typeof resp["status"] === "string") responseStatus = resp["status"];
57032
+ }
56797
57033
  if (type === "response.output_text.done" || type === "response.content_part.done" || type === "response.output_item.done" || type === "response.completed" || type === "response.failed" || type === "response.incomplete") {
56798
57034
  if (type === "response.completed" || type === "response.failed" || type === "response.incomplete") sawTerminal = true;
56799
57035
  let evOut = ev;
@@ -56839,6 +57075,7 @@ async function pipePluginResponsesWithStrip(stream2, res, session, log2) {
56839
57075
  const rest = flushTail("");
56840
57076
  if (rest.length > 0) await write(rest);
56841
57077
  }
57078
+ maybeWarnDegenerate();
56842
57079
  settleUsage();
56843
57080
  maybeNoteTruncated();
56844
57081
  } catch (e) {
@@ -58914,6 +59151,7 @@ async function handle(req, res, opts, core, config, log2, instanceId, instanceSt
58914
59151
  try {
58915
59152
  const result = await fetchWithTimeout(targetUrl2, {
58916
59153
  method: "HEAD",
59154
+ redirect: "follow",
58917
59155
  ...proxyUrl ? { dispatcher: proxyDispatcher(proxyUrl, 15e3) } : {}
58918
59156
  }, 15e3);
58919
59157
  result.clearTimer();
@@ -59110,7 +59348,11 @@ ${bodyBuffer.toString("utf8")}`);
59110
59348
  }
59111
59349
  }
59112
59350
  let prepared = null;
59113
- if (!opts.passthrough && hopMarker === void 0 && protocol && parsed && typeof parsed === "object") {
59351
+ const routePassthrough = !opts.passthrough && findRoute(opts.routes, route?.rewrittenUrl)?.passthrough === true;
59352
+ if (routePassthrough && hopMarker === void 0 && protocol && parsed) {
59353
+ log2("info", `[route-passthrough] ${maskUrlsInText(route?.rewrittenUrl ?? "")} matches a passthrough route \u2014 forwarding verbatim, kernel bypassed`);
59354
+ }
59355
+ if (!opts.passthrough && !routePassthrough && hopMarker === void 0 && protocol && parsed && typeof parsed === "object") {
59114
59356
  const sessionHeader = headerValue2(req, opts.sessionHeader);
59115
59357
  let pluginAgent = pluginAgentHeader(req.headers);
59116
59358
  let pluginConversation = pluginConversationHeader(req.headers);
@@ -59371,7 +59613,7 @@ ${bodyBuffer.toString("utf8")}`);
59371
59613
  }
59372
59614
  }
59373
59615
  if (!prepared) {
59374
- if (protocol === null && !opts.passthrough && !isModelDiscoveryPath(urlPath)) {
59616
+ if (protocol === null && !opts.passthrough && !routePassthrough && !isModelDiscoveryPath(urlPath)) {
59375
59617
  logUnrecognizedPath(log2, req.url ?? "");
59376
59618
  }
59377
59619
  await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, instanceId, void 0);
@@ -59473,9 +59715,11 @@ function diagNudge(turn, sessionId, tokenCount, limit, model, willInject) {
59473
59715
  const modelTag = model ? ` model=${model}` : "";
59474
59716
  return `[${sessionId}] nudge ${inject}: usage=${pct2} (${tokenCount}/${limit}), growth=${growth}/${floor} (ref=${ref}, interval=${interval}), pendingT1=${pendingT1}/${interval}${modelTag}, reason="${n.reason.slice(0, 120)}"`;
59475
59717
  }
59476
- function armHostUsageCredit(session, originalMessages, processedMessages, log2) {
59718
+ function armHostUsageCredit(session, originalMessages, processedMessages, headers, hostUsageCredit, log2) {
59477
59719
  session.hostCreditTokens = 0;
59720
+ if (hostUsageCredit === "off") return;
59478
59721
  if (session.metadata.pluginAgent === "pi" || session.metadata.pluginAgent === "omp") return;
59722
+ if (isCodexClient(headers)) return;
59479
59723
  session.hostCreditTokens = processedMessages.length > 0 ? Math.max(0, estimateCoreMessages(originalMessages) - estimateCoreMessages(processedMessages)) : 0;
59480
59724
  if (session.hostCreditTokens > 0) {
59481
59725
  log2("info", `[${session.id}] host usage backfill armed: +${session.hostCreditTokens} tok (forwarded view is folded); host usage will report the uncompressed baseline`);
@@ -59556,7 +59800,7 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
59556
59800
  markDirty(session);
59557
59801
  const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
59558
59802
  delete rebuilt.prompt_cache_key;
59559
- armHostUsageCredit(session, originalMessages, processedMessages, log2);
59803
+ armHostUsageCredit(session, originalMessages, processedMessages, req.headers, opts.hostUsageCredit, log2);
59560
59804
  return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: injectTools, pluginMode, nudge, prompts, renderTags: "text-only" };
59561
59805
  }
59562
59806
  var OUTPUT_CLAMP_MARGIN_PCT = 0.05;
@@ -59688,7 +59932,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
59688
59932
  if (stream2 && rebuilt.stream_options === void 0) {
59689
59933
  rebuilt.stream_options = { include_usage: true };
59690
59934
  }
59691
- armHostUsageCredit(session, originalMessages, processedMessages, log2);
59935
+ armHostUsageCredit(session, originalMessages, processedMessages, req.headers, opts.hostUsageCredit, log2);
59692
59936
  if (!isTitleGen && openaiOutboundSystem !== void 0) {
59693
59937
  session.metadata.systemPromptTokens = countSystemAndToolsTokens(openaiOutboundSystem, toolsOut);
59694
59938
  }
@@ -59840,7 +60084,7 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
59840
60084
  });
59841
60085
  log2("info", `[${sessionId}] responses forward tools=[${fwdTools.join(",")}] injectTool=${injectTools}${pluginMode ? " (plugin mode: wire injection suppressed)" : ""} NO_INJECT_TOOL=${!!process.env.ACP_NO_INJECT_TOOL} NO_COMPRESS_PROMPT=${!!process.env.ACP_NO_COMPRESS_PROMPT}`);
59842
60086
  }
59843
- armHostUsageCredit(session, originalMessages, processedMessages, log2);
60087
+ armHostUsageCredit(session, originalMessages, processedMessages, req.headers, opts.hostUsageCredit, log2);
59844
60088
  if (transformOk) {
59845
60089
  session.metadata.systemPromptTokens = countSystemAndToolsTokens(responsesDevContent ?? "", toolsOut);
59846
60090
  }