@oh-my-pi/pi-ai 18.13.2 → 18.13.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 +6 -0
- package/dist/types/providers/openai-completions.d.ts +13 -0
- package/dist/types/providers/transform-messages.d.ts +9 -0
- package/package.json +7 -7
- package/src/judgment/text.ts +1 -9
- package/src/providers/cowork-fetch.ts +0 -3
- package/src/providers/openai-completions.ts +28 -9
- package/src/providers/transform-messages.ts +27 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,8 @@ import type { AssistantMessage, Context, Model, ServiceTier, StreamFunction, Str
|
|
|
3
3
|
import type { ChatCompletionMessageParam } from "./openai-chat-wire.js";
|
|
4
4
|
import { type OpenAIPromptCacheOptions } from "./openai-shared.js";
|
|
5
5
|
export { applyOpenRouterRoutingVariant } from "./openai-shared.js";
|
|
6
|
+
/** Whether a thinking block's signature is a raw LiteLLM (Anthropic) one, not a reasoning field name. */
|
|
7
|
+
export declare function isLiteLLMThinkingSignature(signature: string | undefined): signature is string;
|
|
6
8
|
/**
|
|
7
9
|
* Identify "real progress" stream chunks vs. keepalives, role-only preambles,
|
|
8
10
|
* and empty `{choices:[]}` no-ops emitted by some OpenAI-compatible endpoints.
|
|
@@ -43,4 +45,15 @@ export interface OpenAICompletionsOptions extends StreamOptions {
|
|
|
43
45
|
*/
|
|
44
46
|
export declare const streamOpenAICompletions: StreamFunction<"openai-completions">;
|
|
45
47
|
export declare function parseChunkUsage(rawUsage: object, model: Model<"openai-completions">, premiumRequests: number | undefined, timestamp?: number, cachedTokensHeader?: number): AssistantMessage["usage"];
|
|
48
|
+
/**
|
|
49
|
+
* Whether every chat-completions request to `model` drops the thinking text of
|
|
50
|
+
* its own prior turns: neither the base compat nor its `whenThinking` view
|
|
51
|
+
* sends it. Blocks with a raw LiteLLM signature ({@link isLiteLLMThinkingSignature})
|
|
52
|
+
* still replay in `thinking_blocks`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function completionsDropsOwnThinking(model: {
|
|
55
|
+
api?: string;
|
|
56
|
+
reasoning?: boolean;
|
|
57
|
+
compat?: Model["compat"];
|
|
58
|
+
} | null | undefined): boolean;
|
|
46
59
|
export declare function convertMessages(model: Model<"openai-completions">, context: Context, compat: ResolvedOpenAICompat): ChatCompletionMessageParam[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ResolvedOpenAICompat } from "@oh-my-pi/pi-catalog/types";
|
|
1
2
|
import type { Api, AssistantMessage, Message, Model } from "../types.js";
|
|
2
3
|
/**
|
|
3
4
|
* Maximum tool-call id length the strictest replay provider accepts.
|
|
@@ -54,6 +55,14 @@ export declare function isSurvivingAssistant(msg: AssistantMessage, model?: Mode
|
|
|
54
55
|
export declare function isAnthropicMessagesModel(model: Model): model is Model<"anthropic-messages">;
|
|
55
56
|
/** Whether `turn` came from `model` itself: its own turns replay their native reasoning unchanged. */
|
|
56
57
|
export declare function isSameModelTurn(turn: Pick<AssistantMessage, "provider" | "api" | "model">, model: Pick<Model, "provider" | "api" | "id">): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether chat completions (`convertMessages`) writes the model's own past
|
|
60
|
+
* thinking under `compat`: as Mistral content parts, demoted text, or a
|
|
61
|
+
* reasoning field. Otherwise it drops it.
|
|
62
|
+
*/
|
|
63
|
+
export declare function completionsSendsOwnThinking(model: {
|
|
64
|
+
reasoning?: boolean;
|
|
65
|
+
}, compat: Pick<ResolvedOpenAICompat, "mistralReasoningContentParts" | "requiresThinkingAsText" | "requiresReasoningContentForToolCalls" | "thinkingFormat" | "replayReasoningContent">): boolean;
|
|
57
66
|
/**
|
|
58
67
|
* Normalize tool call ID for cross-provider compatibility.
|
|
59
68
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.13.
|
|
3
|
+
"version": "18.13.3",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -155,16 +155,16 @@
|
|
|
155
155
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
156
156
|
},
|
|
157
157
|
"dependencies": {
|
|
158
|
-
"@oh-my-pi/omptype": "18.13.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.13.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.13.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.13.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.13.
|
|
158
|
+
"@oh-my-pi/omptype": "18.13.3",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.13.3",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.13.3",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.13.3",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.13.3"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
|
166
166
|
},
|
|
167
167
|
"engines": {
|
|
168
|
-
"bun": ">=1.
|
|
168
|
+
"bun": ">=1.4.4"
|
|
169
169
|
}
|
|
170
170
|
}
|
package/src/judgment/text.ts
CHANGED
|
@@ -98,15 +98,7 @@ function renderStateField(key: string, value: JsonValue): string {
|
|
|
98
98
|
if (typeof value !== "object" || value === null) {
|
|
99
99
|
return `${open}${escapeXmlText(value === null ? "null" : String(value))}${close}`;
|
|
100
100
|
}
|
|
101
|
-
const yaml = YAML.stringify(value, null, 2)
|
|
102
|
-
// Bun <=1.4.2 emits `key: ` before a nested block and puts an empty
|
|
103
|
-
// collection on its own line; 1.4.3 emits `key:` and `key: {}`. Pin the
|
|
104
|
-
// newer form so prompt bytes (and prompt-cache keys) do not depend on the
|
|
105
|
-
// Bun build. Values with edge whitespace are always quoted, so trimming
|
|
106
|
-
// line ends never alters data.
|
|
107
|
-
.replace(/[ \t]+$/gm, "")
|
|
108
|
-
.replace(/:\n +(\{\}|\[\])$/gm, ": $1")
|
|
109
|
-
.trimEnd();
|
|
101
|
+
const yaml = YAML.stringify(value, null, 2);
|
|
110
102
|
return `${open}\n${escapeXmlText(yaml)}\n${close}`;
|
|
111
103
|
}
|
|
112
104
|
|
|
@@ -200,9 +200,6 @@ async function sendCoworkRequest(
|
|
|
200
200
|
const reason = signal?.reason;
|
|
201
201
|
const error = reason instanceof Error ? reason : new DOMException("The operation was aborted.", "AbortError");
|
|
202
202
|
request?.destroy(error);
|
|
203
|
-
// Bun before 1.4 emits 'close' but not 'error' for destroy(error), so the
|
|
204
|
-
// 'error' listener below never settles a request still waiting for headers.
|
|
205
|
-
result.reject(error);
|
|
206
203
|
};
|
|
207
204
|
if (signal?.aborted) {
|
|
208
205
|
release();
|
|
@@ -121,7 +121,7 @@ import {
|
|
|
121
121
|
shouldDropAutoToolChoiceForReasoning,
|
|
122
122
|
shouldRetryWithoutStrictTools,
|
|
123
123
|
} from "./openai-shared";
|
|
124
|
-
import { transformMessages } from "./transform-messages";
|
|
124
|
+
import { completionsSendsOwnThinking, transformMessages } from "./transform-messages";
|
|
125
125
|
import {
|
|
126
126
|
isOpenAICompletionsVisionSupported,
|
|
127
127
|
joinTextWithImagePlaceholder,
|
|
@@ -273,6 +273,13 @@ function getLiteLLMThinkingBlocksDelta(delta: object): unknown[] | undefined {
|
|
|
273
273
|
return Array.isArray(mirrored) && mirrored.length > 0 ? mirrored : undefined;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
/** Whether a thinking block's signature is a raw LiteLLM (Anthropic) one, not a reasoning field name. */
|
|
277
|
+
export function isLiteLLMThinkingSignature(signature: string | undefined): signature is string {
|
|
278
|
+
return (
|
|
279
|
+
!!signature && signature !== "reasoning_content" && signature !== "reasoning" && signature !== "reasoning_text"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
276
283
|
/**
|
|
277
284
|
* Rebuilds LiteLLM `thinking_blocks` from a stored assistant turn. Thinking
|
|
278
285
|
* blocks parsed from `thinking_blocks` keep the raw Anthropic signature as
|
|
@@ -286,14 +293,7 @@ function encodeLiteLLMThinkingBlocks(content: AssistantMessage["content"]): Lite
|
|
|
286
293
|
for (const block of content) {
|
|
287
294
|
if (block.type === "thinking") {
|
|
288
295
|
const signature = block.thinkingSignature;
|
|
289
|
-
if (
|
|
290
|
-
!signature ||
|
|
291
|
-
signature === "reasoning_content" ||
|
|
292
|
-
signature === "reasoning" ||
|
|
293
|
-
signature === "reasoning_text"
|
|
294
|
-
) {
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
296
|
+
if (!isLiteLLMThinkingSignature(signature)) continue;
|
|
297
297
|
blocks.push({ type: "thinking", thinking: block.thinking, signature });
|
|
298
298
|
} else if (block.type === "redactedThinking") {
|
|
299
299
|
blocks.push({ type: "redacted_thinking", data: block.data });
|
|
@@ -2366,6 +2366,25 @@ function maybeAddAnthropicCacheControl(compat: ResolvedOpenAICompat, messages: C
|
|
|
2366
2366
|
}
|
|
2367
2367
|
}
|
|
2368
2368
|
|
|
2369
|
+
/**
|
|
2370
|
+
* Whether every chat-completions request to `model` drops the thinking text of
|
|
2371
|
+
* its own prior turns: neither the base compat nor its `whenThinking` view
|
|
2372
|
+
* sends it. Blocks with a raw LiteLLM signature ({@link isLiteLLMThinkingSignature})
|
|
2373
|
+
* still replay in `thinking_blocks`.
|
|
2374
|
+
*/
|
|
2375
|
+
export function completionsDropsOwnThinking(
|
|
2376
|
+
model: { api?: string; reasoning?: boolean; compat?: Model["compat"] } | null | undefined,
|
|
2377
|
+
): boolean {
|
|
2378
|
+
if (model?.api !== "openai-completions" || !model.compat) return false;
|
|
2379
|
+
const compat = model.compat as ResolvedOpenAICompat;
|
|
2380
|
+
// A row without a resolved replay flag can't tell; counting the thinking never undercounts.
|
|
2381
|
+
if (!("replayReasoningContent" in compat)) return false;
|
|
2382
|
+
return (
|
|
2383
|
+
!completionsSendsOwnThinking(model, compat) &&
|
|
2384
|
+
(compat.whenThinking === undefined || !completionsSendsOwnThinking(model, compat.whenThinking))
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2369
2388
|
export function convertMessages(
|
|
2370
2389
|
model: Model<"openai-completions">,
|
|
2371
2390
|
context: Context,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { resolveCatalogPolicy } from "@oh-my-pi/pi-catalog/compat/catalog-policy";
|
|
3
3
|
import { carriesReasoning } from "@oh-my-pi/pi-catalog/compat/reasoning-carry";
|
|
4
|
+
import type { ResolvedOpenAICompat } from "@oh-my-pi/pi-catalog/types";
|
|
4
5
|
import { renderDemotedThinking } from "../dialect/demotion";
|
|
5
6
|
import type {
|
|
6
7
|
Api,
|
|
@@ -543,6 +544,31 @@ function targetReadsForeignThinking(model: Model, compat: Model["compat"]): bool
|
|
|
543
544
|
return model.reasoning && compat.thinkingFormat === "zai";
|
|
544
545
|
}
|
|
545
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Whether chat completions (`convertMessages`) writes the model's own past
|
|
549
|
+
* thinking under `compat`: as Mistral content parts, demoted text, or a
|
|
550
|
+
* reasoning field. Otherwise it drops it.
|
|
551
|
+
*/
|
|
552
|
+
export function completionsSendsOwnThinking(
|
|
553
|
+
model: { reasoning?: boolean },
|
|
554
|
+
compat: Pick<
|
|
555
|
+
ResolvedOpenAICompat,
|
|
556
|
+
| "mistralReasoningContentParts"
|
|
557
|
+
| "requiresThinkingAsText"
|
|
558
|
+
| "requiresReasoningContentForToolCalls"
|
|
559
|
+
| "thinkingFormat"
|
|
560
|
+
| "replayReasoningContent"
|
|
561
|
+
>,
|
|
562
|
+
): boolean {
|
|
563
|
+
return Boolean(
|
|
564
|
+
compat.mistralReasoningContentParts ||
|
|
565
|
+
compat.requiresThinkingAsText ||
|
|
566
|
+
compat.requiresReasoningContentForToolCalls ||
|
|
567
|
+
(compat.thinkingFormat === "zai" && model.reasoning) ||
|
|
568
|
+
compat.replayReasoningContent,
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
546
572
|
/**
|
|
547
573
|
* Whether the target's encoder already replays the target's own plaintext
|
|
548
574
|
* reasoning in a native slot. Reasoning carried from another host of the same
|
|
@@ -565,13 +591,7 @@ function targetReplaysPlaintextReasoning(model: Model, compat: Model["compat"]):
|
|
|
565
591
|
if (!("requiresReasoningContentForToolCalls" in compat)) return false;
|
|
566
592
|
switch (model.api) {
|
|
567
593
|
case "openai-completions":
|
|
568
|
-
return (
|
|
569
|
-
!compat.requiresThinkingAsText &&
|
|
570
|
-
(compat.mistralReasoningContentParts === true ||
|
|
571
|
-
compat.requiresReasoningContentForToolCalls ||
|
|
572
|
-
compat.thinkingFormat === "zai" ||
|
|
573
|
-
compat.replayReasoningContent)
|
|
574
|
-
);
|
|
594
|
+
return !compat.requiresThinkingAsText && completionsSendsOwnThinking(model, compat);
|
|
575
595
|
case "openrouter":
|
|
576
596
|
return !compat.filterReasoningHistory && compat.requiresReasoningContentForToolCalls;
|
|
577
597
|
case "openai-responses":
|