@oh-my-pi/pi-ai 15.9.5 → 15.9.67
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
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.9.67] - 2026-06-06
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed llama.cpp/OpenAI Responses parallel tool calls losing arguments when `function_call_arguments.done` events omit `output_index` and `item_id`, by routing those identifierless final-argument events through the open function calls in item order. ([#1970](https://github.com/can1357/oh-my-pi/issues/1970))
|
|
10
|
+
- Fixed local Ollama (`openai-responses`) turns failing with HTTP 400 `invalid reasoning value: "minimal"` when a discovered model ran with `minimal` (or `xhigh`) thinking. Ollama's OpenAI-compatible `reasoning.effort` only accepts `high|medium|low|max|none`, so discovered reasoning-capable Ollama models now carry a `compat.reasoningEffortMap` remapping `minimal → low` and `xhigh → max`; non-reasoning models are left untouched.
|
|
11
|
+
|
|
5
12
|
## [15.9.2] - 2026-06-05
|
|
6
13
|
|
|
7
14
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "15.9.
|
|
4
|
+
"version": "15.9.67",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@bufbuild/protobuf": "^2.12.0",
|
|
42
|
-
"@oh-my-pi/pi-utils": "15.9.
|
|
42
|
+
"@oh-my-pi/pi-utils": "15.9.67",
|
|
43
43
|
"openai": "^6.39.0",
|
|
44
44
|
"partial-json": "^0.1.7",
|
|
45
45
|
"zod": "4.4.3"
|
|
@@ -242,6 +242,22 @@ async function fetchOllamaNativeModels(
|
|
|
242
242
|
const OLLAMA_FALLBACK_CONTEXT_WINDOW = 128_000;
|
|
243
243
|
/** Cap max output tokens at a value that matches OMP's other openai-responses defaults. */
|
|
244
244
|
const OLLAMA_DEFAULT_MAX_TOKENS = 8192;
|
|
245
|
+
/**
|
|
246
|
+
* Ollama's OpenAI-compatible `reasoning.effort` only accepts
|
|
247
|
+
* `high|medium|low|max|none`; passing OMP's `minimal`/`xhigh` levels verbatim
|
|
248
|
+
* makes the server reject the turn with HTTP 400 `invalid reasoning value`.
|
|
249
|
+
* Map the two unsupported levels onto the closest accepted ones (`low`/`max`).
|
|
250
|
+
*/
|
|
251
|
+
const OLLAMA_REASONING_EFFORT_MAP = { minimal: "low", xhigh: "max" } as const;
|
|
252
|
+
|
|
253
|
+
/** Stamp the Ollama reasoning-effort map onto a reasoning-capable model. */
|
|
254
|
+
function applyOllamaReasoningCompat(model: Model<"openai-responses">): void {
|
|
255
|
+
if (!model.reasoning) return;
|
|
256
|
+
model.compat = {
|
|
257
|
+
...model.compat,
|
|
258
|
+
reasoningEffortMap: { ...OLLAMA_REASONING_EFFORT_MAP, ...model.compat?.reasoningEffortMap },
|
|
259
|
+
};
|
|
260
|
+
}
|
|
245
261
|
|
|
246
262
|
interface OllamaResolvedMetadata {
|
|
247
263
|
contextWindow: number;
|
|
@@ -1357,12 +1373,14 @@ export function ollamaModelManagerOptions(config?: OllamaModelManagerConfig): Mo
|
|
|
1357
1373
|
if (metadata.input) {
|
|
1358
1374
|
model.input = metadata.input;
|
|
1359
1375
|
}
|
|
1376
|
+
applyOllamaReasoningCompat(model);
|
|
1360
1377
|
}),
|
|
1361
1378
|
);
|
|
1362
1379
|
return openAiCompatible;
|
|
1363
1380
|
}
|
|
1364
1381
|
const nativeFallback = await fetchOllamaNativeModels(baseUrl, resolveMetadata);
|
|
1365
1382
|
if (nativeFallback && nativeFallback.length > 0) {
|
|
1383
|
+
for (const model of nativeFallback) applyOllamaReasoningCompat(model);
|
|
1366
1384
|
return nativeFallback;
|
|
1367
1385
|
}
|
|
1368
1386
|
return openAiCompatible;
|
|
@@ -1778,16 +1778,12 @@ type SystemBlockOptions = {
|
|
|
1778
1778
|
cacheControl?: AnthropicCacheControl;
|
|
1779
1779
|
};
|
|
1780
1780
|
|
|
1781
|
-
function withGlobalCacheScope(cacheControl: AnthropicCacheControl): AnthropicCacheControl {
|
|
1782
|
-
return { ...cacheControl, scope: "global" };
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
1781
|
function applyClaudeCodeSystemCache(
|
|
1786
1782
|
blocks: AnthropicSystemBlock[],
|
|
1787
1783
|
cacheControl: AnthropicCacheControl | undefined,
|
|
1788
1784
|
): number {
|
|
1789
1785
|
if (!cacheControl || blocks.length <= 2) return 0;
|
|
1790
|
-
blocks[2] = { ...blocks[2], cache_control:
|
|
1786
|
+
blocks[2] = { ...blocks[2], cache_control: cacheControl };
|
|
1791
1787
|
if (blocks.length === 3) return 1;
|
|
1792
1788
|
const lastIndex = blocks.length - 1;
|
|
1793
1789
|
blocks[lastIndex] = { ...blocks[lastIndex], cache_control: cacheControl };
|
|
@@ -395,7 +395,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
395
395
|
model: Model<TApi>,
|
|
396
396
|
options?: ProcessResponsesStreamOptions,
|
|
397
397
|
): Promise<void> {
|
|
398
|
-
type StreamingToolCallBlock = ToolCall & { partialJson: string; lastParseLen?: number };
|
|
398
|
+
type StreamingToolCallBlock = ToolCall & { partialJson: string; lastParseLen?: number; argumentsDone?: boolean };
|
|
399
399
|
interface StreamingItem {
|
|
400
400
|
item: ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | ResponseCustomToolCall;
|
|
401
401
|
block: ThinkingContent | TextContent | StreamingToolCallBlock;
|
|
@@ -409,6 +409,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
409
409
|
const openItemsByOutputIndex = new Map<number, StreamingItem>();
|
|
410
410
|
const openItemsByItemId = new Map<string, StreamingItem>();
|
|
411
411
|
let lastOpenItem: StreamingItem | null = null;
|
|
412
|
+
const openItemsInOrder: StreamingItem[] = [];
|
|
412
413
|
|
|
413
414
|
const registerOpenItem = (
|
|
414
415
|
outputIndex: number | undefined,
|
|
@@ -417,6 +418,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
417
418
|
): void => {
|
|
418
419
|
if (typeof outputIndex === "number") openItemsByOutputIndex.set(outputIndex, entry);
|
|
419
420
|
if (itemId) openItemsByItemId.set(itemId, entry);
|
|
421
|
+
openItemsInOrder.push(entry);
|
|
420
422
|
lastOpenItem = entry;
|
|
421
423
|
};
|
|
422
424
|
const lookupOpenItem = (event: { output_index?: number; item_id?: string }): StreamingItem | undefined => {
|
|
@@ -431,6 +433,24 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
431
433
|
// Fallback for tests / mock providers that omit identifiers on stream events.
|
|
432
434
|
return lastOpenItem ?? undefined;
|
|
433
435
|
};
|
|
436
|
+
const hasOpenItemKey = (event: { output_index?: number; item_id?: string }): boolean =>
|
|
437
|
+
typeof event.output_index === "number" || event.item_id !== undefined;
|
|
438
|
+
const lookupOpenFunctionCallItem = (event: {
|
|
439
|
+
output_index?: number;
|
|
440
|
+
item_id?: string;
|
|
441
|
+
}): StreamingItem | undefined => {
|
|
442
|
+
if (hasOpenItemKey(event)) return lookupOpenItem(event);
|
|
443
|
+
for (const candidate of openItemsInOrder) {
|
|
444
|
+
if (
|
|
445
|
+
candidate.item.type === "function_call" &&
|
|
446
|
+
candidate.block.type === "toolCall" &&
|
|
447
|
+
!candidate.block.argumentsDone
|
|
448
|
+
) {
|
|
449
|
+
return candidate;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return lastOpenItem?.item.type === "function_call" ? lastOpenItem : undefined;
|
|
453
|
+
};
|
|
434
454
|
const closeOpenItem = (
|
|
435
455
|
outputIndex: number | undefined,
|
|
436
456
|
itemId: string | undefined,
|
|
@@ -438,6 +458,10 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
438
458
|
): void => {
|
|
439
459
|
if (typeof outputIndex === "number") openItemsByOutputIndex.delete(outputIndex);
|
|
440
460
|
if (itemId) openItemsByItemId.delete(itemId);
|
|
461
|
+
if (entry) {
|
|
462
|
+
const index = openItemsInOrder.indexOf(entry);
|
|
463
|
+
if (index >= 0) openItemsInOrder.splice(index, 1);
|
|
464
|
+
}
|
|
441
465
|
if (entry && lastOpenItem === entry) lastOpenItem = null;
|
|
442
466
|
};
|
|
443
467
|
const contentIndexOf = (block: ThinkingContent | TextContent | StreamingToolCallBlock): number =>
|
|
@@ -584,7 +608,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
584
608
|
}
|
|
585
609
|
}
|
|
586
610
|
} else if (event.type === "response.function_call_arguments.delta") {
|
|
587
|
-
const entry =
|
|
611
|
+
const entry = lookupOpenFunctionCallItem(event);
|
|
588
612
|
if (entry?.item.type === "function_call" && entry.block.type === "toolCall") {
|
|
589
613
|
const block = entry.block;
|
|
590
614
|
block.partialJson += event.delta;
|
|
@@ -601,11 +625,12 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
601
625
|
});
|
|
602
626
|
}
|
|
603
627
|
} else if (event.type === "response.function_call_arguments.done") {
|
|
604
|
-
const entry =
|
|
628
|
+
const entry = lookupOpenFunctionCallItem(event);
|
|
605
629
|
if (entry?.item.type === "function_call" && entry.block.type === "toolCall") {
|
|
606
630
|
const block = entry.block;
|
|
607
631
|
block.partialJson = event.arguments;
|
|
608
632
|
block.arguments = parseStreamingJson(block.partialJson);
|
|
633
|
+
block.argumentsDone = true;
|
|
609
634
|
delete (block as { partialJson?: string }).partialJson;
|
|
610
635
|
delete (block as { lastParseLen?: number }).lastParseLen;
|
|
611
636
|
}
|
|
@@ -668,9 +693,11 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
668
693
|
closeOpenItem(event.output_index, item.id, entry);
|
|
669
694
|
} else if (item.type === "function_call") {
|
|
670
695
|
const block = entry?.block.type === "toolCall" ? entry.block : undefined;
|
|
671
|
-
const args = block?.
|
|
672
|
-
?
|
|
673
|
-
:
|
|
696
|
+
const args = block?.argumentsDone
|
|
697
|
+
? block.arguments
|
|
698
|
+
: block?.partialJson
|
|
699
|
+
? parseStreamingJson(block.partialJson)
|
|
700
|
+
: parseStreamingJson(item.arguments || "{}");
|
|
674
701
|
const toolCall: ToolCall = {
|
|
675
702
|
type: "toolCall",
|
|
676
703
|
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
@@ -685,6 +712,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
685
712
|
block.arguments = args;
|
|
686
713
|
delete (block as { partialJson?: string }).partialJson;
|
|
687
714
|
delete (block as { lastParseLen?: number }).lastParseLen;
|
|
715
|
+
delete (block as { argumentsDone?: boolean }).argumentsDone;
|
|
688
716
|
}
|
|
689
717
|
const contentIndex = block ? contentIndexOf(block) : output.content.length - 1;
|
|
690
718
|
closeOpenItem(event.output_index, item.id, entry);
|