billion-context-omp 0.1.9 → 0.2.0

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
@@ -3425,6 +3425,11 @@ function makeCompressTool(runtime) {
3425
3425
  async function handleCompress(args, runtime, ctx, toolCallId) {
3426
3426
  const ranges = args.content ?? [];
3427
3427
  if (ranges.length === 0) return "No ranges provided.";
3428
+ const invalid = ranges.filter((r) => !r || typeof r.summary !== "string" || !r.summary.trim() || !r.startId || !r.endId);
3429
+ if (invalid.length > 0) {
3430
+ logWarn("compress", { sid: ctx.sessionManager.getSessionId(), event: "invalid-ranges", count: invalid.length });
3431
+ 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.`;
3432
+ }
3428
3433
  const releaseLock = await runtime.acquireLock(ctx.sessionManager.getSessionId());
3429
3434
  try {
3430
3435
  const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
@@ -4492,7 +4497,7 @@ async function statusReport(runtime, ctx) {
4492
4497
  const coveredIds = collectCoveredMessageIds(state);
4493
4498
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
4494
4499
  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;
4500
+ const versionStr = "0.2.0" ? `billion-context-omp@${"0.2.0"}` : void 0;
4496
4501
  return buildStatusPanel({
4497
4502
  version: versionStr,
4498
4503
  tokenCount: sessionTokens,
@@ -4504,6 +4509,237 @@ async function statusReport(runtime, ctx) {
4504
4509
  });
4505
4510
  }
4506
4511
 
4512
+ // src/wire-transform.ts
4513
+ var AI = /* @__PURE__ */ Symbol("acp.streamIndex");
4514
+ function detectWireFormat(payload) {
4515
+ if (payload === null || typeof payload !== "object") return "unknown";
4516
+ const p = payload;
4517
+ const messages = p.messages;
4518
+ if (!Array.isArray(messages)) return "unknown";
4519
+ if ("system" in p || "anthropic_version" in p) return "anthropic";
4520
+ for (const m of messages) {
4521
+ if (m === null || typeof m !== "object") continue;
4522
+ const c = m.content;
4523
+ if (Array.isArray(c)) {
4524
+ for (const b of c) {
4525
+ if (b && typeof b === "object" && typeof b.type === "string") {
4526
+ if (b.type === "tool_use" || b.type === "tool_result" || b.type === "thinking") return "anthropic";
4527
+ if (b.type === "text" && "cache_control" in b) return "anthropic";
4528
+ }
4529
+ }
4530
+ }
4531
+ if (Array.isArray(m.tool_calls)) return "openai";
4532
+ if (m.role === "tool" && typeof m.tool_call_id === "string") return "openai";
4533
+ if (m.role === "system" || m.role === "developer") return "openai";
4534
+ }
4535
+ return "openai";
4536
+ }
4537
+ function anthropicBlocks(m) {
4538
+ return typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content ?? [];
4539
+ }
4540
+ var assistantBase = () => ({
4541
+ api: "anthropic",
4542
+ provider: "anthropic",
4543
+ model: "wire",
4544
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
4545
+ stopReason: "stop",
4546
+ timestamp: Date.now()
4547
+ });
4548
+ function synthesizeStream(payload, format) {
4549
+ const messages = payload.messages ?? [];
4550
+ const stream = [];
4551
+ const back = [];
4552
+ const push = (msg, wi, kind) => {
4553
+ msg[AI] = stream.length;
4554
+ stream.push(msg);
4555
+ back.push({ wi, kind });
4556
+ };
4557
+ if (format === "anthropic") {
4558
+ const toolNames = /* @__PURE__ */ new Map();
4559
+ for (const raw of messages) {
4560
+ if (raw === null || typeof raw !== "object") continue;
4561
+ const m = raw;
4562
+ if (m.role !== "assistant") continue;
4563
+ for (const b of anthropicBlocks(m)) if (b.type === "tool_use" && b.id) toolNames.set(b.id, b.name ?? "");
4564
+ }
4565
+ messages.forEach((raw, wi) => {
4566
+ if (raw === null || typeof raw !== "object") return;
4567
+ const m = raw;
4568
+ const blocks = anthropicBlocks(m);
4569
+ if (m.role === "user") {
4570
+ let texts = [];
4571
+ for (const b of blocks) {
4572
+ if (b.type === "text" && typeof b.text === "string") texts.push(b.text);
4573
+ else if (b.type === "tool_result") {
4574
+ if (texts.length > 0) {
4575
+ push({ role: "user", content: texts.map((t2) => ({ type: "text", text: t2 })), timestamp: Date.now() }, wi, "text");
4576
+ texts = [];
4577
+ }
4578
+ const trText = typeof b.content === "string" ? b.content : Array.isArray(b.content) ? b.content.map((c) => c.text ?? "").join("\n") : "";
4579
+ push({
4580
+ role: "toolResult",
4581
+ content: [{ type: "text", text: trText }],
4582
+ toolName: toolNames.get(b.tool_use_id ?? "") ?? "",
4583
+ toolCallId: b.tool_use_id ?? "",
4584
+ isError: b.is_error === true,
4585
+ timestamp: Date.now()
4586
+ }, wi, "toolResult");
4587
+ }
4588
+ }
4589
+ if (texts.length > 0) push({ role: "user", content: texts.map((t2) => ({ type: "text", text: t2 })), timestamp: Date.now() }, wi, "text");
4590
+ return;
4591
+ }
4592
+ if (m.role === "assistant") {
4593
+ const content = [];
4594
+ for (const b of blocks) {
4595
+ if (b.type === "text" && typeof b.text === "string" && b.text.length > 0) content.push({ type: "text", text: b.text });
4596
+ else if (b.type === "tool_use") content.push({ type: "toolCall", id: b.id, name: b.name, arguments: b.input ?? {} });
4597
+ }
4598
+ if (content.length > 0) push({ role: "assistant", ...assistantBase(), content }, wi, "toolCall");
4599
+ return;
4600
+ }
4601
+ const t = blocks.map((b) => typeof b.text === "string" ? b.text : "").join("\n");
4602
+ if (t) push({ role: "user", content: [{ type: "text", text: t }], timestamp: Date.now() }, wi, "text");
4603
+ });
4604
+ return { stream, back, format };
4605
+ }
4606
+ messages.forEach((raw, wi) => {
4607
+ if (raw === null || typeof raw !== "object") return;
4608
+ const m = raw;
4609
+ const textOf = () => {
4610
+ const c = m.content;
4611
+ if (typeof c === "string") return c;
4612
+ if (Array.isArray(c)) return c.map((p) => p.type === "text" ? p.text ?? "" : "").join("\n");
4613
+ return "";
4614
+ };
4615
+ if (m.role === "system" || m.role === "developer") {
4616
+ const t2 = textOf();
4617
+ if (t2) push({ role: "user", content: [{ type: "text", text: t2 }], timestamp: Date.now() }, wi, "text");
4618
+ return;
4619
+ }
4620
+ if (m.role === "tool") {
4621
+ push({
4622
+ role: "toolResult",
4623
+ content: [{ type: "text", text: textOf() }],
4624
+ toolName: "",
4625
+ toolCallId: m.tool_call_id ?? "",
4626
+ isError: false,
4627
+ timestamp: Date.now()
4628
+ }, wi, "toolResult");
4629
+ return;
4630
+ }
4631
+ if (m.role === "assistant") {
4632
+ const calls = m.tool_calls ?? [];
4633
+ if (calls.length > 0) {
4634
+ const content = [];
4635
+ const t3 = textOf();
4636
+ if (t3) content.push({ type: "text", text: t3 });
4637
+ for (const c of calls) {
4638
+ let args = {};
4639
+ try {
4640
+ args = c.function?.arguments ? JSON.parse(c.function.arguments) : {};
4641
+ } catch {
4642
+ args = { raw: c.function?.arguments ?? "" };
4643
+ }
4644
+ content.push({ type: "toolCall", id: c.id, name: c.function?.name ?? "", arguments: args });
4645
+ }
4646
+ push({ role: "assistant", ...assistantBase(), content }, wi, "toolCall");
4647
+ return;
4648
+ }
4649
+ const t2 = textOf();
4650
+ if (t2) push({ role: "assistant", ...assistantBase(), content: [{ type: "text", text: t2 }] }, wi, "text");
4651
+ return;
4652
+ }
4653
+ const t = textOf();
4654
+ if (t) push({ role: "user", content: [{ type: "text", text: t }], timestamp: Date.now() }, wi, "text");
4655
+ });
4656
+ return { stream, back, format };
4657
+ }
4658
+ function rebuildWirePayload(rebuilt, payload, synth) {
4659
+ const messages = (payload.messages ?? []).slice();
4660
+ const out = [];
4661
+ const agentIndexOf = (m) => m[AI];
4662
+ for (const agent of rebuilt) {
4663
+ const ai = agentIndexOf(agent);
4664
+ if (ai === void 0 || ai >= synth.back.length) {
4665
+ const text2 = extractText(agent.content);
4666
+ const role = agent.role === "assistant" ? "assistant" : "user";
4667
+ if (synth.format === "anthropic") {
4668
+ out.push({ role, content: [{ type: "text", text: text2 }] });
4669
+ } else {
4670
+ out.push({ role, content: text2 });
4671
+ }
4672
+ continue;
4673
+ }
4674
+ const { wi, kind } = synth.back[ai];
4675
+ const src = messages[wi];
4676
+ if (!src) {
4677
+ out.push(agent);
4678
+ continue;
4679
+ }
4680
+ if (synth.format === "anthropic") {
4681
+ const srcMsg2 = src;
4682
+ const blocks = anthropicBlocks(srcMsg2);
4683
+ if (kind === "toolResult") {
4684
+ const block2 = blocks.find((b) => b.type === "tool_result" && b.tool_use_id === agent.toolCallId);
4685
+ const text3 = extractText(agent.content);
4686
+ if (block2) {
4687
+ out.push({ role: "user", content: [{ ...block2, content: [{ type: "text", text: text3 }] }] });
4688
+ continue;
4689
+ }
4690
+ }
4691
+ if (kind === "toolCall") {
4692
+ const callIds = new Set(
4693
+ (agent.content ?? []).filter((b) => b.type === "toolCall" && b.id).map((b) => b.id)
4694
+ );
4695
+ const agentText = extractText(agent.content);
4696
+ const outBlocks = [];
4697
+ let textSeen = false;
4698
+ for (const b of blocks) {
4699
+ if (b.type === "tool_use") {
4700
+ if (callIds.has(b.id ?? "")) outBlocks.push(b);
4701
+ continue;
4702
+ }
4703
+ if (b.type === "text") {
4704
+ if (!textSeen && typeof b.text === "string") {
4705
+ outBlocks.push(b);
4706
+ textSeen = true;
4707
+ }
4708
+ continue;
4709
+ }
4710
+ outBlocks.push(b);
4711
+ }
4712
+ if (agentText && !textSeen) outBlocks.unshift({ type: "text", text: agentText });
4713
+ out.push({ role: "assistant", content: outBlocks });
4714
+ continue;
4715
+ }
4716
+ const text2 = extractText(agent.content);
4717
+ const block = blocks.find((b) => b.type === "text");
4718
+ if (block) out.push({ role: srcMsg2.role, content: [{ ...block, text: text2 }] });
4719
+ else out.push({ role: srcMsg2.role, content: [{ type: "text", text: text2 }] });
4720
+ continue;
4721
+ }
4722
+ const srcMsg = src;
4723
+ const text = extractText(agent.content);
4724
+ if (kind === "toolResult") {
4725
+ out.push({ role: "tool", tool_call_id: agent.toolCallId ?? srcMsg.tool_call_id, content: text });
4726
+ continue;
4727
+ }
4728
+ if (kind === "toolCall") {
4729
+ const callIds = new Set(
4730
+ (agent.content ?? []).filter((b) => b.type === "toolCall" && b.id).map((b) => b.id)
4731
+ );
4732
+ const surviving = (srcMsg.tool_calls ?? []).filter((c) => callIds.has(c.id));
4733
+ const entry = { role: "assistant", content: text };
4734
+ if (surviving.length > 0) entry.tool_calls = surviving;
4735
+ out.push(entry);
4736
+ continue;
4737
+ }
4738
+ out.push({ role: srcMsg.role === "assistant" ? "assistant" : srcMsg.role, content: text });
4739
+ }
4740
+ return { ...payload, messages: out };
4741
+ }
4742
+
4507
4743
  // src/auto-compress.ts
4508
4744
  import { readFileSync } from "fs";
4509
4745
  import { join as join3 } from "path";
@@ -4934,7 +5170,7 @@ async function checkForUpdate(autoUpdate, notify) {
4934
5170
  const data = await res.json();
4935
5171
  const latest = data.version;
4936
5172
  if (!latest) return;
4937
- const current = runtimeVersion ?? "0.1.9";
5173
+ const current = runtimeVersion ?? "0.2.0";
4938
5174
  const hasUpdate = isNewer(latest, current);
4939
5175
  debug.event("update-check", {
4940
5176
  current,
@@ -5149,6 +5385,7 @@ var KNOWN = /* @__PURE__ */ new Set([
5149
5385
  "debug",
5150
5386
  "autoUpdate",
5151
5387
  "modelContextLimit",
5388
+ "transformMode",
5152
5389
  "toolBashDefaultTimeout",
5153
5390
  "toolOutputMaxBytes",
5154
5391
  "delegate",
@@ -5192,6 +5429,7 @@ function createAcpExtension(adapter = {}) {
5192
5429
  wireSessionLifecycle(pi, runtime);
5193
5430
  wireContextTransform(pi, runtime);
5194
5431
  wireSystemPrompt(pi, runtime);
5432
+ wireProviderTransform(pi, runtime);
5195
5433
  wireProviderDebug(pi);
5196
5434
  wireToolGuardrails(pi, runtime);
5197
5435
  pi.registerTool(makeCompressTool(runtime));
@@ -5248,7 +5486,7 @@ function wireCompactionDisable(pi, runtime) {
5248
5486
  function wireSessionLifecycle(pi, runtime) {
5249
5487
  pi.on("session_start", async (_event, ctx) => {
5250
5488
  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 });
5489
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.0" : null });
5252
5490
  try {
5253
5491
  const user = await loadUserConfig(ctx.cwd);
5254
5492
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
@@ -5275,130 +5513,168 @@ function wireSessionLifecycle(pi, runtime) {
5275
5513
  closeLogStream();
5276
5514
  });
5277
5515
  }
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 });
5516
+ async function transformStream(ctx, runtime, input, mode) {
5517
+ const sid = ctx.sessionManager.getSessionId();
5518
+ const release = await runtime.acquireLock(sid);
5519
+ try {
5520
+ if (input.length === 0) {
5521
+ debug.event("empty-stream-bypass", { sid });
5522
+ return void 0;
5523
+ }
5524
+ debug.event("context-in-raw", { sid, msgs: input.length, mode });
5525
+ const { state, coreMessages, originalById, streamLen } = runtime.foldStream(ctx, input);
5526
+ const preTurnNudgeBaseline = state.nudge.lastPerMessageNudgeTokens;
5527
+ const preTurnNudgeShownTokens = state.nudge.lastNudgeShownTokens;
5528
+ const preTurnNudgeShownByTier = state.nudge.lastShownByTier;
5529
+ const config = runtime.configFor(ctx);
5530
+ const coveredIds = collectCoveredMessageIds(state);
5531
+ const systemPromptTokens = estimateTextTokens2(getSystemPromptText(ctx) ?? "");
5532
+ const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5533
+ const sessionTokens = ctx.getContextUsage?.()?.tokens ?? null;
5534
+ const tokenCount = sentTokens;
5535
+ debug.event("context-in", {
5536
+ sid,
5537
+ mode,
5538
+ streamLen,
5539
+ coreMsgs: coreMessages.length,
5540
+ tokenCount,
5541
+ sessionTokens,
5542
+ limit: config.modelContextLimit,
5543
+ blocksBefore: state.blocks.length,
5544
+ activeBefore: state.blocks.filter((b) => b.active).length
5545
+ });
5546
+ const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
5547
+ runtime.commitFoldState(ctx, turn.state);
5548
+ logInfo("turn", {
5549
+ sid,
5550
+ inMsgs: coreMessages.length,
5551
+ outMsgs: turn.messages.length,
5552
+ tokens: tokenCount,
5553
+ sessionTokens,
5554
+ pct: config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null,
5555
+ limit: config.modelContextLimit,
5556
+ nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
5557
+ nudgeReason: turn.nudge?.reason ?? null,
5558
+ blocks: turn.state.blocks.length,
5559
+ activeBlocks: turn.state.blocks.filter((b) => b.active).length
5560
+ });
5561
+ debug.event("processTurn", {
5562
+ outMsgs: turn.messages.length,
5563
+ summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5564
+ prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5565
+ nudgeShouldInject: turn.nudge?.shouldInject ?? false,
5566
+ nudgeReason: turn.nudge?.reason ?? null,
5567
+ nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
5568
+ nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
5569
+ nudgeTier: turn.nudge?.tier ?? null,
5570
+ nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
5571
+ nudgeProtectedCount: turn.nudge?.protectedRanges?.length ?? 0,
5572
+ nothingToCompress: turn.nudge?.reason?.includes("nothing to compress") ?? false,
5573
+ blocksAfter: turn.state.blocks.length,
5574
+ activeAfter: turn.state.blocks.filter((b) => b.active).length
5575
+ });
5576
+ const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
5577
+ debug.event("core-out", {
5578
+ sid,
5579
+ coreOutMsgs: turn.messages.length,
5580
+ originalByIdSize: originalById.size,
5581
+ rebuiltMsgs: rebuilt.length
5582
+ });
5583
+ const debugOn2 = debug.enabled;
5584
+ let nudgeInjected = false;
5585
+ if (turn.nudge?.shouldInject) {
5586
+ const lastUser = [...input].reverse().find((m) => m.role === "user");
5587
+ const tailText = lastUser ? JSON.stringify(lastUser.content ?? "") : "";
5588
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
5589
+ if (isFeedbackView) {
5590
+ debug.event("nudge-feedback-skip", { sid: ctx.sessionManager.getSessionId(), msgs: input.length });
5591
+ } else {
5592
+ const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
5593
+ const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
5594
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
5595
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
5596
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
5597
+ if (suppressed) {
5598
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
5599
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
5600
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5601
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5355
5602
  } 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 ? `
5603
+ nudgeInjected = true;
5604
+ {
5605
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
5606
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
5607
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
5608
+ const example = top ? `
5373
5609
 
5374
5610
  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]" : ""}
5611
+ rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
5612
+ if (emergency) {
5613
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
5614
+ }
5615
+ if (debugOn2 && ctx.hasUI) {
5616
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
5381
5617
  ${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
5618
  }
5619
+ debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
5385
5620
  }
5386
5621
  }
5387
5622
  }
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 };
5623
+ }
5624
+ dumpContextMessages(rebuilt, {
5625
+ sid,
5626
+ injected: nudgeInjected,
5627
+ emergency: turn.nudge?.breakdown?.emergencyOverride === 1
5628
+ });
5629
+ await checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
5630
+ if (ctx.hasUI) ctx.ui.notify(msg);
5631
+ });
5632
+ return { rebuilt, nudgeInjected };
5633
+ } catch (e) {
5634
+ logThrow("context", e, { sid, phase: "transform", mode });
5635
+ throw e;
5636
+ } finally {
5637
+ release();
5638
+ }
5639
+ }
5640
+ function wireContextTransform(pi, runtime) {
5641
+ pi.on("context", async (event, ctx) => {
5642
+ if ((runtime.adapter.transformMode ?? "context") === "provider") {
5643
+ debug.event("context-observer-skip", { sid: ctx.sessionManager.getSessionId(), msgs: event.messages?.length ?? 0 });
5644
+ return void 0;
5645
+ }
5646
+ const result = await transformStream(ctx, runtime, event.messages ?? [], "context");
5647
+ if (!result) return void 0;
5648
+ return { messages: result.rebuilt };
5649
+ });
5650
+ }
5651
+ function wireProviderTransform(pi, runtime) {
5652
+ pi.on("before_provider_request", async (event, ctx) => {
5653
+ if ((runtime.adapter.transformMode ?? "context") !== "provider") return void 0;
5654
+ const payload = event.payload;
5655
+ if (payload === null || typeof payload !== "object" || !Array.isArray(payload.messages)) return void 0;
5656
+ const sid = ctx.sessionManager?.getSessionId?.() ?? "";
5657
+ const fmt2 = detectWireFormat(payload);
5658
+ if (fmt2 === "unknown") {
5659
+ debug.event("provider-transform-unknown-format", { sid });
5660
+ return void 0;
5661
+ }
5662
+ try {
5663
+ const synth = synthesizeStream(payload, fmt2);
5664
+ if (synth.stream.length === 0) return void 0;
5665
+ const result = await transformStream(ctx, runtime, synth.stream, "provider");
5666
+ if (!result) return void 0;
5667
+ const wireOut = rebuildWirePayload(result.rebuilt, payload, synth);
5668
+ const outMsgs = wireOut.messages?.length ?? 0;
5669
+ const inMsgs = payload.messages?.length ?? 0;
5670
+ if (outMsgs !== inMsgs || wireOut !== payload) {
5671
+ logInfo("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudge: result.nudgeInjected ? "injected" : "idle" });
5672
+ }
5673
+ debug.event("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudgeInjected: result.nudgeInjected });
5674
+ return wireOut === payload ? void 0 : wireOut;
5397
5675
  } catch (e) {
5398
- logThrow("context", e, { sid, phase: "transform" });
5399
- throw e;
5400
- } finally {
5401
- release();
5676
+ logThrow("provider-transform", e, { sid, fmt: fmt2 });
5677
+ return void 0;
5402
5678
  }
5403
5679
  });
5404
5680
  }