smoltalk 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -10
- package/dist/client.d.ts +4 -0
- package/dist/client.js +37 -16
- package/dist/clients/anthropic.d.ts +1 -3
- package/dist/clients/anthropic.js +5 -1
- package/dist/clients/deepinfra.d.ts +17 -0
- package/dist/clients/deepinfra.js +27 -0
- package/dist/clients/google.js +3 -2
- package/dist/clients/litellm.d.ts +19 -0
- package/dist/clients/litellm.js +32 -0
- package/dist/clients/ollama.js +5 -3
- package/dist/clients/openai.d.ts +41 -5
- package/dist/clients/openai.js +75 -12
- package/dist/clients/openaiCompat.d.ts +22 -0
- package/dist/clients/openaiCompat.js +33 -0
- package/dist/clients/openaiResponses.js +3 -2
- package/dist/clients/openrouter.d.ts +23 -0
- package/dist/clients/openrouter.js +76 -0
- package/dist/embed/openai.d.ts +7 -1
- package/dist/embed/openai.js +8 -2
- package/dist/embed.d.ts +18 -4
- package/dist/embed.js +32 -4
- package/dist/image/openaiCompat.d.ts +9 -0
- package/dist/image/openaiCompat.js +60 -0
- package/dist/image.d.ts +13 -2
- package/dist/image.js +28 -3
- package/dist/models.d.ts +5 -1
- package/dist/models.js +17 -1
- package/dist/types.d.ts +22 -10
- package/dist/util/provider.d.ts +32 -6
- package/dist/util/provider.js +38 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,15 @@ Smoltalk exposes a common API to different LLM providers, with built-in cost tra
|
|
|
8
8
|
pnpm install smoltalk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
> **Upgrading to 0.6.x?** The flat API-key/host fields on `SmolConfig` have
|
|
12
|
+
> been removed in favor of nested `apiKey` and `baseUrl` maps. Migration:
|
|
13
|
+
> ```diff
|
|
14
|
+
> -{ openAiApiKey: "sk-...", googleApiKey: "...", ollamaHost: "http://..." }
|
|
15
|
+
> +{ apiKey: { openAi: "sk-...", google: "..." }, baseUrl: { ollama: "http://..." } }
|
|
16
|
+
> ```
|
|
17
|
+
> Env-var fallbacks are unchanged (`OPENAI_API_KEY`, `GEMINI_API_KEY`,
|
|
18
|
+
> `ANTHROPIC_API_KEY`, `OLLAMA_HOST`).
|
|
19
|
+
|
|
11
20
|
## Hello world example
|
|
12
21
|
|
|
13
22
|
```typescript
|
|
@@ -68,8 +77,10 @@ const messages = [
|
|
|
68
77
|
const resp = await text({
|
|
69
78
|
messages,
|
|
70
79
|
model: "gemini-2.0-flash-lite",
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
apiKey: {
|
|
81
|
+
openAi: process.env.OPENAI_API_KEY || "",
|
|
82
|
+
google: process.env.GEMINI_API_KEY || "",
|
|
83
|
+
},
|
|
73
84
|
logLevel: "debug",
|
|
74
85
|
});
|
|
75
86
|
```
|
|
@@ -80,8 +91,10 @@ If you want to construct a client once and reuse it across many calls, use `getC
|
|
|
80
91
|
import { getClient, userMessage } from "smoltalk";
|
|
81
92
|
|
|
82
93
|
const client = getClient({
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
apiKey: {
|
|
95
|
+
openAi: process.env.OPENAI_API_KEY || "",
|
|
96
|
+
google: process.env.GEMINI_API_KEY || "",
|
|
97
|
+
},
|
|
85
98
|
model: "gemini-2.0-flash-lite",
|
|
86
99
|
});
|
|
87
100
|
|
|
@@ -147,12 +160,9 @@ A couple of design decisions to note:
|
|
|
147
160
|
|--------|------|-------------|
|
|
148
161
|
| `model` | `ModelName` | **Required.** The model to use (e.g. `"gpt-4o"`, `"gemini-2.0-flash-lite"`). |
|
|
149
162
|
| `messages` | `Message[]` | **Required.** The conversation messages to send. |
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `ollamaApiKey` | `string` | Ollama API key (only needed for cloud Ollama). |
|
|
154
|
-
| `ollamaHost` | `string` | Ollama host URL (for self-hosted or cloud Ollama). |
|
|
155
|
-
| `provider` | `Provider` | Override provider detection. One of `"openai"`, `"openai-responses"`, `"google"`, `"ollama"`, `"anthropic"`, or any provider registered via `registerProvider()`. |
|
|
163
|
+
| `apiKey` | `{ openAi?, google?, anthropic?, ollama?, openRouter?, deepInfra?, liteLlm?, openAiCompat? }` | API keys, nested by provider. Each falls back to its conventional env var (`OPENAI_API_KEY`, `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `DEEPINFRA_API_KEY`, `LITELLM_API_KEY`, `OPENAI_COMPAT_API_KEY`). Ollama has no env-var fallback for the key. |
|
|
164
|
+
| `baseUrl` | `{ ollama?, openRouter?, deepInfra?, liteLlm?, openAiCompat? }` | Custom base URLs. `ollama` defaults to `$OLLAMA_HOST` then `http://localhost:11434`; `openRouter`/`deepInfra` defaults are baked in; `liteLlm`/`openAiCompat` require an explicit URL (or `LITELLM_BASE_URL` / `OPENAI_COMPAT_BASE_URL` env). |
|
|
165
|
+
| `provider` | `Provider` | Override provider detection. One of `"openai"`, `"openai-responses"`, `"google"`, `"ollama"`, `"anthropic"`, `"openrouter"`, `"deepinfra"`, `"litellm"`, `"openai-compat"`, or any provider registered via `registerProvider()`. |
|
|
156
166
|
| `logLevel` | `LogLevel` | Logging verbosity: `"debug"`, `"info"`, `"warn"`, `"error"`. |
|
|
157
167
|
| `tools` | `{ name, description?, schema }[]` | Tool definitions. `schema` is a Zod object schema. |
|
|
158
168
|
| `responseFormat` | `ZodType` | Zod schema for structured output. The response is parsed and validated against this schema. |
|
|
@@ -194,6 +204,53 @@ Detects when the model is stuck in a repetitive tool-call loop.
|
|
|
194
204
|
| `intervention` | `string` | Action to take: `"remove-tool"`, `"remove-all-tools"`, `"throw-error"`, or `"halt-execution"`. |
|
|
195
205
|
| `excludeTools` | `string[]` | Tool names to ignore when counting calls. |
|
|
196
206
|
|
|
207
|
+
## Hosted OpenAI-compatible providers
|
|
208
|
+
|
|
209
|
+
Smoltalk ships four built-in providers for hosted open-source models that all
|
|
210
|
+
speak the OpenAI chat-completions shape. Use these when you want to run a
|
|
211
|
+
Llama, GLM, Qwen, etc. via someone else's hosted infrastructure without
|
|
212
|
+
adding a new dependency. You must pass `provider:` explicitly because these
|
|
213
|
+
model ids aren't in the smoltalk registry.
|
|
214
|
+
|
|
215
|
+
| `provider:` | What it is | Required config | Cost source |
|
|
216
|
+
|-------------|------------|-----------------|-------------|
|
|
217
|
+
| `"openrouter"` | OpenRouter.ai aggregator | `apiKey.openRouter` (or `OPENROUTER_API_KEY`) | `usage.cost` (auto-enabled by injecting `usage:{include:true}`) |
|
|
218
|
+
| `"deepinfra"` | DeepInfra hosted models | `apiKey.deepInfra` (or `DEEPINFRA_API_KEY`) | `usage.estimated_cost` |
|
|
219
|
+
| `"litellm"` | Your own LiteLLM proxy | `apiKey.liteLlm` + `baseUrl.liteLlm` (or `LITELLM_API_KEY` / `LITELLM_BASE_URL`) | `x-litellm-response-cost` header (non-stream only) |
|
|
220
|
+
| `"openai-compat"` | Any OpenAI-shape backend (vLLM, TGI, LM Studio…) | `apiKey.openAiCompat` + `baseUrl.openAiCompat` (or `OPENAI_COMPAT_API_KEY` / `OPENAI_COMPAT_BASE_URL`) | Best-effort: reads `usage.cost`/`estimated_cost`/`cost_usd` if present |
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { textSync, userMessage } from "smoltalk";
|
|
224
|
+
|
|
225
|
+
const r = await textSync({
|
|
226
|
+
model: "z-ai/glm-5.2",
|
|
227
|
+
provider: "openrouter",
|
|
228
|
+
apiKey: { openRouter: process.env.OPENROUTER_API_KEY! },
|
|
229
|
+
messages: [userMessage("hi")],
|
|
230
|
+
});
|
|
231
|
+
// r.value.cost.totalCost is a real OpenRouter-reported USD cost.
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Capability matrix:**
|
|
235
|
+
|
|
236
|
+
| | chat | embeddings | image generation | `web_search` hosted tool |
|
|
237
|
+
|-----------------|------|------------|------------------|--------------------------|
|
|
238
|
+
| `openrouter` | ✅ | ❌ | ❌ | ✅ (via `:online` / web plugin) |
|
|
239
|
+
| `deepinfra` | ✅ | ✅ | ❌ (uses per-model endpoints, not OpenAI shape) | ❌ |
|
|
240
|
+
| `litellm` | ✅ | ✅ | ✅ (if the upstream model supports it) | ✅ (if upstream supports it) |
|
|
241
|
+
| `openai-compat` | ✅ | ✅ | ✅ (backend-dependent) | depends on backend |
|
|
242
|
+
|
|
243
|
+
Smoltalk surfaces a clear `failure(...)` from `embed()`/`image()` for the
|
|
244
|
+
unsupported combinations rather than silently dropping the call.
|
|
245
|
+
|
|
246
|
+
**Running a local LiteLLM proxy:**
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
pip install 'litellm[proxy]'
|
|
250
|
+
litellm --model openai/gpt-4o
|
|
251
|
+
# In your code: baseUrl: { liteLlm: "http://localhost:4000" }
|
|
252
|
+
```
|
|
253
|
+
|
|
197
254
|
## Refreshing model data
|
|
198
255
|
|
|
199
256
|
Smoltalk ships a baked-in model registry (pricing, context limits, capabilities).
|
package/dist/client.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export * from "./clients/openai.js";
|
|
|
4
4
|
export * from "./clients/openaiResponses.js";
|
|
5
5
|
export * from "./clients/baseClient.js";
|
|
6
6
|
export * from "./clients/ollama.js";
|
|
7
|
+
export * from "./clients/openaiCompat.js";
|
|
8
|
+
export * from "./clients/openrouter.js";
|
|
9
|
+
export * from "./clients/deepinfra.js";
|
|
10
|
+
export * from "./clients/litellm.js";
|
|
7
11
|
import { BaseClient } from "./clients/baseClient.js";
|
|
8
12
|
import { SmolClientConfig } from "./types.js";
|
|
9
13
|
export declare function registerProvider(providerName: string, clientClass: typeof BaseClient): void;
|
package/dist/client.js
CHANGED
|
@@ -4,11 +4,19 @@ export * from "./clients/openai.js";
|
|
|
4
4
|
export * from "./clients/openaiResponses.js";
|
|
5
5
|
export * from "./clients/baseClient.js";
|
|
6
6
|
export * from "./clients/ollama.js";
|
|
7
|
+
export * from "./clients/openaiCompat.js";
|
|
8
|
+
export * from "./clients/openrouter.js";
|
|
9
|
+
export * from "./clients/deepinfra.js";
|
|
10
|
+
export * from "./clients/litellm.js";
|
|
7
11
|
import { SmolAnthropic } from "./clients/anthropic.js";
|
|
8
12
|
import { SmolGoogle } from "./clients/google.js";
|
|
9
13
|
import { SmolOllama } from "./clients/ollama.js";
|
|
10
14
|
import { SmolOpenAi } from "./clients/openai.js";
|
|
11
15
|
import { SmolOpenAiResponses } from "./clients/openaiResponses.js";
|
|
16
|
+
import { SmolOpenAiCompat } from "./clients/openaiCompat.js";
|
|
17
|
+
import { SmolOpenRouter } from "./clients/openrouter.js";
|
|
18
|
+
import { SmolDeepInfra } from "./clients/deepinfra.js";
|
|
19
|
+
import { SmolLiteLlm } from "./clients/litellm.js";
|
|
12
20
|
import { getModel, isTextModel } from "./models.js";
|
|
13
21
|
import { SmolError } from "./smolError.js";
|
|
14
22
|
import { resolveApiKey, resolveProvider } from "./util/provider.js";
|
|
@@ -36,43 +44,56 @@ export function getClient(config) {
|
|
|
36
44
|
throw new SmolError(`Only text models are supported currently. ${modelName} is a ${model?.type} model.`);
|
|
37
45
|
}
|
|
38
46
|
}
|
|
47
|
+
// Resolve API keys from config or env vars and inject them back into the
|
|
48
|
+
// nested apiKey map. The client constructors also read env vars directly,
|
|
49
|
+
// but this lets getClient surface a clear error before construction.
|
|
39
50
|
const resolvedKeys = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
openAi: resolveApiKey("openai", config),
|
|
52
|
+
google: resolveApiKey("google", config),
|
|
53
|
+
anthropic: resolveApiKey("anthropic", config),
|
|
54
|
+
ollama: resolveApiKey("ollama", config),
|
|
55
|
+
openRouter: resolveApiKey("openrouter", config),
|
|
56
|
+
deepInfra: resolveApiKey("deepinfra", config),
|
|
57
|
+
liteLlm: resolveApiKey("litellm", config),
|
|
58
|
+
openAiCompat: resolveApiKey("openai-compat", config),
|
|
43
59
|
};
|
|
44
60
|
const clientConfig = {
|
|
45
61
|
messages: [],
|
|
46
62
|
...config,
|
|
47
|
-
...resolvedKeys,
|
|
63
|
+
apiKey: { ...resolvedKeys, ...config.apiKey },
|
|
48
64
|
model: modelName,
|
|
49
65
|
};
|
|
50
66
|
switch (provider) {
|
|
51
67
|
case "anthropic":
|
|
52
|
-
if (!resolvedKeys.
|
|
53
|
-
throw new SmolError("No Anthropic API key provided. Please provide an Anthropic API key in the config using
|
|
68
|
+
if (!resolvedKeys.anthropic) {
|
|
69
|
+
throw new SmolError("No Anthropic API key provided. Please provide an Anthropic API key in the config using apiKey.anthropic, or set the ANTHROPIC_API_KEY environment variable.");
|
|
54
70
|
}
|
|
55
|
-
return new SmolAnthropic(
|
|
56
|
-
...clientConfig,
|
|
57
|
-
anthropicApiKey: resolvedKeys.anthropicApiKey,
|
|
58
|
-
});
|
|
71
|
+
return new SmolAnthropic(clientConfig);
|
|
59
72
|
case "openai":
|
|
60
|
-
if (!resolvedKeys.
|
|
61
|
-
throw new SmolError("No OpenAI API key provided. Please provide an OpenAI API key in the config using
|
|
73
|
+
if (!resolvedKeys.openAi) {
|
|
74
|
+
throw new SmolError("No OpenAI API key provided. Please provide an OpenAI API key in the config using apiKey.openAi, or set the OPENAI_API_KEY environment variable.");
|
|
62
75
|
}
|
|
63
76
|
return new SmolOpenAi(clientConfig);
|
|
64
77
|
case "openai-responses":
|
|
65
|
-
if (!resolvedKeys.
|
|
66
|
-
throw new SmolError("No OpenAI API key provided. Please provide an OpenAI API key in the config using
|
|
78
|
+
if (!resolvedKeys.openAi) {
|
|
79
|
+
throw new SmolError("No OpenAI API key provided. Please provide an OpenAI API key in the config using apiKey.openAi, or set the OPENAI_API_KEY environment variable.");
|
|
67
80
|
}
|
|
68
81
|
return new SmolOpenAiResponses(clientConfig);
|
|
69
82
|
case "google":
|
|
70
|
-
if (!resolvedKeys.
|
|
71
|
-
throw new SmolError("No Google API key provided. Please provide a Google API key in the config using
|
|
83
|
+
if (!resolvedKeys.google) {
|
|
84
|
+
throw new SmolError("No Google API key provided. Please provide a Google API key in the config using apiKey.google, or set the GEMINI_API_KEY environment variable.");
|
|
72
85
|
}
|
|
73
86
|
return new SmolGoogle(clientConfig);
|
|
74
87
|
case "ollama":
|
|
75
88
|
return new SmolOllama(clientConfig);
|
|
89
|
+
case "openrouter":
|
|
90
|
+
return new SmolOpenRouter(clientConfig);
|
|
91
|
+
case "deepinfra":
|
|
92
|
+
return new SmolDeepInfra(clientConfig);
|
|
93
|
+
case "litellm":
|
|
94
|
+
return new SmolLiteLlm(clientConfig);
|
|
95
|
+
case "openai-compat":
|
|
96
|
+
return new SmolOpenAiCompat(clientConfig);
|
|
76
97
|
default:
|
|
77
98
|
if (provider in registeredProviders) {
|
|
78
99
|
const ClientClass = registeredProviders[provider];
|
|
@@ -3,9 +3,7 @@ import { BaseClient } from "./baseClient.js";
|
|
|
3
3
|
import { ModelName } from "../models.js";
|
|
4
4
|
export declare function anthropicWebSearchEntries(hostedTools?: string[]): any[];
|
|
5
5
|
export declare function parseAnthropicHostedTools(response: any, provider: string): HostedToolResult[];
|
|
6
|
-
export type SmolAnthropicConfig = SmolConfig
|
|
7
|
-
anthropicApiKey: string;
|
|
8
|
-
};
|
|
6
|
+
export type SmolAnthropicConfig = SmolConfig;
|
|
9
7
|
export declare class SmolAnthropic extends BaseClient implements SmolClient {
|
|
10
8
|
private client;
|
|
11
9
|
private logger;
|
|
@@ -149,7 +149,11 @@ export class SmolAnthropic extends BaseClient {
|
|
|
149
149
|
model;
|
|
150
150
|
constructor(config) {
|
|
151
151
|
super(config);
|
|
152
|
-
|
|
152
|
+
const apiKey = config.apiKey?.anthropic || process.env.ANTHROPIC_API_KEY;
|
|
153
|
+
if (!apiKey) {
|
|
154
|
+
throw new Error("Anthropic API key is required for SmolAnthropic client.");
|
|
155
|
+
}
|
|
156
|
+
this.client = new Anthropic({ apiKey });
|
|
153
157
|
this.logger = getLogger();
|
|
154
158
|
this.model = new Model(config.model, undefined, config.modelData);
|
|
155
159
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SmolOpenAiCompat } from "./openaiCompat.js";
|
|
2
|
+
import type { SmolConfig } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* DeepInfra client (https://deepinfra.com). OpenAI-compatible chat completions
|
|
5
|
+
* at the `/v1/openai` base URL.
|
|
6
|
+
*
|
|
7
|
+
* - Baked base URL `https://api.deepinfra.com/v1/openai` (override via config.baseUrl.deepInfra).
|
|
8
|
+
* - Key: config.apiKey.deepInfra or env DEEPINFRA_API_KEY.
|
|
9
|
+
* - Cost: reads `usage.estimated_cost` (USD).
|
|
10
|
+
*/
|
|
11
|
+
export declare class SmolDeepInfra extends SmolOpenAiCompat {
|
|
12
|
+
protected resolveClientOptions(config: SmolConfig): {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
baseURL: string;
|
|
15
|
+
};
|
|
16
|
+
protected resolveCostUsd(usage: any): number | undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SmolOpenAiCompat } from "./openaiCompat.js";
|
|
2
|
+
import { resolveApiKey, resolveBaseUrl } from "../util/provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* DeepInfra client (https://deepinfra.com). OpenAI-compatible chat completions
|
|
5
|
+
* at the `/v1/openai` base URL.
|
|
6
|
+
*
|
|
7
|
+
* - Baked base URL `https://api.deepinfra.com/v1/openai` (override via config.baseUrl.deepInfra).
|
|
8
|
+
* - Key: config.apiKey.deepInfra or env DEEPINFRA_API_KEY.
|
|
9
|
+
* - Cost: reads `usage.estimated_cost` (USD).
|
|
10
|
+
*/
|
|
11
|
+
export class SmolDeepInfra extends SmolOpenAiCompat {
|
|
12
|
+
resolveClientOptions(config) {
|
|
13
|
+
const apiKey = resolveApiKey("deepinfra", config);
|
|
14
|
+
// resolveBaseUrl bakes in the deepinfra default, so this is always defined.
|
|
15
|
+
const baseURL = resolveBaseUrl("deepinfra", config);
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
throw new Error("deepinfra: API key required (config.apiKey.deepInfra or DEEPINFRA_API_KEY).");
|
|
18
|
+
}
|
|
19
|
+
return { apiKey, baseURL };
|
|
20
|
+
}
|
|
21
|
+
resolveCostUsd(usage) {
|
|
22
|
+
// DeepInfra returns usage.estimated_cost (USD) on chat completions.
|
|
23
|
+
return typeof usage?.estimated_cost === "number"
|
|
24
|
+
? usage.estimated_cost
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/clients/google.js
CHANGED
|
@@ -68,10 +68,11 @@ export class SmolGoogle extends BaseClient {
|
|
|
68
68
|
model;
|
|
69
69
|
constructor(config) {
|
|
70
70
|
super(config);
|
|
71
|
-
|
|
71
|
+
const apiKey = config.apiKey?.google || process.env.GEMINI_API_KEY;
|
|
72
|
+
if (!apiKey) {
|
|
72
73
|
throw new Error("Google API key is required for SmolGoogle client.");
|
|
73
74
|
}
|
|
74
|
-
this.client = new GoogleGenAI({ apiKey
|
|
75
|
+
this.client = new GoogleGenAI({ apiKey });
|
|
75
76
|
this.logger = getLogger();
|
|
76
77
|
this.model = new Model(config.model, undefined, config.modelData);
|
|
77
78
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SmolOpenAiCompat } from "./openaiCompat.js";
|
|
2
|
+
import type { SmolConfig } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* LiteLLM proxy client (https://docs.litellm.ai/docs/simple_proxy). OpenAI-
|
|
5
|
+
* compatible chat completions through a user-run LiteLLM proxy.
|
|
6
|
+
*
|
|
7
|
+
* - Base URL is REQUIRED from config.baseUrl.liteLlm or env LITELLM_BASE_URL.
|
|
8
|
+
* - Key: config.apiKey.liteLlm or env LITELLM_API_KEY.
|
|
9
|
+
* - Cost: reads the `x-litellm-response-cost` header (USD). Only available
|
|
10
|
+
* for non-streaming responses; streaming requests fall back to the
|
|
11
|
+
* smoltalk model registry.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SmolLiteLlm extends SmolOpenAiCompat {
|
|
14
|
+
protected resolveClientOptions(config: SmolConfig): {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
baseURL: string;
|
|
17
|
+
};
|
|
18
|
+
protected resolveCostUsd(_usage: any, rawResponse?: Response): number | undefined;
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SmolOpenAiCompat } from "./openaiCompat.js";
|
|
2
|
+
import { resolveApiKey, resolveBaseUrl } from "../util/provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* LiteLLM proxy client (https://docs.litellm.ai/docs/simple_proxy). OpenAI-
|
|
5
|
+
* compatible chat completions through a user-run LiteLLM proxy.
|
|
6
|
+
*
|
|
7
|
+
* - Base URL is REQUIRED from config.baseUrl.liteLlm or env LITELLM_BASE_URL.
|
|
8
|
+
* - Key: config.apiKey.liteLlm or env LITELLM_API_KEY.
|
|
9
|
+
* - Cost: reads the `x-litellm-response-cost` header (USD). Only available
|
|
10
|
+
* for non-streaming responses; streaming requests fall back to the
|
|
11
|
+
* smoltalk model registry.
|
|
12
|
+
*/
|
|
13
|
+
export class SmolLiteLlm extends SmolOpenAiCompat {
|
|
14
|
+
resolveClientOptions(config) {
|
|
15
|
+
const apiKey = resolveApiKey("litellm", config);
|
|
16
|
+
const baseURL = resolveBaseUrl("litellm", config);
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
throw new Error("litellm: API key required (config.apiKey.liteLlm or LITELLM_API_KEY).");
|
|
19
|
+
}
|
|
20
|
+
if (!baseURL) {
|
|
21
|
+
throw new Error("litellm: base URL required (config.baseUrl.liteLlm or LITELLM_BASE_URL).");
|
|
22
|
+
}
|
|
23
|
+
return { apiKey, baseURL };
|
|
24
|
+
}
|
|
25
|
+
resolveCostUsd(_usage, rawResponse) {
|
|
26
|
+
const header = rawResponse?.headers?.get?.("x-litellm-response-cost");
|
|
27
|
+
if (!header)
|
|
28
|
+
return undefined;
|
|
29
|
+
const n = Number(header);
|
|
30
|
+
return Number.isFinite(n) ? n : undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/clients/ollama.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getLogger } from "../util/logger.js";
|
|
|
4
4
|
import { success, } from "../types.js";
|
|
5
5
|
import { zodToGoogleTool } from "../util/tool.js";
|
|
6
6
|
import { sanitizeAttributes } from "../util/util.js";
|
|
7
|
+
import { resolveBaseUrl } from "../util/provider.js";
|
|
7
8
|
import { BaseClient } from "./baseClient.js";
|
|
8
9
|
import { SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
9
10
|
import { extractHttpErrorFields } from "../util/httpError.js";
|
|
@@ -17,14 +18,15 @@ export class SmolOllama extends BaseClient {
|
|
|
17
18
|
super(config);
|
|
18
19
|
this.logger = getLogger();
|
|
19
20
|
this.model = new Model(config.model, undefined, config.modelData);
|
|
20
|
-
|
|
21
|
+
const apiKey = config.apiKey?.ollama;
|
|
22
|
+
if (apiKey) {
|
|
21
23
|
this.client = new Ollama({
|
|
22
24
|
host: "https://cloud.ollama.com",
|
|
23
|
-
headers: { Authorization: "Bearer " +
|
|
25
|
+
headers: { Authorization: "Bearer " + apiKey },
|
|
24
26
|
});
|
|
25
27
|
}
|
|
26
28
|
else {
|
|
27
|
-
const host = config
|
|
29
|
+
const host = resolveBaseUrl("ollama", config) || DEFAULT_OLLAMA_HOST;
|
|
28
30
|
this.client = new Ollama({ host });
|
|
29
31
|
}
|
|
30
32
|
}
|
package/dist/clients/openai.d.ts
CHANGED
|
@@ -1,16 +1,52 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import { PromptResult, Result, SmolClient, SmolConfig, StreamChunk } from "../types.js";
|
|
2
|
+
import { PromptResult, Result, SmolClient, SmolConfig, StreamChunk, HostedToolResult } from "../types.js";
|
|
3
|
+
import { EgonLog } from "../util/logger.js";
|
|
3
4
|
import { BaseClient } from "./baseClient.js";
|
|
4
5
|
import { ModelName } from "../models.js";
|
|
6
|
+
import { Model } from "../model.js";
|
|
7
|
+
import { CostEstimate, TokenUsage } from "../types.js";
|
|
5
8
|
export type SmolOpenAiConfig = SmolConfig;
|
|
6
9
|
export declare class SmolOpenAi extends BaseClient implements SmolClient {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
protected client: OpenAI;
|
|
11
|
+
protected logger: EgonLog;
|
|
12
|
+
protected model: Model;
|
|
10
13
|
constructor(config: SmolOpenAiConfig);
|
|
14
|
+
/**
|
|
15
|
+
* Build the `new OpenAI({...})` options. Subclasses override to inject a
|
|
16
|
+
* different baseURL or to pull the key from a different config field.
|
|
17
|
+
*
|
|
18
|
+
* Default behavior: read OPENAI_API_KEY (or config.apiKey.openAi). Throws
|
|
19
|
+
* if the key is missing — subclasses with required keys do their own check.
|
|
20
|
+
*/
|
|
21
|
+
protected resolveClientOptions(config: SmolConfig): {
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
baseURL?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Inspect the provider-returned usage (and optionally the raw HTTP response)
|
|
27
|
+
* for a USD cost figure. Returns `undefined` if the provider didn't surface
|
|
28
|
+
* one, in which case `calculateUsageAndCost` falls back to the smoltalk
|
|
29
|
+
* model registry. Subclasses (OpenRouter, DeepInfra, LiteLLM) override this.
|
|
30
|
+
*/
|
|
31
|
+
protected resolveCostUsd(_usage: any, _rawResponse?: Response): number | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Extra request body fields injected on every call. Subclasses override to
|
|
34
|
+
* add provider-specific request shapes (e.g. OpenRouter's `usage: { include: true }`
|
|
35
|
+
* or its `plugins: [{ id: "web" }]` for web search). Merged into the request
|
|
36
|
+
* after the standard params so it can override them.
|
|
37
|
+
*/
|
|
38
|
+
protected buildRequestExtras(_config: SmolConfig): Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Extract provider-specific hosted-tool results (e.g. OpenRouter web_search
|
|
41
|
+
* annotations) from a completion. Subclasses override; default returns none.
|
|
42
|
+
*/
|
|
43
|
+
protected parseHostedToolResults(_completion: any, _config: SmolConfig): HostedToolResult[];
|
|
11
44
|
getClient(): OpenAI;
|
|
12
45
|
getModel(): ModelName;
|
|
13
|
-
|
|
46
|
+
protected calculateUsageAndCost(usageData: any, rawResponse?: Response): {
|
|
47
|
+
usage?: TokenUsage;
|
|
48
|
+
cost?: CostEstimate;
|
|
49
|
+
};
|
|
14
50
|
private buildRequest;
|
|
15
51
|
private rethrowAsSmolError;
|
|
16
52
|
_textSync(config: SmolConfig): Promise<Result<PromptResult>>;
|
package/dist/clients/openai.js
CHANGED
|
@@ -14,20 +14,57 @@ export class SmolOpenAi extends BaseClient {
|
|
|
14
14
|
model;
|
|
15
15
|
constructor(config) {
|
|
16
16
|
super(config);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
this.client = new OpenAI({ apiKey: config.openAiApiKey });
|
|
17
|
+
const options = this.resolveClientOptions(config);
|
|
18
|
+
this.client = new OpenAI(options);
|
|
21
19
|
this.logger = getLogger();
|
|
22
20
|
this.model = new Model(config.model, undefined, config.modelData);
|
|
23
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the `new OpenAI({...})` options. Subclasses override to inject a
|
|
24
|
+
* different baseURL or to pull the key from a different config field.
|
|
25
|
+
*
|
|
26
|
+
* Default behavior: read OPENAI_API_KEY (or config.apiKey.openAi). Throws
|
|
27
|
+
* if the key is missing — subclasses with required keys do their own check.
|
|
28
|
+
*/
|
|
29
|
+
resolveClientOptions(config) {
|
|
30
|
+
const apiKey = config.apiKey?.openAi || process.env.OPENAI_API_KEY;
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error("OpenAI API key is required for SmolOpenAi client.");
|
|
33
|
+
}
|
|
34
|
+
return { apiKey };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Inspect the provider-returned usage (and optionally the raw HTTP response)
|
|
38
|
+
* for a USD cost figure. Returns `undefined` if the provider didn't surface
|
|
39
|
+
* one, in which case `calculateUsageAndCost` falls back to the smoltalk
|
|
40
|
+
* model registry. Subclasses (OpenRouter, DeepInfra, LiteLLM) override this.
|
|
41
|
+
*/
|
|
42
|
+
resolveCostUsd(_usage, _rawResponse) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extra request body fields injected on every call. Subclasses override to
|
|
47
|
+
* add provider-specific request shapes (e.g. OpenRouter's `usage: { include: true }`
|
|
48
|
+
* or its `plugins: [{ id: "web" }]` for web search). Merged into the request
|
|
49
|
+
* after the standard params so it can override them.
|
|
50
|
+
*/
|
|
51
|
+
buildRequestExtras(_config) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract provider-specific hosted-tool results (e.g. OpenRouter web_search
|
|
56
|
+
* annotations) from a completion. Subclasses override; default returns none.
|
|
57
|
+
*/
|
|
58
|
+
parseHostedToolResults(_completion, _config) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
24
61
|
getClient() {
|
|
25
62
|
return this.client;
|
|
26
63
|
}
|
|
27
64
|
getModel() {
|
|
28
65
|
return this.model.getModel();
|
|
29
66
|
}
|
|
30
|
-
calculateUsageAndCost(usageData) {
|
|
67
|
+
calculateUsageAndCost(usageData, rawResponse) {
|
|
31
68
|
let usage;
|
|
32
69
|
let cost;
|
|
33
70
|
if (usageData) {
|
|
@@ -40,9 +77,23 @@ export class SmolOpenAi extends BaseClient {
|
|
|
40
77
|
if (cached > 0) {
|
|
41
78
|
usage.cachedInputTokens = cached;
|
|
42
79
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
80
|
+
// Prefer provider-supplied cost when available (e.g. OpenRouter
|
|
81
|
+
// usage.cost, DeepInfra usage.estimated_cost, LiteLLM header).
|
|
82
|
+
// Fall back to the smoltalk model-registry calculation.
|
|
83
|
+
const providerCost = this.resolveCostUsd(usageData, rawResponse);
|
|
84
|
+
if (typeof providerCost === "number") {
|
|
85
|
+
cost = {
|
|
86
|
+
inputCost: 0,
|
|
87
|
+
outputCost: 0,
|
|
88
|
+
totalCost: providerCost,
|
|
89
|
+
currency: "USD",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const calculatedCost = this.model.calculateCost(usage);
|
|
94
|
+
if (calculatedCost) {
|
|
95
|
+
cost = calculatedCost;
|
|
96
|
+
}
|
|
46
97
|
}
|
|
47
98
|
}
|
|
48
99
|
return { usage, cost };
|
|
@@ -61,6 +112,7 @@ export class SmolOpenAi extends BaseClient {
|
|
|
61
112
|
reasoning_effort: config.reasoningEffort,
|
|
62
113
|
}),
|
|
63
114
|
...sanitizeAttributes(config.rawAttributes),
|
|
115
|
+
...this.buildRequestExtras(config),
|
|
64
116
|
};
|
|
65
117
|
if (config.responseFormat) {
|
|
66
118
|
request.response_format = {
|
|
@@ -92,11 +144,16 @@ export class SmolOpenAi extends BaseClient {
|
|
|
92
144
|
this.statelogClient?.promptRequest(request);
|
|
93
145
|
const signal = this.getAbortSignal(config);
|
|
94
146
|
let completion;
|
|
147
|
+
let rawResponse;
|
|
95
148
|
try {
|
|
96
|
-
|
|
149
|
+
const result = await this.client.chat.completions
|
|
150
|
+
.create({
|
|
97
151
|
...request,
|
|
98
152
|
stream: false,
|
|
99
|
-
}, { ...(signal && { signal }) })
|
|
153
|
+
}, { ...(signal && { signal }) })
|
|
154
|
+
.withResponse();
|
|
155
|
+
completion = result.data;
|
|
156
|
+
rawResponse = result.response;
|
|
100
157
|
}
|
|
101
158
|
catch (error) {
|
|
102
159
|
this.rethrowAsSmolError(error);
|
|
@@ -123,14 +180,17 @@ export class SmolOpenAi extends BaseClient {
|
|
|
123
180
|
}
|
|
124
181
|
}
|
|
125
182
|
}
|
|
126
|
-
// Extract usage and calculate cost
|
|
127
|
-
|
|
183
|
+
// Extract usage and calculate cost. rawResponse lets subclasses read
|
|
184
|
+
// response headers (e.g. LiteLLM's x-litellm-response-cost).
|
|
185
|
+
const { usage, cost } = this.calculateUsageAndCost(completion.usage, rawResponse);
|
|
186
|
+
const hostedToolResults = this.parseHostedToolResults(completion, config);
|
|
128
187
|
return success({
|
|
129
188
|
output,
|
|
130
189
|
toolCalls,
|
|
131
190
|
usage,
|
|
132
191
|
cost,
|
|
133
192
|
model: this.getModel(),
|
|
193
|
+
...(hostedToolResults.length > 0 ? { hostedToolResults } : {}),
|
|
134
194
|
});
|
|
135
195
|
}
|
|
136
196
|
async *_textStream(config) {
|
|
@@ -156,6 +216,9 @@ export class SmolOpenAi extends BaseClient {
|
|
|
156
216
|
for await (const chunk of completion) {
|
|
157
217
|
// Extract usage from the final chunk
|
|
158
218
|
if (chunk.usage) {
|
|
219
|
+
// Header-based cost (LiteLLM) is unsupported while streaming.
|
|
220
|
+
// Body-based cost (OpenRouter/DeepInfra) still works because it
|
|
221
|
+
// comes through chunk.usage.
|
|
159
222
|
const usageAndCost = this.calculateUsageAndCost(chunk.usage);
|
|
160
223
|
usage = usageAndCost.usage;
|
|
161
224
|
cost = usageAndCost.cost;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SmolOpenAi } from "./openai.js";
|
|
2
|
+
import type { SmolConfig } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generic OpenAI-compatible client. Use when pointing smoltalk at any
|
|
5
|
+
* arbitrary OpenAI-shaped endpoint (vLLM, TGI, LM Studio, OpenLLM, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Both `config.apiKey.openAiCompat` and `config.baseUrl.openAiCompat` are
|
|
8
|
+
* required (or their env-var fallbacks: `OPENAI_COMPAT_API_KEY`,
|
|
9
|
+
* `OPENAI_COMPAT_BASE_URL`).
|
|
10
|
+
*
|
|
11
|
+
* Cost: opportunistically reads `usage.cost`, `usage.estimated_cost`, or
|
|
12
|
+
* `usage.cost_usd` if the backend includes one of them. Otherwise falls back
|
|
13
|
+
* to the smoltalk model registry (which won't have these custom models, so
|
|
14
|
+
* cost stays undefined — that's expected for arbitrary backends).
|
|
15
|
+
*/
|
|
16
|
+
export declare class SmolOpenAiCompat extends SmolOpenAi {
|
|
17
|
+
protected resolveClientOptions(config: SmolConfig): {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
baseURL: string;
|
|
20
|
+
};
|
|
21
|
+
protected resolveCostUsd(usage: any): number | undefined;
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SmolOpenAi } from "./openai.js";
|
|
2
|
+
import { resolveApiKey, resolveBaseUrl } from "../util/provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generic OpenAI-compatible client. Use when pointing smoltalk at any
|
|
5
|
+
* arbitrary OpenAI-shaped endpoint (vLLM, TGI, LM Studio, OpenLLM, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Both `config.apiKey.openAiCompat` and `config.baseUrl.openAiCompat` are
|
|
8
|
+
* required (or their env-var fallbacks: `OPENAI_COMPAT_API_KEY`,
|
|
9
|
+
* `OPENAI_COMPAT_BASE_URL`).
|
|
10
|
+
*
|
|
11
|
+
* Cost: opportunistically reads `usage.cost`, `usage.estimated_cost`, or
|
|
12
|
+
* `usage.cost_usd` if the backend includes one of them. Otherwise falls back
|
|
13
|
+
* to the smoltalk model registry (which won't have these custom models, so
|
|
14
|
+
* cost stays undefined — that's expected for arbitrary backends).
|
|
15
|
+
*/
|
|
16
|
+
export class SmolOpenAiCompat extends SmolOpenAi {
|
|
17
|
+
resolveClientOptions(config) {
|
|
18
|
+
const apiKey = resolveApiKey("openai-compat", config);
|
|
19
|
+
const baseURL = resolveBaseUrl("openai-compat", config);
|
|
20
|
+
if (!apiKey) {
|
|
21
|
+
throw new Error("openai-compat: API key required (config.apiKey.openAiCompat or OPENAI_COMPAT_API_KEY).");
|
|
22
|
+
}
|
|
23
|
+
if (!baseURL) {
|
|
24
|
+
throw new Error("openai-compat: base URL required (config.baseUrl.openAiCompat or OPENAI_COMPAT_BASE_URL).");
|
|
25
|
+
}
|
|
26
|
+
return { apiKey, baseURL };
|
|
27
|
+
}
|
|
28
|
+
resolveCostUsd(usage) {
|
|
29
|
+
// Try the three common conventions across OpenAI-compatible providers.
|
|
30
|
+
const c = usage?.cost ?? usage?.estimated_cost ?? usage?.cost_usd;
|
|
31
|
+
return typeof c === "number" ? c : undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -53,10 +53,11 @@ export class SmolOpenAiResponses extends BaseClient {
|
|
|
53
53
|
model;
|
|
54
54
|
constructor(config) {
|
|
55
55
|
super(config);
|
|
56
|
-
|
|
56
|
+
const apiKey = config.apiKey?.openAi || process.env.OPENAI_API_KEY;
|
|
57
|
+
if (!apiKey) {
|
|
57
58
|
throw new Error("OpenAI API key is required for SmolOpenAiResponses client.");
|
|
58
59
|
}
|
|
59
|
-
this.client = new OpenAI({ apiKey
|
|
60
|
+
this.client = new OpenAI({ apiKey });
|
|
60
61
|
this.logger = getLogger();
|
|
61
62
|
this.model = new Model(config.model, undefined, config.modelData);
|
|
62
63
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SmolOpenAiCompat } from "./openaiCompat.js";
|
|
2
|
+
import type { SmolConfig, HostedToolResult } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* OpenRouter client (https://openrouter.ai). OpenAI-compatible chat completions.
|
|
5
|
+
*
|
|
6
|
+
* - Baked base URL `https://openrouter.ai/api/v1` (override via config.baseUrl.openRouter).
|
|
7
|
+
* - Key: config.apiKey.openRouter or env OPENROUTER_API_KEY.
|
|
8
|
+
* - Cost: reads `usage.cost` (USD). OpenRouter only returns this when
|
|
9
|
+
* `usage: { include: true }` is set in the request body, which this client
|
|
10
|
+
* injects automatically via buildRequestExtras.
|
|
11
|
+
* - Hosted web_search: when config.hostedTools includes "web_search",
|
|
12
|
+
* injects `plugins: [{ id: "web", max_results: 5 }]` and parses
|
|
13
|
+
* message.annotations into a HostedToolResult.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SmolOpenRouter extends SmolOpenAiCompat {
|
|
16
|
+
protected resolveClientOptions(config: SmolConfig): {
|
|
17
|
+
apiKey: string;
|
|
18
|
+
baseURL: string;
|
|
19
|
+
};
|
|
20
|
+
protected resolveCostUsd(usage: any): number | undefined;
|
|
21
|
+
protected buildRequestExtras(config: SmolConfig): Record<string, unknown>;
|
|
22
|
+
protected parseHostedToolResults(completion: any, config: SmolConfig): HostedToolResult[];
|
|
23
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SmolOpenAiCompat } from "./openaiCompat.js";
|
|
2
|
+
import { webSearchResult } from "../util/hostedTools.js";
|
|
3
|
+
import { resolveApiKey, resolveBaseUrl } from "../util/provider.js";
|
|
4
|
+
/**
|
|
5
|
+
* OpenRouter client (https://openrouter.ai). OpenAI-compatible chat completions.
|
|
6
|
+
*
|
|
7
|
+
* - Baked base URL `https://openrouter.ai/api/v1` (override via config.baseUrl.openRouter).
|
|
8
|
+
* - Key: config.apiKey.openRouter or env OPENROUTER_API_KEY.
|
|
9
|
+
* - Cost: reads `usage.cost` (USD). OpenRouter only returns this when
|
|
10
|
+
* `usage: { include: true }` is set in the request body, which this client
|
|
11
|
+
* injects automatically via buildRequestExtras.
|
|
12
|
+
* - Hosted web_search: when config.hostedTools includes "web_search",
|
|
13
|
+
* injects `plugins: [{ id: "web", max_results: 5 }]` and parses
|
|
14
|
+
* message.annotations into a HostedToolResult.
|
|
15
|
+
*/
|
|
16
|
+
export class SmolOpenRouter extends SmolOpenAiCompat {
|
|
17
|
+
resolveClientOptions(config) {
|
|
18
|
+
const apiKey = resolveApiKey("openrouter", config);
|
|
19
|
+
// resolveBaseUrl bakes in the openrouter default, so this is always defined.
|
|
20
|
+
const baseURL = resolveBaseUrl("openrouter", config);
|
|
21
|
+
if (!apiKey) {
|
|
22
|
+
throw new Error("openrouter: API key required (config.apiKey.openRouter or OPENROUTER_API_KEY).");
|
|
23
|
+
}
|
|
24
|
+
return { apiKey, baseURL };
|
|
25
|
+
}
|
|
26
|
+
resolveCostUsd(usage) {
|
|
27
|
+
return typeof usage?.cost === "number" ? usage.cost : undefined;
|
|
28
|
+
}
|
|
29
|
+
buildRequestExtras(config) {
|
|
30
|
+
// OpenRouter only returns usage.cost when this is set.
|
|
31
|
+
const extras = { usage: { include: true } };
|
|
32
|
+
if (config.hostedTools?.includes("web_search")) {
|
|
33
|
+
extras.plugins = [{ id: "web", max_results: 5 }];
|
|
34
|
+
}
|
|
35
|
+
return extras;
|
|
36
|
+
}
|
|
37
|
+
parseHostedToolResults(completion, config) {
|
|
38
|
+
if (!config.hostedTools?.includes("web_search"))
|
|
39
|
+
return [];
|
|
40
|
+
const annotations = completion?.choices?.[0]?.message?.annotations;
|
|
41
|
+
if (!Array.isArray(annotations) || annotations.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const sources = [];
|
|
44
|
+
const citations = [];
|
|
45
|
+
for (const a of annotations) {
|
|
46
|
+
if (a?.type !== "url_citation" || !a.url_citation?.url)
|
|
47
|
+
continue;
|
|
48
|
+
const uc = a.url_citation;
|
|
49
|
+
const source = {
|
|
50
|
+
url: uc.url,
|
|
51
|
+
};
|
|
52
|
+
if (uc.title)
|
|
53
|
+
source.title = uc.title;
|
|
54
|
+
if (uc.content)
|
|
55
|
+
source.snippet = uc.content;
|
|
56
|
+
sources.push(source);
|
|
57
|
+
const citation = { url: uc.url };
|
|
58
|
+
if (uc.title)
|
|
59
|
+
citation.title = uc.title;
|
|
60
|
+
if (typeof uc.start_index === "number")
|
|
61
|
+
citation.startIndex = uc.start_index;
|
|
62
|
+
if (typeof uc.end_index === "number")
|
|
63
|
+
citation.endIndex = uc.end_index;
|
|
64
|
+
citations.push(citation);
|
|
65
|
+
}
|
|
66
|
+
if (sources.length === 0)
|
|
67
|
+
return [];
|
|
68
|
+
return [
|
|
69
|
+
webSearchResult("openrouter", {
|
|
70
|
+
sources,
|
|
71
|
+
citations,
|
|
72
|
+
callCount: 1,
|
|
73
|
+
}),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/embed/openai.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import { EmbedConfig, EmbedResult } from "../embed.js";
|
|
2
2
|
import { Result } from "../types/result.js";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* OpenAI-compatible embedding call. Used by openai directly and by other
|
|
5
|
+
* OpenAI-shape backends (deepinfra, litellm, openai-compat) which pass a
|
|
6
|
+
* custom `baseURL`. Cost comes from the smoltalk model registry; provider-
|
|
7
|
+
* returned cost fields aren't standardized on this endpoint.
|
|
8
|
+
*/
|
|
9
|
+
export declare function openaiEmbed(inputs: string[], config: EmbedConfig, apiKey: string, baseURL?: string): Promise<Result<EmbedResult>>;
|
package/dist/embed/openai.js
CHANGED
|
@@ -2,9 +2,15 @@ import OpenAI from "openai";
|
|
|
2
2
|
import { success, failure } from "../types/result.js";
|
|
3
3
|
import { getModel, isEmbeddingsModel } from "../models.js";
|
|
4
4
|
import { round } from "../util/util.js";
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* OpenAI-compatible embedding call. Used by openai directly and by other
|
|
7
|
+
* OpenAI-shape backends (deepinfra, litellm, openai-compat) which pass a
|
|
8
|
+
* custom `baseURL`. Cost comes from the smoltalk model registry; provider-
|
|
9
|
+
* returned cost fields aren't standardized on this endpoint.
|
|
10
|
+
*/
|
|
11
|
+
export async function openaiEmbed(inputs, config, apiKey, baseURL) {
|
|
6
12
|
try {
|
|
7
|
-
const client = new OpenAI({ apiKey });
|
|
13
|
+
const client = new OpenAI({ apiKey, ...(baseURL ? { baseURL } : {}) });
|
|
8
14
|
const response = await client.embeddings.create({
|
|
9
15
|
model: config.model,
|
|
10
16
|
input: inputs,
|
package/dist/embed.d.ts
CHANGED
|
@@ -6,10 +6,24 @@ export type EmbedConfig = {
|
|
|
6
6
|
model: string;
|
|
7
7
|
provider?: string;
|
|
8
8
|
dimensions?: number;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
/** API keys, nested by provider. Falls back to env vars
|
|
10
|
+
* (OPENAI_API_KEY / GEMINI_API_KEY / DEEPINFRA_API_KEY /
|
|
11
|
+
* LITELLM_API_KEY / OPENAI_COMPAT_API_KEY). */
|
|
12
|
+
apiKey?: {
|
|
13
|
+
openAi?: string;
|
|
14
|
+
google?: string;
|
|
15
|
+
ollama?: string;
|
|
16
|
+
deepInfra?: string;
|
|
17
|
+
liteLlm?: string;
|
|
18
|
+
openAiCompat?: string;
|
|
19
|
+
};
|
|
20
|
+
/** Custom base URLs, nested by provider. */
|
|
21
|
+
baseUrl?: {
|
|
22
|
+
ollama?: string;
|
|
23
|
+
deepInfra?: string;
|
|
24
|
+
liteLlm?: string;
|
|
25
|
+
openAiCompat?: string;
|
|
26
|
+
};
|
|
13
27
|
metadata?: Record<string, unknown>;
|
|
14
28
|
modelData?: ModelDataBlob;
|
|
15
29
|
};
|
package/dist/embed.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { failure } from "./types/result.js";
|
|
2
|
-
import { resolveProvider, resolveApiKey } from "./util/provider.js";
|
|
2
|
+
import { resolveProvider, resolveApiKey, resolveBaseUrl } from "./util/provider.js";
|
|
3
3
|
import { openaiEmbed } from "./embed/openai.js";
|
|
4
4
|
import { googleEmbed } from "./embed/google.js";
|
|
5
5
|
import { ollamaEmbed } from "./embed/ollama.js";
|
|
@@ -23,18 +23,46 @@ export async function embed(input, config) {
|
|
|
23
23
|
case "openai":
|
|
24
24
|
case "openai-responses": {
|
|
25
25
|
if (!apiKey) {
|
|
26
|
-
return failure("No OpenAI API key provided. Set
|
|
26
|
+
return failure("No OpenAI API key provided. Set config.apiKey.openAi or the OPENAI_API_KEY environment variable.");
|
|
27
27
|
}
|
|
28
28
|
return openaiEmbed(inputs, config, apiKey);
|
|
29
29
|
}
|
|
30
30
|
case "google": {
|
|
31
31
|
if (!apiKey) {
|
|
32
|
-
return failure("No Google API key provided. Set
|
|
32
|
+
return failure("No Google API key provided. Set config.apiKey.google or the GEMINI_API_KEY environment variable.");
|
|
33
33
|
}
|
|
34
34
|
return googleEmbed(inputs, config, apiKey);
|
|
35
35
|
}
|
|
36
36
|
case "ollama":
|
|
37
|
-
return ollamaEmbed(inputs, config, apiKey, config
|
|
37
|
+
return ollamaEmbed(inputs, config, apiKey, resolveBaseUrl("ollama", config));
|
|
38
|
+
case "openrouter":
|
|
39
|
+
return failure("openrouter does not expose an embeddings endpoint; use deepinfra, openai-compat, or litellm instead.");
|
|
40
|
+
case "deepinfra": {
|
|
41
|
+
if (!apiKey) {
|
|
42
|
+
return failure("No DeepInfra API key provided. Set config.apiKey.deepInfra or the DEEPINFRA_API_KEY environment variable.");
|
|
43
|
+
}
|
|
44
|
+
return openaiEmbed(inputs, config, apiKey, resolveBaseUrl("deepinfra", config));
|
|
45
|
+
}
|
|
46
|
+
case "litellm": {
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
return failure("No LiteLLM API key provided. Set config.apiKey.liteLlm or the LITELLM_API_KEY environment variable.");
|
|
49
|
+
}
|
|
50
|
+
const baseURL = resolveBaseUrl("litellm", config);
|
|
51
|
+
if (!baseURL) {
|
|
52
|
+
return failure("No LiteLLM base URL provided. Set config.baseUrl.liteLlm or the LITELLM_BASE_URL environment variable.");
|
|
53
|
+
}
|
|
54
|
+
return openaiEmbed(inputs, config, apiKey, baseURL);
|
|
55
|
+
}
|
|
56
|
+
case "openai-compat": {
|
|
57
|
+
if (!apiKey) {
|
|
58
|
+
return failure("No openai-compat API key provided. Set config.apiKey.openAiCompat or the OPENAI_COMPAT_API_KEY environment variable.");
|
|
59
|
+
}
|
|
60
|
+
const baseURL = resolveBaseUrl("openai-compat", config);
|
|
61
|
+
if (!baseURL) {
|
|
62
|
+
return failure("No openai-compat base URL provided. Set config.baseUrl.openAiCompat or the OPENAI_COMPAT_BASE_URL environment variable.");
|
|
63
|
+
}
|
|
64
|
+
return openaiEmbed(inputs, config, apiKey, baseURL);
|
|
65
|
+
}
|
|
38
66
|
default: {
|
|
39
67
|
const custom = registeredEmbedProviders[provider];
|
|
40
68
|
if (custom) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ImageInput, ImageConfig, ImageGenResult } from "../image.js";
|
|
2
|
+
import { Result } from "../types/result.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generic OpenAI-compatible image generation. Used by litellm/openai-compat —
|
|
5
|
+
* any backend that speaks the OpenAI `/images/generations` shape. Image edits
|
|
6
|
+
* are not supported here (the multipart upload shape varies between backends);
|
|
7
|
+
* pass a plain prompt only.
|
|
8
|
+
*/
|
|
9
|
+
export declare function openaiCompatImage(input: ImageInput, config: ImageConfig, apiKey: string, baseURL: string): Promise<Result<ImageGenResult>>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { success, failure } from "../types/result.js";
|
|
3
|
+
import { omitUndefined } from "../util/util.js";
|
|
4
|
+
/**
|
|
5
|
+
* Generic OpenAI-compatible image generation. Used by litellm/openai-compat —
|
|
6
|
+
* any backend that speaks the OpenAI `/images/generations` shape. Image edits
|
|
7
|
+
* are not supported here (the multipart upload shape varies between backends);
|
|
8
|
+
* pass a plain prompt only.
|
|
9
|
+
*/
|
|
10
|
+
export async function openaiCompatImage(input, config, apiKey, baseURL) {
|
|
11
|
+
try {
|
|
12
|
+
const normalized = typeof input === "string" ? { prompt: input } : input;
|
|
13
|
+
const hasImages = !!(normalized.images && normalized.images.length > 0);
|
|
14
|
+
if (hasImages) {
|
|
15
|
+
return failure("openai-compat: image edits (passing `images`) are not supported on this generic adapter. Use the `openai` provider for OpenAI image edits.");
|
|
16
|
+
}
|
|
17
|
+
if (normalized.mask) {
|
|
18
|
+
return failure("openai-compat: image masks are not supported on this generic adapter.");
|
|
19
|
+
}
|
|
20
|
+
const client = new OpenAI({ apiKey, baseURL });
|
|
21
|
+
const params = omitUndefined({
|
|
22
|
+
model: config.model,
|
|
23
|
+
prompt: normalized.prompt,
|
|
24
|
+
n: config.n,
|
|
25
|
+
size: config.size,
|
|
26
|
+
quality: config.quality,
|
|
27
|
+
output_format: config.outputFormat,
|
|
28
|
+
background: config.background,
|
|
29
|
+
...(config.metadata ?? {}),
|
|
30
|
+
});
|
|
31
|
+
const response = await client.images.generate(params);
|
|
32
|
+
const mimeType = mimeFromFormat(config.outputFormat) ?? "image/png";
|
|
33
|
+
const images = (response.data ?? []).map((d) => ({
|
|
34
|
+
data: new Uint8Array(Buffer.from(d.b64_json, "base64")),
|
|
35
|
+
mimeType,
|
|
36
|
+
revisedPrompt: d.revised_prompt,
|
|
37
|
+
}));
|
|
38
|
+
return success({
|
|
39
|
+
images,
|
|
40
|
+
model: config.model,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return failure(err instanceof Error
|
|
45
|
+
? err.message
|
|
46
|
+
: "OpenAI-compatible image request failed");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function mimeFromFormat(format) {
|
|
50
|
+
switch (format) {
|
|
51
|
+
case "png":
|
|
52
|
+
return "image/png";
|
|
53
|
+
case "jpeg":
|
|
54
|
+
return "image/jpeg";
|
|
55
|
+
case "webp":
|
|
56
|
+
return "image/webp";
|
|
57
|
+
default:
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/dist/image.d.ts
CHANGED
|
@@ -17,8 +17,19 @@ export type ImageConfig = {
|
|
|
17
17
|
quality?: "low" | "medium" | "high" | "auto";
|
|
18
18
|
outputFormat?: "png" | "jpeg" | "webp";
|
|
19
19
|
background?: "transparent" | "opaque" | "auto";
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/** API keys, nested by provider. Falls back to env vars
|
|
21
|
+
* (OPENAI_API_KEY / GEMINI_API_KEY / LITELLM_API_KEY / OPENAI_COMPAT_API_KEY). */
|
|
22
|
+
apiKey?: {
|
|
23
|
+
openAi?: string;
|
|
24
|
+
google?: string;
|
|
25
|
+
liteLlm?: string;
|
|
26
|
+
openAiCompat?: string;
|
|
27
|
+
};
|
|
28
|
+
/** Custom base URLs, nested by provider. */
|
|
29
|
+
baseUrl?: {
|
|
30
|
+
liteLlm?: string;
|
|
31
|
+
openAiCompat?: string;
|
|
32
|
+
};
|
|
22
33
|
metadata?: Record<string, unknown>;
|
|
23
34
|
modelData?: ModelDataBlob;
|
|
24
35
|
};
|
package/dist/image.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { failure } from "./types/result.js";
|
|
2
|
-
import { resolveProvider, resolveApiKey } from "./util/provider.js";
|
|
2
|
+
import { resolveProvider, resolveApiKey, resolveBaseUrl } from "./util/provider.js";
|
|
3
3
|
import { openaiImage } from "./image/openai.js";
|
|
4
4
|
import { googleImage } from "./image/google.js";
|
|
5
|
+
import { openaiCompatImage } from "./image/openaiCompat.js";
|
|
5
6
|
// Null-prototype so provider names like "toString"/"__proto__" can't collide
|
|
6
7
|
// with Object.prototype or pollute the registry.
|
|
7
8
|
const registeredImageProviders = Object.create(null);
|
|
@@ -27,16 +28,40 @@ export async function image(input, config) {
|
|
|
27
28
|
case "openai":
|
|
28
29
|
case "openai-responses": {
|
|
29
30
|
if (!apiKey) {
|
|
30
|
-
return failure("No OpenAI API key provided. Set
|
|
31
|
+
return failure("No OpenAI API key provided. Set config.apiKey.openAi or the OPENAI_API_KEY environment variable.");
|
|
31
32
|
}
|
|
32
33
|
return openaiImage(input, config, apiKey);
|
|
33
34
|
}
|
|
34
35
|
case "google": {
|
|
35
36
|
if (!apiKey) {
|
|
36
|
-
return failure("No Google API key provided. Set
|
|
37
|
+
return failure("No Google API key provided. Set config.apiKey.google or the GEMINI_API_KEY environment variable.");
|
|
37
38
|
}
|
|
38
39
|
return googleImage(input, config, apiKey);
|
|
39
40
|
}
|
|
41
|
+
case "openrouter":
|
|
42
|
+
return failure("openrouter does not expose an OpenAI-compatible images endpoint; use openai-compat or litellm instead.");
|
|
43
|
+
case "deepinfra":
|
|
44
|
+
return failure("deepinfra exposes per-model image endpoints rather than the OpenAI `/images/generations` shape; use openai-compat with a specific backend or litellm instead.");
|
|
45
|
+
case "litellm": {
|
|
46
|
+
if (!apiKey) {
|
|
47
|
+
return failure("No LiteLLM API key provided. Set config.apiKey.liteLlm or the LITELLM_API_KEY environment variable.");
|
|
48
|
+
}
|
|
49
|
+
const baseURL = resolveBaseUrl("litellm", config);
|
|
50
|
+
if (!baseURL) {
|
|
51
|
+
return failure("No LiteLLM base URL provided. Set config.baseUrl.liteLlm or the LITELLM_BASE_URL environment variable.");
|
|
52
|
+
}
|
|
53
|
+
return openaiCompatImage(input, config, apiKey, baseURL);
|
|
54
|
+
}
|
|
55
|
+
case "openai-compat": {
|
|
56
|
+
if (!apiKey) {
|
|
57
|
+
return failure("No openai-compat API key provided. Set config.apiKey.openAiCompat or the OPENAI_COMPAT_API_KEY environment variable.");
|
|
58
|
+
}
|
|
59
|
+
const baseURL = resolveBaseUrl("openai-compat", config);
|
|
60
|
+
if (!baseURL) {
|
|
61
|
+
return failure("No openai-compat base URL provided. Set config.baseUrl.openAiCompat or the OPENAI_COMPAT_BASE_URL environment variable.");
|
|
62
|
+
}
|
|
63
|
+
return openaiCompatImage(input, config, apiKey, baseURL);
|
|
64
|
+
}
|
|
40
65
|
default: {
|
|
41
66
|
const custom = registeredImageProviders[provider];
|
|
42
67
|
if (custom) {
|
package/dist/models.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type ModelDataBlob, type HostedTool, type HostedToolPrice } from "./modelData.js";
|
|
3
|
-
export declare const providers: readonly ["ollama", "openai", "openai-responses", "anthropic", "google", "replicate", "modal"];
|
|
3
|
+
export declare const providers: readonly ["ollama", "openai", "openai-responses", "anthropic", "google", "replicate", "modal", "openrouter", "deepinfra", "litellm", "openai-compat"];
|
|
4
4
|
export declare const ProviderSchema: z.ZodEnum<{
|
|
5
5
|
ollama: "ollama";
|
|
6
6
|
openai: "openai";
|
|
@@ -9,6 +9,10 @@ export declare const ProviderSchema: z.ZodEnum<{
|
|
|
9
9
|
google: "google";
|
|
10
10
|
replicate: "replicate";
|
|
11
11
|
modal: "modal";
|
|
12
|
+
openrouter: "openrouter";
|
|
13
|
+
deepinfra: "deepinfra";
|
|
14
|
+
litellm: "litellm";
|
|
15
|
+
"openai-compat": "openai-compat";
|
|
12
16
|
}>;
|
|
13
17
|
export type Provider = z.infer<typeof ProviderSchema>;
|
|
14
18
|
export type BaseModel = {
|
package/dist/models.js
CHANGED
|
@@ -8,6 +8,10 @@ export const providers = [
|
|
|
8
8
|
"google",
|
|
9
9
|
"replicate",
|
|
10
10
|
"modal",
|
|
11
|
+
"openrouter",
|
|
12
|
+
"deepinfra",
|
|
13
|
+
"litellm",
|
|
14
|
+
"openai-compat",
|
|
11
15
|
];
|
|
12
16
|
export const ProviderSchema = z.enum(providers);
|
|
13
17
|
export const speechToTextModels = [
|
|
@@ -1557,6 +1561,18 @@ export const hostedTools = [
|
|
|
1557
1561
|
models: ["gemini-3-pro-preview", "gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-3.5-flash", "gemini-3.1-flash-lite"],
|
|
1558
1562
|
pricing: { unit: "per_call", note: "Gemini 3 family only; see Google pricing." },
|
|
1559
1563
|
},
|
|
1564
|
+
{
|
|
1565
|
+
name: "web_search",
|
|
1566
|
+
provider: "openrouter",
|
|
1567
|
+
category: "web_search",
|
|
1568
|
+
description: "Web search via OpenRouter's web plugin (Exa-backed). Adds citations as annotations.",
|
|
1569
|
+
providerToolId: "web",
|
|
1570
|
+
pricing: {
|
|
1571
|
+
unit: "per_call",
|
|
1572
|
+
amount: 0.02,
|
|
1573
|
+
note: "$4 per 1,000 results returned by the web plugin × default 5 results/call = $0.02/call. Plus token cost for inserted context. If you override max_results, scale this accordingly.",
|
|
1574
|
+
},
|
|
1575
|
+
},
|
|
1560
1576
|
];
|
|
1561
1577
|
export const registeredTextModels = [];
|
|
1562
1578
|
export function registerTextModel(model) {
|
|
@@ -1658,4 +1674,4 @@ export function isEmbeddingsModel(model) {
|
|
|
1658
1674
|
}
|
|
1659
1675
|
export const ModelNameSchema = z
|
|
1660
1676
|
.string()
|
|
1661
|
-
.regex(/^[a-zA-Z0-9._
|
|
1677
|
+
.regex(/^[a-zA-Z0-9._:@/-]+$/, "Model name must only contain letters, numbers, dots, underscores, hyphens, colons, slashes, and @");
|
package/dist/types.d.ts
CHANGED
|
@@ -16,16 +16,28 @@ export type SmolConfig = {
|
|
|
16
16
|
model: ModelName;
|
|
17
17
|
/** Override the provider for the given model (e.g., use a custom endpoint for an OpenAI-compatible model). */
|
|
18
18
|
provider?: string;
|
|
19
|
-
/** API
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
/** API keys, nested by provider. Each key falls back to its conventional env var
|
|
20
|
+
* if unset (e.g. apiKey.openAi → OPENAI_API_KEY). */
|
|
21
|
+
apiKey?: {
|
|
22
|
+
openAi?: string;
|
|
23
|
+
google?: string;
|
|
24
|
+
anthropic?: string;
|
|
25
|
+
ollama?: string;
|
|
26
|
+
openRouter?: string;
|
|
27
|
+
deepInfra?: string;
|
|
28
|
+
liteLlm?: string;
|
|
29
|
+
openAiCompat?: string;
|
|
30
|
+
};
|
|
31
|
+
/** Custom base URLs, nested by provider. Defaults are baked in where applicable
|
|
32
|
+
* (e.g. openrouter, deepinfra); litellm and openai-compat require an explicit URL. */
|
|
33
|
+
baseUrl?: {
|
|
34
|
+
/** Host URL for the Ollama server. Defaults to http://localhost:11434 (or $OLLAMA_HOST). */
|
|
35
|
+
ollama?: string;
|
|
36
|
+
openRouter?: string;
|
|
37
|
+
deepInfra?: string;
|
|
38
|
+
liteLlm?: string;
|
|
39
|
+
openAiCompat?: string;
|
|
40
|
+
};
|
|
29
41
|
/** Log level for internal debug logging. */
|
|
30
42
|
logLevel?: LogLevel;
|
|
31
43
|
/** Configuration for Statelog observability/tracing integration. */
|
package/dist/util/provider.d.ts
CHANGED
|
@@ -5,14 +5,40 @@ import type { ModelDataBlob } from "../modelData.js";
|
|
|
5
5
|
* Otherwise looks up the model in the registry.
|
|
6
6
|
*/
|
|
7
7
|
export declare function resolveProvider(modelName: string, explicitProvider?: string, modelData?: ModelDataBlob): string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Minimal shape needed to read API keys / base URLs. Kept local to avoid
|
|
10
|
+
* importing SmolConfig (which would create a circular import).
|
|
11
|
+
*/
|
|
12
|
+
type NestedKeyConfig = {
|
|
13
|
+
apiKey?: {
|
|
14
|
+
openAi?: string;
|
|
15
|
+
google?: string;
|
|
16
|
+
anthropic?: string;
|
|
17
|
+
ollama?: string;
|
|
18
|
+
openRouter?: string;
|
|
19
|
+
deepInfra?: string;
|
|
20
|
+
liteLlm?: string;
|
|
21
|
+
openAiCompat?: string;
|
|
22
|
+
};
|
|
23
|
+
baseUrl?: {
|
|
24
|
+
ollama?: string;
|
|
25
|
+
openRouter?: string;
|
|
26
|
+
deepInfra?: string;
|
|
27
|
+
liteLlm?: string;
|
|
28
|
+
openAiCompat?: string;
|
|
29
|
+
};
|
|
13
30
|
};
|
|
14
31
|
/**
|
|
15
32
|
* Resolve the API key for a provider, checking config then env vars.
|
|
16
33
|
*/
|
|
17
|
-
export declare function resolveApiKey(provider: string, config:
|
|
34
|
+
export declare function resolveApiKey(provider: string, config: NestedKeyConfig): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the base URL for a provider, checking config then env vars then a
|
|
37
|
+
* baked-in default. Returns `undefined` when a provider requires the caller to
|
|
38
|
+
* supply one (litellm, openai-compat) and nothing was given.
|
|
39
|
+
*
|
|
40
|
+
* Centralized here so we don't duplicate the env-fallback table across
|
|
41
|
+
* embed.ts, image.ts, and each client constructor.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveBaseUrl(provider: string, config: NestedKeyConfig): string | undefined;
|
|
18
44
|
export {};
|
package/dist/util/provider.js
CHANGED
|
@@ -18,16 +18,50 @@ export function resolveProvider(modelName, explicitProvider, modelData) {
|
|
|
18
18
|
* Resolve the API key for a provider, checking config then env vars.
|
|
19
19
|
*/
|
|
20
20
|
export function resolveApiKey(provider, config) {
|
|
21
|
+
const k = config.apiKey;
|
|
21
22
|
switch (provider) {
|
|
22
23
|
case "openai":
|
|
23
24
|
case "openai-responses":
|
|
24
|
-
return
|
|
25
|
+
return k?.openAi || process.env.OPENAI_API_KEY;
|
|
25
26
|
case "google":
|
|
26
|
-
return
|
|
27
|
+
return k?.google || process.env.GEMINI_API_KEY;
|
|
27
28
|
case "anthropic":
|
|
28
|
-
return
|
|
29
|
+
return k?.anthropic || process.env.ANTHROPIC_API_KEY;
|
|
29
30
|
case "ollama":
|
|
30
|
-
return
|
|
31
|
+
return k?.ollama;
|
|
32
|
+
case "openrouter":
|
|
33
|
+
return k?.openRouter || process.env.OPENROUTER_API_KEY;
|
|
34
|
+
case "deepinfra":
|
|
35
|
+
return k?.deepInfra || process.env.DEEPINFRA_API_KEY;
|
|
36
|
+
case "litellm":
|
|
37
|
+
return k?.liteLlm || process.env.LITELLM_API_KEY;
|
|
38
|
+
case "openai-compat":
|
|
39
|
+
return k?.openAiCompat || process.env.OPENAI_COMPAT_API_KEY;
|
|
40
|
+
default:
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the base URL for a provider, checking config then env vars then a
|
|
46
|
+
* baked-in default. Returns `undefined` when a provider requires the caller to
|
|
47
|
+
* supply one (litellm, openai-compat) and nothing was given.
|
|
48
|
+
*
|
|
49
|
+
* Centralized here so we don't duplicate the env-fallback table across
|
|
50
|
+
* embed.ts, image.ts, and each client constructor.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveBaseUrl(provider, config) {
|
|
53
|
+
const b = config.baseUrl;
|
|
54
|
+
switch (provider) {
|
|
55
|
+
case "ollama":
|
|
56
|
+
return b?.ollama || process.env.OLLAMA_HOST;
|
|
57
|
+
case "openrouter":
|
|
58
|
+
return b?.openRouter || "https://openrouter.ai/api/v1";
|
|
59
|
+
case "deepinfra":
|
|
60
|
+
return b?.deepInfra || "https://api.deepinfra.com/v1/openai";
|
|
61
|
+
case "litellm":
|
|
62
|
+
return b?.liteLlm || process.env.LITELLM_BASE_URL;
|
|
63
|
+
case "openai-compat":
|
|
64
|
+
return b?.openAiCompat || process.env.OPENAI_COMPAT_BASE_URL;
|
|
31
65
|
default:
|
|
32
66
|
return undefined;
|
|
33
67
|
}
|