pi-freeflow 1.33.2 → 1.33.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.33.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix tool calls being rejected with an invalid-arguments error when a model makes several calls in one turn. Some models label every parallel call `0`, so the pieces of different calls were joined together and arrived as one unreadable blob — a planning tool could then reject every update, even a minimal one, and leave its task list stuck mid-turn. Calls are now kept apart by their own identifier, and a call whose text arrives in several pieces still reassembles whole.
8
+
3
9
  ## 1.33.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.33.2",
4
+ "version": "1.33.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",
@@ -564,10 +564,16 @@ export function sseToChatCompletionJson(
564
564
  let reasoningContent = "";
565
565
  let role = "assistant";
566
566
  let usage: unknown = undefined;
567
+ // Keyed by tool-call id whenever the provider sends one. Index is NOT
568
+ // trustworthy: free models routinely label every parallel call `index: 0`,
569
+ // and merging their fragments produces one unparseable argument blob
570
+ // (observed live: `{"i":"a","op":"done"}{"i":"b","op":"done"}`). Index is
571
+ // only the fallback for providers that omit an id entirely.
567
572
  const toolCallsMap = new Map<
568
- number,
573
+ string,
569
574
  { id: string; type: string; function: { name: string; arguments: string } }
570
575
  >();
576
+ const keyByIndex = new Map<number, string>();
571
577
 
572
578
  for (const ev of events) {
573
579
  if (ev.data === "[DONE]") continue;
@@ -591,8 +597,15 @@ export function sseToChatCompletionJson(
591
597
  }
592
598
  if (Array.isArray(delta.tool_calls)) {
593
599
  for (const tc of delta.tool_calls) {
594
- const idx = tc.index ?? 0;
595
- const existing = toolCallsMap.get(idx) ?? {
600
+ // The first delta of a call carries its id; later deltas carry only
601
+ // `arguments`. A later id-less delta therefore belongs to whatever
602
+ // call this index already opened.
603
+ const idx = typeof tc.index === "number" ? tc.index : 0;
604
+ const hasId = typeof tc.id === "string" && tc.id !== "";
605
+ let key = hasId ? `id:${tc.id}` : keyByIndex.get(idx);
606
+ if (key === undefined) key = `idx:${idx}`;
607
+ if (hasId) keyByIndex.set(idx, key);
608
+ const existing = toolCallsMap.get(key) ?? {
596
609
  id: tc.id || "",
597
610
  type: tc.type || "function",
598
611
  function: { name: "", arguments: "" },
@@ -601,7 +614,7 @@ export function sseToChatCompletionJson(
601
614
  if (tc.type) existing.type = tc.type;
602
615
  if (tc.function?.name) existing.function.name += tc.function.name;
603
616
  if (tc.function?.arguments) existing.function.arguments += tc.function.arguments;
604
- toolCallsMap.set(idx, existing);
617
+ toolCallsMap.set(key, existing);
605
618
  }
606
619
  }
607
620
  }
@@ -617,9 +630,9 @@ export function sseToChatCompletionJson(
617
630
  }
618
631
  }
619
632
 
620
- const toolCalls = Array.from(toolCallsMap.entries())
621
- .sort(([a], [b]) => a - b)
622
- .map(([, tc]) => tc);
633
+ // Map insertion order is emission order, which is what the host expects. The
634
+ // previous numeric sort assumed the keys were indices; they are now ids.
635
+ const toolCalls = Array.from(toolCallsMap.values());
623
636
 
624
637
  const message: Record<string, unknown> = {
625
638
  role,