@to11ai/sdk 2.0.0-rc.1 → 2.0.0-rc.2

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/dist/client.cjs CHANGED
@@ -108,7 +108,7 @@ function createClient(options = {}) {
108
108
  const getAccessToken = options.getAccessToken;
109
109
  const apiKey = getAccessToken !== undefined ? options.apiKey : pick(options.apiKey, "TO11_API_KEY");
110
110
  const projectId = pick(options.projectId, "TO11_PROJECT_ID");
111
- const env = options.env;
111
+ const env = pick(options.env, "TO11_ENV");
112
112
  const providerApiKey = options.providerApiKey?.trim() || undefined;
113
113
  if (isBrowser && options.apiKey !== undefined) {
114
114
  throw new Error("[to11ai/sdk] Do not use `apiKey` in browser contexts — use `getAccessToken` instead.");
@@ -119,6 +119,9 @@ function createClient(options = {}) {
119
119
  if (options.projectId !== undefined && options.projectId.trim() === "") {
120
120
  throw new Error("[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.");
121
121
  }
122
+ if (options.env !== undefined && options.env.trim() === "") {
123
+ throw new Error("[to11ai/sdk] `env` must not be an empty string — omit it or pass a valid env slug.");
124
+ }
122
125
  if (env !== undefined && !import_types.ENV_SLUG_REGEX.test(env)) {
123
126
  throw new Error(`[to11ai/sdk] \`env\` must match ${import_types.ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
124
127
  }
package/dist/client.d.ts CHANGED
@@ -17,7 +17,7 @@ export type { CacheConfig } from "./prompts/cache";
17
17
  export type { PromptResourceConfig } from "./prompts/resource";
18
18
  export { PromptResource } from "./prompts/resource";
19
19
  export type { AnthropicRenderedPrompt, OpenAiRenderedPrompt, PromptConfig, PromptMetadata, RenderResult, } from "./prompts/shape";
20
- export type { BlockRenderRecord, FetchedPrompt, FetchPromptOptions, RenderedChatMessage, RenderedPrompt, RenderFormat, RenderOptions, ResolvedPrompt, ResolvedPromptEnvelope, UnknownRoleMode, } from "./prompts/types";
20
+ export type { BlockRenderRecord, RenderedChatMessage, RenderedPrompt, RenderFormat, RenderOptions, ResolvedPrompt, ResolvedPromptEnvelope, UnknownRoleMode, } from "./prompts/types";
21
21
  export { ENV_SLUG_REGEX } from "./prompts/types";
22
22
  export type { CreateRuleVersionInput, CreateRuleVersionResult, GetRuleVersionInput, GetRuleVersionResult, ListRuleReleasesInput, ListRuleReleasesResult, ListRuleVersionsInput, ListRuleVersionsResult, RollbackRuleReleaseInput, RollbackRuleReleaseResult, RuleModelBinding, RuleProviderRef, RuleRelease, RuleReleaseActorType, RuleReleaseApprovalStatus, RuleReleaseChangeKind, RuleReleaseStrategy, RuleReleaseWeight, RuleSnapshot, RuleStrategy, RuleVersion, SetRuleReleaseInput, SetRuleReleaseResult, } from "./routingRules/resource";
23
23
  export { RoutingRuleReleaseResource, RoutingRulesResource, RoutingRuleVersionResource, } from "./routingRules/resource";
@@ -57,8 +57,9 @@ export interface ClientOptions {
57
57
  projectId?: string;
58
58
  /**
59
59
  * Default serving environment for `prompts.render(slug)`, inherited by every
60
- * render unless overridden per call. Validated eagerly against the
61
- * project-environments slug rule (`^[a-z0-9-]{1,63}$`).
60
+ * render unless overridden per call. Precedence: this option → `TO11_ENV`
61
+ * env. Validated eagerly against the project-environments slug rule
62
+ * (`^[a-z0-9-]{1,63}$`).
62
63
  */
63
64
  env?: string;
64
65
  /**
package/dist/client.js CHANGED
@@ -64,7 +64,7 @@ function createClient(options = {}) {
64
64
  const getAccessToken = options.getAccessToken;
65
65
  const apiKey = getAccessToken !== undefined ? options.apiKey : pick(options.apiKey, "TO11_API_KEY");
66
66
  const projectId = pick(options.projectId, "TO11_PROJECT_ID");
67
- const env = options.env;
67
+ const env = pick(options.env, "TO11_ENV");
68
68
  const providerApiKey = options.providerApiKey?.trim() || undefined;
69
69
  if (isBrowser && options.apiKey !== undefined) {
70
70
  throw new Error("[to11ai/sdk] Do not use `apiKey` in browser contexts — use `getAccessToken` instead.");
@@ -75,6 +75,9 @@ function createClient(options = {}) {
75
75
  if (options.projectId !== undefined && options.projectId.trim() === "") {
76
76
  throw new Error("[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.");
77
77
  }
78
+ if (options.env !== undefined && options.env.trim() === "") {
79
+ throw new Error("[to11ai/sdk] `env` must not be an empty string — omit it or pass a valid env slug.");
80
+ }
78
81
  if (env !== undefined && !ENV_SLUG_REGEX.test(env)) {
79
82
  throw new Error(`[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
80
83
  }
package/dist/context.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface TurnAuthContext {
17
17
  }
18
18
  /**
19
19
  * A prompt argument accepted by `turn.headers()`. Either the flat provenance
20
- * (a neutral `FetchedPrompt`) or a `format`-shaped result carrying it under
20
+ * (a neutral `RenderedPrompt`) or a `format`-shaped result carrying it under
21
21
  * `metadata` — both are accepted so the format and neutral render paths carry
22
22
  * provenance identically.
23
23
  */
package/dist/gateway.cjs CHANGED
@@ -88,6 +88,9 @@ function gatewayPromptHeaders(prompt) {
88
88
  if (prompt.label.length > 0) {
89
89
  headers["x-to11-prompt-label"] = prompt.label;
90
90
  }
91
+ if (typeof prompt.slug === "string" && prompt.slug.length > 0) {
92
+ headers["x-to11-prompt-slug"] = prompt.slug;
93
+ }
91
94
  return headers;
92
95
  }
93
96
  function toOpenAITools(tools) {
package/dist/gateway.d.ts CHANGED
@@ -42,6 +42,9 @@ export declare function gatewayAuthHeaders(options: GatewayAuthOptions): Record<
42
42
  * release was weighted); omitted for `single`-strategy releases.
43
43
  * - `x-to11-prompt-label` — the single label the prompt resolved through (the
44
44
  * env slug today); omitted when empty (the gateway rejects empty values).
45
+ * - `x-to11-prompt-slug` — the prompt's human-readable slug, so traces show a
46
+ * readable name alongside the opaque `x-to11-prompt-id`; omitted when empty
47
+ * or absent (a legacy `resolve()` result carries no slug).
45
48
  *
46
49
  * The parameter type is WIDENED to accept either the enriched `render()`
47
50
  * result or the legacy `ResolvedPrompt`. The new fields are read when present
@@ -57,6 +60,11 @@ export interface GatewayPromptProvenance {
57
60
  version: number;
58
61
  /** The single label the prompt resolved through (env slug today). */
59
62
  label: string;
63
+ /**
64
+ * The prompt's human-readable slug (the `render()`/`resolve()` input). Present
65
+ * on an enriched `render()` result; a legacy `ResolvedPrompt` omits it.
66
+ */
67
+ slug?: string;
60
68
  /** Present on an enriched result; a legacy `resolve()` result omits it. */
61
69
  releaseId?: string | null;
62
70
  /** Present IFF the resolved release was weighted. */
package/dist/gateway.js CHANGED
@@ -39,6 +39,9 @@ function gatewayPromptHeaders(prompt) {
39
39
  if (prompt.label.length > 0) {
40
40
  headers["x-to11-prompt-label"] = prompt.label;
41
41
  }
42
+ if (typeof prompt.slug === "string" && prompt.slug.length > 0) {
43
+ headers["x-to11-prompt-slug"] = prompt.slug;
44
+ }
42
45
  return headers;
43
46
  }
44
47
  function toOpenAITools(tools) {
@@ -1,4 +1,4 @@
1
- import type { FetchedPrompt, ResolvedPrompt } from "./types";
1
+ import type { RenderedPrompt } from "./types";
2
2
  export interface CacheConfig {
3
3
  /** Time-to-live in seconds. Default: 300 (5 minutes) */
4
4
  ttlSeconds?: number;
@@ -6,12 +6,11 @@ export interface CacheConfig {
6
6
  maxEntries?: number;
7
7
  }
8
8
  /**
9
- * The cache stores either a legacy `resolve()` result or an enriched
10
- * `render()` result. Both are JSON-cloneable; the cache is shape-agnostic.
9
+ * The cache stores enriched `render()` results, which are JSON-cloneable.
11
10
  */
12
- export type CacheableResolved = ResolvedPrompt | FetchedPrompt;
11
+ export type CacheableRendered = RenderedPrompt;
13
12
  /**
14
- * Build a canonical cache key for a resolve() call.
13
+ * Build a canonical base cache key from project/slug/labels/variables.
15
14
  *
16
15
  * - projectId is included to prevent cross-project cache collisions: a single
17
16
  * PromptResource can serve multiple projects, so the same slug+labels must
@@ -38,9 +37,9 @@ export declare function buildCacheKey(projectId: string, slug: string, labels: s
38
37
  * caches as the same deterministic variant on repeat (D8); the
39
38
  * weighted-no-subject bypass is handled in the resource, not here.
40
39
  */
41
- export declare function buildFetchCacheKey(projectId: string, slug: string, env: string, subject?: string, variables?: Record<string, unknown>): string;
40
+ export declare function buildRenderCacheKey(projectId: string, slug: string, env: string, subject?: string, variables?: Record<string, unknown>): string;
42
41
  /**
43
- * In-memory LRU cache for ResolvedPrompt objects.
42
+ * In-memory LRU cache for RenderedPrompt objects.
44
43
  *
45
44
  * Uses a Map (insertion-ordered) as the backing store. On every access, the
46
45
  * entry is deleted and re-inserted to maintain LRU order — oldest entries sit
@@ -51,8 +50,8 @@ export declare class PromptCache {
51
50
  private readonly maxEntries;
52
51
  private readonly store;
53
52
  constructor(config?: CacheConfig);
54
- get(key: string): CacheableResolved | undefined;
55
- set(key: string, value: CacheableResolved): void;
53
+ get(key: string): CacheableRendered | undefined;
54
+ set(key: string, value: CacheableRendered): void;
56
55
  delete(key: string): void;
57
56
  clear(): void;
58
57
  get size(): number;
@@ -48,17 +48,6 @@ var import_zod2 = require("../generated/zod.gen");
48
48
  var import_cache = require("./cache");
49
49
  var import_shape = require("./shape");
50
50
  var import_types = require("./types");
51
- var zResolvedPromptLegacy = import_zod2.zResolvedPrompt.pick({
52
- promptId: true,
53
- versionId: true,
54
- version: true,
55
- label: true,
56
- contentHash: true,
57
- rendered: true,
58
- tools: true,
59
- toolChoice: true,
60
- modelConfig: true
61
- });
62
51
  var zResolvedPromptEnvelope = import_zod2.zResolvedPrompt.partial({ releaseId: true }).extend({ strategy: import_zod.z.enum(["single", "weighted"]).optional() });
63
52
  function unwrap(envelope) {
64
53
  if (envelope.data === null || envelope.data === undefined) {
@@ -70,12 +59,12 @@ function getProjectPromptsPath(projectId) {
70
59
  return `/v1/projects/${encodeURIComponent(projectId)}/prompts`;
71
60
  }
72
61
  function renderedToMessages(rendered) {
73
- if (rendered.format === "chat") {
74
- return rendered.messages;
62
+ if (rendered.format !== "chat") {
63
+ throw new Error("[to11ai/sdk] Unsupported non-chat prompt format; author the prompt as chat blocks.");
75
64
  }
76
- return [{ role: "user", content: rendered.text }];
65
+ return rendered.messages;
77
66
  }
78
- function normalizeResolvedEnvelope(raw) {
67
+ function normalizeResolvedEnvelope(raw, slug) {
79
68
  const provenance = raw;
80
69
  const result = {
81
70
  messages: renderedToMessages(raw.rendered),
@@ -86,7 +75,7 @@ function normalizeResolvedEnvelope(raw) {
86
75
  versionId: raw.versionId,
87
76
  contentHash: raw.contentHash,
88
77
  label: raw.label,
89
- rendered: raw.rendered,
78
+ slug,
90
79
  modelConfig: provenance.modelConfig
91
80
  };
92
81
  if (raw.tools != null && raw.tools.length > 0) {
@@ -100,11 +89,11 @@ function normalizeResolvedEnvelope(raw) {
100
89
  }
101
90
  return result;
102
91
  }
103
- function liftFallbackToFetched(fallback) {
104
- if ("messages" in fallback) {
105
- return structuredClone(fallback);
92
+ function liftFallbackToRendered(fallback, slug) {
93
+ if (!Array.isArray(fallback.messages)) {
94
+ throw new Error("[to11ai/sdk] `fallback` must be a RenderedPrompt (as returned by render()); the legacy resolve() shape is no longer supported.");
106
95
  }
107
- return normalizeResolvedEnvelope(fallback);
96
+ return { ...structuredClone(fallback), slug: fallback.slug || slug };
108
97
  }
109
98
  function getProjectPromptPath(projectId, promptId) {
110
99
  return `${getProjectPromptsPath(projectId)}/${encodeURIComponent(promptId)}`;
@@ -215,12 +204,12 @@ class PromptResource {
215
204
  return unwrap(envelope);
216
205
  }
217
206
  async render(slug, options = {}) {
218
- const fetched = await this.resolveToFetched(slug, options);
219
- return import_shape.shapePrompt(fetched, options.format ?? this.format, {
207
+ const rendered = await this.resolveToRendered(slug, options);
208
+ return import_shape.shapePrompt(rendered, options.format ?? this.format, {
220
209
  onUnknownRole: options.onUnknownRole ?? this.onUnknownRole
221
210
  });
222
211
  }
223
- async resolveToFetched(slug, options = {}) {
212
+ async resolveToRendered(slug, options = {}) {
224
213
  if (this.projectId === undefined || this.projectId.trim() === "") {
225
214
  throw new Error("[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
226
215
  }
@@ -232,10 +221,10 @@ class PromptResource {
232
221
  throw new Error(`[to11ai/sdk] \`env\` must match ${import_types.ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
233
222
  }
234
223
  const { subject, variables, cache: useCache = true, fallback } = options;
235
- const cacheKey = import_cache.buildFetchCacheKey(this.projectId, slug, env, subject, variables);
224
+ const cacheKey = import_cache.buildRenderCacheKey(this.projectId, slug, env, subject, variables);
236
225
  if (useCache) {
237
226
  const cached = this.cache.get(cacheKey);
238
- if (cached && "messages" in cached)
227
+ if (cached)
239
228
  return structuredClone(cached);
240
229
  }
241
230
  const body = { slug, labels: [env] };
@@ -249,7 +238,7 @@ class PromptResource {
249
238
  decode: (data) => zResolvedPromptEnvelope.parse(data)
250
239
  });
251
240
  const raw = unwrap(envelope);
252
- const result = normalizeResolvedEnvelope(raw);
241
+ const result = normalizeResolvedEnvelope(raw, slug);
253
242
  const isWeighted = raw.strategy === "weighted" || result.variantName !== undefined;
254
243
  const cacheable = !(isWeighted && subject === undefined);
255
244
  if (useCache && cacheable) {
@@ -258,39 +247,7 @@ class PromptResource {
258
247
  return result;
259
248
  } catch (err) {
260
249
  if (err instanceof import_errors.To11aiNetworkError && fallback !== undefined) {
261
- return liftFallbackToFetched(fallback);
262
- }
263
- throw err;
264
- }
265
- }
266
- async resolve(input) {
267
- const {
268
- projectId,
269
- slug,
270
- labels,
271
- variables,
272
- cache: useCache = true,
273
- fallback
274
- } = input;
275
- const cacheKey = import_cache.buildCacheKey(projectId, slug, labels, variables);
276
- if (useCache) {
277
- const cached = this.cache.get(cacheKey);
278
- if (cached && !("messages" in cached))
279
- return structuredClone(cached);
280
- }
281
- try {
282
- const envelope = await this.transport.request("POST", `${getProjectPromptsPath(projectId)}:resolve`, {
283
- body: { slug, labels, variables },
284
- decode: (data) => zResolvedPromptLegacy.parse(data)
285
- });
286
- const result = unwrap(envelope);
287
- if (useCache) {
288
- this.cache.set(cacheKey, structuredClone(result));
289
- }
290
- return result;
291
- } catch (err) {
292
- if (err instanceof import_errors.To11aiNetworkError && fallback !== undefined) {
293
- return fallback;
250
+ return liftFallbackToRendered(fallback, slug);
294
251
  }
295
252
  throw err;
296
253
  }
@@ -1,9 +1,9 @@
1
1
  import type { Transport } from "../transport";
2
2
  import type { CacheConfig } from "./cache";
3
3
  import type { AnthropicRenderedPrompt, OpenAiRenderedPrompt, RenderResult } from "./shape";
4
- import type { ArchivePromptInput, ArchivePromptResult, CreateProjectPromptLabelInput, CreateProjectPromptLabelResult, CreatePromptInput, CreatePromptResult, CreatePromptVersionInput, CreatePromptVersionResult, DeleteProjectPromptLabelInput, DeleteProjectPromptLabelResult, GetProjectPromptLabelInput, GetProjectPromptLabelResult, GetPromptInput, GetPromptResult, GetPromptVersionInput, GetPromptVersionResult, ListLabelEventsInput, ListLabelEventsResult, ListProjectPromptLabelsInput, ListProjectPromptLabelsResult, ListProjectPromptLabelUsagesInput, ListProjectPromptLabelUsagesResult, ListPromptLabelsInput, ListPromptLabelsResult, ListPromptsInput, ListPromptsResult, ListPromptVersionsInput, ListPromptVersionsResult, MovePromptLabelInput, MovePromptLabelResult, PaginatedResult, RenderFormat, RenderOptions, ResolvePromptInput, ResolvePromptResult, UnknownRoleMode, UpdateProjectPromptLabelInput, UpdateProjectPromptLabelResult, UpdatePromptInput, UpdatePromptResult } from "./types";
4
+ import type { ArchivePromptInput, ArchivePromptResult, CreateProjectPromptLabelInput, CreateProjectPromptLabelResult, CreatePromptInput, CreatePromptResult, CreatePromptVersionInput, CreatePromptVersionResult, DeleteProjectPromptLabelInput, DeleteProjectPromptLabelResult, GetProjectPromptLabelInput, GetProjectPromptLabelResult, GetPromptInput, GetPromptResult, GetPromptVersionInput, GetPromptVersionResult, ListLabelEventsInput, ListLabelEventsResult, ListProjectPromptLabelsInput, ListProjectPromptLabelsResult, ListProjectPromptLabelUsagesInput, ListProjectPromptLabelUsagesResult, ListPromptLabelsInput, ListPromptLabelsResult, ListPromptsInput, ListPromptsResult, ListPromptVersionsInput, ListPromptVersionsResult, MovePromptLabelInput, MovePromptLabelResult, PaginatedResult, RenderFormat, RenderOptions, UnknownRoleMode, UpdateProjectPromptLabelInput, UpdateProjectPromptLabelResult, UpdatePromptInput, UpdatePromptResult } from "./types";
5
5
  export type { CacheConfig } from "./cache";
6
- export type { ArchivePromptInput, ArchivePromptResult, BlockRenderRecord, ChatMessage, CreateProjectPromptLabelInput, CreateProjectPromptLabelResult, CreatePromptInput, CreatePromptResult, CreatePromptVersionInput, CreatePromptVersionResult, DeleteProjectPromptLabelInput, DeleteProjectPromptLabelResult, FetchedPrompt, FetchPromptOptions, GetProjectPromptLabelInput, GetProjectPromptLabelResult, GetPromptInput, GetPromptResult, GetPromptVersionInput, GetPromptVersionResult, ListLabelEventsInput, ListLabelEventsResult, ListProjectPromptLabelsInput, ListProjectPromptLabelsResult, ListProjectPromptLabelUsagesInput, ListProjectPromptLabelUsagesResult, ListPromptLabelsInput, ListPromptLabelsResult, ListPromptsInput, ListPromptsResult, ListPromptVersionsInput, ListPromptVersionsResult, MovePromptLabelInput, MovePromptLabelResult, PaginatedResult, PaginationInfo, PaletteToken, ProjectPromptLabel, ProjectPromptLabelUsage, Prompt, PromptFormat, PromptLabelEvent, PromptLabelPointer, PromptStatus, PromptVersion, RenderedChatMessage, RenderedPrompt, ResolvedPrompt, ResolvedPromptEnvelope, ResolvedToolCall, ResolvedToolDefinition, ResolvePromptInput, ResolvePromptResult, RowError, TextChatMessage, TextChatRole, UpdateProjectPromptLabelInput, UpdateProjectPromptLabelResult, UpdatePromptInput, UpdatePromptResult, } from "./types";
6
+ export type { ArchivePromptInput, ArchivePromptResult, BlockRenderRecord, ChatMessage, CreateProjectPromptLabelInput, CreateProjectPromptLabelResult, CreatePromptInput, CreatePromptResult, CreatePromptVersionInput, CreatePromptVersionResult, DeleteProjectPromptLabelInput, DeleteProjectPromptLabelResult, GetProjectPromptLabelInput, GetProjectPromptLabelResult, GetPromptInput, GetPromptResult, GetPromptVersionInput, GetPromptVersionResult, ListLabelEventsInput, ListLabelEventsResult, ListProjectPromptLabelsInput, ListProjectPromptLabelsResult, ListProjectPromptLabelUsagesInput, ListProjectPromptLabelUsagesResult, ListPromptLabelsInput, ListPromptLabelsResult, ListPromptsInput, ListPromptsResult, ListPromptVersionsInput, ListPromptVersionsResult, MovePromptLabelInput, MovePromptLabelResult, PaginatedResult, PaginationInfo, PaletteToken, ProjectPromptLabel, ProjectPromptLabelUsage, Prompt, PromptFormat, PromptLabelEvent, PromptLabelPointer, PromptStatus, PromptVersion, RenderedChatMessage, RenderedPrompt, RenderFormat, RenderOptions, ResolvedPrompt, ResolvedPromptEnvelope, ResolvedToolCall, ResolvedToolDefinition, RowError, TextChatMessage, TextChatRole, UnknownRoleMode, UpdateProjectPromptLabelInput, UpdateProjectPromptLabelResult, UpdatePromptInput, UpdatePromptResult, } from "./types";
7
7
  /**
8
8
  * Init-time config for the env-aware `render()` surface (TO11-980 / S9).
9
9
  *
@@ -49,8 +49,7 @@ export declare class PromptResource<F extends RenderFormat | undefined = undefin
49
49
  * `projectId` + `env`, in one control-plane request. With a `format` (per
50
50
  * call or inherited from the client) the result is provider-shaped
51
51
  * `{ messages, config, metadata }`; without one it is the neutral
52
- * `FetchedPrompt`. Resolves by the client's `env`; for label-addressed
53
- * resolution see {@link PromptResource.resolve | `resolve()`}.
52
+ * `RenderedPrompt`. Resolves by the client's configured `env`.
54
53
  *
55
54
  * @example Render an OpenAI-shaped prompt and send it through the gateway
56
55
  * const prompt = await to11.prompts.render("weather-concierge", {
@@ -69,7 +68,7 @@ export declare class PromptResource<F extends RenderFormat | undefined = undefin
69
68
  }): Promise<AnthropicRenderedPrompt>;
70
69
  render(slug: string, options?: RenderOptions): Promise<RenderResult<F>>;
71
70
  /**
72
- * Env-aware resolution → neutral `FetchedPrompt`. Carries the resolve /
71
+ * Env-aware resolution → neutral `RenderedPrompt`. Carries the resolve /
73
72
  * cache / offline-fallback logic; `render()` is the public shaping surface.
74
73
  *
75
74
  * - **env→label wire mapping:** the resolve route is label-based
@@ -82,17 +81,7 @@ export declare class PromptResource<F extends RenderFormat | undefined = undefin
82
81
  * WITH a `subject`; BYPASSES the cache for weighted releases with NO
83
82
  * `subject` (a uniform-random assignment must not be pinned for the TTL).
84
83
  */
85
- private resolveToFetched;
86
- /**
87
- * Resolve a prompt by explicit `labels` for a given `projectId`, in one
88
- * control-plane request. This is the label-addressed counterpart to
89
- * {@link PromptResource.render | `render()`}, which resolves by the client's
90
- * configured `env`. Reach for `resolve()` when you address prompts by label
91
- * rather than by environment; it is env-agnostic, so it carries no `env` /
92
- * `subject`, no enriched release/variant provenance, and no typed-env errors,
93
- * and is not subject to the env-based no-silent-fallback gate.
94
- */
95
- resolve(input: ResolvePromptInput): Promise<ResolvePromptResult>;
84
+ private resolveToRendered;
96
85
  /**
97
86
  * List the project-level label registry.
98
87
  *
@@ -2,20 +2,9 @@
2
2
  import { z } from "zod";
3
3
  import { EnvNotFoundError, To11aiNetworkError } from "../errors";
4
4
  import { zResolvedPrompt } from "../generated/zod.gen";
5
- import { buildCacheKey, buildFetchCacheKey, PromptCache } from "./cache";
5
+ import { buildRenderCacheKey, PromptCache } from "./cache";
6
6
  import { shapePrompt } from "./shape";
7
7
  import { ENV_SLUG_REGEX } from "./types";
8
- var zResolvedPromptLegacy = zResolvedPrompt.pick({
9
- promptId: true,
10
- versionId: true,
11
- version: true,
12
- label: true,
13
- contentHash: true,
14
- rendered: true,
15
- tools: true,
16
- toolChoice: true,
17
- modelConfig: true
18
- });
19
8
  var zResolvedPromptEnvelope = zResolvedPrompt.partial({ releaseId: true }).extend({ strategy: z.enum(["single", "weighted"]).optional() });
20
9
  function unwrap(envelope) {
21
10
  if (envelope.data === null || envelope.data === undefined) {
@@ -27,12 +16,12 @@ function getProjectPromptsPath(projectId) {
27
16
  return `/v1/projects/${encodeURIComponent(projectId)}/prompts`;
28
17
  }
29
18
  function renderedToMessages(rendered) {
30
- if (rendered.format === "chat") {
31
- return rendered.messages;
19
+ if (rendered.format !== "chat") {
20
+ throw new Error("[to11ai/sdk] Unsupported non-chat prompt format; author the prompt as chat blocks.");
32
21
  }
33
- return [{ role: "user", content: rendered.text }];
22
+ return rendered.messages;
34
23
  }
35
- function normalizeResolvedEnvelope(raw) {
24
+ function normalizeResolvedEnvelope(raw, slug) {
36
25
  const provenance = raw;
37
26
  const result = {
38
27
  messages: renderedToMessages(raw.rendered),
@@ -43,7 +32,7 @@ function normalizeResolvedEnvelope(raw) {
43
32
  versionId: raw.versionId,
44
33
  contentHash: raw.contentHash,
45
34
  label: raw.label,
46
- rendered: raw.rendered,
35
+ slug,
47
36
  modelConfig: provenance.modelConfig
48
37
  };
49
38
  if (raw.tools != null && raw.tools.length > 0) {
@@ -57,11 +46,11 @@ function normalizeResolvedEnvelope(raw) {
57
46
  }
58
47
  return result;
59
48
  }
60
- function liftFallbackToFetched(fallback) {
61
- if ("messages" in fallback) {
62
- return structuredClone(fallback);
49
+ function liftFallbackToRendered(fallback, slug) {
50
+ if (!Array.isArray(fallback.messages)) {
51
+ throw new Error("[to11ai/sdk] `fallback` must be a RenderedPrompt (as returned by render()); the legacy resolve() shape is no longer supported.");
63
52
  }
64
- return normalizeResolvedEnvelope(fallback);
53
+ return { ...structuredClone(fallback), slug: fallback.slug || slug };
65
54
  }
66
55
  function getProjectPromptPath(projectId, promptId) {
67
56
  return `${getProjectPromptsPath(projectId)}/${encodeURIComponent(promptId)}`;
@@ -172,12 +161,12 @@ class PromptResource {
172
161
  return unwrap(envelope);
173
162
  }
174
163
  async render(slug, options = {}) {
175
- const fetched = await this.resolveToFetched(slug, options);
176
- return shapePrompt(fetched, options.format ?? this.format, {
164
+ const rendered = await this.resolveToRendered(slug, options);
165
+ return shapePrompt(rendered, options.format ?? this.format, {
177
166
  onUnknownRole: options.onUnknownRole ?? this.onUnknownRole
178
167
  });
179
168
  }
180
- async resolveToFetched(slug, options = {}) {
169
+ async resolveToRendered(slug, options = {}) {
181
170
  if (this.projectId === undefined || this.projectId.trim() === "") {
182
171
  throw new Error("[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
183
172
  }
@@ -189,10 +178,10 @@ class PromptResource {
189
178
  throw new Error(`[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
190
179
  }
191
180
  const { subject, variables, cache: useCache = true, fallback } = options;
192
- const cacheKey = buildFetchCacheKey(this.projectId, slug, env, subject, variables);
181
+ const cacheKey = buildRenderCacheKey(this.projectId, slug, env, subject, variables);
193
182
  if (useCache) {
194
183
  const cached = this.cache.get(cacheKey);
195
- if (cached && "messages" in cached)
184
+ if (cached)
196
185
  return structuredClone(cached);
197
186
  }
198
187
  const body = { slug, labels: [env] };
@@ -206,7 +195,7 @@ class PromptResource {
206
195
  decode: (data) => zResolvedPromptEnvelope.parse(data)
207
196
  });
208
197
  const raw = unwrap(envelope);
209
- const result = normalizeResolvedEnvelope(raw);
198
+ const result = normalizeResolvedEnvelope(raw, slug);
210
199
  const isWeighted = raw.strategy === "weighted" || result.variantName !== undefined;
211
200
  const cacheable = !(isWeighted && subject === undefined);
212
201
  if (useCache && cacheable) {
@@ -215,39 +204,7 @@ class PromptResource {
215
204
  return result;
216
205
  } catch (err) {
217
206
  if (err instanceof To11aiNetworkError && fallback !== undefined) {
218
- return liftFallbackToFetched(fallback);
219
- }
220
- throw err;
221
- }
222
- }
223
- async resolve(input) {
224
- const {
225
- projectId,
226
- slug,
227
- labels,
228
- variables,
229
- cache: useCache = true,
230
- fallback
231
- } = input;
232
- const cacheKey = buildCacheKey(projectId, slug, labels, variables);
233
- if (useCache) {
234
- const cached = this.cache.get(cacheKey);
235
- if (cached && !("messages" in cached))
236
- return structuredClone(cached);
237
- }
238
- try {
239
- const envelope = await this.transport.request("POST", `${getProjectPromptsPath(projectId)}:resolve`, {
240
- body: { slug, labels, variables },
241
- decode: (data) => zResolvedPromptLegacy.parse(data)
242
- });
243
- const result = unwrap(envelope);
244
- if (useCache) {
245
- this.cache.set(cacheKey, structuredClone(result));
246
- }
247
- return result;
248
- } catch (err) {
249
- if (err instanceof To11aiNetworkError && fallback !== undefined) {
250
- return fallback;
207
+ return liftFallbackToRendered(fallback, slug);
251
208
  }
252
209
  throw err;
253
210
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Format-at-load shaping: turn a neutral `FetchedPrompt` into a provider-shaped
2
+ * Format-at-load shaping: turn a neutral `RenderedPrompt` into a provider-shaped
3
3
  * `{ messages, config, metadata }` result the caller spreads into their own
4
4
  * provider `create()` call. Header-only — this only reshapes data, it never
5
5
  * sends a request. See docs/product-specs/2026-07-14-to11-2691-sdk-simpler-setup.md.
6
6
  */
7
7
  import { type AnthropicMessage, type AnthropicTool, type AnthropicToolChoice, type ConvertMessagesOptions, type OpenAIChatMessage, type OpenAITool, type OpenAIToolChoice } from "../gateway";
8
- import type { FetchedPrompt, RenderFormat } from "./types";
8
+ import type { RenderedPrompt, RenderFormat } from "./types";
9
9
  /** Model params passed through verbatim from the resolved version's modelConfig. */
10
10
  export interface PromptConfig {
11
11
  model?: string;
@@ -22,6 +22,8 @@ export interface PromptMetadata {
22
22
  releaseId: string | null;
23
23
  /** The single label the prompt resolved through (env slug today). */
24
24
  label: string;
25
+ /** The prompt's human-readable slug — surfaced as `x-to11-prompt-slug`. */
26
+ slug: string;
25
27
  variantName?: string;
26
28
  }
27
29
  /** OpenAI-shaped render result: spread `{ ...config, messages }` into create(). */
@@ -47,8 +49,8 @@ export interface AnthropicRenderedPrompt {
47
49
  tool_choice?: AnthropicToolChoice;
48
50
  }
49
51
  /** The render return type for a given client/call format. */
50
- export type RenderResult<F extends RenderFormat | undefined> = F extends "openai" ? OpenAiRenderedPrompt : F extends "anthropic" ? AnthropicRenderedPrompt : FetchedPrompt;
51
- export declare function shapeOpenAI(prompt: FetchedPrompt, options?: ConvertMessagesOptions): OpenAiRenderedPrompt;
52
- export declare function shapeAnthropic(prompt: FetchedPrompt, options?: ConvertMessagesOptions): AnthropicRenderedPrompt;
52
+ export type RenderResult<F extends RenderFormat | undefined> = F extends "openai" ? OpenAiRenderedPrompt : F extends "anthropic" ? AnthropicRenderedPrompt : RenderedPrompt;
53
+ export declare function shapeOpenAI(prompt: RenderedPrompt, options?: ConvertMessagesOptions): OpenAiRenderedPrompt;
54
+ export declare function shapeAnthropic(prompt: RenderedPrompt, options?: ConvertMessagesOptions): AnthropicRenderedPrompt;
53
55
  /** Shape a fetched prompt for `format`, or return it neutral when unset. */
54
- export declare function shapePrompt(prompt: FetchedPrompt, format: RenderFormat | undefined, options?: ConvertMessagesOptions): FetchedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt;
56
+ export declare function shapePrompt(prompt: RenderedPrompt, format: RenderFormat | undefined, options?: ConvertMessagesOptions): RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt;
@@ -1,4 +1,4 @@
1
- import type { ProjectPromptLabel as GenProjectPromptLabel, ProjectPromptLabelUsage as GenProjectPromptLabelUsage, Prompt as GenPrompt, PromptLabelEvent as GenPromptLabelEvent, PromptLabelPointer as GenPromptLabelPointer, PromptVersion as GenPromptVersion, RenderedChatPrompt as GenRenderedChatPrompt, RenderedPrompt as GenRenderedPrompt, ResolvedPrompt as GenResolvedPrompt, ResolvedToolCall as GenResolvedToolCall, ResolvedToolDefinition as GenResolvedToolDefinition } from "../generated/types.gen";
1
+ import type { ProjectPromptLabel as GenProjectPromptLabel, ProjectPromptLabelUsage as GenProjectPromptLabelUsage, Prompt as GenPrompt, PromptLabelEvent as GenPromptLabelEvent, PromptLabelPointer as GenPromptLabelPointer, PromptVersion as GenPromptVersion, RenderedChatPrompt as GenRenderedChatPrompt, ResolvedPrompt as GenResolvedPrompt, ResolvedToolCall as GenResolvedToolCall, ResolvedToolDefinition as GenResolvedToolDefinition } from "../generated/types.gen";
2
2
  export interface RowError {
3
3
  path: ReadonlyArray<string | number>;
4
4
  code: string;
@@ -17,7 +17,6 @@ export interface TextChatMessage {
17
17
  }
18
18
  export type ChatMessage = TextChatMessage;
19
19
  export type RenderedChatMessage = GenRenderedChatPrompt["messages"][number];
20
- export type RenderedPrompt = GenRenderedPrompt;
21
20
  /** Provider shape for `prompts.render()` output. Omit for the neutral result. */
22
21
  export type RenderFormat = "openai" | "anthropic";
23
22
  /**
@@ -119,20 +118,9 @@ export interface ListPromptLabelsInput {
119
118
  promptId: string;
120
119
  }
121
120
  export type ListPromptLabelsResult = PromptLabelPointer[];
122
- export interface ResolvePromptInput {
123
- projectId: string;
124
- slug: string;
125
- labels: string[];
126
- variables?: Record<string, unknown>;
127
- /** Set false to bypass the LRU cache for this call. Default: true */
128
- cache?: boolean;
129
- /** Fallback prompt served if the API is unreachable and cache is empty. */
130
- fallback?: ResolvedPrompt;
131
- }
132
- export type ResolvePromptResult = ResolvedPrompt;
133
121
  export declare const ENV_SLUG_REGEX: RegExp;
134
122
  export type BlockRenderRecord = Record<string, "rendered" | "dimmed">;
135
- export interface FetchedPrompt {
123
+ export interface RenderedPrompt {
136
124
  messages: RenderedChatMessage[];
137
125
  version: number;
138
126
  releaseId: string | null;
@@ -147,7 +135,12 @@ export interface FetchedPrompt {
147
135
  * `gatewayPromptHeaders` / `turn.headers()` (TO11-2725).
148
136
  */
149
137
  label: string;
150
- rendered: RenderedPrompt;
138
+ /**
139
+ * The prompt's human-readable slug (the `render()` input). Surfaced as
140
+ * `x-to11-prompt-slug` by `gatewayPromptHeaders` / `turn.headers()` so traces
141
+ * carry a readable name alongside the opaque `promptId` (TO11-2727).
142
+ */
143
+ slug: string;
151
144
  /**
152
145
  * The served version's model config (model, temperature, max_tokens, …),
153
146
  * passed through verbatim. Surfaced as `config` by `render({ format })`.
@@ -166,7 +159,7 @@ export interface FetchedPrompt {
166
159
  * into `toOpenAIToolChoice` / `toAnthropicToolChoice`.
167
160
  *
168
161
  * `NonNullable` because `normalizeResolvedEnvelope` maps wire-`null` to absent,
169
- * so a present `FetchedPrompt.toolChoice` is never `null` — callers get the
162
+ * so a present `RenderedPrompt.toolChoice` is never `null` — callers get the
170
163
  * key or nothing, and never have to branch on `null`.
171
164
  */
172
165
  toolChoice?: NonNullable<ResolvedPrompt["toolChoice"]>;
@@ -175,14 +168,16 @@ export type ResolvedPromptEnvelope = GenResolvedPrompt & {
175
168
  /** Active release strategy. Used to decide weighted-no-subject cache bypass (D8). */
176
169
  strategy?: "single" | "weighted";
177
170
  };
178
- export interface FetchPromptOptions {
171
+ export interface RenderOptions {
172
+ /** Serving environment for this call; overrides the client default. */
179
173
  env?: string;
174
+ /** Stable id so a weighted release sends the same caller to the same variant. */
180
175
  subject?: string;
181
176
  variables?: Record<string, unknown>;
177
+ /** Set false to bypass the LRU cache for this call. Default: true */
182
178
  cache?: boolean;
183
- fallback?: ResolvedPrompt | FetchedPrompt;
184
- }
185
- export interface RenderOptions extends FetchPromptOptions {
179
+ /** Prompt served if the API is unreachable on a network error and cache is empty. */
180
+ fallback?: RenderedPrompt;
186
181
  /** Provider shape for this call's output; overrides the client default. */
187
182
  format?: RenderFormat;
188
183
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@to11ai/sdk",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.0-rc.2",
4
4
  "license": "MPL-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/src/client.ts CHANGED
@@ -88,8 +88,6 @@ export type {
88
88
  // TO11-980 (S9): env-aware render() surface types at the package entrypoint (AC9).
89
89
  export type {
90
90
  BlockRenderRecord,
91
- FetchedPrompt,
92
- FetchPromptOptions,
93
91
  RenderedChatMessage,
94
92
  RenderedPrompt,
95
93
  RenderFormat,
@@ -168,8 +166,9 @@ export interface ClientOptions {
168
166
  projectId?: string;
169
167
  /**
170
168
  * 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}$`).
169
+ * render unless overridden per call. Precedence: this option → `TO11_ENV`
170
+ * env. Validated eagerly against the project-environments slug rule
171
+ * (`^[a-z0-9-]{1,63}$`).
173
172
  */
174
173
  env?: string;
175
174
  /**
@@ -376,7 +375,12 @@ export function createClient<
376
375
  ? options.apiKey
377
376
  : pick(options.apiKey, "TO11_API_KEY");
378
377
  const projectId = pick(options.projectId, "TO11_PROJECT_ID");
379
- const env = options.env;
378
+ // Default serving env: explicit option → `TO11_ENV` env var — the same
379
+ // `pick(option, "TO11_*")` resolution `apiKey`/`projectId` use (empty option
380
+ // or empty env var trims to absent). An explicit empty option is rejected by
381
+ // the dedicated guard below (mirroring `apiKey`/`projectId`); a non-empty but
382
+ // malformed value fails the `ENV_SLUG_REGEX` check.
383
+ const env = pick(options.env, "TO11_ENV");
380
384
  // Provider (BYOK) key — option only, no env fallback (which provider's env
381
385
  // var is ambiguous). Empty trims to absent so it falls back to the placeholder.
382
386
  const providerApiKey = options.providerApiKey?.trim() || undefined;
@@ -398,6 +402,11 @@ export function createClient<
398
402
  "[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.",
399
403
  );
400
404
  }
405
+ if (options.env !== undefined && options.env.trim() === "") {
406
+ throw new Error(
407
+ "[to11ai/sdk] `env` must not be an empty string — omit it or pass a valid env slug.",
408
+ );
409
+ }
401
410
 
402
411
  // Validate the init-time `env` against the project-environments slug rule so
403
412
  // a malformed env fails locally rather than round-tripping to ENV_NOT_FOUND.
package/src/context.ts CHANGED
@@ -24,7 +24,7 @@ export interface TurnAuthContext {
24
24
 
25
25
  /**
26
26
  * A prompt argument accepted by `turn.headers()`. Either the flat provenance
27
- * (a neutral `FetchedPrompt`) or a `format`-shaped result carrying it under
27
+ * (a neutral `RenderedPrompt`) or a `format`-shaped result carrying it under
28
28
  * `metadata` — both are accepted so the format and neutral render paths carry
29
29
  * provenance identically.
30
30
  */
package/src/gateway.ts CHANGED
@@ -85,6 +85,9 @@ export function gatewayAuthHeaders(
85
85
  * release was weighted); omitted for `single`-strategy releases.
86
86
  * - `x-to11-prompt-label` — the single label the prompt resolved through (the
87
87
  * env slug today); omitted when empty (the gateway rejects empty values).
88
+ * - `x-to11-prompt-slug` — the prompt's human-readable slug, so traces show a
89
+ * readable name alongside the opaque `x-to11-prompt-id`; omitted when empty
90
+ * or absent (a legacy `resolve()` result carries no slug).
88
91
  *
89
92
  * The parameter type is WIDENED to accept either the enriched `render()`
90
93
  * result or the legacy `ResolvedPrompt`. The new fields are read when present
@@ -100,6 +103,11 @@ export interface GatewayPromptProvenance {
100
103
  version: number;
101
104
  /** The single label the prompt resolved through (env slug today). */
102
105
  label: string;
106
+ /**
107
+ * The prompt's human-readable slug (the `render()`/`resolve()` input). Present
108
+ * on an enriched `render()` result; a legacy `ResolvedPrompt` omits it.
109
+ */
110
+ slug?: string;
103
111
  /** Present on an enriched result; a legacy `resolve()` result omits it. */
104
112
  releaseId?: string | null;
105
113
  /** Present IFF the resolved release was weighted. */
@@ -127,6 +135,11 @@ export function gatewayPromptHeaders(
127
135
  if (prompt.label.length > 0) {
128
136
  headers["x-to11-prompt-label"] = prompt.label;
129
137
  }
138
+ // The prompt's human-readable slug. Omit when absent/empty — a legacy
139
+ // `resolve()` result carries no slug, and the gateway rejects empty values.
140
+ if (typeof prompt.slug === "string" && prompt.slug.length > 0) {
141
+ headers["x-to11-prompt-slug"] = prompt.slug;
142
+ }
130
143
  return headers;
131
144
  }
132
145
 
@@ -236,7 +236,7 @@ export const zPrompt = z.object({
236
236
  id: z.string(),
237
237
  name: z.string().min(1).max(200),
238
238
  projectId: z.string(),
239
- slug: z.string().min(1).max(200).regex(/^[a-z0-9-]+$/),
239
+ slug: z.string().min(1).max(128).regex(/^[a-z0-9-]+$/),
240
240
  status: z.enum(['active', 'archived']),
241
241
  tags: z.array(z.string()),
242
242
  updatedAt: z.iso.datetime()
@@ -7359,7 +7359,7 @@ export const zPromptsListPromptsResponse = z.object({
7359
7359
  latestVersion: z.number().nullable(),
7360
7360
  name: z.string().min(1).max(200),
7361
7361
  projectId: z.string(),
7362
- slug: z.string().min(1).max(200).regex(/^[a-z0-9-]+$/),
7362
+ slug: z.string().min(1).max(128).regex(/^[a-z0-9-]+$/),
7363
7363
  status: z.enum(['active', 'archived']),
7364
7364
  tags: z.array(z.string()),
7365
7365
  updatedAt: z.iso.datetime()
@@ -7390,7 +7390,7 @@ export const zPromptsListPromptsResponse = z.object({
7390
7390
  export const zPromptsCreatePromptBody = z.object({
7391
7391
  description: z.string().nullish(),
7392
7392
  name: z.string().min(1).max(200),
7393
- slug: z.string().min(1).max(200).regex(/^[a-z0-9-]+$/),
7393
+ slug: z.string().min(1).max(128).regex(/^[a-z0-9-]+$/),
7394
7394
  tags: z.array(z.string()).nullish()
7395
7395
  });
7396
7396
 
@@ -1,4 +1,4 @@
1
- import type { FetchedPrompt, ResolvedPrompt } from "./types";
1
+ import type { RenderedPrompt } from "./types";
2
2
 
3
3
  export interface CacheConfig {
4
4
  /** Time-to-live in seconds. Default: 300 (5 minutes) */
@@ -8,18 +8,17 @@ export interface CacheConfig {
8
8
  }
9
9
 
10
10
  /**
11
- * The cache stores either a legacy `resolve()` result or an enriched
12
- * `render()` result. Both are JSON-cloneable; the cache is shape-agnostic.
11
+ * The cache stores enriched `render()` results, which are JSON-cloneable.
13
12
  */
14
- export type CacheableResolved = ResolvedPrompt | FetchedPrompt;
13
+ export type CacheableRendered = RenderedPrompt;
15
14
 
16
15
  interface CacheEntry {
17
- value: CacheableResolved;
16
+ value: CacheableRendered;
18
17
  expiresAt: number;
19
18
  }
20
19
 
21
20
  /**
22
- * Build a canonical cache key for a resolve() call.
21
+ * Build a canonical base cache key from project/slug/labels/variables.
23
22
  *
24
23
  * - projectId is included to prevent cross-project cache collisions: a single
25
24
  * PromptResource can serve multiple projects, so the same slug+labels must
@@ -58,7 +57,7 @@ export function buildCacheKey(
58
57
  * caches as the same deterministic variant on repeat (D8); the
59
58
  * weighted-no-subject bypass is handled in the resource, not here.
60
59
  */
61
- export function buildFetchCacheKey(
60
+ export function buildRenderCacheKey(
62
61
  projectId: string,
63
62
  slug: string,
64
63
  env: string,
@@ -66,7 +65,7 @@ export function buildFetchCacheKey(
66
65
  variables?: Record<string, unknown>,
67
66
  ): string {
68
67
  const base = buildCacheKey(projectId, slug, [env], variables);
69
- return `fetch|${base}|${JSON.stringify(env)}|${JSON.stringify(subject ?? null)}`;
68
+ return `render|${base}|${JSON.stringify(env)}|${JSON.stringify(subject ?? null)}`;
70
69
  }
71
70
 
72
71
  function sortObjectKeys(obj: unknown): unknown {
@@ -80,7 +79,7 @@ function sortObjectKeys(obj: unknown): unknown {
80
79
  }
81
80
 
82
81
  /**
83
- * In-memory LRU cache for ResolvedPrompt objects.
82
+ * In-memory LRU cache for RenderedPrompt objects.
84
83
  *
85
84
  * Uses a Map (insertion-ordered) as the backing store. On every access, the
86
85
  * entry is deleted and re-inserted to maintain LRU order — oldest entries sit
@@ -97,7 +96,7 @@ export class PromptCache {
97
96
  this.store = new Map();
98
97
  }
99
98
 
100
- get(key: string): CacheableResolved | undefined {
99
+ get(key: string): CacheableRendered | undefined {
101
100
  const entry = this.store.get(key);
102
101
  if (!entry) return undefined;
103
102
 
@@ -113,7 +112,7 @@ export class PromptCache {
113
112
  return entry.value;
114
113
  }
115
114
 
116
- set(key: string, value: CacheableResolved): void {
115
+ set(key: string, value: CacheableRendered): void {
117
116
  // maxEntries: 0 (or negative) means caching is disabled — drop the entry.
118
117
  if (this.maxEntries <= 0) {
119
118
  return;
@@ -3,7 +3,7 @@ import { EnvNotFoundError, To11aiNetworkError } from "../errors";
3
3
  import { zResolvedPrompt } from "../generated/zod.gen";
4
4
  import type { ApiEnvelope, Transport } from "../transport";
5
5
  import type { CacheConfig } from "./cache";
6
- import { buildCacheKey, buildFetchCacheKey, PromptCache } from "./cache";
6
+ import { buildRenderCacheKey, PromptCache } from "./cache";
7
7
  import type {
8
8
  AnthropicRenderedPrompt,
9
9
  OpenAiRenderedPrompt,
@@ -21,8 +21,6 @@ import type {
21
21
  CreatePromptVersionResult,
22
22
  DeleteProjectPromptLabelInput,
23
23
  DeleteProjectPromptLabelResult,
24
- FetchedPrompt,
25
- FetchPromptOptions,
26
24
  GetProjectPromptLabelInput,
27
25
  GetProjectPromptLabelResult,
28
26
  GetPromptInput,
@@ -50,8 +48,6 @@ import type {
50
48
  RenderOptions,
51
49
  ResolvedPrompt,
52
50
  ResolvedPromptEnvelope,
53
- ResolvePromptInput,
54
- ResolvePromptResult,
55
51
  UnknownRoleMode,
56
52
  UpdateProjectPromptLabelInput,
57
53
  UpdateProjectPromptLabelResult,
@@ -75,8 +71,6 @@ export type {
75
71
  CreatePromptVersionResult,
76
72
  DeleteProjectPromptLabelInput,
77
73
  DeleteProjectPromptLabelResult,
78
- FetchedPrompt,
79
- FetchPromptOptions,
80
74
  GetProjectPromptLabelInput,
81
75
  GetProjectPromptLabelResult,
82
76
  GetPromptInput,
@@ -110,33 +104,22 @@ export type {
110
104
  PromptVersion,
111
105
  RenderedChatMessage,
112
106
  RenderedPrompt,
107
+ RenderFormat,
108
+ RenderOptions,
113
109
  ResolvedPrompt,
114
110
  ResolvedPromptEnvelope,
115
111
  ResolvedToolCall,
116
112
  ResolvedToolDefinition,
117
- ResolvePromptInput,
118
- ResolvePromptResult,
119
113
  RowError,
120
114
  TextChatMessage,
121
115
  TextChatRole,
116
+ UnknownRoleMode,
122
117
  UpdateProjectPromptLabelInput,
123
118
  UpdateProjectPromptLabelResult,
124
119
  UpdatePromptInput,
125
120
  UpdatePromptResult,
126
121
  } from "./types";
127
122
 
128
- const zResolvedPromptLegacy = zResolvedPrompt.pick({
129
- promptId: true,
130
- versionId: true,
131
- version: true,
132
- label: true,
133
- contentHash: true,
134
- rendered: true,
135
- tools: true,
136
- toolChoice: true,
137
- modelConfig: true,
138
- });
139
-
140
123
  const zResolvedPromptEnvelope = zResolvedPrompt
141
124
  .partial({ releaseId: true })
142
125
  .extend({ strategy: z.enum(["single", "weighted"]).optional() });
@@ -153,22 +136,28 @@ function getProjectPromptsPath(projectId: string): string {
153
136
  }
154
137
 
155
138
  /**
156
- * Project a server `rendered` payload into the `messages` array of the R5
157
- * `FetchedPrompt` (D5):
158
- * - `format: "chat"` → the message array directly.
159
- * - `format: "text"` → a single synthesized `{ role: "user", content }`.
139
+ * Project a server `rendered` payload into the enriched result's `messages`.
140
+ *
141
+ * Prompts resolve as chat blocks. The deprecated `text` format is no longer a
142
+ * first-class case: a non-chat payload throws rather than being silently
143
+ * coerced (a text prompt is sugar for a single-block chat and is being removed
144
+ * from the API).
160
145
  */
161
- function renderedToMessages(rendered: RenderedPrompt): RenderedChatMessage[] {
162
- if (rendered.format === "chat") {
163
- return rendered.messages;
146
+ function renderedToMessages(
147
+ rendered: ResolvedPrompt["rendered"],
148
+ ): RenderedChatMessage[] {
149
+ if (rendered.format !== "chat") {
150
+ throw new Error(
151
+ "[to11ai/sdk] Unsupported non-chat prompt format; author the prompt as chat blocks.",
152
+ );
164
153
  }
165
- return [{ role: "user", content: rendered.text }];
154
+ return rendered.messages;
166
155
  }
167
156
 
168
157
  /**
169
- * Normalize the server resolve envelope into the enriched R5 `FetchedPrompt`.
158
+ * Normalize the server resolve envelope into the enriched `RenderedPrompt`.
170
159
  *
171
- * - `messages` is synthesized from `rendered` via the D5 projection.
160
+ * - `messages` is derived from the `rendered` chat payload.
172
161
  * - `releaseId` is ALWAYS a key: a string when the server sends one, else
173
162
  * explicit `null` (R5 `string | null`).
174
163
  * - `variantName` is present IFF the server sent one (i.e. weighted release);
@@ -177,9 +166,10 @@ function renderedToMessages(rendered: RenderedPrompt): RenderedChatMessage[] {
177
166
  */
178
167
  function normalizeResolvedEnvelope(
179
168
  raw: ResolvedPrompt | ResolvedPromptEnvelope,
180
- ): FetchedPrompt {
169
+ slug: string,
170
+ ): RenderedPrompt {
181
171
  const provenance = raw as ResolvedPromptEnvelope;
182
- const result: FetchedPrompt = {
172
+ const result: RenderedPrompt = {
183
173
  messages: renderedToMessages(raw.rendered),
184
174
  version: raw.version,
185
175
  releaseId: provenance.releaseId ?? null,
@@ -188,7 +178,10 @@ function normalizeResolvedEnvelope(
188
178
  versionId: raw.versionId,
189
179
  contentHash: raw.contentHash,
190
180
  label: raw.label,
191
- rendered: raw.rendered,
181
+ // The requested slug — the API resolve envelope does not echo it, so it
182
+ // is threaded from the `render()` input (TO11-2727). Surfaced as
183
+ // `x-to11-prompt-slug` for trace readability.
184
+ slug,
192
185
  modelConfig: provenance.modelConfig,
193
186
  };
194
187
  // `tools` is present IFF the resolved version declares tool definitions; the
@@ -219,20 +212,26 @@ function normalizeResolvedEnvelope(
219
212
  }
220
213
 
221
214
  /**
222
- * Lift a caller-supplied offline `fallback` into the enriched `FetchedPrompt`
223
- * shape (D7), so `render()` returns an identical shape on the online and
224
- * offline paths. An already-enriched `FetchedPrompt` is returned as-is; a
225
- * legacy `ResolvedPrompt` is lifted (messages synthesized; `releaseId` → its
226
- * own value or explicit `null`; `blockRenderRecord` → `{}`; `variantName`
227
- * stays absent).
215
+ * Lift a caller-supplied offline `fallback` into the returned shape (D7), so
216
+ * `render()` returns an identical shape on the online and offline paths. The
217
+ * fallback's own slug is preferred, falling back to the `render()` input when it
218
+ * carries none, so `x-to11-prompt-slug` is still emitted.
228
219
  */
229
- function liftFallbackToFetched(
230
- fallback: ResolvedPrompt | FetchedPrompt,
231
- ): FetchedPrompt {
232
- if ("messages" in fallback) {
233
- return structuredClone(fallback);
220
+ function liftFallbackToRendered(
221
+ fallback: RenderedPrompt,
222
+ slug: string,
223
+ ): RenderedPrompt {
224
+ // Defensive: `fallback` is typed `RenderedPrompt`, but an untyped JS caller,
225
+ // an `as` cast, or a persisted pre-2.0 `resolve()` result could pass the old
226
+ // `ResolvedPrompt` shape (no top-level `messages`). Fail loudly rather than
227
+ // return a malformed result with `undefined` messages.
228
+ if (!Array.isArray((fallback as { messages?: unknown }).messages)) {
229
+ throw new Error(
230
+ "[to11ai/sdk] `fallback` must be a RenderedPrompt (as returned by render()); the legacy resolve() shape is no longer supported.",
231
+ );
234
232
  }
235
- return normalizeResolvedEnvelope(fallback);
233
+ // `||` (not `??`) so an empty-string slug also falls through to the input.
234
+ return { ...structuredClone(fallback), slug: fallback.slug || slug };
236
235
  }
237
236
 
238
237
  function getProjectPromptPath(projectId: string, promptId: string): string {
@@ -468,8 +467,7 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
468
467
  * `projectId` + `env`, in one control-plane request. With a `format` (per
469
468
  * call or inherited from the client) the result is provider-shaped
470
469
  * `{ messages, config, metadata }`; without one it is the neutral
471
- * `FetchedPrompt`. Resolves by the client's `env`; for label-addressed
472
- * resolution see {@link PromptResource.resolve | `resolve()`}.
470
+ * `RenderedPrompt`. Resolves by the client's configured `env`.
473
471
  *
474
472
  * @example Render an OpenAI-shaped prompt and send it through the gateway
475
473
  * const prompt = await to11.prompts.render("weather-concierge", {
@@ -492,15 +490,15 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
492
490
  async render(
493
491
  slug: string,
494
492
  options: RenderOptions = {},
495
- ): Promise<FetchedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt> {
496
- const fetched = await this.resolveToFetched(slug, options);
497
- return shapePrompt(fetched, options.format ?? this.format, {
493
+ ): Promise<RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt> {
494
+ const rendered = await this.resolveToRendered(slug, options);
495
+ return shapePrompt(rendered, options.format ?? this.format, {
498
496
  onUnknownRole: options.onUnknownRole ?? this.onUnknownRole,
499
497
  });
500
498
  }
501
499
 
502
500
  /**
503
- * Env-aware resolution → neutral `FetchedPrompt`. Carries the resolve /
501
+ * Env-aware resolution → neutral `RenderedPrompt`. Carries the resolve /
504
502
  * cache / offline-fallback logic; `render()` is the public shaping surface.
505
503
  *
506
504
  * - **env→label wire mapping:** the resolve route is label-based
@@ -513,10 +511,10 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
513
511
  * WITH a `subject`; BYPASSES the cache for weighted releases with NO
514
512
  * `subject` (a uniform-random assignment must not be pinned for the TTL).
515
513
  */
516
- private async resolveToFetched(
514
+ private async resolveToRendered(
517
515
  slug: string,
518
- options: FetchPromptOptions = {},
519
- ): Promise<FetchedPrompt> {
516
+ options: RenderOptions = {},
517
+ ): Promise<RenderedPrompt> {
520
518
  if (this.projectId === undefined || this.projectId.trim() === "") {
521
519
  throw new Error(
522
520
  "[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).",
@@ -543,7 +541,7 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
543
541
 
544
542
  const { subject, variables, cache: useCache = true, fallback } = options;
545
543
 
546
- const cacheKey = buildFetchCacheKey(
544
+ const cacheKey = buildRenderCacheKey(
547
545
  this.projectId,
548
546
  slug,
549
547
  env,
@@ -553,9 +551,7 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
553
551
 
554
552
  if (useCache) {
555
553
  const cached = this.cache.get(cacheKey);
556
- // Only enriched (fetch) entries are valid hits here; the fetch key
557
- // namespace ("fetch|…") never collides with the legacy resolve key.
558
- if (cached && "messages" in cached) return structuredClone(cached);
554
+ if (cached) return structuredClone(cached);
559
555
  }
560
556
 
561
557
  // env→label mapping: the env slug is the single resolution label.
@@ -574,7 +570,7 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
574
570
  },
575
571
  );
576
572
  const raw = unwrap(envelope);
577
- const result = normalizeResolvedEnvelope(raw);
573
+ const result = normalizeResolvedEnvelope(raw, slug);
578
574
 
579
575
  // D8: bypass the cache for a weighted release with NO subject —
580
576
  // caching a uniform-random assignment would pin one variant for the
@@ -599,64 +595,7 @@ export class PromptResource<F extends RenderFormat | undefined = undefined> {
599
595
  // client-eager throw already happened above the try, so there is no
600
596
  // network error to trigger it on the env-missing path.
601
597
  if (err instanceof To11aiNetworkError && fallback !== undefined) {
602
- return liftFallbackToFetched(fallback);
603
- }
604
- throw err;
605
- }
606
- }
607
-
608
- // ─── Resolve ──────────────────────────────────────────────────────────────
609
-
610
- /**
611
- * Resolve a prompt by explicit `labels` for a given `projectId`, in one
612
- * control-plane request. This is the label-addressed counterpart to
613
- * {@link PromptResource.render | `render()`}, which resolves by the client's
614
- * configured `env`. Reach for `resolve()` when you address prompts by label
615
- * rather than by environment; it is env-agnostic, so it carries no `env` /
616
- * `subject`, no enriched release/variant provenance, and no typed-env errors,
617
- * and is not subject to the env-based no-silent-fallback gate.
618
- */
619
- async resolve(input: ResolvePromptInput): Promise<ResolvePromptResult> {
620
- const {
621
- projectId,
622
- slug,
623
- labels,
624
- variables,
625
- cache: useCache = true,
626
- fallback,
627
- } = input;
628
-
629
- const cacheKey = buildCacheKey(projectId, slug, labels, variables);
630
-
631
- if (useCache) {
632
- const cached = this.cache.get(cacheKey);
633
- // The legacy resolve key namespace only ever stores ResolvedPrompt
634
- // entries (the fetch namespace is "fetch|…"); the `messages` guard
635
- // keeps the static return type as ResolvedPrompt.
636
- if (cached && !("messages" in cached)) return structuredClone(cached);
637
- }
638
-
639
- try {
640
- const envelope = await this.transport.request<ResolvePromptResult>(
641
- "POST",
642
- `${getProjectPromptsPath(projectId)}:resolve`,
643
- {
644
- body: { slug, labels, variables },
645
- decode: (data) =>
646
- zResolvedPromptLegacy.parse(data) as ResolvePromptResult,
647
- },
648
- );
649
- const result = unwrap(envelope);
650
-
651
- if (useCache) {
652
- this.cache.set(cacheKey, structuredClone(result));
653
- }
654
-
655
- return result;
656
- } catch (err) {
657
- // If network error and we have a fallback, serve it
658
- if (err instanceof To11aiNetworkError && fallback !== undefined) {
659
- return fallback;
598
+ return liftFallbackToRendered(fallback, slug);
660
599
  }
661
600
  throw err;
662
601
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Format-at-load shaping: turn a neutral `FetchedPrompt` into a provider-shaped
2
+ * Format-at-load shaping: turn a neutral `RenderedPrompt` into a provider-shaped
3
3
  * `{ messages, config, metadata }` result the caller spreads into their own
4
4
  * provider `create()` call. Header-only — this only reshapes data, it never
5
5
  * sends a request. See docs/product-specs/2026-07-14-to11-2691-sdk-simpler-setup.md.
@@ -21,7 +21,7 @@ import {
21
21
  toOpenAIToolChoice,
22
22
  toOpenAITools,
23
23
  } from "../gateway";
24
- import type { FetchedPrompt, RenderFormat } from "./types";
24
+ import type { RenderedPrompt, RenderFormat } from "./types";
25
25
 
26
26
  /** Model params passed through verbatim from the resolved version's modelConfig. */
27
27
  export interface PromptConfig {
@@ -42,6 +42,8 @@ export interface PromptMetadata {
42
42
  releaseId: string | null;
43
43
  /** The single label the prompt resolved through (env slug today). */
44
44
  label: string;
45
+ /** The prompt's human-readable slug — surfaced as `x-to11-prompt-slug`. */
46
+ slug: string;
45
47
  variantName?: string;
46
48
  }
47
49
 
@@ -75,7 +77,7 @@ export type RenderResult<F extends RenderFormat | undefined> =
75
77
  ? OpenAiRenderedPrompt
76
78
  : F extends "anthropic"
77
79
  ? AnthropicRenderedPrompt
78
- : FetchedPrompt;
80
+ : RenderedPrompt;
79
81
 
80
82
  function toPromptConfig(modelConfig: unknown): PromptConfig {
81
83
  return modelConfig !== null && typeof modelConfig === "object"
@@ -83,13 +85,14 @@ function toPromptConfig(modelConfig: unknown): PromptConfig {
83
85
  : {};
84
86
  }
85
87
 
86
- function toMetadata(prompt: FetchedPrompt): PromptMetadata {
88
+ function toMetadata(prompt: RenderedPrompt): PromptMetadata {
87
89
  const metadata: PromptMetadata = {
88
90
  promptId: prompt.promptId,
89
91
  versionId: prompt.versionId,
90
92
  version: prompt.version,
91
93
  releaseId: prompt.releaseId,
92
94
  label: prompt.label,
95
+ slug: prompt.slug,
93
96
  };
94
97
  if (prompt.variantName !== undefined)
95
98
  metadata.variantName = prompt.variantName;
@@ -97,7 +100,7 @@ function toMetadata(prompt: FetchedPrompt): PromptMetadata {
97
100
  }
98
101
 
99
102
  export function shapeOpenAI(
100
- prompt: FetchedPrompt,
103
+ prompt: RenderedPrompt,
101
104
  options: ConvertMessagesOptions = {},
102
105
  ): OpenAiRenderedPrompt {
103
106
  const result: OpenAiRenderedPrompt = {
@@ -115,7 +118,7 @@ export function shapeOpenAI(
115
118
  }
116
119
 
117
120
  export function shapeAnthropic(
118
- prompt: FetchedPrompt,
121
+ prompt: RenderedPrompt,
119
122
  options: ConvertMessagesOptions = {},
120
123
  ): AnthropicRenderedPrompt {
121
124
  const result: AnthropicRenderedPrompt = {
@@ -134,10 +137,10 @@ export function shapeAnthropic(
134
137
 
135
138
  /** Shape a fetched prompt for `format`, or return it neutral when unset. */
136
139
  export function shapePrompt(
137
- prompt: FetchedPrompt,
140
+ prompt: RenderedPrompt,
138
141
  format: RenderFormat | undefined,
139
142
  options: ConvertMessagesOptions = {},
140
- ): FetchedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt {
143
+ ): RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt {
141
144
  switch (format) {
142
145
  case "openai":
143
146
  return shapeOpenAI(prompt, options);
@@ -6,7 +6,6 @@ import type {
6
6
  PromptLabelPointer as GenPromptLabelPointer,
7
7
  PromptVersion as GenPromptVersion,
8
8
  RenderedChatPrompt as GenRenderedChatPrompt,
9
- RenderedPrompt as GenRenderedPrompt,
10
9
  ResolvedPrompt as GenResolvedPrompt,
11
10
  ResolvedToolCall as GenResolvedToolCall,
12
11
  ResolvedToolDefinition as GenResolvedToolDefinition,
@@ -36,8 +35,6 @@ export type ChatMessage = TextChatMessage;
36
35
 
37
36
  export type RenderedChatMessage = GenRenderedChatPrompt["messages"][number];
38
37
 
39
- export type RenderedPrompt = GenRenderedPrompt;
40
-
41
38
  /** Provider shape for `prompts.render()` output. Omit for the neutral result. */
42
39
  export type RenderFormat = "openai" | "anthropic";
43
40
 
@@ -177,24 +174,11 @@ export interface ListPromptLabelsInput {
177
174
 
178
175
  export type ListPromptLabelsResult = PromptLabelPointer[];
179
176
 
180
- export interface ResolvePromptInput {
181
- projectId: string;
182
- slug: string;
183
- labels: string[];
184
- variables?: Record<string, unknown>;
185
- /** Set false to bypass the LRU cache for this call. Default: true */
186
- cache?: boolean;
187
- /** Fallback prompt served if the API is unreachable and cache is empty. */
188
- fallback?: ResolvedPrompt;
189
- }
190
-
191
- export type ResolvePromptResult = ResolvedPrompt;
192
-
193
177
  export const ENV_SLUG_REGEX = /^[a-z0-9-]{1,63}$/;
194
178
 
195
179
  export type BlockRenderRecord = Record<string, "rendered" | "dimmed">;
196
180
 
197
- export interface FetchedPrompt {
181
+ export interface RenderedPrompt {
198
182
  messages: RenderedChatMessage[];
199
183
  version: number;
200
184
  releaseId: string | null;
@@ -209,7 +193,12 @@ export interface FetchedPrompt {
209
193
  * `gatewayPromptHeaders` / `turn.headers()` (TO11-2725).
210
194
  */
211
195
  label: string;
212
- rendered: RenderedPrompt;
196
+ /**
197
+ * The prompt's human-readable slug (the `render()` input). Surfaced as
198
+ * `x-to11-prompt-slug` by `gatewayPromptHeaders` / `turn.headers()` so traces
199
+ * carry a readable name alongside the opaque `promptId` (TO11-2727).
200
+ */
201
+ slug: string;
213
202
  /**
214
203
  * The served version's model config (model, temperature, max_tokens, …),
215
204
  * passed through verbatim. Surfaced as `config` by `render({ format })`.
@@ -228,7 +217,7 @@ export interface FetchedPrompt {
228
217
  * into `toOpenAIToolChoice` / `toAnthropicToolChoice`.
229
218
  *
230
219
  * `NonNullable` because `normalizeResolvedEnvelope` maps wire-`null` to absent,
231
- * so a present `FetchedPrompt.toolChoice` is never `null` — callers get the
220
+ * so a present `RenderedPrompt.toolChoice` is never `null` — callers get the
232
221
  * key or nothing, and never have to branch on `null`.
233
222
  */
234
223
  toolChoice?: NonNullable<ResolvedPrompt["toolChoice"]>;
@@ -239,15 +228,16 @@ export type ResolvedPromptEnvelope = GenResolvedPrompt & {
239
228
  strategy?: "single" | "weighted";
240
229
  };
241
230
 
242
- export interface FetchPromptOptions {
231
+ export interface RenderOptions {
232
+ /** Serving environment for this call; overrides the client default. */
243
233
  env?: string;
234
+ /** Stable id so a weighted release sends the same caller to the same variant. */
244
235
  subject?: string;
245
236
  variables?: Record<string, unknown>;
237
+ /** Set false to bypass the LRU cache for this call. Default: true */
246
238
  cache?: boolean;
247
- fallback?: ResolvedPrompt | FetchedPrompt;
248
- }
249
-
250
- export interface RenderOptions extends FetchPromptOptions {
239
+ /** Prompt served if the API is unreachable on a network error and cache is empty. */
240
+ fallback?: RenderedPrompt;
251
241
  /** Provider shape for this call's output; overrides the client default. */
252
242
  format?: RenderFormat;
253
243
  /**