billion-context-omp 0.1.9 → 0.2.1

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/README.md CHANGED
@@ -145,6 +145,7 @@ Create `~/.omp/acp-omp.json` (global) and/or `<project>/.omp/acp-omp.json` (proj
145
145
  | Key | Default | Description |
146
146
  |-----|---------|-------------|
147
147
  | `debug` | `false` | Enable verbose **debug-level** events in the log. The always-on log (lifecycle events, errors, warnings) is written regardless; `debug` only adds extra diagnostics. Also enabled by env `ACP_DEBUG=1`. |
148
+ | `transformMode` | `"context"` (default) or `"provider"` — where the compression surgery intercepts. `provider` transforms the provider wire payload (no feedback re-entry; experimental). |
148
149
  | `autoUpdate` | `true` | On session start (throttled to one check per 3 minutes), check npm for a newer version and auto-install it. Disable to avoid all startup network calls. |
149
150
  | `modelContextLimit` | *(auto)* | Override the context limit (in tokens). Defaults to the model's `contextWindow`. |
150
151
  | `compressModel` | *(session model)* | `provider:modelId` used for `/compact` model-summarized compaction (e.g. `"zhipuai:glm-5.2"`). Defaults to the current session model when omitted. |
package/dist/config.d.ts CHANGED
@@ -27,6 +27,13 @@ export interface CompressConfig {
27
27
  * (live model context window, protected tools, state persistence).
28
28
  */
29
29
  export interface AdapterConfig {
30
+ /** Where the compression surgery intercepts (issue #52). "context" (default)
31
+ * rewrites the context event — battle-tested, but omp's recap/subagent
32
+ * pipelines can re-feed our output as input (feedback-view loops).
33
+ * "provider" leaves the agent array untouched and transforms the WIRE
34
+ * payload at before_provider_request — request-local, no re-entry; unknown
35
+ * provider formats pass through untransformed (fail-open). */
36
+ transformMode?: "context" | "provider";
30
37
  /** When omitted, the adapter reads `ctx.model.contextWindow` live each turn.
31
38
  * Set explicitly for tests/headless runs. */
32
39
  modelContextLimit?: number;
package/dist/index.js CHANGED
@@ -3166,6 +3166,15 @@ function freshSlot(preserveFrom) {
3166
3166
  }
3167
3167
  return slot;
3168
3168
  }
3169
+ function isViewFlip(foldedLen, lcp) {
3170
+ return foldedLen > 0 && lcp >= Math.floor(foldedLen / 2);
3171
+ }
3172
+ function preserveCompressedSlot(prev) {
3173
+ const slot = freshSlot(prev);
3174
+ slot.state = { ...slot.state, blocks: prev.state.blocks, messageRefs: prev.state.messageRefs, stats: prev.state.stats };
3175
+ slot.appliedCallIds = new Set(prev.appliedCallIds);
3176
+ return slot;
3177
+ }
3169
3178
  function stateHasCompressCall(state, callId) {
3170
3179
  return state.blocks.some((b) => b.compressCallId === callId);
3171
3180
  }
@@ -3217,8 +3226,9 @@ function createRuntime(adapter) {
3217
3226
  let lcp = 0;
3218
3227
  while (lcp < Math.min(ids.length, slot.identities.length) && ids[lcp] === slot.identities[lcp]) lcp++;
3219
3228
  if (lcp < slot.foldedLen) {
3220
- debug.event("fold-refold", { sid, foldedLen: slot.foldedLen, lcp, streamLen: ids.length });
3221
- slot = freshSlot(slot);
3229
+ const flip = isViewFlip(slot.foldedLen, lcp);
3230
+ debug.event("fold-refold", { sid, foldedLen: slot.foldedLen, lcp, streamLen: ids.length, flip });
3231
+ slot = flip ? preserveCompressedSlot(slot) : freshSlot(slot);
3222
3232
  slots.set(sid, slot);
3223
3233
  lcp = 0;
3224
3234
  }
@@ -3244,6 +3254,7 @@ function createRuntime(adapter) {
3244
3254
  debug.event("fold-replay-skipped", { sid, callId: call.id });
3245
3255
  continue;
3246
3256
  }
3257
+ if (slot.appliedCallIds.has(call.id) || stateHasCompressCall(slot.state, call.id)) continue;
3247
3258
  const stale = call.ranges.map((r, ri) => staleRange(r, ri, resultText, coreMessages, i, slot.state.messageRefs.byRef, slot.state.blocks)).find((s) => s !== false);
3248
3259
  if (stale) {
3249
3260
  debug.event("fold-replay-stale", { sid, callId: call.id, reason: stale });
@@ -3425,6 +3436,11 @@ function makeCompressTool(runtime) {
3425
3436
  async function handleCompress(args, runtime, ctx, toolCallId) {
3426
3437
  const ranges = args.content ?? [];
3427
3438
  if (ranges.length === 0) return "No ranges provided.";
3439
+ const invalid = ranges.filter((r) => !r || typeof r.summary !== "string" || !r.summary.trim() || !r.startId || !r.endId);
3440
+ if (invalid.length > 0) {
3441
+ logWarn("compress", { sid: ctx.sessionManager.getSessionId(), event: "invalid-ranges", count: invalid.length });
3442
+ return `Every range needs startId, endId and a summary (min 50 chars) \u2014 got ${invalid.length} range(s) missing fields. Re-send the FULL ranges array with summaries included.`;
3443
+ }
3428
3444
  const releaseLock = await runtime.acquireLock(ctx.sessionManager.getSessionId());
3429
3445
  try {
3430
3446
  const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
@@ -4492,7 +4508,7 @@ async function statusReport(runtime, ctx) {
4492
4508
  const coveredIds = collectCoveredMessageIds(state);
4493
4509
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
4494
4510
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
4495
- const versionStr = "0.1.9" ? `billion-context-omp@${"0.1.9"}` : void 0;
4511
+ const versionStr = "0.2.1" ? `billion-context-omp@${"0.2.1"}` : void 0;
4496
4512
  return buildStatusPanel({
4497
4513
  version: versionStr,
4498
4514
  tokenCount: sessionTokens,
@@ -4504,6 +4520,237 @@ async function statusReport(runtime, ctx) {
4504
4520
  });
4505
4521
  }
4506
4522
 
4523
+ // src/wire-transform.ts
4524
+ var AI = /* @__PURE__ */ Symbol("acp.streamIndex");
4525
+ function detectWireFormat(payload) {
4526
+ if (payload === null || typeof payload !== "object") return "unknown";
4527
+ const p = payload;
4528
+ const messages = p.messages;
4529
+ if (!Array.isArray(messages)) return "unknown";
4530
+ if ("system" in p || "anthropic_version" in p) return "anthropic";
4531
+ for (const m of messages) {
4532
+ if (m === null || typeof m !== "object") continue;
4533
+ const c = m.content;
4534
+ if (Array.isArray(c)) {
4535
+ for (const b of c) {
4536
+ if (b && typeof b === "object" && typeof b.type === "string") {
4537
+ if (b.type === "tool_use" || b.type === "tool_result" || b.type === "thinking") return "anthropic";
4538
+ if (b.type === "text" && "cache_control" in b) return "anthropic";
4539
+ }
4540
+ }
4541
+ }
4542
+ if (Array.isArray(m.tool_calls)) return "openai";
4543
+ if (m.role === "tool" && typeof m.tool_call_id === "string") return "openai";
4544
+ if (m.role === "system" || m.role === "developer") return "openai";
4545
+ }
4546
+ return "openai";
4547
+ }
4548
+ function anthropicBlocks(m) {
4549
+ return typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content ?? [];
4550
+ }
4551
+ var assistantBase = () => ({
4552
+ api: "anthropic",
4553
+ provider: "anthropic",
4554
+ model: "wire",
4555
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
4556
+ stopReason: "stop",
4557
+ timestamp: Date.now()
4558
+ });
4559
+ function synthesizeStream(payload, format) {
4560
+ const messages = payload.messages ?? [];
4561
+ const stream = [];
4562
+ const back = [];
4563
+ const push = (msg, wi, kind) => {
4564
+ msg[AI] = stream.length;
4565
+ stream.push(msg);
4566
+ back.push({ wi, kind });
4567
+ };
4568
+ if (format === "anthropic") {
4569
+ const toolNames = /* @__PURE__ */ new Map();
4570
+ for (const raw of messages) {
4571
+ if (raw === null || typeof raw !== "object") continue;
4572
+ const m = raw;
4573
+ if (m.role !== "assistant") continue;
4574
+ for (const b of anthropicBlocks(m)) if (b.type === "tool_use" && b.id) toolNames.set(b.id, b.name ?? "");
4575
+ }
4576
+ messages.forEach((raw, wi) => {
4577
+ if (raw === null || typeof raw !== "object") return;
4578
+ const m = raw;
4579
+ const blocks = anthropicBlocks(m);
4580
+ if (m.role === "user") {
4581
+ let texts = [];
4582
+ for (const b of blocks) {
4583
+ if (b.type === "text" && typeof b.text === "string") texts.push(b.text);
4584
+ else if (b.type === "tool_result") {
4585
+ if (texts.length > 0) {
4586
+ push({ role: "user", content: texts.map((t2) => ({ type: "text", text: t2 })), timestamp: Date.now() }, wi, "text");
4587
+ texts = [];
4588
+ }
4589
+ const trText = typeof b.content === "string" ? b.content : Array.isArray(b.content) ? b.content.map((c) => c.text ?? "").join("\n") : "";
4590
+ push({
4591
+ role: "toolResult",
4592
+ content: [{ type: "text", text: trText }],
4593
+ toolName: toolNames.get(b.tool_use_id ?? "") ?? "",
4594
+ toolCallId: b.tool_use_id ?? "",
4595
+ isError: b.is_error === true,
4596
+ timestamp: Date.now()
4597
+ }, wi, "toolResult");
4598
+ }
4599
+ }
4600
+ if (texts.length > 0) push({ role: "user", content: texts.map((t2) => ({ type: "text", text: t2 })), timestamp: Date.now() }, wi, "text");
4601
+ return;
4602
+ }
4603
+ if (m.role === "assistant") {
4604
+ const content = [];
4605
+ for (const b of blocks) {
4606
+ if (b.type === "text" && typeof b.text === "string" && b.text.length > 0) content.push({ type: "text", text: b.text });
4607
+ else if (b.type === "tool_use") content.push({ type: "toolCall", id: b.id, name: b.name, arguments: b.input ?? {} });
4608
+ }
4609
+ if (content.length > 0) push({ role: "assistant", ...assistantBase(), content }, wi, "toolCall");
4610
+ return;
4611
+ }
4612
+ const t = blocks.map((b) => typeof b.text === "string" ? b.text : "").join("\n");
4613
+ if (t) push({ role: "user", content: [{ type: "text", text: t }], timestamp: Date.now() }, wi, "text");
4614
+ });
4615
+ return { stream, back, format };
4616
+ }
4617
+ messages.forEach((raw, wi) => {
4618
+ if (raw === null || typeof raw !== "object") return;
4619
+ const m = raw;
4620
+ const textOf = () => {
4621
+ const c = m.content;
4622
+ if (typeof c === "string") return c;
4623
+ if (Array.isArray(c)) return c.map((p) => p.type === "text" ? p.text ?? "" : "").join("\n");
4624
+ return "";
4625
+ };
4626
+ if (m.role === "system" || m.role === "developer") {
4627
+ const t2 = textOf();
4628
+ if (t2) push({ role: "user", content: [{ type: "text", text: t2 }], timestamp: Date.now() }, wi, "text");
4629
+ return;
4630
+ }
4631
+ if (m.role === "tool") {
4632
+ push({
4633
+ role: "toolResult",
4634
+ content: [{ type: "text", text: textOf() }],
4635
+ toolName: "",
4636
+ toolCallId: m.tool_call_id ?? "",
4637
+ isError: false,
4638
+ timestamp: Date.now()
4639
+ }, wi, "toolResult");
4640
+ return;
4641
+ }
4642
+ if (m.role === "assistant") {
4643
+ const calls = m.tool_calls ?? [];
4644
+ if (calls.length > 0) {
4645
+ const content = [];
4646
+ const t3 = textOf();
4647
+ if (t3) content.push({ type: "text", text: t3 });
4648
+ for (const c of calls) {
4649
+ let args = {};
4650
+ try {
4651
+ args = c.function?.arguments ? JSON.parse(c.function.arguments) : {};
4652
+ } catch {
4653
+ args = { raw: c.function?.arguments ?? "" };
4654
+ }
4655
+ content.push({ type: "toolCall", id: c.id, name: c.function?.name ?? "", arguments: args });
4656
+ }
4657
+ push({ role: "assistant", ...assistantBase(), content }, wi, "toolCall");
4658
+ return;
4659
+ }
4660
+ const t2 = textOf();
4661
+ if (t2) push({ role: "assistant", ...assistantBase(), content: [{ type: "text", text: t2 }] }, wi, "text");
4662
+ return;
4663
+ }
4664
+ const t = textOf();
4665
+ if (t) push({ role: "user", content: [{ type: "text", text: t }], timestamp: Date.now() }, wi, "text");
4666
+ });
4667
+ return { stream, back, format };
4668
+ }
4669
+ function rebuildWirePayload(rebuilt, payload, synth) {
4670
+ const messages = (payload.messages ?? []).slice();
4671
+ const out = [];
4672
+ const agentIndexOf = (m) => m[AI];
4673
+ for (const agent of rebuilt) {
4674
+ const ai = agentIndexOf(agent);
4675
+ if (ai === void 0 || ai >= synth.back.length) {
4676
+ const text2 = extractText(agent.content);
4677
+ const role = agent.role === "assistant" ? "assistant" : "user";
4678
+ if (synth.format === "anthropic") {
4679
+ out.push({ role, content: [{ type: "text", text: text2 }] });
4680
+ } else {
4681
+ out.push({ role, content: text2 });
4682
+ }
4683
+ continue;
4684
+ }
4685
+ const { wi, kind } = synth.back[ai];
4686
+ const src = messages[wi];
4687
+ if (!src) {
4688
+ out.push(agent);
4689
+ continue;
4690
+ }
4691
+ if (synth.format === "anthropic") {
4692
+ const srcMsg2 = src;
4693
+ const blocks = anthropicBlocks(srcMsg2);
4694
+ if (kind === "toolResult") {
4695
+ const block2 = blocks.find((b) => b.type === "tool_result" && b.tool_use_id === agent.toolCallId);
4696
+ const text3 = extractText(agent.content);
4697
+ if (block2) {
4698
+ out.push({ role: "user", content: [{ ...block2, content: [{ type: "text", text: text3 }] }] });
4699
+ continue;
4700
+ }
4701
+ }
4702
+ if (kind === "toolCall") {
4703
+ const callIds = new Set(
4704
+ (agent.content ?? []).filter((b) => b.type === "toolCall" && b.id).map((b) => b.id)
4705
+ );
4706
+ const agentText = extractText(agent.content);
4707
+ const outBlocks = [];
4708
+ let textSeen = false;
4709
+ for (const b of blocks) {
4710
+ if (b.type === "tool_use") {
4711
+ if (callIds.has(b.id ?? "")) outBlocks.push(b);
4712
+ continue;
4713
+ }
4714
+ if (b.type === "text") {
4715
+ if (!textSeen && typeof b.text === "string") {
4716
+ outBlocks.push(b);
4717
+ textSeen = true;
4718
+ }
4719
+ continue;
4720
+ }
4721
+ outBlocks.push(b);
4722
+ }
4723
+ if (agentText && !textSeen) outBlocks.unshift({ type: "text", text: agentText });
4724
+ out.push({ role: "assistant", content: outBlocks });
4725
+ continue;
4726
+ }
4727
+ const text2 = extractText(agent.content);
4728
+ const block = blocks.find((b) => b.type === "text");
4729
+ if (block) out.push({ role: srcMsg2.role, content: [{ ...block, text: text2 }] });
4730
+ else out.push({ role: srcMsg2.role, content: [{ type: "text", text: text2 }] });
4731
+ continue;
4732
+ }
4733
+ const srcMsg = src;
4734
+ const text = extractText(agent.content);
4735
+ if (kind === "toolResult") {
4736
+ out.push({ role: "tool", tool_call_id: agent.toolCallId ?? srcMsg.tool_call_id, content: text });
4737
+ continue;
4738
+ }
4739
+ if (kind === "toolCall") {
4740
+ const callIds = new Set(
4741
+ (agent.content ?? []).filter((b) => b.type === "toolCall" && b.id).map((b) => b.id)
4742
+ );
4743
+ const surviving = (srcMsg.tool_calls ?? []).filter((c) => callIds.has(c.id));
4744
+ const entry = { role: "assistant", content: text };
4745
+ if (surviving.length > 0) entry.tool_calls = surviving;
4746
+ out.push(entry);
4747
+ continue;
4748
+ }
4749
+ out.push({ role: srcMsg.role === "assistant" ? "assistant" : srcMsg.role, content: text });
4750
+ }
4751
+ return { ...payload, messages: out };
4752
+ }
4753
+
4507
4754
  // src/auto-compress.ts
4508
4755
  import { readFileSync } from "fs";
4509
4756
  import { join as join3 } from "path";
@@ -4934,7 +5181,7 @@ async function checkForUpdate(autoUpdate, notify) {
4934
5181
  const data = await res.json();
4935
5182
  const latest = data.version;
4936
5183
  if (!latest) return;
4937
- const current = runtimeVersion ?? "0.1.9";
5184
+ const current = runtimeVersion ?? "0.2.1";
4938
5185
  const hasUpdate = isNewer(latest, current);
4939
5186
  debug.event("update-check", {
4940
5187
  current,
@@ -5149,6 +5396,7 @@ var KNOWN = /* @__PURE__ */ new Set([
5149
5396
  "debug",
5150
5397
  "autoUpdate",
5151
5398
  "modelContextLimit",
5399
+ "transformMode",
5152
5400
  "toolBashDefaultTimeout",
5153
5401
  "toolOutputMaxBytes",
5154
5402
  "delegate",
@@ -5192,6 +5440,7 @@ function createAcpExtension(adapter = {}) {
5192
5440
  wireSessionLifecycle(pi, runtime);
5193
5441
  wireContextTransform(pi, runtime);
5194
5442
  wireSystemPrompt(pi, runtime);
5443
+ wireProviderTransform(pi, runtime);
5195
5444
  wireProviderDebug(pi);
5196
5445
  wireToolGuardrails(pi, runtime);
5197
5446
  pi.registerTool(makeCompressTool(runtime));
@@ -5248,7 +5497,7 @@ function wireCompactionDisable(pi, runtime) {
5248
5497
  function wireSessionLifecycle(pi, runtime) {
5249
5498
  pi.on("session_start", async (_event, ctx) => {
5250
5499
  const sid = ctx.sessionManager.getSessionId();
5251
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.9" : null });
5500
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.1" : null });
5252
5501
  try {
5253
5502
  const user = await loadUserConfig(ctx.cwd);
5254
5503
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
@@ -5275,130 +5524,168 @@ function wireSessionLifecycle(pi, runtime) {
5275
5524
  closeLogStream();
5276
5525
  });
5277
5526
  }
5278
- function wireContextTransform(pi, runtime) {
5279
- pi.on("context", async (event, ctx) => {
5280
- const sid = ctx.sessionManager.getSessionId();
5281
- const release = await runtime.acquireLock(sid);
5282
- try {
5283
- const input = event.messages ?? [];
5284
- if (input.length === 0) {
5285
- debug.event("empty-stream-bypass", { sid });
5286
- return void 0;
5287
- }
5288
- debug.event("context-in-raw", { sid, msgs: input.length });
5289
- const { state, coreMessages, originalById, streamLen } = runtime.foldStream(ctx, input);
5290
- const preTurnNudgeBaseline = state.nudge.lastPerMessageNudgeTokens;
5291
- const preTurnNudgeShownTokens = state.nudge.lastNudgeShownTokens;
5292
- const preTurnNudgeShownByTier = state.nudge.lastShownByTier;
5293
- const config = runtime.configFor(ctx);
5294
- const coveredIds = collectCoveredMessageIds(state);
5295
- const systemPromptTokens = estimateTextTokens2(getSystemPromptText(ctx) ?? "");
5296
- const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5297
- const sessionTokens = ctx.getContextUsage?.()?.tokens ?? null;
5298
- const tokenCount = sentTokens;
5299
- debug.event("context-in", {
5300
- sid,
5301
- eventMsgs: event.messages?.length ?? 0,
5302
- streamLen,
5303
- coreMsgs: coreMessages.length,
5304
- tokenCount,
5305
- sessionTokens,
5306
- limit: config.modelContextLimit,
5307
- blocksBefore: state.blocks.length,
5308
- activeBefore: state.blocks.filter((b) => b.active).length
5309
- });
5310
- const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
5311
- runtime.commitFoldState(ctx, turn.state);
5312
- logInfo("turn", {
5313
- sid,
5314
- inMsgs: coreMessages.length,
5315
- outMsgs: turn.messages.length,
5316
- tokens: tokenCount,
5317
- sessionTokens,
5318
- pct: config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null,
5319
- limit: config.modelContextLimit,
5320
- nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
5321
- nudgeReason: turn.nudge?.reason ?? null,
5322
- blocks: turn.state.blocks.length,
5323
- activeBlocks: turn.state.blocks.filter((b) => b.active).length
5324
- });
5325
- debug.event("processTurn", {
5326
- outMsgs: turn.messages.length,
5327
- summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5328
- prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5329
- nudgeShouldInject: turn.nudge?.shouldInject ?? false,
5330
- nudgeReason: turn.nudge?.reason ?? null,
5331
- nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
5332
- nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
5333
- nudgeTier: turn.nudge?.tier ?? null,
5334
- nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
5335
- nudgeProtectedCount: turn.nudge?.protectedRanges?.length ?? 0,
5336
- nothingToCompress: turn.nudge?.reason?.includes("nothing to compress") ?? false,
5337
- blocksAfter: turn.state.blocks.length,
5338
- activeAfter: turn.state.blocks.filter((b) => b.active).length
5339
- });
5340
- const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
5341
- debug.event("core-out", {
5342
- sid,
5343
- coreOutMsgs: turn.messages.length,
5344
- originalByIdSize: originalById.size,
5345
- rebuiltMsgs: rebuilt.length
5346
- });
5347
- const debugOn2 = debug.enabled;
5348
- let nudgeInjected = false;
5349
- if (turn.nudge?.shouldInject) {
5350
- const lastUser = [...input].reverse().find((m) => m.role === "user");
5351
- const tailText = lastUser ? JSON.stringify(lastUser.content ?? "") : "";
5352
- const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
5353
- if (isFeedbackView) {
5354
- debug.event("nudge-feedback-skip", { sid: ctx.sessionManager.getSessionId(), msgs: input.length });
5527
+ async function transformStream(ctx, runtime, input, mode) {
5528
+ const sid = ctx.sessionManager.getSessionId();
5529
+ const release = await runtime.acquireLock(sid);
5530
+ try {
5531
+ if (input.length === 0) {
5532
+ debug.event("empty-stream-bypass", { sid });
5533
+ return void 0;
5534
+ }
5535
+ debug.event("context-in-raw", { sid, msgs: input.length, mode });
5536
+ const { state, coreMessages, originalById, streamLen } = runtime.foldStream(ctx, input);
5537
+ const preTurnNudgeBaseline = state.nudge.lastPerMessageNudgeTokens;
5538
+ const preTurnNudgeShownTokens = state.nudge.lastNudgeShownTokens;
5539
+ const preTurnNudgeShownByTier = state.nudge.lastShownByTier;
5540
+ const config = runtime.configFor(ctx);
5541
+ const coveredIds = collectCoveredMessageIds(state);
5542
+ const systemPromptTokens = estimateTextTokens2(getSystemPromptText(ctx) ?? "");
5543
+ const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5544
+ const sessionTokens = ctx.getContextUsage?.()?.tokens ?? null;
5545
+ const tokenCount = sentTokens;
5546
+ debug.event("context-in", {
5547
+ sid,
5548
+ mode,
5549
+ streamLen,
5550
+ coreMsgs: coreMessages.length,
5551
+ tokenCount,
5552
+ sessionTokens,
5553
+ limit: config.modelContextLimit,
5554
+ blocksBefore: state.blocks.length,
5555
+ activeBefore: state.blocks.filter((b) => b.active).length
5556
+ });
5557
+ const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
5558
+ runtime.commitFoldState(ctx, turn.state);
5559
+ logInfo("turn", {
5560
+ sid,
5561
+ inMsgs: coreMessages.length,
5562
+ outMsgs: turn.messages.length,
5563
+ tokens: tokenCount,
5564
+ sessionTokens,
5565
+ pct: config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null,
5566
+ limit: config.modelContextLimit,
5567
+ nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
5568
+ nudgeReason: turn.nudge?.reason ?? null,
5569
+ blocks: turn.state.blocks.length,
5570
+ activeBlocks: turn.state.blocks.filter((b) => b.active).length
5571
+ });
5572
+ debug.event("processTurn", {
5573
+ outMsgs: turn.messages.length,
5574
+ summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5575
+ prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5576
+ nudgeShouldInject: turn.nudge?.shouldInject ?? false,
5577
+ nudgeReason: turn.nudge?.reason ?? null,
5578
+ nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
5579
+ nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
5580
+ nudgeTier: turn.nudge?.tier ?? null,
5581
+ nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
5582
+ nudgeProtectedCount: turn.nudge?.protectedRanges?.length ?? 0,
5583
+ nothingToCompress: turn.nudge?.reason?.includes("nothing to compress") ?? false,
5584
+ blocksAfter: turn.state.blocks.length,
5585
+ activeAfter: turn.state.blocks.filter((b) => b.active).length
5586
+ });
5587
+ const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
5588
+ debug.event("core-out", {
5589
+ sid,
5590
+ coreOutMsgs: turn.messages.length,
5591
+ originalByIdSize: originalById.size,
5592
+ rebuiltMsgs: rebuilt.length
5593
+ });
5594
+ const debugOn2 = debug.enabled;
5595
+ let nudgeInjected = false;
5596
+ if (turn.nudge?.shouldInject) {
5597
+ const lastUser = [...input].reverse().find((m) => m.role === "user");
5598
+ const tailText = lastUser ? JSON.stringify(lastUser.content ?? "") : "";
5599
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
5600
+ if (isFeedbackView) {
5601
+ debug.event("nudge-feedback-skip", { sid: ctx.sessionManager.getSessionId(), msgs: input.length });
5602
+ } else {
5603
+ const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
5604
+ const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
5605
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
5606
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
5607
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
5608
+ if (suppressed) {
5609
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
5610
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
5611
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5612
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5355
5613
  } else {
5356
- const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
5357
- const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
5358
- const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
5359
- const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
5360
- const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
5361
- if (suppressed) {
5362
- turn.state.nudge.lastNudgeShownTokens = prevShown;
5363
- turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
5364
- logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5365
- debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5366
- } else {
5367
- nudgeInjected = true;
5368
- {
5369
- turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
5370
- const rendered = renderNudgeText(turn.nudge, runtime.prompts);
5371
- const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
5372
- const example = top ? `
5614
+ nudgeInjected = true;
5615
+ {
5616
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
5617
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
5618
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
5619
+ const example = top ? `
5373
5620
 
5374
5621
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
5375
- rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
5376
- if (emergency) {
5377
- logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
5378
- }
5379
- if (debugOn2 && ctx.hasUI) {
5380
- ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
5622
+ rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
5623
+ if (emergency) {
5624
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
5625
+ }
5626
+ if (debugOn2 && ctx.hasUI) {
5627
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
5381
5628
  ${rendered.text}${example}`);
5382
- }
5383
- debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
5384
5629
  }
5630
+ debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
5385
5631
  }
5386
5632
  }
5387
5633
  }
5388
- dumpContextMessages(rebuilt, {
5389
- sid,
5390
- injected: nudgeInjected,
5391
- emergency: turn.nudge?.breakdown?.emergencyOverride === 1
5392
- });
5393
- await checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
5394
- if (ctx.hasUI) ctx.ui.notify(msg);
5395
- });
5396
- return { messages: rebuilt };
5634
+ }
5635
+ dumpContextMessages(rebuilt, {
5636
+ sid,
5637
+ injected: nudgeInjected,
5638
+ emergency: turn.nudge?.breakdown?.emergencyOverride === 1
5639
+ });
5640
+ await checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
5641
+ if (ctx.hasUI) ctx.ui.notify(msg);
5642
+ });
5643
+ return { rebuilt, nudgeInjected };
5644
+ } catch (e) {
5645
+ logThrow("context", e, { sid, phase: "transform", mode });
5646
+ throw e;
5647
+ } finally {
5648
+ release();
5649
+ }
5650
+ }
5651
+ function wireContextTransform(pi, runtime) {
5652
+ pi.on("context", async (event, ctx) => {
5653
+ if ((runtime.adapter.transformMode ?? "context") === "provider") {
5654
+ debug.event("context-observer-skip", { sid: ctx.sessionManager.getSessionId(), msgs: event.messages?.length ?? 0 });
5655
+ return void 0;
5656
+ }
5657
+ const result = await transformStream(ctx, runtime, event.messages ?? [], "context");
5658
+ if (!result) return void 0;
5659
+ return { messages: result.rebuilt };
5660
+ });
5661
+ }
5662
+ function wireProviderTransform(pi, runtime) {
5663
+ pi.on("before_provider_request", async (event, ctx) => {
5664
+ if ((runtime.adapter.transformMode ?? "context") !== "provider") return void 0;
5665
+ const payload = event.payload;
5666
+ if (payload === null || typeof payload !== "object" || !Array.isArray(payload.messages)) return void 0;
5667
+ const sid = ctx.sessionManager?.getSessionId?.() ?? "";
5668
+ const fmt2 = detectWireFormat(payload);
5669
+ if (fmt2 === "unknown") {
5670
+ debug.event("provider-transform-unknown-format", { sid });
5671
+ return void 0;
5672
+ }
5673
+ try {
5674
+ const synth = synthesizeStream(payload, fmt2);
5675
+ if (synth.stream.length === 0) return void 0;
5676
+ const result = await transformStream(ctx, runtime, synth.stream, "provider");
5677
+ if (!result) return void 0;
5678
+ const wireOut = rebuildWirePayload(result.rebuilt, payload, synth);
5679
+ const outMsgs = wireOut.messages?.length ?? 0;
5680
+ const inMsgs = payload.messages?.length ?? 0;
5681
+ if (outMsgs !== inMsgs || wireOut !== payload) {
5682
+ logInfo("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudge: result.nudgeInjected ? "injected" : "idle" });
5683
+ }
5684
+ debug.event("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudgeInjected: result.nudgeInjected });
5685
+ return wireOut === payload ? void 0 : wireOut;
5397
5686
  } catch (e) {
5398
- logThrow("context", e, { sid, phase: "transform" });
5399
- throw e;
5400
- } finally {
5401
- release();
5687
+ logThrow("provider-transform", e, { sid, fmt: fmt2 });
5688
+ return void 0;
5402
5689
  }
5403
5690
  });
5404
5691
  }