pi-freeflow 1.38.1 → 1.38.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.38.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Absorb tool-call argument fragments arriving on terminal finish_reason chunks into buffered prefixes to prevent argument rotation and corrupted JSON loops. Add Reflection AI's Beam Free (1M context) to the OpenCode Zen lineup, and retire deprecated LL Alpha and Muse Spark 1.3 contributor free models.
8
+
9
+ ## 1.38.2
10
+
11
+ ### Patch Changes
12
+
13
+ - Add Exo to the OpenCode free tier — a live preview model with chat, tools, reasoning, and image input that the official free lineup lists.
14
+ - Add GLM-5.3 Flash and LL Alpha to the OpenCode free tier, and retire Step 5 Preview — it is gone from the Zen lineup and demoted to paid on the Kilo lane, so those entries can no longer be selected.
15
+
3
16
  ## 1.38.1
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -16,7 +16,7 @@ Thin by design: a model list, a relay proxy, and a log. The host (`pi-ai`) handl
16
16
 
17
17
  | Feature | What it does | Cost |
18
18
  | :--- | :--- | :--- |
19
- | **36 free models** | 12 from OpenCode Zen, 14 from KiloCode Gateway, 2 from Cline, 8 from OVHcloud AI Endpoints, context windows up to 1M. Full list below. | **$0** |
19
+ | **36 free models** | 13 from OpenCode Zen, 13 from KiloCode Gateway, 2 from Cline, 8 from OVHcloud AI Endpoints, context windows up to 1M. Full list below. | **$0** |
20
20
  | **Relay pool** | Route requests through your own Cloudflare Workers, Deno Deploy, and Vercel Edge relays. Traffic spreads across the pool, or stays on one sticky relay. A relay that rate-limits, times out, or drops the connection cools down while healthy ones take its traffic. | **$0** beyond your platforms' free tiers |
21
21
  | **Automatic fallback** | When every relay is cooling down, requests go direct to upstream instead of failing. | **$0** |
22
22
  | **Short model names** | Most models have a slash-free, colon-free alias, plus an optional `:effort` suffix for thinking depth. You type `freeflow/<name>`; the two Cline models have no alias and must be typed in full. | **$0** |
@@ -204,7 +204,7 @@ The same command set works identically in OMP and Pi:
204
204
  ```
205
205
 
206
206
  Every model specification is reconciled against [models.dev](https://models.dev/), [OpenRouter](https://openrouter.ai/), and OMP's built-in catalog, configured with the highest verified context, output ceiling, and reasoning capabilities across twin models.
207
- #### OpenCode Zen (12 models), Responses and Chat API
207
+ #### OpenCode Zen (13 models), Responses and Chat API
208
208
 
209
209
  Good defaults for long coding sessions and agentic work.
210
210
 
@@ -212,7 +212,6 @@ Good defaults for long coding sessions and agentic work.
212
212
  | :--- | :--- | :--- | :--- | :--- | :--- |
213
213
  | `cat-jam-free` | Cat Jam | **256K** (262.144) | **131K** (131.072) | `low … max` | ❌ |
214
214
  | `muse-spark-1.2-contributor-free` | Meta Superintelligence Labs | **1M** (1.048.576) | **131K** (131.072) | `minimal … xhigh` | ✅ |
215
- | `muse-spark-1.3-contributor-free` | Meta Superintelligence Labs | **1M** (1.048.576) | **131K** (131.072) | `minimal … xhigh` | ✅ |
216
215
  | `mimo-v2.6-flash-free` | Xiaomi MiMo | **1M** (1.048.576) | **131K** (131.072) | `minimal … xhigh`\* | ✅ |
217
216
  | `nemotron-3.5-lightning-free` | NVIDIA | **1M** (1.000.000) | **262K** (262.144) | `minimal … xhigh` | ❌ |
218
217
  | `nemotron-3.5-super-vl-free` | NVIDIA | **1M** (1.000.000) | **131K** (131.072) | `minimal … xhigh` | ✅ |
@@ -220,10 +219,12 @@ Good defaults for long coding sessions and agentic work.
220
219
  | `big-pickle` | Big Pickle | **200K** (200.000) | **32K** (32.000) | `high / max` | ❌ |
221
220
  | `space-bunny-free` | Stealth preview (lab undisclosed) | **1M** (1.048.576) | **512K** (524.288) | `low … max` | ✅ |
222
221
  | `longcat-2.5-preview-free` | Meituan LongCat | **1M** (1.000.000) | **131K** (131.072) | `minimal … max` | ✅ |
223
- | `step-5-preview-free` | StepFun | **1M** (1.000.000) | **64K** (65.536) | `low … high` | ✅ |
222
+ | `glm-5.3-flash-free` | Zhipu AI | **1M** (1.000.000) | **32K** (32.000) | `minimal … max` | ✅ |
223
+ | `beam-free` | Reflection AI | **1M** (1.048.576) | **131K** (131.072) | `low … max` | ❌ |
224
+ | `exo-free` | Undisclosed | **1M** (1.048.576) | **131K** (131.072) | `high` | ✅ |
224
225
  | `deepseek-v4.1-flash-free` | DeepSeek | **1M** (1.000.000) | **384K** (384.000) | `minimal … max` | ✅ |
225
226
 
226
- #### KiloCode Gateway (14 models), OpenRouter compatible
227
+ #### KiloCode Gateway (13 models), OpenRouter compatible
227
228
 
228
229
  Keyless access. Short aliases work for every row (the full ID is in parentheses).
229
230
 
@@ -241,7 +242,6 @@ Keyless access. Short aliases work for every row (the full ID is in parentheses)
241
242
  | `kilo-auto` (`kilo-auto/free`) | Kilo Gateway Auto | **256K** (256.000) | **10K** (10.000) | `minimal…xhigh`\* | ❌ |
242
243
  | `openrouter` (`openrouter/free`) | OpenRouter Free | **200K** (200.000) | **65K** (65.536) | `minimal…xhigh`\* | ✅ |
243
244
  | `glyph-cluster` (`stealth/glyph-cluster`) | Stealth preview (lab undisclosed) | **256K** (256.000) | **256K** (256.000) | `minimal…xhigh`\* | ❌ |
244
- | `step-5-preview:kilo` (`stepfun/...:free`) | StepFun | **1M** (1.000.000) | **64K** (65.536) | `minimal…xhigh`\* | ✅ |
245
245
  | `inkling-small` (`thinkingmachines/inkling-small:free`) | Thinking Machines | **1M** (1.048.576) | **262K** (262.144) | `minimal…xhigh`\* | ✅ |
246
246
 
247
247
  \* Levels are forwarded as-is through the OpenRouter-style nested `reasoning` parameter; effort mapping is decided by each model. MiMo collapses `minimal→low` and `xhigh→high` upstream, so its selector shows 5 labels but only 3 distinct effort values.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.38.1",
4
+ "version": "1.38.3",
5
5
  "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
6
  "main": "extensions/index.ts",
7
7
  "types": "src/index.ts",
package/src/catalog.ts CHANGED
@@ -43,8 +43,14 @@ import type {
43
43
  * - inclusionai/ling-3.0-flash-fin:free: gone from Kilo (2026-10-01) — keyless chat 404
44
44
  * "The requested model ... does not exist".
45
45
  * - mimo-v2.5-free: decommissioned upstream (2026-10-07) — HTTP 401 "Model mimo-v2.5-free is not supported".
46
- * - exo-free: deprecated upstream (2026-10-09) — HTTP 410 "Model exo-free has
47
- * been deprecated." (was 503 "Endpoint is unavailable" on 2026-10-07).
46
+ * Re-probed 2026-10-16: 410 "Model mimo-v2.5-free has been deprecated. Use
47
+ * mimo-v2.6-flash-free instead." The docs still list it as free; they are stale.
48
+ * - exo-free: dead-listed 2026-10-09 (410 "Model exo-free has been deprecated")
49
+ * but revived upstream on 2026-10-16 — back on the official free lineup and
50
+ * answering live through the proxy (chat 200 with tools, stream, reasoning,
51
+ * vision; dual-canary recall at ~520K tokens; a 1.2M-word prompt draws the
52
+ * empty-bodied 400). Re-registered statically the same day: do NOT add this
53
+ * id back to this set unless it dies again.
48
54
  * - fledge-alpha-free: withdrawn upstream (2026-10-09) — absent from the live
49
55
  * Zen list, chat 401 "Model fledge-alpha-free is not supported".
50
56
  * - inclusionai/ling-3.0-flash-sante:free: demoted to paid on Kilo (2026-10-09) —
@@ -94,7 +100,6 @@ export const DEAD_MODEL_IDS = new Set<string>([
94
100
  "ling-3.0-flash-fin-free",
95
101
  "inclusionai/ling-3.0-flash-fin:free",
96
102
  "mimo-v2.5-free",
97
- "exo-free",
98
103
  "stealth/space-bunny-alpha",
99
104
  "qwen/qwen3.8-27b:free",
100
105
  "nvidia/nemotron-3.5-content-safety:free",
@@ -110,6 +115,21 @@ export const DEAD_MODEL_IDS = new Set<string>([
110
115
  "inclusionai/ling-3.1-flash",
111
116
  // ovh-free/Mistral-7B-Instruct-v0.3: removed upstream by OVH (2026-10-15) — answers 404 no route matched.
112
117
  "ovh-free/Mistral-7B-Instruct-v0.3",
118
+ // step-5-preview-free: removed from the Zen free docs (2026-10-16 drift,
119
+ // v1.19.2) and the endpoint answers 400 "Model is unavailable" on both the
120
+ // chat and the responses shape — the same retirement signature as
121
+ // deepseek-v4-flash-free.
122
+ "step-5-preview-free",
123
+ // stepfun/step-5-preview-free: demoted to paid on Kilo (2026-10-16) —
124
+ // answers 401 PAID_MODEL_AUTH_REQUIRED while the sibling z-ai/glm-5.3-flash
125
+ // answers 200 keyless with the same request shape.
126
+ "stepfun/step-5-preview-free",
127
+ // ll-alpha-free: decommissioned upstream (2026-10-17) — gone from live
128
+ // Zen /v1/models list, answers HTTP 410 "Model ll-alpha-free has been deprecated".
129
+ "ll-alpha-free",
130
+ // muse-spark-1.3-contributor-free: decommissioned upstream (2026-10-17) — gone from
131
+ // live Zen /v1/models list, answers HTTP 410 "Model muse-spark-1.3-contributor-free has been deprecated".
132
+ "muse-spark-1.3-contributor-free",
113
133
  ]);
114
134
  /**
115
135
  * Free-tier allowlist for anything entering the picker via network or stale disk.
package/src/models.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Static model definitions and upstream routing catalogs for pi-freeflow
3
3
  *
4
- * Defines the 36 verified free models (live-verified 2026-10-16):
5
- * - 12 OpenCode Zen models (2 Responses API + 10 Chat Completions)
6
- * - 14 KiloCode Keyless Gateway models (OpenRouter format)
4
+ * Defines the 36 verified free models (live-verified 2026-10-17):
5
+ * - 13 OpenCode Zen models (1 Responses API + 12 Chat Completions)
6
+ * - 13 KiloCode Keyless Gateway models (OpenRouter format)
7
7
  * - 2 Cline direct-only models (per-user pool)
8
8
  * - 8 OVHcloud AI Endpoints models (keyless, direct-only)
9
9
  */
@@ -59,24 +59,6 @@ export const OPENCODE_MODELS: ModelDef[] = [
59
59
  max: null,
60
60
  },
61
61
  },
62
- {
63
- id: "muse-spark-1.3-contributor-free",
64
- name: "Muse Spark 1.3 [OpenCode]",
65
- reasoning: true,
66
- contextWindow: 1_048_576,
67
- maxTokens: 131_072,
68
- api: "openai-responses",
69
- input: ["text", "image", "video"],
70
- thinkingLevelMap: {
71
- off: null,
72
- minimal: "minimal",
73
- low: "low",
74
- medium: "medium",
75
- high: "high",
76
- xhigh: "xhigh",
77
- max: null,
78
- },
79
- },
80
62
  {
81
63
  id: "mimo-v2.6-flash-free",
82
64
  name: "MiMo V2.6 Flash [OpenCode]",
@@ -216,30 +198,97 @@ export const OPENCODE_MODELS: ModelDef[] = [
216
198
  },
217
199
  },
218
200
  {
219
- // Added 2026-10-09: replaces fledge-alpha-free (withdrawn upstream —
220
- // absent from the live list, chat 401 "not supported"). Live on the Zen
221
- // list; gated-lane 403 matches the working models from a refused egress,
222
- // so it rides the same fingerprint path. Specs per models.dev
223
- // (opencode/step-5-preview-free) + StepFun docs, NOT the enrichModelDef
224
- // substring heuristic (which yields reasoning:false / 262K / text-only):
225
- // 1M ctx, 64K max output, text+image+video, efforts low/medium/high only.
226
- // minimal is explicitly null (omitting the key leaves it selectable in
227
- // the host); xhigh/max are unsupported upstream.
228
- id: "step-5-preview-free",
229
- name: "Step 5 Preview [OpenCode]",
230
- reasoning: true,
231
- contextWindow: 1_000_000,
232
- maxTokens: 65_536,
233
- input: ["text", "image", "video"],
234
- thinkingLevelMap: {
235
- off: null,
236
- minimal: null,
237
- low: "low",
238
- medium: "medium",
239
- high: "high",
240
- xhigh: null,
241
- max: null,
201
+ // Added 2026-10-16 (issue #71 drift): Zen v1.19.2 added this free id;
202
+ // served through the fingerprint path (chat 200 with tools + stream).
203
+ // Not published on pi.dev yet, and models.dev's 200,000 context is
204
+ // contradicted by probe: a marker planted at 90% depth (~250K words) is
205
+ // recalled, and only above ~1M words does the gateway refuse with
206
+ // context_length_exceeded. Declared 1,000,000 like its Zen siblings and
207
+ // the Kilo twin (z-ai/glm-5.3-flash, 1,048,576 in this catalog); the
208
+ // 450K/700K-word recall probes timed out upstream and stayed inconclusive.
209
+ // maxTokens 32,000 per models.dev — the gateway accepted up to 131,072,
210
+ // and acceptance is not a cap, so the published figure stays the budget.
211
+ // Effort ladder probed level-by-level 2026-10-16: minimal/low/medium/high/
212
+ // xhigh/max all answer 200 (an unsupported value answers 400
213
+ // [invalid_value], so the gateway validates per model), and minimal vs max
214
+ // visibly changes reasoning length. Chat is the only native shape
215
+ // (/v1/responses answers 400 "Model does not support this protocol"),
216
+ // so `api` stays unset. Vision verified live (a 1x1 PNG reads back as a
217
+ // colour); video/pdf from the published entry stay unverified and are not
218
+ // offered.
219
+ id: "glm-5.3-flash-free",
220
+ name: "GLM-5.3 Flash [OpenCode]",
221
+ reasoning: true,
222
+ contextWindow: 1_000_000,
223
+ maxTokens: 32_000,
224
+ input: ["text", "image"],
225
+ thinkingLevelMap: {
226
+ off: null,
227
+ minimal: "minimal",
228
+ low: "low",
229
+ medium: "medium",
230
+ high: "high",
231
+ xhigh: "xhigh",
232
+ max: "max",
233
+ },
242
234
  },
235
+ {
236
+ // Added 2026-10-17: Reflection AI's Beam 501B open-weight MoE model (23B active),
237
+ // added as a limited-time free model on OpenCode Zen (/zen/v1/models: beam-free).
238
+ // Live-probed 2026-10-17: 200 with stream:true and tool sextet.
239
+ // Context window: 1_048_576 tokens (1M), maxTokens: 131_072.
240
+ // Probed boundary: filler up to 900,000 words (~1.1M tokens) answers 200.
241
+ // Input: text-only (image probe answers 400 "Beam supports text input only").
242
+ // Wire format: Chat Completions (/v1/responses answers 400 ModelProtocolUnsupported).
243
+ // Reasoning effort ladder: minimal is rejected with 400 (unsupported_value),
244
+ // low/medium/high/xhigh/max all answer 200 with reasoning.
245
+ id: "beam-free",
246
+ name: "Beam Free [OpenCode]",
247
+ reasoning: true,
248
+ contextWindow: 1_048_576,
249
+ maxTokens: 131_072,
250
+ input: ["text"],
251
+ thinkingLevelMap: {
252
+ off: null,
253
+ minimal: null,
254
+ low: "low",
255
+ medium: "medium",
256
+ high: "high",
257
+ xhigh: "xhigh",
258
+ max: "max",
259
+ },
260
+ },
261
+ {
262
+ // Added 2026-10-16 (recheck): live free id on the Zen lane, listed on the
263
+ // official free lineup while models.dev already tags it deprecated — the
264
+ // tag also sits on live ids (muse-spark-1.2-contributor-free) and on
265
+ // already-dead ones (mimo-v2.5-free), so the live answer decides: chat 200
266
+ // with tools + stream, and the tool call round-trips. Chat is the only
267
+ // native shape (/v1/responses answers 400 "Model does not support this
268
+ // protocol"), so `api` stays unset. Reasoning: the published ladder lists
269
+ // `high` only; high streams thinking and the unavailable rungs stay null
270
+ // so the picker never offers them. Vision verified live (a 1x1 PNG reads
271
+ // back as "Pink"). Context: published 1,048,576; a dual-canary prompt at
272
+ // ~400K words (~520K tokens, canaries at ~30%/~97% depth) recalls both
273
+ // markers, and a 1.2M-word prompt (~1.56M tokens) draws an empty-bodied
274
+ // 400 — the wall sits between, matching the published figure. maxTokens
275
+ // 131,072 published; 131,073 accepted (acceptance is not a cap, so the
276
+ // published figure stays the budget).
277
+ id: "exo-free",
278
+ name: "Exo [OpenCode]",
279
+ reasoning: true,
280
+ contextWindow: 1_048_576,
281
+ maxTokens: 131_072,
282
+ input: ["text", "image"],
283
+ thinkingLevelMap: {
284
+ off: null,
285
+ minimal: null,
286
+ low: null,
287
+ medium: null,
288
+ high: "high",
289
+ xhigh: null,
290
+ max: null,
291
+ },
243
292
  },
244
293
  {
245
294
  // Added 2026-10-15: new free model on the OpenCode Zen lane. Live-verified by
@@ -409,19 +458,6 @@ export const KILO_MODELS: ModelDef[] = [
409
458
  thinkingFormat: "openrouter",
410
459
  thinkingLevelMap: KILO_REASONING_MAP,
411
460
  },
412
- {
413
- // Added 2026-10-09: replaces step-3.7-flash (demoted to paid upstream —
414
- // :free ID 404s keyless). Live on Kilo free list (isFree:true, 0/0
415
- // pricing, 1M ctx) + keyless chat served (429 provider concurrency).
416
- id: "stepfun/step-5-preview-free",
417
- name: "Step 5 Preview [Kilo]",
418
- reasoning: true,
419
- contextWindow: 1_000_000,
420
- maxTokens: 65_536,
421
- input: ["text", "image", "video"],
422
- thinkingFormat: "openrouter",
423
- thinkingLevelMap: KILO_REASONING_MAP,
424
- },
425
461
  {
426
462
  id: "thinkingmachines/inkling-small:free",
427
463
  name: "Inkling Small [Kilo]",
@@ -656,7 +692,8 @@ export const MODEL_ALIASES: Record<string, string> = {
656
692
  "nemotron-3-super": "nvidia/nemotron-3-super-120b-a12b:free",
657
693
  "north-mini-code": "cohere/north-mini-code:free",
658
694
  "lfm-2.5": "liquid/lfm-2.5-2.6b:free",
659
- "step-5-preview:kilo": "stepfun/step-5-preview-free",
695
+ "glm-5.3-flash": "glm-5.3-flash-free",
696
+ "exo": "exo-free",
660
697
  "glyph-cluster": "stealth/glyph-cluster",
661
698
  "inkling-small": "thinkingmachines/inkling-small:free",
662
699
  "mimo-v2.6-flash": "mimo-v2.6-flash-free",
@@ -664,7 +701,7 @@ export const MODEL_ALIASES: Record<string, string> = {
664
701
  "space-bunny": "space-bunny-free",
665
702
  "longcat-2.5-preview": "longcat-2.5-preview-free",
666
703
  "longcat": "longcat-2.5-preview-free",
667
- "step-5-preview": "step-5-preview-free",
704
+ "beam": "beam-free",
668
705
  "deepseek-v4.1-flash": "deepseek-v4.1-flash-free",
669
706
  // provider-prefixed short aliases (slash-normalized)
670
707
  "laguna-s-2.1:free": "poolside/laguna-s-2.1:free",
@@ -442,6 +442,53 @@ function cloakStreamResponsesObject(resp: unknown, cloak: StreamCloakOptions): v
442
442
  (resp as Record<string, unknown>).output = kept;
443
443
  }
444
444
 
445
+ /**
446
+ * Terminal chunk for chat choice: absorb any tool-call argument fragment
447
+ * riding the finish_reason chunk into the buffered prefix, then re-emit every
448
+ * buffered tool call's whole argument packet, repaired.
449
+ * Returns true if delta was mutated.
450
+ */
451
+ function flushChatTerminalToolCalls(
452
+ d: Record<string, unknown>,
453
+ state: SseStreamCloakState,
454
+ cloak: StreamCloakOptions,
455
+ flushedIndices: Set<number>,
456
+ ): boolean {
457
+ if (state.chatArgsByIndex.size === 0) return false;
458
+
459
+ for (const tc of Array.isArray(d.tool_calls) ? d.tool_calls : []) {
460
+ if (!tc || typeof tc !== "object" || Array.isArray(tc)) continue;
461
+ const rec = tc as Record<string, unknown>;
462
+ const idx = typeof rec.index === "number" ? rec.index : 0;
463
+ const fn = rec.function;
464
+ const fnRec = fn && typeof fn === "object" && !Array.isArray(fn) ? (fn as Record<string, unknown>) : null;
465
+ if (fnRec && state.chatArgsByIndex.has(idx) && typeof fnRec.arguments === "string") {
466
+ state.chatArgsByIndex.set(idx, (state.chatArgsByIndex.get(idx) ?? "") + fnRec.arguments);
467
+ }
468
+ }
469
+
470
+ const flushed: unknown[] = [];
471
+ for (const [idx, args] of state.chatArgsByIndex) {
472
+ const name = state.chatNameByIndex.get(idx);
473
+ if (name === undefined) continue;
474
+ flushedIndices.add(idx);
475
+ flushed.push({ index: idx, type: "function", function: { name, arguments: repairToolArguments(name, args, cloak.argSchemas) } });
476
+ }
477
+ state.chatArgsByIndex.clear();
478
+ state.chatNameByIndex.clear();
479
+
480
+ if (flushed.length === 0) return false;
481
+
482
+ const remaining = (Array.isArray(d.tool_calls) ? d.tool_calls : []).filter((tc) => {
483
+ if (!tc || typeof tc !== "object" || Array.isArray(tc)) return true;
484
+ const rec = tc as Record<string, unknown>;
485
+ const idx = typeof rec.index === "number" ? rec.index : 0;
486
+ return !flushedIndices.has(idx);
487
+ });
488
+ d.tool_calls = [...remaining, ...flushed];
489
+ return true;
490
+ }
491
+
445
492
  /**
446
493
  * Rewrite one Chat Completions SSE block: drop injected delta.tool_calls
447
494
  * (including nameless argument continuations via per-index tracking), restore
@@ -489,20 +536,8 @@ function rewriteChatBlock(
489
536
  // Indices flushed on this block: their arguments are final, so the
490
537
  // per-delta loop below must not buffer them a second time.
491
538
  const flushedIndices = new Set<number>();
492
- if (c.finish_reason && state.chatArgsByIndex.size > 0) {
493
- const flushed: unknown[] = [];
494
- for (const [idx, args] of state.chatArgsByIndex) {
495
- const name = state.chatNameByIndex.get(idx);
496
- if (name === undefined) continue;
497
- flushedIndices.add(idx);
498
- flushed.push({ index: idx, type: "function", function: { name, arguments: repairToolArguments(name, args, cloak.argSchemas) } });
499
- }
500
- state.chatArgsByIndex.clear();
501
- state.chatNameByIndex.clear();
502
- if (flushed.length > 0) {
503
- d.tool_calls = [...(Array.isArray(d.tool_calls) ? d.tool_calls : []), ...flushed];
504
- changed = true;
505
- }
539
+ if (c.finish_reason && flushChatTerminalToolCalls(d, state, cloak, flushedIndices)) {
540
+ changed = true;
506
541
  }
507
542
  if (!Array.isArray(d.tool_calls)) {
508
543
  if (Object.keys(d).length > 0) allDeltasEmpty = false;