@providerkit/core 0.5.1 → 0.7.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.
@@ -0,0 +1,102 @@
1
+ // The one-line switch: resolve a preset id into a wired provider.
2
+ //
3
+ // Consumers used to write their own switch over providers — the same
4
+ // dispatch, re-derived per codebase, each quietly accumulating dialect
5
+ // knowledge (which gateway takes Bearer, which needs the thinking marker).
6
+ // The presets table + this factory is that dispatch, said once: a caller
7
+ // names an id and hands over a credential, and the header style, endpoint
8
+ // and adapter come from the table.
9
+ //
10
+ // `effort` passes through to the adapter unchanged — the per-endpoint
11
+ // thinking dialects (Z.ai's explicit disabled marker, OpenRouter's floors)
12
+ // are the adapters' knowledge, and this factory must not re-derive them.
13
+ import { createAnthropicProvider } from "./anthropic.ts";
14
+ import { createGeminiProvider } from "./gemini.ts";
15
+ import { createOpenAIProvider } from "./openai.ts";
16
+ import { createResponsesProvider } from "./responses.ts";
17
+ import { PRESET_IDS, PROVIDER_PRESETS, type ProviderPreset, type ProviderPresetId } from "../presets.ts";
18
+ import type { Effort, Provider } from "../types.ts";
19
+
20
+ export interface PresetProviderConfig {
21
+ /** The credential: an API key for `key`/`bearer` presets, an ACCESS TOKEN
22
+ * for `oauth` ones. Acquiring the token is the caller's job. */
23
+ apiKey: string;
24
+ /** Which model to run. Absent = the preset's `defaultModel`; a preset
25
+ * without either throws rather than sending a request nobody chose. */
26
+ model?: string;
27
+ effort?: Effort;
28
+ /** Output ceiling. The Anthropic shape requires one; adapters default it. */
29
+ maxTokens?: number;
30
+ /** Per-request headers (request ids, `ChatGPT-Account-Id`) on top of the
31
+ * preset's static protocol headers. */
32
+ headers?: Record<string, string>;
33
+ fetchImpl?: typeof fetch;
34
+ }
35
+
36
+ export function createPresetProvider(id: ProviderPresetId, config: PresetProviderConfig): Provider {
37
+ // The table is closed (`satisfies`), but a caller may hold an id as a plain
38
+ // string — validate rather than index into undefined.
39
+ if (!(id in PROVIDER_PRESETS)) {
40
+ throw new Error(
41
+ `[providerkit] Unknown provider preset "${id}". Valid ids: ${PRESET_IDS.join(", ")}`,
42
+ );
43
+ }
44
+ const preset: ProviderPreset = PROVIDER_PRESETS[id];
45
+ const model = config.model ?? preset.defaultModel;
46
+ if (!model) {
47
+ throw new Error(`[providerkit] Preset "${id}" has no defaultModel — pass a model.`);
48
+ }
49
+ const headers = { ...preset.headers, ...config.headers };
50
+
51
+ switch (preset.shape) {
52
+ case "anthropic":
53
+ // `key` rides Anthropic's native x-api-key; coding plans and OAuth
54
+ // tokens read Bearer (measured across the family's coding endpoints).
55
+ return createAnthropicProvider({
56
+ apiKey: config.apiKey,
57
+ model,
58
+ effort: config.effort,
59
+ maxTokens: config.maxTokens,
60
+ baseUrl: preset.baseUrl,
61
+ id,
62
+ headers,
63
+ bearer: preset.auth !== "key",
64
+ // The endpoint's thinking dialect rides the table (zai: silence means
65
+ // the model default, which is ON — "none" must be said out loud).
66
+ ...(preset.explicitNone ? { explicitNone: true } : {}),
67
+ fetchImpl: config.fetchImpl,
68
+ });
69
+ case "openai":
70
+ return createOpenAIProvider({
71
+ id,
72
+ apiKey: config.apiKey,
73
+ model,
74
+ effort: config.effort,
75
+ maxTokens: config.maxTokens,
76
+ baseUrl: preset.baseUrl,
77
+ headers,
78
+ fetchImpl: config.fetchImpl,
79
+ });
80
+ case "responses":
81
+ return createResponsesProvider({
82
+ apiKey: config.apiKey,
83
+ model,
84
+ effort: config.effort,
85
+ maxTokens: config.maxTokens,
86
+ baseUrl: preset.baseUrl,
87
+ id,
88
+ headers,
89
+ fetchImpl: config.fetchImpl,
90
+ });
91
+ case "gemini":
92
+ return createGeminiProvider({
93
+ apiKey: config.apiKey,
94
+ model,
95
+ effort: config.effort,
96
+ baseUrl: preset.baseUrl,
97
+ id,
98
+ headers,
99
+ fetchImpl: config.fetchImpl,
100
+ });
101
+ }
102
+ }
@@ -24,6 +24,10 @@ export interface OpenAIConfig {
24
24
  model: string;
25
25
  /** Any OpenAI-compatible endpoint. Defaults to OpenAI itself. */
26
26
  baseUrl?: string;
27
+ /** Path appended to baseUrl. Defaults to /v1/chat/completions. Set
28
+ * /chat/completions when the base already includes the API version, such as
29
+ * a gateway using /v4 rather than /v1. */
30
+ path?: string;
27
31
  /** Names the provider in errors and logs — "openrouter", "deepseek", … It
28
32
  * also picks the effort dialect below, unless `effortDialect` overrides. */
29
33
  id?: string;
@@ -398,7 +402,7 @@ export function createOpenAIProvider(config: OpenAIConfig): Provider {
398
402
  }
399
403
 
400
404
  for await (const data of streamSse({
401
- url: apiUrl(baseUrl, "/v1/chat/completions"),
405
+ url: apiUrl(baseUrl, config.path ?? "/v1/chat/completions"),
402
406
  headers: { authorization: `Bearer ${config.apiKey}`, ...config.headers },
403
407
  body: request,
404
408
  provider: id,
@@ -0,0 +1,31 @@
1
+ import { createAnthropicProvider, type AnthropicConfig } from "./anthropic.ts";
2
+ import type { Provider } from "../types.ts";
3
+
4
+ /** Z.ai Coding Plan's Anthropic-compatible API root, not its pay-per-token API. */
5
+ export const ZAI_CODING_BASE_URL = "https://api.z.ai/api/anthropic";
6
+
7
+ export type ZaiCodingConfig = Omit<AnthropicConfig, "baseUrl" | "id" | "bearer">;
8
+
9
+ /** Use a Z.ai Coding Plan key with the shared Messages adapter. The preset owns
10
+ * the endpoint, Bearer auth and the thinking dialect; callers still choose
11
+ * their model and effort. Tool calls, usage and errors use the existing
12
+ * adapter.
13
+ *
14
+ * Dialect notes, measured 2026-09-13 against the live endpoint on
15
+ * `glm-5.3-flash`: an absent thinking field means the MODEL's default —
16
+ * thinking ON — so `effort: "none"` is sent as an explicit
17
+ * `thinking: { type: "disabled" }`; `low`–`max` ride the standard thinking
18
+ * budgets, which the endpoint accepts. Images force a thinking block even
19
+ * under `disabled` (the model's choice, billed as output). */
20
+ export function createZaiCodingProvider(config: ZaiCodingConfig): Provider {
21
+ return createAnthropicProvider({
22
+ ...config,
23
+ id: "zai",
24
+ baseUrl: ZAI_CODING_BASE_URL,
25
+ bearer: true,
26
+ explicitNone: true,
27
+ // Coding gateways can allocate a 32K thinking budget themselves. Leave room
28
+ // for the answer; callers can still set a smaller cap for their model.
29
+ maxTokens: config.maxTokens ?? 65_536,
30
+ });
31
+ }