@coder/ai-sdk-provider 0.3.5 → 0.4.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 CHANGED
@@ -55,29 +55,73 @@ Requires Node ≥ 22, `ai` v7, and a Coder deployment with **AI Gateway enabled*
55
55
  (stable since Coder **v2.29**, GA in v2.30, on by default in v2.34; requires the
56
56
  AI Governance Add-On).
57
57
 
58
- ## The two surfaces
58
+ ## Named providers and the two wire protocols
59
59
 
60
- AI Gateway exposes **two provider-namespaced surfaces** on your deployment, and
61
- routing is **by URL path, not by model id** — so each surface reaches a fixed set
62
- of upstreams:
60
+ Your Coder admins define AI Gateway **providers** — named routes on the
61
+ deployment (`/api/v2/aibridge/<name>/v1/…`), each speaking one of **two wire
62
+ protocols** determined by its admin-configured type:
63
63
 
64
- | Surface | Reaches | Accessor |
65
- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------- |
66
- | **OpenAI-compatible** (`/api/v2/aibridge/openai/v1`) | OpenAI, Azure, Google, OpenRouter, Vercel, openai-compat — and **Copilot** (incl. Claude via Copilot) | `coder.openai(id)` / `coder.chat(id)` |
67
- | **Anthropic-compatible** (`/api/v2/aibridge/anthropic`) | **native Claude** + **Bedrock-hosted Claude** | `coder.anthropic(id)` / `coder.messages(id)` |
64
+ | Wire protocol | Provider types behind it | Default name |
65
+ | ------------------------ | ------------------------------------------------------------------------------- | ------------ |
66
+ | **OpenAI-compatible** | `openai`, `azure`, `google`, `copilot`, `openai-compat`, `openrouter`, `vercel` | `openai` |
67
+ | **Anthropic-compatible** | `anthropic` (native Claude), `bedrock` (Bedrock-hosted Claude) | `anthropic` |
68
68
 
69
- The bare call `coder(modelId)` picks a surface by heuristic — model ids starting
70
- with `claude`/`anthropic` go to the Anthropic surface, everything else to the
71
- OpenAI surface. Use the explicit accessors to override (e.g. to reach Claude
72
- through a Copilot-typed provider on the OpenAI surface):
69
+ Routing is **by URL path, not by model id** — the provider name in the URL
70
+ decides which upstream handles the request. `createCoder` fronts the default
71
+ `openai` / `anthropic` pair: the bare call `coder(modelId)` picks between them
72
+ by heuristic (model ids starting with `claude`/`anthropic` go to the
73
+ Anthropic-protocol provider, everything else to the OpenAI-protocol one), and
74
+ the explicit accessors override it (e.g. to reach Claude through a
75
+ Copilot-typed provider on the OpenAI protocol):
73
76
 
74
77
  ```ts
75
- coder("gpt-4o"); // → OpenAI surface
76
- coder("claude-sonnet-4-6"); // → Anthropic surface (heuristic)
77
- coder.openai("claude-sonnet-4"); // → OpenAI surface (e.g. Copilot)
78
- coder.anthropic("claude-opus-4-5"); // → Anthropic surface (explicit)
78
+ coder("gpt-4o"); // → `openai` provider
79
+ coder("claude-sonnet-4-6"); // → `anthropic` provider (heuristic)
80
+ coder.openai("claude-sonnet-4"); // → `openai` provider (e.g. Copilot)
81
+ coder.anthropic("claude-opus-4-5"); // → `anthropic` provider (explicit)
79
82
  ```
80
83
 
84
+ ### Custom-named providers
85
+
86
+ Provider names are admin-chosen (matching `^[a-z0-9]+(-[a-z0-9]+)*$`), so a
87
+ deployment may expose e.g. an Azure-backed `azure-openai` next to a
88
+ Bedrock-backed `anthropic-bedrock`. Reach them in two ways:
89
+
90
+ **Sub-provider accessors** — `openaiProvider(name)` / `anthropicProvider(name)`
91
+ return a full sub-provider bound to that gateway provider, so one
92
+ `createCoder` instance can target any number of providers. Pick the accessor
93
+ matching the provider's wire protocol:
94
+
95
+ ```ts
96
+ const azure = coder.openaiProvider("azure-openai"); // OpenAI-compatible type
97
+ const bedrock = coder.anthropicProvider("anthropic-bedrock"); // Anthropic-compatible type
98
+
99
+ await generateText({ model: azure("gpt-4o"), prompt: "Hi" });
100
+ await generateText({ model: bedrock("claude-sonnet-4-6"), prompt: "Hi" });
101
+ ```
102
+
103
+ A name outside the gateway's grammar throws the AI SDK's
104
+ `InvalidArgumentError` at accessor time (such a name can never be registered);
105
+ a well-formed name that is not configured on your deployment fails at request
106
+ time with the gateway's 404.
107
+
108
+ **Re-pointing the defaults** — when your deployment simply names its one
109
+ OpenAI/Anthropic pair differently, override the names once and keep using the
110
+ bare call and the `openai` / `anthropic` accessors:
111
+
112
+ ```ts
113
+ const renamed = createCoder({
114
+ baseURL: "https://coder.example.com",
115
+ apiKey: process.env.CODER_API_TOKEN!,
116
+ providers: { openai: "azure-openai", anthropic: "anthropic-bedrock" },
117
+ });
118
+ ```
119
+
120
+ **Provider names come from your platform team.** Discovery is admin-only
121
+ server-side: `GET /api/v2/ai/providers` returns `403` for regular users, and
122
+ the models endpoint does not attribute models to providers. Ask your Coder
123
+ admins which provider names your deployment defines.
124
+
81
125
  Model ids are passed through **unchanged** to the upstream provider (no
82
126
  `vendor/model` namespacing) — use whatever ids your deployment's providers accept.
83
127
 
@@ -119,7 +163,7 @@ createCoder({
119
163
  | `coderToken` | `string` | — | Enables BYOK mode; sent in `X-Coder-AI-Governance-Token`. |
120
164
  | `headers` | `Record<string,string>` | — | Extra headers merged into every request. |
121
165
  | `aiGatewayPath` | `string` | `/api/v2/aibridge` | Override if your deployment uses a different mount path. |
122
- | `providers` | `{ openai?, anthropic? }` | `openai` / `anthropic` | Override the admin-configured provider path segments. |
166
+ | `providers` | `{ openai?, anthropic? }` | `openai` / `anthropic` | Re-point the default pair at differently-named providers. |
123
167
  | `fetch` | `typeof fetch` | global `fetch` | Custom fetch (testing / middleware). |
124
168
 
125
169
  ## Enterprise governance & security
@@ -127,7 +171,7 @@ createCoder({
127
171
  Reference for security reviewers evaluating this package. The boundary between
128
172
  the two kinds of claims below matters: **client behavior** (what this package
129
173
  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 —
174
+ ~250 lines with no dependencies beyond the official AI SDK provider packages —
131
175
  while **gateway behavior** (key custody, audit capture, retention) is a
132
176
  property of your Coder deployment, enforced server-side regardless of what any
133
177
  client does, and documented in the
package/dist/index.d.ts CHANGED
@@ -63,6 +63,23 @@ 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
+ /**
67
+ * An OpenAI-compatible sub-provider bound to the *named* gateway provider
68
+ * (`<aiGatewayPath>/<name>/v1`). Use it to reach admin-defined providers
69
+ * beyond the default pair, e.g.
70
+ * `coder.openaiProvider("azure-openai")("gpt-4o")`. Throws the AI SDK's
71
+ * `InvalidArgumentError` when `name` does not match the gateway's
72
+ * provider-name grammar (`^[a-z0-9]+(-[a-z0-9]+)*$`); a well-formed name
73
+ * that is not configured on the deployment fails at request time with the
74
+ * gateway's 404.
75
+ */
76
+ openaiProvider(name: string): OpenAICompatibleProvider;
77
+ /**
78
+ * An Anthropic-compatible sub-provider bound to the *named* gateway
79
+ * provider, e.g. `coder.anthropicProvider("anthropic-bedrock")("claude-sonnet-4-6")`.
80
+ * Same name validation as {@link CoderProvider.openaiProvider}.
81
+ */
82
+ anthropicProvider(name: string): AnthropicProvider;
66
83
  /**
67
84
  * Text embeddings are not supported: AI Gateway does not yet intercept
68
85
  * `/v1/embeddings`, so this always throws {@link NoSuchModelError} instead of
package/dist/index.js CHANGED
@@ -1,10 +1,14 @@
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
+ import {
5
+ InvalidArgumentError,
6
+ NoSuchModelError
7
+ } from "@ai-sdk/provider";
5
8
  var DEFAULT_AI_GATEWAY_PATH = "/api/v2/aibridge";
6
9
  var DEFAULT_OPENAI_PROVIDER = "openai";
7
10
  var DEFAULT_ANTHROPIC_PROVIDER = "anthropic";
11
+ var PROVIDER_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
8
12
  var CODER_TOKEN_HEADER = "X-Coder-AI-Governance-Token";
9
13
  function isAnthropicModelId(modelId) {
10
14
  return /^(?:claude|anthropic[./:])/i.test(modelId);
@@ -19,6 +23,14 @@ function unsupportedEmbeddingModel(modelId) {
19
23
  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
24
  });
21
25
  }
26
+ function assertValidProviderName(name) {
27
+ if (!PROVIDER_NAME_PATTERN.test(name)) {
28
+ throw new InvalidArgumentError({
29
+ argument: "name",
30
+ message: `Invalid AI Gateway provider name "${name}": provider names are lowercase alphanumeric segments separated by single hyphens (${PROVIDER_NAME_PATTERN}). Ask your Coder admins for the provider names configured on your deployment.`
31
+ });
32
+ }
33
+ }
22
34
  function createCoder(settings) {
23
35
  if (!settings.baseURL) {
24
36
  throw new Error(
@@ -27,35 +39,41 @@ function createCoder(settings) {
27
39
  }
28
40
  const deployment = trimTrailingSlash(settings.baseURL);
29
41
  const gatewayPath = settings.aiGatewayPath ?? DEFAULT_AI_GATEWAY_PATH;
30
- const openaiName = settings.providers?.openai ?? DEFAULT_OPENAI_PROVIDER;
31
- const anthropicName = settings.providers?.anthropic ?? DEFAULT_ANTHROPIC_PROVIDER;
32
- const openaiBaseURL = `${deployment}${gatewayPath}/${openaiName}/v1`;
33
- const anthropicBaseURL = `${deployment}${gatewayPath}/${anthropicName}/v1`;
34
42
  const byok = settings.coderToken !== void 0;
35
43
  const headers = {
36
44
  ...byok ? { [CODER_TOKEN_HEADER]: settings.coderToken } : {},
37
45
  ...settings.headers
38
46
  };
39
- const openai = createOpenAICompatible({
40
- name: "coder.openai",
41
- baseURL: openaiBaseURL,
42
- apiKey: settings.apiKey,
43
- // → `Authorization: Bearer <apiKey>`
44
- headers,
45
- fetch: settings.fetch,
46
- includeUsage: true
47
- });
48
- openai.embeddingModel = unsupportedEmbeddingModel;
49
- openai.textEmbeddingModel = unsupportedEmbeddingModel;
50
- const anthropic = createAnthropic({
51
- name: "coder.anthropic",
52
- baseURL: anthropicBaseURL,
53
- // Centralized: send the Coder token via `Authorization: Bearer` (the
54
- // documented path). BYOK: send the upstream key via `x-api-key`.
55
- ...byok ? { apiKey: settings.apiKey } : { authToken: settings.apiKey },
56
- headers,
57
- fetch: settings.fetch
58
- });
47
+ const providerBaseURL = (name) => `${deployment}${gatewayPath}/${name}/v1`;
48
+ const openaiProvider = (name) => {
49
+ assertValidProviderName(name);
50
+ const provider = createOpenAICompatible({
51
+ name: `coder.${name}`,
52
+ baseURL: providerBaseURL(name),
53
+ apiKey: settings.apiKey,
54
+ // → `Authorization: Bearer <apiKey>`
55
+ headers,
56
+ fetch: settings.fetch,
57
+ includeUsage: true
58
+ });
59
+ provider.embeddingModel = unsupportedEmbeddingModel;
60
+ provider.textEmbeddingModel = unsupportedEmbeddingModel;
61
+ return provider;
62
+ };
63
+ const anthropicProvider = (name) => {
64
+ assertValidProviderName(name);
65
+ return createAnthropic({
66
+ name: `coder.${name}`,
67
+ baseURL: providerBaseURL(name),
68
+ // Centralized: send the Coder token via `Authorization: Bearer` (the
69
+ // documented path). BYOK: send the upstream key via `x-api-key`.
70
+ ...byok ? { apiKey: settings.apiKey } : { authToken: settings.apiKey },
71
+ headers,
72
+ fetch: settings.fetch
73
+ });
74
+ };
75
+ const openai = openaiProvider(settings.providers?.openai ?? DEFAULT_OPENAI_PROVIDER);
76
+ const anthropic = anthropicProvider(settings.providers?.anthropic ?? DEFAULT_ANTHROPIC_PROVIDER);
59
77
  const languageModel = (modelId) => isAnthropicModelId(modelId) ? anthropic(modelId) : openai(modelId);
60
78
  return Object.assign(languageModel, {
61
79
  languageModel,
@@ -63,6 +81,8 @@ function createCoder(settings) {
63
81
  anthropic,
64
82
  chat: (modelId) => openai(modelId),
65
83
  messages: (modelId) => anthropic(modelId),
84
+ openaiProvider,
85
+ anthropicProvider,
66
86
  textEmbeddingModel: unsupportedEmbeddingModel
67
87
  });
68
88
  }
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, 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":[]}
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 {\n type EmbeddingModelV4,\n InvalidArgumentError,\n type LanguageModelV4,\n NoSuchModelError,\n} 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/**\n * AI Gateway's provider-name grammar: lowercase alphanumeric segments\n * separated by single hyphens. Names outside this grammar can never be\n * registered on a deployment, so they are rejected client-side.\n */\nconst PROVIDER_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\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 * An OpenAI-compatible sub-provider bound to the *named* gateway provider\n * (`<aiGatewayPath>/<name>/v1`). Use it to reach admin-defined providers\n * beyond the default pair, e.g.\n * `coder.openaiProvider(\"azure-openai\")(\"gpt-4o\")`. Throws the AI SDK's\n * `InvalidArgumentError` when `name` does not match the gateway's\n * provider-name grammar (`^[a-z0-9]+(-[a-z0-9]+)*$`); a well-formed name\n * that is not configured on the deployment fails at request time with the\n * gateway's 404.\n */\n openaiProvider(name: string): OpenAICompatibleProvider;\n /**\n * An Anthropic-compatible sub-provider bound to the *named* gateway\n * provider, e.g. `coder.anthropicProvider(\"anthropic-bedrock\")(\"claude-sonnet-4-6\")`.\n * Same name validation as {@link CoderProvider.openaiProvider}.\n */\n anthropicProvider(name: string): AnthropicProvider;\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 * Fail fast at accessor time: a name outside the gateway's provider-name\n * grammar can never be registered on a deployment, so a request to it would\n * always die with a confusing 404. Same philosophy as the embeddings guard\n * (https://github.com/coder/ai-sdk/issues/69).\n */\nfunction assertValidProviderName(name: string): void {\n if (!PROVIDER_NAME_PATTERN.test(name)) {\n throw new InvalidArgumentError({\n argument: \"name\",\n message:\n `Invalid AI Gateway provider name \"${name}\": provider names are ` +\n `lowercase alphanumeric segments separated by single hyphens ` +\n `(${PROVIDER_NAME_PATTERN}). Ask your Coder admins for the provider ` +\n `names configured on your deployment.`,\n });\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\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 // Both sub-provider kinds 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 providerBaseURL = (name: string): string => `${deployment}${gatewayPath}/${name}/v1`;\n\n const openaiProvider = (name: string): OpenAICompatibleProvider => {\n assertValidProviderName(name);\n const provider = createOpenAICompatible({\n name: `coder.${name}`,\n baseURL: providerBaseURL(name),\n apiKey: settings.apiKey, // → `Authorization: Bearer <apiKey>`\n headers,\n fetch: settings.fetch,\n includeUsage: true,\n });\n // Sub-providers are public, so their embedding accessors must fail fast\n // too — otherwise they bypass the top-level guard and hit the gateway 404.\n provider.embeddingModel = unsupportedEmbeddingModel;\n provider.textEmbeddingModel = unsupportedEmbeddingModel;\n return provider;\n };\n\n const anthropicProvider = (name: string): AnthropicProvider => {\n assertValidProviderName(name);\n return createAnthropic({\n name: `coder.${name}`,\n baseURL: providerBaseURL(name),\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\n // The default surfaces are ordinary named sub-providers — one code path.\n const openai = openaiProvider(settings.providers?.openai ?? DEFAULT_OPENAI_PROVIDER);\n const anthropic = anthropicProvider(settings.providers?.anthropic ?? DEFAULT_ANTHROPIC_PROVIDER);\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 openaiProvider,\n anthropicProvider,\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;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AAGP,IAAM,0BAA0B;AAEhC,IAAM,0BAA0B;AAChC,IAAM,6BAA6B;AAMnC,IAAM,wBAAwB;AAE9B,IAAM,qBAAqB;AAiGpB,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;AAQA,SAAS,wBAAwB,MAAoB;AACnD,MAAI,CAAC,sBAAsB,KAAK,IAAI,GAAG;AACrC,UAAM,IAAI,qBAAqB;AAAA,MAC7B,UAAU;AAAA,MACV,SACE,qCAAqC,IAAI,sFAErC,qBAAqB;AAAA,IAE7B,CAAC;AAAA,EACH;AACF;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;AAK9C,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;AAMA,QAAM,kBAAkB,CAAC,SAAyB,GAAG,UAAU,GAAG,WAAW,IAAI,IAAI;AAErF,QAAM,iBAAiB,CAAC,SAA2C;AACjE,4BAAwB,IAAI;AAC5B,UAAM,WAAW,uBAAuB;AAAA,MACtC,MAAM,SAAS,IAAI;AAAA,MACnB,SAAS,gBAAgB,IAAI;AAAA,MAC7B,QAAQ,SAAS;AAAA;AAAA,MACjB;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,cAAc;AAAA,IAChB,CAAC;AAGD,aAAS,iBAAiB;AAC1B,aAAS,qBAAqB;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,oBAAoB,CAAC,SAAoC;AAC7D,4BAAwB,IAAI;AAC5B,WAAO,gBAAgB;AAAA,MACrB,MAAM,SAAS,IAAI;AAAA,MACnB,SAAS,gBAAgB,IAAI;AAAA;AAAA;AAAA,MAG7B,GAAI,OAAO,EAAE,QAAQ,SAAS,OAAO,IAAI,EAAE,WAAW,SAAS,OAAO;AAAA,MACtE;AAAA,MACA,OAAO,SAAS;AAAA,IAClB,CAAC;AAAA,EACH;AAGA,QAAM,SAAS,eAAe,SAAS,WAAW,UAAU,uBAAuB;AACnF,QAAM,YAAY,kBAAkB,SAAS,WAAW,aAAa,0BAA0B;AAE/F,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;AAAA,IACA;AAAA,IACA,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.5",
3
+ "version": "0.4.0",
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",