@aexol/spectral 0.9.231 → 0.9.234

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 (106) hide show
  1. package/dist/agent/index.d.ts.map +1 -1
  2. package/dist/agent/index.js +89 -14
  3. package/dist/backend/generated-images.d.ts +1 -1
  4. package/dist/backend/generated-images.d.ts.map +1 -1
  5. package/dist/commands/serve.d.ts.map +1 -1
  6. package/dist/commands/serve.js +12 -1
  7. package/dist/extensions/logic-trace.d.ts +1 -1
  8. package/dist/extensions/logic-trace.d.ts.map +1 -1
  9. package/dist/extensions/logic-trace.js +199 -70
  10. package/dist/extensions/video-generation/index.d.ts +3 -0
  11. package/dist/extensions/video-generation/index.d.ts.map +1 -0
  12. package/dist/extensions/video-generation/index.js +299 -0
  13. package/dist/generated/zeus/const.d.ts.map +1 -1
  14. package/dist/generated/zeus/const.js +30 -0
  15. package/dist/generated/zeus/index.d.ts +140 -0
  16. package/dist/generated/zeus/index.d.ts.map +1 -1
  17. package/dist/generated/zeus/index.js +5 -0
  18. package/dist/relay/dispatcher.d.ts +13 -1
  19. package/dist/relay/dispatcher.d.ts.map +1 -1
  20. package/dist/relay/dispatcher.js +11 -0
  21. package/dist/sdk/agent-core/agent-loop.js +13 -3
  22. package/dist/sdk/agent-core/agent.d.ts +11 -4
  23. package/dist/sdk/agent-core/agent.d.ts.map +1 -1
  24. package/dist/sdk/agent-core/agent.js +16 -4
  25. package/dist/sdk/agent-core/tool-call-registry.d.ts +75 -0
  26. package/dist/sdk/agent-core/tool-call-registry.d.ts.map +1 -0
  27. package/dist/sdk/agent-core/tool-call-registry.js +131 -0
  28. package/dist/sdk/agent-core/tool-execution.d.ts.map +1 -1
  29. package/dist/sdk/agent-core/tool-execution.js +103 -4
  30. package/dist/sdk/agent-core/types.d.ts +15 -4
  31. package/dist/sdk/agent-core/types.d.ts.map +1 -1
  32. package/dist/sdk/ai/index.d.ts +3 -0
  33. package/dist/sdk/ai/index.d.ts.map +1 -1
  34. package/dist/sdk/ai/index.js +3 -0
  35. package/dist/sdk/ai/providers/faux.d.ts.map +1 -1
  36. package/dist/sdk/ai/providers/faux.js +3 -0
  37. package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts +39 -6
  38. package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts.map +1 -1
  39. package/dist/sdk/ai/providers/leaked-tool-call-parser.js +159 -18
  40. package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
  41. package/dist/sdk/ai/providers/openai-completions.js +22 -8
  42. package/dist/sdk/ai/providers/openrouter-videos.d.ts +3 -0
  43. package/dist/sdk/ai/providers/openrouter-videos.d.ts.map +1 -0
  44. package/dist/sdk/ai/providers/openrouter-videos.js +412 -0
  45. package/dist/sdk/ai/providers/register-builtins.d.ts.map +1 -1
  46. package/dist/sdk/ai/providers/register-builtins.js +6 -0
  47. package/dist/sdk/ai/providers/simple-options.d.ts +1 -6
  48. package/dist/sdk/ai/providers/simple-options.d.ts.map +1 -1
  49. package/dist/sdk/ai/providers/simple-options.js +0 -22
  50. package/dist/sdk/ai/providers/transform-messages.d.ts.map +1 -1
  51. package/dist/sdk/ai/providers/transform-messages.js +52 -1
  52. package/dist/sdk/ai/types.d.ts +91 -10
  53. package/dist/sdk/ai/types.d.ts.map +1 -1
  54. package/dist/sdk/ai/video-models.d.ts +10 -0
  55. package/dist/sdk/ai/video-models.d.ts.map +1 -0
  56. package/dist/sdk/ai/video-models.generated.d.ts +440 -0
  57. package/dist/sdk/ai/video-models.generated.d.ts.map +1 -0
  58. package/dist/sdk/ai/video-models.generated.js +441 -0
  59. package/dist/sdk/ai/video-models.js +22 -0
  60. package/dist/sdk/ai/videos-api-registry.d.ts +14 -0
  61. package/dist/sdk/ai/videos-api-registry.d.ts.map +1 -0
  62. package/dist/sdk/ai/videos-api-registry.js +21 -0
  63. package/dist/sdk/ai/videos.d.ts +4 -0
  64. package/dist/sdk/ai/videos.d.ts.map +1 -0
  65. package/dist/sdk/ai/videos.js +13 -0
  66. package/dist/sdk/coding-agent/core/agent-session.d.ts +8 -0
  67. package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
  68. package/dist/sdk/coding-agent/core/agent-session.js +11 -0
  69. package/dist/sdk/coding-agent/core/extensions/loader.d.ts.map +1 -1
  70. package/dist/sdk/coding-agent/core/extensions/loader.js +14 -0
  71. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts +26 -0
  72. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
  73. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +23 -0
  74. package/dist/sdk/coding-agent/core/extensions/session-events.d.ts +2 -2
  75. package/dist/sdk/coding-agent/core/extensions/session-events.d.ts.map +1 -1
  76. package/dist/sdk/coding-agent/core/extensions/types.d.ts +4 -2
  77. package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
  78. package/dist/sdk/coding-agent/core/messages.d.ts.map +1 -1
  79. package/dist/sdk/coding-agent/core/messages.js +42 -2
  80. package/dist/sdk/coding-agent/core/resource-loader.d.ts +11 -0
  81. package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
  82. package/dist/sdk/coding-agent/core/resource-loader.js +23 -6
  83. package/dist/sdk/coding-agent/core/sdk.d.ts +24 -2
  84. package/dist/sdk/coding-agent/core/sdk.d.ts.map +1 -1
  85. package/dist/sdk/coding-agent/core/sdk.js +15 -3
  86. package/dist/sdk/coding-agent/core/settings-manager.d.ts +0 -8
  87. package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
  88. package/dist/sdk/coding-agent/core/settings-manager.js +3 -3
  89. package/dist/sdk/coding-agent/index.d.ts +2 -2
  90. package/dist/sdk/coding-agent/index.d.ts.map +1 -1
  91. package/dist/sdk/coding-agent/index.js +1 -1
  92. package/dist/sdk-public.d.ts +96 -0
  93. package/dist/sdk-public.d.ts.map +1 -0
  94. package/dist/sdk-public.js +109 -0
  95. package/dist/server/agent-bridge/rehydrate.d.ts +2 -2
  96. package/dist/server/agent-bridge/rehydrate.d.ts.map +1 -1
  97. package/dist/server/agent-bridge/rehydrate.js +14 -0
  98. package/dist/server/agent-bridge.d.ts +8 -2
  99. package/dist/server/agent-bridge.d.ts.map +1 -1
  100. package/dist/server/agent-bridge.js +25 -2
  101. package/dist/server/session-stream.d.ts +19 -0
  102. package/dist/server/session-stream.d.ts.map +1 -1
  103. package/dist/server/session-stream.js +35 -0
  104. package/dist/server/wire.d.ts +13 -0
  105. package/dist/server/wire.d.ts.map +1 -1
  106. package/package.json +7 -1
@@ -15,6 +15,12 @@
15
15
  * where the wrapper tag is `function_calls` (optionally with an `antml:`
16
16
  * namespace prefix) and invoke/parameter tags follow the same convention.
17
17
  *
18
+ * Models also emit the same markup WITHOUT the wrapper — orphan
19
+ * `<invoke name="x">…</invoke>` blocks straight in prose (pi-style) — or
20
+ * truncated blocks that never receive their closing tags before the stream
21
+ * ends. Both shapes are recovered: orphan blocks during streaming, truncated
22
+ * parameter-complete tails at end of stream (see salvageLeakedToolCallTail).
23
+ *
18
24
  * Everything here is pure string logic — no provider imports — so the tests
19
25
  * cover it without any streaming harness.
20
26
  */
@@ -22,16 +28,52 @@
22
28
  const NS = "(?:antml:)?";
23
29
  const INVOKE_RE = new RegExp(`<${NS}invoke\\s+name="([^"]*)"\\s*>([\\s\\S]*?)</${NS}invoke\\s*>`, "g");
24
30
  const PARAMETER_RE = new RegExp(`<${NS}parameter\\s+name="([^"]*)"\\s*>([\\s\\S]*?)</${NS}parameter\\s*>`, "g");
31
+ const INVOKE_OPEN_RE = new RegExp(`<${NS}invoke\\s+name="([^"]*)"\\s*>`, "g");
32
+ const INVOKE_CLOSE_RE = new RegExp(`</${NS}invoke\\s*>`, "g");
33
+ const PARAMETER_OPEN_RE = new RegExp(`<${NS}parameter\\s+name="[^"]*"\\s*>`, "g");
25
34
  const WRAPPER_OPEN_RE = /<(?:antml:)?function_calls\s*>/;
26
35
  const WRAPPER_CLOSE = /<\/(?:antml:)?function_calls\s*>/;
27
- /** Longest opener tag is the antml-prefixed one (22 chars). Keep a margin. */
36
+ /**
37
+ * Floor for how much tail a caller should expect a partial opener to occupy
38
+ * (the longest opener tag is the antml-prefixed one, 22 chars, plus margin).
39
+ * Partial detection anchors at the last `<` of the buffered text, so an
40
+ * invoke opener whose name attribute is still open can hold back more than
41
+ * this when the tool name is long — it is a floor, not a cap.
42
+ */
28
43
  export const LEAK_TAIL_LENGTH = 32;
29
44
  const OPENER_PREFIXES = ["<function_calls", "<antml:function_calls"];
45
+ const INVOKE_OPENER_PREFIXES = ['<invoke name="', '<antml:invoke name="'];
46
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47
+ /**
48
+ * A buffered tail that could still grow into a leaked opener, matched
49
+ * (anchored) against everything from the last `<` of the buffered text:
50
+ * - any prefix of a wrapper/invoke opener literal, from a bare `<` up to the
51
+ * full declared prefix (`<function_ca`, `<invoke name="`),
52
+ * - an invoke opener whose double-quoted name attribute is still open
53
+ * (`<invoke name="bash` — the closing `">` has not arrived), however long
54
+ * the name is.
55
+ * Single-quoted names and attribute-less `<invoke>` tags never match, so
56
+ * ordinary prose (`a < b`, `Array<string>`) stays out of the buffer.
57
+ */
58
+ const PARTIAL_TAIL_RE = new RegExp(`^(?:${[
59
+ ...[...OPENER_PREFIXES, ...INVOKE_OPENER_PREFIXES].flatMap((opener) => {
60
+ const prefixes = [];
61
+ for (let size = 1; size <= opener.length; size++) {
62
+ prefixes.push(escapeRegExp(opener.slice(0, size)));
63
+ }
64
+ return prefixes;
65
+ }),
66
+ '<(?:antml:)?invoke\\s+name="[^">]*',
67
+ ].join("|")})$`);
30
68
  /**
31
- * Locate a leaked wrapper-opener tag in `text`.
69
+ * Locate a leaked opener tag in `text` — either the wrapper
70
+ * (`<function_calls>`) or an orphan invoke (`<invoke name="x">`, pi-style
71
+ * markup emitted without the wrapper).
32
72
  * - "full": a complete opener tag exists; `index` points at its `<`.
33
- * - "partial": the text ENDS with a strict prefix of an opener tag (an opener
34
- * split across streaming deltas); `index` points at the start of that tail.
73
+ * - "partial": the text ENDS mid-opener (an opener split across streaming
74
+ * deltas); `index` points at the start of that tail. A partial always
75
+ * starts at the last `<` — the tail matcher admits no further `<` — so the
76
+ * scan is a single anchored match there instead of a prefix enumeration.
35
77
  * - "none": no opener, complete or partial.
36
78
  */
37
79
  export function findLeakedToolCallOpener(text) {
@@ -46,19 +88,16 @@ export function findLeakedToolCallOpener(text) {
46
88
  best = { match: "full", index: m.index };
47
89
  }
48
90
  }
91
+ for (const m of text.matchAll(INVOKE_OPEN_RE)) {
92
+ if (best.match !== "full" || m.index < best.index) {
93
+ best = { match: "full", index: m.index };
94
+ }
95
+ }
49
96
  if (best.match === "full")
50
97
  return best;
51
- for (const opener of OPENER_PREFIXES) {
52
- const maxLen = Math.min(opener.length - 1, text.length);
53
- for (let size = maxLen; size > 0; size--) {
54
- if (text.endsWith(opener.slice(0, size))) {
55
- const index = text.length - size;
56
- if (best.match === "none" || index < best.index) {
57
- best = { match: "partial", index };
58
- }
59
- break;
60
- }
61
- }
98
+ const lastLt = text.lastIndexOf("<");
99
+ if (lastLt >= 0 && PARTIAL_TAIL_RE.test(text.slice(lastLt))) {
100
+ best = { match: "partial", index: lastLt };
62
101
  }
63
102
  return best;
64
103
  }
@@ -71,13 +110,17 @@ export function findLeakedToolCallOpener(text) {
71
110
  * before + after). `consumed` is the raw block text (opener→closer
72
111
  * inclusive) so callers can fall back to emitting it as text when the
73
112
  * parsed tool names turn out to be unknown.
74
- * - `complete: false` → no closed wrapper block yet; `calls` is empty and
75
- * `remaining`/`consumed` echo the input so nothing is cut prematurely.
113
+ * When no wrapper opener exists at all, orphan invoke blocks (pi-style, no
114
+ * wrapper) are parsed instead — but only when EVERY invoke opener has a
115
+ * matching closer; otherwise `complete: false` and `remaining`/`consumed`
116
+ * echo the input so nothing is cut prematurely. (A wrapper opener blocks the
117
+ * orphan path: wrapper-less invokes around a wrapper block are picked up
118
+ * later from `remaining`, once the wrapper block itself is consumed.)
76
119
  */
77
120
  export function extractLeakedToolCalls(text) {
78
121
  const openMatch = WRAPPER_OPEN_RE.exec(text);
79
122
  if (!openMatch) {
80
- return { calls: [], complete: false, remaining: text, consumed: "" };
123
+ return extractOrphanInvokes(text);
81
124
  }
82
125
  const openStart = openMatch.index;
83
126
  const bodyStart = openStart + openMatch[0].length;
@@ -112,6 +155,104 @@ export function extractLeakedToolCalls(text) {
112
155
  remaining: text.slice(0, openStart) + text.slice(closeEnd + 1),
113
156
  };
114
157
  }
158
+ /**
159
+ * Orphan invoke blocks: pi-style markup without any wrapper tag. Complete
160
+ * only when EVERY invoke opener has a matching closer; otherwise nothing is
161
+ * cut and the caller keeps buffering. `consumed` spans the first opener
162
+ * through the last closer (prose between invokes inside the parsed region is
163
+ * dropped, mirroring the wrapper path) and `remaining` echoes the prose
164
+ * before and after.
165
+ */
166
+ function extractOrphanInvokes(text) {
167
+ const opens = [...text.matchAll(INVOKE_OPEN_RE)];
168
+ if (opens.length === 0) {
169
+ return { calls: [], complete: false, remaining: text, consumed: "" };
170
+ }
171
+ const closes = [...text.matchAll(INVOKE_CLOSE_RE)];
172
+ let closerPtr = 0;
173
+ const calls = [];
174
+ let firstOpen = -1;
175
+ let lastCloseEnd = -1;
176
+ for (const open of opens) {
177
+ const openStart = open.index;
178
+ if (firstOpen === -1)
179
+ firstOpen = openStart;
180
+ while (closerPtr < closes.length && closes[closerPtr].index < openStart) {
181
+ closerPtr++;
182
+ }
183
+ const closer = closes[closerPtr];
184
+ if (!closer) {
185
+ return { calls: [], complete: false, remaining: text, consumed: "" };
186
+ }
187
+ const body = text.slice(openStart + open[0].length, closer.index);
188
+ const args = {};
189
+ for (const p of body.matchAll(PARAMETER_RE)) {
190
+ args[p[1]] = p[2].trim();
191
+ }
192
+ calls.push({ name: open[1], args });
193
+ closerPtr++;
194
+ lastCloseEnd = closer.index + closer[0].length;
195
+ }
196
+ return {
197
+ calls,
198
+ complete: true,
199
+ consumed: text.slice(firstOpen, lastCloseEnd),
200
+ remaining: text.slice(0, firstOpen) + text.slice(lastCloseEnd),
201
+ };
202
+ }
203
+ /**
204
+ * End-of-stream recovery for a leaked buffer that never received its closing
205
+ * tags (stream cut off, or a model that forgets `</invoke>` / the wrapper
206
+ * closer). Parses invoke blocks tolerantly: a missing closer is fine as long
207
+ * as every `<parameter>` value is fully closed. A truncated parameter value
208
+ * means the model was cut mid-argument — guessing at the value is unsafe, so
209
+ * the whole tail degrades to plain text (null).
210
+ *
211
+ * Returns the parsed calls (leading wrapper skeleton dropped), or null when
212
+ * nothing salvageable remains. Callers still validate tool names before
213
+ * emitting synthetic tool calls.
214
+ */
215
+ export function salvageLeakedToolCallTail(text) {
216
+ const opens = [...text.matchAll(INVOKE_OPEN_RE)];
217
+ if (opens.length === 0)
218
+ return null;
219
+ const closes = [...text.matchAll(INVOKE_CLOSE_RE)];
220
+ const calls = [];
221
+ let closerPtr = 0;
222
+ for (let i = 0; i < opens.length; i++) {
223
+ const open = opens[i];
224
+ const openStart = open.index;
225
+ while (closerPtr < closes.length && closes[closerPtr].index < openStart) {
226
+ closerPtr++;
227
+ }
228
+ const closer = closes[closerPtr];
229
+ const nextOpen = opens[i + 1];
230
+ let bodyEnd;
231
+ if (closer && (!nextOpen || closer.index < nextOpen.index)) {
232
+ bodyEnd = closer.index;
233
+ closerPtr++;
234
+ }
235
+ else if (nextOpen) {
236
+ // Unclosed invoke whose body runs up to the next opener.
237
+ bodyEnd = nextOpen.index;
238
+ }
239
+ else {
240
+ bodyEnd = text.length;
241
+ }
242
+ const body = text.slice(openStart + open[0].length, bodyEnd);
243
+ const args = {};
244
+ for (const p of body.matchAll(PARAMETER_RE)) {
245
+ args[p[1]] = p[2].trim();
246
+ }
247
+ const paramOpeners = [...body.matchAll(PARAMETER_OPEN_RE)];
248
+ if (paramOpeners.length > 0 &&
249
+ paramOpeners.length !== [...body.matchAll(PARAMETER_RE)].length) {
250
+ return null;
251
+ }
252
+ calls.push({ name: open[1], args });
253
+ }
254
+ return calls;
255
+ }
115
256
  /**
116
257
  * Coerce a leaked parameter string into a JSON value when it clearly parses
117
258
  * as one — native `tool_calls` arguments are real JSON, so a leaked
@@ -1 +1 @@
1
- {"version":3,"file":"openai-completions.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/openai-completions.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAMX,0BAA0B,EAE1B,MAAM,sCAAsC,CAAC;AAI9C,OAAO,KAAK,EAGX,OAAO,EAGP,KAAK,EACL,uBAAuB,EACvB,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AA+FrB,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC9D,UAAU,CAAC,EACR,MAAM,GACN,MAAM,GACN,UAAU,GACV;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACpD,eAAe,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;CAClE;AAED,KAAK,+BAA+B,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAuBzE,eAAO,MAAM,uBAAuB,EAAE,cAAc,CACnD,oBAAoB,EACpB,wBAAwB,CAylBxB,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,cAAc,CACzD,oBAAoB,EACpB,mBAAmB,CAyBnB,CAAC;AAkNF,wBAAgB,eAAe,CAC9B,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,+BAA+B,GACrC,0BAA0B,EAAE,CA0R9B"}
1
+ {"version":3,"file":"openai-completions.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/openai-completions.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAMX,0BAA0B,EAE1B,MAAM,sCAAsC,CAAC;AAI9C,OAAO,KAAK,EAGX,OAAO,EAGP,KAAK,EACL,uBAAuB,EACvB,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AA+FrB,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC9D,UAAU,CAAC,EACR,MAAM,GACN,MAAM,GACN,UAAU,GACV;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACpD,eAAe,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;CAClE;AAED,KAAK,+BAA+B,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAuBzE,eAAO,MAAM,uBAAuB,EAAE,cAAc,CACnD,oBAAoB,EACpB,wBAAwB,CAumBxB,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,cAAc,CACzD,oBAAoB,EACpB,mBAAmB,CAyBnB,CAAC;AAkNF,wBAAgB,eAAe,CAC9B,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,+BAA+B,GACrC,0BAA0B,EAAE,CA0R9B"}
@@ -1,5 +1,5 @@
1
1
  import OpenAI from "openai";
2
- import { coerceLeakedParameterValue, extractLeakedToolCalls, findLeakedToolCallOpener, } from "./leaked-tool-call-parser.js";
2
+ import { coerceLeakedParameterValue, extractLeakedToolCalls, findLeakedToolCallOpener, salvageLeakedToolCallTail, } from "./leaked-tool-call-parser.js";
3
3
  import { backendHostedImageResolver } from "../utils/hosted-image-resolver.js";
4
4
  import { getApiUrl, readConfig } from "../../../config.js";
5
5
  import { getEnvApiKey } from "../env-api-keys.js";
@@ -179,9 +179,11 @@ export const streamOpenAICompletions = (model, context, options) => {
179
179
  let inlineThinkingPending = "";
180
180
  let inInlineThinking = false;
181
181
  // Leaked tool-call XML: some OpenRouter models emit antml-style
182
- // markup in plain content instead of native tool_calls. Text is
183
- // buffered here until a complete wrapper block arrives, then parsed
184
- // into real toolCall blocks (see leaked-tool-call-parser.ts).
182
+ // markup in plain content instead of native tool_calls — either a
183
+ // wrapper block or orphan pi-style invoke blocks with no wrapper
184
+ // at all. Text is buffered here until a complete block arrives,
185
+ // then parsed into real toolCall blocks (see
186
+ // leaked-tool-call-parser.ts).
185
187
  let leakMode = false;
186
188
  let leakBuffer = "";
187
189
  let leakPending = "";
@@ -548,11 +550,23 @@ export const streamOpenAICompletions = (model, context, options) => {
548
550
  }
549
551
  inlineThinkingPending = "";
550
552
  }
551
- // Flush leak buffers. A never-closed block is emitted as plain text
552
- // (graceful degradation) so no content is lost; a held-back tail
553
- // without an opener is just prose.
553
+ // Flush leak buffers. A never-closed block is first offered to the
554
+ // end-of-stream salvage (models that were cut off after emitting
555
+ // complete parameter values); whatever it cannot recover is emitted
556
+ // as plain text (graceful degradation) so no content is lost. A
557
+ // held-back tail without an opener is just prose.
554
558
  if (leakMode && leakBuffer) {
555
- emitTextDelta(leakBuffer);
559
+ const salvaged = salvageLeakedToolCallTail(leakBuffer);
560
+ if (salvaged &&
561
+ salvaged.length > 0 &&
562
+ salvaged.every((call) => availableToolNames.has(call.name))) {
563
+ for (const call of salvaged)
564
+ emitSyntheticToolCall(call);
565
+ emittedLeakedToolCalls = true;
566
+ }
567
+ else {
568
+ emitTextDelta(leakBuffer);
569
+ }
556
570
  leakBuffer = "";
557
571
  leakMode = false;
558
572
  }
@@ -0,0 +1,3 @@
1
+ import type { ProviderVideosOptions, VideosContext, VideosModel, VideosResult } from "../types.js";
2
+ export declare function generateOpenRouterVideos(model: VideosModel<"openrouter-videos">, context: VideosContext, options?: ProviderVideosOptions): Promise<VideosResult>;
3
+ //# sourceMappingURL=openrouter-videos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter-videos.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/openrouter-videos.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,qBAAqB,EAIrB,aAAa,EACb,WAAW,EACX,YAAY,EACZ,MAAM,aAAa,CAAC;AAgarB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,WAAW,CAAC,mBAAmB,CAAC,EACvC,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,qBAA0B,GACjC,OAAO,CAAC,YAAY,CAAC,CAkCvB"}