@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.
Files changed (219) hide show
  1. package/README.md +198 -0
  2. package/dist/config-browser.d.ts +3 -0
  3. package/dist/config.d.ts +3 -0
  4. package/dist/index.browser.d.ts +4 -0
  5. package/dist/index.browser.js +1 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +7 -0
  8. package/dist/models/generated-access.d.ts +4 -0
  9. package/dist/models/generated-provider-loaders.d.ts +13 -0
  10. package/dist/models/generated.d.ts +14 -0
  11. package/dist/models/index.d.ts +43 -0
  12. package/dist/models/models-dev-catalog.d.ts +32 -0
  13. package/dist/models/providers/aihubmix.d.ts +5 -0
  14. package/dist/models/providers/anthropic.d.ts +53 -0
  15. package/dist/models/providers/asksage.d.ts +5 -0
  16. package/dist/models/providers/baseten.d.ts +5 -0
  17. package/dist/models/providers/bedrock.d.ts +7 -0
  18. package/dist/models/providers/cerebras.d.ts +7 -0
  19. package/dist/models/providers/claude-code.d.ts +4 -0
  20. package/dist/models/providers/cline.d.ts +34 -0
  21. package/dist/models/providers/deepseek.d.ts +8 -0
  22. package/dist/models/providers/dify.d.ts +5 -0
  23. package/dist/models/providers/doubao.d.ts +7 -0
  24. package/dist/models/providers/fireworks.d.ts +8 -0
  25. package/dist/models/providers/gemini.d.ts +9 -0
  26. package/dist/models/providers/groq.d.ts +8 -0
  27. package/dist/models/providers/hicap.d.ts +5 -0
  28. package/dist/models/providers/huawei-cloud-maas.d.ts +5 -0
  29. package/dist/models/providers/huggingface.d.ts +6 -0
  30. package/dist/models/providers/index.d.ts +45 -0
  31. package/dist/models/providers/litellm.d.ts +5 -0
  32. package/dist/models/providers/lmstudio.d.ts +5 -0
  33. package/dist/models/providers/minimax.d.ts +7 -0
  34. package/dist/models/providers/mistral.d.ts +5 -0
  35. package/dist/models/providers/moonshot.d.ts +7 -0
  36. package/dist/models/providers/nebius.d.ts +7 -0
  37. package/dist/models/providers/nous-research.d.ts +7 -0
  38. package/dist/models/providers/oca.d.ts +9 -0
  39. package/dist/models/providers/ollama.d.ts +5 -0
  40. package/dist/models/providers/openai-codex.d.ts +10 -0
  41. package/dist/models/providers/openai.d.ts +9 -0
  42. package/dist/models/providers/opencode.d.ts +10 -0
  43. package/dist/models/providers/openrouter.d.ts +7 -0
  44. package/dist/models/providers/qwen-code.d.ts +7 -0
  45. package/dist/models/providers/qwen.d.ts +7 -0
  46. package/dist/models/providers/requesty.d.ts +6 -0
  47. package/dist/models/providers/sambanova.d.ts +7 -0
  48. package/dist/models/providers/sapaicore.d.ts +7 -0
  49. package/dist/models/providers/together.d.ts +8 -0
  50. package/dist/models/providers/vercel-ai-gateway.d.ts +5 -0
  51. package/dist/models/providers/vertex.d.ts +7 -0
  52. package/dist/models/providers/xai.d.ts +8 -0
  53. package/dist/models/providers/zai.d.ts +7 -0
  54. package/dist/models/query.d.ts +181 -0
  55. package/dist/models/registry.d.ts +123 -0
  56. package/dist/models/schemas/index.d.ts +7 -0
  57. package/dist/models/schemas/model.d.ts +340 -0
  58. package/dist/models/schemas/query.d.ts +191 -0
  59. package/dist/providers/handlers/ai-sdk-community.d.ts +46 -0
  60. package/dist/providers/handlers/ai-sdk-provider-base.d.ts +32 -0
  61. package/dist/providers/handlers/anthropic-base.d.ts +26 -0
  62. package/dist/providers/handlers/asksage.d.ts +12 -0
  63. package/dist/providers/handlers/auth.d.ts +5 -0
  64. package/dist/providers/handlers/base.d.ts +55 -0
  65. package/dist/providers/handlers/bedrock-base.d.ts +23 -0
  66. package/dist/providers/handlers/bedrock-client.d.ts +4 -0
  67. package/dist/providers/handlers/community-sdk.d.ts +97 -0
  68. package/dist/providers/handlers/fetch-base.d.ts +18 -0
  69. package/dist/providers/handlers/gemini-base.d.ts +25 -0
  70. package/dist/providers/handlers/index.d.ts +19 -0
  71. package/dist/providers/handlers/openai-base.d.ts +54 -0
  72. package/dist/providers/handlers/openai-responses.d.ts +64 -0
  73. package/dist/providers/handlers/providers.d.ts +43 -0
  74. package/dist/providers/handlers/r1-base.d.ts +62 -0
  75. package/dist/providers/handlers/registry.d.ts +106 -0
  76. package/dist/providers/handlers/vertex.d.ts +32 -0
  77. package/dist/providers/index.d.ts +100 -0
  78. package/dist/providers/public.browser.d.ts +2 -0
  79. package/dist/providers/public.d.ts +3 -0
  80. package/dist/providers/shared/openai-compatible.d.ts +10 -0
  81. package/dist/providers/transform/ai-sdk-community-format.d.ts +9 -0
  82. package/dist/providers/transform/anthropic-format.d.ts +24 -0
  83. package/dist/providers/transform/content-format.d.ts +3 -0
  84. package/dist/providers/transform/gemini-format.d.ts +19 -0
  85. package/dist/providers/transform/index.d.ts +10 -0
  86. package/dist/providers/transform/openai-format.d.ts +36 -0
  87. package/dist/providers/transform/r1-format.d.ts +26 -0
  88. package/dist/providers/types/config.d.ts +261 -0
  89. package/dist/providers/types/handler.d.ts +71 -0
  90. package/dist/providers/types/index.d.ts +11 -0
  91. package/dist/providers/types/messages.d.ts +139 -0
  92. package/dist/providers/types/model-info.d.ts +32 -0
  93. package/dist/providers/types/provider-ids.d.ts +63 -0
  94. package/dist/providers/types/settings.d.ts +308 -0
  95. package/dist/providers/types/stream.d.ts +106 -0
  96. package/dist/providers/utils/index.d.ts +7 -0
  97. package/dist/providers/utils/retry.d.ts +38 -0
  98. package/dist/providers/utils/stream-processor.d.ts +110 -0
  99. package/dist/providers/utils/tool-processor.d.ts +34 -0
  100. package/dist/sdk.d.ts +18 -0
  101. package/dist/types.d.ts +60 -0
  102. package/package.json +66 -0
  103. package/src/catalog.ts +20 -0
  104. package/src/config-browser.ts +11 -0
  105. package/src/config.ts +49 -0
  106. package/src/index.browser.ts +9 -0
  107. package/src/index.ts +10 -0
  108. package/src/live-providers.test.ts +137 -0
  109. package/src/models/generated-access.ts +41 -0
  110. package/src/models/generated-provider-loaders.ts +166 -0
  111. package/src/models/generated.ts +11997 -0
  112. package/src/models/index.ts +271 -0
  113. package/src/models/models-dev-catalog.test.ts +161 -0
  114. package/src/models/models-dev-catalog.ts +161 -0
  115. package/src/models/providers/aihubmix.ts +19 -0
  116. package/src/models/providers/anthropic.ts +60 -0
  117. package/src/models/providers/asksage.ts +19 -0
  118. package/src/models/providers/baseten.ts +21 -0
  119. package/src/models/providers/bedrock.ts +30 -0
  120. package/src/models/providers/cerebras.ts +24 -0
  121. package/src/models/providers/claude-code.ts +51 -0
  122. package/src/models/providers/cline.ts +25 -0
  123. package/src/models/providers/deepseek.ts +33 -0
  124. package/src/models/providers/dify.ts +17 -0
  125. package/src/models/providers/doubao.ts +33 -0
  126. package/src/models/providers/fireworks.ts +34 -0
  127. package/src/models/providers/gemini.ts +43 -0
  128. package/src/models/providers/groq.ts +33 -0
  129. package/src/models/providers/hicap.ts +18 -0
  130. package/src/models/providers/huawei-cloud-maas.ts +18 -0
  131. package/src/models/providers/huggingface.ts +22 -0
  132. package/src/models/providers/index.ts +162 -0
  133. package/src/models/providers/litellm.ts +19 -0
  134. package/src/models/providers/lmstudio.ts +22 -0
  135. package/src/models/providers/minimax.ts +34 -0
  136. package/src/models/providers/mistral.ts +19 -0
  137. package/src/models/providers/moonshot.ts +34 -0
  138. package/src/models/providers/nebius.ts +24 -0
  139. package/src/models/providers/nous-research.ts +21 -0
  140. package/src/models/providers/oca.ts +30 -0
  141. package/src/models/providers/ollama.ts +18 -0
  142. package/src/models/providers/openai-codex.ts +30 -0
  143. package/src/models/providers/openai.ts +43 -0
  144. package/src/models/providers/opencode.ts +28 -0
  145. package/src/models/providers/openrouter.ts +24 -0
  146. package/src/models/providers/qwen-code.ts +33 -0
  147. package/src/models/providers/qwen.ts +34 -0
  148. package/src/models/providers/requesty.ts +23 -0
  149. package/src/models/providers/sambanova.ts +23 -0
  150. package/src/models/providers/sapaicore.ts +34 -0
  151. package/src/models/providers/together.ts +35 -0
  152. package/src/models/providers/vercel-ai-gateway.ts +23 -0
  153. package/src/models/providers/vertex.ts +36 -0
  154. package/src/models/providers/xai.ts +34 -0
  155. package/src/models/providers/zai.ts +25 -0
  156. package/src/models/query.ts +407 -0
  157. package/src/models/registry.ts +511 -0
  158. package/src/models/schemas/index.ts +62 -0
  159. package/src/models/schemas/model.ts +308 -0
  160. package/src/models/schemas/query.ts +336 -0
  161. package/src/providers/browser.ts +4 -0
  162. package/src/providers/handlers/ai-sdk-community.ts +226 -0
  163. package/src/providers/handlers/ai-sdk-provider-base.ts +193 -0
  164. package/src/providers/handlers/anthropic-base.ts +372 -0
  165. package/src/providers/handlers/asksage.test.ts +103 -0
  166. package/src/providers/handlers/asksage.ts +138 -0
  167. package/src/providers/handlers/auth.test.ts +19 -0
  168. package/src/providers/handlers/auth.ts +121 -0
  169. package/src/providers/handlers/base.test.ts +46 -0
  170. package/src/providers/handlers/base.ts +160 -0
  171. package/src/providers/handlers/bedrock-base.ts +390 -0
  172. package/src/providers/handlers/bedrock-client.ts +100 -0
  173. package/src/providers/handlers/codex.test.ts +123 -0
  174. package/src/providers/handlers/community-sdk.test.ts +288 -0
  175. package/src/providers/handlers/community-sdk.ts +392 -0
  176. package/src/providers/handlers/fetch-base.ts +68 -0
  177. package/src/providers/handlers/gemini-base.ts +302 -0
  178. package/src/providers/handlers/index.ts +67 -0
  179. package/src/providers/handlers/openai-base.ts +277 -0
  180. package/src/providers/handlers/openai-responses.ts +598 -0
  181. package/src/providers/handlers/providers.test.ts +120 -0
  182. package/src/providers/handlers/providers.ts +563 -0
  183. package/src/providers/handlers/r1-base.ts +280 -0
  184. package/src/providers/handlers/registry.ts +185 -0
  185. package/src/providers/handlers/vertex.test.ts +124 -0
  186. package/src/providers/handlers/vertex.ts +292 -0
  187. package/src/providers/index.ts +534 -0
  188. package/src/providers/public.browser.ts +20 -0
  189. package/src/providers/public.ts +51 -0
  190. package/src/providers/shared/openai-compatible.ts +63 -0
  191. package/src/providers/transform/ai-sdk-community-format.test.ts +73 -0
  192. package/src/providers/transform/ai-sdk-community-format.ts +115 -0
  193. package/src/providers/transform/anthropic-format.ts +218 -0
  194. package/src/providers/transform/content-format.ts +34 -0
  195. package/src/providers/transform/format-conversion.test.ts +310 -0
  196. package/src/providers/transform/gemini-format.ts +167 -0
  197. package/src/providers/transform/index.ts +22 -0
  198. package/src/providers/transform/openai-format.ts +247 -0
  199. package/src/providers/transform/r1-format.ts +287 -0
  200. package/src/providers/types/config.ts +388 -0
  201. package/src/providers/types/handler.ts +87 -0
  202. package/src/providers/types/index.ts +120 -0
  203. package/src/providers/types/messages.ts +158 -0
  204. package/src/providers/types/model-info.test.ts +57 -0
  205. package/src/providers/types/model-info.ts +65 -0
  206. package/src/providers/types/provider-ids.test.ts +12 -0
  207. package/src/providers/types/provider-ids.ts +89 -0
  208. package/src/providers/types/settings.test.ts +49 -0
  209. package/src/providers/types/settings.ts +533 -0
  210. package/src/providers/types/stream.ts +117 -0
  211. package/src/providers/utils/index.ts +27 -0
  212. package/src/providers/utils/retry.test.ts +140 -0
  213. package/src/providers/utils/retry.ts +188 -0
  214. package/src/providers/utils/stream-processor.test.ts +232 -0
  215. package/src/providers/utils/stream-processor.ts +472 -0
  216. package/src/providers/utils/tool-processor.test.ts +34 -0
  217. package/src/providers/utils/tool-processor.ts +111 -0
  218. package/src/sdk.ts +264 -0
  219. 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
+ }