tuplet 2.29.0 → 2.31.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.
@@ -0,0 +1,455 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Tuplet } from './agent.js';
3
+ import { MemoryRepository } from './providers/repository/memory.js';
4
+ import { resolveSections, evaluateInjections, formatSectionsForPrompt, wrapInjection, renderInjectionsPayload, countUserTurns, extractLastUserText, loadTupletState, } from './prompt-sections.js';
5
+ const endTurn = {
6
+ content: [{ type: 'text', text: 'done' }],
7
+ stopReason: 'end_turn',
8
+ usage: { inputTokens: 1, outputTokens: 1 },
9
+ };
10
+ function recordingLLM(response = endTurn) {
11
+ const calls = [];
12
+ const llm = {
13
+ chat: async (system, messages) => {
14
+ calls.push({ system, messages: JSON.parse(JSON.stringify(messages)) });
15
+ return response;
16
+ },
17
+ getModelId: () => 'stub:stub',
18
+ supportsNativeTools: true,
19
+ };
20
+ return { llm, calls };
21
+ }
22
+ describe('pure helpers', () => {
23
+ it('countUserTurns ignores tool_result-only messages', () => {
24
+ const messages = [
25
+ { role: 'user', content: 'hi' },
26
+ { role: 'assistant', content: [{ type: 'tool_use', id: 't', name: 'x', input: {} }] },
27
+ { role: 'user', content: [{ type: 'tool_result', tool_use_id: 't', content: 'ok' }] },
28
+ { role: 'assistant', content: 'ok' },
29
+ { role: 'user', content: 'again' },
30
+ ];
31
+ expect(countUserTurns(messages)).toBe(2);
32
+ });
33
+ it('extractLastUserText walks backward through messages', () => {
34
+ const messages = [
35
+ { role: 'user', content: 'first' },
36
+ { role: 'assistant', content: 'hi' },
37
+ { role: 'user', content: [{ type: 'tool_result', tool_use_id: 't', content: 'x' }] },
38
+ { role: 'user', content: 'latest' },
39
+ ];
40
+ expect(extractLastUserText(messages)).toBe('latest');
41
+ });
42
+ it('wrapInjection uses <tuplet-note> with kind and name attrs', () => {
43
+ const wrapped = wrapInjection('restoration-mode', 'be gentle');
44
+ expect(wrapped).toContain('<tuplet-note kind="history-injection" name="restoration-mode">');
45
+ expect(wrapped).toContain('</tuplet-note>');
46
+ expect(wrapped).toContain('be gentle');
47
+ });
48
+ it('renderInjectionsPayload joins multiple injections into one block', () => {
49
+ const payload = renderInjectionsPayload([
50
+ { name: 'a', content: 'first', once: true },
51
+ { name: 'b', content: 'second', once: false },
52
+ ]);
53
+ expect(payload).toMatch(/name="a"[\s\S]*first[\s\S]*name="b"[\s\S]*second/);
54
+ });
55
+ it('formatSectionsForPrompt wraps each section', () => {
56
+ const out = formatSectionsForPrompt([
57
+ { name: 'admin', content: 'admin stuff' },
58
+ { name: 'source', content: 'cron source' },
59
+ ]);
60
+ expect(out).toContain('kind="prompt-section" name="admin"');
61
+ expect(out).toContain('kind="prompt-section" name="source"');
62
+ expect(out).toContain('admin stuff');
63
+ expect(out).toContain('cron source');
64
+ });
65
+ });
66
+ describe('resolveSections', () => {
67
+ const ctx = { context: { isAdmin: true }, conversationId: 'c1' };
68
+ it('evaluates sections whose when returns true', async () => {
69
+ const sections = [
70
+ { name: 'admin', when: c => c.context.isAdmin, content: 'ADMIN' },
71
+ { name: 'guest', when: () => false, content: 'GUEST' },
72
+ ];
73
+ const { fired, updatedCache } = await resolveSections(sections, ctx, undefined);
74
+ expect(fired.map(f => f.name)).toEqual(['admin']);
75
+ expect(fired[0].content).toBe('ADMIN');
76
+ expect(updatedCache.admin).toEqual({ fired: true, content: 'ADMIN' });
77
+ expect(updatedCache.guest).toEqual({ fired: false });
78
+ });
79
+ it('reads from cache on subsequent runs without re-evaluating', async () => {
80
+ let evalCount = 0;
81
+ const sections = [
82
+ {
83
+ name: 'admin',
84
+ when: () => {
85
+ evalCount++;
86
+ return true;
87
+ },
88
+ content: 'A',
89
+ },
90
+ ];
91
+ const { updatedCache } = await resolveSections(sections, ctx, undefined);
92
+ expect(evalCount).toBe(1);
93
+ const second = await resolveSections(sections, ctx, updatedCache);
94
+ expect(evalCount).toBe(1);
95
+ expect(second.fired.map(f => f.name)).toEqual(['admin']);
96
+ });
97
+ it('awaits async predicates and async content', async () => {
98
+ const sections = [
99
+ {
100
+ name: 'async',
101
+ when: async () => true,
102
+ content: async () => 'async-content',
103
+ },
104
+ ];
105
+ const { fired } = await resolveSections(sections, ctx, undefined);
106
+ expect(fired[0].content).toBe('async-content');
107
+ });
108
+ it('preserves registration order', async () => {
109
+ const sections = [
110
+ { name: 'a', when: () => true, content: 'A' },
111
+ { name: 'b', when: () => true, content: 'B' },
112
+ { name: 'c', when: () => true, content: 'C' },
113
+ ];
114
+ const { fired } = await resolveSections(sections, ctx, undefined);
115
+ expect(fired.map(f => f.name)).toEqual(['a', 'b', 'c']);
116
+ });
117
+ });
118
+ describe('evaluateInjections', () => {
119
+ const ctx = {
120
+ context: { mood: 'stressed' },
121
+ conversationId: 'c1',
122
+ turnIndex: 1,
123
+ lastUserMessage: 'help',
124
+ };
125
+ it('fires injections whose when returns true', async () => {
126
+ const injections = [
127
+ {
128
+ name: 'stress',
129
+ when: c => c.context.mood === 'stressed',
130
+ content: 'be gentle',
131
+ },
132
+ { name: 'happy', when: () => false, content: 'celebrate' },
133
+ ];
134
+ const fired = await evaluateInjections(injections, ctx, []);
135
+ expect(fired.map(f => f.name)).toEqual(['stress']);
136
+ });
137
+ it('skips once-fired injections by default', async () => {
138
+ const injections = [
139
+ { name: 'stress', when: () => true, content: 'x' },
140
+ ];
141
+ const fired = await evaluateInjections(injections, ctx, ['stress']);
142
+ expect(fired).toEqual([]);
143
+ });
144
+ it('re-fires once:false injections even when listed as fired', async () => {
145
+ const injections = [
146
+ { name: 'recurring', once: false, when: () => true, content: 'x' },
147
+ ];
148
+ const fired = await evaluateInjections(injections, ctx, ['recurring']);
149
+ expect(fired.map(f => f.name)).toEqual(['recurring']);
150
+ });
151
+ });
152
+ describe('Tuplet integration', () => {
153
+ it('appends fired PromptSection content to the system prompt on turn 1', async () => {
154
+ const { llm, calls } = recordingLLM();
155
+ const agent = new Tuplet({
156
+ role: 'tester',
157
+ tools: [],
158
+ agents: [],
159
+ llm,
160
+ sections: [
161
+ {
162
+ name: 'admin',
163
+ when: ctx => ctx.context.admin,
164
+ content: 'ADMIN_GUIDE_TEXT',
165
+ },
166
+ { name: 'guest', when: () => false, content: 'GUEST_GUIDE' },
167
+ ],
168
+ });
169
+ await agent.run('hello', { context: { admin: true } });
170
+ expect(calls[0].system).toContain('ADMIN_GUIDE_TEXT');
171
+ expect(calls[0].system).toContain('kind="prompt-section" name="admin"');
172
+ expect(calls[0].system).not.toContain('GUEST_GUIDE');
173
+ });
174
+ it('caches section evaluation across runs (no re-evaluation on turn 2)', async () => {
175
+ const { llm } = recordingLLM();
176
+ const repo = new MemoryRepository();
177
+ let evalCount = 0;
178
+ const agent = new Tuplet({
179
+ role: 'tester',
180
+ tools: [],
181
+ agents: [],
182
+ llm,
183
+ repository: repo,
184
+ sections: [
185
+ {
186
+ name: 'p',
187
+ when: () => {
188
+ evalCount++;
189
+ return true;
190
+ },
191
+ content: 'P',
192
+ },
193
+ ],
194
+ });
195
+ await agent.run('one', { conversationId: 'c1' });
196
+ await agent.run('two', { conversationId: 'c1' });
197
+ expect(evalCount).toBe(1);
198
+ });
199
+ it('inserts HistoryInjection wrapped in <tuplet-note> before the current user msg', async () => {
200
+ const { llm, calls } = recordingLLM();
201
+ const agent = new Tuplet({
202
+ role: 'tester',
203
+ tools: [],
204
+ agents: [],
205
+ llm,
206
+ historyInjections: [
207
+ { name: 'stress', when: () => true, content: 'be gentle' },
208
+ ],
209
+ });
210
+ await agent.run('I am tired', {});
211
+ const msgs = calls[0].messages;
212
+ // Expected: injection-user, ack-asst, real-user
213
+ expect(msgs).toHaveLength(3);
214
+ expect(msgs[0].role).toBe('user');
215
+ expect(typeof msgs[0].content === 'string' ? msgs[0].content : '')
216
+ .toContain('<tuplet-note kind="history-injection" name="stress">');
217
+ expect(typeof msgs[0].content === 'string' ? msgs[0].content : '').toContain('be gentle');
218
+ expect(msgs[1].role).toBe('assistant');
219
+ expect(msgs[2].role).toBe('user');
220
+ expect(msgs[2].content).toBe('I am tired');
221
+ });
222
+ it('HistoryInjection once:true (default) does not re-fire on the next run', async () => {
223
+ const { llm, calls } = recordingLLM();
224
+ const repo = new MemoryRepository();
225
+ let fireCount = 0;
226
+ const agent = new Tuplet({
227
+ role: 'tester',
228
+ tools: [],
229
+ agents: [],
230
+ llm,
231
+ repository: repo,
232
+ historyInjections: [
233
+ {
234
+ name: 'once-only',
235
+ when: () => {
236
+ fireCount++;
237
+ return true;
238
+ },
239
+ content: 'first-only note',
240
+ },
241
+ ],
242
+ });
243
+ await agent.run('first', { conversationId: 'c1' });
244
+ await agent.run('second', { conversationId: 'c1' });
245
+ // Once the injection has fired, subsequent runs short-circuit and do not
246
+ // re-evaluate its `when` predicate.
247
+ expect(fireCount).toBe(1);
248
+ const secondCallSystem = calls[1].messages
249
+ .map(m => (typeof m.content === 'string' ? m.content : JSON.stringify(m.content)))
250
+ .join('\n');
251
+ // Should not contain a fresh injection — but the FIRST injection is still
252
+ // in history (persisted from turn 1). Assert the second call does not have
253
+ // TWO injection blocks.
254
+ const occurrences = secondCallSystem.match(/name="once-only"/g)?.length ?? 0;
255
+ expect(occurrences).toBe(1);
256
+ });
257
+ it('HistoryInjection once:false fires every turn', async () => {
258
+ const { llm, calls } = recordingLLM();
259
+ const repo = new MemoryRepository();
260
+ const agent = new Tuplet({
261
+ role: 'tester',
262
+ tools: [],
263
+ agents: [],
264
+ llm,
265
+ repository: repo,
266
+ historyInjections: [
267
+ { name: 'heartbeat', once: false, when: () => true, content: 'ping' },
268
+ ],
269
+ });
270
+ await agent.run('one', { conversationId: 'c1' });
271
+ await agent.run('two', { conversationId: 'c1' });
272
+ // First call has 1 injection + 1 ack + 1 user = 3 messages
273
+ expect(calls[0].messages).toHaveLength(3);
274
+ // Second call history: [injection1, ack, user1, asst, injection2, ack, user2]
275
+ const second = calls[1].messages;
276
+ const userTextMsgs = second.filter(m => m.role === 'user' && typeof m.content === 'string' && !m.content.includes('<tuplet-note'));
277
+ expect(userTextMsgs.map(m => m.content)).toEqual(['one', 'two']);
278
+ const injectionMsgs = second.filter(m => m.role === 'user' && typeof m.content === 'string' && m.content.includes('<tuplet-note kind="history-injection"'));
279
+ expect(injectionMsgs).toHaveLength(2);
280
+ });
281
+ it('persists firedInjections across runs in repository state', async () => {
282
+ const { llm } = recordingLLM();
283
+ const repo = new MemoryRepository();
284
+ const agent = new Tuplet({
285
+ role: 'tester',
286
+ tools: [],
287
+ agents: [],
288
+ llm,
289
+ repository: repo,
290
+ historyInjections: [{ name: 'fired-once', when: () => true, content: 'x' }],
291
+ });
292
+ await agent.run('hi', { conversationId: 'c1' });
293
+ const state = await loadTupletState(repo, 'c1');
294
+ expect(state.firedInjections).toEqual(['fired-once']);
295
+ });
296
+ it('merges multiple simultaneous injections into one user message', async () => {
297
+ const { llm, calls } = recordingLLM();
298
+ const agent = new Tuplet({
299
+ role: 'tester',
300
+ tools: [],
301
+ agents: [],
302
+ llm,
303
+ historyInjections: [
304
+ { name: 'a', when: () => true, content: 'A' },
305
+ { name: 'b', when: () => true, content: 'B' },
306
+ ],
307
+ });
308
+ await agent.run('hello', {});
309
+ const msgs = calls[0].messages;
310
+ expect(msgs).toHaveLength(3);
311
+ const payload = typeof msgs[0].content === 'string' ? msgs[0].content : '';
312
+ expect(payload).toContain('name="a"');
313
+ expect(payload).toContain('name="b"');
314
+ });
315
+ it('does not evaluate sections or injections when resuming from __ask_user__', async () => {
316
+ const { llm } = recordingLLM();
317
+ let sectionCalls = 0;
318
+ let injectionCalls = 0;
319
+ const agent = new Tuplet({
320
+ role: 'tester',
321
+ tools: [],
322
+ agents: [],
323
+ llm,
324
+ sections: [
325
+ {
326
+ name: 's',
327
+ when: () => {
328
+ sectionCalls++;
329
+ return true;
330
+ },
331
+ content: 'S',
332
+ },
333
+ ],
334
+ historyInjections: [
335
+ {
336
+ name: 'i',
337
+ when: () => {
338
+ injectionCalls++;
339
+ return true;
340
+ },
341
+ content: 'I',
342
+ },
343
+ ],
344
+ });
345
+ // Simulate a resume: history ends with assistant __ask_user__ tool_use
346
+ const history = [
347
+ { role: 'user', content: 'start' },
348
+ {
349
+ role: 'assistant',
350
+ content: [
351
+ {
352
+ type: 'tool_use',
353
+ id: 'ask1',
354
+ name: '__ask_user__',
355
+ input: { questions: [{ question: 'Which?' }] },
356
+ },
357
+ ],
358
+ },
359
+ ];
360
+ await agent.run('my answer', { history });
361
+ // Section still evaluates (turn 1 was never completed, and sections are
362
+ // cached for the session). Injection must NOT fire on resume.
363
+ expect(injectionCalls).toBe(0);
364
+ // Sections evaluate once regardless of resume — resume is mid-turn.
365
+ expect(sectionCalls).toBe(1);
366
+ });
367
+ it('Tuplet.resolveSections returns fired sections without repository state', async () => {
368
+ const agent = new Tuplet({
369
+ role: 'tester',
370
+ tools: [],
371
+ agents: [],
372
+ llm: recordingLLM().llm,
373
+ sections: [
374
+ { name: 'yes', when: c => c.context.x, content: 'YES' },
375
+ { name: 'no', when: c => !c.context.x, content: 'NO' },
376
+ ],
377
+ });
378
+ const fired = await agent.resolveSections({ context: { x: true }, conversationId: 'c' });
379
+ expect(fired.map(f => f.name)).toEqual(['yes']);
380
+ });
381
+ it('Tuplet.evaluateInjections respects firedNames', async () => {
382
+ const agent = new Tuplet({
383
+ role: 'tester',
384
+ tools: [],
385
+ agents: [],
386
+ llm: recordingLLM().llm,
387
+ historyInjections: [
388
+ { name: 'a', when: () => true, content: 'A' },
389
+ { name: 'b', when: () => true, content: 'B' },
390
+ ],
391
+ });
392
+ const fired = await agent.evaluateInjections({ context: {}, conversationId: 'c', turnIndex: 1, lastUserMessage: '' }, ['a']);
393
+ expect(fired.map(f => f.name)).toEqual(['b']);
394
+ });
395
+ it('invalidateSection clears a section from cache so it re-evaluates', async () => {
396
+ const { llm } = recordingLLM();
397
+ const repo = new MemoryRepository();
398
+ let evalCount = 0;
399
+ const agent = new Tuplet({
400
+ role: 'tester',
401
+ tools: [],
402
+ agents: [],
403
+ llm,
404
+ repository: repo,
405
+ sections: [
406
+ {
407
+ name: 'dyn',
408
+ when: () => {
409
+ evalCount++;
410
+ return true;
411
+ },
412
+ content: () => `v${evalCount}`,
413
+ },
414
+ ],
415
+ });
416
+ await agent.run('one', { conversationId: 'c1' });
417
+ expect(evalCount).toBe(1);
418
+ // Without invalidation, a second run would use cache.
419
+ await agent.run('two', { conversationId: 'c1' });
420
+ expect(evalCount).toBe(1);
421
+ await agent.invalidateSection('c1', 'dyn');
422
+ await agent.run('three', { conversationId: 'c1' });
423
+ expect(evalCount).toBe(2);
424
+ });
425
+ it('TurnContext exposes turnIndex and lastUserMessage', async () => {
426
+ const { llm } = recordingLLM();
427
+ const repo = new MemoryRepository();
428
+ const seen = [];
429
+ const agent = new Tuplet({
430
+ role: 'tester',
431
+ tools: [],
432
+ agents: [],
433
+ llm,
434
+ repository: repo,
435
+ historyInjections: [
436
+ {
437
+ name: 'watch',
438
+ once: false,
439
+ when: ctx => {
440
+ seen.push({ turnIndex: ctx.turnIndex, lastUserMessage: ctx.lastUserMessage });
441
+ return false;
442
+ },
443
+ content: '',
444
+ },
445
+ ],
446
+ });
447
+ await agent.run('first message', { conversationId: 'c1' });
448
+ await agent.run('second message', { conversationId: 'c1' });
449
+ expect(seen).toEqual([
450
+ { turnIndex: 1, lastUserMessage: 'first message' },
451
+ { turnIndex: 2, lastUserMessage: 'second message' },
452
+ ]);
453
+ });
454
+ });
455
+ //# sourceMappingURL=prompt-sections.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-sections.test.js","sourceRoot":"","sources":["../src/prompt-sections.test.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,19 @@ export interface OpenRouterProviderConfig {
15
15
  referer?: string;
16
16
  title?: string;
17
17
  cache?: boolean;
18
+ /**
19
+ * Max retries when the model returns a fuzzy response (empty, or leaked
20
+ * chat-template markers, etc.) — treated as a stochastic glitch. Default: 2.
21
+ * Set to 0 to disable.
22
+ */
23
+ maxFuzzyRetries?: number;
18
24
  }
25
+ /**
26
+ * A response is "fuzzy" if it has no tool calls AND the text is either empty
27
+ * or contains leaked template markers. From the caller's perspective this is
28
+ * just a broken response that should be retried.
29
+ */
30
+ export declare function isFuzzyResponse(content: string | null | undefined, hasToolCalls: boolean): boolean;
19
31
  export declare class OpenRouterProvider implements LLMProvider {
20
32
  private apiKey;
21
33
  private model;
@@ -24,9 +36,11 @@ export declare class OpenRouterProvider implements LLMProvider {
24
36
  private referer?;
25
37
  private title?;
26
38
  private cache;
39
+ private maxFuzzyRetries;
27
40
  constructor(config?: OpenRouterProviderConfig);
28
41
  get supportsNativeTools(): boolean;
29
42
  chat(systemPrompt: string, messages: Message[], tools: ToolSchema[], options?: LLMOptions): Promise<LLMResponse>;
43
+ private requestOnce;
30
44
  private convertMessages;
31
45
  private findLastUserMessageIndex;
32
46
  private convertContentBlocksToOpenAI;
@@ -1 +1 @@
1
- {"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../../../src/providers/llm/openrouter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,OAAO,EACP,UAAU,EAIX,MAAM,gBAAgB,CAAA;AAGvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExD,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAoED,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,GAAE,wBAA6B;IAcjD,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAEK,IAAI,CACR,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,WAAW,CAAC;IAoDvB,OAAO,CAAC,eAAe;IA2DvB,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,4BAA4B;IAkEpC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,eAAe;IA8DvB,UAAU,IAAI,MAAM;IAIpB;;;;;;OAMG;WACU,iBAAiB,CAC5B,OAAO,SAAiC,GACvC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAkCzC"}
1
+ {"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../../../src/providers/llm/openrouter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,OAAO,EACP,UAAU,EAIX,MAAM,gBAAgB,CAAA;AAGvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExD,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAcD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAIlG;AAoED,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,eAAe,CAAQ;gBAEnB,MAAM,GAAE,wBAA6B;IAejD,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAEK,IAAI,CACR,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,WAAW,CAAC;YAuET,WAAW;IA6BzB,OAAO,CAAC,eAAe;IA2DvB,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,4BAA4B;IAkEpC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,eAAe;IA8DvB,UAAU,IAAI,MAAM;IAIpB;;;;;;OAMG;WACU,iBAAiB,CAC5B,OAAO,SAAiC,GACvC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAkCzC"}
@@ -6,6 +6,29 @@
6
6
  * an OpenAI-compatible API gateway.
7
7
  */
8
8
  import { defaultSanitize } from '../../sanitize.js';
9
+ /**
10
+ * Patterns that indicate the model emitted chat-template artefacts as plain
11
+ * text instead of a clean response (observed mostly on Gemma-family models).
12
+ */
13
+ const FUZZY_CONTENT_PATTERNS = [
14
+ /<\|?tool_call\|?>/i,
15
+ /<\|"\|>/,
16
+ /^\s*call\s*:\s*\w+\s*\{/,
17
+ /<function_call\b/i,
18
+ /<tool_use\b/i,
19
+ ];
20
+ /**
21
+ * A response is "fuzzy" if it has no tool calls AND the text is either empty
22
+ * or contains leaked template markers. From the caller's perspective this is
23
+ * just a broken response that should be retried.
24
+ */
25
+ export function isFuzzyResponse(content, hasToolCalls) {
26
+ if (hasToolCalls)
27
+ return false;
28
+ if (!content || !content.trim())
29
+ return true;
30
+ return FUZZY_CONTENT_PATTERNS.some(re => re.test(content));
31
+ }
9
32
  /**
10
33
  * Model prefixes that reliably handle tool descriptions from the API tools parameter,
11
34
  * making system prompt tool listings redundant. Other models may support tool calling
@@ -26,6 +49,7 @@ export class OpenRouterProvider {
26
49
  referer;
27
50
  title;
28
51
  cache;
52
+ maxFuzzyRetries;
29
53
  constructor(config = {}) {
30
54
  this.apiKey = config.apiKey || process.env.OPENROUTER_API_KEY || '';
31
55
  this.model = config.model || 'anthropic/claude-sonnet-4';
@@ -34,6 +58,7 @@ export class OpenRouterProvider {
34
58
  this.referer = config.referer;
35
59
  this.title = config.title;
36
60
  this.cache = config.cache !== false;
61
+ this.maxFuzzyRetries = config.maxFuzzyRetries ?? 2;
37
62
  if (!this.apiKey) {
38
63
  throw new Error('OpenRouter API key is required');
39
64
  }
@@ -62,6 +87,44 @@ export class OpenRouterProvider {
62
87
  if (this.title) {
63
88
  headers['X-Title'] = this.title;
64
89
  }
90
+ // Retry on fuzzy responses (empty content, leaked chat-template markers,
91
+ // etc.) — stochastic model glitches that a plain retry usually fixes.
92
+ // Tokens from failed attempts are summed into the returned usage so cost
93
+ // accounting stays correct; only the final response is returned, so the
94
+ // fuzzy text can never leak into chat history.
95
+ const maxAttempts = 1 + Math.max(0, this.maxFuzzyRetries);
96
+ let lastData;
97
+ let totalInput = 0;
98
+ let totalOutput = 0;
99
+ let totalCacheRead = 0;
100
+ let totalCacheWrite = 0;
101
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
102
+ const data = await this.requestOnce(requestBody, headers);
103
+ lastData = data;
104
+ const details = data.usage.prompt_tokens_details;
105
+ const cachedRead = details?.cached_tokens || 0;
106
+ const cacheWrite = details?.cache_write_tokens || 0;
107
+ totalInput += data.usage.prompt_tokens - cachedRead - cacheWrite;
108
+ totalOutput += data.usage.completion_tokens;
109
+ totalCacheRead += cachedRead;
110
+ totalCacheWrite += cacheWrite;
111
+ const choice = data.choices[0];
112
+ const hasToolCalls = (choice.message.tool_calls ?? []).length > 0;
113
+ if (!isFuzzyResponse(choice.message.content, hasToolCalls)) {
114
+ break;
115
+ }
116
+ }
117
+ const converted = this.convertResponse(lastData);
118
+ const cacheUsage = totalCacheRead || totalCacheWrite
119
+ ? { cacheReadInputTokens: totalCacheRead, cacheCreationInputTokens: totalCacheWrite }
120
+ : undefined;
121
+ return {
122
+ ...converted,
123
+ usage: { inputTokens: totalInput, outputTokens: totalOutput },
124
+ cacheUsage
125
+ };
126
+ }
127
+ async requestOnce(requestBody, headers) {
65
128
  const response = await fetch(`${this.baseURL}/chat/completions`, {
66
129
  method: 'POST',
67
130
  headers,
@@ -79,7 +142,7 @@ export class OpenRouterProvider {
79
142
  if (!data.choices?.[0]?.message) {
80
143
  throw new Error(`OpenRouter API error: empty response (no choices returned)`);
81
144
  }
82
- return this.convertResponse(data);
145
+ return data;
83
146
  }
84
147
  convertMessages(systemPrompt, messages, useCache) {
85
148
  // System message: use cache_control array format for Anthropic models