@coder/ai-sdk-provider 0.3.2 → 0.3.4
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 +135 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,12 +76,19 @@ coder("gpt-4o"); // → OpenAI surface
|
|
|
76
76
|
coder("claude-sonnet-4-6"); // → Anthropic surface (heuristic)
|
|
77
77
|
coder.openai("claude-sonnet-4"); // → OpenAI surface (e.g. Copilot)
|
|
78
78
|
coder.anthropic("claude-opus-4-5"); // → Anthropic surface (explicit)
|
|
79
|
-
coder.textEmbeddingModel("text-embedding-3-small"); // → OpenAI surface
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
Model ids are passed through **unchanged** to the upstream provider (no
|
|
83
82
|
`vendor/model` namespacing) — use whatever ids your deployment's providers accept.
|
|
84
83
|
|
|
84
|
+
**Embeddings are not supported yet.** AI Gateway does not intercept
|
|
85
|
+
`/v1/embeddings`, so `coder.textEmbeddingModel(id)` — and the embedding
|
|
86
|
+
accessors on `coder.openai` — throw the AI SDK's `NoSuchModelError`
|
|
87
|
+
immediately instead of emitting a request the gateway rejects with a 404. The
|
|
88
|
+
accessors stay so they can light up without a breaking change once the gateway
|
|
89
|
+
adds an embeddings route — see
|
|
90
|
+
[coder/ai-sdk#69](https://github.com/coder/ai-sdk/issues/69).
|
|
91
|
+
|
|
85
92
|
## Authentication
|
|
86
93
|
|
|
87
94
|
**Centralized mode (default).** Pass your Coder API token as `apiKey`. AI Gateway
|
|
@@ -115,6 +122,133 @@ createCoder({
|
|
|
115
122
|
| `providers` | `{ openai?, anthropic? }` | `openai` / `anthropic` | Override the admin-configured provider path segments. |
|
|
116
123
|
| `fetch` | `typeof fetch` | global `fetch` | Custom fetch (testing / middleware). |
|
|
117
124
|
|
|
125
|
+
## Enterprise governance & security
|
|
126
|
+
|
|
127
|
+
Reference for security reviewers evaluating this package. The boundary between
|
|
128
|
+
the two kinds of claims below matters: **client behavior** (what this package
|
|
129
|
+
puts on the wire) is verifiable in [`src/provider.ts`](./src/provider.ts) —
|
|
130
|
+
~175 lines with no dependencies beyond the official AI SDK provider packages —
|
|
131
|
+
while **gateway behavior** (key custody, audit capture, retention) is a
|
|
132
|
+
property of your Coder deployment, enforced server-side regardless of what any
|
|
133
|
+
client does, and documented in the
|
|
134
|
+
[AI Gateway docs](https://coder.com/docs/ai-coder/ai-gateway).
|
|
135
|
+
|
|
136
|
+
### Data flow
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
your app ──HTTP(S)──▶ your Coder deployment ──▶ upstream provider
|
|
140
|
+
(this package) (AI Gateway intercepts (Anthropic, OpenAI,
|
|
141
|
+
/api/v2/aibridge/…) Bedrock, Copilot, …)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**What leaves your app.** This package's only network destination is the
|
|
145
|
+
`baseURL` you configure. It never contacts upstream vendors directly and adds
|
|
146
|
+
no telemetry of its own; each request is exactly what the AI SDK builds for a
|
|
147
|
+
normal provider call:
|
|
148
|
+
|
|
149
|
+
- **URL** — `POST <baseURL>/api/v2/aibridge/<provider>/v1/chat/completions`
|
|
150
|
+
(OpenAI surface) or `…/v1/messages` (Anthropic surface). `/api/v2/aibridge`
|
|
151
|
+
is this package's
|
|
152
|
+
default; deployments also serve the post-rename alias `/api/v2/ai-gateway`
|
|
153
|
+
(see `aiGatewayPath`).
|
|
154
|
+
- **Auth headers** — per the mode matrix below.
|
|
155
|
+
- **Body** — standard OpenAI-/Anthropic-format JSON: the model id (passed
|
|
156
|
+
through unchanged), your full prompt/message content, tool definitions, and
|
|
157
|
+
sampling parameters. Prompt content is visible to the Gateway — that is what
|
|
158
|
+
enables auditing.
|
|
159
|
+
- Anything you add via the `headers` option.
|
|
160
|
+
|
|
161
|
+
Transport security follows the scheme of your `baseURL` — the client does not
|
|
162
|
+
enforce `https://` (an `http://` URL sends tokens, keys, and prompts in
|
|
163
|
+
plaintext). Always use an HTTPS deployment URL outside trusted local
|
|
164
|
+
environments.
|
|
165
|
+
|
|
166
|
+
**What the Gateway does before forwarding** _(server-side —
|
|
167
|
+
[authentication docs](https://coder.com/docs/ai-coder/ai-gateway/auth))_: it
|
|
168
|
+
authenticates the token as an active Coder user and rejects the request
|
|
169
|
+
outright when it is missing or invalid (nothing is forwarded upstream), strips
|
|
170
|
+
all Coder credentials from the outbound request, and attaches the upstream
|
|
171
|
+
credential for the mode in use.
|
|
172
|
+
|
|
173
|
+
### Credential isolation: centralized vs. BYOK
|
|
174
|
+
|
|
175
|
+
| | **Centralized (default)** | **BYOK** |
|
|
176
|
+
| ------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| Your app holds | Coder API token only | Coder API token **+** the user's own upstream key |
|
|
178
|
+
| On the wire | `Authorization: Bearer <Coder token>` (both surfaces) | `X-Coder-AI-Governance-Token: <Coder token>`; upstream key in `Authorization: Bearer` (OpenAI surface) / `x-api-key` (Anthropic surface) |
|
|
179
|
+
| Upstream provider keys live | On the deployment, admin-configured — never distributed to developers | With the individual user; forwarded per request, bypassing the deployment's central key pool |
|
|
180
|
+
| Coder token forwarded upstream? | **No** — stripped and replaced by the deployment's provider key | **No** — the governance header is stripped before forwarding |
|
|
181
|
+
| Admin control | Provider and key configuration, rotation, failover | Can be disabled deployment-wide (`CODER_AI_GATEWAY_ALLOW_BYOK=false` rejects requests carrying the governance header with `403`) |
|
|
182
|
+
|
|
183
|
+
Both modes are audited identically: audit records store _which credential
|
|
184
|
+
kind_ was used (`centralized` / `byok`), not the credential itself.
|
|
185
|
+
|
|
186
|
+
### Audit capture
|
|
187
|
+
|
|
188
|
+
Every request is attributed to the Coder user whose token authenticated it —
|
|
189
|
+
per user, per request, in both modes. Per intercepted request the Gateway
|
|
190
|
+
records _(server-side —
|
|
191
|
+
[audit docs](https://coder.com/docs/ai-coder/ai-gateway/audit))_:
|
|
192
|
+
|
|
193
|
+
- **Identity & metadata** — initiating user, provider, model, client,
|
|
194
|
+
credential kind, timestamps.
|
|
195
|
+
- **Last user prompt** — earlier turns and system prompts are not stored.
|
|
196
|
+
- **Token usage** — input / output / cache counts.
|
|
197
|
+
- **Tool calls** — tool name and arguments; tool _results_ are not stored.
|
|
198
|
+
- **Model reasoning** — extended-thinking / reasoning-summary content when
|
|
199
|
+
present.
|
|
200
|
+
|
|
201
|
+
Model-generated response text is **discarded**, not stored. Retention defaults
|
|
202
|
+
to **60 days** and is configurable (`CODER_AI_GATEWAY_RETENTION`; `0` keeps
|
|
203
|
+
data indefinitely). Auditors browse sessions and causal tool-call chains in
|
|
204
|
+
the deployment dashboard under `/ai-gateway/sessions`.
|
|
205
|
+
|
|
206
|
+
### Required Coder permissions
|
|
207
|
+
|
|
208
|
+
| To… | You need |
|
|
209
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| Send requests through the Gateway | An active Coder user with a valid API token — no extra role. (RBAC: members hold create/update on `aibridge_interception`, i.e. recording their own traffic.) |
|
|
211
|
+
| Read audit data (prompts, tool calls, usage) | The **Owner** or **Auditor** role. Regular members cannot read interceptions back — not even their own. |
|
|
212
|
+
| Configure providers / keys, toggle BYOK | Deployment administrator (server flags / deployment configuration). |
|
|
213
|
+
| Use the feature at all | A deployment licensed for [AI Governance](https://coder.com/docs/ai-coder/ai-governance) — the Gateway is license-gated server-side. |
|
|
214
|
+
|
|
215
|
+
### Security FAQ
|
|
216
|
+
|
|
217
|
+
- **Does prompt data ever go anywhere other than my deployment?** This package
|
|
218
|
+
only ever _initiates_ requests to `baseURL`; onward traffic to the upstream
|
|
219
|
+
vendor originates from your deployment (or its standalone gateway replicas),
|
|
220
|
+
using the providers your admins configured. One standard-`fetch` caveat:
|
|
221
|
+
redirects are followed by default, so a cross-origin redirect issued by your
|
|
222
|
+
deployment or an intermediary would resend the request — prompt body and
|
|
223
|
+
non-`Authorization` headers (in BYOK mode that includes `x-api-key` and the
|
|
224
|
+
governance token) — to the redirect target. To forbid this, supply a custom
|
|
225
|
+
fetch: `fetch: (url, init) => fetch(url, { ...init, redirect: "error" })`.
|
|
226
|
+
- **Do developers ever handle raw provider keys?** Centralized mode: no —
|
|
227
|
+
developers only ever hold a Coder token. BYOK mode: they supply their own
|
|
228
|
+
personal key, which is forwarded per request without entering central custody.
|
|
229
|
+
- **What is the blast radius of a leaked Coder token?** A Coder API token is
|
|
230
|
+
not an AI-only credential — it grants the bearer the user's **full Coder API
|
|
231
|
+
permissions** (workspaces, templates, and anything else that user's roles
|
|
232
|
+
allow), and, until revoked or expired, AI usage through your Gateway (fully
|
|
233
|
+
attributed to that user). What it can _not_ do is authenticate to upstream
|
|
234
|
+
vendors: under normal Gateway forwarding it is stripped and does not leave
|
|
235
|
+
your deployment (the cross-origin-redirect caveat above is the exception —
|
|
236
|
+
a redirect can resend non-`Authorization` headers, including the governance
|
|
237
|
+
header, before the Gateway ever sees them). Treat a leak as a Coder account
|
|
238
|
+
compromise — revoke the token — and prefer short-lived, dedicated tokens
|
|
239
|
+
for AI workloads.
|
|
240
|
+
- **Is model-generated content stored?** Partially. Assistant _response text_
|
|
241
|
+
is discarded, but two model-generated artifacts are retained for auditing:
|
|
242
|
+
reasoning content (extended thinking / reasoning summaries) and tool-call
|
|
243
|
+
arguments — alongside the last user prompt and token counts.
|
|
244
|
+
- **Can I verify the client claims myself?** Yes: [`src/provider.ts`](./src/provider.ts)
|
|
245
|
+
is the entire wire-facing surface — it only selects base URLs and auth
|
|
246
|
+
headers, then delegates request construction to the official AI SDK provider
|
|
247
|
+
packages. [`test/provider.test.ts`](./test/provider.test.ts) asserts the
|
|
248
|
+
request URL, auth headers, and model pass-through for the chat/messages
|
|
249
|
+
routes in both auth modes; the underlying AI SDK packages add their own
|
|
250
|
+
protocol headers (e.g. `anthropic-version`) and are not re-tested here.
|
|
251
|
+
|
|
118
252
|
## Examples
|
|
119
253
|
|
|
120
254
|
Runnable scripts live in [`examples/`](./examples) (run against a real deployment via `tsx`):
|
package/dist/index.d.ts
CHANGED
|
@@ -63,7 +63,12 @@ interface CoderProvider {
|
|
|
63
63
|
chat(modelId: string): LanguageModelV4;
|
|
64
64
|
/** Shorthand for an {@link CoderProvider.anthropic} messages model. */
|
|
65
65
|
messages(modelId: string): LanguageModelV4;
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Text embeddings are not supported: AI Gateway does not yet intercept
|
|
68
|
+
* `/v1/embeddings`, so this always throws {@link NoSuchModelError} instead of
|
|
69
|
+
* emitting a request that the gateway rejects with a 404. Tracked in
|
|
70
|
+
* https://github.com/coder/ai-sdk/issues/69.
|
|
71
|
+
*/
|
|
67
72
|
textEmbeddingModel(modelId: string): EmbeddingModelV4;
|
|
68
73
|
}
|
|
69
74
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/provider.ts
|
|
2
2
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
3
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
4
|
+
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
4
5
|
var DEFAULT_AI_GATEWAY_PATH = "/api/v2/aibridge";
|
|
5
6
|
var DEFAULT_OPENAI_PROVIDER = "openai";
|
|
6
7
|
var DEFAULT_ANTHROPIC_PROVIDER = "anthropic";
|
|
@@ -11,6 +12,13 @@ function isAnthropicModelId(modelId) {
|
|
|
11
12
|
function trimTrailingSlash(url) {
|
|
12
13
|
return url.replace(/\/+$/, "");
|
|
13
14
|
}
|
|
15
|
+
function unsupportedEmbeddingModel(modelId) {
|
|
16
|
+
throw new NoSuchModelError({
|
|
17
|
+
modelId,
|
|
18
|
+
modelType: "embeddingModel",
|
|
19
|
+
message: `Coder AI Gateway does not yet intercept /v1/embeddings, so the embedding model "${modelId}" cannot be reached through it. See https://github.com/coder/ai-sdk/issues/69 for status.`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
14
22
|
function createCoder(settings) {
|
|
15
23
|
if (!settings.baseURL) {
|
|
16
24
|
throw new Error(
|
|
@@ -37,6 +45,8 @@ function createCoder(settings) {
|
|
|
37
45
|
fetch: settings.fetch,
|
|
38
46
|
includeUsage: true
|
|
39
47
|
});
|
|
48
|
+
openai.embeddingModel = unsupportedEmbeddingModel;
|
|
49
|
+
openai.textEmbeddingModel = unsupportedEmbeddingModel;
|
|
40
50
|
const anthropic = createAnthropic({
|
|
41
51
|
name: "coder.anthropic",
|
|
42
52
|
baseURL: anthropicBaseURL,
|
|
@@ -53,7 +63,7 @@ function createCoder(settings) {
|
|
|
53
63
|
anthropic,
|
|
54
64
|
chat: (modelId) => openai(modelId),
|
|
55
65
|
messages: (modelId) => anthropic(modelId),
|
|
56
|
-
textEmbeddingModel:
|
|
66
|
+
textEmbeddingModel: unsupportedEmbeddingModel
|
|
57
67
|
});
|
|
58
68
|
}
|
|
59
69
|
var coder = createCoder;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts"],"sourcesContent":["import { type AnthropicProvider, createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createOpenAICompatible, type OpenAICompatibleProvider } from \"@ai-sdk/openai-compatible\";\nimport type { EmbeddingModelV4, LanguageModelV4 } from \"@ai-sdk/provider\";\n\n/** Default mount path of AI Gateway on a Coder deployment. */\nconst DEFAULT_AI_GATEWAY_PATH = \"/api/v2/aibridge\";\n/** Default provider path segments (the admin-configured provider names). */\nconst DEFAULT_OPENAI_PROVIDER = \"openai\";\nconst DEFAULT_ANTHROPIC_PROVIDER = \"anthropic\";\n/** Header that carries the Coder token to AI Gateway in bring-your-own-key mode. */\nconst CODER_TOKEN_HEADER = \"X-Coder-AI-Governance-Token\";\n\nexport interface CoderProviderSettings {\n /**\n * Base URL of your Coder deployment, e.g. `https://coder.example.com`. Do NOT\n * include the AI Gateway path (`/api/v2/aibridge/...`) — it is appended for you.\n */\n baseURL: string;\n /**\n * The credential sent to AI Gateway.\n *\n * - **Centralized mode (default):** your **Coder API token**. AI Gateway holds\n * the upstream provider keys and brokers the call, so this is all you need.\n * - **BYOK mode** (when {@link CoderProviderSettings.coderToken} is also set):\n * your **upstream provider key**, which AI Gateway forwards to the upstream.\n */\n apiKey?: string;\n /**\n * Enables bring-your-own-key (BYOK) mode. This Coder API token is sent in the\n * `X-Coder-AI-Governance-Token` header to authenticate you to the gateway,\n * while {@link CoderProviderSettings.apiKey} carries your upstream provider key.\n * Leave unset for the default centralized mode.\n */\n coderToken?: string;\n /** Extra headers merged into every request to both surfaces. */\n headers?: Record<string, string>;\n /**\n * Mount path of AI Gateway on the deployment. Defaults to `/api/v2/aibridge`.\n * Exposed because the `aibridge` path segment may change in a future Coder\n * release (the feature was renamed \"AI Gateway\").\n */\n aiGatewayPath?: string;\n /**\n * Override the provider path segments — the *admin-configured provider names*\n * on the deployment (e.g. `anthropic-corp`). Default to `openai` / `anthropic`.\n */\n providers?: {\n openai?: string;\n anthropic?: string;\n };\n /** Custom fetch implementation (useful for testing or middleware). */\n fetch?: typeof globalThis.fetch;\n}\n\nexport interface CoderProvider {\n /** Route a model id to a surface by heuristic (Claude ids → Anthropic surface). */\n (modelId: string): LanguageModelV4;\n /** Route a model id to a surface by heuristic (Claude ids → Anthropic surface). */\n languageModel(modelId: string): LanguageModelV4;\n /**\n * AI Gateway's OpenAI-compatible surface (`/aibridge/openai/v1`). Reaches the\n * OpenAI / Azure / Google / OpenRouter / Vercel / openai-compat and Copilot\n * upstreams. Routing is by URL, so any model id sent here hits this surface.\n */\n openai: OpenAICompatibleProvider;\n /**\n * AI Gateway's Anthropic-compatible surface (`/aibridge/anthropic`). Reaches\n * native Claude and Bedrock-hosted Claude.\n */\n anthropic: AnthropicProvider;\n /** Shorthand for an {@link CoderProvider.openai} chat model. */\n chat(modelId: string): LanguageModelV4;\n /** Shorthand for an {@link CoderProvider.anthropic} messages model. */\n messages(modelId: string): LanguageModelV4;\n /** Text-embedding model via the OpenAI-compatible surface. */\n textEmbeddingModel(modelId: string): EmbeddingModelV4;\n}\n\n/**\n * Heuristic for the bare {@link CoderProvider} call: which AI Gateway surface\n * should a model id route to? Claude / Anthropic ids go to the Anthropic\n * surface; everything else to the OpenAI-compatible surface. Use the explicit\n * `.openai(id)` / `.anthropic(id)` accessors to override (e.g. to reach Claude\n * through a Copilot-typed provider on the OpenAI surface).\n */\nexport function isAnthropicModelId(modelId: string): boolean {\n return /^(?:claude|anthropic[./:])/i.test(modelId);\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/**\n * Create a {@link CoderProvider} that routes Vercel AI SDK calls through a Coder\n * deployment's AI Gateway (formerly \"AI Bridge\"). AI Gateway exposes two\n * provider-namespaced surfaces; this provider fronts both and selects between\n * them per model.\n *\n * @example\n * ```ts\n * import { generateText } from \"ai\";\n * import { createCoder } from \"@coder/ai-sdk-provider\";\n *\n * const coder = createCoder({\n * baseURL: \"https://coder.example.com\",\n * apiKey: process.env.CODER_API_TOKEN!,\n * });\n *\n * await generateText({ model: coder(\"gpt-4o\"), prompt: \"Hi\" }); // OpenAI surface\n * await generateText({ model: coder(\"claude-sonnet-4-6\"), prompt: \"Hi\" }); // Anthropic surface\n * ```\n */\nexport function createCoder(settings: CoderProviderSettings): CoderProvider {\n if (!settings.baseURL) {\n throw new Error(\n \"createCoder: `baseURL` is required (your Coder deployment URL, e.g. https://coder.example.com).\",\n );\n }\n\n const deployment = trimTrailingSlash(settings.baseURL);\n const gatewayPath = settings.aiGatewayPath ?? DEFAULT_AI_GATEWAY_PATH;\n const openaiName = settings.providers?.openai ?? DEFAULT_OPENAI_PROVIDER;\n const anthropicName = settings.providers?.anthropic ?? DEFAULT_ANTHROPIC_PROVIDER;\n\n // Both sub-providers append their route to a baseURL that INCLUDES `/v1`:\n // openai-compatible POSTs `${baseURL}/chat/completions`, and @ai-sdk/anthropic\n // POSTs `${baseURL}/messages`. AI Gateway's intercepted routes are\n // `/aibridge/<name>/v1/chat/completions` and `/aibridge/<name>/v1/messages`.\n const openaiBaseURL = `${deployment}${gatewayPath}/${openaiName}/v1`;\n const anthropicBaseURL = `${deployment}${gatewayPath}/${anthropicName}/v1`;\n\n // BYOK mode: the Coder token authenticates via a dedicated header and `apiKey`\n // carries the upstream key. Centralized mode (default): `apiKey` is the Coder\n // token itself, sent in the standard provider auth header.\n const byok = settings.coderToken !== undefined;\n const headers: Record<string, string> = {\n ...(byok ? { [CODER_TOKEN_HEADER]: settings.coderToken as string } : {}),\n ...settings.headers,\n };\n\n const openai = createOpenAICompatible({\n name: \"coder.openai\",\n baseURL: openaiBaseURL,\n apiKey: settings.apiKey, // → `Authorization: Bearer <apiKey>`\n headers,\n fetch: settings.fetch,\n includeUsage: true,\n });\n\n const anthropic = createAnthropic({\n name: \"coder.anthropic\",\n baseURL: anthropicBaseURL,\n // Centralized: send the Coder token via `Authorization: Bearer` (the\n // documented path). BYOK: send the upstream key via `x-api-key`.\n ...(byok ? { apiKey: settings.apiKey } : { authToken: settings.apiKey }),\n headers,\n fetch: settings.fetch,\n });\n\n const languageModel = (modelId: string): LanguageModelV4 =>\n isAnthropicModelId(modelId) ? anthropic(modelId) : openai(modelId);\n\n return Object.assign(languageModel, {\n languageModel,\n openai,\n anthropic,\n chat: (modelId: string): LanguageModelV4 => openai(modelId),\n messages: (modelId: string): LanguageModelV4 => anthropic(modelId),\n textEmbeddingModel: (modelId: string): EmbeddingModelV4 => openai.textEmbeddingModel(modelId),\n });\n}\n\n/** Convenience alias mirroring other AI SDK providers' `createX` naming. */\nexport const coder = createCoder;\n"],"mappings":";AAAA,SAAiC,uBAAuB;AACxD,SAAS,8BAA6D;AAItE,IAAM,0BAA0B;AAEhC,IAAM,0BAA0B;AAChC,IAAM,6BAA6B;AAEnC,IAAM,qBAAqB;AA2EpB,SAAS,mBAAmB,SAA0B;AAC3D,SAAO,8BAA8B,KAAK,OAAO;AACnD;AAEA,SAAS,kBAAkB,KAAqB;AAC9C,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAsBO,SAAS,YAAY,UAAgD;AAC1E,MAAI,CAAC,SAAS,SAAS;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,kBAAkB,SAAS,OAAO;AACrD,QAAM,cAAc,SAAS,iBAAiB;AAC9C,QAAM,aAAa,SAAS,WAAW,UAAU;AACjD,QAAM,gBAAgB,SAAS,WAAW,aAAa;AAMvD,QAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,IAAI,UAAU;AAC/D,QAAM,mBAAmB,GAAG,UAAU,GAAG,WAAW,IAAI,aAAa;AAKrE,QAAM,OAAO,SAAS,eAAe;AACrC,QAAM,UAAkC;AAAA,IACtC,GAAI,OAAO,EAAE,CAAC,kBAAkB,GAAG,SAAS,WAAqB,IAAI,CAAC;AAAA,IACtE,GAAG,SAAS;AAAA,EACd;AAEA,QAAM,SAAS,uBAAuB;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ,SAAS;AAAA;AAAA,IACjB;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,cAAc;AAAA,EAChB,CAAC;AAED,QAAM,YAAY,gBAAgB;AAAA,IAChC,MAAM;AAAA,IACN,SAAS;AAAA;AAAA;AAAA,IAGT,GAAI,OAAO,EAAE,QAAQ,SAAS,OAAO,IAAI,EAAE,WAAW,SAAS,OAAO;AAAA,IACtE;AAAA,IACA,OAAO,SAAS;AAAA,EAClB,CAAC;AAED,QAAM,gBAAgB,CAAC,YACrB,mBAAmB,OAAO,IAAI,UAAU,OAAO,IAAI,OAAO,OAAO;AAEnE,SAAO,OAAO,OAAO,eAAe;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,CAAC,YAAqC,OAAO,OAAO;AAAA,IAC1D,UAAU,CAAC,YAAqC,UAAU,OAAO;AAAA,IACjE,oBAAoB,CAAC,YAAsC,OAAO,mBAAmB,OAAO;AAAA,EAC9F,CAAC;AACH;AAGO,IAAM,QAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts"],"sourcesContent":["import { type AnthropicProvider, createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createOpenAICompatible, type OpenAICompatibleProvider } from \"@ai-sdk/openai-compatible\";\nimport { type EmbeddingModelV4, type LanguageModelV4, NoSuchModelError } from \"@ai-sdk/provider\";\n\n/** Default mount path of AI Gateway on a Coder deployment. */\nconst DEFAULT_AI_GATEWAY_PATH = \"/api/v2/aibridge\";\n/** Default provider path segments (the admin-configured provider names). */\nconst DEFAULT_OPENAI_PROVIDER = \"openai\";\nconst DEFAULT_ANTHROPIC_PROVIDER = \"anthropic\";\n/** Header that carries the Coder token to AI Gateway in bring-your-own-key mode. */\nconst CODER_TOKEN_HEADER = \"X-Coder-AI-Governance-Token\";\n\nexport interface CoderProviderSettings {\n /**\n * Base URL of your Coder deployment, e.g. `https://coder.example.com`. Do NOT\n * include the AI Gateway path (`/api/v2/aibridge/...`) — it is appended for you.\n */\n baseURL: string;\n /**\n * The credential sent to AI Gateway.\n *\n * - **Centralized mode (default):** your **Coder API token**. AI Gateway holds\n * the upstream provider keys and brokers the call, so this is all you need.\n * - **BYOK mode** (when {@link CoderProviderSettings.coderToken} is also set):\n * your **upstream provider key**, which AI Gateway forwards to the upstream.\n */\n apiKey?: string;\n /**\n * Enables bring-your-own-key (BYOK) mode. This Coder API token is sent in the\n * `X-Coder-AI-Governance-Token` header to authenticate you to the gateway,\n * while {@link CoderProviderSettings.apiKey} carries your upstream provider key.\n * Leave unset for the default centralized mode.\n */\n coderToken?: string;\n /** Extra headers merged into every request to both surfaces. */\n headers?: Record<string, string>;\n /**\n * Mount path of AI Gateway on the deployment. Defaults to `/api/v2/aibridge`.\n * Exposed because the `aibridge` path segment may change in a future Coder\n * release (the feature was renamed \"AI Gateway\").\n */\n aiGatewayPath?: string;\n /**\n * Override the provider path segments — the *admin-configured provider names*\n * on the deployment (e.g. `anthropic-corp`). Default to `openai` / `anthropic`.\n */\n providers?: {\n openai?: string;\n anthropic?: string;\n };\n /** Custom fetch implementation (useful for testing or middleware). */\n fetch?: typeof globalThis.fetch;\n}\n\nexport interface CoderProvider {\n /** Route a model id to a surface by heuristic (Claude ids → Anthropic surface). */\n (modelId: string): LanguageModelV4;\n /** Route a model id to a surface by heuristic (Claude ids → Anthropic surface). */\n languageModel(modelId: string): LanguageModelV4;\n /**\n * AI Gateway's OpenAI-compatible surface (`/aibridge/openai/v1`). Reaches the\n * OpenAI / Azure / Google / OpenRouter / Vercel / openai-compat and Copilot\n * upstreams. Routing is by URL, so any model id sent here hits this surface.\n */\n openai: OpenAICompatibleProvider;\n /**\n * AI Gateway's Anthropic-compatible surface (`/aibridge/anthropic`). Reaches\n * native Claude and Bedrock-hosted Claude.\n */\n anthropic: AnthropicProvider;\n /** Shorthand for an {@link CoderProvider.openai} chat model. */\n chat(modelId: string): LanguageModelV4;\n /** Shorthand for an {@link CoderProvider.anthropic} messages model. */\n messages(modelId: string): LanguageModelV4;\n /**\n * Text embeddings are not supported: AI Gateway does not yet intercept\n * `/v1/embeddings`, so this always throws {@link NoSuchModelError} instead of\n * emitting a request that the gateway rejects with a 404. Tracked in\n * https://github.com/coder/ai-sdk/issues/69.\n */\n textEmbeddingModel(modelId: string): EmbeddingModelV4;\n}\n\n/**\n * Heuristic for the bare {@link CoderProvider} call: which AI Gateway surface\n * should a model id route to? Claude / Anthropic ids go to the Anthropic\n * surface; everything else to the OpenAI-compatible surface. Use the explicit\n * `.openai(id)` / `.anthropic(id)` accessors to override (e.g. to reach Claude\n * through a Copilot-typed provider on the OpenAI surface).\n */\nexport function isAnthropicModelId(modelId: string): boolean {\n return /^(?:claude|anthropic[./:])/i.test(modelId);\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/**\n * Fail fast at accessor time: AI Gateway intercepts only\n * `/v1/chat/completions`, `/v1/responses`, and `/v1/messages`, so an\n * embeddings request would always die with a 404 at request time.\n */\nfunction unsupportedEmbeddingModel(modelId: string): EmbeddingModelV4 {\n throw new NoSuchModelError({\n modelId,\n modelType: \"embeddingModel\",\n message:\n `Coder AI Gateway does not yet intercept /v1/embeddings, so the ` +\n `embedding model \"${modelId}\" cannot be reached through it. ` +\n `See https://github.com/coder/ai-sdk/issues/69 for status.`,\n });\n}\n\n/**\n * Create a {@link CoderProvider} that routes Vercel AI SDK calls through a Coder\n * deployment's AI Gateway (formerly \"AI Bridge\"). AI Gateway exposes two\n * provider-namespaced surfaces; this provider fronts both and selects between\n * them per model.\n *\n * @example\n * ```ts\n * import { generateText } from \"ai\";\n * import { createCoder } from \"@coder/ai-sdk-provider\";\n *\n * const coder = createCoder({\n * baseURL: \"https://coder.example.com\",\n * apiKey: process.env.CODER_API_TOKEN!,\n * });\n *\n * await generateText({ model: coder(\"gpt-4o\"), prompt: \"Hi\" }); // OpenAI surface\n * await generateText({ model: coder(\"claude-sonnet-4-6\"), prompt: \"Hi\" }); // Anthropic surface\n * ```\n */\nexport function createCoder(settings: CoderProviderSettings): CoderProvider {\n if (!settings.baseURL) {\n throw new Error(\n \"createCoder: `baseURL` is required (your Coder deployment URL, e.g. https://coder.example.com).\",\n );\n }\n\n const deployment = trimTrailingSlash(settings.baseURL);\n const gatewayPath = settings.aiGatewayPath ?? DEFAULT_AI_GATEWAY_PATH;\n const openaiName = settings.providers?.openai ?? DEFAULT_OPENAI_PROVIDER;\n const anthropicName = settings.providers?.anthropic ?? DEFAULT_ANTHROPIC_PROVIDER;\n\n // Both sub-providers append their route to a baseURL that INCLUDES `/v1`:\n // openai-compatible POSTs `${baseURL}/chat/completions`, and @ai-sdk/anthropic\n // POSTs `${baseURL}/messages`. AI Gateway's intercepted routes are\n // `/aibridge/<name>/v1/chat/completions` and `/aibridge/<name>/v1/messages`.\n const openaiBaseURL = `${deployment}${gatewayPath}/${openaiName}/v1`;\n const anthropicBaseURL = `${deployment}${gatewayPath}/${anthropicName}/v1`;\n\n // BYOK mode: the Coder token authenticates via a dedicated header and `apiKey`\n // carries the upstream key. Centralized mode (default): `apiKey` is the Coder\n // token itself, sent in the standard provider auth header.\n const byok = settings.coderToken !== undefined;\n const headers: Record<string, string> = {\n ...(byok ? { [CODER_TOKEN_HEADER]: settings.coderToken as string } : {}),\n ...settings.headers,\n };\n\n const openai = createOpenAICompatible({\n name: \"coder.openai\",\n baseURL: openaiBaseURL,\n apiKey: settings.apiKey, // → `Authorization: Bearer <apiKey>`\n headers,\n fetch: settings.fetch,\n includeUsage: true,\n });\n\n // `coder.openai` is public, so its embedding accessors must fail fast too —\n // otherwise they bypass the top-level guard and hit the gateway 404.\n openai.embeddingModel = unsupportedEmbeddingModel;\n openai.textEmbeddingModel = unsupportedEmbeddingModel;\n\n const anthropic = createAnthropic({\n name: \"coder.anthropic\",\n baseURL: anthropicBaseURL,\n // Centralized: send the Coder token via `Authorization: Bearer` (the\n // documented path). BYOK: send the upstream key via `x-api-key`.\n ...(byok ? { apiKey: settings.apiKey } : { authToken: settings.apiKey }),\n headers,\n fetch: settings.fetch,\n });\n\n const languageModel = (modelId: string): LanguageModelV4 =>\n isAnthropicModelId(modelId) ? anthropic(modelId) : openai(modelId);\n\n return Object.assign(languageModel, {\n languageModel,\n openai,\n anthropic,\n chat: (modelId: string): LanguageModelV4 => openai(modelId),\n messages: (modelId: string): LanguageModelV4 => anthropic(modelId),\n textEmbeddingModel: unsupportedEmbeddingModel,\n });\n}\n\n/** Convenience alias mirroring other AI SDK providers' `createX` naming. */\nexport const coder = createCoder;\n"],"mappings":";AAAA,SAAiC,uBAAuB;AACxD,SAAS,8BAA6D;AACtE,SAAsD,wBAAwB;AAG9E,IAAM,0BAA0B;AAEhC,IAAM,0BAA0B;AAChC,IAAM,6BAA6B;AAEnC,IAAM,qBAAqB;AAgFpB,SAAS,mBAAmB,SAA0B;AAC3D,SAAO,8BAA8B,KAAK,OAAO;AACnD;AAEA,SAAS,kBAAkB,KAAqB;AAC9C,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAOA,SAAS,0BAA0B,SAAmC;AACpE,QAAM,IAAI,iBAAiB;AAAA,IACzB;AAAA,IACA,WAAW;AAAA,IACX,SACE,mFACoB,OAAO;AAAA,EAE/B,CAAC;AACH;AAsBO,SAAS,YAAY,UAAgD;AAC1E,MAAI,CAAC,SAAS,SAAS;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,kBAAkB,SAAS,OAAO;AACrD,QAAM,cAAc,SAAS,iBAAiB;AAC9C,QAAM,aAAa,SAAS,WAAW,UAAU;AACjD,QAAM,gBAAgB,SAAS,WAAW,aAAa;AAMvD,QAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,IAAI,UAAU;AAC/D,QAAM,mBAAmB,GAAG,UAAU,GAAG,WAAW,IAAI,aAAa;AAKrE,QAAM,OAAO,SAAS,eAAe;AACrC,QAAM,UAAkC;AAAA,IACtC,GAAI,OAAO,EAAE,CAAC,kBAAkB,GAAG,SAAS,WAAqB,IAAI,CAAC;AAAA,IACtE,GAAG,SAAS;AAAA,EACd;AAEA,QAAM,SAAS,uBAAuB;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ,SAAS;AAAA;AAAA,IACjB;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,cAAc;AAAA,EAChB,CAAC;AAID,SAAO,iBAAiB;AACxB,SAAO,qBAAqB;AAE5B,QAAM,YAAY,gBAAgB;AAAA,IAChC,MAAM;AAAA,IACN,SAAS;AAAA;AAAA;AAAA,IAGT,GAAI,OAAO,EAAE,QAAQ,SAAS,OAAO,IAAI,EAAE,WAAW,SAAS,OAAO;AAAA,IACtE;AAAA,IACA,OAAO,SAAS;AAAA,EAClB,CAAC;AAED,QAAM,gBAAgB,CAAC,YACrB,mBAAmB,OAAO,IAAI,UAAU,OAAO,IAAI,OAAO,OAAO;AAEnE,SAAO,OAAO,OAAO,eAAe;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,CAAC,YAAqC,OAAO,OAAO;AAAA,IAC1D,UAAU,CAAC,YAAqC,UAAU,OAAO;AAAA,IACjE,oBAAoB;AAAA,EACtB,CAAC;AACH;AAGO,IAAM,QAAQ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coder/ai-sdk-provider",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Vercel AI SDK provider for Coder AI Gateway. Point the AI SDK at your Coder deployment with a Coder API token and use any model your deployment proxies — across AI Gateway's OpenAI- and Anthropic-compatible surfaces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|