@esankhan3/anvil-agent-core 0.2.0 → 0.4.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.
Files changed (175) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/fetch-pool.integration.test.d.ts +15 -0
  3. package/dist/__tests__/fetch-pool.integration.test.d.ts.map +1 -0
  4. package/dist/__tests__/fetch-pool.integration.test.js +109 -0
  5. package/dist/__tests__/fetch-pool.integration.test.js.map +1 -0
  6. package/dist/__tests__/fetch-pool.test.d.ts +15 -0
  7. package/dist/__tests__/fetch-pool.test.d.ts.map +1 -0
  8. package/dist/__tests__/fetch-pool.test.js +115 -0
  9. package/dist/__tests__/fetch-pool.test.js.map +1 -0
  10. package/dist/__tests__/gemini-adapter-recording.test.d.ts +11 -0
  11. package/dist/__tests__/gemini-adapter-recording.test.d.ts.map +1 -0
  12. package/dist/__tests__/gemini-adapter-recording.test.js +108 -0
  13. package/dist/__tests__/gemini-adapter-recording.test.js.map +1 -0
  14. package/dist/__tests__/legacy-adapter-language-model.test.d.ts +11 -0
  15. package/dist/__tests__/legacy-adapter-language-model.test.d.ts.map +1 -0
  16. package/dist/__tests__/legacy-adapter-language-model.test.js +102 -0
  17. package/dist/__tests__/legacy-adapter-language-model.test.js.map +1 -0
  18. package/dist/__tests__/ollama-adapter-recording.test.d.ts +15 -0
  19. package/dist/__tests__/ollama-adapter-recording.test.d.ts.map +1 -0
  20. package/dist/__tests__/ollama-adapter-recording.test.js +128 -0
  21. package/dist/__tests__/ollama-adapter-recording.test.js.map +1 -0
  22. package/dist/__tests__/openrouter-prefill.integration.test.d.ts +19 -0
  23. package/dist/__tests__/openrouter-prefill.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/openrouter-prefill.integration.test.js +410 -0
  25. package/dist/__tests__/openrouter-prefill.integration.test.js.map +1 -0
  26. package/dist/__tests__/prefill-truncate.test.d.ts +8 -0
  27. package/dist/__tests__/prefill-truncate.test.d.ts.map +1 -0
  28. package/dist/__tests__/prefill-truncate.test.js +75 -0
  29. package/dist/__tests__/prefill-truncate.test.js.map +1 -0
  30. package/dist/__tests__/router-circuit-breaker.test.js +1 -1
  31. package/dist/__tests__/router-circuit-breaker.test.js.map +1 -1
  32. package/dist/__tests__/router-classify-unified.test.d.ts +12 -0
  33. package/dist/__tests__/router-classify-unified.test.d.ts.map +1 -0
  34. package/dist/__tests__/router-classify-unified.test.js +108 -0
  35. package/dist/__tests__/router-classify-unified.test.js.map +1 -0
  36. package/dist/__tests__/router-fallback.test.js +1 -1
  37. package/dist/__tests__/router-fallback.test.js.map +1 -1
  38. package/dist/__tests__/router-model-registry.test.js +24 -0
  39. package/dist/__tests__/router-model-registry.test.js.map +1 -1
  40. package/dist/__tests__/router-retry.test.js +3 -5
  41. package/dist/__tests__/router-retry.test.js.map +1 -1
  42. package/dist/__tests__/router-runagent.test.d.ts +14 -0
  43. package/dist/__tests__/router-runagent.test.d.ts.map +1 -0
  44. package/dist/__tests__/router-runagent.test.js +95 -0
  45. package/dist/__tests__/router-runagent.test.js.map +1 -0
  46. package/dist/__tests__/router-scaffold.test.js +2 -1
  47. package/dist/__tests__/router-scaffold.test.js.map +1 -1
  48. package/dist/__tests__/router-spend-ledger.test.js +1 -1
  49. package/dist/__tests__/router-spend-ledger.test.js.map +1 -1
  50. package/dist/__tests__/router-stream-fallback.test.d.ts +13 -0
  51. package/dist/__tests__/router-stream-fallback.test.d.ts.map +1 -0
  52. package/dist/__tests__/router-stream-fallback.test.js +129 -0
  53. package/dist/__tests__/router-stream-fallback.test.js.map +1 -0
  54. package/dist/__tests__/router-telemetry.test.js +3 -1
  55. package/dist/__tests__/router-telemetry.test.js.map +1 -1
  56. package/dist/__tests__/util/mock-upstream.d.ts +77 -0
  57. package/dist/__tests__/util/mock-upstream.d.ts.map +1 -0
  58. package/dist/__tests__/util/mock-upstream.js +219 -0
  59. package/dist/__tests__/util/mock-upstream.js.map +1 -0
  60. package/dist/adk-adapter.d.ts.map +1 -1
  61. package/dist/adk-adapter.js +51 -0
  62. package/dist/adk-adapter.js.map +1 -1
  63. package/dist/adk-anthropic-llm.d.ts.map +1 -1
  64. package/dist/adk-anthropic-llm.js +23 -10
  65. package/dist/adk-anthropic-llm.js.map +1 -1
  66. package/dist/agent/session/adapter.d.ts +25 -0
  67. package/dist/agent/session/adapter.d.ts.map +1 -1
  68. package/dist/agent/session/adapter.js +4 -0
  69. package/dist/agent/session/adapter.js.map +1 -1
  70. package/dist/agent/session/index.d.ts +1 -0
  71. package/dist/agent/session/index.d.ts.map +1 -1
  72. package/dist/agent/session/index.js +1 -0
  73. package/dist/agent/session/index.js.map +1 -1
  74. package/dist/agent/session/language-model-bridge.d.ts.map +1 -1
  75. package/dist/agent/session/language-model-bridge.js +30 -2
  76. package/dist/agent/session/language-model-bridge.js.map +1 -1
  77. package/dist/agent/session/legacy-adapter-language-model.d.ts +27 -0
  78. package/dist/agent/session/legacy-adapter-language-model.d.ts.map +1 -0
  79. package/dist/agent/session/legacy-adapter-language-model.js +221 -0
  80. package/dist/agent/session/legacy-adapter-language-model.js.map +1 -0
  81. package/dist/agent/session/types.d.ts +32 -0
  82. package/dist/agent/session/types.d.ts.map +1 -1
  83. package/dist/agent/session/types.js.map +1 -1
  84. package/dist/claude-adapter.d.ts.map +1 -1
  85. package/dist/claude-adapter.js +27 -0
  86. package/dist/claude-adapter.js.map +1 -1
  87. package/dist/fetch-pool.d.ts +47 -0
  88. package/dist/fetch-pool.d.ts.map +1 -0
  89. package/dist/fetch-pool.js +132 -0
  90. package/dist/fetch-pool.js.map +1 -0
  91. package/dist/gemini-adapter.d.ts.map +1 -1
  92. package/dist/gemini-adapter.js +65 -6
  93. package/dist/gemini-adapter.js.map +1 -1
  94. package/dist/index.d.ts +11 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +6 -0
  97. package/dist/index.js.map +1 -1
  98. package/dist/ollama-adapter.d.ts.map +1 -1
  99. package/dist/ollama-adapter.js +145 -15
  100. package/dist/ollama-adapter.js.map +1 -1
  101. package/dist/openrouter-adapter.d.ts.map +1 -1
  102. package/dist/openrouter-adapter.js +296 -15
  103. package/dist/openrouter-adapter.js.map +1 -1
  104. package/dist/prefill/strip.d.ts +49 -0
  105. package/dist/prefill/strip.d.ts.map +1 -0
  106. package/dist/prefill/strip.js +93 -0
  107. package/dist/prefill/strip.js.map +1 -0
  108. package/dist/prefill/translate.d.ts +55 -0
  109. package/dist/prefill/translate.d.ts.map +1 -0
  110. package/dist/prefill/translate.js +162 -0
  111. package/dist/prefill/translate.js.map +1 -0
  112. package/dist/prefill/truncate.d.ts +62 -0
  113. package/dist/prefill/truncate.d.ts.map +1 -0
  114. package/dist/prefill/truncate.js +77 -0
  115. package/dist/prefill/truncate.js.map +1 -0
  116. package/dist/provider-liveness.d.ts.map +1 -1
  117. package/dist/provider-liveness.js +17 -6
  118. package/dist/provider-liveness.js.map +1 -1
  119. package/dist/router/errors.d.ts +24 -7
  120. package/dist/router/errors.d.ts.map +1 -1
  121. package/dist/router/errors.js +71 -14
  122. package/dist/router/errors.js.map +1 -1
  123. package/dist/router/index.d.ts +3 -2
  124. package/dist/router/index.d.ts.map +1 -1
  125. package/dist/router/index.js +2 -1
  126. package/dist/router/index.js.map +1 -1
  127. package/dist/router/local-executor.d.ts.map +1 -1
  128. package/dist/router/local-executor.js +19 -6
  129. package/dist/router/local-executor.js.map +1 -1
  130. package/dist/router/model-registry.d.ts +22 -0
  131. package/dist/router/model-registry.d.ts.map +1 -1
  132. package/dist/router/model-registry.js +64 -1
  133. package/dist/router/model-registry.js.map +1 -1
  134. package/dist/router/provider-registry-resolver.d.ts +25 -0
  135. package/dist/router/provider-registry-resolver.d.ts.map +1 -0
  136. package/dist/router/provider-registry-resolver.js +84 -0
  137. package/dist/router/provider-registry-resolver.js.map +1 -0
  138. package/dist/router/retry.d.ts +3 -0
  139. package/dist/router/retry.d.ts.map +1 -1
  140. package/dist/router/retry.js +4 -1
  141. package/dist/router/retry.js.map +1 -1
  142. package/dist/router/router.d.ts +79 -1
  143. package/dist/router/router.d.ts.map +1 -1
  144. package/dist/router/router.js +256 -2
  145. package/dist/router/router.js.map +1 -1
  146. package/dist/router/types.d.ts +1 -1
  147. package/dist/router/types.d.ts.map +1 -1
  148. package/dist/router/types.js +1 -0
  149. package/dist/router/types.js.map +1 -1
  150. package/dist/single-shot.d.ts.map +1 -1
  151. package/dist/single-shot.js +26 -1
  152. package/dist/single-shot.js.map +1 -1
  153. package/dist/tools/builtin.d.ts +19 -0
  154. package/dist/tools/builtin.d.ts.map +1 -1
  155. package/dist/tools/builtin.js +51 -0
  156. package/dist/tools/builtin.js.map +1 -1
  157. package/dist/turn-recorder/__tests__/turn-recorder.test.d.ts +20 -0
  158. package/dist/turn-recorder/__tests__/turn-recorder.test.d.ts.map +1 -0
  159. package/dist/turn-recorder/__tests__/turn-recorder.test.js +282 -0
  160. package/dist/turn-recorder/__tests__/turn-recorder.test.js.map +1 -0
  161. package/dist/turn-recorder/hash.d.ts +10 -0
  162. package/dist/turn-recorder/hash.d.ts.map +1 -0
  163. package/dist/turn-recorder/hash.js +35 -0
  164. package/dist/turn-recorder/hash.js.map +1 -0
  165. package/dist/turn-recorder/index.d.ts +119 -0
  166. package/dist/turn-recorder/index.d.ts.map +1 -0
  167. package/dist/turn-recorder/index.js +326 -0
  168. package/dist/turn-recorder/index.js.map +1 -0
  169. package/dist/turn-recorder/types.d.ts +227 -0
  170. package/dist/turn-recorder/types.d.ts.map +1 -0
  171. package/dist/turn-recorder/types.js +20 -0
  172. package/dist/turn-recorder/types.js.map +1 -0
  173. package/dist/types.d.ts +35 -2
  174. package/dist/types.d.ts.map +1 -1
  175. package/package.json +4 -3
@@ -0,0 +1,128 @@
1
+ /**
2
+ * §H4 — turn recording + replay-equivalence for the Ollama AGENTIC adapter.
3
+ *
4
+ * The agentic port records per-turn assistant-start / tool_use / tool_result /
5
+ * assistant-end and, crucially, HONORS `replayed` on a same-runId resume so the
6
+ * recorded effects re-issue in order WITHOUT re-calling the upstream or
7
+ * re-executing tools. This is the guarantee that prevents a crash-resume from
8
+ * tripping a DeterminismViolation under FO1's runId reuse.
9
+ *
10
+ * Test 1 records a 2-turn (tool-call → text) run and asserts the effect log.
11
+ * Test 2 replays that exact log and asserts ZERO upstream fetches + ZERO tool
12
+ * executions, with byte-identical output.
13
+ */
14
+ import { describe, it, before, after } from 'node:test';
15
+ import assert from 'node:assert/strict';
16
+ import { OllamaAdapter } from '../ollama-adapter.js';
17
+ import { TurnRecorder } from '../turn-recorder/index.js';
18
+ function ollamaText(text) {
19
+ return new Response([
20
+ JSON.stringify({ message: { content: text } }),
21
+ JSON.stringify({ done: true, prompt_eval_count: 5, eval_count: 7, total_duration: 1_000_000 }),
22
+ ].join('\n') + '\n', { status: 200 });
23
+ }
24
+ function ollamaToolCall(name, args) {
25
+ return new Response(JSON.stringify({
26
+ done: true, prompt_eval_count: 3, eval_count: 2, total_duration: 500_000,
27
+ message: { role: 'assistant', content: '', tool_calls: [{ function: { name, arguments: args } }] },
28
+ }) + '\n', { status: 200 });
29
+ }
30
+ class StubWritable {
31
+ write() { return true; }
32
+ end() { }
33
+ on() { }
34
+ }
35
+ class FakeExecutor {
36
+ handlers;
37
+ calls = [];
38
+ constructor(handlers) {
39
+ this.handlers = handlers;
40
+ }
41
+ listSchemas() {
42
+ return Object.keys(this.handlers).map((name) => ({
43
+ name, description: `${name} tool`, inputSchema: { type: 'object', properties: {} },
44
+ }));
45
+ }
46
+ async execute(call) {
47
+ this.calls.push(call);
48
+ return this.handlers[call.name]();
49
+ }
50
+ }
51
+ /** Record (seed empty) or replay (seed populated) fake EffectRuntime. */
52
+ function fakeRuntime(seed = {}) {
53
+ const log = [];
54
+ const runtime = {
55
+ async effect(name, fn, _opts) {
56
+ if (name in seed) {
57
+ log.push({ name, payload: seed[name] });
58
+ return seed[name];
59
+ }
60
+ const payload = await fn();
61
+ log.push({ name, payload });
62
+ return payload;
63
+ },
64
+ peekRecorded(name) {
65
+ return (name in seed ? seed[name] : undefined);
66
+ },
67
+ };
68
+ return { runtime, log };
69
+ }
70
+ const baseConfig = {
71
+ userPrompt: 'list the files then finish',
72
+ model: 'qwen3:14b',
73
+ workingDir: '/tmp',
74
+ stage: 'build',
75
+ persona: 'engineer',
76
+ sessionId: 'run-oll',
77
+ };
78
+ let originalFetch;
79
+ before(() => { originalFetch = globalThis.fetch; });
80
+ after(() => { globalThis.fetch = originalFetch; });
81
+ describe('OllamaAdapter — H4 recording + replay-equivalence', () => {
82
+ // Shared between the two tests: test 1 records into `recordedLog`.
83
+ let recordedLog = [];
84
+ let liveFetchCount = 0;
85
+ it('records assistant-start/tool_use/tool_result/assistant-end across a tool-call run', async () => {
86
+ liveFetchCount = 0;
87
+ globalThis.fetch = (async () => {
88
+ liveFetchCount += 1;
89
+ return liveFetchCount === 1 ? ollamaToolCall('list', {}) : ollamaText('done');
90
+ });
91
+ const fx = new FakeExecutor({ list: () => ({ content: 'a.ts\nb.ts', isError: false }) });
92
+ const { runtime, log } = fakeRuntime();
93
+ const recorder = new TurnRecorder({ runtime, partialSink: () => { }, runId: 'run-oll', stepId: 'build' });
94
+ const r = await new OllamaAdapter().run({ ...baseConfig, toolExecutor: fx, turnRecorder: recorder }, new StubWritable());
95
+ assert.equal(r.output, 'done');
96
+ assert.equal(liveFetchCount, 2, 'two upstream turns on the live path');
97
+ assert.equal(fx.calls.length, 1, 'tool executed once on the live path');
98
+ const names = log.map((e) => e.name);
99
+ assert.ok(names.includes('turn:0:assistant-start'));
100
+ assert.ok(names.includes('turn:0:tool_use:0'));
101
+ assert.ok(names.includes('turn:0:tool_result:0'));
102
+ assert.ok(names.includes('turn:0:assistant-end'));
103
+ assert.ok(names.includes('turn:1:assistant-start'));
104
+ assert.ok(names.includes('turn:1:assistant-end'));
105
+ const start0 = log.find((e) => e.name === 'turn:0:assistant-start');
106
+ assert.equal(start0.payload.userPrompt, 'list the files then finish');
107
+ const end0 = log.find((e) => e.name === 'turn:0:assistant-end');
108
+ assert.equal(end0.payload.stopReason, 'tool_use');
109
+ assert.ok(Array.isArray(end0.payload.historyDelta));
110
+ recordedLog = log;
111
+ });
112
+ it('replays the recorded run with ZERO upstream fetches and ZERO tool executions', async () => {
113
+ // Build the replay seed from test 1's effect log.
114
+ const seed = {};
115
+ for (const e of recordedLog)
116
+ seed[e.name] = e.payload;
117
+ let replayFetchCount = 0;
118
+ globalThis.fetch = (async () => { replayFetchCount += 1; return ollamaText('SHOULD NOT RUN'); });
119
+ const fx = new FakeExecutor({ list: () => { throw new Error('tool exec must NOT run on replay'); } });
120
+ const { runtime } = fakeRuntime(seed);
121
+ const recorder = new TurnRecorder({ runtime, partialSink: () => { }, runId: 'run-oll', stepId: 'build' });
122
+ const r = await new OllamaAdapter().run({ ...baseConfig, toolExecutor: fx, turnRecorder: recorder }, new StubWritable());
123
+ assert.equal(replayFetchCount, 0, 'replay must NOT hit the upstream');
124
+ assert.equal(fx.calls.length, 0, 'replay must NOT re-execute tools');
125
+ assert.equal(r.output, 'done', 'replay reproduces the recorded output byte-for-byte');
126
+ });
127
+ });
128
+ //# sourceMappingURL=ollama-adapter-recording.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama-adapter-recording.test.js","sourceRoot":"","sources":["../../src/__tests__/ollama-adapter-recording.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Integration tests for the H2 turn-level durable resume machinery in
3
+ * the OpenRouter adapter (v2 ADR §2.1 / §2.3 / §2.5), driven against a
4
+ * real HTTP server (`MockUpstream`) that can cut its SSE stream at a
5
+ * chosen frame to simulate a model dying mid-output.
6
+ *
7
+ * Proves:
8
+ * 1. Burn mid-SSE → the partial sink receives the EXACT assistant
9
+ * text streamed before the cut (not an empty string). This is the
10
+ * whole point of surfacing the accumulator out of consumeSSE.
11
+ * 2. Mid-`tool_use` truncation (§2.1.1) → a tool_call whose streamed
12
+ * args are incomplete JSON is NOT counted in `toolUsesEmitted`.
13
+ * 3. Prefill (§2.3) → when config.prefill is present, the adapter's
14
+ * outbound request carries the re-presented tool_use/tool_result
15
+ * pair AND a trailing assistant message with the partial text, so
16
+ * the new model continues from the exact stopping point.
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=openrouter-prefill.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter-prefill.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/openrouter-prefill.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Integration tests for the H2 turn-level durable resume machinery in
3
+ * the OpenRouter adapter (v2 ADR §2.1 / §2.3 / §2.5), driven against a
4
+ * real HTTP server (`MockUpstream`) that can cut its SSE stream at a
5
+ * chosen frame to simulate a model dying mid-output.
6
+ *
7
+ * Proves:
8
+ * 1. Burn mid-SSE → the partial sink receives the EXACT assistant
9
+ * text streamed before the cut (not an empty string). This is the
10
+ * whole point of surfacing the accumulator out of consumeSSE.
11
+ * 2. Mid-`tool_use` truncation (§2.1.1) → a tool_call whose streamed
12
+ * args are incomplete JSON is NOT counted in `toolUsesEmitted`.
13
+ * 3. Prefill (§2.3) → when config.prefill is present, the adapter's
14
+ * outbound request carries the re-presented tool_use/tool_result
15
+ * pair AND a trailing assistant message with the partial text, so
16
+ * the new model continues from the exact stopping point.
17
+ */
18
+ import { describe, it, beforeEach, afterEach } from 'node:test';
19
+ import assert from 'node:assert/strict';
20
+ import { Writable } from 'node:stream';
21
+ import { OpenRouterAdapter } from '../openrouter-adapter.js';
22
+ import { TurnRecorder } from '../turn-recorder/index.js';
23
+ import { resetAllPools } from '../fetch-pool.js';
24
+ import { MockUpstream } from './util/mock-upstream.js';
25
+ // ── Live pass-through effect runtime (no persistence) ─────────────────
26
+ function liveRuntime() {
27
+ return { async effect(_n, fn) { return fn(); } };
28
+ }
29
+ // ── Replay runtime: serves recorded effect payloads by name + peek ─────
30
+ // Simulates a crash-resume where the durable log already holds the turn's
31
+ // sub-effects. `effect()` returns the recorded payload (replay); `fn` runs
32
+ // only for un-recorded names. `peekRecorded` lets startTurn detect a
33
+ // finished turn so the adapter skips the upstream call.
34
+ function replayRuntime(recorded) {
35
+ return {
36
+ async effect(name, fn) {
37
+ return (name in recorded ? recorded[name] : await fn());
38
+ },
39
+ peekRecorded(name) {
40
+ return (name in recorded ? recorded[name] : undefined);
41
+ },
42
+ };
43
+ }
44
+ // ── Recording runtime: stores every effect payload for inspection ─────
45
+ function recordingRuntime() {
46
+ const recorded = new Map();
47
+ return {
48
+ recorded,
49
+ runtime: {
50
+ async effect(name, fn) {
51
+ const v = await fn();
52
+ recorded.set(name, v);
53
+ return v;
54
+ },
55
+ },
56
+ };
57
+ }
58
+ // ── Discarding output sink ────────────────────────────────────────────
59
+ function nullSink() {
60
+ return new Writable({ write(_c, _e, cb) { cb(); } });
61
+ }
62
+ // ── Minimal tool executor advertising one write_file tool ─────────────
63
+ function fakeExecutor() {
64
+ return {
65
+ listSchemas() {
66
+ return [{
67
+ name: 'write_file',
68
+ description: 'write a file',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: { path: { type: 'string' }, content: { type: 'string' } },
72
+ required: ['path', 'content'],
73
+ },
74
+ }];
75
+ },
76
+ async execute() {
77
+ return { content: 'ok', isError: false };
78
+ },
79
+ };
80
+ }
81
+ function buildConfig(over = {}) {
82
+ return {
83
+ userPrompt: 'do the thing',
84
+ projectPrompt: 'you are a coder',
85
+ model: 'qwen/qwen3',
86
+ workingDir: '/tmp',
87
+ stage: 'build',
88
+ persona: 'engineer',
89
+ sessionId: 'sess-1',
90
+ ...over,
91
+ };
92
+ }
93
+ describe('OpenRouterAdapter — H2 turn-level durable resume', () => {
94
+ let up;
95
+ beforeEach(async () => {
96
+ await resetAllPools();
97
+ up = await MockUpstream.start();
98
+ process.env.OPENROUTER_API_KEY = 'test-key';
99
+ process.env.OPENROUTER_BASE_URL = up.baseUrl;
100
+ });
101
+ afterEach(async () => {
102
+ await up.stop();
103
+ await resetAllPools();
104
+ delete process.env.OPENROUTER_BASE_URL;
105
+ });
106
+ it('flushes the streamed partial text when the stream dies mid-SSE', async () => {
107
+ const partials = [];
108
+ const recorder = new TurnRecorder({
109
+ runtime: liveRuntime(),
110
+ partialSink: (p) => { partials.push(p); },
111
+ runId: 'run-1', stepId: 'build',
112
+ });
113
+ up.script([
114
+ MockUpstream.textFrame('Hello, I am writing'),
115
+ MockUpstream.textFrame(' a function to add'),
116
+ MockUpstream.textFrame(' two numbers'),
117
+ ]).cutAfterFrames(2); // die after 2 text frames
118
+ const adapter = new OpenRouterAdapter();
119
+ await assert.rejects(adapter.run(buildConfig({ turnRecorder: recorder }), nullSink()));
120
+ assert.equal(partials.length, 1, 'exactly one partial flushed');
121
+ assert.equal(partials[0].text, 'Hello, I am writing a function to add', 'partial carries the text streamed before the cut');
122
+ assert.equal(partials[0].turn, 0);
123
+ assert.ok(partials[0].turnUuid.length > 0);
124
+ });
125
+ it('excludes a mid-args-truncated tool_call from toolUsesEmitted (§2.1.1)', async () => {
126
+ const partials = [];
127
+ const recorder = new TurnRecorder({
128
+ runtime: liveRuntime(),
129
+ partialSink: (p) => { partials.push(p); },
130
+ runId: 'run-2', stepId: 'build',
131
+ });
132
+ up.script([
133
+ MockUpstream.textFrame('working'),
134
+ // complete tool call — parses
135
+ MockUpstream.toolCallFrame({ index: 0, id: 'tc1', name: 'write_file', argsChunk: '{"path":"a.txt","content":"hi"}' }),
136
+ // truncated tool call — unparseable JSON
137
+ MockUpstream.toolCallFrame({ index: 1, id: 'tc2', name: 'write_file', argsChunk: '{"path":"b.txt' }),
138
+ ]).cutAfterFrames(3);
139
+ const adapter = new OpenRouterAdapter();
140
+ await assert.rejects(adapter.run(buildConfig({ turnRecorder: recorder, toolExecutor: fakeExecutor() }), nullSink()));
141
+ assert.equal(partials.length, 1);
142
+ assert.equal(partials[0].toolUsesEmitted, 1, 'only the cleanly-parsed tool_call is counted; truncated one is dropped');
143
+ });
144
+ it('does NOT flush a partial when the SSE completed but a post-stream effect throws', async () => {
145
+ const partials = [];
146
+ // Runtime that throws specifically on the assistant-end effect — a
147
+ // determinism-style failure AFTER the stream completed cleanly.
148
+ const throwingOnEnd = {
149
+ async effect(name, fn) {
150
+ if (name.endsWith(':assistant-end'))
151
+ throw new Error('determinism violation on end');
152
+ return fn();
153
+ },
154
+ };
155
+ const recorder = new TurnRecorder({
156
+ runtime: throwingOnEnd,
157
+ partialSink: (p) => { partials.push(p); },
158
+ runId: 'run-3', stepId: 'build',
159
+ });
160
+ up.script([
161
+ MockUpstream.textFrame('A complete answer'),
162
+ MockUpstream.finishFrame('stop'),
163
+ MockUpstream.usageFrame({ prompt_tokens: 10, completion_tokens: 4 }),
164
+ MockUpstream.doneFrame(),
165
+ ]).noCut();
166
+ const adapter = new OpenRouterAdapter();
167
+ await assert.rejects(adapter.run(buildConfig({ turnRecorder: recorder }), nullSink()));
168
+ assert.equal(partials.length, 0, 'SSE completed → no partial flushed even though endTurn threw (no spurious complete-text partial)');
169
+ });
170
+ it('REPLAY-SKIP: a fully-recorded terminal turn makes ZERO upstream calls (§H3)', async () => {
171
+ // The whole point of the cutover: on crash-resume, a turn already in
172
+ // the durable log must NOT re-hit the model.
173
+ const recorder = new TurnRecorder({
174
+ runtime: replayRuntime({
175
+ 'turn:0:assistant-start': { turnUuid: 'u', model: 'qwen/qwen3', provider: 'openrouter' },
176
+ 'turn:0:assistant-end': {
177
+ text: 'the recorded answer',
178
+ stopReason: 'end_turn',
179
+ usage: { inputTokens: 20, outputTokens: 4 },
180
+ provenance: { segments: [] },
181
+ historyDelta: [],
182
+ model: 'qwen/qwen3',
183
+ provider: 'openrouter',
184
+ },
185
+ }),
186
+ partialSink: () => { },
187
+ runId: 'run-replay', stepId: 'build',
188
+ });
189
+ // Script frames so that IF the adapter wrongly called upstream, the
190
+ // test would still terminate — but we assert it never connects.
191
+ up.script([MockUpstream.textFrame('LIVE — should never run'), MockUpstream.finishFrame('stop'), MockUpstream.doneFrame()]).noCut();
192
+ const adapter = new OpenRouterAdapter();
193
+ const res = await adapter.run(buildConfig({ turnRecorder: recorder }), nullSink());
194
+ assert.equal(up.capturedRequests().length, 0, 'replayed turn must make NO upstream request');
195
+ assert.equal(res.output, 'the recorded answer', 'output comes from the recorded turn, not the wire');
196
+ });
197
+ it('REPLAY-SKIP: a recorded tool turn replays without re-executing the tool or calling upstream (§H3)', async () => {
198
+ let execRan = false;
199
+ const executor = {
200
+ listSchemas() {
201
+ return [{ name: 'write_file', description: 'w', inputSchema: { type: 'object', properties: {} } }];
202
+ },
203
+ async execute() { execRan = true; return { content: 'LIVE-EXEC', isError: false }; },
204
+ };
205
+ const recorder = new TurnRecorder({
206
+ runtime: replayRuntime({
207
+ // turn 0: a tool turn (one completed tool)
208
+ 'turn:0:assistant-start': { turnUuid: 'u0', model: 'qwen/qwen3', provider: 'openrouter' },
209
+ 'turn:0:tool_use:0': { name: 'write_file', arguments: { path: 'a.txt', content: 'hi' }, idempotencyKey: 'k0' },
210
+ 'turn:0:tool_result:0': { toolUseId: 'tc0', toolName: 'write_file', ok: true, content: 'recorded-write' },
211
+ 'turn:0:assistant-end': {
212
+ text: 'working', stopReason: 'tool_use',
213
+ usage: { inputTokens: 30, outputTokens: 2 },
214
+ provenance: { segments: [] },
215
+ historyDelta: [
216
+ { role: 'assistant', content: 'working', tool_calls: [{ id: 'tc0', type: 'function', function: { name: 'write_file', arguments: '{"path":"a.txt","content":"hi"}' } }] },
217
+ { role: 'tool', content: 'recorded-write', tool_call_id: 'tc0' },
218
+ ],
219
+ model: 'qwen/qwen3', provider: 'openrouter',
220
+ },
221
+ // turn 1: terminal continuation
222
+ 'turn:1:assistant-start': { turnUuid: 'u1', model: 'qwen/qwen3', provider: 'openrouter' },
223
+ 'turn:1:assistant-end': {
224
+ text: ' and done', stopReason: 'end_turn',
225
+ usage: { inputTokens: 35, outputTokens: 3 },
226
+ provenance: { segments: [] }, historyDelta: [],
227
+ model: 'qwen/qwen3', provider: 'openrouter',
228
+ },
229
+ }),
230
+ partialSink: () => { },
231
+ runId: 'run-replay-tools', stepId: 'build',
232
+ });
233
+ up.script([MockUpstream.textFrame('LIVE — should never run'), MockUpstream.finishFrame('stop'), MockUpstream.doneFrame()]).noCut();
234
+ const adapter = new OpenRouterAdapter();
235
+ const res = await adapter.run(buildConfig({ turnRecorder: recorder, toolExecutor: executor }), nullSink());
236
+ assert.equal(execRan, false, 'recorded tool_result is replayed — exec must NOT re-run the side effect');
237
+ assert.equal(up.capturedRequests().length, 0, 'no upstream calls across the replayed multi-turn run');
238
+ assert.equal(res.output, 'working and done', 'output is the concatenation of both replayed turns');
239
+ });
240
+ it('records TWO-SEGMENT provenance + prefilledInputTokens when a prefill is consumed (§2.6/§2.7)', async () => {
241
+ const { runtime, recorded } = recordingRuntime();
242
+ const recorder = new TurnRecorder({
243
+ runtime, partialSink: () => { }, runId: 'run-prov', stepId: 'build',
244
+ });
245
+ const prefill = {
246
+ turnUuid: 'turn-A',
247
+ text: 'partial from A', // 14 chars
248
+ toolUses: [],
249
+ sourceProvider: 'openrouter',
250
+ sourceModel: 'kimi/k2',
251
+ sourceTokens: 99,
252
+ };
253
+ up.script([
254
+ MockUpstream.textFrame(' continued by B'),
255
+ MockUpstream.finishFrame('stop'),
256
+ MockUpstream.usageFrame({ prompt_tokens: 120, completion_tokens: 5 }),
257
+ MockUpstream.doneFrame(),
258
+ ]).noCut();
259
+ const adapter = new OpenRouterAdapter();
260
+ await adapter.run(buildConfig({ model: 'qwen/qwen3', turnRecorder: recorder, prefill }), nullSink());
261
+ const end = recorded.get('turn:0:assistant-end');
262
+ assert.ok(end, 'assistant-end recorded');
263
+ assert.equal(end.usage.prefilledInputTokens, 99, 'prefill sourceTokens carved out for §2.6 reinjection');
264
+ assert.equal(end.provenance.segments.length, 2, 'two segments: prefill + live');
265
+ assert.deepEqual(end.provenance.segments[0], { model: 'kimi/k2', provider: 'openrouter', range: [0, 14], source: 'prefill' });
266
+ assert.equal(end.provenance.segments[1].source, 'live');
267
+ assert.equal(end.provenance.segments[1].model, 'qwen/qwen3');
268
+ assert.deepEqual(end.provenance.segments[1].range, [14, 14 + ' continued by B'.length]);
269
+ });
270
+ it('re-presents a prefill (tool history + partial text) in the next request (§2.3)', async () => {
271
+ const prefill = {
272
+ turnUuid: 'turn-abc',
273
+ text: 'The partial answer so far',
274
+ toolUses: [{
275
+ id: 'tc-prev',
276
+ name: 'read_file',
277
+ input: { path: 'x.ts' },
278
+ result: { toolUseId: 'tc-prev', toolName: 'read_file', ok: true, content: 'file contents here' },
279
+ producedBy: 'openrouter',
280
+ }],
281
+ sourceProvider: 'openrouter',
282
+ sourceTokens: 42,
283
+ };
284
+ // The resumed model just completes cleanly.
285
+ up.script([
286
+ MockUpstream.textFrame(' and the rest of the answer.'),
287
+ MockUpstream.finishFrame('stop'),
288
+ MockUpstream.usageFrame({ prompt_tokens: 100, completion_tokens: 8 }),
289
+ MockUpstream.doneFrame(),
290
+ ]).noCut();
291
+ const adapter = new OpenRouterAdapter();
292
+ await adapter.run(buildConfig({ prefill }), nullSink());
293
+ const req = up.lastRequest();
294
+ assert.ok(req, 'a request was captured');
295
+ const messages = req.body.messages;
296
+ // Trailing assistant message carries the partial continuation.
297
+ const assistantText = messages.find((m) => m.role === 'assistant' && m.content === 'The partial answer so far');
298
+ assert.ok(assistantText, 'trailing assistant continuation present');
299
+ // The recorded tool_use is re-presented as an assistant tool_call.
300
+ const assistantToolCall = messages.find((m) => m.role === 'assistant' && Array.isArray(m.tool_calls)
301
+ && m.tool_calls[0]?.function?.name === 'read_file');
302
+ assert.ok(assistantToolCall, 'tool_use re-presented as assistant tool_call');
303
+ // The recorded tool result is re-presented as a tool message.
304
+ const toolResult = messages.find((m) => m.role === 'tool' && m.content === 'file contents here');
305
+ assert.ok(toolResult, 'tool_result re-presented as tool message');
306
+ });
307
+ it('§Tier 2: splices priorMessages (completed prior turns) BEFORE the user message, in order', async () => {
308
+ const priorMessages = [
309
+ {
310
+ userPrompt: 'explore the repo',
311
+ text: 'I looked around',
312
+ toolUses: [{
313
+ id: 'tc-explore',
314
+ name: 'grep',
315
+ input: { pattern: 'foo' },
316
+ result: { toolUseId: 'tc-explore', toolName: 'grep', ok: true, content: 'found foo' },
317
+ producedBy: 'openrouter',
318
+ }],
319
+ producedBy: 'openrouter',
320
+ },
321
+ ];
322
+ up.script([
323
+ MockUpstream.textFrame('synthesizing'),
324
+ MockUpstream.finishFrame('stop'),
325
+ MockUpstream.usageFrame({ prompt_tokens: 200, completion_tokens: 3 }),
326
+ MockUpstream.doneFrame(),
327
+ ]).noCut();
328
+ const adapter = new OpenRouterAdapter();
329
+ await adapter.run(buildConfig({ userPrompt: 'now synthesize', priorMessages }), nullSink());
330
+ const req = up.lastRequest();
331
+ assert.ok(req);
332
+ const messages = req.body.messages;
333
+ const roles = messages.map((m) => m.role);
334
+ // Order: system, [prior turn: user, assistant(text+tool_calls), tool], then THIS phase's user.
335
+ assert.equal(messages[0].role, 'system');
336
+ assert.equal(messages[1].role, 'user');
337
+ assert.equal(messages[1].content, 'explore the repo', 'prior phase prompt comes first');
338
+ const priorAssistant = messages[2];
339
+ assert.equal(priorAssistant.role, 'assistant');
340
+ assert.equal(priorAssistant.content, 'I looked around', 'completed turn keeps text + tool_calls on ONE message');
341
+ assert.equal(priorAssistant.tool_calls[0]?.function?.name, 'grep');
342
+ assert.equal(messages[3].role, 'tool', 'tool result follows the combined assistant message');
343
+ assert.equal(messages[3].content, 'found foo');
344
+ // The CURRENT phase's user prompt is LAST (after all prior history).
345
+ const lastUser = messages[messages.length - 1];
346
+ assert.equal(lastUser.role, 'user');
347
+ assert.equal(lastUser.content, 'now synthesize', 'current prompt is appended after prior history');
348
+ // No consecutive assistant messages from the combined-turn shape.
349
+ for (let i = 1; i < roles.length; i++) {
350
+ assert.ok(!(roles[i] === 'assistant' && roles[i - 1] === 'assistant'), `no consecutive assistant at ${i}`);
351
+ }
352
+ });
353
+ it('§Tier 2: empty/absent priorMessages is byte-identical to the single-turn path', async () => {
354
+ up.script([
355
+ MockUpstream.textFrame('answer'),
356
+ MockUpstream.finishFrame('stop'),
357
+ MockUpstream.usageFrame({ prompt_tokens: 10, completion_tokens: 1 }),
358
+ MockUpstream.doneFrame(),
359
+ ]).noCut();
360
+ const adapter = new OpenRouterAdapter();
361
+ await adapter.run(buildConfig({ userPrompt: 'just this', priorMessages: [] }), nullSink());
362
+ const messages = up.lastRequest().body.messages;
363
+ assert.deepEqual(messages.map((m) => m.role), ['system', 'user'], 'no prior history injected');
364
+ assert.equal(messages[1].content, 'just this');
365
+ });
366
+ it('records a `stopReason:"burned"` sentinel assistant-end on a mid-SSE burn (§H3 Tier 1 #3)', async () => {
367
+ const { runtime, recorded } = recordingRuntime();
368
+ const recorder = new TurnRecorder({
369
+ runtime, partialSink: () => { }, runId: 'run-burn-sentinel', stepId: 'build',
370
+ });
371
+ up.script([
372
+ MockUpstream.textFrame('I started answering'),
373
+ MockUpstream.textFrame(' but then'),
374
+ MockUpstream.textFrame(' the stream died'),
375
+ ]).cutAfterFrames(2);
376
+ const adapter = new OpenRouterAdapter();
377
+ await assert.rejects(adapter.run(buildConfig({ turnRecorder: recorder }), nullSink()));
378
+ const end = recorded.get('turn:0:assistant-end');
379
+ assert.ok(end, 'a sentinel assistant-end is recorded for the burned turn so replay can skip it');
380
+ assert.equal(end.stopReason, 'burned', 'sentinel marks the turn as burned, not a real completion');
381
+ assert.equal(end.text, 'I started answering but then', 'sentinel carries the streamed-so-far partial text');
382
+ assert.deepEqual(end.historyDelta, [], 'burn before any message append → empty historyDelta');
383
+ });
384
+ it('REPLAY of a burned sentinel re-throws (re-burn) and makes ZERO upstream calls (§H3 Tier 1 #4)', async () => {
385
+ // On crash-resume the burned turn must NOT re-hit the model; it replays
386
+ // the sentinel, re-issues its recorded tools, then re-throws so the chain
387
+ // walker re-derives the same model→turn mapping it had live.
388
+ const recorder = new TurnRecorder({
389
+ runtime: replayRuntime({
390
+ 'turn:0:assistant-start': { turnUuid: 'u0', model: 'qwen/qwen3', provider: 'openrouter' },
391
+ 'turn:0:assistant-end': {
392
+ text: 'partial before burn',
393
+ stopReason: 'burned',
394
+ usage: { inputTokens: 0, outputTokens: 0 },
395
+ provenance: { segments: [] },
396
+ historyDelta: [],
397
+ model: 'qwen/qwen3',
398
+ provider: 'openrouter',
399
+ },
400
+ }),
401
+ partialSink: () => { },
402
+ runId: 'run-burn-replay', stepId: 'build',
403
+ });
404
+ up.script([MockUpstream.textFrame('LIVE — must never run'), MockUpstream.finishFrame('stop'), MockUpstream.doneFrame()]).noCut();
405
+ const adapter = new OpenRouterAdapter();
406
+ await assert.rejects(adapter.run(buildConfig({ turnRecorder: recorder }), nullSink()), (err) => err.name === 'UpstreamError' && err.retryable === true, 'a replayed burned turn re-throws a retryable UpstreamError so chain-fallback advances');
407
+ assert.equal(up.capturedRequests().length, 0, 'replayed burned turn makes NO upstream request');
408
+ });
409
+ });
410
+ //# sourceMappingURL=openrouter-prefill.integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter-prefill.integration.test.js","sourceRoot":"","sources":["../../src/__tests__/openrouter-prefill.integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * §2.3.3 context-window truncation policy — unit tests.
3
+ *
4
+ * Uses the `maxInputTokensFor` override so the budget math is deterministic
5
+ * and independent of the live cost table.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=prefill-truncate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefill-truncate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/prefill-truncate.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}