@to11ai/sdk 0.1.1 → 0.2.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.
Files changed (42) hide show
  1. package/dist/behaviors/store.d.ts +62 -0
  2. package/dist/client.cjs +37 -4
  3. package/dist/client.d.ts +41 -2
  4. package/dist/client.js +45 -4
  5. package/dist/errors.cjs +90 -1
  6. package/dist/errors.d.ts +142 -0
  7. package/dist/errors.js +90 -1
  8. package/dist/gateway.cjs +12 -0
  9. package/dist/gateway.d.ts +26 -12
  10. package/dist/gateway.js +14 -0
  11. package/dist/generated/index.d.ts +1 -0
  12. package/dist/generated/types.gen.d.ts +22974 -0
  13. package/dist/generated/zod.gen.d.ts +13531 -0
  14. package/dist/projectEnvironments/resource.cjs +7 -2
  15. package/dist/projectEnvironments/resource.js +7 -2
  16. package/dist/projectEnvironments/types.d.ts +4 -75
  17. package/dist/prompts/cache.d.ts +24 -3
  18. package/dist/prompts/resource.cjs +101 -3
  19. package/dist/prompts/resource.d.ts +47 -3
  20. package/dist/prompts/resource.js +103 -5
  21. package/dist/prompts/types.d.ts +36 -177
  22. package/dist/routingRules/resource.cjs +9 -2
  23. package/dist/routingRules/resource.js +9 -2
  24. package/dist/routingRules/types.d.ts +12 -123
  25. package/dist/transport.d.ts +23 -0
  26. package/package.json +23 -12
  27. package/src/behaviors/store.ts +175 -0
  28. package/src/client.ts +115 -3
  29. package/src/errors.ts +261 -0
  30. package/src/gateway.ts +54 -12
  31. package/src/generated/index.ts +3 -0
  32. package/src/generated/types.gen.ts +24083 -0
  33. package/src/generated/zod.gen.ts +12648 -0
  34. package/src/projectEnvironments/resource.ts +10 -0
  35. package/src/projectEnvironments/types.ts +9 -91
  36. package/src/prompts/cache.ts +39 -4
  37. package/src/prompts/resource.ts +258 -5
  38. package/src/prompts/types.ts +56 -237
  39. package/src/routingRules/resource.ts +10 -1
  40. package/src/routingRules/types.ts +21 -155
  41. package/src/traceparent.ts +69 -4
  42. package/src/transport.ts +188 -3
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Published-SDK behavior resolution (TO11-2100 / feature-flagging Slice S7).
3
+ *
4
+ * The to11 platform evaluates flags that influence *shipped-SDK behavior*
5
+ * server-side and delivers their values on the optional `sdkBehavior` field of
6
+ * the success envelope (PRD Decision 7). This SDK carries **no** LaunchDarkly
7
+ * dependency and never connects to to11's control plane — it only reads the
8
+ * value the server already evaluated.
9
+ *
10
+ * Resolution precedence, applied by {@link BehaviorStore.resolve}:
11
+ * 1. local override — init option, then env var (init option wins ties)
12
+ * 2. server-delivered value — the last value stored for that key
13
+ * 3. coded default — the value the caller passes to `resolve`
14
+ *
15
+ * A value whose runtime type disagrees with the coded default's type falls
16
+ * through to the next tier (ultimately the coded default), mirroring the
17
+ * server wrapper's kind-guard. An absent envelope field never clears
18
+ * previously-stored values.
19
+ */
20
+
21
+ /** The wire shape of the optional `sdkBehavior` envelope field. */
22
+ export interface SdkBehaviorEnvelope {
23
+ readonly v: number;
24
+ readonly flags: Record<string, unknown>;
25
+ }
26
+
27
+ /**
28
+ * The only `sdkBehavior.v` this SDK understands. A response carrying any other
29
+ * version is ignored wholesale (the store is left untouched), so a future
30
+ * envelope shape can never break this SDK — it degrades to coded defaults, the
31
+ * safe state.
32
+ */
33
+ const SUPPORTED_SDK_BEHAVIOR_VERSION = 1;
34
+
35
+ /** Env-var prefix for local SDK-behavior overrides: `TO11_SDK_BEHAVIOR_<KEY>`. */
36
+ const ENV_PREFIX = "TO11_SDK_BEHAVIOR_";
37
+
38
+ /**
39
+ * Convert a namespaced flag key to its env-var name: prefix + uppercased key
40
+ * with `.` and `-` replaced by `_`. e.g. `sdk.retry-v2` →
41
+ * `TO11_SDK_BEHAVIOR_SDK_RETRY_V2`.
42
+ */
43
+ export const envVarNameForKey = (key: string): string =>
44
+ `${ENV_PREFIX}${key.toUpperCase().replace(/[.-]/g, "_")}`;
45
+
46
+ /** Parse a JSON value out of an env-var string, falling back to the raw string. */
47
+ const parseEnvValue = (raw: string): unknown => {
48
+ try {
49
+ return JSON.parse(raw);
50
+ } catch {
51
+ return raw;
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Reject a candidate value whose runtime type disagrees with the coded
57
+ * default's type, for primitive defaults. Objects/arrays (JSON defaults) accept
58
+ * any non-null object candidate. `null`/`undefined` candidates are always
59
+ * rejected (treated as absent).
60
+ */
61
+ const typeMatchesDefault = <T>(
62
+ candidate: unknown,
63
+ codedDefault: T,
64
+ ): boolean => {
65
+ if (candidate === null || candidate === undefined) {
66
+ return false;
67
+ }
68
+ const defaultType = typeof codedDefault;
69
+ if (
70
+ defaultType === "boolean" ||
71
+ defaultType === "string" ||
72
+ defaultType === "number"
73
+ ) {
74
+ return typeof candidate === defaultType;
75
+ }
76
+ // JSON default: preserve container kind (array vs object) so an array
77
+ // default does not accept a plain object and vice versa. This matches the
78
+ // python-sdk store (which distinguishes mapping vs list/tuple) so identical
79
+ // payloads resolve identically across SDKs.
80
+ if (defaultType === "object" && codedDefault !== null) {
81
+ if (Array.isArray(codedDefault)) {
82
+ return Array.isArray(candidate);
83
+ }
84
+ return typeof candidate === "object" && !Array.isArray(candidate);
85
+ }
86
+ // Unusual default types — accept on exact typeof match.
87
+ return typeof candidate === defaultType;
88
+ };
89
+
90
+ export interface BehaviorStoreOptions {
91
+ /** Init-time local overrides, keyed by namespaced flag key. Highest precedence. */
92
+ readonly initOverrides?: Record<string, unknown>;
93
+ /**
94
+ * The process environment to read `TO11_SDK_BEHAVIOR_*` overrides from.
95
+ * Pass `undefined` (the default) in browser contexts so the SDK never
96
+ * touches `process.env`.
97
+ */
98
+ readonly env?: Record<string, string | undefined>;
99
+ }
100
+
101
+ export class BehaviorStore {
102
+ /** Last-write-wins server-delivered values, keyed by namespaced flag key. */
103
+ private readonly serverValues = new Map<string, unknown>();
104
+ /** Init-option overrides, keyed by flag key. */
105
+ private readonly initOverrides: ReadonlyMap<string, unknown>;
106
+ /** Env-var overrides, keyed by full env-var name (e.g. `TO11_SDK_BEHAVIOR_SDK_RETRY_V2`). */
107
+ private readonly envOverrides: ReadonlyMap<string, unknown>;
108
+
109
+ constructor(options?: BehaviorStoreOptions) {
110
+ this.initOverrides = new Map(Object.entries(options?.initOverrides ?? {}));
111
+
112
+ const env = options?.env;
113
+ const envMap = new Map<string, unknown>();
114
+ if (env) {
115
+ for (const [name, raw] of Object.entries(env)) {
116
+ if (raw !== undefined && name.startsWith(ENV_PREFIX)) {
117
+ envMap.set(name, parseEnvValue(raw));
118
+ }
119
+ }
120
+ }
121
+ this.envOverrides = envMap;
122
+ }
123
+
124
+ /**
125
+ * Update the server-delivered values from a response's `sdkBehavior` field.
126
+ * No-op when the field is absent or carries an unsupported version. Never
127
+ * clears previously-stored values.
128
+ */
129
+ updateFromEnvelope(sdkBehavior: SdkBehaviorEnvelope | undefined): void {
130
+ if (
131
+ sdkBehavior === undefined ||
132
+ sdkBehavior.v !== SUPPORTED_SDK_BEHAVIOR_VERSION ||
133
+ typeof sdkBehavior.flags !== "object" ||
134
+ sdkBehavior.flags === null
135
+ ) {
136
+ return;
137
+ }
138
+ for (const [key, value] of Object.entries(sdkBehavior.flags)) {
139
+ this.serverValues.set(key, value);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Resolve a behavior value under the fixed precedence: local override (init
145
+ * option, then env var) → server-delivered value → coded default. A tier
146
+ * whose value is absent or type-mismatched against `codedDefault` falls
147
+ * through to the next tier.
148
+ */
149
+ resolve<T>(key: string, codedDefault: T): T {
150
+ // 1a. Init-option override (highest precedence).
151
+ if (this.initOverrides.has(key)) {
152
+ const value = this.initOverrides.get(key);
153
+ if (typeMatchesDefault(value, codedDefault)) {
154
+ return value as T;
155
+ }
156
+ }
157
+ // 1b. Env-var override.
158
+ const envName = envVarNameForKey(key);
159
+ if (this.envOverrides.has(envName)) {
160
+ const value = this.envOverrides.get(envName);
161
+ if (typeMatchesDefault(value, codedDefault)) {
162
+ return value as T;
163
+ }
164
+ }
165
+ // 2. Server-delivered value.
166
+ if (this.serverValues.has(key)) {
167
+ const value = this.serverValues.get(key);
168
+ if (typeMatchesDefault(value, codedDefault)) {
169
+ return value as T;
170
+ }
171
+ }
172
+ // 3. Coded default.
173
+ return codedDefault;
174
+ }
175
+ }
package/src/client.ts CHANGED
@@ -1,15 +1,37 @@
1
+ import { BehaviorStore } from "./behaviors/store";
1
2
  import { ProjectEnvironmentResource } from "./projectEnvironments/resource";
2
3
  import type { CacheConfig } from "./prompts/cache";
3
4
  import { PromptResource } from "./prompts/resource";
5
+ import { ENV_SLUG_REGEX } from "./prompts/types";
4
6
  import { RoutingRulesResource } from "./routingRules/resource";
5
7
  import { Transport } from "./transport";
6
8
 
9
+ // SDK-behavior delivery (TO11-2100): the resolution store + helpers.
10
+ export {
11
+ BehaviorStore,
12
+ type BehaviorStoreOptions,
13
+ envVarNameForKey,
14
+ type SdkBehaviorEnvelope,
15
+ } from "./behaviors/store";
7
16
  // Re-export all error types for convenience
8
- export type { NoApplicableRouteReason } from "./errors";
17
+ export type {
18
+ AmbiguousEnvLabelsReason,
19
+ EnvNotFoundReason,
20
+ NoActiveReleaseReason,
21
+ NoApplicableRouteReason,
22
+ PromptNotFoundReason,
23
+ PromptPolicyViolationReason,
24
+ } from "./errors";
9
25
  export {
26
+ // TO11-980 (S9) prompt-resolution typed errors.
27
+ AmbiguousEnvLabelsError,
10
28
  EnvNotEnabledForProviderError,
29
+ EnvNotFoundError,
30
+ NoActiveReleaseError,
11
31
  NoApplicableRouteError,
12
32
  PromptLabelNotRegisteredError,
33
+ PromptNotFoundError,
34
+ PromptPolicyViolationError,
13
35
  RuleNotFoundError,
14
36
  RuleVersionInvalidSnapshotError,
15
37
  To11aiApiError,
@@ -39,7 +61,20 @@ export type {
39
61
  } from "./projectEnvironments/resource";
40
62
  export { ProjectEnvironmentResource } from "./projectEnvironments/resource";
41
63
  export type { CacheConfig } from "./prompts/cache";
64
+ export type { PromptResourceConfig } from "./prompts/resource";
42
65
  export { PromptResource } from "./prompts/resource";
66
+ // TO11-980 (S9): env-aware fetch() surface types at the package entrypoint (AC9).
67
+ export type {
68
+ BlockRenderRecord,
69
+ FetchedPrompt,
70
+ FetchPromptOptions,
71
+ PromptContext,
72
+ RenderedChatMessage,
73
+ RenderedPrompt,
74
+ ResolvedPrompt,
75
+ ResolvedPromptEnvelope,
76
+ } from "./prompts/types";
77
+ export { ENV_SLUG_REGEX } from "./prompts/types";
43
78
  export type {
44
79
  CreateRuleVersionInput,
45
80
  CreateRuleVersionResult,
@@ -92,12 +127,37 @@ type AuthOptions = ServerAuthOptions | BrowserAuthOptions;
92
127
 
93
128
  export type ClientOptions = AuthOptions & {
94
129
  baseUrl: string;
130
+ /**
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.
136
+ */
137
+ projectId?: string;
138
+ /**
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.
145
+ */
146
+ env?: string;
95
147
  /** LRU cache configuration for resolve(). Defaults: ttlSeconds=300, maxEntries=100 */
96
148
  cache?: CacheConfig;
97
149
  /** Request timeout in milliseconds. Default: 30_000 */
98
150
  timeoutMs?: number;
99
151
  /** Max retry attempts for network errors / 5xx. Default: 3 */
100
152
  maxRetries?: number;
153
+ /**
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.
159
+ */
160
+ behaviorOverrides?: Record<string, unknown>;
101
161
  };
102
162
 
103
163
  // ─── Client interface ──────────────────────────────────────────────────────
@@ -106,6 +166,16 @@ export interface To11aiClient {
106
166
  readonly prompts: PromptResource;
107
167
  readonly projectEnvironments: ProjectEnvironmentResource;
108
168
  readonly routingRules: RoutingRulesResource;
169
+ /**
170
+ * Resolve a server-evaluated SDK-behavior flag value (TO11-2100) under the
171
+ * fixed precedence: local override (init option → `TO11_SDK_BEHAVIOR_*` env
172
+ * var) → server-delivered value (captured from responses the client has
173
+ * already made) → the `codedDefault` passed here. A value whose runtime type
174
+ * disagrees with `codedDefault` falls through to the next tier. The SDK
175
+ * carries no LaunchDarkly dependency; this only reads what the server
176
+ * delivered on the envelope.
177
+ */
178
+ readonly resolveBehavior: <T>(key: string, codedDefault: T) => T;
109
179
  }
110
180
 
111
181
  // ─── Factory ───────────────────────────────────────────────────────────────
@@ -147,6 +217,23 @@ export function createClient(options: ClientOptions): To11aiClient {
147
217
  throw new Error("[to11ai/sdk] `apiKey` must not be an empty string.");
148
218
  }
149
219
 
220
+ // R0: validate `projectId` eagerly when supplied (loud throw). An empty /
221
+ // whitespace-only value is a misconfiguration; reject before any request.
222
+ if (options.projectId !== undefined && options.projectId.trim() === "") {
223
+ throw new Error(
224
+ "[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.",
225
+ );
226
+ }
227
+
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)) {
232
+ throw new Error(
233
+ `[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(options.env)}`,
234
+ );
235
+ }
236
+
150
237
  // TypeScript's union type prevents callers from providing both, but JS consumers can.
151
238
  // Perform an explicit XOR check at runtime to catch the case where both are supplied.
152
239
  const hasApiKey = options.apiKey !== undefined;
@@ -164,16 +251,41 @@ export function createClient(options: ClientOptions): To11aiClient {
164
251
  ? ({ apiKey: options.apiKey } as const)
165
252
  : ({ getAccessToken: getAccessToken as () => Promise<string> } as const);
166
253
 
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
+ const behaviorStore = new BehaviorStore({
265
+ initOverrides: options.behaviorOverrides,
266
+ env: processEnv,
267
+ });
268
+
167
269
  const transport = new Transport({
168
270
  baseUrl: options.baseUrl,
169
271
  auth,
170
272
  timeoutMs: options.timeoutMs,
171
273
  maxRetries: options.maxRetries,
274
+ behaviorStore,
172
275
  });
173
276
 
174
- const prompts = new PromptResource(transport, options.cache);
277
+ const prompts = new PromptResource(transport, options.cache, {
278
+ projectId: options.projectId,
279
+ env: options.env,
280
+ });
175
281
  const projectEnvironments = new ProjectEnvironmentResource(transport);
176
282
  const routingRules = new RoutingRulesResource(transport);
177
283
 
178
- return { prompts, projectEnvironments, routingRules };
284
+ return {
285
+ prompts,
286
+ projectEnvironments,
287
+ routingRules,
288
+ resolveBehavior: (key, codedDefault) =>
289
+ behaviorStore.resolve(key, codedDefault),
290
+ };
179
291
  }
package/src/errors.ts CHANGED
@@ -90,6 +90,29 @@ export class To11aiNetworkError extends To11aiError {
90
90
  }
91
91
  }
92
92
 
93
+ /**
94
+ * Thrown when a 2xx response body fails runtime validation against the
95
+ * generated wire schema. Distinct from `To11aiValidationError`, which models a
96
+ * server-side 4xx validation rejection.
97
+ */
98
+ export class To11aiResponseValidationError extends To11aiError {
99
+ readonly path: string;
100
+ readonly issues: { message: string; path: (string | number)[] }[];
101
+
102
+ constructor(path: string, zodError: unknown, cause?: unknown) {
103
+ super(`[to11ai/sdk] Response from ${path} failed schema validation`, {
104
+ cause,
105
+ });
106
+ this.name = "To11aiResponseValidationError";
107
+ this.path = path;
108
+ const candidate = (zodError as { issues?: unknown })?.issues;
109
+ this.issues = Array.isArray(candidate)
110
+ ? (candidate as { message: string; path: (string | number)[] }[])
111
+ : [];
112
+ Object.setPrototypeOf(this, new.target.prototype);
113
+ }
114
+ }
115
+
93
116
  /**
94
117
  * Thrown when strict-mode rejects a label move for an unregistered label.
95
118
  *
@@ -227,6 +250,244 @@ export class EnvNotEnabledForProviderError extends To11aiApiError {
227
250
  }
228
251
  }
229
252
 
253
+ // ─── Prompt resolution typed errors (TO11-980 / S9) ────────────────────────
254
+ //
255
+ // These four subclasses give `prompts.fetch()` callers a discriminable error
256
+ // family for prompt resolution. Each:
257
+ // - extends `To11aiApiError`, so existing broad `catch (err instanceof
258
+ // To11aiApiError)` blocks keep matching (AC8),
259
+ // - carries a structured `reason` payload (not a bare string),
260
+ // - ships a `static is()` type guard like its siblings.
261
+ //
262
+ // IMPORTANT — wire mapping (server TO11-1925/1926): the canonical
263
+ // SCREAMING_SNAKE error code lives in the envelope's `error.details.code`
264
+ // (e.g. `PROMPT_NOT_FOUND`), NOT top-level `error.code` (which carries the
265
+ // generic HTTP class `not_found` / `conflict` / `unprocessable_entity`). The
266
+ // transport mapper reads `details.code` for these four branches — see
267
+ // `transport.ts`. `errorCode` on the constructed error is set to that
268
+ // canonical `details.code` so call sites can switch on it consistently.
269
+
270
+ /**
271
+ * Reason payload for `PromptNotFoundError`.
272
+ *
273
+ * Surfaced when the server cannot resolve the prompt for the given slug
274
+ * (`PROMPT_NOT_FOUND`) or cannot find a version matching the supplied
275
+ * labels (`PROMPT_LABELS_NOT_FOUND`). `slug` / `projectId` are echoed from
276
+ * the request context when the server provides them in `details`.
277
+ */
278
+ export interface PromptNotFoundReason {
279
+ slug?: string;
280
+ projectId?: string;
281
+ }
282
+
283
+ /**
284
+ * Thrown when a prompt cannot be resolved. Covers two server codes (single
285
+ * class so one `catch` matches both):
286
+ *
287
+ * * `PROMPT_NOT_FOUND` — no prompt exists for the requested slug.
288
+ * * `PROMPT_LABELS_NOT_FOUND` — the prompt exists but no version matches
289
+ * all the supplied labels (the `env`→label resolution found nothing).
290
+ *
291
+ * Surfaces at HTTP 404 (the canonical code lives in `details.code`).
292
+ */
293
+ export class PromptNotFoundError extends To11aiApiError {
294
+ readonly reason: PromptNotFoundReason;
295
+
296
+ constructor(
297
+ message: string,
298
+ errorCode: string,
299
+ reason: PromptNotFoundReason,
300
+ details?: unknown,
301
+ statusCode = 404,
302
+ ) {
303
+ super(message, statusCode, errorCode, details);
304
+ this.name = "PromptNotFoundError";
305
+ this.reason = reason;
306
+ Object.setPrototypeOf(this, new.target.prototype);
307
+ }
308
+
309
+ static is(err: unknown): err is PromptNotFoundError {
310
+ return err instanceof PromptNotFoundError;
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Reason payload for `EnvNotFoundError`.
316
+ *
317
+ * `source` discriminates the two failure modes (D2):
318
+ * - `"missing"` — no env was supplied at init OR at call time. Raised
319
+ * CLIENT-side, eagerly, before any request (R4 / AC4); `env` is absent.
320
+ * - `"unknown"` — an env was supplied but the server's registry has no
321
+ * such (active) env. Surfaced from the `ENV_NOT_FOUND` server code; `env`
322
+ * carries the rejected slug when the server echoes it.
323
+ */
324
+ export interface EnvNotFoundReason {
325
+ env?: string;
326
+ source: "missing" | "unknown";
327
+ }
328
+
329
+ /**
330
+ * Thrown when no serving environment can be resolved for a `prompts.fetch()`
331
+ * call. One class, two `reason.source` discriminants (see
332
+ * `EnvNotFoundReason`): client-eager `"missing"` (R4) and server-forwarded
333
+ * `"unknown"` (`ENV_NOT_FOUND`).
334
+ *
335
+ * The client-eager variant carries no `statusCode`/`errorCode` from the wire
336
+ * (no request was made); it reports status `0` and errorCode `ENV_NOT_FOUND`
337
+ * for a uniform catch surface.
338
+ */
339
+ export class EnvNotFoundError extends To11aiApiError {
340
+ readonly reason: EnvNotFoundReason;
341
+
342
+ constructor(
343
+ message: string,
344
+ reason: EnvNotFoundReason,
345
+ details: unknown,
346
+ // Required (no misleading default): the client-eager `missing()` variant
347
+ // passes 0 (no HTTP call) and the transport mapper passes the real server
348
+ // status, so a hardcoded default would never reflect reality.
349
+ statusCode: number,
350
+ ) {
351
+ super(message, statusCode, "ENV_NOT_FOUND", details);
352
+ this.name = "EnvNotFoundError";
353
+ this.reason = reason;
354
+ Object.setPrototypeOf(this, new.target.prototype);
355
+ }
356
+
357
+ /** Build the client-eager (missing-at-both) variant — no transport call. */
358
+ static missing(env?: string): EnvNotFoundError {
359
+ return new EnvNotFoundError(
360
+ "[to11ai/sdk] No environment resolvable for prompts.fetch(): none supplied at client init and none supplied per call. Pass `env` to createClient or fetch(slug, { env }).",
361
+ { env, source: "missing" },
362
+ undefined,
363
+ 0,
364
+ );
365
+ }
366
+
367
+ static is(err: unknown): err is EnvNotFoundError {
368
+ return err instanceof EnvNotFoundError;
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Reason payload for `NoActiveReleaseError`.
374
+ *
375
+ * Identifies the prompt (by `slug` and/or `promptId`) and the `env` for
376
+ * which no active release row exists.
377
+ */
378
+ export interface NoActiveReleaseReason {
379
+ slug?: string;
380
+ promptId?: string;
381
+ env?: string;
382
+ }
383
+
384
+ /**
385
+ * Thrown when a prompt resolves but has no active release for the requested
386
+ * env (`NO_ACTIVE_RELEASE`). The release primitive (S3) found the prompt but
387
+ * nothing is currently serving in that environment.
388
+ */
389
+ export class NoActiveReleaseError extends To11aiApiError {
390
+ readonly reason: NoActiveReleaseReason;
391
+
392
+ constructor(
393
+ message: string,
394
+ reason: NoActiveReleaseReason,
395
+ details?: unknown,
396
+ statusCode = 409,
397
+ ) {
398
+ super(message, statusCode, "NO_ACTIVE_RELEASE", details);
399
+ this.name = "NoActiveReleaseError";
400
+ this.reason = reason;
401
+ Object.setPrototypeOf(this, new.target.prototype);
402
+ }
403
+
404
+ static is(err: unknown): err is NoActiveReleaseError {
405
+ return err instanceof NoActiveReleaseError;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Reason payload for `PromptPolicyViolationError`.
411
+ *
412
+ * `code` is the specific server policy code (`POLICY_VIOLATION` or one of the
413
+ * `ENV_POLICY_*` family from S5 / TO11-976). `policy` / `detail` are
414
+ * server-shaped and forwarded verbatim when present.
415
+ */
416
+ export interface PromptPolicyViolationReason {
417
+ code: string;
418
+ policy?: string;
419
+ detail?: unknown;
420
+ }
421
+
422
+ /**
423
+ * Thrown when the server's S5 environment-policy machinery rejects a
424
+ * resolution. Forwarded verbatim from the server policy codes — the generic
425
+ * `POLICY_VIOLATION` and the `ENV_POLICY_*` family (required-source-env,
426
+ * minimum-dwell-time, requires-approval, separation-of-duties, …).
427
+ *
428
+ * CRITICAL (D3 branch-ordering): the server can return a policy violation at
429
+ * HTTP 403. The transport mapper MUST match this (by `details.code`) BEFORE
430
+ * the generic `401 || 403 → To11aiAuthError` guard, otherwise a 403 policy
431
+ * error would be swallowed as an auth error (AC6).
432
+ */
433
+ export class PromptPolicyViolationError extends To11aiApiError {
434
+ readonly reason: PromptPolicyViolationReason;
435
+
436
+ constructor(
437
+ message: string,
438
+ reason: PromptPolicyViolationReason,
439
+ details?: unknown,
440
+ statusCode = 403,
441
+ ) {
442
+ super(message, statusCode, reason.code, details);
443
+ this.name = "PromptPolicyViolationError";
444
+ this.reason = reason;
445
+ Object.setPrototypeOf(this, new.target.prototype);
446
+ }
447
+
448
+ static is(err: unknown): err is PromptPolicyViolationError {
449
+ return err instanceof PromptPolicyViolationError;
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Reason payload for `AmbiguousEnvLabelsError`.
455
+ *
456
+ * Carried when the supplied env-label matched more than one candidate
457
+ * version and the server cannot pick deterministically
458
+ * (`AMBIGUOUS_ENV_LABELS`).
459
+ */
460
+ export interface AmbiguousEnvLabelsReason {
461
+ env?: string;
462
+ slug?: string;
463
+ }
464
+
465
+ /**
466
+ * Thrown when the env→label resolution is ambiguous on the server
467
+ * (`AMBIGUOUS_ENV_LABELS`) — the label matched multiple candidates and the
468
+ * server declines to guess. Surfaces (rather than 500ing) so callers get a
469
+ * typed, actionable error.
470
+ */
471
+ export class AmbiguousEnvLabelsError extends To11aiApiError {
472
+ readonly reason: AmbiguousEnvLabelsReason;
473
+
474
+ constructor(
475
+ message: string,
476
+ reason: AmbiguousEnvLabelsReason,
477
+ details?: unknown,
478
+ statusCode = 409,
479
+ ) {
480
+ super(message, statusCode, "AMBIGUOUS_ENV_LABELS", details);
481
+ this.name = "AmbiguousEnvLabelsError";
482
+ this.reason = reason;
483
+ Object.setPrototypeOf(this, new.target.prototype);
484
+ }
485
+
486
+ static is(err: unknown): err is AmbiguousEnvLabelsError {
487
+ return err instanceof AmbiguousEnvLabelsError;
488
+ }
489
+ }
490
+
230
491
  /**
231
492
  * Thrown when version create rejects the snapshot for a structural
232
493
  * reason — invalid variant params, malformed `when` clause, illegal