billion-context 0.1.97 → 0.1.98-pr.665.492

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
@@ -48726,6 +48726,7 @@ function mergeCompress(global2, provider, model) {
48726
48726
  const promptLevels = [global2?.prompts, provider?.prompts, model?.prompts].filter(Boolean);
48727
48727
  const rangeOf = (s3) => s3?.minCompressRangeChars ?? s3?.minCompressRange;
48728
48728
  const absorbLevels = [global2?.absorb, provider?.absorb, model?.absorb].filter(Boolean);
48729
+ const reasoningLevels = [global2?.reasoning, provider?.reasoning, model?.reasoning].filter(Boolean);
48729
48730
  return {
48730
48731
  modelContextLimit: pick("modelContextLimit"),
48731
48732
  maxContextLimit: pick("maxContextLimit"),
@@ -48739,7 +48740,11 @@ function mergeCompress(global2, provider, model) {
48739
48740
  acknowledgePromptsRisk: pick("acknowledgePromptsRisk"),
48740
48741
  absorb: absorbLevels.length > 0 ? Object.assign({}, ...absorbLevels) : void 0,
48741
48742
  stripImages: pick("stripImages"),
48742
- stripImagesKeepRecent: pick("stripImagesKeepRecent")
48743
+ stripImagesKeepRecent: pick("stripImagesKeepRecent"),
48744
+ // `reasoning` is a third nested-object field merged sub-field-wise
48745
+ // exactly like `absorb`/`prompts`: a model-level `threshold` must not
48746
+ // discard a provider-level `drop: false`.
48747
+ reasoning: reasoningLevels.length > 0 ? Object.assign({}, ...reasoningLevels) : void 0
48743
48748
  };
48744
48749
  }
48745
48750
  function resolveCompress(routes, upstreamUrl, model, global2) {
@@ -48819,6 +48824,53 @@ function resolveRequestConfig(base, routes, embeddedUrl, model, native, globalCo
48819
48824
  return applyCompressSettings(base, limit, compress);
48820
48825
  }
48821
48826
 
48827
+ // src/reasoning-drop.ts
48828
+ var DEFAULT_COMPRESS_REASONING = { drop: true, threshold: 2048 };
48829
+ function resolveReasoningDrop(cfg) {
48830
+ let threshold = DEFAULT_COMPRESS_REASONING.threshold;
48831
+ if (cfg?.threshold !== void 0) {
48832
+ const t = cfg.threshold;
48833
+ if (typeof t === "number" && Number.isFinite(t) && t >= 0) {
48834
+ threshold = Math.floor(t);
48835
+ }
48836
+ }
48837
+ return { drop: cfg?.drop !== false, threshold };
48838
+ }
48839
+ function dropCompressReasoning(messages, cfg) {
48840
+ const { drop, threshold } = resolveReasoningDrop(cfg);
48841
+ if (!drop || messages.length === 0) return messages;
48842
+ try {
48843
+ const last = messages.length - 1;
48844
+ const resultAt = /* @__PURE__ */ new Map();
48845
+ for (let i = 0; i <= last; i++) {
48846
+ const m2 = messages[i];
48847
+ if (m2.contentType === "tool-result" && typeof m2.toolCallId === "string" && !resultAt.has(m2.toolCallId)) {
48848
+ resultAt.set(m2.toolCallId, i);
48849
+ }
48850
+ }
48851
+ const dropIdx = /* @__PURE__ */ new Set();
48852
+ for (let i = 0; i <= last; i++) {
48853
+ const m2 = messages[i];
48854
+ if (m2.contentType !== "tool-call" || m2.toolName !== "compress") continue;
48855
+ const ri2 = typeof m2.toolCallId === "string" ? resultAt.get(m2.toolCallId) : void 0;
48856
+ if (ri2 === void 0 || ri2 <= i || ri2 >= last) continue;
48857
+ let total = 0;
48858
+ let j2 = i - 1;
48859
+ while (j2 >= 0 && messages[j2].contentType === "reasoning") {
48860
+ total += (messages[j2].text ?? "").length;
48861
+ j2--;
48862
+ }
48863
+ if (total > threshold) {
48864
+ for (let k2 = j2 + 1; k2 < i; k2++) dropIdx.add(k2);
48865
+ }
48866
+ }
48867
+ if (dropIdx.size === 0) return messages;
48868
+ return messages.filter((_2, idx) => !dropIdx.has(idx));
48869
+ } catch {
48870
+ return messages;
48871
+ }
48872
+ }
48873
+
48822
48874
  // src/strip-images.ts
48823
48875
  var DEFAULT_STRIP_IMAGES_KEEP_RECENT = 5;
48824
48876
  var IMAGE_PLACEHOLDER = "[image]";
@@ -51880,16 +51932,41 @@ function splitChunks(messages, startIdx, endIdx, budget, minUnits, countText = d
51880
51932
  }
51881
51933
  return chunks;
51882
51934
  }
51883
- function summaryPayload(protocol, model, system, content, stream2) {
51935
+ function summaryPayload(protocol, model, system, content, stream2, includeMaxOutputTokens) {
51884
51936
  if (protocol === "anthropic") {
51885
51937
  return { model, max_tokens: MAX_SUMMARY_OUTPUT_TOKENS, system, messages: [{ role: "user", content }], stream: stream2 };
51886
51938
  }
51887
51939
  if (protocol === "openai") {
51888
51940
  return { model, max_tokens: MAX_SUMMARY_OUTPUT_TOKENS, messages: [{ role: "system", content: system }, { role: "user", content }], stream: stream2 };
51889
51941
  }
51890
- return { model, max_output_tokens: MAX_SUMMARY_OUTPUT_TOKENS, instructions: system, input: [{ role: "user", content }], stream: stream2, store: false };
51942
+ const payload = { model, instructions: system, input: [{ role: "user", content }], stream: stream2, store: false };
51943
+ if (includeMaxOutputTokens) payload.max_output_tokens = MAX_SUMMARY_OUTPUT_TOKENS;
51944
+ return payload;
51891
51945
  }
51892
51946
  var STREAM_REQUIRED_RE = /\bstream\b[^\n]{0,60}\btrue\b/i;
51947
+ var MAX_OUTPUT_TOKENS_REJECTED_RE = /\bmax_output_tokens\b/i;
51948
+ function noMaxOutputTokensKey(deps) {
51949
+ return `${deps.url}\0${deps.model}`;
51950
+ }
51951
+ function hasLearnedNoMaxOutputTokens(deps) {
51952
+ const learned = deps.session.metadata.preflightNoMaxOutputTokens;
51953
+ return typeof learned === "object" && learned !== null && learned[noMaxOutputTokensKey(deps)] === true;
51954
+ }
51955
+ function rememberNoMaxOutputTokens(deps) {
51956
+ const learned = deps.session.metadata.preflightNoMaxOutputTokens;
51957
+ const map = typeof learned === "object" && learned !== null ? learned : {};
51958
+ map[noMaxOutputTokensKey(deps)] = true;
51959
+ deps.session.metadata.preflightNoMaxOutputTokens = map;
51960
+ }
51961
+ var SUMMARY_STRIP_HEADERS = /* @__PURE__ */ new Set(["x-openai-internal-codex-responses-lite"]);
51962
+ function summaryHeaders(deps) {
51963
+ const headers = {};
51964
+ for (const [k2, v2] of Object.entries(deps.headers)) {
51965
+ if (SUMMARY_STRIP_HEADERS.has(k2.toLowerCase())) continue;
51966
+ headers[k2] = v2;
51967
+ }
51968
+ return headers;
51969
+ }
51893
51970
  function extractSummaryFromSse(protocol, text) {
51894
51971
  let out = "";
51895
51972
  for (const line of text.split("\n")) {
@@ -51953,25 +52030,39 @@ async function summarizeRange(deps, content, startRef, endRef) {
51953
52030
  const system = buildCompressSystemPrompt(deps.prompts) + `
51954
52031
 
51955
52032
  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.`;
51956
- const learned = deps.session.metadata.preflightStreamSummary === true;
51957
- try {
51958
- return await requestSummary(deps, system, content, learned);
51959
- } catch (err2) {
51960
- if (err2 instanceof UpstreamHttpError && err2.status === 400 && !learned && STREAM_REQUIRED_RE.test(err2.body)) {
51961
- deps.session.metadata.preflightStreamSummary = true;
51962
- deps.log("info", "[preflight] upstream requires stream for summaries; retrying with SSE (learned for this session)");
51963
- return await requestSummary(deps, system, content, true);
52033
+ let stream2 = deps.session.metadata.preflightStreamSummary === true;
52034
+ let includeMaxOutputTokens = !(deps.protocol === "responses" && hasLearnedNoMaxOutputTokens(deps));
52035
+ for (; ; ) {
52036
+ try {
52037
+ return await requestSummary(deps, system, content, stream2, includeMaxOutputTokens);
52038
+ } catch (err2) {
52039
+ if (err2 instanceof UpstreamHttpError && err2.status === 400) {
52040
+ let adapted = false;
52041
+ if (!stream2 && STREAM_REQUIRED_RE.test(err2.body)) {
52042
+ deps.session.metadata.preflightStreamSummary = true;
52043
+ stream2 = true;
52044
+ adapted = true;
52045
+ deps.log("info", "[preflight] upstream requires stream for summaries; retrying with SSE (learned for this session)");
52046
+ }
52047
+ if (deps.protocol === "responses" && includeMaxOutputTokens && MAX_OUTPUT_TOKENS_REJECTED_RE.test(err2.body)) {
52048
+ rememberNoMaxOutputTokens(deps);
52049
+ includeMaxOutputTokens = false;
52050
+ adapted = true;
52051
+ deps.log("info", `[preflight] upstream rejects max_output_tokens for summaries (model=${deps.model}); retrying without it (learned for this session+upstream+model)`);
52052
+ }
52053
+ if (adapted) continue;
52054
+ }
52055
+ throw err2;
51964
52056
  }
51965
- throw err2;
51966
52057
  }
51967
52058
  }
51968
- async function requestSummary(deps, system, content, stream2) {
52059
+ async function requestSummary(deps, system, content, stream2, includeMaxOutputTokens) {
51969
52060
  const { response, clearTimer } = await fetchWithRetry(
51970
52061
  deps.url,
51971
52062
  {
51972
52063
  method: "POST",
51973
- headers: { "content-type": "application/json", ...deps.headers },
51974
- body: JSON.stringify(summaryPayload(deps.protocol, deps.model, system, content, stream2)),
52064
+ headers: { "content-type": "application/json", ...summaryHeaders(deps) },
52065
+ body: JSON.stringify(summaryPayload(deps.protocol, deps.model, system, content, stream2, includeMaxOutputTokens)),
51975
52066
  dispatcher: proxyDispatcher(deps.proxyUrl)
51976
52067
  },
51977
52068
  void 0,
@@ -56255,15 +56346,28 @@ function handlePluginManifest(res) {
56255
56346
  statusEndpoint: "/__bili/plugin/status"
56256
56347
  }));
56257
56348
  }
56349
+ function conversationIdForSession(sessionId) {
56350
+ let bestId;
56351
+ let bestSeen = -Infinity;
56352
+ for (const [cid, entry] of conversations) {
56353
+ if (entry.sessionId === sessionId && entry.lastSeen > bestSeen) {
56354
+ bestId = cid;
56355
+ bestSeen = entry.lastSeen;
56356
+ }
56357
+ }
56358
+ return bestId;
56359
+ }
56258
56360
  function handlePluginStatus(conversationId2, res, deps, fallbackLatest = false) {
56259
56361
  let entry = conversations.get(conversationId2);
56260
56362
  let session = entry ? peekSession(entry.sessionId) : void 0;
56261
56363
  let viaFallback = false;
56364
+ let resolvedConversationId = conversationId2;
56262
56365
  if ((!entry || !session) && fallbackLatest) {
56263
56366
  const latest = listSessions().filter((s3) => s3.restored !== true).sort((a, b2) => (b2.lastSeen ?? 0) - (a.lastSeen ?? 0))[0];
56264
56367
  if (latest) {
56265
56368
  session = latest;
56266
56369
  viaFallback = true;
56370
+ resolvedConversationId = conversationIdForSession(latest.id) ?? conversationId2;
56267
56371
  } else {
56268
56372
  res.writeHead(404, { "content-type": "application/json" });
56269
56373
  res.end(JSON.stringify({ ok: false, error: "no session with activity since boot \u2014 issue a model request or pass the conversation id" }));
@@ -56313,7 +56417,7 @@ function handlePluginStatus(conversationId2, res, deps, fallbackLatest = false)
56313
56417
  res.writeHead(200, { "content-type": "application/json" });
56314
56418
  res.end(JSON.stringify({
56315
56419
  ok: true,
56316
- conversationId: conversationId2,
56420
+ conversationId: resolvedConversationId,
56317
56421
  fallback: viaFallback || void 0,
56318
56422
  label: session.meta.label ?? null,
56319
56423
  pluginAgent: session.metadata.pluginAgent ?? null,
@@ -56348,8 +56452,12 @@ async function handlePluginTool(payload, res, deps) {
56348
56452
  const entry = conversations.get(conversationId2);
56349
56453
  const session = entry ? peekSession(entry.sessionId) : void 0;
56350
56454
  if (!entry || !session) {
56455
+ deps.log("warn", `[plugin] tool "${tool}" rejected for conversation ${conversationId2}: ${entry ? "id registered but session not resident in this proxy instance" : "id never registered (stale shim session id after host resume?)"}`);
56351
56456
  res.writeHead(404, { "content-type": "application/json" });
56352
- res.end(JSON.stringify({ ok: false, error: "unknown plugin conversation (no model request has arrived with this conversation id yet)" }));
56457
+ res.end(JSON.stringify({
56458
+ ok: false,
56459
+ error: !entry ? "unknown plugin conversation (no model request has arrived with this conversation id yet)" : "unknown plugin conversation (id registered but its session is not resident in this proxy instance \u2014 a fresh model request re-binds it)"
56460
+ }));
56353
56461
  return;
56354
56462
  }
56355
56463
  if (!isProxyToolFor(tool, session, deps.config)) {
@@ -59214,13 +59322,14 @@ ${bodyBuffer.toString("utf8")}`);
59214
59322
  await withSessionLock(session, async () => {
59215
59323
  const runPrepare = () => {
59216
59324
  const cs2 = resolveCompress(opts.routes, route?.rewrittenUrl, parsed.model, opts.compress);
59325
+ const reasoningCfg = cs2.reasoning;
59217
59326
  const keepRecent = cs2.stripImagesKeepRecent ?? DEFAULT_STRIP_IMAGES_KEEP_RECENT;
59218
59327
  const stripped = cs2.stripImages ? stripHistoricalImages(parsed, protocol, keepRecent) : { body: parsed, removed: 0 };
59219
59328
  if (opts.debug && stripped.removed > 0) {
59220
59329
  log2("info", `[debug] strip-images: dropped ${stripped.removed} historical image part(s), kept last ${keepRecent} (session=${session.id})`);
59221
59330
  }
59222
59331
  const work = stripped.body;
59223
- return countTokens ? prepareCountTokens(work, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(work, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode) : protocol === "openai" ? prepareOpenai(work, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode, nativeWindow) : responsesCompact ? prepareResponsesCompact(stripped.removed > 0 ? Buffer.from(JSON.stringify(work)) : bodyBuffer, work, session, req, core, reqConfig, log2) : prepareResponses(work, req, opts, core, reqConfig, reqPrompts, log2, session, responsesIdentity, pluginMode, upstreamOrigin, nativeWindow);
59332
+ return countTokens ? prepareCountTokens(work, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(work, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode, reasoningCfg) : protocol === "openai" ? prepareOpenai(work, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode, nativeWindow, reasoningCfg) : responsesCompact ? prepareResponsesCompact(stripped.removed > 0 ? Buffer.from(JSON.stringify(work)) : bodyBuffer, work, session, req, core, reqConfig, log2) : prepareResponses(work, req, opts, core, reqConfig, reqPrompts, log2, session, responsesIdentity, pluginMode, upstreamOrigin, nativeWindow, reasoningCfg);
59224
59333
  };
59225
59334
  const isCodexCompactTrigger = protocol === "responses" && !responsesCompact && isCodexClient(req.headers) && hasCompactionTrigger(parsed.input);
59226
59335
  if (isCodexCompactTrigger) {
@@ -59308,6 +59417,13 @@ ${bodyBuffer.toString("utf8")}`);
59308
59417
  }
59309
59418
  }
59310
59419
  var ACP_TAG_MARK = "<acp ";
59420
+ function withReasoningDrop(msgs, reasoning, log2, sessionId) {
59421
+ const out = dropCompressReasoning(msgs, reasoning);
59422
+ if (out.length !== msgs.length) {
59423
+ log2("info", `[${sessionId}] compress-reasoning: dropped ${msgs.length - out.length} reasoning message(s) from closed compress turns (#651)`);
59424
+ }
59425
+ return out;
59426
+ }
59311
59427
  function stripKernelSummaries(messages, state) {
59312
59428
  const carried = /* @__PURE__ */ new Set();
59313
59429
  for (const b2 of state.blocks) {
@@ -59409,12 +59525,13 @@ function effectiveTokenCount(session, msgs) {
59409
59525
  if (!session.metadata.anonymousPrefixAffinity) return 0;
59410
59526
  return estimateCoreMessagesUpper(msgs);
59411
59527
  }
59412
- function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, session, pluginMode) {
59528
+ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, session, pluginMode, reasoning) {
59413
59529
  const sessionId = session.id;
59414
59530
  const stream2 = parsed.stream === true;
59415
59531
  ++session.stats.requests;
59416
59532
  session.hostCreditTokens = 0;
59417
59533
  const injectTools = opts.compress.injectTool && !pluginMode;
59534
+ const stripReasoning = (msgs) => withReasoningDrop(msgs, reasoning, log2, sessionId);
59418
59535
  if (isAutoModeClassifier(parsed)) {
59419
59536
  log2("info", `[${sessionId}] auto-mode classifier passthrough (skipping compress injection)`);
59420
59537
  return { body: JSON.stringify(parsed), session, processedMessages: [], originalMessages: [], anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: false, pluginMode, nudge: void 0, prompts };
@@ -59452,7 +59569,7 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
59452
59569
  log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
59453
59570
  const willInjectNudge = opts.compress.injectNudge && !!turn.nudge && (turn.nudge.shouldInject || emergencyNudge(turn.nudge));
59454
59571
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit, parsed.model, willInjectNudge));
59455
- processedMessages = stripKernelSummaries(turn.messages, turn.state);
59572
+ processedMessages = stripReasoning(stripKernelSummaries(turn.messages, turn.state));
59456
59573
  applyCompactionArchive(session, activeBefore, new Set(msgs.map((m2) => m2.id)), log2);
59457
59574
  reapOrphanBlocks(session, msgs, deactivateBlock);
59458
59575
  rebuiltMessages = coreToAnthropic(processedMessages, cacheControls);
@@ -59534,12 +59651,13 @@ function clampOutgoingOutput(rebuilt, field, ctx, sessionId, log2) {
59534
59651
  log2("info", `[${sessionId}] output budget clamped ${raw} -> ${capped} (input~${inputEstimate}, window=${ctx.nativeWindow}); prevents input+output overflow (#453)`);
59535
59652
  }
59536
59653
  }
59537
- function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session, pluginMode, nativeWindow) {
59654
+ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session, pluginMode, nativeWindow, reasoning) {
59538
59655
  const sessionId = session.id;
59539
59656
  const stream2 = parsed.stream === true;
59540
59657
  ++session.stats.requests;
59541
59658
  session.hostCreditTokens = 0;
59542
59659
  let openaiSystemText = "";
59660
+ const stripReasoning = (msgs) => withReasoningDrop(msgs, reasoning, log2, sessionId);
59543
59661
  let openaiOutboundSystem;
59544
59662
  let processedMessages = [];
59545
59663
  let originalMessages = [];
@@ -59577,7 +59695,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
59577
59695
  log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
59578
59696
  const willInjectNudge = opts.compress.injectNudge && !!turn.nudge && shouldInject && (turn.nudge.shouldInject || emergencyNudge(turn.nudge));
59579
59697
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit, parsed.model, willInjectNudge));
59580
- processedMessages = stripKernelSummaries(turn.messages, turn.state);
59698
+ processedMessages = stripReasoning(stripKernelSummaries(turn.messages, turn.state));
59581
59699
  applyCompactionArchive(session, activeBefore, new Set(msgs.map((m2) => m2.id)), log2);
59582
59700
  reapOrphanBlocks(session, msgs, deactivateBlock);
59583
59701
  rebuiltMessages = systemToUser(coreToOpenai(processedMessages));
@@ -59617,11 +59735,12 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
59617
59735
  markDirty(session);
59618
59736
  return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: injectTools, pluginMode, nudge, prompts, openaiSystemText, renderTags: "text-only" };
59619
59737
  }
59620
- function prepareResponses(parsed, req, opts, core, config, prompts, log2, session, identity, pluginMode, upstreamOrigin, nativeWindow) {
59738
+ function prepareResponses(parsed, req, opts, core, config, prompts, log2, session, identity, pluginMode, upstreamOrigin, nativeWindow, reasoning) {
59621
59739
  const sessionId = session.id;
59622
59740
  const stream2 = parsed.stream === true;
59623
59741
  ++session.stats.requests;
59624
59742
  session.hostCreditTokens = 0;
59743
+ const stripReasoning = (msgs) => withReasoningDrop(msgs, reasoning, log2, sessionId);
59625
59744
  if (reconcileNativeCompactionBoundary(session)) {
59626
59745
  log2("info", `[${sessionId}] reconciled ACP state after native Responses compact boundary`);
59627
59746
  }
@@ -59686,7 +59805,7 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
59686
59805
  log2("info", diagTagSummary(turn.messages, sessionId, "text-only"));
59687
59806
  const willInjectNudge = opts.compress.injectNudge && !!turn.nudge && shouldInject && !isCompactionTrigger && (turn.nudge.shouldInject || emergencyNudge(turn.nudge));
59688
59807
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit, parsed.model, willInjectNudge));
59689
- processedMessages = repairResponsesAssistantOrdering(stripKernelSummaries(turn.messages, turn.state), originalMessages);
59808
+ processedMessages = repairResponsesAssistantOrdering(stripReasoning(stripKernelSummaries(turn.messages, turn.state)), originalMessages);
59690
59809
  reapOrphanBlocks(session, msgs, deactivateBlock);
59691
59810
  rebuiltInput = patchResponsesInput(projection, processedMessages);
59692
59811
  const forgedSummaries = echoReplaced ? [] : session.metadata.codexForgedSummaries ?? [];
@@ -64321,6 +64440,7 @@ function resolveProxyOrigin() {
64321
64440
  var TOOL_TIMEOUT_MS = 6e4;
64322
64441
  var CONVERSATION_FROM_ENV = process.env.CLAUDE_CODE_SESSION_ID?.trim() || process.env.BILI_CONVERSATION_ID?.trim() || void 0;
64323
64442
  var IDENTITY_BINDING = Boolean(process.env.CLAUDE_CODE_SESSION_ID?.trim());
64443
+ var ORPHAN_ADOPT = IDENTITY_BINDING && process.env.BILI_MCP_NO_ORPHAN_ADOPT !== "1";
64324
64444
  var manifestTools = [];
64325
64445
  var conversationId = CONVERSATION_FROM_ENV;
64326
64446
  var registered = false;
@@ -64353,21 +64473,45 @@ function ensureManifest() {
64353
64473
  return manifestPromise;
64354
64474
  }
64355
64475
  async function forwardTool(tool, args, timeoutMs = TOOL_TIMEOUT_MS) {
64356
- let res;
64476
+ for (let attempt = 0; ; attempt++) {
64477
+ let res;
64478
+ try {
64479
+ res = await fetch(`${resolveProxyOrigin()}/__bili/plugin/tool`, {
64480
+ method: "POST",
64481
+ headers: { "content-type": "application/json" },
64482
+ body: JSON.stringify({ conversationId, tool, args }),
64483
+ signal: AbortSignal.timeout(timeoutMs)
64484
+ });
64485
+ } catch (err2) {
64486
+ if (err2 instanceof Error && err2.name === "TimeoutError") throw new Error(`tool forward timed out after ${timeoutMs}ms: ${tool}`);
64487
+ throw err2;
64488
+ }
64489
+ const data = await res.json();
64490
+ if (res.ok && data.ok) return data.result ?? "";
64491
+ if (res.status === 404 && attempt === 0 && ORPHAN_ADOPT && conversationId && typeof data.error === "string" && data.error.includes("no model request has arrived")) {
64492
+ if (await adoptLatestActiveConversation()) continue;
64493
+ }
64494
+ throw new Error(data.error ?? `tool forward failed: ${res.status}`);
64495
+ }
64496
+ }
64497
+ async function adoptLatestActiveConversation() {
64357
64498
  try {
64358
- res = await fetch(`${resolveProxyOrigin()}/__bili/plugin/tool`, {
64359
- method: "POST",
64360
- headers: { "content-type": "application/json" },
64361
- body: JSON.stringify({ conversationId, tool, args }),
64362
- signal: AbortSignal.timeout(timeoutMs)
64363
- });
64364
- } catch (err2) {
64365
- if (err2 instanceof Error && err2.name === "TimeoutError") throw new Error(`tool forward timed out after ${timeoutMs}ms: ${tool}`);
64366
- throw err2;
64499
+ const res = await fetch(
64500
+ `${resolveProxyOrigin()}/__bili/plugin/status?conversationId=${encodeURIComponent(conversationId ?? "")}&fallback=latest`,
64501
+ { signal: AbortSignal.timeout(5e3) }
64502
+ );
64503
+ if (!res.ok) return false;
64504
+ const data = await res.json();
64505
+ if (data.ok !== true || data.fallback !== true || !data.conversationId || data.conversationId === conversationId) return false;
64506
+ process.stderr.write(
64507
+ `[bili-mcp] conversation id no longer known by the proxy (host resumed its session?); adopting latest active conversation ${data.conversationId}
64508
+ `
64509
+ );
64510
+ conversationId = data.conversationId;
64511
+ return true;
64512
+ } catch {
64513
+ return false;
64367
64514
  }
64368
- const data = await res.json();
64369
- if (!res.ok || !data.ok) throw new Error(data.error ?? `tool forward failed: ${res.status}`);
64370
- return data.result ?? "";
64371
64515
  }
64372
64516
  var ERR_TOOL = -32602;
64373
64517
  async function handleMessage(msg) {