@clinebot/llms 0.0.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/README.md +198 -0
- package/dist/config-browser.d.ts +3 -0
- package/dist/config.d.ts +3 -0
- package/dist/index.browser.d.ts +4 -0
- package/dist/index.browser.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -0
- package/dist/models/generated-access.d.ts +4 -0
- package/dist/models/generated-provider-loaders.d.ts +13 -0
- package/dist/models/generated.d.ts +14 -0
- package/dist/models/index.d.ts +43 -0
- package/dist/models/models-dev-catalog.d.ts +32 -0
- package/dist/models/providers/aihubmix.d.ts +5 -0
- package/dist/models/providers/anthropic.d.ts +53 -0
- package/dist/models/providers/asksage.d.ts +5 -0
- package/dist/models/providers/baseten.d.ts +5 -0
- package/dist/models/providers/bedrock.d.ts +7 -0
- package/dist/models/providers/cerebras.d.ts +7 -0
- package/dist/models/providers/claude-code.d.ts +4 -0
- package/dist/models/providers/cline.d.ts +34 -0
- package/dist/models/providers/deepseek.d.ts +8 -0
- package/dist/models/providers/dify.d.ts +5 -0
- package/dist/models/providers/doubao.d.ts +7 -0
- package/dist/models/providers/fireworks.d.ts +8 -0
- package/dist/models/providers/gemini.d.ts +9 -0
- package/dist/models/providers/groq.d.ts +8 -0
- package/dist/models/providers/hicap.d.ts +5 -0
- package/dist/models/providers/huawei-cloud-maas.d.ts +5 -0
- package/dist/models/providers/huggingface.d.ts +6 -0
- package/dist/models/providers/index.d.ts +45 -0
- package/dist/models/providers/litellm.d.ts +5 -0
- package/dist/models/providers/lmstudio.d.ts +5 -0
- package/dist/models/providers/minimax.d.ts +7 -0
- package/dist/models/providers/mistral.d.ts +5 -0
- package/dist/models/providers/moonshot.d.ts +7 -0
- package/dist/models/providers/nebius.d.ts +7 -0
- package/dist/models/providers/nous-research.d.ts +7 -0
- package/dist/models/providers/oca.d.ts +9 -0
- package/dist/models/providers/ollama.d.ts +5 -0
- package/dist/models/providers/openai-codex.d.ts +10 -0
- package/dist/models/providers/openai.d.ts +9 -0
- package/dist/models/providers/opencode.d.ts +10 -0
- package/dist/models/providers/openrouter.d.ts +7 -0
- package/dist/models/providers/qwen-code.d.ts +7 -0
- package/dist/models/providers/qwen.d.ts +7 -0
- package/dist/models/providers/requesty.d.ts +6 -0
- package/dist/models/providers/sambanova.d.ts +7 -0
- package/dist/models/providers/sapaicore.d.ts +7 -0
- package/dist/models/providers/together.d.ts +8 -0
- package/dist/models/providers/vercel-ai-gateway.d.ts +5 -0
- package/dist/models/providers/vertex.d.ts +7 -0
- package/dist/models/providers/xai.d.ts +8 -0
- package/dist/models/providers/zai.d.ts +7 -0
- package/dist/models/query.d.ts +181 -0
- package/dist/models/registry.d.ts +123 -0
- package/dist/models/schemas/index.d.ts +7 -0
- package/dist/models/schemas/model.d.ts +340 -0
- package/dist/models/schemas/query.d.ts +191 -0
- package/dist/providers/handlers/ai-sdk-community.d.ts +46 -0
- package/dist/providers/handlers/ai-sdk-provider-base.d.ts +32 -0
- package/dist/providers/handlers/anthropic-base.d.ts +26 -0
- package/dist/providers/handlers/asksage.d.ts +12 -0
- package/dist/providers/handlers/auth.d.ts +5 -0
- package/dist/providers/handlers/base.d.ts +55 -0
- package/dist/providers/handlers/bedrock-base.d.ts +23 -0
- package/dist/providers/handlers/bedrock-client.d.ts +4 -0
- package/dist/providers/handlers/community-sdk.d.ts +97 -0
- package/dist/providers/handlers/fetch-base.d.ts +18 -0
- package/dist/providers/handlers/gemini-base.d.ts +25 -0
- package/dist/providers/handlers/index.d.ts +19 -0
- package/dist/providers/handlers/openai-base.d.ts +54 -0
- package/dist/providers/handlers/openai-responses.d.ts +64 -0
- package/dist/providers/handlers/providers.d.ts +43 -0
- package/dist/providers/handlers/r1-base.d.ts +62 -0
- package/dist/providers/handlers/registry.d.ts +106 -0
- package/dist/providers/handlers/vertex.d.ts +32 -0
- package/dist/providers/index.d.ts +100 -0
- package/dist/providers/public.browser.d.ts +2 -0
- package/dist/providers/public.d.ts +3 -0
- package/dist/providers/shared/openai-compatible.d.ts +10 -0
- package/dist/providers/transform/ai-sdk-community-format.d.ts +9 -0
- package/dist/providers/transform/anthropic-format.d.ts +24 -0
- package/dist/providers/transform/content-format.d.ts +3 -0
- package/dist/providers/transform/gemini-format.d.ts +19 -0
- package/dist/providers/transform/index.d.ts +10 -0
- package/dist/providers/transform/openai-format.d.ts +36 -0
- package/dist/providers/transform/r1-format.d.ts +26 -0
- package/dist/providers/types/config.d.ts +261 -0
- package/dist/providers/types/handler.d.ts +71 -0
- package/dist/providers/types/index.d.ts +11 -0
- package/dist/providers/types/messages.d.ts +139 -0
- package/dist/providers/types/model-info.d.ts +32 -0
- package/dist/providers/types/provider-ids.d.ts +63 -0
- package/dist/providers/types/settings.d.ts +308 -0
- package/dist/providers/types/stream.d.ts +106 -0
- package/dist/providers/utils/index.d.ts +7 -0
- package/dist/providers/utils/retry.d.ts +38 -0
- package/dist/providers/utils/stream-processor.d.ts +110 -0
- package/dist/providers/utils/tool-processor.d.ts +34 -0
- package/dist/sdk.d.ts +18 -0
- package/dist/types.d.ts +60 -0
- package/package.json +66 -0
- package/src/catalog.ts +20 -0
- package/src/config-browser.ts +11 -0
- package/src/config.ts +49 -0
- package/src/index.browser.ts +9 -0
- package/src/index.ts +10 -0
- package/src/live-providers.test.ts +137 -0
- package/src/models/generated-access.ts +41 -0
- package/src/models/generated-provider-loaders.ts +166 -0
- package/src/models/generated.ts +11997 -0
- package/src/models/index.ts +271 -0
- package/src/models/models-dev-catalog.test.ts +161 -0
- package/src/models/models-dev-catalog.ts +161 -0
- package/src/models/providers/aihubmix.ts +19 -0
- package/src/models/providers/anthropic.ts +60 -0
- package/src/models/providers/asksage.ts +19 -0
- package/src/models/providers/baseten.ts +21 -0
- package/src/models/providers/bedrock.ts +30 -0
- package/src/models/providers/cerebras.ts +24 -0
- package/src/models/providers/claude-code.ts +51 -0
- package/src/models/providers/cline.ts +25 -0
- package/src/models/providers/deepseek.ts +33 -0
- package/src/models/providers/dify.ts +17 -0
- package/src/models/providers/doubao.ts +33 -0
- package/src/models/providers/fireworks.ts +34 -0
- package/src/models/providers/gemini.ts +43 -0
- package/src/models/providers/groq.ts +33 -0
- package/src/models/providers/hicap.ts +18 -0
- package/src/models/providers/huawei-cloud-maas.ts +18 -0
- package/src/models/providers/huggingface.ts +22 -0
- package/src/models/providers/index.ts +162 -0
- package/src/models/providers/litellm.ts +19 -0
- package/src/models/providers/lmstudio.ts +22 -0
- package/src/models/providers/minimax.ts +34 -0
- package/src/models/providers/mistral.ts +19 -0
- package/src/models/providers/moonshot.ts +34 -0
- package/src/models/providers/nebius.ts +24 -0
- package/src/models/providers/nous-research.ts +21 -0
- package/src/models/providers/oca.ts +30 -0
- package/src/models/providers/ollama.ts +18 -0
- package/src/models/providers/openai-codex.ts +30 -0
- package/src/models/providers/openai.ts +43 -0
- package/src/models/providers/opencode.ts +28 -0
- package/src/models/providers/openrouter.ts +24 -0
- package/src/models/providers/qwen-code.ts +33 -0
- package/src/models/providers/qwen.ts +34 -0
- package/src/models/providers/requesty.ts +23 -0
- package/src/models/providers/sambanova.ts +23 -0
- package/src/models/providers/sapaicore.ts +34 -0
- package/src/models/providers/together.ts +35 -0
- package/src/models/providers/vercel-ai-gateway.ts +23 -0
- package/src/models/providers/vertex.ts +36 -0
- package/src/models/providers/xai.ts +34 -0
- package/src/models/providers/zai.ts +25 -0
- package/src/models/query.ts +407 -0
- package/src/models/registry.ts +511 -0
- package/src/models/schemas/index.ts +62 -0
- package/src/models/schemas/model.ts +308 -0
- package/src/models/schemas/query.ts +336 -0
- package/src/providers/browser.ts +4 -0
- package/src/providers/handlers/ai-sdk-community.ts +226 -0
- package/src/providers/handlers/ai-sdk-provider-base.ts +193 -0
- package/src/providers/handlers/anthropic-base.ts +372 -0
- package/src/providers/handlers/asksage.test.ts +103 -0
- package/src/providers/handlers/asksage.ts +138 -0
- package/src/providers/handlers/auth.test.ts +19 -0
- package/src/providers/handlers/auth.ts +121 -0
- package/src/providers/handlers/base.test.ts +46 -0
- package/src/providers/handlers/base.ts +160 -0
- package/src/providers/handlers/bedrock-base.ts +390 -0
- package/src/providers/handlers/bedrock-client.ts +100 -0
- package/src/providers/handlers/codex.test.ts +123 -0
- package/src/providers/handlers/community-sdk.test.ts +288 -0
- package/src/providers/handlers/community-sdk.ts +392 -0
- package/src/providers/handlers/fetch-base.ts +68 -0
- package/src/providers/handlers/gemini-base.ts +302 -0
- package/src/providers/handlers/index.ts +67 -0
- package/src/providers/handlers/openai-base.ts +277 -0
- package/src/providers/handlers/openai-responses.ts +598 -0
- package/src/providers/handlers/providers.test.ts +120 -0
- package/src/providers/handlers/providers.ts +563 -0
- package/src/providers/handlers/r1-base.ts +280 -0
- package/src/providers/handlers/registry.ts +185 -0
- package/src/providers/handlers/vertex.test.ts +124 -0
- package/src/providers/handlers/vertex.ts +292 -0
- package/src/providers/index.ts +534 -0
- package/src/providers/public.browser.ts +20 -0
- package/src/providers/public.ts +51 -0
- package/src/providers/shared/openai-compatible.ts +63 -0
- package/src/providers/transform/ai-sdk-community-format.test.ts +73 -0
- package/src/providers/transform/ai-sdk-community-format.ts +115 -0
- package/src/providers/transform/anthropic-format.ts +218 -0
- package/src/providers/transform/content-format.ts +34 -0
- package/src/providers/transform/format-conversion.test.ts +310 -0
- package/src/providers/transform/gemini-format.ts +167 -0
- package/src/providers/transform/index.ts +22 -0
- package/src/providers/transform/openai-format.ts +247 -0
- package/src/providers/transform/r1-format.ts +287 -0
- package/src/providers/types/config.ts +388 -0
- package/src/providers/types/handler.ts +87 -0
- package/src/providers/types/index.ts +120 -0
- package/src/providers/types/messages.ts +158 -0
- package/src/providers/types/model-info.test.ts +57 -0
- package/src/providers/types/model-info.ts +65 -0
- package/src/providers/types/provider-ids.test.ts +12 -0
- package/src/providers/types/provider-ids.ts +89 -0
- package/src/providers/types/settings.test.ts +49 -0
- package/src/providers/types/settings.ts +533 -0
- package/src/providers/types/stream.ts +117 -0
- package/src/providers/utils/index.ts +27 -0
- package/src/providers/utils/retry.test.ts +140 -0
- package/src/providers/utils/retry.ts +188 -0
- package/src/providers/utils/stream-processor.test.ts +232 -0
- package/src/providers/utils/stream-processor.ts +472 -0
- package/src/providers/utils/tool-processor.test.ts +34 -0
- package/src/providers/utils/tool-processor.ts +111 -0
- package/src/sdk.ts +264 -0
- package/src/types.ts +79 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Base Handler
|
|
3
|
+
*
|
|
4
|
+
* Handler for Google's Gemini API using the official SDK.
|
|
5
|
+
* Supports Vertex AI, thinking/reasoning, and native tool calling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
FunctionCallingConfigMode,
|
|
10
|
+
type GenerateContentConfig,
|
|
11
|
+
GoogleGenAI,
|
|
12
|
+
ThinkingLevel,
|
|
13
|
+
} from "@google/genai";
|
|
14
|
+
import {
|
|
15
|
+
convertToGeminiMessages,
|
|
16
|
+
convertToolsToGemini,
|
|
17
|
+
} from "../transform/gemini-format";
|
|
18
|
+
import {
|
|
19
|
+
type ApiStream,
|
|
20
|
+
type HandlerModelInfo,
|
|
21
|
+
type ModelInfo,
|
|
22
|
+
type ProviderConfig,
|
|
23
|
+
supportsModelThinking,
|
|
24
|
+
} from "../types";
|
|
25
|
+
import type { Message, ToolDefinition } from "../types/messages";
|
|
26
|
+
import { RetriableError, retryStream } from "../utils/retry";
|
|
27
|
+
import { BaseHandler } from "./base";
|
|
28
|
+
|
|
29
|
+
const DEFAULT_THINKING_BUDGET_TOKENS = 1024;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handler for Google's Gemini API
|
|
33
|
+
*/
|
|
34
|
+
export class GeminiHandler extends BaseHandler {
|
|
35
|
+
private client: GoogleGenAI | undefined;
|
|
36
|
+
|
|
37
|
+
private ensureClient(): GoogleGenAI {
|
|
38
|
+
if (!this.client) {
|
|
39
|
+
// Check for Vertex AI configuration
|
|
40
|
+
if (this.config.gcp?.projectId) {
|
|
41
|
+
this.client = new GoogleGenAI({
|
|
42
|
+
vertexai: true,
|
|
43
|
+
project: this.config.gcp.projectId,
|
|
44
|
+
location: this.config.region ?? "us-central1",
|
|
45
|
+
httpOptions: {
|
|
46
|
+
headers: this.getRequestHeaders(),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
// Standard API key auth
|
|
51
|
+
if (!this.config.apiKey) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"Gemini API key is required when not using Vertex AI",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.client = new GoogleGenAI({
|
|
58
|
+
apiKey: this.config.apiKey,
|
|
59
|
+
httpOptions: {
|
|
60
|
+
headers: this.getRequestHeaders(),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return this.client;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getModel(): HandlerModelInfo {
|
|
69
|
+
const modelId = this.config.modelId;
|
|
70
|
+
const knownModels = this.config.knownModels ?? {};
|
|
71
|
+
const fallbackModel = knownModels[modelId] ?? {};
|
|
72
|
+
const modelInfo = this.config.modelInfo ?? fallbackModel;
|
|
73
|
+
|
|
74
|
+
return { id: modelId, info: { ...modelInfo, id: modelId } };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getMessages(_systemPrompt: string, messages: Message[]) {
|
|
78
|
+
return convertToGeminiMessages(messages);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async *createMessage(
|
|
82
|
+
systemPrompt: string,
|
|
83
|
+
messages: Message[],
|
|
84
|
+
tools?: ToolDefinition[],
|
|
85
|
+
): ApiStream {
|
|
86
|
+
yield* retryStream(
|
|
87
|
+
() => this.createMessageInternal(systemPrompt, messages, tools),
|
|
88
|
+
{ maxRetries: 4, baseDelay: 2000, maxDelay: 15000 },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async *createMessageInternal(
|
|
93
|
+
systemPrompt: string,
|
|
94
|
+
messages: Message[],
|
|
95
|
+
tools?: ToolDefinition[],
|
|
96
|
+
): ApiStream {
|
|
97
|
+
const client = this.ensureClient();
|
|
98
|
+
const { id: modelId, info } = this.getModel();
|
|
99
|
+
const abortSignal = this.getAbortSignal();
|
|
100
|
+
const responseId = this.createResponseId();
|
|
101
|
+
|
|
102
|
+
// Convert messages
|
|
103
|
+
const contents = this.getMessages(systemPrompt, messages);
|
|
104
|
+
|
|
105
|
+
const thinkingSupported = supportsModelThinking(info);
|
|
106
|
+
const thinkingRequested =
|
|
107
|
+
this.config.thinking === true ||
|
|
108
|
+
typeof this.config.thinkingBudgetTokens === "number" ||
|
|
109
|
+
typeof this.config.reasoningEffort === "string";
|
|
110
|
+
let thinkingBudget = 0;
|
|
111
|
+
let thinkingLevel: ThinkingLevel | undefined;
|
|
112
|
+
|
|
113
|
+
if (thinkingSupported && thinkingRequested) {
|
|
114
|
+
const requestedBudget =
|
|
115
|
+
this.config.thinkingBudgetTokens ??
|
|
116
|
+
(this.config.thinking ? DEFAULT_THINKING_BUDGET_TOKENS : 0);
|
|
117
|
+
thinkingBudget = Math.min(
|
|
118
|
+
Math.max(0, requestedBudget),
|
|
119
|
+
info.thinkingConfig?.maxBudget ?? 24576,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// If thinkingConfig has a thinkingLevel, it supports thinking level control
|
|
123
|
+
if (info.thinkingConfig?.thinkingLevel) {
|
|
124
|
+
const level = this.config.reasoningEffort;
|
|
125
|
+
if (level === "high") {
|
|
126
|
+
thinkingLevel = ThinkingLevel.HIGH;
|
|
127
|
+
} else if (level === "low" || level === "medium") {
|
|
128
|
+
thinkingLevel = ThinkingLevel.LOW;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Build request config with abort signal
|
|
134
|
+
const requestConfig: GenerateContentConfig = {
|
|
135
|
+
httpOptions: this.config.baseUrl
|
|
136
|
+
? { baseUrl: this.config.baseUrl, headers: this.getRequestHeaders() }
|
|
137
|
+
: undefined,
|
|
138
|
+
abortSignal,
|
|
139
|
+
systemInstruction: systemPrompt,
|
|
140
|
+
temperature: info.temperature ?? 1,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Add thinking config only when explicitly requested and supported.
|
|
144
|
+
if (
|
|
145
|
+
info.thinkingConfig &&
|
|
146
|
+
thinkingSupported &&
|
|
147
|
+
thinkingRequested &&
|
|
148
|
+
(thinkingBudget > 0 || !!thinkingLevel)
|
|
149
|
+
) {
|
|
150
|
+
requestConfig.thinkingConfig = {
|
|
151
|
+
thinkingBudget: thinkingLevel ? undefined : thinkingBudget,
|
|
152
|
+
thinkingLevel,
|
|
153
|
+
includeThoughts: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Add tools if provided
|
|
158
|
+
if (tools && tools.length > 0) {
|
|
159
|
+
const functionDeclarations = convertToolsToGemini(tools);
|
|
160
|
+
requestConfig.tools = [{ functionDeclarations }];
|
|
161
|
+
requestConfig.toolConfig = {
|
|
162
|
+
functionCallingConfig: {
|
|
163
|
+
mode: FunctionCallingConfigMode.AUTO,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = await client.models.generateContentStream({
|
|
170
|
+
model: modelId,
|
|
171
|
+
contents,
|
|
172
|
+
config: requestConfig,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
let promptTokens = 0;
|
|
176
|
+
let outputTokens = 0;
|
|
177
|
+
let cacheReadTokens = 0;
|
|
178
|
+
let thoughtsTokenCount = 0;
|
|
179
|
+
|
|
180
|
+
for await (const chunk of result) {
|
|
181
|
+
// Handle content parts
|
|
182
|
+
const parts = chunk?.candidates?.[0]?.content?.parts ?? [];
|
|
183
|
+
|
|
184
|
+
for (const part of parts) {
|
|
185
|
+
if ((part as any).thought && part.text) {
|
|
186
|
+
// Thinking content
|
|
187
|
+
yield {
|
|
188
|
+
type: "reasoning",
|
|
189
|
+
reasoning: part.text || "",
|
|
190
|
+
signature: (part as any).thoughtSignature,
|
|
191
|
+
id: responseId,
|
|
192
|
+
};
|
|
193
|
+
} else if (part.text) {
|
|
194
|
+
// Regular text
|
|
195
|
+
yield {
|
|
196
|
+
type: "text",
|
|
197
|
+
text: part.text,
|
|
198
|
+
id: responseId,
|
|
199
|
+
signature: (part as any).thoughtSignature,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (part.functionCall) {
|
|
204
|
+
// Tool call
|
|
205
|
+
const functionCall = part.functionCall;
|
|
206
|
+
const args = Object.entries(functionCall.args ?? {}).filter(
|
|
207
|
+
([, val]) => !!val,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (args.length > 0) {
|
|
211
|
+
yield {
|
|
212
|
+
type: "tool_calls",
|
|
213
|
+
tool_call: {
|
|
214
|
+
function: {
|
|
215
|
+
id: responseId,
|
|
216
|
+
name: functionCall.name,
|
|
217
|
+
arguments: functionCall.args as Record<string, unknown>,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
id: responseId,
|
|
221
|
+
signature: (part as any).thoughtSignature,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Track usage
|
|
228
|
+
if (chunk.usageMetadata) {
|
|
229
|
+
promptTokens = chunk.usageMetadata.promptTokenCount ?? promptTokens;
|
|
230
|
+
outputTokens =
|
|
231
|
+
chunk.usageMetadata.candidatesTokenCount ?? outputTokens;
|
|
232
|
+
thoughtsTokenCount =
|
|
233
|
+
(chunk.usageMetadata as any).thoughtsTokenCount ??
|
|
234
|
+
thoughtsTokenCount;
|
|
235
|
+
cacheReadTokens =
|
|
236
|
+
(chunk.usageMetadata as any).cachedContentTokenCount ??
|
|
237
|
+
cacheReadTokens;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Yield final usage
|
|
242
|
+
const totalCost = this.calculateGeminiCost(
|
|
243
|
+
info,
|
|
244
|
+
promptTokens,
|
|
245
|
+
outputTokens,
|
|
246
|
+
thoughtsTokenCount,
|
|
247
|
+
cacheReadTokens,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
yield {
|
|
251
|
+
type: "usage",
|
|
252
|
+
inputTokens: promptTokens - cacheReadTokens,
|
|
253
|
+
outputTokens,
|
|
254
|
+
thoughtsTokenCount,
|
|
255
|
+
cacheReadTokens,
|
|
256
|
+
cacheWriteTokens: 0,
|
|
257
|
+
totalCost,
|
|
258
|
+
id: responseId,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Yield done chunk to indicate streaming completed successfully
|
|
262
|
+
yield { type: "done", success: true, id: responseId };
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// Handle rate limit errors with retry info
|
|
265
|
+
if (error instanceof Error && error.message.includes("429")) {
|
|
266
|
+
throw new RetriableError(error.message, undefined, { cause: error });
|
|
267
|
+
}
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private calculateGeminiCost(
|
|
273
|
+
info: ModelInfo,
|
|
274
|
+
inputTokens: number,
|
|
275
|
+
outputTokens: number,
|
|
276
|
+
thoughtsTokenCount: number,
|
|
277
|
+
cacheReadTokens: number,
|
|
278
|
+
): number | undefined {
|
|
279
|
+
const pricing = info.pricing;
|
|
280
|
+
if (!pricing?.input || !pricing?.output) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const uncachedInputTokens = inputTokens - cacheReadTokens;
|
|
285
|
+
const inputCost = pricing.input * (uncachedInputTokens / 1_000_000);
|
|
286
|
+
const outputCost =
|
|
287
|
+
pricing.output * ((outputTokens + thoughtsTokenCount) / 1_000_000);
|
|
288
|
+
const cacheReadCost =
|
|
289
|
+
cacheReadTokens > 0
|
|
290
|
+
? (pricing.cacheRead ?? 0) * (cacheReadTokens / 1_000_000)
|
|
291
|
+
: 0;
|
|
292
|
+
|
|
293
|
+
return inputCost + outputCost + cacheReadCost;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create a Gemini handler
|
|
299
|
+
*/
|
|
300
|
+
export function createGeminiHandler(config: ProviderConfig): GeminiHandler {
|
|
301
|
+
return new GeminiHandler(config);
|
|
302
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handlers Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all handler classes and factory functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { AnthropicHandler, createAnthropicHandler } from "./anthropic-base";
|
|
8
|
+
export { AskSageHandler, createAskSageHandler } from "./asksage";
|
|
9
|
+
export {
|
|
10
|
+
getMissingApiKeyError,
|
|
11
|
+
getProviderEnvKeys,
|
|
12
|
+
normalizeProviderId,
|
|
13
|
+
resolveApiKeyForProvider,
|
|
14
|
+
} from "./auth";
|
|
15
|
+
// Base classes
|
|
16
|
+
export { BaseHandler } from "./base";
|
|
17
|
+
export { BedrockHandler, createBedrockHandler } from "./bedrock-base";
|
|
18
|
+
export {
|
|
19
|
+
ClaudeCodeHandler,
|
|
20
|
+
CodexHandler,
|
|
21
|
+
createClaudeCodeHandler,
|
|
22
|
+
createCodexHandler,
|
|
23
|
+
createDifyHandler,
|
|
24
|
+
createMistralHandler,
|
|
25
|
+
createOpenCodeHandler,
|
|
26
|
+
createSapAiCoreHandler,
|
|
27
|
+
DifyHandler,
|
|
28
|
+
MistralHandler,
|
|
29
|
+
OpenCodeHandler,
|
|
30
|
+
SapAiCoreHandler,
|
|
31
|
+
} from "./community-sdk";
|
|
32
|
+
export { FetchBaseHandler } from "./fetch-base";
|
|
33
|
+
export { createGeminiHandler, GeminiHandler } from "./gemini-base";
|
|
34
|
+
// OpenAI Chat Completions API handler
|
|
35
|
+
export { createOpenAIHandler, OpenAIBaseHandler } from "./openai-base";
|
|
36
|
+
// OpenAI Responses API handler
|
|
37
|
+
export {
|
|
38
|
+
createOpenAIResponsesHandler,
|
|
39
|
+
OpenAIResponsesHandler,
|
|
40
|
+
} from "./openai-responses";
|
|
41
|
+
// Provider configurations
|
|
42
|
+
export {
|
|
43
|
+
clearLiveModelsCatalogCache,
|
|
44
|
+
clearPrivateModelsCatalogCache,
|
|
45
|
+
DEFAULT_MODELS_CATALOG_URL,
|
|
46
|
+
getLiveModelsCatalog,
|
|
47
|
+
getProviderConfig,
|
|
48
|
+
isOpenAICompatibleProvider,
|
|
49
|
+
OPENAI_COMPATIBLE_PROVIDERS,
|
|
50
|
+
type ProviderDefaults,
|
|
51
|
+
resolveProviderConfig,
|
|
52
|
+
} from "./providers";
|
|
53
|
+
// R1-based handlers (DeepSeek Reasoner, etc.)
|
|
54
|
+
export { createR1Handler, R1BaseHandler } from "./r1-base";
|
|
55
|
+
// Custom handler registry
|
|
56
|
+
export {
|
|
57
|
+
clearRegistry,
|
|
58
|
+
getRegisteredHandler,
|
|
59
|
+
getRegisteredHandlerAsync,
|
|
60
|
+
getRegisteredProviderIds,
|
|
61
|
+
hasRegisteredHandler,
|
|
62
|
+
isRegisteredHandlerAsync,
|
|
63
|
+
registerAsyncHandler,
|
|
64
|
+
registerHandler,
|
|
65
|
+
unregisterHandler,
|
|
66
|
+
} from "./registry";
|
|
67
|
+
export { createVertexHandler, VertexHandler } from "./vertex";
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Base Handler
|
|
3
|
+
*
|
|
4
|
+
* Base class for all handlers using the OpenAI SDK.
|
|
5
|
+
* This handles the common streaming logic and can be extended for:
|
|
6
|
+
* - Standard OpenAI API
|
|
7
|
+
* - OpenAI-compatible providers (DeepSeek, xAI, Together, etc.)
|
|
8
|
+
* - OpenRouter
|
|
9
|
+
* - Azure OpenAI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import OpenAI from "openai";
|
|
13
|
+
import type { ChatCompletionChunk } from "openai/resources/chat/completions";
|
|
14
|
+
import {
|
|
15
|
+
convertToOpenAIMessages,
|
|
16
|
+
getOpenAIToolParams,
|
|
17
|
+
} from "../transform/openai-format";
|
|
18
|
+
import type {
|
|
19
|
+
ApiStream,
|
|
20
|
+
HandlerModelInfo,
|
|
21
|
+
ModelCapability,
|
|
22
|
+
ModelInfo,
|
|
23
|
+
ProviderConfig,
|
|
24
|
+
} from "../types";
|
|
25
|
+
import type { Message, ToolDefinition } from "../types/messages";
|
|
26
|
+
import { retryStream } from "../utils/retry";
|
|
27
|
+
import { ToolCallProcessor } from "../utils/tool-processor";
|
|
28
|
+
import { getMissingApiKeyError, resolveApiKeyForProvider } from "./auth";
|
|
29
|
+
import { BaseHandler } from "./base";
|
|
30
|
+
|
|
31
|
+
const DEFAULT_REASONING_EFFORT = "medium" as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Base handler for OpenAI SDK-based providers
|
|
35
|
+
*
|
|
36
|
+
* Uses ProviderConfig fields:
|
|
37
|
+
* - baseUrl: Base URL for the API
|
|
38
|
+
* - modelId: Model ID
|
|
39
|
+
* - knownModels: Known models with their info
|
|
40
|
+
* - headers: Custom headers
|
|
41
|
+
* - capabilities: Array of supported capabilities (reasoning, prompt-cache, etc.)
|
|
42
|
+
*/
|
|
43
|
+
export class OpenAIBaseHandler extends BaseHandler {
|
|
44
|
+
protected client: OpenAI | undefined;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Ensure the OpenAI client is initialized
|
|
48
|
+
* Can be overridden for custom client creation (e.g., Azure)
|
|
49
|
+
*/
|
|
50
|
+
protected ensureClient(): OpenAI {
|
|
51
|
+
if (!this.client) {
|
|
52
|
+
const baseURL = this.config.baseUrl;
|
|
53
|
+
|
|
54
|
+
if (!baseURL) {
|
|
55
|
+
throw new Error("Base URL is required. Set baseUrl in config.");
|
|
56
|
+
}
|
|
57
|
+
const apiKey = resolveApiKeyForProvider(
|
|
58
|
+
this.config.providerId,
|
|
59
|
+
this.config.apiKey,
|
|
60
|
+
);
|
|
61
|
+
if (!apiKey) {
|
|
62
|
+
throw new Error(getMissingApiKeyError(this.config.providerId));
|
|
63
|
+
}
|
|
64
|
+
const requestHeaders = this.getRequestHeaders();
|
|
65
|
+
// const hasAuthorizationHeader = Object.keys(requestHeaders).some((key) => key.toLowerCase() === "authorization")
|
|
66
|
+
|
|
67
|
+
this.client = new OpenAI({
|
|
68
|
+
apiKey,
|
|
69
|
+
baseURL,
|
|
70
|
+
defaultHeaders: requestHeaders,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return this.client;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get model info, falling back to provider defaults
|
|
78
|
+
*/
|
|
79
|
+
getModel(): HandlerModelInfo {
|
|
80
|
+
const modelId = this.config.modelId;
|
|
81
|
+
if (!modelId) {
|
|
82
|
+
throw new Error("Model ID is required. Set modelId in config.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const modelInfo =
|
|
86
|
+
this.config.modelInfo ??
|
|
87
|
+
this.config.knownModels?.[modelId] ??
|
|
88
|
+
this.getDefaultModelInfo();
|
|
89
|
+
|
|
90
|
+
return { id: modelId, info: { ...modelInfo, id: modelId } };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected getDefaultModelInfo(): ModelInfo {
|
|
94
|
+
const capabilities: ModelCapability[] = this.config.capabilities?.includes(
|
|
95
|
+
"prompt-cache",
|
|
96
|
+
)
|
|
97
|
+
? ["prompt-cache"]
|
|
98
|
+
: [];
|
|
99
|
+
return {
|
|
100
|
+
id: this.config.modelId,
|
|
101
|
+
capabilities,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getMessages(
|
|
106
|
+
systemPrompt: string,
|
|
107
|
+
messages: Message[],
|
|
108
|
+
): OpenAI.Chat.ChatCompletionMessageParam[] {
|
|
109
|
+
return [
|
|
110
|
+
{ role: "system", content: systemPrompt },
|
|
111
|
+
...convertToOpenAIMessages(messages),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a streaming message
|
|
117
|
+
*/
|
|
118
|
+
async *createMessage(
|
|
119
|
+
systemPrompt: string,
|
|
120
|
+
messages: Message[],
|
|
121
|
+
tools?: ToolDefinition[],
|
|
122
|
+
): ApiStream {
|
|
123
|
+
yield* retryStream(() =>
|
|
124
|
+
this.createMessageInternal(systemPrompt, messages, tools),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async *createMessageInternal(
|
|
129
|
+
systemPrompt: string,
|
|
130
|
+
messages: Message[],
|
|
131
|
+
tools?: ToolDefinition[],
|
|
132
|
+
): ApiStream {
|
|
133
|
+
const client = this.ensureClient();
|
|
134
|
+
const { id: modelId, info: modelInfo } = this.getModel();
|
|
135
|
+
const responseId = this.createResponseId();
|
|
136
|
+
|
|
137
|
+
// Convert messages to OpenAI format
|
|
138
|
+
const openAiMessages = this.getMessages(systemPrompt, messages);
|
|
139
|
+
|
|
140
|
+
// Build request options
|
|
141
|
+
const requestOptions: OpenAI.ChatCompletionCreateParamsStreaming = {
|
|
142
|
+
model: modelId,
|
|
143
|
+
messages: openAiMessages,
|
|
144
|
+
stream: true,
|
|
145
|
+
stream_options: { include_usage: true },
|
|
146
|
+
...getOpenAIToolParams(tools, {
|
|
147
|
+
// OpenRouter can reject strict function schemas on some routed models.
|
|
148
|
+
strict: this.config.providerId !== "openrouter",
|
|
149
|
+
}),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Add max tokens if configured
|
|
153
|
+
if (modelInfo.maxTokens) {
|
|
154
|
+
requestOptions.max_completion_tokens = modelInfo.maxTokens;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Add temperature if not a reasoning model
|
|
158
|
+
const modelSupportsReasoning =
|
|
159
|
+
modelInfo.capabilities?.includes("reasoning") ?? false;
|
|
160
|
+
if (!modelSupportsReasoning) {
|
|
161
|
+
requestOptions.temperature = modelInfo.temperature ?? 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Add reasoning effort for supported models
|
|
165
|
+
const supportsReasoningEffort =
|
|
166
|
+
modelInfo.capabilities?.includes("reasoning-effort") ||
|
|
167
|
+
modelInfo.capabilities?.includes("reasoning") ||
|
|
168
|
+
false;
|
|
169
|
+
const effectiveReasoningEffort =
|
|
170
|
+
this.config.reasoningEffort ??
|
|
171
|
+
(this.config.thinking ? DEFAULT_REASONING_EFFORT : undefined);
|
|
172
|
+
if (supportsReasoningEffort && effectiveReasoningEffort) {
|
|
173
|
+
(requestOptions as any).reasoning_effort = effectiveReasoningEffort;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const requestHeaders = this.getRequestHeaders();
|
|
177
|
+
const hasAuthorizationHeader = Object.keys(requestHeaders).some(
|
|
178
|
+
(key) => key.toLowerCase() === "authorization",
|
|
179
|
+
);
|
|
180
|
+
const apiKey = resolveApiKeyForProvider(
|
|
181
|
+
this.config.providerId,
|
|
182
|
+
this.config.apiKey,
|
|
183
|
+
);
|
|
184
|
+
if (!hasAuthorizationHeader && apiKey) {
|
|
185
|
+
requestHeaders.Authorization = `Bearer ${apiKey}`;
|
|
186
|
+
}
|
|
187
|
+
const abortSignal = this.getAbortSignal();
|
|
188
|
+
const stream = await client.chat.completions.create(requestOptions, {
|
|
189
|
+
signal: abortSignal,
|
|
190
|
+
headers: requestHeaders,
|
|
191
|
+
});
|
|
192
|
+
const toolCallProcessor = new ToolCallProcessor();
|
|
193
|
+
|
|
194
|
+
for await (const chunk of stream) {
|
|
195
|
+
yield* this.withResponseIdForAll(
|
|
196
|
+
this.processChunk(chunk, toolCallProcessor, modelInfo, responseId),
|
|
197
|
+
responseId,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Yield done chunk to indicate streaming completed successfully
|
|
202
|
+
yield { type: "done", success: true, id: responseId };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Process a single chunk from the stream
|
|
207
|
+
* Can be overridden for provider-specific handling
|
|
208
|
+
*/
|
|
209
|
+
protected *processChunk(
|
|
210
|
+
chunk: ChatCompletionChunk,
|
|
211
|
+
toolCallProcessor: ToolCallProcessor,
|
|
212
|
+
_modelInfo: ModelInfo,
|
|
213
|
+
responseId: string,
|
|
214
|
+
): Generator<import("../types").ApiStreamChunk> {
|
|
215
|
+
const delta = chunk.choices?.[0]?.delta && {
|
|
216
|
+
...chunk.choices[0].delta,
|
|
217
|
+
reasoning_content: (chunk.choices[0].delta as any).reasoning_content,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Handle text content
|
|
221
|
+
if (delta?.content) {
|
|
222
|
+
yield { type: "text", text: delta.content, id: responseId };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Handle reasoning content (DeepSeek, xAI, etc.)
|
|
226
|
+
if (delta?.reasoning_content) {
|
|
227
|
+
yield {
|
|
228
|
+
type: "reasoning",
|
|
229
|
+
reasoning: (delta as any).reasoning_content,
|
|
230
|
+
id: responseId,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Handle tool calls
|
|
235
|
+
if (delta?.tool_calls) {
|
|
236
|
+
yield* toolCallProcessor.processToolCallDeltas(
|
|
237
|
+
delta.tool_calls.map((tc) => ({
|
|
238
|
+
index: tc.index,
|
|
239
|
+
id: tc.id,
|
|
240
|
+
function: tc.function,
|
|
241
|
+
})),
|
|
242
|
+
responseId,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Handle usage information
|
|
247
|
+
if (chunk.usage) {
|
|
248
|
+
const inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
249
|
+
const outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
250
|
+
const cacheReadTokens =
|
|
251
|
+
(chunk.usage as any).prompt_tokens_details?.cached_tokens ?? 0;
|
|
252
|
+
const cacheWriteTokens =
|
|
253
|
+
(chunk.usage as any).prompt_cache_miss_tokens ?? 0;
|
|
254
|
+
|
|
255
|
+
yield {
|
|
256
|
+
type: "usage",
|
|
257
|
+
inputTokens,
|
|
258
|
+
outputTokens,
|
|
259
|
+
cacheReadTokens,
|
|
260
|
+
cacheWriteTokens,
|
|
261
|
+
totalCost: this.calculateCost(
|
|
262
|
+
inputTokens,
|
|
263
|
+
outputTokens,
|
|
264
|
+
cacheReadTokens,
|
|
265
|
+
),
|
|
266
|
+
id: responseId,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Create an OpenAI-compatible handler
|
|
274
|
+
*/
|
|
275
|
+
export function createOpenAIHandler(config: ProviderConfig): OpenAIBaseHandler {
|
|
276
|
+
return new OpenAIBaseHandler(config);
|
|
277
|
+
}
|