@funkai/models 0.3.2 → 0.3.3
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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +12 -0
- package/README.md +99 -74
- package/dist/index.d.mts +18 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +20 -5
- package/dist/index.mjs.map +1 -1
- package/docs/catalog/providers.md +1 -1
- package/docs/catalog.md +302 -0
- package/docs/cost-tracking.md +144 -0
- package/docs/guides/setup-resolver.md +15 -30
- package/docs/overview.md +16 -72
- package/docs/provider/configuration.md +20 -43
- package/docs/provider/openrouter.md +22 -41
- package/docs/provider/overview.md +19 -33
- package/docs/provider-resolution.md +210 -0
- package/docs/troubleshooting.md +12 -16
- package/package.json +2 -2
- package/src/cost/calculate.ts +6 -0
- package/src/cost/types.ts +4 -0
- package/src/provider/registry.ts +23 -8
- package/src/provider/types.ts +6 -0
- package/tsconfig.json +12 -5
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
# Provider Configuration
|
|
2
2
|
|
|
3
|
-
Configuration options for `
|
|
3
|
+
Configuration options for `createProviderRegistry()` and how to set up provider mappings.
|
|
4
4
|
|
|
5
5
|
## Key Concepts
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### ProviderRegistryConfig
|
|
8
8
|
|
|
9
|
-
| Option | Type
|
|
10
|
-
| ----------- |
|
|
11
|
-
| `providers` | `ProviderMap`
|
|
12
|
-
| `fallback` | `(modelId: string) => LanguageModel` | `undefined` | Fallback factory for unmapped prefixes |
|
|
9
|
+
| Option | Type | Default | Description |
|
|
10
|
+
| ----------- | ------------- | ------- | ----------------------------------------- |
|
|
11
|
+
| `providers` | `ProviderMap` | `{}` | Direct AI SDK provider mappings by prefix |
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
A registry with no providers throws on every call.
|
|
15
14
|
|
|
16
15
|
### ProviderMap
|
|
17
16
|
|
|
@@ -35,7 +34,7 @@ const providers: ProviderMap = {
|
|
|
35
34
|
Map each provider explicitly. Unmapped prefixes throw an error:
|
|
36
35
|
|
|
37
36
|
```ts
|
|
38
|
-
const resolve =
|
|
37
|
+
const resolve = createProviderRegistry({
|
|
39
38
|
providers: {
|
|
40
39
|
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
41
40
|
anthropic: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
@@ -44,54 +43,32 @@ const resolve = createModelResolver({
|
|
|
44
43
|
});
|
|
45
44
|
```
|
|
46
45
|
|
|
47
|
-
###
|
|
46
|
+
### With OpenRouter
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
Include OpenRouter as a provider using `@openrouter/ai-sdk-provider`:
|
|
50
49
|
|
|
51
50
|
```ts
|
|
52
|
-
|
|
53
|
-
providers: {
|
|
54
|
-
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
55
|
-
},
|
|
56
|
-
fallback: openrouter,
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### OpenRouter-Only
|
|
61
|
-
|
|
62
|
-
Route all models through OpenRouter:
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
const resolve = createModelResolver({
|
|
66
|
-
fallback: openrouter,
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Custom Fallback
|
|
51
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
71
52
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
const resolve = createModelResolver({
|
|
53
|
+
const registry = createProviderRegistry({
|
|
76
54
|
providers: {
|
|
77
55
|
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
78
|
-
|
|
79
|
-
fallback: (modelId: string) => {
|
|
80
|
-
const provider = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
81
|
-
return provider(modelId);
|
|
56
|
+
openrouter: createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }),
|
|
82
57
|
},
|
|
83
58
|
});
|
|
59
|
+
|
|
60
|
+
const lm = registry("openrouter/anthropic/claude-sonnet-4");
|
|
84
61
|
```
|
|
85
62
|
|
|
86
63
|
## Error Handling
|
|
87
64
|
|
|
88
|
-
`
|
|
65
|
+
`createProviderRegistry()` throws in these cases:
|
|
89
66
|
|
|
90
|
-
| Condition
|
|
91
|
-
|
|
|
92
|
-
| Empty model ID
|
|
93
|
-
| No prefix
|
|
94
|
-
| Unmapped prefix
|
|
67
|
+
| Condition | Error Message |
|
|
68
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
69
|
+
| Empty model ID | `Cannot resolve model: model ID is empty` |
|
|
70
|
+
| No prefix | `Cannot resolve model "<id>": no provider prefix` |
|
|
71
|
+
| Unmapped prefix | `Cannot resolve model "<id>": no provider mapped for "<prefix>"` |
|
|
95
72
|
|
|
96
73
|
## References
|
|
97
74
|
|
|
@@ -1,83 +1,64 @@
|
|
|
1
1
|
# OpenRouter Integration
|
|
2
2
|
|
|
3
|
-
OpenRouter acts as a model aggregator, routing requests to the underlying provider. `@
|
|
3
|
+
OpenRouter acts as a model aggregator, routing requests to the underlying provider. Use the `@openrouter/ai-sdk-provider` package directly to create an OpenRouter provider instance.
|
|
4
4
|
|
|
5
5
|
## Key Concepts
|
|
6
6
|
|
|
7
7
|
### API Key Resolution
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`createOpenRouter` from `@openrouter/ai-sdk-provider` resolves the API key in this order:
|
|
10
10
|
|
|
11
|
-
1. Explicit `apiKey` in options
|
|
11
|
+
1. Explicit `apiKey` in options
|
|
12
12
|
2. `OPENROUTER_API_KEY` environment variable
|
|
13
13
|
|
|
14
14
|
If neither is set, an error is thrown at call time.
|
|
15
15
|
|
|
16
|
-
### Cached Provider
|
|
17
|
-
|
|
18
|
-
The `openrouter` export is a cached resolver. The underlying provider instance is created once and reused across calls. If `OPENROUTER_API_KEY` changes at runtime, the cache invalidates and a new provider is created.
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
const lm = openrouter("openai/gpt-4.1");
|
|
22
|
-
```
|
|
23
|
-
|
|
24
16
|
### Provider Factory
|
|
25
17
|
|
|
26
|
-
`createOpenRouter` creates a
|
|
18
|
+
`createOpenRouter` from `@openrouter/ai-sdk-provider` creates a provider instance:
|
|
27
19
|
|
|
28
20
|
```ts
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
22
|
+
|
|
23
|
+
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
24
|
+
const lm = openrouter("openai/gpt-4.1");
|
|
31
25
|
```
|
|
32
26
|
|
|
33
27
|
## Usage
|
|
34
28
|
|
|
35
|
-
### As a
|
|
29
|
+
### As a Provider in the Registry
|
|
36
30
|
|
|
37
|
-
The most common pattern is
|
|
31
|
+
The most common pattern is registering OpenRouter as a provider in `createProviderRegistry()`:
|
|
38
32
|
|
|
39
33
|
```ts
|
|
40
|
-
|
|
34
|
+
import { createProviderRegistry } from "@funkai/models";
|
|
35
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
36
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
37
|
+
|
|
38
|
+
const registry = createProviderRegistry({
|
|
41
39
|
providers: {
|
|
42
40
|
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
41
|
+
openrouter: createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }),
|
|
43
42
|
},
|
|
44
|
-
fallback: openrouter,
|
|
45
43
|
});
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
Models with an `"openai"` prefix route
|
|
49
|
-
|
|
50
|
-
### As the Only Provider
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
const resolve = createModelResolver({
|
|
54
|
-
fallback: openrouter,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const lm = resolve("anthropic/claude-sonnet-4");
|
|
58
|
-
```
|
|
46
|
+
Models with an `"openai"` prefix route through `@ai-sdk/openai`. Models with an `"openrouter"` prefix route through OpenRouter.
|
|
59
47
|
|
|
60
48
|
### Direct Usage
|
|
61
49
|
|
|
62
|
-
Use `
|
|
50
|
+
Use `createOpenRouter` directly without a registry:
|
|
63
51
|
|
|
64
52
|
```ts
|
|
65
|
-
|
|
66
|
-
```
|
|
53
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
67
54
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
const provider = createOpenRouter({
|
|
72
|
-
apiKey: process.env.OPENROUTER_API_KEY,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const lm = provider("mistral/mistral-large-latest");
|
|
55
|
+
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
56
|
+
const lm = openrouter("openai/gpt-4.1");
|
|
76
57
|
```
|
|
77
58
|
|
|
78
59
|
## Configuration
|
|
79
60
|
|
|
80
|
-
`createOpenRouter`
|
|
61
|
+
`createOpenRouter` from `@openrouter/ai-sdk-provider` accepts:
|
|
81
62
|
|
|
82
63
|
| Option | Type | Default | Description |
|
|
83
64
|
| -------- | -------- | -------------------------------- | ------------------ |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Provider Resolution
|
|
2
2
|
|
|
3
|
-
Provider resolution maps model ID strings to AI SDK `LanguageModel` instances. `
|
|
3
|
+
Provider resolution maps model ID strings to AI SDK `LanguageModel` instances. `createProviderRegistry()` extracts the provider prefix from a model ID and dispatches to the appropriate provider factory.
|
|
4
4
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
@@ -23,20 +23,16 @@ Provider resolution maps model ID strings to AI SDK `LanguageModel` instances. `
|
|
|
23
23
|
}}%%
|
|
24
24
|
sequenceDiagram
|
|
25
25
|
participant C as Caller
|
|
26
|
-
participant R as
|
|
26
|
+
participant R as ProviderRegistry
|
|
27
27
|
participant P as ProviderFactory
|
|
28
|
-
participant F as Fallback
|
|
29
28
|
|
|
30
|
-
C->>R:
|
|
29
|
+
C->>R: registry("openai/gpt-4.1")
|
|
31
30
|
R->>R: Extract prefix "openai"
|
|
32
31
|
|
|
33
32
|
alt Provider mapped
|
|
34
33
|
R->>P: factory("gpt-4.1")
|
|
35
34
|
P-->>R: LanguageModel
|
|
36
|
-
else No match
|
|
37
|
-
R->>F: fallback("openai/gpt-4.1")
|
|
38
|
-
F-->>R: LanguageModel
|
|
39
|
-
else No match, no fallback
|
|
35
|
+
else No match
|
|
40
36
|
R-->>C: Error thrown
|
|
41
37
|
end
|
|
42
38
|
|
|
@@ -47,17 +43,16 @@ sequenceDiagram
|
|
|
47
43
|
|
|
48
44
|
### Resolution Algorithm
|
|
49
45
|
|
|
50
|
-
When `
|
|
46
|
+
When `registry("openai/gpt-4.1")` is called:
|
|
51
47
|
|
|
52
48
|
1. The model ID is validated (non-empty)
|
|
53
49
|
2. The prefix before the first `/` is extracted (`"openai"`)
|
|
54
50
|
3. If a provider factory is mapped for that prefix, it receives the model portion (`"gpt-4.1"`)
|
|
55
|
-
4. If no provider matches,
|
|
56
|
-
5. If no fallback exists, an error is thrown
|
|
51
|
+
4. If no provider matches, an error is thrown
|
|
57
52
|
|
|
58
53
|
### Model IDs Without a Prefix
|
|
59
54
|
|
|
60
|
-
Model IDs without a `/` (e.g. `"gpt-4.1"`)
|
|
55
|
+
Model IDs without a `/` (e.g. `"gpt-4.1"`) have no prefix to match, so an error is thrown. Always use the full `"provider/model"` format.
|
|
61
56
|
|
|
62
57
|
### ProviderFactory
|
|
63
58
|
|
|
@@ -83,45 +78,36 @@ const providers: ProviderMap = {
|
|
|
83
78
|
|
|
84
79
|
## Usage
|
|
85
80
|
|
|
86
|
-
### Basic
|
|
81
|
+
### Basic Registry
|
|
87
82
|
|
|
88
83
|
```ts
|
|
89
|
-
const
|
|
84
|
+
const registry = createProviderRegistry({
|
|
90
85
|
providers: {
|
|
91
86
|
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
92
87
|
},
|
|
93
88
|
});
|
|
94
89
|
|
|
95
|
-
const lm =
|
|
90
|
+
const lm = registry("openai/gpt-4.1");
|
|
96
91
|
```
|
|
97
92
|
|
|
98
|
-
###
|
|
93
|
+
### Multi-Provider Registry
|
|
99
94
|
|
|
100
95
|
```ts
|
|
101
|
-
|
|
96
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
97
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
98
|
+
|
|
99
|
+
const registry = createProviderRegistry({
|
|
102
100
|
providers: {
|
|
103
101
|
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
102
|
+
anthropic: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
104
103
|
},
|
|
105
|
-
fallback: openrouter,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const lm1 = resolve("openai/gpt-4.1");
|
|
109
|
-
const lm2 = resolve("anthropic/claude-sonnet-4");
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
`lm1` routes through the direct OpenAI provider. `lm2` has no mapped provider for `"anthropic"`, so it falls through to the OpenRouter fallback.
|
|
113
|
-
|
|
114
|
-
### Fallback-Only Resolver
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
const resolve = createModelResolver({
|
|
118
|
-
fallback: openrouter,
|
|
119
104
|
});
|
|
120
105
|
|
|
121
|
-
const
|
|
106
|
+
const lm1 = registry("openai/gpt-4.1");
|
|
107
|
+
const lm2 = registry("anthropic/claude-sonnet-4");
|
|
122
108
|
```
|
|
123
109
|
|
|
124
|
-
|
|
110
|
+
`lm1` routes through `@ai-sdk/openai`. `lm2` routes through `@ai-sdk/anthropic`.
|
|
125
111
|
|
|
126
112
|
## References
|
|
127
113
|
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Provider Resolution
|
|
2
|
+
|
|
3
|
+
Provider resolution maps model ID strings to AI SDK `LanguageModel` instances. `createProviderRegistry()` extracts the provider prefix from a model ID and dispatches to the appropriate provider factory.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
%%{init: {
|
|
9
|
+
'theme': 'base',
|
|
10
|
+
'themeVariables': {
|
|
11
|
+
'primaryColor': '#313244',
|
|
12
|
+
'primaryTextColor': '#cdd6f4',
|
|
13
|
+
'primaryBorderColor': '#6c7086',
|
|
14
|
+
'lineColor': '#89b4fa',
|
|
15
|
+
'secondaryColor': '#45475a',
|
|
16
|
+
'tertiaryColor': '#1e1e2e',
|
|
17
|
+
'actorBkg': '#313244',
|
|
18
|
+
'actorBorder': '#89b4fa',
|
|
19
|
+
'actorTextColor': '#cdd6f4',
|
|
20
|
+
'signalColor': '#cdd6f4',
|
|
21
|
+
'signalTextColor': '#cdd6f4'
|
|
22
|
+
}
|
|
23
|
+
}}%%
|
|
24
|
+
sequenceDiagram
|
|
25
|
+
participant C as Caller
|
|
26
|
+
participant R as ProviderRegistry
|
|
27
|
+
participant P as ProviderFactory
|
|
28
|
+
|
|
29
|
+
C->>R: registry("openai/gpt-4.1")
|
|
30
|
+
R->>R: Extract prefix "openai"
|
|
31
|
+
|
|
32
|
+
alt Provider mapped
|
|
33
|
+
R->>P: factory("gpt-4.1")
|
|
34
|
+
P-->>R: LanguageModel
|
|
35
|
+
else No match
|
|
36
|
+
R-->>C: Error thrown
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
R-->>C: LanguageModel
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When `registry("openai/gpt-4.1")` is called:
|
|
43
|
+
|
|
44
|
+
1. The model ID is validated (non-empty)
|
|
45
|
+
2. The prefix before the first `/` is extracted (`"openai"`)
|
|
46
|
+
3. If a provider factory is mapped for that prefix, it receives the model portion (`"gpt-4.1"`)
|
|
47
|
+
4. If no provider matches, an error is thrown
|
|
48
|
+
|
|
49
|
+
Model IDs without a `/` (e.g. `"gpt-4.1"`) have no prefix to match, so an error is thrown. Always use the full `"provider/model"` format.
|
|
50
|
+
|
|
51
|
+
## createProviderRegistry() API
|
|
52
|
+
|
|
53
|
+
### ProviderRegistryConfig
|
|
54
|
+
|
|
55
|
+
| Option | Type | Default | Description |
|
|
56
|
+
| ----------- | ------------- | ------- | ----------------------------------------- |
|
|
57
|
+
| `providers` | `ProviderMap` | `{}` | Direct AI SDK provider mappings by prefix |
|
|
58
|
+
|
|
59
|
+
A registry with no providers throws on every call.
|
|
60
|
+
|
|
61
|
+
### ProviderMap
|
|
62
|
+
|
|
63
|
+
`ProviderMap` is `Readonly<Record<string, ProviderFactory>>`. Keys are provider prefixes that match the portion before `/` in a model ID.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const providers: ProviderMap = {
|
|
67
|
+
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
68
|
+
anthropic: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### ProviderFactory
|
|
73
|
+
|
|
74
|
+
`ProviderFactory` is `(modelName: string) => LanguageModel`. AI SDK provider constructors (`createOpenAI`, `createAnthropic`, etc.) return compatible factory functions.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
78
|
+
|
|
79
|
+
const factory: ProviderFactory = createOpenAI({ apiKey: "..." });
|
|
80
|
+
const lm = factory("gpt-4.1");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Setting Up Providers
|
|
84
|
+
|
|
85
|
+
### Install Provider SDKs
|
|
86
|
+
|
|
87
|
+
Install the AI SDK providers you want to use:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm add @ai-sdk/openai @ai-sdk/anthropic
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Basic Registry
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { createProviderRegistry } from "@funkai/models";
|
|
97
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
98
|
+
|
|
99
|
+
const registry = createProviderRegistry({
|
|
100
|
+
providers: {
|
|
101
|
+
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const lm = registry("openai/gpt-4.1");
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Multi-Provider Registry
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { createProviderRegistry } from "@funkai/models";
|
|
112
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
113
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
114
|
+
|
|
115
|
+
const registry = createProviderRegistry({
|
|
116
|
+
providers: {
|
|
117
|
+
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
118
|
+
anthropic: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const gpt = registry("openai/gpt-4.1");
|
|
123
|
+
const claude = registry("anthropic/claude-sonnet-4");
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Use with Agents
|
|
127
|
+
|
|
128
|
+
Pass the registry to `@funkai/agents` by resolving the model before creating the agent:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { agent } from "@funkai/agents";
|
|
132
|
+
|
|
133
|
+
const summarizer = agent({
|
|
134
|
+
name: "summarizer",
|
|
135
|
+
model: registry("openai/gpt-4.1"),
|
|
136
|
+
prompt: ({ input }) => `Summarize:\n\n${input.text}`,
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## OpenRouter Integration
|
|
141
|
+
|
|
142
|
+
OpenRouter acts as a model aggregator, routing requests to the underlying provider. Use the `@openrouter/ai-sdk-provider` package to create an OpenRouter provider instance.
|
|
143
|
+
|
|
144
|
+
### API Key Resolution
|
|
145
|
+
|
|
146
|
+
`createOpenRouter` resolves the API key in this order:
|
|
147
|
+
|
|
148
|
+
1. Explicit `apiKey` in options
|
|
149
|
+
2. `OPENROUTER_API_KEY` environment variable
|
|
150
|
+
|
|
151
|
+
If neither is set, an error is thrown at call time.
|
|
152
|
+
|
|
153
|
+
### Configuration
|
|
154
|
+
|
|
155
|
+
| Option | Type | Default | Description |
|
|
156
|
+
| -------- | -------- | -------------------------------- | ------------------ |
|
|
157
|
+
| `apiKey` | `string` | `process.env.OPENROUTER_API_KEY` | OpenRouter API key |
|
|
158
|
+
|
|
159
|
+
Additional options are forwarded directly to the underlying `@openrouter/ai-sdk-provider`.
|
|
160
|
+
|
|
161
|
+
### As a Registry Provider
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { createProviderRegistry } from "@funkai/models";
|
|
165
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
166
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
167
|
+
|
|
168
|
+
const registry = createProviderRegistry({
|
|
169
|
+
providers: {
|
|
170
|
+
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
171
|
+
openrouter: createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const lm = registry("openrouter/anthropic/claude-sonnet-4");
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Models with an `"openai"` prefix route through `@ai-sdk/openai`. Models with an `"openrouter"` prefix route through OpenRouter.
|
|
179
|
+
|
|
180
|
+
### Direct Usage
|
|
181
|
+
|
|
182
|
+
Use `createOpenRouter` directly without a registry:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
186
|
+
|
|
187
|
+
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
188
|
+
const lm = openrouter("openai/gpt-4.1");
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Resources
|
|
192
|
+
|
|
193
|
+
- [OpenRouter Documentation](https://openrouter.ai/docs)
|
|
194
|
+
- [@openrouter/ai-sdk-provider](https://www.npmjs.com/package/@openrouter/ai-sdk-provider)
|
|
195
|
+
|
|
196
|
+
## Error Handling
|
|
197
|
+
|
|
198
|
+
`createProviderRegistry()` throws in these cases:
|
|
199
|
+
|
|
200
|
+
| Condition | Error Message |
|
|
201
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
202
|
+
| Empty model ID | `Cannot resolve model: model ID is empty` |
|
|
203
|
+
| No prefix | `Cannot resolve model "<id>": no provider prefix` |
|
|
204
|
+
| Unmapped prefix | `Cannot resolve model "<id>": no provider mapped for "<prefix>"` |
|
|
205
|
+
|
|
206
|
+
## References
|
|
207
|
+
|
|
208
|
+
- [Model Catalog](catalog.md)
|
|
209
|
+
- [Cost Tracking](cost-tracking.md)
|
|
210
|
+
- [Troubleshooting](troubleshooting.md)
|
package/docs/troubleshooting.md
CHANGED
|
@@ -4,7 +4,7 @@ Common issues and fixes for `@funkai/models`.
|
|
|
4
4
|
|
|
5
5
|
## Cannot resolve model: model ID is empty
|
|
6
6
|
|
|
7
|
-
The model ID passed to the
|
|
7
|
+
The model ID passed to the registry is an empty string or whitespace.
|
|
8
8
|
|
|
9
9
|
**Fix:** Ensure the model ID is a non-empty string:
|
|
10
10
|
|
|
@@ -12,30 +12,27 @@ The model ID passed to the resolver is an empty string or whitespace.
|
|
|
12
12
|
const lm = resolve("openai/gpt-4.1");
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
## Cannot resolve model: no provider prefix
|
|
15
|
+
## Cannot resolve model: no provider prefix
|
|
16
16
|
|
|
17
|
-
A model ID without a `/` (e.g. `"gpt-4.1"`) was passed to
|
|
17
|
+
A model ID without a `/` (e.g. `"gpt-4.1"`) was passed to the registry.
|
|
18
18
|
|
|
19
|
-
**Fix:**
|
|
19
|
+
**Fix:** Use the full `"provider/model"` format:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
const
|
|
23
|
-
fallback: openrouter,
|
|
24
|
-
});
|
|
22
|
+
const lm = registry("openai/gpt-4.1");
|
|
25
23
|
```
|
|
26
24
|
|
|
27
|
-
## Cannot resolve model: no provider mapped for "x"
|
|
25
|
+
## Cannot resolve model: no provider mapped for "x"
|
|
28
26
|
|
|
29
|
-
The model ID prefix does not match any key in the `providers` map
|
|
27
|
+
The model ID prefix does not match any key in the `providers` map.
|
|
30
28
|
|
|
31
|
-
**Fix:** Add the provider to the `providers` map
|
|
29
|
+
**Fix:** Add the provider to the `providers` map:
|
|
32
30
|
|
|
33
31
|
```ts
|
|
34
|
-
const
|
|
32
|
+
const registry = createProviderRegistry({
|
|
35
33
|
providers: {
|
|
36
34
|
openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
37
35
|
},
|
|
38
|
-
fallback: openrouter,
|
|
39
36
|
});
|
|
40
37
|
```
|
|
41
38
|
|
|
@@ -94,7 +91,6 @@ const id: ModelId = "openai/gpt-4.1";
|
|
|
94
91
|
|
|
95
92
|
## References
|
|
96
93
|
|
|
97
|
-
- [Model Catalog](catalog
|
|
98
|
-
- [Provider Resolution](provider
|
|
99
|
-
- [Cost
|
|
100
|
-
- [Setup Resolver Guide](guides/setup-resolver.md)
|
|
94
|
+
- [Model Catalog](catalog.md)
|
|
95
|
+
- [Provider Resolution](provider-resolution.md)
|
|
96
|
+
- [Cost Tracking](cost-tracking.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funkai/models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Model catalog, provider resolution, and cost calculations for the funkai AI SDK",
|
|
6
6
|
"keywords": [
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"access": "public"
|
|
118
118
|
},
|
|
119
119
|
"dependencies": {
|
|
120
|
-
"ai": "^6.0.
|
|
120
|
+
"ai": "^6.0.136",
|
|
121
121
|
"type-fest": "^5.5.0"
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
package/src/cost/calculate.ts
CHANGED
|
@@ -8,6 +8,12 @@ import type { TokenUsage } from "@/provider/types.js";
|
|
|
8
8
|
* Multiplies each token count by the corresponding per-token pricing rate.
|
|
9
9
|
* Optional pricing fields (cache) default to `0` when absent.
|
|
10
10
|
*
|
|
11
|
+
* **Reasoning token semantics**: `reasoningTokens` in {@link TokenUsage} are
|
|
12
|
+
* expected to be **exclusive** of `outputTokens` — they are billed separately.
|
|
13
|
+
* If your provider includes reasoning tokens _within_ the `outputTokens` count,
|
|
14
|
+
* you must deduct them before passing usage to this function to avoid
|
|
15
|
+
* double-counting output costs.
|
|
16
|
+
*
|
|
11
17
|
* @param usage - Token counts from a model invocation.
|
|
12
18
|
* @param pricing - Per-token pricing rates for the model.
|
|
13
19
|
* @returns A {@link UsageCost} with per-field and total costs in USD.
|
package/src/cost/types.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each field is the dollar cost for that token category.
|
|
5
5
|
* All fields are non-negative numbers. Fields that don't apply are `0`.
|
|
6
|
+
*
|
|
7
|
+
* **Note**: Values may exhibit floating-point imprecision inherent to
|
|
8
|
+
* JavaScript `number` (IEEE 754 double-precision) arithmetic. Do not rely
|
|
9
|
+
* on exact equality comparisons against expected cost values.
|
|
6
10
|
*/
|
|
7
11
|
export interface UsageCost {
|
|
8
12
|
/** Cost for input tokens. */
|
package/src/provider/registry.ts
CHANGED
|
@@ -3,14 +3,6 @@ import { createProviderRegistry as baseCreateProviderRegistry } from "ai";
|
|
|
3
3
|
import type { ModelId } from "@/catalog/index.js";
|
|
4
4
|
import type { LanguageModel } from "@/provider/types.js";
|
|
5
5
|
|
|
6
|
-
/** @private */
|
|
7
|
-
function errorMessage(error: unknown): string {
|
|
8
|
-
if (error instanceof Error) {
|
|
9
|
-
return error.message;
|
|
10
|
-
}
|
|
11
|
-
return String(error);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
/**
|
|
15
7
|
* Extract the provider type accepted by the AI SDK's `createProviderRegistry`.
|
|
16
8
|
*
|
|
@@ -67,6 +59,8 @@ export type ProviderRegistry = (modelId: ModelId) => LanguageModel;
|
|
|
67
59
|
*
|
|
68
60
|
* @param config - Provider mappings.
|
|
69
61
|
* @returns A resolver function that maps model IDs to {@link LanguageModel} instances.
|
|
62
|
+
* @throws {Error} If the model ID is empty, missing the `provider/model` format,
|
|
63
|
+
* or the underlying AI SDK registry fails to resolve the provider or model.
|
|
70
64
|
*
|
|
71
65
|
* @example
|
|
72
66
|
* ```typescript
|
|
@@ -102,6 +96,10 @@ export function createProviderRegistry(config: ProviderRegistryConfig): Provider
|
|
|
102
96
|
// Cast needed: AI SDK overloads expect `provider/model` template literal,
|
|
103
97
|
// But our ModelId is a branded string union. The runtime validates the format.
|
|
104
98
|
try {
|
|
99
|
+
// SAFETY: The first cast (`as \`${string}/${string}\``) is safe because we
|
|
100
|
+
// Validated above that `modelId` contains `/`. The second cast (`as
|
|
101
|
+
// LanguageModel`) narrows from the AI SDK's broader LanguageModel union to
|
|
102
|
+
// The v3 specification, which is the only version we support.
|
|
105
103
|
return inner.languageModel(modelId as `${string}/${string}`) as LanguageModel;
|
|
106
104
|
} catch (error) {
|
|
107
105
|
throw new Error(`Failed to resolve model "${modelId}": ${errorMessage(error)}`, {
|
|
@@ -110,3 +108,20 @@ export function createProviderRegistry(config: ProviderRegistryConfig): Provider
|
|
|
110
108
|
}
|
|
111
109
|
};
|
|
112
110
|
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Extract a human-readable message from an unknown error value.
|
|
116
|
+
*
|
|
117
|
+
* @param error - The caught error value.
|
|
118
|
+
* @returns The error message string.
|
|
119
|
+
*
|
|
120
|
+
* @private
|
|
121
|
+
*/
|
|
122
|
+
function errorMessage(error: unknown): string {
|
|
123
|
+
if (error instanceof Error) {
|
|
124
|
+
return error.message;
|
|
125
|
+
}
|
|
126
|
+
return String(error);
|
|
127
|
+
}
|