@oh-my-pi/pi-ai 15.5.13 → 15.6.0
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 +16 -0
- package/dist/types/model-thinking.d.ts +7 -0
- package/dist/types/provider-models/openai-compat.d.ts +2 -2
- package/dist/types/utils/request-debug.d.ts +29 -0
- package/package.json +4 -4
- package/src/auth-broker/remote-store.ts +1 -1
- package/src/auth-storage.ts +2 -2
- package/src/model-manager.ts +5 -3
- package/src/model-thinking.ts +12 -0
- package/src/models.json +55 -20
- package/src/provider-models/openai-compat.ts +59 -29
- package/src/providers/amazon-bedrock.ts +1 -0
- package/src/providers/anthropic.ts +18 -34
- package/src/providers/cursor.ts +56 -13
- package/src/providers/openai-codex-responses.ts +40 -2
- package/src/providers/openai-completions-compat.ts +5 -0
- package/src/providers/openai-completions.ts +38 -12
- package/src/providers/openai-responses-server.ts +6 -6
- package/src/providers/transform-messages.ts +38 -23
- package/src/stream.ts +36 -24
- package/src/utils/request-debug.ts +336 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.6.0] - 2026-05-30
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Anthropic adaptive-thinking replay preserving signed thinking blocks on the latest abandoned tool-use assistant message, avoiding `thinking blocks in the latest assistant message cannot be modified` 400s. ([#1531](https://github.com/can1357/oh-my-pi/issues/1531))
|
|
10
|
+
|
|
11
|
+
## [15.5.15] - 2026-05-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added `PI_REQ_DEBUG=1` request/response recording for provider transports. Each request writes `rr-session-N.json`; each received response writes `rr-session-N.res.log` with response headers followed by raw body bytes.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed OpenCode-Go dynamic model refresh downgrading `qwen3.7-max` from Anthropic Messages to OpenAI-compatible transport, which caused `401 Model qwen3.7-max is not supported for format oa-compat` after `/v1/models` cache refreshes.
|
|
20
|
+
|
|
5
21
|
## [15.5.12] - 2026-05-29
|
|
6
22
|
### Removed
|
|
7
23
|
|
|
@@ -91,3 +91,10 @@ export declare function hasOpus47ApiRestrictions(modelId: string): boolean;
|
|
|
91
91
|
* @see https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages
|
|
92
92
|
*/
|
|
93
93
|
export declare function supportsMidConversationSystemMessages(modelId: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Claude Opus 4.8 must emit at most one tool call per turn: the Anthropic
|
|
96
|
+
* Messages provider sends `tool_choice.disable_parallel_tool_use = true` for
|
|
97
|
+
* this model. Scoped to exactly 4.8 — earlier and later Opus versions keep
|
|
98
|
+
* Anthropic's default parallel tool-calling.
|
|
99
|
+
*/
|
|
100
|
+
export declare function disablesParallelToolUse(modelId: string): boolean;
|
|
@@ -142,8 +142,8 @@ export interface OpenCodeModelManagerConfig {
|
|
|
142
142
|
apiKey?: string;
|
|
143
143
|
baseUrl?: string;
|
|
144
144
|
}
|
|
145
|
-
export declare function opencodeZenModelManagerOptions(config?: OpenCodeModelManagerConfig): ModelManagerOptions<
|
|
146
|
-
export declare function opencodeGoModelManagerOptions(config?: OpenCodeModelManagerConfig): ModelManagerOptions<
|
|
145
|
+
export declare function opencodeZenModelManagerOptions(config?: OpenCodeModelManagerConfig): ModelManagerOptions<Api>;
|
|
146
|
+
export declare function opencodeGoModelManagerOptions(config?: OpenCodeModelManagerConfig): ModelManagerOptions<Api>;
|
|
147
147
|
export interface OllamaModelManagerConfig {
|
|
148
148
|
apiKey?: string;
|
|
149
149
|
baseUrl?: string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FetchImpl } from "../types";
|
|
2
|
+
export type RequestDebugHeaders = Headers | Record<string, string | string[] | number | undefined | null> | undefined;
|
|
3
|
+
export interface RequestDebugPayload {
|
|
4
|
+
method: string;
|
|
5
|
+
url: string;
|
|
6
|
+
headers?: RequestDebugHeaders;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
bodyText?: string;
|
|
9
|
+
bodyBase64?: string;
|
|
10
|
+
bodyUnavailable?: string;
|
|
11
|
+
protocol?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RequestDebugResponseLog {
|
|
14
|
+
write(chunk: Uint8Array | string): void;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface RequestDebugSession {
|
|
18
|
+
readonly id: number;
|
|
19
|
+
readonly requestPath: string;
|
|
20
|
+
readonly responsePath: string;
|
|
21
|
+
openResponseLog(statusLine: string, headers?: RequestDebugHeaders): Promise<RequestDebugResponseLog>;
|
|
22
|
+
wrapResponse(response: Response): Promise<Response>;
|
|
23
|
+
}
|
|
24
|
+
export declare function isRequestDebugEnabled(): boolean;
|
|
25
|
+
export declare function wrapFetchForRequestDebug(fetchImpl: FetchImpl): FetchImpl;
|
|
26
|
+
export declare function withRequestDebugFetch<T extends {
|
|
27
|
+
fetch?: FetchImpl;
|
|
28
|
+
} | undefined>(options: T): T;
|
|
29
|
+
export declare function createRequestDebugSession(payload: RequestDebugPayload): Promise<RequestDebugSession>;
|
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.
|
|
4
|
+
"version": "15.6.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"generate-models": "bun scripts/generate-models.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@anthropic-ai/sdk": "^0.
|
|
41
|
+
"@anthropic-ai/sdk": "^0.99.0",
|
|
42
42
|
"@bufbuild/protobuf": "^2.12.0",
|
|
43
|
-
"@oh-my-pi/pi-utils": "15.
|
|
44
|
-
"openai": "^6.
|
|
43
|
+
"@oh-my-pi/pi-utils": "15.6.0",
|
|
44
|
+
"openai": "^6.39.0",
|
|
45
45
|
"partial-json": "^0.1.7",
|
|
46
46
|
"zod": "4.4.3"
|
|
47
47
|
},
|
|
@@ -281,7 +281,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
281
281
|
|
|
282
282
|
async prepareForRequest(credentialId: number, opts: { signal?: AbortSignal } = {}): Promise<boolean> {
|
|
283
283
|
const entry = this.#snapshot.credentials.find(candidate => candidate.id === credentialId);
|
|
284
|
-
if (
|
|
284
|
+
if (entry?.credential.type !== "oauth" || entry.rotatesInMs === null) return false;
|
|
285
285
|
const remainingMs = this.#snapshotReceivedAt + entry.rotatesInMs - Date.now();
|
|
286
286
|
if (remainingMs > WAIT_THRESHOLD_MS) return false;
|
|
287
287
|
return this.waitForFreshSnapshot(MAX_WAIT_MS, opts);
|
package/src/auth-storage.ts
CHANGED
|
@@ -2984,7 +2984,7 @@ export class AuthStorage {
|
|
|
2984
2984
|
if (!prepare) return true;
|
|
2985
2985
|
const stored = this.#getStoredCredentials(provider);
|
|
2986
2986
|
const selected = stored[selection.index];
|
|
2987
|
-
if (
|
|
2987
|
+
if (selected?.credential.type !== "oauth") return false;
|
|
2988
2988
|
|
|
2989
2989
|
const prepared = await prepare(selected.id, { signal: options?.signal });
|
|
2990
2990
|
if (!prepared) return true;
|
|
@@ -2996,7 +2996,7 @@ export class AuthStorage {
|
|
|
2996
2996
|
const latestIndex = latestRows.findIndex(row => row.id === selected.id);
|
|
2997
2997
|
if (latestIndex === -1) return false;
|
|
2998
2998
|
const latest = latestRows[latestIndex];
|
|
2999
|
-
if (
|
|
2999
|
+
if (latest?.credential.type !== "oauth") return false;
|
|
3000
3000
|
selection.index = latestIndex;
|
|
3001
3001
|
selection.credential = latest.credential;
|
|
3002
3002
|
return true;
|
package/src/model-manager.ts
CHANGED
|
@@ -289,20 +289,22 @@ function retainModelIds<TApi extends Api>(
|
|
|
289
289
|
* arms calling `resolveProviderModels` with the same `staticModels` array)
|
|
290
290
|
* skip the JSON+hash work after the first call.
|
|
291
291
|
*/
|
|
292
|
+
const MODEL_CACHE_FINGERPRINT_VERSION = "merge-v2";
|
|
292
293
|
const kStaticFingerprint = Symbol("model-manager.staticFingerprint");
|
|
293
294
|
type ModelArrayWithFingerprint = readonly Model<Api>[] & { [kStaticFingerprint]?: string };
|
|
294
295
|
function fingerprintStatic<TApi extends Api>(
|
|
295
296
|
models: readonly Model<TApi>[],
|
|
296
297
|
dynamicModelsAuthoritative = false,
|
|
297
298
|
): string {
|
|
298
|
-
if (models.length === 0) return
|
|
299
|
-
if (dynamicModelsAuthoritative)
|
|
299
|
+
if (models.length === 0) return `${MODEL_CACHE_FINGERPRINT_VERSION}:empty`;
|
|
300
|
+
if (dynamicModelsAuthoritative)
|
|
301
|
+
return `${MODEL_CACHE_FINGERPRINT_VERSION}:authoritative:${fingerprintStatic(models)}`;
|
|
300
302
|
const tagged = models as ModelArrayWithFingerprint;
|
|
301
303
|
const cached = tagged[kStaticFingerprint];
|
|
302
304
|
if (cached !== undefined) return cached;
|
|
303
305
|
// `Bun.hash` returns a `bigint`; base36 keeps the string short for the
|
|
304
306
|
// SQLite column without sacrificing distinguishability.
|
|
305
|
-
const fingerprint = Bun.hash(JSON.stringify(models)).toString(36)
|
|
307
|
+
const fingerprint = `${MODEL_CACHE_FINGERPRINT_VERSION}:${Bun.hash(JSON.stringify(models)).toString(36)}`;
|
|
306
308
|
tagged[kStaticFingerprint] = fingerprint;
|
|
307
309
|
return fingerprint;
|
|
308
310
|
}
|
package/src/model-thinking.ts
CHANGED
|
@@ -379,6 +379,18 @@ export function supportsMidConversationSystemMessages(modelId: string): boolean
|
|
|
379
379
|
return parsed.kind === "opus" && semverGte(parsed.version, "4.8");
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Claude Opus 4.8 must emit at most one tool call per turn: the Anthropic
|
|
384
|
+
* Messages provider sends `tool_choice.disable_parallel_tool_use = true` for
|
|
385
|
+
* this model. Scoped to exactly 4.8 — earlier and later Opus versions keep
|
|
386
|
+
* Anthropic's default parallel tool-calling.
|
|
387
|
+
*/
|
|
388
|
+
export function disablesParallelToolUse(modelId: string): boolean {
|
|
389
|
+
const parsed = parseAnthropicModel(getCanonicalModelId(modelId));
|
|
390
|
+
if (!parsed) return false;
|
|
391
|
+
return parsed.kind === "opus" && semverEqual(parsed.version, "4.8");
|
|
392
|
+
}
|
|
393
|
+
|
|
382
394
|
function anthropicModelHasRealXHighEffort<TApi extends Api>(model: ApiModel<TApi>): boolean {
|
|
383
395
|
if (model.api !== "anthropic-messages") return false;
|
|
384
396
|
const parsedModel = parseKnownModel(model.id);
|
package/src/models.json
CHANGED
|
@@ -78417,9 +78417,9 @@
|
|
|
78417
78417
|
"mimo-v2-flash": {
|
|
78418
78418
|
"id": "mimo-v2-flash",
|
|
78419
78419
|
"name": "MiMo-V2-Flash",
|
|
78420
|
-
"api": "
|
|
78420
|
+
"api": "openai-completions",
|
|
78421
78421
|
"provider": "xiaomi",
|
|
78422
|
-
"baseUrl": "https://api.xiaomimimo.com/
|
|
78422
|
+
"baseUrl": "https://api.xiaomimimo.com/v1",
|
|
78423
78423
|
"reasoning": true,
|
|
78424
78424
|
"input": [
|
|
78425
78425
|
"text"
|
|
@@ -78432,18 +78432,25 @@
|
|
|
78432
78432
|
},
|
|
78433
78433
|
"contextWindow": 262144,
|
|
78434
78434
|
"maxTokens": 65536,
|
|
78435
|
+
"compat": {
|
|
78436
|
+
"supportsStore": false,
|
|
78437
|
+
"thinkingFormat": "zai",
|
|
78438
|
+
"reasoningContentField": "reasoning_content",
|
|
78439
|
+
"requiresReasoningContentForToolCalls": true,
|
|
78440
|
+
"allowsSyntheticReasoningContentForToolCalls": false
|
|
78441
|
+
},
|
|
78435
78442
|
"thinking": {
|
|
78436
|
-
"mode": "
|
|
78443
|
+
"mode": "effort",
|
|
78437
78444
|
"minLevel": "minimal",
|
|
78438
|
-
"maxLevel": "
|
|
78445
|
+
"maxLevel": "high"
|
|
78439
78446
|
}
|
|
78440
78447
|
},
|
|
78441
78448
|
"mimo-v2-omni": {
|
|
78442
78449
|
"id": "mimo-v2-omni",
|
|
78443
78450
|
"name": "MiMo-V2-Omni",
|
|
78444
|
-
"api": "
|
|
78451
|
+
"api": "openai-completions",
|
|
78445
78452
|
"provider": "xiaomi",
|
|
78446
|
-
"baseUrl": "https://api.xiaomimimo.com/
|
|
78453
|
+
"baseUrl": "https://api.xiaomimimo.com/v1",
|
|
78447
78454
|
"reasoning": true,
|
|
78448
78455
|
"input": [
|
|
78449
78456
|
"text",
|
|
@@ -78457,18 +78464,25 @@
|
|
|
78457
78464
|
},
|
|
78458
78465
|
"contextWindow": 262144,
|
|
78459
78466
|
"maxTokens": 131072,
|
|
78467
|
+
"compat": {
|
|
78468
|
+
"supportsStore": false,
|
|
78469
|
+
"thinkingFormat": "zai",
|
|
78470
|
+
"reasoningContentField": "reasoning_content",
|
|
78471
|
+
"requiresReasoningContentForToolCalls": true,
|
|
78472
|
+
"allowsSyntheticReasoningContentForToolCalls": false
|
|
78473
|
+
},
|
|
78460
78474
|
"thinking": {
|
|
78461
|
-
"mode": "
|
|
78475
|
+
"mode": "effort",
|
|
78462
78476
|
"minLevel": "minimal",
|
|
78463
|
-
"maxLevel": "
|
|
78477
|
+
"maxLevel": "high"
|
|
78464
78478
|
}
|
|
78465
78479
|
},
|
|
78466
78480
|
"mimo-v2-pro": {
|
|
78467
78481
|
"id": "mimo-v2-pro",
|
|
78468
78482
|
"name": "MiMo-V2-Pro",
|
|
78469
|
-
"api": "
|
|
78483
|
+
"api": "openai-completions",
|
|
78470
78484
|
"provider": "xiaomi",
|
|
78471
|
-
"baseUrl": "https://api.xiaomimimo.com/
|
|
78485
|
+
"baseUrl": "https://api.xiaomimimo.com/v1",
|
|
78472
78486
|
"reasoning": true,
|
|
78473
78487
|
"input": [
|
|
78474
78488
|
"text"
|
|
@@ -78481,18 +78495,25 @@
|
|
|
78481
78495
|
},
|
|
78482
78496
|
"contextWindow": 1048576,
|
|
78483
78497
|
"maxTokens": 131072,
|
|
78498
|
+
"compat": {
|
|
78499
|
+
"supportsStore": false,
|
|
78500
|
+
"thinkingFormat": "zai",
|
|
78501
|
+
"reasoningContentField": "reasoning_content",
|
|
78502
|
+
"requiresReasoningContentForToolCalls": true,
|
|
78503
|
+
"allowsSyntheticReasoningContentForToolCalls": false
|
|
78504
|
+
},
|
|
78484
78505
|
"thinking": {
|
|
78485
|
-
"mode": "
|
|
78506
|
+
"mode": "effort",
|
|
78486
78507
|
"minLevel": "minimal",
|
|
78487
|
-
"maxLevel": "
|
|
78508
|
+
"maxLevel": "high"
|
|
78488
78509
|
}
|
|
78489
78510
|
},
|
|
78490
78511
|
"mimo-v2.5": {
|
|
78491
78512
|
"id": "mimo-v2.5",
|
|
78492
78513
|
"name": "MiMo-V2.5",
|
|
78493
|
-
"api": "
|
|
78514
|
+
"api": "openai-completions",
|
|
78494
78515
|
"provider": "xiaomi",
|
|
78495
|
-
"baseUrl": "https://api.xiaomimimo.com/
|
|
78516
|
+
"baseUrl": "https://api.xiaomimimo.com/v1",
|
|
78496
78517
|
"reasoning": true,
|
|
78497
78518
|
"input": [
|
|
78498
78519
|
"text",
|
|
@@ -78506,18 +78527,25 @@
|
|
|
78506
78527
|
},
|
|
78507
78528
|
"contextWindow": 1048576,
|
|
78508
78529
|
"maxTokens": 131072,
|
|
78530
|
+
"compat": {
|
|
78531
|
+
"supportsStore": false,
|
|
78532
|
+
"thinkingFormat": "zai",
|
|
78533
|
+
"reasoningContentField": "reasoning_content",
|
|
78534
|
+
"requiresReasoningContentForToolCalls": true,
|
|
78535
|
+
"allowsSyntheticReasoningContentForToolCalls": false
|
|
78536
|
+
},
|
|
78509
78537
|
"thinking": {
|
|
78510
|
-
"mode": "
|
|
78538
|
+
"mode": "effort",
|
|
78511
78539
|
"minLevel": "minimal",
|
|
78512
|
-
"maxLevel": "
|
|
78540
|
+
"maxLevel": "high"
|
|
78513
78541
|
}
|
|
78514
78542
|
},
|
|
78515
78543
|
"mimo-v2.5-pro": {
|
|
78516
78544
|
"id": "mimo-v2.5-pro",
|
|
78517
78545
|
"name": "MiMo-V2.5-Pro",
|
|
78518
|
-
"api": "
|
|
78546
|
+
"api": "openai-completions",
|
|
78519
78547
|
"provider": "xiaomi",
|
|
78520
|
-
"baseUrl": "https://api.xiaomimimo.com/
|
|
78548
|
+
"baseUrl": "https://api.xiaomimimo.com/v1",
|
|
78521
78549
|
"reasoning": true,
|
|
78522
78550
|
"input": [
|
|
78523
78551
|
"text"
|
|
@@ -78530,10 +78558,17 @@
|
|
|
78530
78558
|
},
|
|
78531
78559
|
"contextWindow": 1048576,
|
|
78532
78560
|
"maxTokens": 131072,
|
|
78561
|
+
"compat": {
|
|
78562
|
+
"supportsStore": false,
|
|
78563
|
+
"thinkingFormat": "zai",
|
|
78564
|
+
"reasoningContentField": "reasoning_content",
|
|
78565
|
+
"requiresReasoningContentForToolCalls": true,
|
|
78566
|
+
"allowsSyntheticReasoningContentForToolCalls": false
|
|
78567
|
+
},
|
|
78533
78568
|
"thinking": {
|
|
78534
|
-
"mode": "
|
|
78569
|
+
"mode": "effort",
|
|
78535
78570
|
"minLevel": "minimal",
|
|
78536
|
-
"maxLevel": "
|
|
78571
|
+
"maxLevel": "high"
|
|
78537
78572
|
}
|
|
78538
78573
|
}
|
|
78539
78574
|
},
|
|
@@ -1218,37 +1218,62 @@ export interface OpenCodeModelManagerConfig {
|
|
|
1218
1218
|
baseUrl?: string;
|
|
1219
1219
|
}
|
|
1220
1220
|
|
|
1221
|
+
function normalizeOpenCodeBasePath(baseUrl: string | undefined, fallbackBasePath: string): string {
|
|
1222
|
+
const value = normalizeAnthropicBaseUrl(baseUrl, fallbackBasePath);
|
|
1223
|
+
return value.endsWith("/v1") ? value.slice(0, -3) : value;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function openCodeBaseUrlForApi(api: Api, basePath: string): string {
|
|
1227
|
+
return api === "anthropic-messages" ? basePath : `${basePath}/v1`;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1221
1230
|
function openCodeModelManagerOptions(
|
|
1222
1231
|
providerId: "opencode-go" | "opencode-zen",
|
|
1223
|
-
|
|
1232
|
+
defaultBasePath: string,
|
|
1224
1233
|
config?: OpenCodeModelManagerConfig,
|
|
1225
|
-
): ModelManagerOptions<
|
|
1234
|
+
): ModelManagerOptions<Api> {
|
|
1226
1235
|
const apiKey = config?.apiKey;
|
|
1227
|
-
const
|
|
1236
|
+
const basePath = normalizeOpenCodeBasePath(config?.baseUrl, defaultBasePath);
|
|
1237
|
+
const discoveryBaseUrl = openCodeBaseUrlForApi("openai-completions", basePath);
|
|
1238
|
+
const references = createBundledReferenceMap<Api>(providerId);
|
|
1228
1239
|
return {
|
|
1229
1240
|
providerId,
|
|
1230
1241
|
...(apiKey && {
|
|
1231
1242
|
fetchDynamicModels: () =>
|
|
1232
|
-
fetchOpenAICompatibleModels({
|
|
1243
|
+
fetchOpenAICompatibleModels<Api>({
|
|
1233
1244
|
api: "openai-completions",
|
|
1234
1245
|
provider: providerId,
|
|
1235
|
-
baseUrl,
|
|
1246
|
+
baseUrl: discoveryBaseUrl,
|
|
1236
1247
|
apiKey,
|
|
1248
|
+
mapModel: (entry, defaults) => {
|
|
1249
|
+
const reference = references.get(defaults.id);
|
|
1250
|
+
const name = toModelName(entry.name, reference?.name ?? defaults.name);
|
|
1251
|
+
if (!reference) {
|
|
1252
|
+
return {
|
|
1253
|
+
...defaults,
|
|
1254
|
+
name,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
...reference,
|
|
1259
|
+
id: defaults.id,
|
|
1260
|
+
name,
|
|
1261
|
+
baseUrl: openCodeBaseUrlForApi(reference.api, basePath),
|
|
1262
|
+
contextWindow: toPositiveNumber(entry.context_length, reference.contextWindow),
|
|
1263
|
+
maxTokens: toPositiveNumber(entry.max_completion_tokens, reference.maxTokens),
|
|
1264
|
+
};
|
|
1265
|
+
},
|
|
1237
1266
|
}),
|
|
1238
1267
|
}),
|
|
1239
1268
|
};
|
|
1240
1269
|
}
|
|
1241
1270
|
|
|
1242
|
-
export function opencodeZenModelManagerOptions(
|
|
1243
|
-
|
|
1244
|
-
): ModelManagerOptions<"openai-completions"> {
|
|
1245
|
-
return openCodeModelManagerOptions("opencode-zen", "https://opencode.ai/zen/v1", config);
|
|
1271
|
+
export function opencodeZenModelManagerOptions(config?: OpenCodeModelManagerConfig): ModelManagerOptions<Api> {
|
|
1272
|
+
return openCodeModelManagerOptions("opencode-zen", "https://opencode.ai/zen", config);
|
|
1246
1273
|
}
|
|
1247
1274
|
|
|
1248
|
-
export function opencodeGoModelManagerOptions(
|
|
1249
|
-
|
|
1250
|
-
): ModelManagerOptions<"openai-completions"> {
|
|
1251
|
-
return openCodeModelManagerOptions("opencode-go", "https://opencode.ai/zen/go/v1", config);
|
|
1275
|
+
export function opencodeGoModelManagerOptions(config?: OpenCodeModelManagerConfig): ModelManagerOptions<Api> {
|
|
1276
|
+
return openCodeModelManagerOptions("opencode-go", "https://opencode.ai/zen/go", config);
|
|
1252
1277
|
}
|
|
1253
1278
|
|
|
1254
1279
|
// ---------------------------------------------------------------------------
|
|
@@ -2122,25 +2147,23 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
2122
2147
|
const reference = resolveReference(defaults.id);
|
|
2123
2148
|
const copilotLimits = extractCopilotLimits(entry);
|
|
2124
2149
|
// Copilot exposes token limits under capabilities.limits.*.
|
|
2125
|
-
//
|
|
2126
|
-
//
|
|
2127
|
-
//
|
|
2128
|
-
// breaks compaction thresholds, overflow detection, and promotion.
|
|
2129
|
-
// The OpenAI-compatible root-level `context_length` field mirrors the
|
|
2130
|
-
// total window (e.g. 400k for gpt-5.4), so Copilot's max_prompt_tokens
|
|
2131
|
-
// (the true prompt budget) must take precedence whenever it is present.
|
|
2132
|
-
const contextWindowFallback = toPositiveNumber(
|
|
2133
|
-
entry.context_length,
|
|
2134
|
-
reference?.contextWindow ?? defaults.contextWindow,
|
|
2135
|
-
);
|
|
2150
|
+
// max_context_window_tokens is the model's total usable window;
|
|
2151
|
+
// max_prompt_tokens is Copilot's prompt/summarization budget and
|
|
2152
|
+
// must only be a fallback when total-window fields are absent.
|
|
2136
2153
|
const contextWindow = toPositiveNumber(
|
|
2137
|
-
copilotLimits.
|
|
2138
|
-
|
|
2154
|
+
copilotLimits.maxContextWindowTokens,
|
|
2155
|
+
toPositiveNumber(
|
|
2156
|
+
entry.context_length,
|
|
2157
|
+
toPositiveNumber(
|
|
2158
|
+
copilotLimits.maxPromptTokens,
|
|
2159
|
+
reference?.contextWindow ?? defaults.contextWindow,
|
|
2160
|
+
),
|
|
2161
|
+
),
|
|
2139
2162
|
);
|
|
2140
2163
|
const maxTokens = toPositiveNumber(
|
|
2141
|
-
|
|
2164
|
+
copilotLimits.maxOutputTokens,
|
|
2142
2165
|
toPositiveNumber(
|
|
2143
|
-
|
|
2166
|
+
entry.max_completion_tokens,
|
|
2144
2167
|
toPositiveNumber(
|
|
2145
2168
|
copilotLimits.maxNonStreamingOutputTokens,
|
|
2146
2169
|
reference?.maxTokens ?? defaults.maxTokens,
|
|
@@ -2643,9 +2666,16 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDe
|
|
|
2643
2666
|
// --- zAI ---
|
|
2644
2667
|
anthropicMessagesDescriptor("zai-coding-plan", "zai", "https://api.z.ai/api/anthropic"),
|
|
2645
2668
|
// --- Xiaomi ---
|
|
2646
|
-
|
|
2669
|
+
openAiCompletionsDescriptor("xiaomi", "xiaomi", "https://api.xiaomimimo.com/v1", {
|
|
2647
2670
|
defaultContextWindow: 262144,
|
|
2648
2671
|
defaultMaxTokens: 8192,
|
|
2672
|
+
compat: {
|
|
2673
|
+
supportsStore: false,
|
|
2674
|
+
thinkingFormat: "zai",
|
|
2675
|
+
reasoningContentField: "reasoning_content",
|
|
2676
|
+
requiresReasoningContentForToolCalls: true,
|
|
2677
|
+
allowsSyntheticReasoningContentForToolCalls: false,
|
|
2678
|
+
},
|
|
2649
2679
|
}),
|
|
2650
2680
|
// --- MiniMax Coding Plan ---
|
|
2651
2681
|
openAiCompletionsDescriptor("minimax-coding-plan", "minimax-code", "https://api.minimax.io/v1", {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
readSseEvents,
|
|
23
23
|
} from "@oh-my-pi/pi-utils";
|
|
24
24
|
import {
|
|
25
|
+
disablesParallelToolUse,
|
|
25
26
|
hasOpus47ApiRestrictions,
|
|
26
27
|
mapEffortToAnthropicAdaptiveEffort,
|
|
27
28
|
supportsMidConversationSystemMessages,
|
|
@@ -654,17 +655,6 @@ function convertContentBlocks(
|
|
|
654
655
|
return blocks;
|
|
655
656
|
}
|
|
656
657
|
|
|
657
|
-
/**
|
|
658
|
-
* Marker phrase that Claude has been observed to hallucinate inside reasoning summaries
|
|
659
|
-
* (e.g. "I don't see any current rewritten thinking or next thinking to process. Could
|
|
660
|
-
* you provide..."). When this substring appears in a streamed thinking block we collapse
|
|
661
|
-
* the entire block to {@link BROKEN_THINKING_REPLACEMENT} and drop the signature so
|
|
662
|
-
* downstream UI/transcripts don't surface the meta-prompt and replay can't re-anchor on
|
|
663
|
-
* the garbled chain.
|
|
664
|
-
*/
|
|
665
|
-
const BROKEN_THINKING_MARKER = "rewritten thinking";
|
|
666
|
-
const BROKEN_THINKING_REPLACEMENT = "Thinking...";
|
|
667
|
-
|
|
668
658
|
export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
669
659
|
export type AnthropicThinkingDisplay = "summarized" | "omitted";
|
|
670
660
|
|
|
@@ -1221,12 +1211,6 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1221
1211
|
const requestTimeoutMs =
|
|
1222
1212
|
firstEventTimeoutMs !== undefined && firstEventTimeoutMs > 0 ? firstEventTimeoutMs : undefined;
|
|
1223
1213
|
const blocks = output.content as Block[];
|
|
1224
|
-
// Recent Claude releases occasionally hallucinate meta-prompts asking the operator
|
|
1225
|
-
// to supply "rewritten thinking" / "next thinking" as reasoning content. The summary
|
|
1226
|
-
// is useless and confuses the UI, so we collapse any thinking block whose stream
|
|
1227
|
-
// contains the marker phrase down to a plain "Thinking..." placeholder and drop the
|
|
1228
|
-
// (now invalid) signature so subsequent turns don't replay the garbled chain.
|
|
1229
|
-
const suppressedThinkingBlocks = new WeakSet<Block>();
|
|
1230
1214
|
stream.push({ type: "start", partial: output });
|
|
1231
1215
|
// Retry loop for transient errors from the stream.
|
|
1232
1216
|
// Provider-level transport/rate-limit failures: only before any streamed content starts.
|
|
@@ -1381,14 +1365,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1381
1365
|
const index = blocks.findIndex(b => b.index === event.index);
|
|
1382
1366
|
const block = blocks[index];
|
|
1383
1367
|
if (block && block.type === "thinking") {
|
|
1384
|
-
if (suppressedThinkingBlocks.has(block)) continue;
|
|
1385
1368
|
block.thinking += event.delta.thinking;
|
|
1386
|
-
if (block.thinking.includes(BROKEN_THINKING_MARKER)) {
|
|
1387
|
-
suppressedThinkingBlocks.add(block);
|
|
1388
|
-
block.thinking = BROKEN_THINKING_REPLACEMENT;
|
|
1389
|
-
block.thinkingSignature = "";
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
1369
|
stream.push({
|
|
1393
1370
|
type: "thinking_delta",
|
|
1394
1371
|
contentIndex: index,
|
|
@@ -1412,7 +1389,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1412
1389
|
} else if (event.delta.type === "signature_delta") {
|
|
1413
1390
|
const index = blocks.findIndex(b => b.index === event.index);
|
|
1414
1391
|
const block = blocks[index];
|
|
1415
|
-
if (block && block.type === "thinking"
|
|
1392
|
+
if (block && block.type === "thinking") {
|
|
1416
1393
|
block.thinkingSignature = block.thinkingSignature || "";
|
|
1417
1394
|
block.thinkingSignature += event.delta.signature;
|
|
1418
1395
|
}
|
|
@@ -1430,14 +1407,6 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1430
1407
|
partial: output,
|
|
1431
1408
|
});
|
|
1432
1409
|
} else if (block.type === "thinking") {
|
|
1433
|
-
if (
|
|
1434
|
-
!suppressedThinkingBlocks.has(block) &&
|
|
1435
|
-
block.thinking.includes(BROKEN_THINKING_MARKER)
|
|
1436
|
-
) {
|
|
1437
|
-
suppressedThinkingBlocks.add(block);
|
|
1438
|
-
block.thinking = BROKEN_THINKING_REPLACEMENT;
|
|
1439
|
-
block.thinkingSignature = "";
|
|
1440
|
-
}
|
|
1441
1410
|
stream.push({
|
|
1442
1411
|
type: "thinking_end",
|
|
1443
1412
|
contentIndex: index,
|
|
@@ -1806,7 +1775,7 @@ function disableThinkingIfToolChoiceForced(params: MessageCreateParamsStreaming)
|
|
|
1806
1775
|
|
|
1807
1776
|
function ensureMaxTokensForThinking(params: MessageCreateParamsStreaming, model: Model<"anthropic-messages">): void {
|
|
1808
1777
|
const thinking = params.thinking;
|
|
1809
|
-
if (
|
|
1778
|
+
if (thinking?.type !== "enabled") return;
|
|
1810
1779
|
|
|
1811
1780
|
const budgetTokens = thinking.budget_tokens ?? 0;
|
|
1812
1781
|
if (budgetTokens <= 0) return;
|
|
@@ -2165,6 +2134,21 @@ function buildParams(
|
|
|
2165
2134
|
}
|
|
2166
2135
|
}
|
|
2167
2136
|
|
|
2137
|
+
// Claude Opus 4.8 must emit at most one tool call per turn. Force
|
|
2138
|
+
// `disable_parallel_tool_use` onto the outgoing tool_choice (synthesizing an
|
|
2139
|
+
// `auto` choice when none is set). Gated on tools being present: Anthropic
|
|
2140
|
+
// rejects `tool_choice` without `tools`, and parallelism is moot otherwise.
|
|
2141
|
+
// `none` rejects the field, so leave it untouched. A fresh object is built
|
|
2142
|
+
// rather than mutated so the caller's `options.toolChoice` is never aliased.
|
|
2143
|
+
if (disablesParallelToolUse(model.id) && params.tools && params.tools.length > 0) {
|
|
2144
|
+
const current = params.tool_choice;
|
|
2145
|
+
if (!current) {
|
|
2146
|
+
params.tool_choice = { type: "auto", disable_parallel_tool_use: true };
|
|
2147
|
+
} else if (current.type !== "none") {
|
|
2148
|
+
params.tool_choice = { ...current, disable_parallel_tool_use: true };
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2168
2152
|
const shouldInjectClaudeCodeInstruction = isOAuthToken && !model.id.startsWith("claude-3-5-haiku");
|
|
2169
2153
|
const billingSystemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
|
2170
2154
|
const billingPayload = shouldInjectClaudeCodeInstruction
|