@to11ai/sdk 1.1.0 → 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.
@@ -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
- * `fetch()` 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
@@ -24,7 +23,7 @@ export type CacheableResolved = ResolvedPrompt | FetchedPrompt;
24
23
  */
25
24
  export declare function buildCacheKey(projectId: string, slug: string, labels: string[], variables?: Record<string, unknown>): string;
26
25
  /**
27
- * Build a cache key for an env-aware `fetch()` call.
26
+ * Build a cache key for an env-aware `render()` call.
28
27
  *
29
28
  * Folds in `env` and `subject` ON TOP of the base
30
29
  * project/slug/labels/variables key so two calls that differ only by
@@ -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>, developerRole?: string): 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;
@@ -46,17 +46,8 @@ var import_zod = require("zod");
46
46
  var import_errors = require("../errors");
47
47
  var import_zod2 = require("../generated/zod.gen");
48
48
  var import_cache = require("./cache");
49
+ var import_shape = require("./shape");
49
50
  var import_types = require("./types");
50
- var zResolvedPromptLegacy = import_zod2.zResolvedPrompt.pick({
51
- promptId: true,
52
- versionId: true,
53
- version: true,
54
- labels: true,
55
- contentHash: true,
56
- rendered: true,
57
- tools: true,
58
- toolChoice: true
59
- });
60
51
  var zResolvedPromptEnvelope = import_zod2.zResolvedPrompt.partial({ releaseId: true }).extend({ strategy: import_zod.z.enum(["single", "weighted"]).optional() });
61
52
  function unwrap(envelope) {
62
53
  if (envelope.data === null || envelope.data === undefined) {
@@ -68,12 +59,12 @@ function getProjectPromptsPath(projectId) {
68
59
  return `/v1/projects/${encodeURIComponent(projectId)}/prompts`;
69
60
  }
70
61
  function renderedToMessages(rendered) {
71
- if (rendered.format === "chat") {
72
- 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.");
73
64
  }
74
- return [{ role: "user", content: rendered.text }];
65
+ return rendered.messages;
75
66
  }
76
- function normalizeResolvedEnvelope(raw) {
67
+ function normalizeResolvedEnvelope(raw, slug) {
77
68
  const provenance = raw;
78
69
  const result = {
79
70
  messages: renderedToMessages(raw.rendered),
@@ -83,8 +74,9 @@ function normalizeResolvedEnvelope(raw) {
83
74
  promptId: raw.promptId,
84
75
  versionId: raw.versionId,
85
76
  contentHash: raw.contentHash,
86
- labels: raw.labels,
87
- rendered: raw.rendered
77
+ label: raw.label,
78
+ slug,
79
+ modelConfig: provenance.modelConfig
88
80
  };
89
81
  if (raw.tools != null && raw.tools.length > 0) {
90
82
  result.tools = raw.tools;
@@ -97,11 +89,11 @@ function normalizeResolvedEnvelope(raw) {
97
89
  }
98
90
  return result;
99
91
  }
100
- function liftFallbackToFetched(fallback) {
101
- if ("messages" in fallback) {
102
- 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.");
103
95
  }
104
- return normalizeResolvedEnvelope(fallback);
96
+ return { ...structuredClone(fallback), slug: fallback.slug || slug };
105
97
  }
106
98
  function getProjectPromptPath(projectId, promptId) {
107
99
  return `${getProjectPromptsPath(projectId)}/${encodeURIComponent(promptId)}`;
@@ -139,11 +131,15 @@ class PromptResource {
139
131
  cache;
140
132
  projectId;
141
133
  env;
134
+ format;
135
+ onUnknownRole;
142
136
  constructor(transport, cacheConfig, config) {
143
137
  this.transport = transport;
144
138
  this.cache = new import_cache.PromptCache(cacheConfig);
145
139
  this.projectId = config?.projectId;
146
140
  this.env = config?.env;
141
+ this.format = config?.format;
142
+ this.onUnknownRole = config?.onUnknownRole;
147
143
  }
148
144
  async create(input) {
149
145
  const { projectId, ...body } = input;
@@ -207,9 +203,15 @@ class PromptResource {
207
203
  const envelope = await this.transport.request("GET", `${getProjectPromptPath(projectId, promptId)}/labels`);
208
204
  return unwrap(envelope);
209
205
  }
210
- async fetch(slug, options = {}) {
206
+ async render(slug, options = {}) {
207
+ const rendered = await this.resolveToRendered(slug, options);
208
+ return import_shape.shapePrompt(rendered, options.format ?? this.format, {
209
+ onUnknownRole: options.onUnknownRole ?? this.onUnknownRole
210
+ });
211
+ }
212
+ async resolveToRendered(slug, options = {}) {
211
213
  if (this.projectId === undefined || this.projectId.trim() === "") {
212
- throw new Error("[to11ai/sdk] `prompts.fetch()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
214
+ throw new Error("[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
213
215
  }
214
216
  const env = options.env ?? this.env;
215
217
  if (env === undefined) {
@@ -218,17 +220,11 @@ class PromptResource {
218
220
  if (!import_types.ENV_SLUG_REGEX.test(env)) {
219
221
  throw new Error(`[to11ai/sdk] \`env\` must match ${import_types.ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
220
222
  }
221
- const {
222
- subject,
223
- variables,
224
- cache: useCache = true,
225
- fallback,
226
- developerRole
227
- } = options;
228
- const cacheKey = import_cache.buildFetchCacheKey(this.projectId, slug, env, subject, variables, developerRole);
223
+ const { subject, variables, cache: useCache = true, fallback } = options;
224
+ const cacheKey = import_cache.buildRenderCacheKey(this.projectId, slug, env, subject, variables);
229
225
  if (useCache) {
230
226
  const cached = this.cache.get(cacheKey);
231
- if (cached && "messages" in cached)
227
+ if (cached)
232
228
  return structuredClone(cached);
233
229
  }
234
230
  const body = { slug, labels: [env] };
@@ -236,15 +232,13 @@ class PromptResource {
236
232
  body.subject = subject;
237
233
  if (variables !== undefined)
238
234
  body.variables = variables;
239
- if (developerRole !== undefined)
240
- body.developerRole = developerRole;
241
235
  try {
242
236
  const envelope = await this.transport.request("POST", `${getProjectPromptsPath(this.projectId)}:resolve`, {
243
237
  body,
244
238
  decode: (data) => zResolvedPromptEnvelope.parse(data)
245
239
  });
246
240
  const raw = unwrap(envelope);
247
- const result = normalizeResolvedEnvelope(raw);
241
+ const result = normalizeResolvedEnvelope(raw, slug);
248
242
  const isWeighted = raw.strategy === "weighted" || result.variantName !== undefined;
249
243
  const cacheable = !(isWeighted && subject === undefined);
250
244
  if (useCache && cacheable) {
@@ -253,39 +247,7 @@ class PromptResource {
253
247
  return result;
254
248
  } catch (err) {
255
249
  if (err instanceof import_errors.To11aiNetworkError && fallback !== undefined) {
256
- return liftFallbackToFetched(fallback);
257
- }
258
- throw err;
259
- }
260
- }
261
- async resolve(input) {
262
- const {
263
- projectId,
264
- slug,
265
- labels,
266
- variables,
267
- cache: useCache = true,
268
- fallback
269
- } = input;
270
- const cacheKey = import_cache.buildCacheKey(projectId, slug, labels, variables);
271
- if (useCache) {
272
- const cached = this.cache.get(cacheKey);
273
- if (cached && !("messages" in cached))
274
- return structuredClone(cached);
275
- }
276
- try {
277
- const envelope = await this.transport.request("POST", `${getProjectPromptsPath(projectId)}:resolve`, {
278
- body: { slug, labels, variables },
279
- decode: (data) => zResolvedPromptLegacy.parse(data)
280
- });
281
- const result = unwrap(envelope);
282
- if (useCache) {
283
- this.cache.set(cacheKey, structuredClone(result));
284
- }
285
- return result;
286
- } catch (err) {
287
- if (err instanceof import_errors.To11aiNetworkError && fallback !== undefined) {
288
- return fallback;
250
+ return liftFallbackToRendered(fallback, slug);
289
251
  }
290
252
  throw err;
291
253
  }
@@ -1,27 +1,39 @@
1
1
  import type { Transport } from "../transport";
2
2
  import type { CacheConfig } from "./cache";
3
- import type { ArchivePromptInput, ArchivePromptResult, 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, ResolvePromptInput, ResolvePromptResult, UpdateProjectPromptLabelInput, UpdateProjectPromptLabelResult, UpdatePromptInput, UpdatePromptResult } from "./types";
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, UnknownRoleMode, UpdateProjectPromptLabelInput, UpdateProjectPromptLabelResult, UpdatePromptInput, UpdatePromptResult } from "./types";
4
5
  export type { CacheConfig } from "./cache";
5
- 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";
6
7
  /**
7
- * Init-time config for the env-aware `fetch()` surface (TO11-980 / S9).
8
+ * Init-time config for the env-aware `render()` surface (TO11-980 / S9).
8
9
  *
9
10
  * `projectId` and `env` come from `createClient`. `projectId` is required for
10
- * `fetch()` (it builds the project-scoped resolve path); `env` is the default
11
- * serving environment inherited by every `fetch(slug)` call.
11
+ * `render()` (it builds the project-scoped resolve path); `env` is the default
12
+ * serving environment inherited by every `render(slug)` call.
12
13
  */
13
14
  export interface PromptResourceConfig {
14
- /** Project the client is bound to. Required for `fetch()`; validated at createClient. */
15
+ /** Project the client is bound to. Required for `render()`; validated at createClient. */
15
16
  projectId?: string;
16
- /** Default serving env for `fetch()`. Validated against ENV_SLUG_REGEX at createClient. */
17
+ /** Default serving env for `render()`. Validated against ENV_SLUG_REGEX at createClient. */
17
18
  env?: string;
19
+ /** Default provider shape for `render()` output. Per-call override allowed. */
20
+ format?: RenderFormat;
21
+ /**
22
+ * Default unknown-role policy for `render({ format })` output conversion.
23
+ * Per-call override allowed; defaults to `"drop"`.
24
+ */
25
+ onUnknownRole?: UnknownRoleMode;
18
26
  }
19
- export declare class PromptResource {
27
+ export declare class PromptResource<F extends RenderFormat | undefined = undefined> {
20
28
  private readonly transport;
21
29
  private readonly cache;
22
30
  private readonly projectId;
23
31
  private readonly env;
24
- constructor(transport: Transport, cacheConfig?: CacheConfig, config?: PromptResourceConfig);
32
+ private readonly format;
33
+ private readonly onUnknownRole;
34
+ constructor(transport: Transport, cacheConfig?: CacheConfig, config?: Omit<PromptResourceConfig, "format"> & {
35
+ format?: F;
36
+ });
25
37
  create(input: CreatePromptInput): Promise<CreatePromptResult>;
26
38
  list(input: ListPromptsInput): Promise<PaginatedResult<ListPromptsResult>>;
27
39
  get(input: GetPromptInput): Promise<GetPromptResult>;
@@ -33,35 +45,43 @@ export declare class PromptResource {
33
45
  moveLabel(input: MovePromptLabelInput): Promise<MovePromptLabelResult>;
34
46
  listLabels(input: ListPromptLabelsInput): Promise<ListPromptLabelsResult>;
35
47
  /**
36
- * Resolve a prompt for the client's configured `projectId` + `env`
37
- * (Style-A). The ergonomic slug-first surface: env is configured once at
38
- * `createClient` and inherited here, with an optional per-call override.
48
+ * Resolve and (optionally) shape a prompt for the client's configured
49
+ * `projectId` + `env`, in one control-plane request. With a `format` (per
50
+ * call or inherited from the client) the result is provider-shaped
51
+ * `{ messages, config, metadata }`; without one it is the neutral
52
+ * `RenderedPrompt`. Resolves by the client's configured `env`.
53
+ *
54
+ * @example Render an OpenAI-shaped prompt and send it through the gateway
55
+ * const prompt = await to11.prompts.render("weather-concierge", {
56
+ * variables: { city: "NYC" },
57
+ * });
58
+ * const res = await openai.chat.completions.create(
59
+ * { ...prompt.config, messages: prompt.messages },
60
+ * { headers: to11.turn().headers(prompt) },
61
+ * );
62
+ */
63
+ render(slug: string, options: RenderOptions & {
64
+ format: "openai";
65
+ }): Promise<OpenAiRenderedPrompt>;
66
+ render(slug: string, options: RenderOptions & {
67
+ format: "anthropic";
68
+ }): Promise<AnthropicRenderedPrompt>;
69
+ render(slug: string, options?: RenderOptions): Promise<RenderResult<F>>;
70
+ /**
71
+ * Env-aware resolution → neutral `RenderedPrompt`. Carries the resolve /
72
+ * cache / offline-fallback logic; `render()` is the public shaping surface.
39
73
  *
40
- * - **env→label wire mapping (correction #3):** the resolve route is
41
- * label-based (`/v1/projects/:projectId/prompts:resolve`, body
42
- * `labels[]`); it does NOT accept a top-level `env` field. The resolved
43
- * env slug (lowercase-kebab — a valid label name) is therefore sent as
44
- * the single resolution label: body `{ slug, labels: [env], subject?,
45
- * variables? }`.
74
+ * - **env→label wire mapping:** the resolve route is label-based
75
+ * (`/v1/projects/:projectId/prompts:resolve`, body `labels[]`); the
76
+ * resolved env slug is sent as the single resolution label.
46
77
  * - **No silent fallback (R4):** if no env is resolvable (none at init AND
47
- * none per call) this throws `EnvNotFoundError` (`reason.source =
48
- * "missing"`) CLIENT-side, eagerly, before any transport call.
78
+ * none per call) this throws `EnvNotFoundError` CLIENT-side, eagerly,
79
+ * before any transport call.
49
80
  * - **Cache (D8):** caches `single`-strategy releases and weighted releases
50
81
  * WITH a `subject`; BYPASSES the cache for weighted releases with NO
51
82
  * `subject` (a uniform-random assignment must not be pinned for the TTL).
52
- *
53
- * Prefer this over the deprecated `resolve()`.
54
- */
55
- fetch(slug: string, options?: FetchPromptOptions): Promise<FetchedPrompt>;
56
- /**
57
- * @deprecated Use {@link PromptResource.fetch | `prompts.fetch(slug,
58
- * options?)`} instead. `resolve()` is env-agnostic (no `env` / `subject`,
59
- * no enriched release/variant provenance, no typed-env errors)
60
- * and is retained only for back-compat. It is NOT subject to the R4
61
- * no-silent-fallback env gate (it has no env concept). New code should
62
- * configure `projectId` + `env` at `createClient` and call `fetch()`.
63
83
  */
64
- resolve(input: ResolvePromptInput): Promise<ResolvePromptResult>;
84
+ private resolveToRendered;
65
85
  /**
66
86
  * List the project-level label registry.
67
87
  *
@@ -2,18 +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
+ import { shapePrompt } from "./shape";
6
7
  import { ENV_SLUG_REGEX } from "./types";
7
- var zResolvedPromptLegacy = zResolvedPrompt.pick({
8
- promptId: true,
9
- versionId: true,
10
- version: true,
11
- labels: true,
12
- contentHash: true,
13
- rendered: true,
14
- tools: true,
15
- toolChoice: true
16
- });
17
8
  var zResolvedPromptEnvelope = zResolvedPrompt.partial({ releaseId: true }).extend({ strategy: z.enum(["single", "weighted"]).optional() });
18
9
  function unwrap(envelope) {
19
10
  if (envelope.data === null || envelope.data === undefined) {
@@ -25,12 +16,12 @@ function getProjectPromptsPath(projectId) {
25
16
  return `/v1/projects/${encodeURIComponent(projectId)}/prompts`;
26
17
  }
27
18
  function renderedToMessages(rendered) {
28
- if (rendered.format === "chat") {
29
- 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.");
30
21
  }
31
- return [{ role: "user", content: rendered.text }];
22
+ return rendered.messages;
32
23
  }
33
- function normalizeResolvedEnvelope(raw) {
24
+ function normalizeResolvedEnvelope(raw, slug) {
34
25
  const provenance = raw;
35
26
  const result = {
36
27
  messages: renderedToMessages(raw.rendered),
@@ -40,8 +31,9 @@ function normalizeResolvedEnvelope(raw) {
40
31
  promptId: raw.promptId,
41
32
  versionId: raw.versionId,
42
33
  contentHash: raw.contentHash,
43
- labels: raw.labels,
44
- rendered: raw.rendered
34
+ label: raw.label,
35
+ slug,
36
+ modelConfig: provenance.modelConfig
45
37
  };
46
38
  if (raw.tools != null && raw.tools.length > 0) {
47
39
  result.tools = raw.tools;
@@ -54,11 +46,11 @@ function normalizeResolvedEnvelope(raw) {
54
46
  }
55
47
  return result;
56
48
  }
57
- function liftFallbackToFetched(fallback) {
58
- if ("messages" in fallback) {
59
- 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.");
60
52
  }
61
- return normalizeResolvedEnvelope(fallback);
53
+ return { ...structuredClone(fallback), slug: fallback.slug || slug };
62
54
  }
63
55
  function getProjectPromptPath(projectId, promptId) {
64
56
  return `${getProjectPromptsPath(projectId)}/${encodeURIComponent(promptId)}`;
@@ -96,11 +88,15 @@ class PromptResource {
96
88
  cache;
97
89
  projectId;
98
90
  env;
91
+ format;
92
+ onUnknownRole;
99
93
  constructor(transport, cacheConfig, config) {
100
94
  this.transport = transport;
101
95
  this.cache = new PromptCache(cacheConfig);
102
96
  this.projectId = config?.projectId;
103
97
  this.env = config?.env;
98
+ this.format = config?.format;
99
+ this.onUnknownRole = config?.onUnknownRole;
104
100
  }
105
101
  async create(input) {
106
102
  const { projectId, ...body } = input;
@@ -164,9 +160,15 @@ class PromptResource {
164
160
  const envelope = await this.transport.request("GET", `${getProjectPromptPath(projectId, promptId)}/labels`);
165
161
  return unwrap(envelope);
166
162
  }
167
- async fetch(slug, options = {}) {
163
+ async render(slug, options = {}) {
164
+ const rendered = await this.resolveToRendered(slug, options);
165
+ return shapePrompt(rendered, options.format ?? this.format, {
166
+ onUnknownRole: options.onUnknownRole ?? this.onUnknownRole
167
+ });
168
+ }
169
+ async resolveToRendered(slug, options = {}) {
168
170
  if (this.projectId === undefined || this.projectId.trim() === "") {
169
- throw new Error("[to11ai/sdk] `prompts.fetch()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
171
+ throw new Error("[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
170
172
  }
171
173
  const env = options.env ?? this.env;
172
174
  if (env === undefined) {
@@ -175,17 +177,11 @@ class PromptResource {
175
177
  if (!ENV_SLUG_REGEX.test(env)) {
176
178
  throw new Error(`[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
177
179
  }
178
- const {
179
- subject,
180
- variables,
181
- cache: useCache = true,
182
- fallback,
183
- developerRole
184
- } = options;
185
- const cacheKey = buildFetchCacheKey(this.projectId, slug, env, subject, variables, developerRole);
180
+ const { subject, variables, cache: useCache = true, fallback } = options;
181
+ const cacheKey = buildRenderCacheKey(this.projectId, slug, env, subject, variables);
186
182
  if (useCache) {
187
183
  const cached = this.cache.get(cacheKey);
188
- if (cached && "messages" in cached)
184
+ if (cached)
189
185
  return structuredClone(cached);
190
186
  }
191
187
  const body = { slug, labels: [env] };
@@ -193,15 +189,13 @@ class PromptResource {
193
189
  body.subject = subject;
194
190
  if (variables !== undefined)
195
191
  body.variables = variables;
196
- if (developerRole !== undefined)
197
- body.developerRole = developerRole;
198
192
  try {
199
193
  const envelope = await this.transport.request("POST", `${getProjectPromptsPath(this.projectId)}:resolve`, {
200
194
  body,
201
195
  decode: (data) => zResolvedPromptEnvelope.parse(data)
202
196
  });
203
197
  const raw = unwrap(envelope);
204
- const result = normalizeResolvedEnvelope(raw);
198
+ const result = normalizeResolvedEnvelope(raw, slug);
205
199
  const isWeighted = raw.strategy === "weighted" || result.variantName !== undefined;
206
200
  const cacheable = !(isWeighted && subject === undefined);
207
201
  if (useCache && cacheable) {
@@ -210,39 +204,7 @@ class PromptResource {
210
204
  return result;
211
205
  } catch (err) {
212
206
  if (err instanceof To11aiNetworkError && fallback !== undefined) {
213
- return liftFallbackToFetched(fallback);
214
- }
215
- throw err;
216
- }
217
- }
218
- async resolve(input) {
219
- const {
220
- projectId,
221
- slug,
222
- labels,
223
- variables,
224
- cache: useCache = true,
225
- fallback
226
- } = input;
227
- const cacheKey = buildCacheKey(projectId, slug, labels, variables);
228
- if (useCache) {
229
- const cached = this.cache.get(cacheKey);
230
- if (cached && !("messages" in cached))
231
- return structuredClone(cached);
232
- }
233
- try {
234
- const envelope = await this.transport.request("POST", `${getProjectPromptsPath(projectId)}:resolve`, {
235
- body: { slug, labels, variables },
236
- decode: (data) => zResolvedPromptLegacy.parse(data)
237
- });
238
- const result = unwrap(envelope);
239
- if (useCache) {
240
- this.cache.set(cacheKey, structuredClone(result));
241
- }
242
- return result;
243
- } catch (err) {
244
- if (err instanceof To11aiNetworkError && fallback !== undefined) {
245
- return fallback;
207
+ return liftFallbackToRendered(fallback, slug);
246
208
  }
247
209
  throw err;
248
210
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Format-at-load shaping: turn a neutral `RenderedPrompt` into a provider-shaped
3
+ * `{ messages, config, metadata }` result the caller spreads into their own
4
+ * provider `create()` call. Header-only — this only reshapes data, it never
5
+ * sends a request. See docs/product-specs/2026-07-14-to11-2691-sdk-simpler-setup.md.
6
+ */
7
+ import { type AnthropicMessage, type AnthropicTool, type AnthropicToolChoice, type ConvertMessagesOptions, type OpenAIChatMessage, type OpenAITool, type OpenAIToolChoice } from "../gateway";
8
+ import type { RenderedPrompt, RenderFormat } from "./types";
9
+ /** Model params passed through verbatim from the resolved version's modelConfig. */
10
+ export interface PromptConfig {
11
+ model?: string;
12
+ temperature?: number;
13
+ max_tokens?: number;
14
+ /** Any additional stored model-config keys are passed through unchanged. */
15
+ [key: string]: unknown;
16
+ }
17
+ /** Prompt provenance for `turn.headers()` — kept apart from the request body. */
18
+ export interface PromptMetadata {
19
+ promptId: string;
20
+ versionId: string;
21
+ version: number;
22
+ releaseId: string | null;
23
+ /** The single label the prompt resolved through (env slug today). */
24
+ label: string;
25
+ /** The prompt's human-readable slug — surfaced as `x-to11-prompt-slug`. */
26
+ slug: string;
27
+ variantName?: string;
28
+ }
29
+ /** OpenAI-shaped render result: spread `{ ...config, messages }` into create(). */
30
+ export interface OpenAiRenderedPrompt {
31
+ messages: OpenAIChatMessage[];
32
+ config: PromptConfig;
33
+ metadata: PromptMetadata;
34
+ /** Authored tools, provider-shaped. Present only when the prompt declares tools. */
35
+ tools?: OpenAITool[];
36
+ /** Authored tool-choice directive. Present only when the prompt sets one. */
37
+ tool_choice?: OpenAIToolChoice;
38
+ }
39
+ /** Anthropic-shaped render result: system/developer folded into `system`. */
40
+ export interface AnthropicRenderedPrompt {
41
+ /** Folded system/developer instruction text (Anthropic's top-level `system`). */
42
+ system?: string;
43
+ messages: AnthropicMessage[];
44
+ config: PromptConfig;
45
+ metadata: PromptMetadata;
46
+ /** Authored tools, provider-shaped. Present only when the prompt declares tools. */
47
+ tools?: AnthropicTool[];
48
+ /** Authored tool-choice directive. Present only when the prompt sets one. */
49
+ tool_choice?: AnthropicToolChoice;
50
+ }
51
+ /** The render return type for a given client/call format. */
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;
55
+ /** Shape a fetched prompt for `format`, or return it neutral when unset. */
56
+ export declare function shapePrompt(prompt: RenderedPrompt, format: RenderFormat | undefined, options?: ConvertMessagesOptions): RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt;