@oh-my-pi/pi-ai 5.0.1 → 5.1.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/package.json +2 -1
- package/src/models.generated.ts +1085 -27
- package/src/models.ts +5 -3
- package/src/providers/amazon-bedrock.ts +549 -0
- package/src/providers/anthropic.ts +1 -1
- package/src/providers/google-gemini-cli.ts +400 -191
- package/src/providers/google-shared.ts +37 -7
- package/src/providers/openai-codex-responses.ts +1 -1
- package/src/providers/openai-completions.ts +48 -4
- package/src/providers/openai-responses.ts +11 -2
- package/src/providers/{transorm-messages.ts → transform-messages.ts} +13 -7
- package/src/stream.ts +31 -0
- package/src/types.ts +8 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type Content, FinishReason, FunctionCallingConfigMode, type Part, type Schema } from "@google/genai";
|
|
6
6
|
import type { Context, ImageContent, Model, StopReason, TextContent, Tool } from "../types";
|
|
7
7
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
8
|
-
import { transformMessages } from "./
|
|
8
|
+
import { transformMessages } from "./transform-messages";
|
|
9
9
|
|
|
10
10
|
type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "google-vertex";
|
|
11
11
|
|
|
@@ -42,6 +42,29 @@ export function retainThoughtSignature(existing: string | undefined, incoming: s
|
|
|
42
42
|
return existing;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Thought signatures must be base64 for Google APIs (TYPE_BYTES).
|
|
46
|
+
const base64SignaturePattern = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
47
|
+
|
|
48
|
+
function isValidThoughtSignature(signature: string | undefined): boolean {
|
|
49
|
+
if (!signature) return false;
|
|
50
|
+
if (signature.length % 4 !== 0) return false;
|
|
51
|
+
return base64SignaturePattern.test(signature);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Only keep signatures from the same provider/model and with valid base64.
|
|
56
|
+
*/
|
|
57
|
+
function resolveThoughtSignature(isSameProviderAndModel: boolean, signature: string | undefined): string | undefined {
|
|
58
|
+
return isSameProviderAndModel && isValidThoughtSignature(signature) ? signature : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Claude models via Google APIs require explicit tool call IDs in function calls/responses.
|
|
63
|
+
*/
|
|
64
|
+
export function requiresToolCallId(modelId: string): boolean {
|
|
65
|
+
return modelId.startsWith("claude-");
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
/**
|
|
46
69
|
* Convert internal messages to Gemini Content[] format.
|
|
47
70
|
*/
|
|
@@ -85,17 +108,22 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|
|
85
108
|
if (block.type === "text") {
|
|
86
109
|
// Skip empty text blocks - they can cause issues with some models (e.g. Claude via Antigravity)
|
|
87
110
|
if (!block.text || block.text.trim() === "") continue;
|
|
88
|
-
|
|
111
|
+
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.textSignature);
|
|
112
|
+
parts.push({
|
|
113
|
+
text: sanitizeSurrogates(block.text),
|
|
114
|
+
...(thoughtSignature && { thoughtSignature }),
|
|
115
|
+
});
|
|
89
116
|
} else if (block.type === "thinking") {
|
|
90
117
|
// Skip empty thinking blocks
|
|
91
118
|
if (!block.thinking || block.thinking.trim() === "") continue;
|
|
92
119
|
// Only keep as thinking block if same provider AND same model
|
|
93
120
|
// Otherwise convert to plain text (no tags to avoid model mimicking them)
|
|
94
121
|
if (isSameProviderAndModel) {
|
|
122
|
+
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thinkingSignature);
|
|
95
123
|
parts.push({
|
|
96
124
|
thought: true,
|
|
97
125
|
text: sanitizeSurrogates(block.thinking),
|
|
98
|
-
...(
|
|
126
|
+
...(thoughtSignature && { thoughtSignature }),
|
|
99
127
|
});
|
|
100
128
|
} else {
|
|
101
129
|
parts.push({
|
|
@@ -105,16 +133,17 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|
|
105
133
|
} else if (block.type === "toolCall") {
|
|
106
134
|
const part: Part = {
|
|
107
135
|
functionCall: {
|
|
108
|
-
id: block.id,
|
|
109
136
|
name: block.name,
|
|
110
137
|
args: block.arguments,
|
|
138
|
+
...(requiresToolCallId(model.id) ? { id: block.id } : {}),
|
|
111
139
|
},
|
|
112
140
|
};
|
|
113
141
|
if (model.provider === "google-vertex" && part?.functionCall?.id) {
|
|
114
142
|
delete part.functionCall.id; // Vertex AI does not support 'id' in functionCall
|
|
115
143
|
}
|
|
116
|
-
|
|
117
|
-
|
|
144
|
+
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
|
|
145
|
+
if (thoughtSignature) {
|
|
146
|
+
part.thoughtSignature = thoughtSignature;
|
|
118
147
|
}
|
|
119
148
|
parts.push(part);
|
|
120
149
|
}
|
|
@@ -151,13 +180,14 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|
|
151
180
|
},
|
|
152
181
|
}));
|
|
153
182
|
|
|
183
|
+
const includeId = requiresToolCallId(model.id);
|
|
154
184
|
const functionResponsePart: Part = {
|
|
155
185
|
functionResponse: {
|
|
156
|
-
id: msg.toolCallId,
|
|
157
186
|
name: msg.toolName,
|
|
158
187
|
response: msg.isError ? { error: responseValue } : { output: responseValue },
|
|
159
188
|
// Nest images inside functionResponse.parts for Gemini 3
|
|
160
189
|
...(hasImages && supportsMultimodalFunctionResponse && { parts: imageParts }),
|
|
190
|
+
...(includeId ? { id: msg.toolCallId } : {}),
|
|
161
191
|
},
|
|
162
192
|
};
|
|
163
193
|
|
|
@@ -39,7 +39,7 @@ import { getCodexInstructions } from "./openai-codex/prompts/codex";
|
|
|
39
39
|
import { buildCodexSystemPrompt } from "./openai-codex/prompts/system-prompt";
|
|
40
40
|
import { type CodexRequestOptions, type RequestBody, transformRequestBody } from "./openai-codex/request-transformer";
|
|
41
41
|
import { parseCodexError, parseCodexSseStream } from "./openai-codex/response-handler";
|
|
42
|
-
import { transformMessages } from "./
|
|
42
|
+
import { transformMessages } from "./transform-messages";
|
|
43
43
|
|
|
44
44
|
export interface OpenAICodexResponsesOptions extends StreamOptions {
|
|
45
45
|
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
@@ -28,7 +28,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
|
28
28
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
29
29
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
30
30
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
31
|
-
import { transformMessages } from "./
|
|
31
|
+
import { transformMessages } from "./transform-messages";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Normalize tool call ID for Mistral.
|
|
@@ -366,6 +366,7 @@ function createClient(model: Model<"openai-completions">, context: Context, apiK
|
|
|
366
366
|
function buildParams(model: Model<"openai-completions">, context: Context, options?: OpenAICompletionsOptions) {
|
|
367
367
|
const compat = getCompat(model);
|
|
368
368
|
const messages = convertMessages(model, context, compat);
|
|
369
|
+
maybeAddOpenRouterAnthropicCacheControl(model, messages);
|
|
369
370
|
|
|
370
371
|
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
|
371
372
|
model: model.id,
|
|
@@ -404,13 +405,51 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio
|
|
|
404
405
|
params.tool_choice = options.toolChoice;
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
if (
|
|
408
|
+
if (compat.thinkingFormat === "zai" && model.reasoning) {
|
|
409
|
+
// Z.ai uses binary thinking: { type: "enabled" | "disabled" }
|
|
410
|
+
// Must explicitly disable since z.ai defaults to thinking enabled
|
|
411
|
+
(params as any).thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
|
|
412
|
+
} else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
|
413
|
+
// OpenAI-style reasoning_effort
|
|
408
414
|
params.reasoning_effort = options.reasoningEffort;
|
|
409
415
|
}
|
|
410
416
|
|
|
411
417
|
return params;
|
|
412
418
|
}
|
|
413
419
|
|
|
420
|
+
function maybeAddOpenRouterAnthropicCacheControl(
|
|
421
|
+
model: Model<"openai-completions">,
|
|
422
|
+
messages: ChatCompletionMessageParam[],
|
|
423
|
+
): void {
|
|
424
|
+
if (model.provider !== "openrouter" || !model.id.startsWith("anthropic/")) return;
|
|
425
|
+
|
|
426
|
+
// Anthropic-style caching requires cache_control on a text part. Add a breakpoint
|
|
427
|
+
// on the last user/assistant message (walking backwards until we find text content).
|
|
428
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
429
|
+
const msg = messages[i];
|
|
430
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
431
|
+
|
|
432
|
+
const content = msg.content;
|
|
433
|
+
if (typeof content === "string") {
|
|
434
|
+
msg.content = [
|
|
435
|
+
Object.assign({ type: "text" as const, text: content }, { cache_control: { type: "ephemeral" } }),
|
|
436
|
+
];
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!Array.isArray(content)) continue;
|
|
441
|
+
|
|
442
|
+
// Find last text part and add cache_control
|
|
443
|
+
for (let j = content.length - 1; j >= 0; j--) {
|
|
444
|
+
const part = content[j];
|
|
445
|
+
if (part?.type === "text") {
|
|
446
|
+
Object.assign(part, { cache_control: { type: "ephemeral" } });
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
414
453
|
function convertMessages(
|
|
415
454
|
model: Model<"openai-completions">,
|
|
416
455
|
context: Context,
|
|
@@ -645,11 +684,14 @@ function mapStopReason(reason: ChatCompletionChunk.Choice["finish_reason"]): Sto
|
|
|
645
684
|
* Returns a fully resolved OpenAICompat object with all fields set.
|
|
646
685
|
*/
|
|
647
686
|
function detectCompatFromUrl(baseUrl: string): Required<OpenAICompat> {
|
|
687
|
+
const isZai = baseUrl.includes("api.z.ai");
|
|
688
|
+
|
|
648
689
|
const isNonStandard =
|
|
649
690
|
baseUrl.includes("cerebras.ai") ||
|
|
650
691
|
baseUrl.includes("api.x.ai") ||
|
|
651
692
|
baseUrl.includes("mistral.ai") ||
|
|
652
|
-
baseUrl.includes("chutes.ai")
|
|
693
|
+
baseUrl.includes("chutes.ai") ||
|
|
694
|
+
isZai;
|
|
653
695
|
|
|
654
696
|
const useMaxTokens = baseUrl.includes("mistral.ai") || baseUrl.includes("chutes.ai");
|
|
655
697
|
|
|
@@ -660,13 +702,14 @@ function detectCompatFromUrl(baseUrl: string): Required<OpenAICompat> {
|
|
|
660
702
|
return {
|
|
661
703
|
supportsStore: !isNonStandard,
|
|
662
704
|
supportsDeveloperRole: !isNonStandard,
|
|
663
|
-
supportsReasoningEffort: !isGrok,
|
|
705
|
+
supportsReasoningEffort: !isGrok && !isZai,
|
|
664
706
|
supportsUsageInStreaming: true,
|
|
665
707
|
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
|
666
708
|
requiresToolResultName: isMistral,
|
|
667
709
|
requiresAssistantAfterToolResult: false, // Mistral no longer requires this as of Dec 2024
|
|
668
710
|
requiresThinkingAsText: isMistral,
|
|
669
711
|
requiresMistralToolIds: isMistral,
|
|
712
|
+
thinkingFormat: isZai ? "zai" : "openai",
|
|
670
713
|
};
|
|
671
714
|
}
|
|
672
715
|
|
|
@@ -689,5 +732,6 @@ function getCompat(model: Model<"openai-completions">): Required<OpenAICompat> {
|
|
|
689
732
|
model.compat.requiresAssistantAfterToolResult ?? detected.requiresAssistantAfterToolResult,
|
|
690
733
|
requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
|
|
691
734
|
requiresMistralToolIds: model.compat.requiresMistralToolIds ?? detected.requiresMistralToolIds,
|
|
735
|
+
thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
|
|
692
736
|
};
|
|
693
737
|
}
|
|
@@ -29,7 +29,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
|
29
29
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
30
30
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
31
31
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
32
|
-
import { transformMessages } from "./
|
|
32
|
+
import { transformMessages } from "./transform-messages";
|
|
33
33
|
|
|
34
34
|
/** Fast deterministic hash to shorten long strings */
|
|
35
35
|
function shortHash(str: string): string {
|
|
@@ -49,6 +49,7 @@ function shortHash(str: string): string {
|
|
|
49
49
|
export interface OpenAIResponsesOptions extends StreamOptions {
|
|
50
50
|
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
51
51
|
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
52
|
+
serviceTier?: ResponseCreateParamsStreaming["service_tier"];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
@@ -86,7 +87,10 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
86
87
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
87
88
|
const client = createClient(model, context, apiKey);
|
|
88
89
|
const params = buildParams(model, context, options);
|
|
89
|
-
const openaiStream = await client.responses.create(
|
|
90
|
+
const openaiStream = await client.responses.create(
|
|
91
|
+
params,
|
|
92
|
+
options?.signal ? { signal: options.signal } : undefined,
|
|
93
|
+
);
|
|
90
94
|
stream.push({ type: "start", partial: output });
|
|
91
95
|
|
|
92
96
|
let currentItem: ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | null = null;
|
|
@@ -364,6 +368,7 @@ function buildParams(model: Model<"openai-responses">, context: Context, options
|
|
|
364
368
|
model: model.id,
|
|
365
369
|
input: messages,
|
|
366
370
|
stream: true,
|
|
371
|
+
prompt_cache_key: options?.sessionId,
|
|
367
372
|
};
|
|
368
373
|
|
|
369
374
|
if (options?.maxTokens) {
|
|
@@ -374,6 +379,10 @@ function buildParams(model: Model<"openai-responses">, context: Context, options
|
|
|
374
379
|
params.temperature = options?.temperature;
|
|
375
380
|
}
|
|
376
381
|
|
|
382
|
+
if (options?.serviceTier !== undefined) {
|
|
383
|
+
params.service_tier = options.serviceTier;
|
|
384
|
+
}
|
|
385
|
+
|
|
377
386
|
if (context.tools) {
|
|
378
387
|
params.tools = convertTools(context.tools);
|
|
379
388
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from "../types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Normalize tool call ID for
|
|
4
|
+
* Normalize tool call ID for cross-provider compatibility.
|
|
5
5
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
|
6
|
-
*
|
|
6
|
+
* Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
|
|
7
7
|
*/
|
|
8
|
-
function
|
|
8
|
+
function normalizeToolCallId(id: string): string {
|
|
9
9
|
return id.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40);
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -38,11 +38,17 @@ export function transformMessages<TApi extends Api>(messages: Message[], model:
|
|
|
38
38
|
return msg;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// Check if we need to normalize tool call IDs
|
|
42
|
-
|
|
41
|
+
// Check if we need to normalize tool call IDs
|
|
42
|
+
// Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars)
|
|
43
|
+
// OpenAI Responses API generates IDs with `|` and 450+ chars
|
|
44
|
+
// GitHub Copilot routes to Anthropic for Claude models
|
|
45
|
+
const targetRequiresStrictIds = model.api === "anthropic-messages" || model.provider === "github-copilot";
|
|
46
|
+
const crossProviderSwitch = assistantMsg.provider !== model.provider;
|
|
47
|
+
const copilotCrossApiSwitch =
|
|
43
48
|
assistantMsg.provider === "github-copilot" &&
|
|
44
49
|
model.provider === "github-copilot" &&
|
|
45
50
|
assistantMsg.api !== model.api;
|
|
51
|
+
const needsToolCallIdNormalization = targetRequiresStrictIds && (crossProviderSwitch || copilotCrossApiSwitch);
|
|
46
52
|
|
|
47
53
|
// Transform message from different provider/model
|
|
48
54
|
const transformedContent = assistantMsg.content.flatMap((block) => {
|
|
@@ -54,10 +60,10 @@ export function transformMessages<TApi extends Api>(messages: Message[], model:
|
|
|
54
60
|
text: block.thinking,
|
|
55
61
|
};
|
|
56
62
|
}
|
|
57
|
-
// Normalize tool call IDs
|
|
63
|
+
// Normalize tool call IDs when target API requires strict format
|
|
58
64
|
if (block.type === "toolCall" && needsToolCallIdNormalization) {
|
|
59
65
|
const toolCall = block as ToolCall;
|
|
60
|
-
const normalizedId =
|
|
66
|
+
const normalizedId = normalizeToolCallId(toolCall.id);
|
|
61
67
|
if (normalizedId !== toolCall.id) {
|
|
62
68
|
toolCallIdMap.set(toolCall.id, normalizedId);
|
|
63
69
|
return { ...toolCall, id: normalizedId };
|
package/src/stream.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { supportsXhigh } from "./models";
|
|
5
|
+
import { type BedrockOptions, streamBedrock } from "./providers/amazon-bedrock";
|
|
5
6
|
import { type AnthropicOptions, streamAnthropic } from "./providers/anthropic";
|
|
6
7
|
import { type CursorOptions, streamCursor } from "./providers/cursor";
|
|
7
8
|
import { type GoogleOptions, streamGoogle } from "./providers/google";
|
|
@@ -73,6 +74,20 @@ export function getEnvApiKey(provider: any): string | undefined {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
if (provider === "amazon-bedrock") {
|
|
78
|
+
// Amazon Bedrock supports multiple credential sources:
|
|
79
|
+
// 1. AWS_PROFILE - named profile from ~/.aws/credentials
|
|
80
|
+
// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys
|
|
81
|
+
// 3. AWS_BEARER_TOKEN_BEDROCK - Bedrock API keys (bearer token)
|
|
82
|
+
if (
|
|
83
|
+
process.env.AWS_PROFILE ||
|
|
84
|
+
(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||
|
|
85
|
+
process.env.AWS_BEARER_TOKEN_BEDROCK
|
|
86
|
+
) {
|
|
87
|
+
return "<authenticated>";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
const envMap: Record<string, string> = {
|
|
77
92
|
openai: "OPENAI_API_KEY",
|
|
78
93
|
google: "GEMINI_API_KEY",
|
|
@@ -80,8 +95,10 @@ export function getEnvApiKey(provider: any): string | undefined {
|
|
|
80
95
|
cerebras: "CEREBRAS_API_KEY",
|
|
81
96
|
xai: "XAI_API_KEY",
|
|
82
97
|
openrouter: "OPENROUTER_API_KEY",
|
|
98
|
+
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
|
|
83
99
|
zai: "ZAI_API_KEY",
|
|
84
100
|
mistral: "MISTRAL_API_KEY",
|
|
101
|
+
minimax: "MINIMAX_API_KEY",
|
|
85
102
|
opencode: "OPENCODE_API_KEY",
|
|
86
103
|
cursor: "CURSOR_ACCESS_TOKEN",
|
|
87
104
|
};
|
|
@@ -98,6 +115,9 @@ export function stream<TApi extends Api>(
|
|
|
98
115
|
// Vertex AI uses Application Default Credentials, not API keys
|
|
99
116
|
if (model.api === "google-vertex") {
|
|
100
117
|
return streamGoogleVertex(model as Model<"google-vertex">, context, options as GoogleVertexOptions);
|
|
118
|
+
} else if (model.api === "bedrock-converse-stream") {
|
|
119
|
+
// Bedrock doesn't have any API keys instead it sources credentials from standard AWS env variables or from given AWS profile.
|
|
120
|
+
return streamBedrock(model as Model<"bedrock-converse-stream">, context, (options || {}) as BedrockOptions);
|
|
101
121
|
}
|
|
102
122
|
|
|
103
123
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider);
|
|
@@ -159,6 +179,10 @@ export function streamSimple<TApi extends Api>(
|
|
|
159
179
|
if (model.api === "google-vertex") {
|
|
160
180
|
const providerOptions = mapOptionsForApi(model, options, undefined);
|
|
161
181
|
return stream(model, context, providerOptions);
|
|
182
|
+
} else if (model.api === "bedrock-converse-stream") {
|
|
183
|
+
// Bedrock doesn't have any API keys instead it sources credentials from standard AWS env variables or from given AWS profile.
|
|
184
|
+
const providerOptions = mapOptionsForApi(model, options, undefined);
|
|
185
|
+
return stream(model, context, providerOptions);
|
|
162
186
|
}
|
|
163
187
|
|
|
164
188
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider);
|
|
@@ -258,6 +282,13 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
258
282
|
}
|
|
259
283
|
}
|
|
260
284
|
|
|
285
|
+
case "bedrock-converse-stream":
|
|
286
|
+
return {
|
|
287
|
+
...base,
|
|
288
|
+
reasoning: options?.reasoning,
|
|
289
|
+
thinkingBudgets: options?.thinkingBudgets,
|
|
290
|
+
} satisfies BedrockOptions;
|
|
291
|
+
|
|
261
292
|
case "openai-completions":
|
|
262
293
|
return {
|
|
263
294
|
...base,
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BedrockOptions } from "./providers/amazon-bedrock";
|
|
1
2
|
import type { AnthropicOptions } from "./providers/anthropic";
|
|
2
3
|
import type { CursorOptions } from "./providers/cursor";
|
|
3
4
|
import type {
|
|
@@ -32,6 +33,7 @@ export type Api =
|
|
|
32
33
|
| "openai-responses"
|
|
33
34
|
| "openai-codex-responses"
|
|
34
35
|
| "anthropic-messages"
|
|
36
|
+
| "bedrock-converse-stream"
|
|
35
37
|
| "google-generative-ai"
|
|
36
38
|
| "google-gemini-cli"
|
|
37
39
|
| "google-vertex"
|
|
@@ -39,6 +41,7 @@ export type Api =
|
|
|
39
41
|
|
|
40
42
|
export interface ApiOptionsMap {
|
|
41
43
|
"anthropic-messages": AnthropicOptions;
|
|
44
|
+
"bedrock-converse-stream": BedrockOptions;
|
|
42
45
|
"openai-completions": OpenAICompletionsOptions;
|
|
43
46
|
"openai-responses": OpenAIResponsesOptions;
|
|
44
47
|
"openai-codex-responses": OpenAICodexResponsesOptions;
|
|
@@ -61,6 +64,7 @@ const _exhaustive: _CheckExhaustive = true;
|
|
|
61
64
|
export type OptionsForApi<TApi extends Api> = ApiOptionsMap[TApi];
|
|
62
65
|
|
|
63
66
|
export type KnownProvider =
|
|
67
|
+
| "amazon-bedrock"
|
|
64
68
|
| "anthropic"
|
|
65
69
|
| "google"
|
|
66
70
|
| "google-gemini-cli"
|
|
@@ -74,8 +78,10 @@ export type KnownProvider =
|
|
|
74
78
|
| "groq"
|
|
75
79
|
| "cerebras"
|
|
76
80
|
| "openrouter"
|
|
81
|
+
| "vercel-ai-gateway"
|
|
77
82
|
| "zai"
|
|
78
83
|
| "mistral"
|
|
84
|
+
| "minimax"
|
|
79
85
|
| "opencode";
|
|
80
86
|
export type Provider = KnownProvider | string;
|
|
81
87
|
|
|
@@ -269,6 +275,8 @@ export interface OpenAICompat {
|
|
|
269
275
|
requiresThinkingAsText?: boolean;
|
|
270
276
|
/** Whether tool call IDs must be normalized to Mistral format (exactly 9 alphanumeric chars). Default: auto-detected from URL. */
|
|
271
277
|
requiresMistralToolIds?: boolean;
|
|
278
|
+
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "zai" uses thinking: { type: "enabled" }. Default: "openai". */
|
|
279
|
+
thinkingFormat?: "openai" | "zai";
|
|
272
280
|
}
|
|
273
281
|
|
|
274
282
|
// Model interface for the unified model system
|