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

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.
@@ -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)}`;
@@ -109,13 +98,22 @@ class PromptResource {
109
98
  this.format = config?.format;
110
99
  this.onUnknownRole = config?.onUnknownRole;
111
100
  }
101
+ resolveProjectId(perCall) {
102
+ const projectId = perCall ?? this.projectId;
103
+ if (projectId === undefined || projectId.trim() === "") {
104
+ throw new Error("[to11ai/sdk] this call needs a `projectId` — pass it on the call or bind one via createClient({ projectId, ... }).");
105
+ }
106
+ return projectId.trim();
107
+ }
112
108
  async create(input) {
113
- const { projectId, ...body } = input;
109
+ const { projectId: perCallProjectId, ...body } = input;
110
+ const projectId = this.resolveProjectId(perCallProjectId);
114
111
  const envelope = await this.transport.request("POST", getProjectPromptsPath(projectId), { body });
115
112
  return unwrap(envelope);
116
113
  }
117
- async list(input) {
118
- const { projectId, ...queryRest } = input;
114
+ async list(input = {}) {
115
+ const { projectId: perCallProjectId, ...queryRest } = input;
116
+ const projectId = this.resolveProjectId(perCallProjectId);
119
117
  const envelope = await this.transport.request("GET", getProjectPromptsPath(projectId), {
120
118
  query: {
121
119
  ...queryRest
@@ -132,52 +130,60 @@ class PromptResource {
132
130
  return { items, pagination };
133
131
  }
134
132
  async get(input) {
135
- const { projectId, promptId } = input;
133
+ const { projectId: perCallProjectId, promptId } = input;
134
+ const projectId = this.resolveProjectId(perCallProjectId);
136
135
  const envelope = await this.transport.request("GET", getProjectPromptPath(projectId, promptId));
137
136
  return unwrap(envelope);
138
137
  }
139
138
  async update(input) {
140
- const { projectId, promptId, ...body } = input;
139
+ const { projectId: perCallProjectId, promptId, ...body } = input;
140
+ const projectId = this.resolveProjectId(perCallProjectId);
141
141
  const envelope = await this.transport.request("PATCH", getProjectPromptPath(projectId, promptId), { body });
142
142
  return unwrap(envelope);
143
143
  }
144
144
  async archive(input) {
145
- const { projectId, promptId } = input;
145
+ const { projectId: perCallProjectId, promptId } = input;
146
+ const projectId = this.resolveProjectId(perCallProjectId);
146
147
  const envelope = await this.transport.request("DELETE", getProjectPromptPath(projectId, promptId));
147
148
  return unwrap(envelope);
148
149
  }
149
150
  async createVersion(input) {
150
- const { projectId, promptId, ...body } = input;
151
+ const { projectId: perCallProjectId, promptId, ...body } = input;
152
+ const projectId = this.resolveProjectId(perCallProjectId);
151
153
  const envelope = await this.transport.request("POST", `${getProjectPromptPath(projectId, promptId)}/versions`, { body });
152
154
  return unwrap(envelope);
153
155
  }
154
156
  async listVersions(input) {
155
- const { projectId, promptId } = input;
157
+ const { projectId: perCallProjectId, promptId } = input;
158
+ const projectId = this.resolveProjectId(perCallProjectId);
156
159
  const envelope = await this.transport.request("GET", `${getProjectPromptPath(projectId, promptId)}/versions`);
157
160
  return unwrap(envelope);
158
161
  }
159
162
  async getVersion(input) {
160
- const { projectId, promptId, versionNumber } = input;
163
+ const { projectId: perCallProjectId, promptId, versionNumber } = input;
164
+ const projectId = this.resolveProjectId(perCallProjectId);
161
165
  const envelope = await this.transport.request("GET", `${getProjectPromptPath(projectId, promptId)}/versions/${encodeURIComponent(String(versionNumber))}`);
162
166
  return unwrap(envelope);
163
167
  }
164
168
  async moveLabel(input) {
165
- const { projectId, promptId, label, ...body } = input;
169
+ const { projectId: perCallProjectId, promptId, label, ...body } = input;
170
+ const projectId = this.resolveProjectId(perCallProjectId);
166
171
  const envelope = await this.transport.request("PUT", `${getProjectPromptPath(projectId, promptId)}/labels/${encodeURIComponent(label)}`, { body });
167
172
  return unwrap(envelope);
168
173
  }
169
174
  async listLabels(input) {
170
- const { projectId, promptId } = input;
175
+ const { projectId: perCallProjectId, promptId } = input;
176
+ const projectId = this.resolveProjectId(perCallProjectId);
171
177
  const envelope = await this.transport.request("GET", `${getProjectPromptPath(projectId, promptId)}/labels`);
172
178
  return unwrap(envelope);
173
179
  }
174
180
  async render(slug, options = {}) {
175
- const fetched = await this.resolveToFetched(slug, options);
176
- return shapePrompt(fetched, options.format ?? this.format, {
181
+ const rendered = await this.resolveToRendered(slug, options);
182
+ return shapePrompt(rendered, options.format ?? this.format, {
177
183
  onUnknownRole: options.onUnknownRole ?? this.onUnknownRole
178
184
  });
179
185
  }
180
- async resolveToFetched(slug, options = {}) {
186
+ async resolveToRendered(slug, options = {}) {
181
187
  if (this.projectId === undefined || this.projectId.trim() === "") {
182
188
  throw new Error("[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).");
183
189
  }
@@ -189,10 +195,10 @@ class PromptResource {
189
195
  throw new Error(`[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`);
190
196
  }
191
197
  const { subject, variables, cache: useCache = true, fallback } = options;
192
- const cacheKey = buildFetchCacheKey(this.projectId, slug, env, subject, variables);
198
+ const cacheKey = buildRenderCacheKey(this.projectId, slug, env, subject, variables);
193
199
  if (useCache) {
194
200
  const cached = this.cache.get(cacheKey);
195
- if (cached && "messages" in cached)
201
+ if (cached)
196
202
  return structuredClone(cached);
197
203
  }
198
204
  const body = { slug, labels: [env] };
@@ -206,7 +212,7 @@ class PromptResource {
206
212
  decode: (data) => zResolvedPromptEnvelope.parse(data)
207
213
  });
208
214
  const raw = unwrap(envelope);
209
- const result = normalizeResolvedEnvelope(raw);
215
+ const result = normalizeResolvedEnvelope(raw, slug);
210
216
  const isWeighted = raw.strategy === "weighted" || result.variantName !== undefined;
211
217
  const cacheable = !(isWeighted && subject === undefined);
212
218
  if (useCache && cacheable) {
@@ -215,61 +221,33 @@ class PromptResource {
215
221
  return result;
216
222
  } catch (err) {
217
223
  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;
224
+ return liftFallbackToRendered(fallback, slug);
251
225
  }
252
226
  throw err;
253
227
  }
254
228
  }
255
- async listProjectLabels(input) {
256
- const { projectId, includeArchived } = input;
229
+ async listProjectLabels(input = {}) {
230
+ const { projectId: perCallProjectId, includeArchived } = input;
231
+ const projectId = this.resolveProjectId(perCallProjectId);
257
232
  const envelope = await this.transport.request("GET", getProjectPromptLabelsPath(projectId), {
258
233
  query: includeArchived ? { includeArchived: "true" } : undefined
259
234
  });
260
235
  return unwrap(envelope);
261
236
  }
262
237
  async createProjectLabel(input) {
263
- const { projectId, ...body } = input;
238
+ const { projectId: perCallProjectId, ...body } = input;
239
+ const projectId = this.resolveProjectId(perCallProjectId);
264
240
  const envelope = await this.transport.request("POST", getProjectPromptLabelsPath(projectId), { body });
265
241
  return unwrap(envelope);
266
242
  }
267
243
  async getProjectLabel(input) {
268
- const envelope = await this.transport.request("GET", getProjectPromptLabelPath(input.projectId, input.label));
244
+ const projectId = this.resolveProjectId(input.projectId);
245
+ const envelope = await this.transport.request("GET", getProjectPromptLabelPath(projectId, input.label));
269
246
  return unwrap(envelope);
270
247
  }
271
248
  async updateProjectLabel(input) {
272
- const { projectId, label, ...rest } = input;
249
+ const { projectId: perCallProjectId, label, ...rest } = input;
250
+ const projectId = this.resolveProjectId(perCallProjectId);
273
251
  const body = {};
274
252
  if (rest.isDefault !== undefined)
275
253
  body.isDefault = rest.isDefault;
@@ -281,15 +259,16 @@ class PromptResource {
281
259
  return unwrap(envelope);
282
260
  }
283
261
  async deleteProjectLabel(input) {
284
- const envelope = await this.transport.request("DELETE", getProjectPromptLabelPath(input.projectId, input.label));
262
+ const projectId = this.resolveProjectId(input.projectId);
263
+ const envelope = await this.transport.request("DELETE", getProjectPromptLabelPath(projectId, input.label));
285
264
  return unwrap(envelope);
286
265
  }
287
266
  async listProjectLabelUsages(input) {
288
- const envelope = await this.transport.request("GET", `${getProjectPromptLabelPath(input.projectId, input.label)}/usages`);
267
+ const envelope = await this.transport.request("GET", `${getProjectPromptLabelPath(this.resolveProjectId(input.projectId), input.label)}/usages`);
289
268
  return unwrap(envelope);
290
269
  }
291
270
  async listLabelEvents(input) {
292
- const projectSeg = encodeURIComponent(input.projectId);
271
+ const projectSeg = encodeURIComponent(this.resolveProjectId(input.projectId));
293
272
  let path;
294
273
  switch (input.scope) {
295
274
  case "label":
@@ -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
  /**
@@ -51,7 +50,7 @@ export interface PaginatedResult<T> {
51
50
  pagination: PaginationInfo;
52
51
  }
53
52
  export interface CreatePromptInput {
54
- projectId: string;
53
+ projectId?: string;
55
54
  name: string;
56
55
  slug: string;
57
56
  description?: string;
@@ -59,7 +58,7 @@ export interface CreatePromptInput {
59
58
  }
60
59
  export type CreatePromptResult = Prompt;
61
60
  export interface ListPromptsInput {
62
- projectId: string;
61
+ projectId?: string;
63
62
  status?: PromptStatus;
64
63
  sortBy?: "createdAt" | "updatedAt" | "name";
65
64
  tags?: string;
@@ -68,12 +67,12 @@ export interface ListPromptsInput {
68
67
  }
69
68
  export type ListPromptsResult = Prompt;
70
69
  export interface GetPromptInput {
71
- projectId: string;
70
+ projectId?: string;
72
71
  promptId: string;
73
72
  }
74
73
  export type GetPromptResult = Prompt;
75
74
  export interface UpdatePromptInput {
76
- projectId: string;
75
+ projectId?: string;
77
76
  promptId: string;
78
77
  name?: string;
79
78
  description?: string | null;
@@ -81,12 +80,12 @@ export interface UpdatePromptInput {
81
80
  }
82
81
  export type UpdatePromptResult = Prompt;
83
82
  export interface ArchivePromptInput {
84
- projectId: string;
83
+ projectId?: string;
85
84
  promptId: string;
86
85
  }
87
86
  export type ArchivePromptResult = Prompt;
88
87
  export interface CreatePromptVersionInput {
89
- projectId: string;
88
+ projectId?: string;
90
89
  promptId: string;
91
90
  format: PromptFormat;
92
91
  templateJson: unknown;
@@ -96,18 +95,18 @@ export interface CreatePromptVersionInput {
96
95
  }
97
96
  export type CreatePromptVersionResult = PromptVersion;
98
97
  export interface ListPromptVersionsInput {
99
- projectId: string;
98
+ projectId?: string;
100
99
  promptId: string;
101
100
  }
102
101
  export type ListPromptVersionsResult = PromptVersion[];
103
102
  export interface GetPromptVersionInput {
104
- projectId: string;
103
+ projectId?: string;
105
104
  promptId: string;
106
105
  versionNumber: number;
107
106
  }
108
107
  export type GetPromptVersionResult = PromptVersion;
109
108
  export interface MovePromptLabelInput {
110
- projectId: string;
109
+ projectId?: string;
111
110
  promptId: string;
112
111
  label: string;
113
112
  versionId: string;
@@ -115,24 +114,13 @@ export interface MovePromptLabelInput {
115
114
  }
116
115
  export type MovePromptLabelResult = PromptLabelPointer;
117
116
  export interface ListPromptLabelsInput {
118
- projectId: string;
117
+ projectId?: string;
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
  /**
@@ -197,13 +192,13 @@ export type ProjectPromptLabel = GenProjectPromptLabel;
197
192
  export type ProjectPromptLabelUsage = GenProjectPromptLabelUsage;
198
193
  export type PromptLabelEvent = GenPromptLabelEvent;
199
194
  export interface ListProjectPromptLabelsInput {
200
- projectId: string;
195
+ projectId?: string;
201
196
  /** Set true to include archived (soft-deleted) entries. Default: false. */
202
197
  includeArchived?: boolean;
203
198
  }
204
199
  export type ListProjectPromptLabelsResult = ProjectPromptLabel[];
205
200
  export interface CreateProjectPromptLabelInput {
206
- projectId: string;
201
+ projectId?: string;
207
202
  label: string;
208
203
  isDefault?: boolean;
209
204
  description?: string;
@@ -211,12 +206,12 @@ export interface CreateProjectPromptLabelInput {
211
206
  }
212
207
  export type CreateProjectPromptLabelResult = ProjectPromptLabel;
213
208
  export interface GetProjectPromptLabelInput {
214
- projectId: string;
209
+ projectId?: string;
215
210
  label: string;
216
211
  }
217
212
  export type GetProjectPromptLabelResult = ProjectPromptLabel;
218
213
  export interface UpdateProjectPromptLabelInput {
219
- projectId: string;
214
+ projectId?: string;
220
215
  label: string;
221
216
  isDefault?: boolean;
222
217
  description?: string | null;
@@ -224,18 +219,18 @@ export interface UpdateProjectPromptLabelInput {
224
219
  }
225
220
  export type UpdateProjectPromptLabelResult = ProjectPromptLabel;
226
221
  export interface DeleteProjectPromptLabelInput {
227
- projectId: string;
222
+ projectId?: string;
228
223
  label: string;
229
224
  }
230
225
  export type DeleteProjectPromptLabelResult = ProjectPromptLabel;
231
226
  export interface ListProjectPromptLabelUsagesInput {
232
- projectId: string;
227
+ projectId?: string;
233
228
  label: string;
234
229
  }
235
230
  export type ListProjectPromptLabelUsagesResult = ProjectPromptLabelUsage[];
236
231
  export type ListLabelEventsInput = {
237
232
  scope: "label";
238
- projectId: string;
233
+ projectId?: string;
239
234
  promptId: string;
240
235
  label: string;
241
236
  page?: number;
@@ -244,7 +239,7 @@ export type ListLabelEventsInput = {
244
239
  until?: string;
245
240
  } | {
246
241
  scope: "prompt";
247
- projectId: string;
242
+ projectId?: string;
248
243
  promptId: string;
249
244
  label?: string;
250
245
  page?: number;
@@ -253,7 +248,7 @@ export type ListLabelEventsInput = {
253
248
  until?: string;
254
249
  } | {
255
250
  scope: "project";
256
- projectId: string;
251
+ projectId?: string;
257
252
  promptId?: string;
258
253
  label?: string;
259
254
  page?: number;
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.3",
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
  /**
@@ -246,6 +245,12 @@ export interface To11aiClient<F extends RenderFormat | undefined = undefined> {
246
245
  readonly apiKey: string | undefined;
247
246
  /** Resolved data-plane gateway URL (option → `TO11_GATEWAY_URL` → default). */
248
247
  readonly gatewayUrl: string;
248
+ /**
249
+ * Resolved project id (option → `TO11_PROJECT_ID`); `undefined` when none is
250
+ * configured. Read-only, for your own logging / manual headers — `prompts.*`
251
+ * already default to it, so you don't need to pass it back into calls.
252
+ */
253
+ readonly projectId: string | undefined;
249
254
  /** A new session (unit of work). Pass an id to group telemetry by your own id. */
250
255
  session(id?: string): Session;
251
256
  /**
@@ -376,7 +381,12 @@ export function createClient<
376
381
  ? options.apiKey
377
382
  : pick(options.apiKey, "TO11_API_KEY");
378
383
  const projectId = pick(options.projectId, "TO11_PROJECT_ID");
379
- const env = options.env;
384
+ // Default serving env: explicit option → `TO11_ENV` env var — the same
385
+ // `pick(option, "TO11_*")` resolution `apiKey`/`projectId` use (empty option
386
+ // or empty env var trims to absent). An explicit empty option is rejected by
387
+ // the dedicated guard below (mirroring `apiKey`/`projectId`); a non-empty but
388
+ // malformed value fails the `ENV_SLUG_REGEX` check.
389
+ const env = pick(options.env, "TO11_ENV");
380
390
  // Provider (BYOK) key — option only, no env fallback (which provider's env
381
391
  // var is ambiguous). Empty trims to absent so it falls back to the placeholder.
382
392
  const providerApiKey = options.providerApiKey?.trim() || undefined;
@@ -398,6 +408,11 @@ export function createClient<
398
408
  "[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.",
399
409
  );
400
410
  }
411
+ if (options.env !== undefined && options.env.trim() === "") {
412
+ throw new Error(
413
+ "[to11ai/sdk] `env` must not be an empty string — omit it or pass a valid env slug.",
414
+ );
415
+ }
401
416
 
402
417
  // Validate the init-time `env` against the project-environments slug rule so
403
418
  // a malformed env fails locally rather than round-tripping to ENV_NOT_FOUND.
@@ -520,6 +535,7 @@ export function createClient<
520
535
  behaviorStore.resolve(key, codedDefault),
521
536
  apiKey,
522
537
  gatewayUrl,
538
+ projectId,
523
539
  session: (id) => new Session(gatewayAuthContext(), id),
524
540
  conversation: (id) => clientDefaultSession().conversation(id),
525
541
  turn: () => clientDefaultSession().turn(),
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