@to11ai/sdk 2.0.0-rc.3 → 2.0.0-rc.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.
@@ -1,11 +1,13 @@
1
1
  // src/prompts/resource.ts
2
- import { z } from "zod";
3
- import { EnvNotFoundError, To11aiNetworkError } from "../errors";
2
+ import {
3
+ MissingLabelError,
4
+ To11aiApiError,
5
+ To11aiNetworkError
6
+ } from "../errors";
4
7
  import { zResolvedPrompt } from "../generated/zod.gen";
5
- import { buildRenderCacheKey, PromptCache } from "./cache";
6
8
  import { shapePrompt } from "./shape";
7
9
  import { ENV_SLUG_REGEX } from "./types";
8
- var zResolvedPromptEnvelope = zResolvedPrompt.partial({ releaseId: true }).extend({ strategy: z.enum(["single", "weighted"]).optional() });
10
+ var zResolvedPromptEnvelope = zResolvedPrompt.partial({ releaseId: true });
9
11
  function unwrap(envelope) {
10
12
  if (envelope.data === null || envelope.data === undefined) {
11
13
  throw new Error("API returned success but data is null");
@@ -85,14 +87,12 @@ function buildLabelEventsQuery(input) {
85
87
 
86
88
  class PromptResource {
87
89
  transport;
88
- cache;
89
90
  projectId;
90
91
  env;
91
92
  format;
92
93
  onUnknownRole;
93
- constructor(transport, cacheConfig, config) {
94
+ constructor(transport, config) {
94
95
  this.transport = transport;
95
- this.cache = new PromptCache(cacheConfig);
96
96
  this.projectId = config?.projectId;
97
97
  this.env = config?.env;
98
98
  this.format = config?.format;
@@ -187,21 +187,15 @@ class PromptResource {
187
187
  if (this.projectId === undefined || this.projectId.trim() === "") {
188
188
  throw new Error("[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
189
189
  }
190
- const env = options.env ?? this.env;
191
- if (env === undefined) {
192
- throw EnvNotFoundError.missing();
190
+ const label = options.label ?? this.env;
191
+ if (label === undefined) {
192
+ throw new MissingLabelError;
193
193
  }
194
- if (!ENV_SLUG_REGEX.test(env)) {
195
- throw new Error(`[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
194
+ if (!ENV_SLUG_REGEX.test(label)) {
195
+ throw new Error(`[to11ai/sdk] \`label\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(label)}`);
196
196
  }
197
- const { subject, variables, cache: useCache = true, fallback } = options;
198
- const cacheKey = buildRenderCacheKey(this.projectId, slug, env, subject, variables);
199
- if (useCache) {
200
- const cached = this.cache.get(cacheKey);
201
- if (cached)
202
- return structuredClone(cached);
203
- }
204
- const body = { slug, labels: [env] };
197
+ const { subject, variables, fallback } = options;
198
+ const body = { slug, labels: [label] };
205
199
  if (subject !== undefined)
206
200
  body.subject = subject;
207
201
  if (variables !== undefined)
@@ -212,15 +206,10 @@ class PromptResource {
212
206
  decode: (data) => zResolvedPromptEnvelope.parse(data)
213
207
  });
214
208
  const raw = unwrap(envelope);
215
- const result = normalizeResolvedEnvelope(raw, slug);
216
- const isWeighted = raw.strategy === "weighted" || result.variantName !== undefined;
217
- const cacheable = !(isWeighted && subject === undefined);
218
- if (useCache && cacheable) {
219
- this.cache.set(cacheKey, structuredClone(result));
220
- }
221
- return result;
209
+ return normalizeResolvedEnvelope(raw, slug);
222
210
  } catch (err) {
223
- if (err instanceof To11aiNetworkError && fallback !== undefined) {
211
+ const transient = err instanceof To11aiNetworkError || err instanceof To11aiApiError && err.statusCode >= 500;
212
+ if (transient && fallback !== undefined) {
224
213
  return liftFallbackToRendered(fallback, slug);
225
214
  }
226
215
  throw err;
@@ -52,5 +52,5 @@ export interface AnthropicRenderedPrompt {
52
52
  export type RenderResult<F extends RenderFormat | undefined> = F extends "openai" ? OpenAiRenderedPrompt : F extends "anthropic" ? AnthropicRenderedPrompt : RenderedPrompt;
53
53
  export declare function shapeOpenAI(prompt: RenderedPrompt, options?: ConvertMessagesOptions): OpenAiRenderedPrompt;
54
54
  export declare function shapeAnthropic(prompt: RenderedPrompt, options?: ConvertMessagesOptions): AnthropicRenderedPrompt;
55
- /** Shape a fetched prompt for `format`, or return it neutral when unset. */
55
+ /** Shape a rendered prompt for `format`, or return it neutral when unset. */
56
56
  export declare function shapePrompt(prompt: RenderedPrompt, format: RenderFormat | undefined, options?: ConvertMessagesOptions): RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt;
@@ -9,13 +9,12 @@ export type PromptStatus = Prompt["status"];
9
9
  export type PromptVersion = GenPromptVersion;
10
10
  export type PromptFormat = PromptVersion["format"];
11
11
  export type PromptLabelPointer = GenPromptLabelPointer;
12
- export type TextChatRole = "system" | "user" | "assistant" | "developer";
13
- export interface TextChatMessage {
14
- role: TextChatRole;
12
+ export type ChatRole = "system" | "user" | "assistant" | "developer";
13
+ export interface ChatMessage {
14
+ role: ChatRole;
15
15
  content: string;
16
16
  disabled?: boolean;
17
17
  }
18
- export type ChatMessage = TextChatMessage;
19
18
  export type RenderedChatMessage = GenRenderedChatPrompt["messages"][number];
20
19
  /** Provider shape for `prompts.render()` output. Omit for the neutral result. */
21
20
  export type RenderFormat = "openai" | "anthropic";
@@ -118,6 +117,13 @@ export interface ListPromptLabelsInput {
118
117
  promptId: string;
119
118
  }
120
119
  export type ListPromptLabelsResult = PromptLabelPointer[];
120
+ /**
121
+ * Slug rule for a `render()` label — lowercase-kebab, 1-63 chars. Env slugs are
122
+ * a special case of a label value, so labels are held to the same strict rule
123
+ * (no uppercase, no underscores): a label is a routing key, and case-insensitive
124
+ * routing keys invite silent "production" vs "Production" mismatches. Intentionally
125
+ * strict — do not loosen.
126
+ */
121
127
  export declare const ENV_SLUG_REGEX: RegExp;
122
128
  export type BlockRenderRecord = Record<string, "rendered" | "dimmed">;
123
129
  export interface RenderedPrompt {
@@ -164,19 +170,17 @@ export interface RenderedPrompt {
164
170
  */
165
171
  toolChoice?: NonNullable<ResolvedPrompt["toolChoice"]>;
166
172
  }
167
- export type ResolvedPromptEnvelope = GenResolvedPrompt & {
168
- /** Active release strategy. Used to decide weighted-no-subject cache bypass (D8). */
169
- strategy?: "single" | "weighted";
170
- };
173
+ export type ResolvedPromptEnvelope = GenResolvedPrompt;
171
174
  export interface RenderOptions {
172
- /** Serving environment for this call; overrides the client default. */
173
- env?: string;
175
+ /**
176
+ * Label to resolve this call's prompt version; overrides the client default.
177
+ * When omitted, falls back to the client's `env` (env is a label value).
178
+ */
179
+ label?: string;
174
180
  /** Stable id so a weighted release sends the same caller to the same variant. */
175
181
  subject?: string;
176
182
  variables?: Record<string, unknown>;
177
- /** Set false to bypass the LRU cache for this call. Default: true */
178
- cache?: boolean;
179
- /** Prompt served if the API is unreachable on a network error and cache is empty. */
183
+ /** Prompt served if the API is unreachable on a network error. */
180
184
  fallback?: RenderedPrompt;
181
185
  /** Provider shape for this call's output; overrides the client default. */
182
186
  format?: RenderFormat;
@@ -1,11 +1,7 @@
1
1
  import type { BehaviorStore } from "./behaviors/store";
2
- export type AuthConfig = {
2
+ export interface AuthConfig {
3
3
  readonly apiKey: string;
4
- readonly getAccessToken?: never;
5
- } | {
6
- readonly getAccessToken: () => Promise<string>;
7
- readonly apiKey?: never;
8
- };
4
+ }
9
5
  export interface TransportConfig {
10
6
  readonly baseUrl: string;
11
7
  readonly auth: AuthConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@to11ai/sdk",
3
- "version": "2.0.0-rc.3",
3
+ "version": "2.0.0-rc.4",
4
4
  "license": "MPL-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,14 +44,6 @@
44
44
  "require": "./dist/projectEnvironments/resource.cjs",
45
45
  "default": "./dist/projectEnvironments/resource.js"
46
46
  },
47
- "./routingRules": {
48
- "types": "./dist/routingRules/resource.d.ts",
49
- "bun": "./src/routingRules/resource.ts",
50
- "deno": "./src/routingRules/resource.ts",
51
- "import": "./dist/routingRules/resource.js",
52
- "require": "./dist/routingRules/resource.cjs",
53
- "default": "./dist/routingRules/resource.js"
54
- },
55
47
  "./errors": {
56
48
  "types": "./dist/errors.d.ts",
57
49
  "bun": "./src/errors.ts",
@@ -75,8 +67,8 @@
75
67
  "scripts": {
76
68
  "build": "bun run build:clean && bun run build:esm && bun run build:cjs && bun run build:types",
77
69
  "build:clean": "rm -rf dist",
78
- "build:esm": "bun build src/client.ts src/prompts/resource.ts src/projectEnvironments/resource.ts src/routingRules/resource.ts src/errors.ts src/gateway.ts --outdir dist --target node --format esm --external '*'",
79
- "build:cjs": "bun build src/client.ts src/prompts/resource.ts src/projectEnvironments/resource.ts src/routingRules/resource.ts src/errors.ts src/gateway.ts --outdir dist --target node --format cjs --external '*' --entry-naming '[dir]/[name].cjs'",
70
+ "build:esm": "bun build src/client.ts src/prompts/resource.ts src/projectEnvironments/resource.ts src/errors.ts src/gateway.ts --outdir dist --target node --format esm --external '*'",
71
+ "build:cjs": "bun build src/client.ts src/prompts/resource.ts src/projectEnvironments/resource.ts src/errors.ts src/gateway.ts --outdir dist --target node --format cjs --external '*' --entry-naming '[dir]/[name].cjs'",
80
72
  "build:types": "tsc -p tsconfig.build.json",
81
73
  "prepack": "bun run build",
82
74
  "codegen": "openapi-ts",
package/src/client.ts CHANGED
@@ -8,46 +8,30 @@ import {
8
8
  } from "./context";
9
9
  import { gatewayAuthHeaders } from "./gateway";
10
10
  import { ProjectEnvironmentResource } from "./projectEnvironments/resource";
11
- import type { CacheConfig } from "./prompts/cache";
12
11
  import { PromptResource } from "./prompts/resource";
13
12
  import {
14
13
  ENV_SLUG_REGEX,
15
14
  type RenderFormat,
16
15
  type UnknownRoleMode,
17
16
  } from "./prompts/types";
18
- import { RoutingRulesResource } from "./routingRules/resource";
19
17
  import { Transport } from "./transport";
20
18
 
21
- // SDK-behavior delivery (TO11-2100): the resolution store + helpers.
22
- export {
23
- BehaviorStore,
24
- type BehaviorStoreOptions,
25
- envVarNameForKey,
26
- type SdkBehaviorEnvelope,
27
- } from "./behaviors/store";
28
19
  export type { TurnAuthContext } from "./context";
29
20
  export { Conversation, Session, Turn } from "./context";
30
21
  // Re-export all error types for convenience
31
22
  export type {
32
- AmbiguousEnvLabelsReason,
33
- EnvNotFoundReason,
34
23
  NoActiveReleaseReason,
35
- NoApplicableRouteReason,
36
24
  PromptNotFoundReason,
37
25
  PromptPolicyViolationReason,
38
26
  } from "./errors";
39
27
  export {
40
28
  // TO11-980 (S9) prompt-resolution typed errors.
41
- AmbiguousEnvLabelsError,
42
- EnvNotEnabledForProviderError,
43
- EnvNotFoundError,
29
+ LabelNotFoundError,
30
+ MissingLabelError,
44
31
  NoActiveReleaseError,
45
- NoApplicableRouteError,
46
32
  PromptLabelNotRegisteredError,
47
33
  PromptNotFoundError,
48
34
  PromptPolicyViolationError,
49
- RuleNotFoundError,
50
- RuleVersionInvalidSnapshotError,
51
35
  To11aiApiError,
52
36
  To11aiAuthError,
53
37
  To11aiError,
@@ -75,7 +59,6 @@ export type {
75
59
  UpdateProjectEnvironmentResult,
76
60
  } from "./projectEnvironments/resource";
77
61
  export { ProjectEnvironmentResource } from "./projectEnvironments/resource";
78
- export type { CacheConfig } from "./prompts/cache";
79
62
  export type { PromptResourceConfig } from "./prompts/resource";
80
63
  export { PromptResource } from "./prompts/resource";
81
64
  export type {
@@ -92,42 +75,9 @@ export type {
92
75
  RenderedPrompt,
93
76
  RenderFormat,
94
77
  RenderOptions,
95
- ResolvedPrompt,
96
- ResolvedPromptEnvelope,
97
78
  UnknownRoleMode,
98
79
  } from "./prompts/types";
99
80
  export { ENV_SLUG_REGEX } from "./prompts/types";
100
- export type {
101
- CreateRuleVersionInput,
102
- CreateRuleVersionResult,
103
- GetRuleVersionInput,
104
- GetRuleVersionResult,
105
- ListRuleReleasesInput,
106
- ListRuleReleasesResult,
107
- ListRuleVersionsInput,
108
- ListRuleVersionsResult,
109
- RollbackRuleReleaseInput,
110
- RollbackRuleReleaseResult,
111
- RuleModelBinding,
112
- RuleProviderRef,
113
- RuleRelease,
114
- RuleReleaseActorType,
115
- RuleReleaseApprovalStatus,
116
- RuleReleaseChangeKind,
117
- RuleReleaseStrategy,
118
- RuleReleaseWeight,
119
- RuleSnapshot,
120
- RuleStrategy,
121
- RuleVersion,
122
- SetRuleReleaseInput,
123
- SetRuleReleaseResult,
124
- } from "./routingRules/resource";
125
- export {
126
- RoutingRuleReleaseResource,
127
- RoutingRulesResource,
128
- RoutingRuleVersionResource,
129
- } from "./routingRules/resource";
130
- export type { Transport } from "./transport";
131
81
 
132
82
  // ─── Client options ────────────────────────────────────────────────────────
133
83
 
@@ -143,12 +93,11 @@ export interface ClientOptions {
143
93
  */
144
94
  gatewayUrl?: string;
145
95
  /**
146
- * API key for server-side use. Precedence: this option → `TO11_API_KEY` env.
147
- * Rejected in browser contexts — use `getAccessToken` there.
96
+ * API key for server-side use (server, CLI, backend job). Precedence: this
97
+ * option → `TO11_API_KEY` env. Required. Rejected in browser contexts — a
98
+ * to11 API key carries gateway access and must never ship in client code.
148
99
  */
149
100
  apiKey?: string;
150
- /** Token callback for browser / edge contexts — avoids static secrets. */
151
- getAccessToken?: () => Promise<string>;
152
101
  /**
153
102
  * Provider API key (BYOK) carried by `openaiOptions()` / `anthropicOptions()`
154
103
  * as the provider client's `apiKey`, so a BYOK call is a single
@@ -183,17 +132,10 @@ export interface ClientOptions {
183
132
  * via `render(slug, { onUnknownRole })`.
184
133
  */
185
134
  onUnknownRole?: UnknownRoleMode;
186
- /** LRU cache configuration for render(). Defaults: ttlSeconds=300, maxEntries=100 */
187
- cache?: CacheConfig;
188
135
  /** Request timeout in milliseconds. Default: 30_000 */
189
136
  timeoutMs?: number;
190
137
  /** Max retry attempts for network errors / 5xx. Default: 3 */
191
138
  maxRetries?: number;
192
- /**
193
- * Purely local overrides for server-evaluated SDK-behavior flags (TO11-2100).
194
- * An init option here wins over a `TO11_SDK_BEHAVIOR_*` env var. No LaunchDarkly.
195
- */
196
- behaviorOverrides?: Record<string, unknown>;
197
139
  }
198
140
 
199
141
  // ─── Provider-client options (openaiOptions / anthropicOptions) ─────────────
@@ -230,19 +172,10 @@ export type ProviderClientOptions<
230
172
  export interface To11aiClient<F extends RenderFormat | undefined = undefined> {
231
173
  readonly prompts: PromptResource<F>;
232
174
  readonly projectEnvironments: ProjectEnvironmentResource;
233
- readonly routingRules: RoutingRulesResource;
234
- /**
235
- * Resolve a server-evaluated SDK-behavior flag value (TO11-2100) under the
236
- * fixed precedence: local override (init option → `TO11_SDK_BEHAVIOR_*` env
237
- * var) → server-delivered value (captured from responses the client has
238
- * already made) → the `codedDefault` passed here. A value whose runtime type
239
- * disagrees with `codedDefault` falls through to the next tier. The SDK
240
- * carries no LaunchDarkly dependency; this only reads what the server
241
- * delivered on the envelope.
242
- */
243
- readonly resolveBehavior: <T>(key: string, codedDefault: T) => T;
244
- /** Resolved API key (server mode); `undefined` in browser (`getAccessToken`) mode. */
245
- readonly apiKey: string | undefined;
175
+ /** Resolved API key (option → `TO11_API_KEY`). */
176
+ readonly apiKey: string;
177
+ /** Resolved control-plane base URL (option → `TO11_API_URL` → default). */
178
+ readonly baseUrl: string;
246
179
  /** Resolved data-plane gateway URL (option → `TO11_GATEWAY_URL` → default). */
247
180
  readonly gatewayUrl: string;
248
181
  /**
@@ -251,6 +184,11 @@ export interface To11aiClient<F extends RenderFormat | undefined = undefined> {
251
184
  * already default to it, so you don't need to pass it back into calls.
252
185
  */
253
186
  readonly projectId: string | undefined;
187
+ /**
188
+ * Resolved default serving environment (option → `TO11_ENV`); `undefined`
189
+ * when none is configured. `prompts.render()` already defaults to it.
190
+ */
191
+ readonly env: string | undefined;
254
192
  /** A new session (unit of work). Pass an id to group telemetry by your own id. */
255
193
  session(id?: string): Session;
256
194
  /**
@@ -299,9 +237,8 @@ export interface To11aiClient<F extends RenderFormat | undefined = undefined> {
299
237
  * - URLs (`baseUrl` / `gatewayUrl`) — option → env var → built-in default.
300
238
  * - `apiKey` / `projectId` — option → env var (no default).
301
239
  * - `env` / `format` — option only.
302
- * Auth mode:
303
- * - `apiKey` (or `TO11_API_KEY`) → server-side; rejected in browser contexts.
304
- * - `getAccessToken` → browser-safe; token fetched per request.
240
+ * Authenticate with `apiKey` (or `TO11_API_KEY`) — required, and server-side
241
+ * only (server, CLI, backend job); constructing with `apiKey` in a browser throws.
305
242
  *
306
243
  * @example Minimal setup (single call)
307
244
  * import { createClient } from "@to11ai/sdk";
@@ -371,15 +308,7 @@ export function createClient<
371
308
  )
372
309
  .replace(/\/+$/, "")
373
310
  .replace(/\/v1$/, "");
374
- const getAccessToken = options.getAccessToken;
375
- // Explicit token auth suppresses an ambient `TO11_API_KEY` so a shared env
376
- // (SSR / CI / edge) can't defeat an explicit `getAccessToken`. Only an
377
- // *explicit* `options.apiKey` alongside `getAccessToken` stays ambiguous and
378
- // is rejected by the exactly-one check below.
379
- const apiKey =
380
- getAccessToken !== undefined
381
- ? options.apiKey
382
- : pick(options.apiKey, "TO11_API_KEY");
311
+ const apiKey = pick(options.apiKey, "TO11_API_KEY");
383
312
  const projectId = pick(options.projectId, "TO11_PROJECT_ID");
384
313
  // Default serving env: explicit option → `TO11_ENV` env var — the same
385
314
  // `pick(option, "TO11_*")` resolution `apiKey`/`projectId` use (empty option
@@ -395,7 +324,7 @@ export function createClient<
395
324
  // undefined in the browser, so only the explicit option can reach here.
396
325
  if (isBrowser && options.apiKey !== undefined) {
397
326
  throw new Error(
398
- "[to11ai/sdk] Do not use `apiKey` in browser contexts — use `getAccessToken` instead.",
327
+ "[to11ai/sdk] Do not use `apiKey` in a browser — a to11 API key carries gateway access and must stay server-side (server, CLI, or backend job).",
399
328
  );
400
329
  }
401
330
 
@@ -422,23 +351,19 @@ export function createClient<
422
351
  );
423
352
  }
424
353
 
425
- // Exactly one auth source after resolution.
426
- const hasApiKey = apiKey !== undefined;
427
- const hasGetAccessToken = getAccessToken !== undefined;
428
- if (hasApiKey === hasGetAccessToken) {
354
+ // `apiKey` is the only auth source and is required.
355
+ if (apiKey === undefined) {
429
356
  throw new Error(
430
- "[to11ai/sdk] Provide exactly one of `apiKey` (or `TO11_API_KEY`) or `getAccessToken`.",
357
+ "[to11ai/sdk] An `apiKey` (or `TO11_API_KEY`) is required.",
431
358
  );
432
359
  }
433
360
 
434
- const auth = hasApiKey
435
- ? ({ apiKey: apiKey as string } as const)
436
- : ({ getAccessToken: getAccessToken as () => Promise<string> } as const);
361
+ const auth = { apiKey } as const;
437
362
 
438
- const behaviorStore = new BehaviorStore({
439
- initOverrides: options.behaviorOverrides,
440
- env: processEnv,
441
- });
363
+ // The SDK-behavior store (TO11-2100) still captures any server-delivered
364
+ // flags off responses, but the public `resolveBehavior` accessor is hidden
365
+ // for the 2.0 surface — kept internal so it can be re-exposed step by step.
366
+ const behaviorStore = new BehaviorStore({ env: processEnv });
442
367
 
443
368
  const transport = new Transport({
444
369
  baseUrl,
@@ -448,24 +373,18 @@ export function createClient<
448
373
  behaviorStore,
449
374
  });
450
375
 
451
- const prompts = new PromptResource<F>(transport, options.cache, {
376
+ const prompts = new PromptResource<F>(transport, {
452
377
  projectId,
453
378
  env,
454
379
  format: options.format,
455
380
  onUnknownRole: options.onUnknownRole,
456
381
  });
457
382
  const projectEnvironments = new ProjectEnvironmentResource(transport);
458
- const routingRules = new RoutingRulesResource(transport);
459
383
 
460
384
  // ── Gateway observability context (session → conversation → turn) ──
461
- // These build gateway auth headers, so they require a server-side apiKey +
462
- // projectId; fail loudly rather than emit a broken header bag.
385
+ // These build gateway auth headers, so they require a projectId; fail loudly
386
+ // rather than emit a broken header bag.
463
387
  const gatewayAuthContext = (): TurnAuthContext => {
464
- if (apiKey === undefined) {
465
- throw new Error(
466
- "[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.",
467
- );
468
- }
469
388
  if (projectId === undefined) {
470
389
  throw new Error(
471
390
  "[to11ai/sdk] session()/turn() need a `projectId` (or `TO11_PROJECT_ID`).",
@@ -496,11 +415,6 @@ export function createClient<
496
415
  defaultBaseURL: string,
497
416
  overrides?: T,
498
417
  ): ProviderClientOptions<T> => {
499
- if (apiKey === undefined) {
500
- throw new Error(
501
- "[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.",
502
- );
503
- }
504
418
  if (projectId === undefined) {
505
419
  throw new Error(
506
420
  "[to11ai/sdk] openaiOptions()/anthropicOptions() need a `projectId` (or `TO11_PROJECT_ID`) — it authenticates the gateway call via `x-to11-project-id`.",
@@ -530,12 +444,11 @@ export function createClient<
530
444
  return {
531
445
  prompts,
532
446
  projectEnvironments,
533
- routingRules,
534
- resolveBehavior: (key, codedDefault) =>
535
- behaviorStore.resolve(key, codedDefault),
536
447
  apiKey,
448
+ baseUrl,
537
449
  gatewayUrl,
538
450
  projectId,
451
+ env,
539
452
  session: (id) => new Session(gatewayAuthContext(), id),
540
453
  conversation: (id) => clientDefaultSession().conversation(id),
541
454
  turn: () => clientDefaultSession().turn(),
package/src/context.ts CHANGED
@@ -49,10 +49,6 @@ function newId(): string {
49
49
  return crypto.randomUUID();
50
50
  }
51
51
 
52
- function provenanceOf(prompt: PromptForHeaders): GatewayPromptProvenance {
53
- return "metadata" in prompt ? prompt.metadata : prompt;
54
- }
55
-
56
52
  /**
57
53
  * One agent turn — a single trace. A turn's `traceparent` is fixed for its
58
54
  * lifetime, so every LLM-call made under it groups into one trace.
@@ -105,7 +101,9 @@ export class Turn {
105
101
  traceparent: this.traceparent,
106
102
  };
107
103
  if (prompt !== undefined) {
108
- Object.assign(headers, gatewayPromptHeaders(provenanceOf(prompt)));
104
+ // gatewayPromptHeaders accepts a RenderedPrompt or a format-shaped
105
+ // result and unwraps `metadata` itself.
106
+ Object.assign(headers, gatewayPromptHeaders(prompt));
109
107
  }
110
108
  return headers;
111
109
  }
package/src/errors.ts CHANGED
@@ -342,60 +342,54 @@ export class PromptNotFoundError extends To11aiApiError {
342
342
  }
343
343
 
344
344
  /**
345
- * Reason payload for `EnvNotFoundError`.
345
+ * Thrown when the label supplied to `prompts.render()` does not resolve to an
346
+ * existing (active) label on the server. Surfaced from the server's
347
+ * `ENV_NOT_FOUND` code; `label` carries the rejected slug when the server
348
+ * echoes it.
346
349
  *
347
- * `source` discriminates the two failure modes (D2):
348
- * - `"missing"` — no env was supplied at init OR at call time. Raised
349
- * CLIENT-side, eagerly, before any request (R4 / AC4); `env` is absent.
350
- * - `"unknown"` — an env was supplied but the server's registry has no
351
- * such (active) env. Surfaced from the `ENV_NOT_FOUND` server code; `env`
352
- * carries the rejected slug when the server echoes it.
350
+ * This is distinct from `MissingLabelError`, which is raised client-side when
351
+ * no label was supplied at all.
353
352
  */
354
- export interface EnvNotFoundReason {
355
- env?: string;
356
- source: "missing" | "unknown";
357
- }
358
-
359
- /**
360
- * Thrown when no serving environment can be resolved for a `prompts.render()`
361
- * call. One class, two `reason.source` discriminants (see
362
- * `EnvNotFoundReason`): client-eager `"missing"` (R4) and server-forwarded
363
- * `"unknown"` (`ENV_NOT_FOUND`).
364
- *
365
- * The client-eager variant carries no `statusCode`/`errorCode` from the wire
366
- * (no request was made); it reports status `0` and errorCode `ENV_NOT_FOUND`
367
- * for a uniform catch surface.
368
- */
369
- export class EnvNotFoundError extends To11aiApiError {
370
- readonly reason: EnvNotFoundReason;
353
+ export class LabelNotFoundError extends To11aiApiError {
354
+ /** The label slug that could not be found, when the server echoes it. */
355
+ readonly label?: string;
371
356
 
372
357
  constructor(
373
358
  message: string,
374
- reason: EnvNotFoundReason,
359
+ label: string | undefined,
375
360
  details: unknown,
376
- // Required (no misleading default): the client-eager `missing()` variant
377
- // passes 0 (no HTTP call) and the transport mapper passes the real server
378
- // status, so a hardcoded default would never reflect reality.
379
361
  statusCode: number,
380
362
  ) {
363
+ // The wire code is unchanged (`ENV_NOT_FOUND`); only the SDK class name
364
+ // is label-oriented, decoupled from the server error code.
381
365
  super(message, statusCode, "ENV_NOT_FOUND", details);
382
- this.name = "EnvNotFoundError";
383
- this.reason = reason;
366
+ this.name = "LabelNotFoundError";
367
+ this.label = label;
384
368
  Object.setPrototypeOf(this, new.target.prototype);
385
369
  }
386
370
 
387
- /** Build the client-eager (missing-at-both) variant — no transport call. */
388
- static missing(env?: string): EnvNotFoundError {
389
- return new EnvNotFoundError(
390
- "[to11ai/sdk] No environment resolvable for prompts.render(): none supplied at client init and none supplied per call. Pass `env` to createClient or render(slug, { env }).",
391
- { env, source: "missing" },
392
- undefined,
393
- 0,
394
- );
371
+ static is(err: unknown): err is LabelNotFoundError {
372
+ return err instanceof LabelNotFoundError;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Thrown CLIENT-side, eagerly (before any request), when `prompts.render()` is
378
+ * called but no label is resolvable at all: none supplied per call, and no
379
+ * client `env` (or `TO11_ENV`) to fall back to. A usage error, not a lookup
380
+ * failure — distinct from `LabelNotFoundError`.
381
+ */
382
+ export class MissingLabelError extends To11aiError {
383
+ constructor(
384
+ message = "[to11ai/sdk] No label resolvable for prompts.render(): none supplied per call, and no client env (or TO11_ENV) to fall back to. Pass `label` to render(slug, { label }) or set `env` on createClient.",
385
+ ) {
386
+ super(message);
387
+ this.name = "MissingLabelError";
388
+ Object.setPrototypeOf(this, new.target.prototype);
395
389
  }
396
390
 
397
- static is(err: unknown): err is EnvNotFoundError {
398
- return err instanceof EnvNotFoundError;
391
+ static is(err: unknown): err is MissingLabelError {
392
+ return err instanceof MissingLabelError;
399
393
  }
400
394
  }
401
395
 
@@ -480,44 +474,6 @@ export class PromptPolicyViolationError extends To11aiApiError {
480
474
  }
481
475
  }
482
476
 
483
- /**
484
- * Reason payload for `AmbiguousEnvLabelsError`.
485
- *
486
- * Carried when the supplied env-label matched more than one candidate
487
- * version and the server cannot pick deterministically
488
- * (`AMBIGUOUS_ENV_LABELS`).
489
- */
490
- export interface AmbiguousEnvLabelsReason {
491
- env?: string;
492
- slug?: string;
493
- }
494
-
495
- /**
496
- * Thrown when the env→label resolution is ambiguous on the server
497
- * (`AMBIGUOUS_ENV_LABELS`) — the label matched multiple candidates and the
498
- * server declines to guess. Surfaces (rather than 500ing) so callers get a
499
- * typed, actionable error.
500
- */
501
- export class AmbiguousEnvLabelsError extends To11aiApiError {
502
- readonly reason: AmbiguousEnvLabelsReason;
503
-
504
- constructor(
505
- message: string,
506
- reason: AmbiguousEnvLabelsReason,
507
- details?: unknown,
508
- statusCode = 409,
509
- ) {
510
- super(message, statusCode, "AMBIGUOUS_ENV_LABELS", details);
511
- this.name = "AmbiguousEnvLabelsError";
512
- this.reason = reason;
513
- Object.setPrototypeOf(this, new.target.prototype);
514
- }
515
-
516
- static is(err: unknown): err is AmbiguousEnvLabelsError {
517
- return err instanceof AmbiguousEnvLabelsError;
518
- }
519
- }
520
-
521
477
  /**
522
478
  * Thrown when version create rejects the snapshot for a structural
523
479
  * reason — invalid variant params, malformed `when` clause, illegal