@oh-my-pi/pi-ai 15.5.15 → 15.7.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 +6 -0
- package/dist/types/model-thinking.d.ts +7 -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-thinking.ts +12 -0
- package/src/provider-models/openai-compat.ts +13 -15
- package/src/providers/anthropic.ts +17 -1
- package/src/providers/openai-completions.ts +3 -4
- package/src/providers/openai-responses-server.ts +6 -6
- package/src/providers/transform-messages.ts +38 -23
- package/src/utils/oauth/index.ts +99 -93
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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
|
+
|
|
5
11
|
## [15.5.15] - 2026-05-30
|
|
6
12
|
|
|
7
13
|
### Added
|
|
@@ -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;
|
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.7.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.7.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-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);
|
|
@@ -2147,25 +2147,23 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
2147
2147
|
const reference = resolveReference(defaults.id);
|
|
2148
2148
|
const copilotLimits = extractCopilotLimits(entry);
|
|
2149
2149
|
// Copilot exposes token limits under capabilities.limits.*.
|
|
2150
|
-
//
|
|
2151
|
-
//
|
|
2152
|
-
//
|
|
2153
|
-
// breaks compaction thresholds, overflow detection, and promotion.
|
|
2154
|
-
// The OpenAI-compatible root-level `context_length` field mirrors the
|
|
2155
|
-
// total window (e.g. 400k for gpt-5.4), so Copilot's max_prompt_tokens
|
|
2156
|
-
// (the true prompt budget) must take precedence whenever it is present.
|
|
2157
|
-
const contextWindowFallback = toPositiveNumber(
|
|
2158
|
-
entry.context_length,
|
|
2159
|
-
reference?.contextWindow ?? defaults.contextWindow,
|
|
2160
|
-
);
|
|
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.
|
|
2161
2153
|
const contextWindow = toPositiveNumber(
|
|
2162
|
-
copilotLimits.
|
|
2163
|
-
|
|
2154
|
+
copilotLimits.maxContextWindowTokens,
|
|
2155
|
+
toPositiveNumber(
|
|
2156
|
+
entry.context_length,
|
|
2157
|
+
toPositiveNumber(
|
|
2158
|
+
copilotLimits.maxPromptTokens,
|
|
2159
|
+
reference?.contextWindow ?? defaults.contextWindow,
|
|
2160
|
+
),
|
|
2161
|
+
),
|
|
2164
2162
|
);
|
|
2165
2163
|
const maxTokens = toPositiveNumber(
|
|
2166
|
-
|
|
2164
|
+
copilotLimits.maxOutputTokens,
|
|
2167
2165
|
toPositiveNumber(
|
|
2168
|
-
|
|
2166
|
+
entry.max_completion_tokens,
|
|
2169
2167
|
toPositiveNumber(
|
|
2170
2168
|
copilotLimits.maxNonStreamingOutputTokens,
|
|
2171
2169
|
reference?.maxTokens ?? defaults.maxTokens,
|
|
@@ -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,
|
|
@@ -1774,7 +1775,7 @@ function disableThinkingIfToolChoiceForced(params: MessageCreateParamsStreaming)
|
|
|
1774
1775
|
|
|
1775
1776
|
function ensureMaxTokensForThinking(params: MessageCreateParamsStreaming, model: Model<"anthropic-messages">): void {
|
|
1776
1777
|
const thinking = params.thinking;
|
|
1777
|
-
if (
|
|
1778
|
+
if (thinking?.type !== "enabled") return;
|
|
1778
1779
|
|
|
1779
1780
|
const budgetTokens = thinking.budget_tokens ?? 0;
|
|
1780
1781
|
if (budgetTokens <= 0) return;
|
|
@@ -2133,6 +2134,21 @@ function buildParams(
|
|
|
2133
2134
|
}
|
|
2134
2135
|
}
|
|
2135
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
|
+
|
|
2136
2152
|
const shouldInjectClaudeCodeInstruction = isOAuthToken && !model.id.startsWith("claude-3-5-haiku");
|
|
2137
2153
|
const billingSystemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
|
2138
2154
|
const billingPayload = shouldInjectClaudeCodeInstruction
|
|
@@ -586,7 +586,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
586
586
|
eventStream: AssistantMessageEventStream,
|
|
587
587
|
text: string,
|
|
588
588
|
): void => {
|
|
589
|
-
if (
|
|
589
|
+
if (currentBlock?.type !== "text") {
|
|
590
590
|
finishCurrentBlock(currentBlock);
|
|
591
591
|
currentBlock = { type: "text", text: "" };
|
|
592
592
|
message.content.push(currentBlock);
|
|
@@ -607,8 +607,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
607
607
|
signature?: string,
|
|
608
608
|
): void => {
|
|
609
609
|
if (
|
|
610
|
-
|
|
611
|
-
currentBlock.type !== "thinking" ||
|
|
610
|
+
currentBlock?.type !== "thinking" ||
|
|
612
611
|
(signature !== undefined && currentBlock.thinkingSignature !== signature)
|
|
613
612
|
) {
|
|
614
613
|
finishCurrentBlock(currentBlock);
|
|
@@ -815,7 +814,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
815
814
|
}
|
|
816
815
|
|
|
817
816
|
if (!block) {
|
|
818
|
-
if (
|
|
817
|
+
if (currentBlock?.type !== "toolCall") {
|
|
819
818
|
finishCurrentBlock(currentBlock);
|
|
820
819
|
}
|
|
821
820
|
block = {
|
|
@@ -954,7 +954,7 @@ export function encodeStream(
|
|
|
954
954
|
break;
|
|
955
955
|
}
|
|
956
956
|
case "text_delta": {
|
|
957
|
-
if (
|
|
957
|
+
if (state.open?.kind !== "message") break;
|
|
958
958
|
const cur: OpenMessage = state.open;
|
|
959
959
|
cur.currentPartText += ev.delta;
|
|
960
960
|
emit("response.output_text.delta", {
|
|
@@ -970,7 +970,7 @@ export function encodeStream(
|
|
|
970
970
|
break;
|
|
971
971
|
}
|
|
972
972
|
case "text_end": {
|
|
973
|
-
if (
|
|
973
|
+
if (state.open?.kind !== "message") break;
|
|
974
974
|
const cur: OpenMessage = state.open;
|
|
975
975
|
const text = ev.content ?? cur.currentPartText;
|
|
976
976
|
emit("response.output_text.done", {
|
|
@@ -997,7 +997,7 @@ export function encodeStream(
|
|
|
997
997
|
break;
|
|
998
998
|
}
|
|
999
999
|
case "thinking_delta": {
|
|
1000
|
-
if (
|
|
1000
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1001
1001
|
const cur: OpenReasoning = state.open;
|
|
1002
1002
|
cur.reasoningText += ev.delta;
|
|
1003
1003
|
emit("response.reasoning_summary_text.delta", {
|
|
@@ -1009,7 +1009,7 @@ export function encodeStream(
|
|
|
1009
1009
|
break;
|
|
1010
1010
|
}
|
|
1011
1011
|
case "thinking_end": {
|
|
1012
|
-
if (
|
|
1012
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1013
1013
|
const cur: OpenReasoning = state.open;
|
|
1014
1014
|
const text = ev.content ?? cur.reasoningText;
|
|
1015
1015
|
cur.reasoningText = text;
|
|
@@ -1034,7 +1034,7 @@ export function encodeStream(
|
|
|
1034
1034
|
break;
|
|
1035
1035
|
}
|
|
1036
1036
|
case "toolcall_delta": {
|
|
1037
|
-
if (
|
|
1037
|
+
if (state.open?.kind !== "function_call") break;
|
|
1038
1038
|
const cur: OpenFunctionCall = state.open;
|
|
1039
1039
|
cur.argsText += ev.delta;
|
|
1040
1040
|
if (cur.customWireName) {
|
|
@@ -1053,7 +1053,7 @@ export function encodeStream(
|
|
|
1053
1053
|
break;
|
|
1054
1054
|
}
|
|
1055
1055
|
case "toolcall_end": {
|
|
1056
|
-
if (
|
|
1056
|
+
if (state.open?.kind !== "function_call") break;
|
|
1057
1057
|
const cur: OpenFunctionCall = state.open;
|
|
1058
1058
|
// Promote possibly-late info from the canonical ToolCall.
|
|
1059
1059
|
const tc = ev.toolCall;
|
|
@@ -17,6 +17,21 @@ const enum ToolCallStatus {
|
|
|
17
17
|
Aborted = 2,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function shouldDropTruncatedThinkingOnlyAssistant(msg: AssistantMessage): boolean {
|
|
21
|
+
const isTruncatedStop = msg.stopReason === "length" || msg.stopReason === "error" || msg.stopReason === "aborted";
|
|
22
|
+
return isTruncatedStop && !msg.content.some(block => block.type === "toolCall" || block.type === "text");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getLatestSurvivingAssistantIndex(messages: readonly Message[]): number {
|
|
26
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
27
|
+
const msg = messages[index]!;
|
|
28
|
+
if (msg.role === "assistant" && !shouldDropTruncatedThinkingOnlyAssistant(msg)) {
|
|
29
|
+
return index;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
/**
|
|
21
36
|
* Normalize tool call ID for cross-provider compatibility.
|
|
22
37
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
|
@@ -35,7 +50,7 @@ export function transformMessages<TApi extends Api>(
|
|
|
35
50
|
// Build a map of original tool call IDs to normalized IDs
|
|
36
51
|
const toolCallIdMap = new Map<string, string>();
|
|
37
52
|
|
|
38
|
-
const
|
|
53
|
+
const latestSurvivingAssistantIndex = getLatestSurvivingAssistantIndex(messages);
|
|
39
54
|
// First pass: transform messages (thinking blocks, tool call ID normalization)
|
|
40
55
|
const transformed = messages.map((msg, index) => {
|
|
41
56
|
// User and developer messages pass through unchanged
|
|
@@ -61,34 +76,36 @@ export function transformMessages<TApi extends Api>(
|
|
|
61
76
|
assistantMsg.model === model.id;
|
|
62
77
|
|
|
63
78
|
const mustPreserveLatestAnthropicThinking =
|
|
64
|
-
index ===
|
|
79
|
+
index === latestSurvivingAssistantIndex &&
|
|
65
80
|
model.api === "anthropic-messages" &&
|
|
66
81
|
assistantMsg.api === "anthropic-messages";
|
|
67
82
|
// Aborted/errored messages may have partially-streamed thinking signatures.
|
|
68
83
|
// A partial signature is invalid and will be rejected by the API, so we must
|
|
69
84
|
// strip signatures from thinking blocks in these messages.
|
|
70
85
|
//
|
|
71
|
-
// Abandoned tool-use turns get the same treatment
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
86
|
+
// Abandoned tool-use turns get the same treatment once they are no longer
|
|
87
|
+
// the latest assistant message. When a turn carries toolCall blocks but did
|
|
88
|
+
// NOT request tool execution (stopReason !== "toolUse" — e.g.
|
|
89
|
+
// adaptive-thinking Opus emitting tool calls and then ending the turn on
|
|
90
|
+
// `end_turn`/`stop`), the agent loop pairs those calls with placeholder
|
|
91
|
+
// tool_results to keep the tool_use/tool_result contract valid. Historical
|
|
92
|
+
// abandoned turns cannot safely replay their end_turn-bound signatures in
|
|
93
|
+
// that continuation, so stripping downgrades them to plain text downstream.
|
|
94
|
+
// Latest abandoned turns are exempt because Anthropic requires thinking
|
|
95
|
+
// blocks from its most recent response to remain byte-for-byte unmodified.
|
|
96
|
+
const invalidStopReason = assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error";
|
|
81
97
|
const abandonedToolUse =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
assistantMsg.
|
|
98
|
+
!invalidStopReason &&
|
|
99
|
+
assistantMsg.stopReason !== "toolUse" &&
|
|
100
|
+
assistantMsg.content.some(b => b.type === "toolCall");
|
|
101
|
+
const hasInvalidSignatures = invalidStopReason || abandonedToolUse;
|
|
85
102
|
|
|
86
103
|
const transformedContent = assistantMsg.content.flatMap(block => {
|
|
87
104
|
if (block.type === "thinking") {
|
|
88
|
-
// Strip
|
|
105
|
+
// Strip untrustworthy signatures so the encoder can downgrade to text.
|
|
89
106
|
const sanitized =
|
|
90
107
|
hasInvalidSignatures && block.thinkingSignature ? { ...block, thinkingSignature: undefined } : block;
|
|
91
|
-
if (mustPreserveLatestAnthropicThinking) return sanitized;
|
|
108
|
+
if (mustPreserveLatestAnthropicThinking) return abandonedToolUse ? block : sanitized;
|
|
92
109
|
// For same model: keep thinking blocks with signatures (needed for replay)
|
|
93
110
|
// even if the thinking text is empty (OpenAI encrypted reasoning)
|
|
94
111
|
if (isSameModel && sanitized.thinkingSignature) return sanitized;
|
|
@@ -236,7 +253,6 @@ export function transformMessages<TApi extends Api>(
|
|
|
236
253
|
flushPendingAbortedToolCalls();
|
|
237
254
|
|
|
238
255
|
const assistantMsg = msg as AssistantMessage;
|
|
239
|
-
const toolCalls = assistantMsg.content.filter(b => b.type === "toolCall") as ToolCall[];
|
|
240
256
|
|
|
241
257
|
// Drop assistant turns that carry no actionable content (no `text`, no `toolCall`)
|
|
242
258
|
// AND were terminated by a truncating stop reason (`length` / `error` / `aborted`).
|
|
@@ -250,11 +266,8 @@ export function transformMessages<TApi extends Api>(
|
|
|
250
266
|
// `stopReason: "stop"` thinking-only messages are intentionally preserved: they
|
|
251
267
|
// represent reasoning-only assistant turns used for replay round-trips
|
|
252
268
|
// (OpenAI completions `reasoning_text`, Google signed thought parts).
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
assistantMsg.stopReason === "error" ||
|
|
256
|
-
assistantMsg.stopReason === "aborted";
|
|
257
|
-
if (isTruncatedStop && toolCalls.length === 0 && !assistantMsg.content.some(b => b.type === "text")) {
|
|
269
|
+
const originalMsg = messages[i]!;
|
|
270
|
+
if (originalMsg.role === "assistant" && shouldDropTruncatedThinkingOnlyAssistant(originalMsg)) {
|
|
258
271
|
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
|
|
259
272
|
// Still arm the aborted-turn note so downstream guidance fires.
|
|
260
273
|
pendingAbortedToolCalls = new Map();
|
|
@@ -263,6 +276,8 @@ export function transformMessages<TApi extends Api>(
|
|
|
263
276
|
continue;
|
|
264
277
|
}
|
|
265
278
|
|
|
279
|
+
const toolCalls = assistantMsg.content.filter(b => b.type === "toolCall") as ToolCall[];
|
|
280
|
+
|
|
266
281
|
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
|
|
267
282
|
// Keep the assistant message with tool calls intact. Real tool results are
|
|
268
283
|
// emitted immediately if available; otherwise synthesize aborted results
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -10,119 +10,127 @@ import type {
|
|
|
10
10
|
} from "./types";
|
|
11
11
|
|
|
12
12
|
const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
13
|
+
// Most popular coding subscriptions / gateways.
|
|
14
|
+
{
|
|
15
|
+
id: "openai-codex",
|
|
16
|
+
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
17
|
+
available: true,
|
|
18
|
+
},
|
|
13
19
|
{
|
|
14
20
|
id: "anthropic",
|
|
15
21
|
name: "Anthropic (Claude Pro/Max)",
|
|
16
22
|
available: true,
|
|
17
23
|
},
|
|
18
24
|
{
|
|
19
|
-
id: "
|
|
20
|
-
name: "
|
|
25
|
+
id: "zai",
|
|
26
|
+
name: "Z.AI (GLM Coding Plan)",
|
|
21
27
|
available: true,
|
|
22
28
|
},
|
|
23
29
|
{
|
|
24
|
-
id: "
|
|
25
|
-
name: "
|
|
30
|
+
id: "kimi-code",
|
|
31
|
+
name: "Kimi Code",
|
|
26
32
|
available: true,
|
|
27
33
|
},
|
|
28
34
|
{
|
|
29
|
-
id: "
|
|
30
|
-
name: "
|
|
35
|
+
id: "openrouter",
|
|
36
|
+
name: "OpenRouter",
|
|
31
37
|
available: true,
|
|
32
38
|
},
|
|
39
|
+
// Other coding subscriptions & first-party assistants.
|
|
33
40
|
{
|
|
34
|
-
id: "
|
|
35
|
-
name: "
|
|
41
|
+
id: "github-copilot",
|
|
42
|
+
name: "GitHub Copilot",
|
|
36
43
|
available: true,
|
|
37
44
|
},
|
|
38
45
|
{
|
|
39
|
-
id: "
|
|
40
|
-
name: "
|
|
46
|
+
id: "cursor",
|
|
47
|
+
name: "Cursor (Claude, GPT, etc.)",
|
|
41
48
|
available: true,
|
|
42
49
|
},
|
|
43
50
|
{
|
|
44
|
-
id: "
|
|
45
|
-
name: "
|
|
51
|
+
id: "google-antigravity",
|
|
52
|
+
name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
|
|
46
53
|
available: true,
|
|
47
54
|
},
|
|
48
55
|
{
|
|
49
|
-
id: "
|
|
50
|
-
name: "
|
|
56
|
+
id: "google-gemini-cli",
|
|
57
|
+
name: "Google Cloud Code Assist (Gemini CLI)",
|
|
51
58
|
available: true,
|
|
52
59
|
},
|
|
53
60
|
{
|
|
54
|
-
id: "
|
|
55
|
-
name: "
|
|
61
|
+
id: "openai-codex-device",
|
|
62
|
+
name: "ChatGPT Plus/Pro (Codex, headless/device)",
|
|
56
63
|
available: true,
|
|
57
64
|
},
|
|
58
65
|
{
|
|
59
|
-
id: "
|
|
60
|
-
name: "
|
|
66
|
+
id: "xai-oauth",
|
|
67
|
+
name: "xAI Grok OAuth (SuperGrok Subscription)",
|
|
61
68
|
available: true,
|
|
62
69
|
},
|
|
63
70
|
{
|
|
64
|
-
id: "
|
|
65
|
-
name: "
|
|
71
|
+
id: "gitlab-duo",
|
|
72
|
+
name: "GitLab Duo",
|
|
66
73
|
available: true,
|
|
67
74
|
},
|
|
68
75
|
{
|
|
69
|
-
id: "
|
|
70
|
-
name: "
|
|
76
|
+
id: "alibaba-coding-plan",
|
|
77
|
+
name: "Alibaba Coding Plan",
|
|
71
78
|
available: true,
|
|
72
79
|
},
|
|
73
80
|
{
|
|
74
|
-
id: "
|
|
75
|
-
name: "
|
|
81
|
+
id: "zhipu-coding-plan",
|
|
82
|
+
name: "Zhipu Coding Plan (智谱)",
|
|
76
83
|
available: true,
|
|
77
84
|
},
|
|
78
85
|
{
|
|
79
|
-
id: "
|
|
80
|
-
name: "
|
|
86
|
+
id: "qwen-portal",
|
|
87
|
+
name: "Qwen Portal",
|
|
81
88
|
available: true,
|
|
82
89
|
},
|
|
83
90
|
{
|
|
84
|
-
id: "
|
|
85
|
-
name: "
|
|
91
|
+
id: "minimax-code",
|
|
92
|
+
name: "MiniMax Coding Plan (International)",
|
|
86
93
|
available: true,
|
|
87
94
|
},
|
|
88
95
|
{
|
|
89
|
-
id: "
|
|
90
|
-
name: "
|
|
96
|
+
id: "minimax-code-cn",
|
|
97
|
+
name: "MiniMax Coding Plan (China)",
|
|
91
98
|
available: true,
|
|
92
99
|
},
|
|
93
100
|
{
|
|
94
|
-
id: "
|
|
95
|
-
name: "
|
|
101
|
+
id: "xiaomi",
|
|
102
|
+
name: "Xiaomi MiMo",
|
|
96
103
|
available: true,
|
|
97
104
|
},
|
|
98
105
|
{
|
|
99
|
-
id: "
|
|
100
|
-
name: "
|
|
106
|
+
id: "firepass",
|
|
107
|
+
name: "Fire Pass (Fireworks Kimi K2.6 Turbo subscription)",
|
|
101
108
|
available: true,
|
|
102
109
|
},
|
|
103
110
|
{
|
|
104
|
-
id: "
|
|
105
|
-
name: "
|
|
111
|
+
id: "wafer-pass",
|
|
112
|
+
name: "Wafer Pass (flat-rate subscription)",
|
|
106
113
|
available: true,
|
|
107
114
|
},
|
|
115
|
+
// Direct model-provider APIs (pay-as-you-go inference).
|
|
108
116
|
{
|
|
109
|
-
id: "
|
|
110
|
-
name: "
|
|
117
|
+
id: "deepseek",
|
|
118
|
+
name: "DeepSeek",
|
|
111
119
|
available: true,
|
|
112
120
|
},
|
|
113
121
|
{
|
|
114
|
-
id: "
|
|
115
|
-
name: "
|
|
122
|
+
id: "moonshot",
|
|
123
|
+
name: "Moonshot (Kimi API)",
|
|
116
124
|
available: true,
|
|
117
125
|
},
|
|
118
126
|
{
|
|
119
|
-
id: "
|
|
120
|
-
name: "
|
|
127
|
+
id: "cerebras",
|
|
128
|
+
name: "Cerebras",
|
|
121
129
|
available: true,
|
|
122
130
|
},
|
|
123
131
|
{
|
|
124
|
-
id: "
|
|
125
|
-
name: "
|
|
132
|
+
id: "fireworks",
|
|
133
|
+
name: "Fireworks",
|
|
126
134
|
available: true,
|
|
127
135
|
},
|
|
128
136
|
{
|
|
@@ -131,118 +139,116 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
|
131
139
|
available: true,
|
|
132
140
|
},
|
|
133
141
|
{
|
|
134
|
-
id: "
|
|
135
|
-
name: "
|
|
136
|
-
available: true,
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
id: "opencode-zen",
|
|
140
|
-
name: "OpenCode Zen",
|
|
142
|
+
id: "nvidia",
|
|
143
|
+
name: "NVIDIA",
|
|
141
144
|
available: true,
|
|
142
145
|
},
|
|
143
146
|
{
|
|
144
|
-
id: "
|
|
145
|
-
name: "
|
|
147
|
+
id: "huggingface",
|
|
148
|
+
name: "Hugging Face Inference",
|
|
146
149
|
available: true,
|
|
147
150
|
},
|
|
148
151
|
{
|
|
149
|
-
id: "
|
|
150
|
-
name: "
|
|
152
|
+
id: "perplexity",
|
|
153
|
+
name: "Perplexity (Pro/Max)",
|
|
151
154
|
available: true,
|
|
152
155
|
},
|
|
153
156
|
{
|
|
154
|
-
id: "
|
|
155
|
-
name: "
|
|
157
|
+
id: "qianfan",
|
|
158
|
+
name: "Qianfan",
|
|
156
159
|
available: true,
|
|
157
160
|
},
|
|
158
161
|
{
|
|
159
|
-
id: "
|
|
160
|
-
name: "
|
|
162
|
+
id: "venice",
|
|
163
|
+
name: "Venice",
|
|
161
164
|
available: true,
|
|
162
165
|
},
|
|
163
166
|
{
|
|
164
|
-
id: "
|
|
165
|
-
name: "
|
|
167
|
+
id: "synthetic",
|
|
168
|
+
name: "Synthetic",
|
|
166
169
|
available: true,
|
|
167
170
|
},
|
|
168
171
|
{
|
|
169
|
-
id: "
|
|
170
|
-
name: "
|
|
172
|
+
id: "nanogpt",
|
|
173
|
+
name: "NanoGPT",
|
|
171
174
|
available: true,
|
|
172
175
|
},
|
|
173
176
|
{
|
|
174
|
-
id: "
|
|
175
|
-
name: "
|
|
177
|
+
id: "wafer-serverless",
|
|
178
|
+
name: "Wafer Serverless (pay-as-you-go)",
|
|
176
179
|
available: true,
|
|
177
180
|
},
|
|
181
|
+
// Aggregator gateways / routers.
|
|
178
182
|
{
|
|
179
|
-
id: "
|
|
180
|
-
name: "
|
|
183
|
+
id: "vercel-ai-gateway",
|
|
184
|
+
name: "Vercel AI Gateway",
|
|
181
185
|
available: true,
|
|
182
186
|
},
|
|
183
187
|
{
|
|
184
|
-
id: "
|
|
185
|
-
name: "
|
|
188
|
+
id: "cloudflare-ai-gateway",
|
|
189
|
+
name: "Cloudflare AI Gateway",
|
|
186
190
|
available: true,
|
|
187
191
|
},
|
|
188
192
|
{
|
|
189
|
-
id: "
|
|
190
|
-
name: "
|
|
193
|
+
id: "litellm",
|
|
194
|
+
name: "LiteLLM",
|
|
191
195
|
available: true,
|
|
192
196
|
},
|
|
193
197
|
{
|
|
194
|
-
id: "
|
|
195
|
-
name: "
|
|
198
|
+
id: "kilo",
|
|
199
|
+
name: "Kilo Gateway",
|
|
196
200
|
available: true,
|
|
197
201
|
},
|
|
198
202
|
{
|
|
199
|
-
id: "
|
|
200
|
-
name: "
|
|
203
|
+
id: "zenmux",
|
|
204
|
+
name: "ZenMux",
|
|
201
205
|
available: true,
|
|
202
206
|
},
|
|
203
207
|
{
|
|
204
|
-
id: "
|
|
205
|
-
name: "
|
|
208
|
+
id: "opencode-zen",
|
|
209
|
+
name: "OpenCode Zen",
|
|
206
210
|
available: true,
|
|
207
211
|
},
|
|
208
212
|
{
|
|
209
|
-
id: "
|
|
210
|
-
name: "
|
|
213
|
+
id: "opencode-go",
|
|
214
|
+
name: "OpenCode Go",
|
|
211
215
|
available: true,
|
|
212
216
|
},
|
|
217
|
+
// Search & tool providers.
|
|
213
218
|
{
|
|
214
|
-
id: "
|
|
215
|
-
name: "
|
|
219
|
+
id: "tavily",
|
|
220
|
+
name: "Tavily",
|
|
216
221
|
available: true,
|
|
217
222
|
},
|
|
218
223
|
{
|
|
219
|
-
id: "
|
|
220
|
-
name: "
|
|
224
|
+
id: "kagi",
|
|
225
|
+
name: "Kagi",
|
|
221
226
|
available: true,
|
|
222
227
|
},
|
|
223
228
|
{
|
|
224
|
-
id: "
|
|
225
|
-
name: "
|
|
229
|
+
id: "parallel",
|
|
230
|
+
name: "Parallel",
|
|
226
231
|
available: true,
|
|
227
232
|
},
|
|
233
|
+
// Local runtimes.
|
|
228
234
|
{
|
|
229
|
-
id: "
|
|
230
|
-
name: "
|
|
235
|
+
id: "ollama",
|
|
236
|
+
name: "Ollama (Local OpenAI-compatible)",
|
|
231
237
|
available: true,
|
|
232
238
|
},
|
|
233
239
|
{
|
|
234
|
-
id: "
|
|
235
|
-
name: "
|
|
240
|
+
id: "ollama-cloud",
|
|
241
|
+
name: "Ollama Cloud",
|
|
236
242
|
available: true,
|
|
237
243
|
},
|
|
238
244
|
{
|
|
239
|
-
id: "
|
|
240
|
-
name: "
|
|
245
|
+
id: "lm-studio",
|
|
246
|
+
name: "LM Studio (Local OpenAI-compatible)",
|
|
241
247
|
available: true,
|
|
242
248
|
},
|
|
243
249
|
{
|
|
244
|
-
id: "
|
|
245
|
-
name: "
|
|
250
|
+
id: "vllm",
|
|
251
|
+
name: "vLLM (Local OpenAI-compatible)",
|
|
246
252
|
available: true,
|
|
247
253
|
},
|
|
248
254
|
];
|