@to11ai/sdk 1.0.0 → 2.0.0-rc.1

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/src/client.ts CHANGED
@@ -1,8 +1,20 @@
1
1
  import { BehaviorStore } from "./behaviors/store";
2
+ import { DEFAULT_API_URL, DEFAULT_GATEWAY_URL } from "./config";
3
+ import {
4
+ type Conversation,
5
+ Session,
6
+ type Turn,
7
+ type TurnAuthContext,
8
+ } from "./context";
9
+ import { gatewayAuthHeaders } from "./gateway";
2
10
  import { ProjectEnvironmentResource } from "./projectEnvironments/resource";
3
11
  import type { CacheConfig } from "./prompts/cache";
4
12
  import { PromptResource } from "./prompts/resource";
5
- import { ENV_SLUG_REGEX } from "./prompts/types";
13
+ import {
14
+ ENV_SLUG_REGEX,
15
+ type RenderFormat,
16
+ type UnknownRoleMode,
17
+ } from "./prompts/types";
6
18
  import { RoutingRulesResource } from "./routingRules/resource";
7
19
  import { Transport } from "./transport";
8
20
 
@@ -13,6 +25,8 @@ export {
13
25
  envVarNameForKey,
14
26
  type SdkBehaviorEnvelope,
15
27
  } from "./behaviors/store";
28
+ export type { TurnAuthContext } from "./context";
29
+ export { Conversation, Session, Turn } from "./context";
16
30
  // Re-export all error types for convenience
17
31
  export type {
18
32
  AmbiguousEnvLabelsReason,
@@ -40,8 +54,9 @@ export {
40
54
  To11aiNetworkError,
41
55
  To11aiRateLimitError,
42
56
  To11aiValidationError,
57
+ UnknownRoleError,
43
58
  } from "./errors";
44
- export type { GatewayAuthOptions } from "./gateway";
59
+ export type { ConvertMessagesOptions, GatewayAuthOptions } from "./gateway";
45
60
  export { gatewayAuthHeaders, gatewayPromptHeaders } from "./gateway";
46
61
  export type {
47
62
  ArchiveProjectEnvironmentInput,
@@ -63,16 +78,25 @@ export { ProjectEnvironmentResource } from "./projectEnvironments/resource";
63
78
  export type { CacheConfig } from "./prompts/cache";
64
79
  export type { PromptResourceConfig } from "./prompts/resource";
65
80
  export { PromptResource } from "./prompts/resource";
66
- // TO11-980 (S9): env-aware fetch() surface types at the package entrypoint (AC9).
81
+ export type {
82
+ AnthropicRenderedPrompt,
83
+ OpenAiRenderedPrompt,
84
+ PromptConfig,
85
+ PromptMetadata,
86
+ RenderResult,
87
+ } from "./prompts/shape";
88
+ // TO11-980 (S9): env-aware render() surface types at the package entrypoint (AC9).
67
89
  export type {
68
90
  BlockRenderRecord,
69
- DeveloperRole,
70
91
  FetchedPrompt,
71
92
  FetchPromptOptions,
72
93
  RenderedChatMessage,
73
94
  RenderedPrompt,
95
+ RenderFormat,
96
+ RenderOptions,
74
97
  ResolvedPrompt,
75
98
  ResolvedPromptEnvelope,
99
+ UnknownRoleMode,
76
100
  } from "./prompts/types";
77
101
  export { ENV_SLUG_REGEX } from "./prompts/types";
78
102
  export type {
@@ -107,63 +131,105 @@ export {
107
131
  } from "./routingRules/resource";
108
132
  export type { Transport } from "./transport";
109
133
 
110
- // ─── Auth option shapes ────────────────────────────────────────────────────
111
-
112
- interface ServerAuthOptions {
113
- /** API key for server-side use. Must not be used in browser contexts. */
114
- apiKey: string;
115
- getAccessToken?: never;
116
- }
117
-
118
- interface BrowserAuthOptions {
119
- /** Token callback for browser / edge contexts — avoids static secrets. */
120
- getAccessToken: () => Promise<string>;
121
- apiKey?: never;
122
- }
123
-
124
- type AuthOptions = ServerAuthOptions | BrowserAuthOptions;
125
-
126
134
  // ─── Client options ────────────────────────────────────────────────────────
127
135
 
128
- export type ClientOptions = AuthOptions & {
129
- baseUrl: string;
136
+ export interface ClientOptions {
137
+ /**
138
+ * Control-plane base URL. Precedence: this option → `TO11_API_URL` env →
139
+ * `https://api.to11.ai`.
140
+ */
141
+ baseUrl?: string;
142
+ /**
143
+ * Data-plane gateway URL (the app's provider-client `baseURL`). Precedence:
144
+ * this option → `TO11_GATEWAY_URL` env → `https://gw.to11.ai`.
145
+ */
146
+ gatewayUrl?: string;
130
147
  /**
131
- * Project the client is bound to (TO11-980 / S9). Required for the
132
- * env-aware `prompts.fetch(slug, …)` surface, which builds the
133
- * project-scoped resolve path from it. Validated non-empty at
134
- * construction. The deprecated `prompts.resolve()` keeps its per-call
135
- * `projectId` and ignores this.
148
+ * API key for server-side use. Precedence: this option → `TO11_API_KEY` env.
149
+ * Rejected in browser contexts — use `getAccessToken` there.
150
+ */
151
+ apiKey?: string;
152
+ /** Token callback for browser / edge contexts — avoids static secrets. */
153
+ getAccessToken?: () => Promise<string>;
154
+ /**
155
+ * Provider API key (BYOK) carried by `openaiOptions()` / `anthropicOptions()`
156
+ * as the provider client's `apiKey`, so a BYOK call is a single
157
+ * `new OpenAI(to11.openaiOptions())`. When omitted, those helpers fall back to
158
+ * the to11 `apiKey` as a placeholder (the managed / server-side-credential
159
+ * path). Option only — no env fallback. Whether the gateway actually uses this
160
+ * key depends on the provider's passthrough mode; otherwise the project's
161
+ * stored credential wins.
162
+ */
163
+ providerApiKey?: string;
164
+ /**
165
+ * Project the client is bound to. Precedence: this option → `TO11_PROJECT_ID`
166
+ * env. Required for `prompts.render()` and the gateway context helpers.
136
167
  */
137
168
  projectId?: string;
138
169
  /**
139
- * Default serving environment for `prompts.fetch(slug)` (TO11-980 / S9,
140
- * Style-A). When set, every `fetch()` call inherits it unless overridden
141
- * per call. Validated eagerly against the project-environments registry
142
- * rule (lowercase-kebab, `^[a-z0-9-]{1,63}$`): empty / whitespace-only /
143
- * uppercase / 64+-char values throw at construction with a loud
144
- * `[to11ai/sdk]` message before any request hits the wire.
170
+ * Default serving environment for `prompts.render(slug)`, inherited by every
171
+ * render unless overridden per call. Validated eagerly against the
172
+ * project-environments slug rule (`^[a-z0-9-]{1,63}$`).
145
173
  */
146
174
  env?: string;
147
- /** LRU cache configuration for resolve(). Defaults: ttlSeconds=300, maxEntries=100 */
175
+ /**
176
+ * Default provider shape for `prompts.render()` output (`"openai"` |
177
+ * `"anthropic"`). Omit for the neutral result. Per-call override allowed.
178
+ */
179
+ format?: RenderFormat;
180
+ /**
181
+ * Default policy for a message with an unrecognized role while shaping
182
+ * `render({ format })` output: `"drop"` (default, silent), `"warn"` (drop +
183
+ * `console.warn`), or `"error"` (throw `UnknownRoleError`). Per-call override
184
+ * via `render(slug, { onUnknownRole })`.
185
+ */
186
+ onUnknownRole?: UnknownRoleMode;
187
+ /** LRU cache configuration for render(). Defaults: ttlSeconds=300, maxEntries=100 */
148
188
  cache?: CacheConfig;
149
189
  /** Request timeout in milliseconds. Default: 30_000 */
150
190
  timeoutMs?: number;
151
191
  /** Max retry attempts for network errors / 5xx. Default: 3 */
152
192
  maxRetries?: number;
153
193
  /**
154
- * Purely local overrides for server-evaluated SDK-behavior flags (TO11-2100),
155
- * keyed by namespaced flag key (e.g. `{ "sdk.retry-v2": true }`). These take
156
- * precedence over server-delivered values and the SDK's coded defaults. For
157
- * the same flag key, an init option here wins over a `TO11_SDK_BEHAVIOR_*`
158
- * environment variable. Published-SDK only — no LaunchDarkly involved.
194
+ * Purely local overrides for server-evaluated SDK-behavior flags (TO11-2100).
195
+ * An init option here wins over a `TO11_SDK_BEHAVIOR_*` env var. No LaunchDarkly.
159
196
  */
160
197
  behaviorOverrides?: Record<string, unknown>;
198
+ }
199
+
200
+ // ─── Provider-client options (openaiOptions / anthropicOptions) ─────────────
201
+
202
+ /**
203
+ * Overrides accepted by `openaiOptions()` / `anthropicOptions()`. The three
204
+ * known fields are merged (`apiKey` / `baseURL` override; `defaultHeaders`
205
+ * shallow-merges over the tenant-auth headers, caller wins); any other keys —
206
+ * `timeout`, `maxRetries`, `fetch`, … — pass through untouched to the provider
207
+ * client, so `new OpenAI(to11.openaiOptions({ apiKey, timeout }))` needs no
208
+ * outer spread.
209
+ */
210
+ export interface ProviderClientOverrides {
211
+ apiKey?: string;
212
+ baseURL?: string;
213
+ defaultHeaders?: Record<string, string>;
214
+ }
215
+
216
+ /**
217
+ * The spread-ready result of `openaiOptions()` / `anthropicOptions()`: the three
218
+ * managed fields plus whatever passthrough keys the caller supplied (their types
219
+ * preserved), so it drops straight into `new OpenAI(...)` / `new Anthropic(...)`.
220
+ */
221
+ export type ProviderClientOptions<
222
+ T extends ProviderClientOverrides = ProviderClientOverrides,
223
+ > = Omit<T, "apiKey" | "baseURL" | "defaultHeaders"> & {
224
+ baseURL: string;
225
+ apiKey: string;
226
+ defaultHeaders: Record<string, string>;
161
227
  };
162
228
 
163
229
  // ─── Client interface ──────────────────────────────────────────────────────
164
230
 
165
- export interface To11aiClient {
166
- readonly prompts: PromptResource;
231
+ export interface To11aiClient<F extends RenderFormat | undefined = undefined> {
232
+ readonly prompts: PromptResource<F>;
167
233
  readonly projectEnvironments: ProjectEnvironmentResource;
168
234
  readonly routingRules: RoutingRulesResource;
169
235
  /**
@@ -176,18 +242,79 @@ export interface To11aiClient {
176
242
  * delivered on the envelope.
177
243
  */
178
244
  readonly resolveBehavior: <T>(key: string, codedDefault: T) => T;
245
+ /** Resolved API key (server mode); `undefined` in browser (`getAccessToken`) mode. */
246
+ readonly apiKey: string | undefined;
247
+ /** Resolved data-plane gateway URL (option → `TO11_GATEWAY_URL` → default). */
248
+ readonly gatewayUrl: string;
249
+ /** A new session (unit of work). Pass an id to group telemetry by your own id. */
250
+ session(id?: string): Session;
251
+ /**
252
+ * A conversation in the client's default session (for side / parallel agents).
253
+ *
254
+ * @example Two agents under one session
255
+ * const research = to11.conversation(); // a separate thread from to11.turn()'s default
256
+ * const res = await openai.chat.completions.create(
257
+ * { ...researchPrompt.config, messages },
258
+ * { headers: research.turn().headers(researchPrompt) },
259
+ * );
260
+ */
261
+ conversation(id?: string): Conversation;
262
+ /** A turn in the client's default session + default conversation. */
263
+ turn(): Turn;
264
+ /**
265
+ * Spread-ready options for `new OpenAI(...)` targeting the gateway:
266
+ * `{ baseURL, apiKey, defaultHeaders }` (baseURL carries `/v1`). `defaultHeaders`
267
+ * holds the static tenant auth, so the client authenticates on its own; add
268
+ * `turn().headers(prompt)` per call for trace + prompt provenance. Pass
269
+ * `overrides` to change `apiKey`/`baseURL`, merge extra `defaultHeaders`, or
270
+ * forward any other provider-SDK option (`timeout`, …).
271
+ *
272
+ * @example
273
+ * const openai = new OpenAI(to11.openaiOptions());
274
+ * @example BYOK + a provider-SDK option, no outer spread
275
+ * const openai = new OpenAI(to11.openaiOptions({ apiKey: MY_KEY, timeout: 60_000 }));
276
+ */
277
+ openaiOptions<T extends ProviderClientOverrides = ProviderClientOverrides>(
278
+ overrides?: T,
279
+ ): ProviderClientOptions<T>;
280
+ /**
281
+ * Spread-ready options for `new Anthropic(...)` targeting the gateway root
282
+ * (the Anthropic SDK appends `/v1/messages` itself). Same shape and merge
283
+ * rules as {@link To11aiClient.openaiOptions}.
284
+ */
285
+ anthropicOptions<T extends ProviderClientOverrides = ProviderClientOverrides>(
286
+ overrides?: T,
287
+ ): ProviderClientOptions<T>;
179
288
  }
180
289
 
181
290
  // ─── Factory ───────────────────────────────────────────────────────────────
182
291
 
183
292
  /**
184
- * Create a to11ai SDK client.
293
+ * Create a to11ai SDK client. Resolution precedence differs per option:
294
+ * - URLs (`baseUrl` / `gatewayUrl`) — option → env var → built-in default.
295
+ * - `apiKey` / `projectId` — option → env var (no default).
296
+ * - `env` / `format` — option only.
297
+ * Auth mode:
298
+ * - `apiKey` (or `TO11_API_KEY`) → server-side; rejected in browser contexts.
299
+ * - `getAccessToken` → browser-safe; token fetched per request.
300
+ *
301
+ * @example Minimal setup (single call)
302
+ * import { createClient } from "@to11ai/sdk";
303
+ * import OpenAI from "openai";
185
304
  *
186
- * Auth mode is inferred from options:
187
- * - `apiKey` → server-side; rejected in browser contexts.
188
- * - `getAccessToken` → browser-safe; token fetched per request.
305
+ * const to11 = createClient({ env: "prod", format: "openai" });
306
+ * const openai = new OpenAI(to11.openaiOptions());
307
+ * const prompt = await to11.prompts.render("weather-concierge", { variables: { city: "NYC" } });
308
+ * const res = await openai.chat.completions.create(
309
+ * { ...prompt.config, messages: prompt.messages },
310
+ * { headers: to11.turn().headers(prompt) },
311
+ * );
189
312
  */
190
- export function createClient(options: ClientOptions): To11aiClient {
313
+ export function createClient<
314
+ const F extends RenderFormat | undefined = undefined,
315
+ >(
316
+ options: Omit<ClientOptions, "format"> & { format?: F } = {},
317
+ ): To11aiClient<F> {
191
318
  const isDomBrowser =
192
319
  typeof globalThis === "object" &&
193
320
  typeof (globalThis as { document?: unknown }).document !== "undefined";
@@ -206,86 +333,205 @@ export function createClient(options: ClientOptions): To11aiClient {
206
333
  (!isBunMainThread &&
207
334
  typeof workerLikeGlobal.postMessage === "function" &&
208
335
  typeof workerLikeGlobal.onmessage !== "undefined"));
336
+ const isBrowser = isDomBrowser || isWorkerBrowser;
337
+
338
+ // Env vars are read ONLY in a non-browser context, so the published browser
339
+ // bundle never touches `process.env`; explicit options apply everywhere.
340
+ const processEnv =
341
+ !isBrowser &&
342
+ typeof process !== "undefined" &&
343
+ typeof process.env === "object"
344
+ ? process.env
345
+ : undefined;
346
+
347
+ // Resolve each value: explicit option → env var → default. `pick` trims and
348
+ // treats empty as absent, so an empty option or env value falls through.
349
+ const pick = (
350
+ opt: string | undefined,
351
+ envKey: string,
352
+ ): string | undefined => {
353
+ const fromOpt = opt?.trim();
354
+ if (fromOpt) return fromOpt;
355
+ const fromEnv = processEnv?.[envKey]?.trim();
356
+ return fromEnv ? fromEnv : undefined;
357
+ };
358
+
359
+ const baseUrl = pick(options.baseUrl, "TO11_API_URL") ?? DEFAULT_API_URL;
360
+ // The gateway root (host, no version path). Normalize away a trailing slash
361
+ // and a trailing `/v1` so both the root form (`https://gw.to11.ai`) and the
362
+ // older `/v1` form resolve identically — the provider helpers add the path
363
+ // each SDK expects.
364
+ const gatewayUrl = (
365
+ pick(options.gatewayUrl, "TO11_GATEWAY_URL") ?? DEFAULT_GATEWAY_URL
366
+ )
367
+ .replace(/\/+$/, "")
368
+ .replace(/\/v1$/, "");
369
+ const getAccessToken = options.getAccessToken;
370
+ // Explicit token auth suppresses an ambient `TO11_API_KEY` so a shared env
371
+ // (SSR / CI / edge) can't defeat an explicit `getAccessToken`. Only an
372
+ // *explicit* `options.apiKey` alongside `getAccessToken` stays ambiguous and
373
+ // is rejected by the exactly-one check below.
374
+ const apiKey =
375
+ getAccessToken !== undefined
376
+ ? options.apiKey
377
+ : pick(options.apiKey, "TO11_API_KEY");
378
+ const projectId = pick(options.projectId, "TO11_PROJECT_ID");
379
+ const env = options.env;
380
+ // Provider (BYOK) key — option only, no env fallback (which provider's env
381
+ // var is ambiguous). Empty trims to absent so it falls back to the placeholder.
382
+ const providerApiKey = options.providerApiKey?.trim() || undefined;
209
383
 
210
- if ((isDomBrowser || isWorkerBrowser) && options.apiKey !== undefined) {
384
+ // A browser must never carry a static API key. `processEnv` is already
385
+ // undefined in the browser, so only the explicit option can reach here.
386
+ if (isBrowser && options.apiKey !== undefined) {
211
387
  throw new Error(
212
388
  "[to11ai/sdk] Do not use `apiKey` in browser contexts — use `getAccessToken` instead.",
213
389
  );
214
390
  }
215
391
 
392
+ // Explicit empty credentials are a misconfiguration — reject loudly.
216
393
  if (options.apiKey !== undefined && options.apiKey.trim() === "") {
217
394
  throw new Error("[to11ai/sdk] `apiKey` must not be an empty string.");
218
395
  }
219
-
220
- // R0: validate `projectId` eagerly when supplied (loud throw). An empty /
221
- // whitespace-only value is a misconfiguration; reject before any request.
222
396
  if (options.projectId !== undefined && options.projectId.trim() === "") {
223
397
  throw new Error(
224
398
  "[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.",
225
399
  );
226
400
  }
227
401
 
228
- // R1: validate the init-time `env` against the project-environments
229
- // registry rule (stricter than gatewayAuthHeaders' empty-only check) so a
230
- // malformed env fails locally rather than round-tripping to ENV_NOT_FOUND.
231
- if (options.env !== undefined && !ENV_SLUG_REGEX.test(options.env)) {
402
+ // Validate the init-time `env` against the project-environments slug rule so
403
+ // a malformed env fails locally rather than round-tripping to ENV_NOT_FOUND.
404
+ if (env !== undefined && !ENV_SLUG_REGEX.test(env)) {
232
405
  throw new Error(
233
- `[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(options.env)}`,
406
+ `[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`,
234
407
  );
235
408
  }
236
409
 
237
- // TypeScript's union type prevents callers from providing both, but JS consumers can.
238
- // Perform an explicit XOR check at runtime to catch the case where both are supplied.
239
- const hasApiKey = options.apiKey !== undefined;
240
- const hasGetAccessToken = options.getAccessToken !== undefined;
241
- const getAccessToken = options.getAccessToken;
242
-
410
+ // Exactly one auth source after resolution.
411
+ const hasApiKey = apiKey !== undefined;
412
+ const hasGetAccessToken = getAccessToken !== undefined;
243
413
  if (hasApiKey === hasGetAccessToken) {
244
414
  throw new Error(
245
- "[to11ai/sdk] Provide exactly one of `apiKey` (server) or `getAccessToken` (browser).",
415
+ "[to11ai/sdk] Provide exactly one of `apiKey` (or `TO11_API_KEY`) or `getAccessToken`.",
246
416
  );
247
417
  }
248
418
 
249
- const auth =
250
- options.apiKey !== undefined
251
- ? ({ apiKey: options.apiKey } as const)
252
- : ({ getAccessToken: getAccessToken as () => Promise<string> } as const);
419
+ const auth = hasApiKey
420
+ ? ({ apiKey: apiKey as string } as const)
421
+ : ({ getAccessToken: getAccessToken as () => Promise<string> } as const);
253
422
 
254
- // SDK-behavior store (TO11-2100). Env-var overrides (`TO11_SDK_BEHAVIOR_*`)
255
- // are read ONLY in a non-browser context so the published browser bundle
256
- // never touches `process.env`; init-option overrides apply everywhere.
257
- const isBrowser = isDomBrowser || isWorkerBrowser;
258
- const processEnv =
259
- !isBrowser &&
260
- typeof process !== "undefined" &&
261
- typeof process.env === "object"
262
- ? process.env
263
- : undefined;
264
423
  const behaviorStore = new BehaviorStore({
265
424
  initOverrides: options.behaviorOverrides,
266
425
  env: processEnv,
267
426
  });
268
427
 
269
428
  const transport = new Transport({
270
- baseUrl: options.baseUrl,
429
+ baseUrl,
271
430
  auth,
272
431
  timeoutMs: options.timeoutMs,
273
432
  maxRetries: options.maxRetries,
274
433
  behaviorStore,
275
434
  });
276
435
 
277
- const prompts = new PromptResource(transport, options.cache, {
278
- projectId: options.projectId,
279
- env: options.env,
436
+ const prompts = new PromptResource<F>(transport, options.cache, {
437
+ projectId,
438
+ env,
439
+ format: options.format,
440
+ onUnknownRole: options.onUnknownRole,
280
441
  });
281
442
  const projectEnvironments = new ProjectEnvironmentResource(transport);
282
443
  const routingRules = new RoutingRulesResource(transport);
283
444
 
445
+ // ── Gateway observability context (session → conversation → turn) ──
446
+ // These build gateway auth headers, so they require a server-side apiKey +
447
+ // projectId; fail loudly rather than emit a broken header bag.
448
+ const gatewayAuthContext = (): TurnAuthContext => {
449
+ if (apiKey === undefined) {
450
+ throw new Error(
451
+ "[to11ai/sdk] session()/turn()/openaiOptions() need an `apiKey` (or `TO11_API_KEY`) — gateway calls require a server-side key. In the browser use `getAccessToken` for control-plane calls only.",
452
+ );
453
+ }
454
+ if (projectId === undefined) {
455
+ throw new Error(
456
+ "[to11ai/sdk] session()/turn() need a `projectId` (or `TO11_PROJECT_ID`).",
457
+ );
458
+ }
459
+ return env !== undefined
460
+ ? { apiKey, projectId, env }
461
+ : { apiKey, projectId };
462
+ };
463
+
464
+ let defaultSession: Session | undefined;
465
+ const clientDefaultSession = (): Session => {
466
+ if (defaultSession === undefined) {
467
+ defaultSession = new Session(gatewayAuthContext());
468
+ }
469
+ return defaultSession;
470
+ };
471
+
472
+ // OpenAI's SDK convention puts `/v1` in `baseURL`; the Anthropic SDK appends
473
+ // `/v1/messages` itself, so it takes the gateway root. Both point at the same
474
+ // gateway; only the base differs per provider convention. The result is a
475
+ // spread-ready provider-client options object: managed fields (baseURL/apiKey/
476
+ // defaultHeaders) are computed+merged, and any other override key passes
477
+ // through untouched to the provider SDK.
478
+ const gatewayClientOptions = <
479
+ T extends ProviderClientOverrides = ProviderClientOverrides,
480
+ >(
481
+ defaultBaseURL: string,
482
+ overrides?: T,
483
+ ): ProviderClientOptions<T> => {
484
+ if (apiKey === undefined) {
485
+ throw new Error(
486
+ "[to11ai/sdk] openaiOptions()/anthropicOptions() need an `apiKey` (or `TO11_API_KEY`) — gateway calls require a server-side key. In the browser use `getAccessToken` for control-plane calls only.",
487
+ );
488
+ }
489
+ if (projectId === undefined) {
490
+ throw new Error(
491
+ "[to11ai/sdk] openaiOptions()/anthropicOptions() need a `projectId` (or `TO11_PROJECT_ID`) — it authenticates the gateway call via `x-to11-project-id`.",
492
+ );
493
+ }
494
+ // Static tenant auth for the provider client's defaultHeaders; per-call
495
+ // trace + prompt provenance still come from `turn().headers(prompt)`.
496
+ const authHeaders = gatewayAuthHeaders(
497
+ env !== undefined ? { apiKey, projectId, env } : { apiKey, projectId },
498
+ );
499
+ const {
500
+ apiKey: overrideApiKey,
501
+ baseURL: overrideBaseURL,
502
+ defaultHeaders: overrideHeaders,
503
+ ...passthrough
504
+ } = overrides ?? ({} as T);
505
+ return {
506
+ ...passthrough,
507
+ baseURL: overrideBaseURL ?? defaultBaseURL,
508
+ // Provider (BYOK) key when supplied, else the to11 key as a placeholder
509
+ // (the managed / server-side-credential path).
510
+ apiKey: overrideApiKey ?? providerApiKey ?? apiKey,
511
+ defaultHeaders: { ...authHeaders, ...overrideHeaders },
512
+ } as ProviderClientOptions<T>;
513
+ };
514
+
284
515
  return {
285
516
  prompts,
286
517
  projectEnvironments,
287
518
  routingRules,
288
519
  resolveBehavior: (key, codedDefault) =>
289
520
  behaviorStore.resolve(key, codedDefault),
521
+ apiKey,
522
+ gatewayUrl,
523
+ session: (id) => new Session(gatewayAuthContext(), id),
524
+ conversation: (id) => clientDefaultSession().conversation(id),
525
+ turn: () => clientDefaultSession().turn(),
526
+ openaiOptions: <
527
+ T extends ProviderClientOverrides = ProviderClientOverrides,
528
+ >(
529
+ overrides?: T,
530
+ ) => gatewayClientOptions(`${gatewayUrl}/v1`, overrides),
531
+ anthropicOptions: <
532
+ T extends ProviderClientOverrides = ProviderClientOverrides,
533
+ >(
534
+ overrides?: T,
535
+ ) => gatewayClientOptions(gatewayUrl, overrides),
290
536
  };
291
537
  }
package/src/config.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Internal default endpoints. Deliberately NOT re-exported from the package
3
+ * root: `createClient` resolves the URLs (option → env → default) and exposes
4
+ * the resolved data-plane URL as `to11.gatewayUrl`, so app code never imports a
5
+ * bare URL constant. Keeping these out of the public surface avoids a JS-only
6
+ * exported symbol that would need a Python-parity record.
7
+ */
8
+
9
+ export const DEFAULT_API_URL = "https://api.to11.ai";
10
+ // The gateway *root* — no `/v1` version path. Provider helpers add the path each
11
+ // provider SDK expects (`openaiOptions()` appends `/v1`; the Anthropic SDK adds
12
+ // `/v1/messages` itself), so users configure only the host.
13
+ export const DEFAULT_GATEWAY_URL = "https://gw.to11.ai";