@to11ai/sdk 1.1.0 → 2.0.0-rc.10
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/README.md +99 -258
- package/dist/client.cjs +91 -45
- package/dist/client.d.ts +149 -61
- package/dist/client.js +95 -55
- package/dist/config.d.ts +9 -0
- package/dist/context.d.ts +124 -0
- package/dist/errors.cjs +36 -24
- package/dist/errors.d.ts +35 -46
- package/dist/errors.js +36 -24
- package/dist/gateway.cjs +85 -31
- package/dist/gateway.d.ts +102 -27
- package/dist/gateway.js +85 -31
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/types.gen.d.ts +2802 -502
- package/dist/generated/zod.gen.d.ts +1018 -243
- package/dist/prompts/resource.cjs +80 -113
- package/dist/prompts/resource.d.ts +60 -39
- package/dist/prompts/resource.js +84 -113
- package/dist/prompts/shape.d.ts +56 -0
- package/dist/prompts/types.d.ts +76 -61
- package/dist/transport.d.ts +2 -6
- package/package.json +5 -11
- package/src/client.ts +332 -157
- package/src/config.ts +13 -0
- package/src/context.ts +196 -0
- package/src/errors.ts +65 -79
- package/src/gateway.ts +212 -67
- package/src/generated/index.ts +1 -1
- package/src/generated/types.gen.ts +2977 -611
- package/src/generated/zod.gen.ts +1029 -338
- package/src/prompts/resource.ts +211 -226
- package/src/prompts/shape.ts +152 -0
- package/src/prompts/types.ts +77 -65
- package/src/transport.ts +53 -40
- package/dist/prompts/cache.d.ts +0 -59
- package/dist/routingRules/resource.cjs +0 -138
- package/dist/routingRules/resource.js +0 -98
- package/src/prompts/cache.ts +0 -157
package/src/prompts/resource.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
MissingLabelError,
|
|
3
|
+
To11aiApiError,
|
|
4
|
+
To11aiNetworkError,
|
|
5
|
+
} from "../errors";
|
|
3
6
|
import { zResolvedPrompt } from "../generated/zod.gen";
|
|
4
7
|
import type { ApiEnvelope, Transport } from "../transport";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
8
|
+
import type {
|
|
9
|
+
AnthropicRenderedPrompt,
|
|
10
|
+
OpenAiRenderedPrompt,
|
|
11
|
+
RenderResult,
|
|
12
|
+
} from "./shape";
|
|
13
|
+
import { shapePrompt } from "./shape";
|
|
7
14
|
import type {
|
|
8
15
|
ArchivePromptInput,
|
|
9
16
|
ArchivePromptResult,
|
|
@@ -15,8 +22,6 @@ import type {
|
|
|
15
22
|
CreatePromptVersionResult,
|
|
16
23
|
DeleteProjectPromptLabelInput,
|
|
17
24
|
DeleteProjectPromptLabelResult,
|
|
18
|
-
FetchedPrompt,
|
|
19
|
-
FetchPromptOptions,
|
|
20
25
|
GetProjectPromptLabelInput,
|
|
21
26
|
GetProjectPromptLabelResult,
|
|
22
27
|
GetPromptInput,
|
|
@@ -40,10 +45,11 @@ import type {
|
|
|
40
45
|
PaginatedResult,
|
|
41
46
|
RenderedChatMessage,
|
|
42
47
|
RenderedPrompt,
|
|
48
|
+
RenderFormat,
|
|
49
|
+
RenderOptions,
|
|
43
50
|
ResolvedPrompt,
|
|
44
51
|
ResolvedPromptEnvelope,
|
|
45
|
-
|
|
46
|
-
ResolvePromptResult,
|
|
52
|
+
UnknownRoleMode,
|
|
47
53
|
UpdateProjectPromptLabelInput,
|
|
48
54
|
UpdateProjectPromptLabelResult,
|
|
49
55
|
UpdatePromptInput,
|
|
@@ -51,13 +57,12 @@ import type {
|
|
|
51
57
|
} from "./types";
|
|
52
58
|
import { ENV_SLUG_REGEX } from "./types";
|
|
53
59
|
|
|
54
|
-
// Re-export CacheConfig so callers can import from this module
|
|
55
|
-
export type { CacheConfig } from "./cache";
|
|
56
60
|
export type {
|
|
57
61
|
ArchivePromptInput,
|
|
58
62
|
ArchivePromptResult,
|
|
59
63
|
BlockRenderRecord,
|
|
60
64
|
ChatMessage,
|
|
65
|
+
ChatRole,
|
|
61
66
|
CreateProjectPromptLabelInput,
|
|
62
67
|
CreateProjectPromptLabelResult,
|
|
63
68
|
CreatePromptInput,
|
|
@@ -66,8 +71,6 @@ export type {
|
|
|
66
71
|
CreatePromptVersionResult,
|
|
67
72
|
DeleteProjectPromptLabelInput,
|
|
68
73
|
DeleteProjectPromptLabelResult,
|
|
69
|
-
FetchedPrompt,
|
|
70
|
-
FetchPromptOptions,
|
|
71
74
|
GetProjectPromptLabelInput,
|
|
72
75
|
GetProjectPromptLabelResult,
|
|
73
76
|
GetPromptInput,
|
|
@@ -101,35 +104,21 @@ export type {
|
|
|
101
104
|
PromptVersion,
|
|
102
105
|
RenderedChatMessage,
|
|
103
106
|
RenderedPrompt,
|
|
107
|
+
RenderFormat,
|
|
108
|
+
RenderOptions,
|
|
104
109
|
ResolvedPrompt,
|
|
105
110
|
ResolvedPromptEnvelope,
|
|
106
111
|
ResolvedToolCall,
|
|
107
112
|
ResolvedToolDefinition,
|
|
108
|
-
ResolvePromptInput,
|
|
109
|
-
ResolvePromptResult,
|
|
110
113
|
RowError,
|
|
111
|
-
|
|
112
|
-
TextChatRole,
|
|
114
|
+
UnknownRoleMode,
|
|
113
115
|
UpdateProjectPromptLabelInput,
|
|
114
116
|
UpdateProjectPromptLabelResult,
|
|
115
117
|
UpdatePromptInput,
|
|
116
118
|
UpdatePromptResult,
|
|
117
119
|
} from "./types";
|
|
118
120
|
|
|
119
|
-
const
|
|
120
|
-
promptId: true,
|
|
121
|
-
versionId: true,
|
|
122
|
-
version: true,
|
|
123
|
-
labels: true,
|
|
124
|
-
contentHash: true,
|
|
125
|
-
rendered: true,
|
|
126
|
-
tools: true,
|
|
127
|
-
toolChoice: true,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const zResolvedPromptEnvelope = zResolvedPrompt
|
|
131
|
-
.partial({ releaseId: true })
|
|
132
|
-
.extend({ strategy: z.enum(["single", "weighted"]).optional() });
|
|
121
|
+
const zResolvedPromptEnvelope = zResolvedPrompt.partial({ releaseId: true });
|
|
133
122
|
|
|
134
123
|
function unwrap<T>(envelope: ApiEnvelope<T>): T {
|
|
135
124
|
if (envelope.data === null || envelope.data === undefined) {
|
|
@@ -143,22 +132,28 @@ function getProjectPromptsPath(projectId: string): string {
|
|
|
143
132
|
}
|
|
144
133
|
|
|
145
134
|
/**
|
|
146
|
-
* Project a server `rendered` payload into the `messages
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
135
|
+
* Project a server `rendered` payload into the enriched result's `messages`.
|
|
136
|
+
*
|
|
137
|
+
* Prompts resolve as chat blocks. The deprecated `text` format is no longer a
|
|
138
|
+
* first-class case: a non-chat payload throws rather than being silently
|
|
139
|
+
* coerced (a text prompt is sugar for a single-block chat and is being removed
|
|
140
|
+
* from the API).
|
|
150
141
|
*/
|
|
151
|
-
function renderedToMessages(
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
function renderedToMessages(
|
|
143
|
+
rendered: ResolvedPrompt["rendered"],
|
|
144
|
+
): RenderedChatMessage[] {
|
|
145
|
+
if (rendered.format !== "chat") {
|
|
146
|
+
throw new Error(
|
|
147
|
+
"[to11ai/sdk] Unsupported non-chat prompt format; author the prompt as chat blocks.",
|
|
148
|
+
);
|
|
154
149
|
}
|
|
155
|
-
return
|
|
150
|
+
return rendered.messages;
|
|
156
151
|
}
|
|
157
152
|
|
|
158
153
|
/**
|
|
159
|
-
* Normalize the server resolve envelope into the enriched
|
|
154
|
+
* Normalize the server resolve envelope into the enriched `RenderedPrompt`.
|
|
160
155
|
*
|
|
161
|
-
* - `messages` is
|
|
156
|
+
* - `messages` is derived from the `rendered` chat payload.
|
|
162
157
|
* - `releaseId` is ALWAYS a key: a string when the server sends one, else
|
|
163
158
|
* explicit `null` (R5 `string | null`).
|
|
164
159
|
* - `variantName` is present IFF the server sent one (i.e. weighted release);
|
|
@@ -167,9 +162,10 @@ function renderedToMessages(rendered: RenderedPrompt): RenderedChatMessage[] {
|
|
|
167
162
|
*/
|
|
168
163
|
function normalizeResolvedEnvelope(
|
|
169
164
|
raw: ResolvedPrompt | ResolvedPromptEnvelope,
|
|
170
|
-
|
|
165
|
+
slug: string,
|
|
166
|
+
): RenderedPrompt {
|
|
171
167
|
const provenance = raw as ResolvedPromptEnvelope;
|
|
172
|
-
const result:
|
|
168
|
+
const result: RenderedPrompt = {
|
|
173
169
|
messages: renderedToMessages(raw.rendered),
|
|
174
170
|
version: raw.version,
|
|
175
171
|
releaseId: provenance.releaseId ?? null,
|
|
@@ -177,8 +173,12 @@ function normalizeResolvedEnvelope(
|
|
|
177
173
|
promptId: raw.promptId,
|
|
178
174
|
versionId: raw.versionId,
|
|
179
175
|
contentHash: raw.contentHash,
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
label: raw.label,
|
|
177
|
+
// The requested slug — the API resolve envelope does not echo it, so it
|
|
178
|
+
// is threaded from the `render()` input (TO11-2727). Surfaced as
|
|
179
|
+
// `x-to11-prompt-slug` for trace readability.
|
|
180
|
+
slug,
|
|
181
|
+
modelConfig: provenance.modelConfig,
|
|
182
182
|
};
|
|
183
183
|
// `tools` is present IFF the resolved version declares tool definitions; the
|
|
184
184
|
// wire may send `null` (or omit it) — normalize both to absent so callers
|
|
@@ -208,20 +208,26 @@ function normalizeResolvedEnvelope(
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* Lift a caller-supplied offline `fallback` into the
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* own value or explicit `null`; `blockRenderRecord` → `{}`; `variantName`
|
|
216
|
-
* stays absent).
|
|
211
|
+
* Lift a caller-supplied offline `fallback` into the returned shape (D7), so
|
|
212
|
+
* `render()` returns an identical shape on the online and offline paths. The
|
|
213
|
+
* fallback's own slug is preferred, falling back to the `render()` input when it
|
|
214
|
+
* carries none, so `x-to11-prompt-slug` is still emitted.
|
|
217
215
|
*/
|
|
218
|
-
function
|
|
219
|
-
fallback:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
216
|
+
function liftFallbackToRendered(
|
|
217
|
+
fallback: RenderedPrompt,
|
|
218
|
+
slug: string,
|
|
219
|
+
): RenderedPrompt {
|
|
220
|
+
// Defensive: `fallback` is typed `RenderedPrompt`, but an untyped JS caller,
|
|
221
|
+
// an `as` cast, or a persisted pre-2.0 `resolve()` result could pass the old
|
|
222
|
+
// `ResolvedPrompt` shape (no top-level `messages`). Fail loudly rather than
|
|
223
|
+
// return a malformed result with `undefined` messages.
|
|
224
|
+
if (!Array.isArray((fallback as { messages?: unknown }).messages)) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
"[to11ai/sdk] `fallback` must be a RenderedPrompt (as returned by render()); the legacy resolve() shape is no longer supported.",
|
|
227
|
+
);
|
|
223
228
|
}
|
|
224
|
-
|
|
229
|
+
// `||` (not `??`) so an empty-string slug also falls through to the input.
|
|
230
|
+
return { ...structuredClone(fallback), slug: fallback.slug || slug };
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
function getProjectPromptPath(projectId: string, promptId: string): string {
|
|
@@ -261,40 +267,70 @@ function buildLabelEventsQuery(input: ListLabelEventsInput): string {
|
|
|
261
267
|
}
|
|
262
268
|
|
|
263
269
|
/**
|
|
264
|
-
* Init-time config for the env-aware `
|
|
270
|
+
* Init-time config for the env-aware `render()` surface (TO11-980 / S9).
|
|
265
271
|
*
|
|
266
272
|
* `projectId` and `env` come from `createClient`. `projectId` is required for
|
|
267
|
-
* `
|
|
268
|
-
* serving environment inherited by every `
|
|
273
|
+
* `render()` (it builds the project-scoped resolve path); `env` is the default
|
|
274
|
+
* serving environment inherited by every `render(slug)` call.
|
|
269
275
|
*/
|
|
270
276
|
export interface PromptResourceConfig {
|
|
271
|
-
/** Project the client is bound to. Required for `
|
|
277
|
+
/** Project the client is bound to. Required for `render()`; validated at createClient. */
|
|
272
278
|
projectId?: string;
|
|
273
|
-
/** Default serving env for `
|
|
279
|
+
/** Default serving env for `render()`. Validated against ENV_SLUG_REGEX at createClient. */
|
|
274
280
|
env?: string;
|
|
281
|
+
/** Default provider shape for `render()` output. Per-call override allowed. */
|
|
282
|
+
format?: RenderFormat;
|
|
283
|
+
/**
|
|
284
|
+
* Default unknown-role policy for `render({ format })` output conversion.
|
|
285
|
+
* Per-call override allowed; defaults to `"drop"`.
|
|
286
|
+
*/
|
|
287
|
+
onUnknownRole?: UnknownRoleMode;
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
export class PromptResource {
|
|
290
|
+
export class PromptResource<F extends RenderFormat | undefined = undefined> {
|
|
278
291
|
private readonly transport: Transport;
|
|
279
|
-
private readonly cache: PromptCache;
|
|
280
292
|
private readonly projectId: string | undefined;
|
|
281
293
|
private readonly env: string | undefined;
|
|
294
|
+
private readonly format: RenderFormat | undefined;
|
|
295
|
+
private readonly onUnknownRole: UnknownRoleMode | undefined;
|
|
282
296
|
|
|
283
297
|
constructor(
|
|
284
298
|
transport: Transport,
|
|
285
|
-
|
|
286
|
-
|
|
299
|
+
// `format` is bound to the class generic `F` (not the wider `RenderFormat`)
|
|
300
|
+
// so the constructed default shape cannot disagree with what `render()`'s
|
|
301
|
+
// return type (`RenderResult<F>`) promises.
|
|
302
|
+
config?: Omit<PromptResourceConfig, "format"> & { format?: F },
|
|
287
303
|
) {
|
|
288
304
|
this.transport = transport;
|
|
289
|
-
this.cache = new PromptCache(cacheConfig);
|
|
290
305
|
this.projectId = config?.projectId;
|
|
291
306
|
this.env = config?.env;
|
|
307
|
+
this.format = config?.format;
|
|
308
|
+
this.onUnknownRole = config?.onUnknownRole;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Resolve the project for a control-plane call: the per-call `projectId`
|
|
313
|
+
* wins, else the client-bound one from `createClient({ projectId })`. Throws
|
|
314
|
+
* if neither is set. Mirrors how `render()` resolves its project, so every
|
|
315
|
+
* method honors the bound `projectId` and callers need not repeat it.
|
|
316
|
+
*/
|
|
317
|
+
private resolveProjectId(perCall?: string): string {
|
|
318
|
+
const projectId = perCall ?? this.projectId;
|
|
319
|
+
if (projectId === undefined || projectId.trim() === "") {
|
|
320
|
+
throw new Error(
|
|
321
|
+
"[to11ai/sdk] this call needs a `projectId` — pass it on the call or bind one via createClient({ projectId, ... }).",
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
// Trim so a whitespace-padded per-call id normalizes like the client-bound
|
|
325
|
+
// one (resolved via `pick()`), instead of reaching the wire untrimmed.
|
|
326
|
+
return projectId.trim();
|
|
292
327
|
}
|
|
293
328
|
|
|
294
329
|
// ─── Create ──────────────────────────────────────────────────────────────
|
|
295
330
|
|
|
296
331
|
async create(input: CreatePromptInput): Promise<CreatePromptResult> {
|
|
297
|
-
const { projectId, ...body } = input;
|
|
332
|
+
const { projectId: perCallProjectId, ...body } = input;
|
|
333
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
298
334
|
const envelope = await this.transport.request<CreatePromptResult>(
|
|
299
335
|
"POST",
|
|
300
336
|
getProjectPromptsPath(projectId),
|
|
@@ -306,9 +342,10 @@ export class PromptResource {
|
|
|
306
342
|
// ─── List ─────────────────────────────────────────────────────────────────
|
|
307
343
|
|
|
308
344
|
async list(
|
|
309
|
-
input: ListPromptsInput,
|
|
345
|
+
input: ListPromptsInput = {},
|
|
310
346
|
): Promise<PaginatedResult<ListPromptsResult>> {
|
|
311
|
-
const { projectId, ...queryRest } = input;
|
|
347
|
+
const { projectId: perCallProjectId, ...queryRest } = input;
|
|
348
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
312
349
|
const envelope = await this.transport.request<
|
|
313
350
|
| ListPromptsResult[]
|
|
314
351
|
| {
|
|
@@ -340,7 +377,8 @@ export class PromptResource {
|
|
|
340
377
|
// ─── Get ──────────────────────────────────────────────────────────────────
|
|
341
378
|
|
|
342
379
|
async get(input: GetPromptInput): Promise<GetPromptResult> {
|
|
343
|
-
const { projectId, promptId } = input;
|
|
380
|
+
const { projectId: perCallProjectId, promptId } = input;
|
|
381
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
344
382
|
const envelope = await this.transport.request<GetPromptResult>(
|
|
345
383
|
"GET",
|
|
346
384
|
getProjectPromptPath(projectId, promptId),
|
|
@@ -351,7 +389,8 @@ export class PromptResource {
|
|
|
351
389
|
// ─── Update ───────────────────────────────────────────────────────────────
|
|
352
390
|
|
|
353
391
|
async update(input: UpdatePromptInput): Promise<UpdatePromptResult> {
|
|
354
|
-
const { projectId, promptId, ...body } = input;
|
|
392
|
+
const { projectId: perCallProjectId, promptId, ...body } = input;
|
|
393
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
355
394
|
const envelope = await this.transport.request<UpdatePromptResult>(
|
|
356
395
|
"PATCH",
|
|
357
396
|
getProjectPromptPath(projectId, promptId),
|
|
@@ -363,7 +402,8 @@ export class PromptResource {
|
|
|
363
402
|
// ─── Archive ──────────────────────────────────────────────────────────────
|
|
364
403
|
|
|
365
404
|
async archive(input: ArchivePromptInput): Promise<ArchivePromptResult> {
|
|
366
|
-
const { projectId, promptId } = input;
|
|
405
|
+
const { projectId: perCallProjectId, promptId } = input;
|
|
406
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
367
407
|
const envelope = await this.transport.request<ArchivePromptResult>(
|
|
368
408
|
"DELETE",
|
|
369
409
|
getProjectPromptPath(projectId, promptId),
|
|
@@ -376,7 +416,8 @@ export class PromptResource {
|
|
|
376
416
|
async createVersion(
|
|
377
417
|
input: CreatePromptVersionInput,
|
|
378
418
|
): Promise<CreatePromptVersionResult> {
|
|
379
|
-
const { projectId, promptId, ...body } = input;
|
|
419
|
+
const { projectId: perCallProjectId, promptId, ...body } = input;
|
|
420
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
380
421
|
const envelope = await this.transport.request<CreatePromptVersionResult>(
|
|
381
422
|
"POST",
|
|
382
423
|
`${getProjectPromptPath(projectId, promptId)}/versions`,
|
|
@@ -390,7 +431,8 @@ export class PromptResource {
|
|
|
390
431
|
async listVersions(
|
|
391
432
|
input: ListPromptVersionsInput,
|
|
392
433
|
): Promise<ListPromptVersionsResult> {
|
|
393
|
-
const { projectId, promptId } = input;
|
|
434
|
+
const { projectId: perCallProjectId, promptId } = input;
|
|
435
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
394
436
|
const envelope = await this.transport.request<ListPromptVersionsResult>(
|
|
395
437
|
"GET",
|
|
396
438
|
`${getProjectPromptPath(projectId, promptId)}/versions`,
|
|
@@ -403,7 +445,8 @@ export class PromptResource {
|
|
|
403
445
|
async getVersion(
|
|
404
446
|
input: GetPromptVersionInput,
|
|
405
447
|
): Promise<GetPromptVersionResult> {
|
|
406
|
-
const { projectId, promptId, versionNumber } = input;
|
|
448
|
+
const { projectId: perCallProjectId, promptId, versionNumber } = input;
|
|
449
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
407
450
|
const envelope = await this.transport.request<GetPromptVersionResult>(
|
|
408
451
|
"GET",
|
|
409
452
|
`${getProjectPromptPath(projectId, promptId)}/versions/${encodeURIComponent(String(versionNumber))}`,
|
|
@@ -414,7 +457,8 @@ export class PromptResource {
|
|
|
414
457
|
// ─── Move Label ───────────────────────────────────────────────────────────
|
|
415
458
|
|
|
416
459
|
async moveLabel(input: MovePromptLabelInput): Promise<MovePromptLabelResult> {
|
|
417
|
-
const { projectId, promptId, label, ...body } = input;
|
|
460
|
+
const { projectId: perCallProjectId, promptId, label, ...body } = input;
|
|
461
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
418
462
|
const envelope = await this.transport.request<MovePromptLabelResult>(
|
|
419
463
|
"PUT",
|
|
420
464
|
`${getProjectPromptPath(projectId, promptId)}/labels/${encodeURIComponent(label)}`,
|
|
@@ -428,7 +472,8 @@ export class PromptResource {
|
|
|
428
472
|
async listLabels(
|
|
429
473
|
input: ListPromptLabelsInput,
|
|
430
474
|
): Promise<ListPromptLabelsResult> {
|
|
431
|
-
const { projectId, promptId } = input;
|
|
475
|
+
const { projectId: perCallProjectId, promptId } = input;
|
|
476
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
432
477
|
const envelope = await this.transport.request<ListPromptLabelsResult>(
|
|
433
478
|
"GET",
|
|
434
479
|
`${getProjectPromptPath(projectId, promptId)}/labels`,
|
|
@@ -436,90 +481,94 @@ export class PromptResource {
|
|
|
436
481
|
return unwrap(envelope);
|
|
437
482
|
}
|
|
438
483
|
|
|
439
|
-
// ───
|
|
484
|
+
// ─── Render (env-aware resolution + format shaping, TO11-980 / TO11-2691) ──
|
|
440
485
|
|
|
441
486
|
/**
|
|
442
|
-
* Resolve a prompt for the client's configured
|
|
443
|
-
*
|
|
444
|
-
*
|
|
487
|
+
* Resolve and (optionally) shape a prompt for the client's configured
|
|
488
|
+
* `projectId` + `env`, in one control-plane request. With a `format` (per
|
|
489
|
+
* call or inherited from the client) the result is provider-shaped
|
|
490
|
+
* `{ messages, config, metadata }`; without one it is the neutral
|
|
491
|
+
* `RenderedPrompt`. Resolves by the client's configured `env`.
|
|
445
492
|
*
|
|
446
|
-
* -
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
493
|
+
* @example Render an OpenAI-shaped prompt and send it through the gateway
|
|
494
|
+
* const prompt = await to11.prompts.render("weather-concierge", {
|
|
495
|
+
* variables: { city: "NYC" },
|
|
496
|
+
* });
|
|
497
|
+
* const res = await openai.chat.completions.create(
|
|
498
|
+
* { ...prompt.config, messages: prompt.messages },
|
|
499
|
+
* { headers: to11.turn().headers(prompt) },
|
|
500
|
+
* );
|
|
501
|
+
*/
|
|
502
|
+
async render(
|
|
503
|
+
slug: string,
|
|
504
|
+
options: RenderOptions & { format: "openai" },
|
|
505
|
+
): Promise<OpenAiRenderedPrompt>;
|
|
506
|
+
async render(
|
|
507
|
+
slug: string,
|
|
508
|
+
options: RenderOptions & { format: "anthropic" },
|
|
509
|
+
): Promise<AnthropicRenderedPrompt>;
|
|
510
|
+
async render(slug: string, options?: RenderOptions): Promise<RenderResult<F>>;
|
|
511
|
+
async render(
|
|
512
|
+
slug: string,
|
|
513
|
+
options: RenderOptions = {},
|
|
514
|
+
): Promise<RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt> {
|
|
515
|
+
const rendered = await this.resolveToRendered(slug, options);
|
|
516
|
+
return shapePrompt(rendered, options.format ?? this.format, {
|
|
517
|
+
onUnknownRole: options.onUnknownRole ?? this.onUnknownRole,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Env-aware resolution → neutral `RenderedPrompt`. Carries the resolve /
|
|
523
|
+
* offline-fallback logic; `render()` is the public shaping surface.
|
|
458
524
|
*
|
|
459
|
-
*
|
|
525
|
+
* - **wire mapping (TO11-2743):** posts to the path-addressed render route
|
|
526
|
+
* (`/v1/projects/:projectId/prompts/by-slug/:slug/labels/:label/render`),
|
|
527
|
+
* body `{ variables? }`. The resolved label is the single path label.
|
|
528
|
+
* - **No silent fallback (R4):** if no label is resolvable (none per call
|
|
529
|
+
* AND no client `env` fallback) this throws `MissingLabelError`
|
|
530
|
+
* CLIENT-side, eagerly, before any transport call.
|
|
460
531
|
*/
|
|
461
|
-
async
|
|
532
|
+
private async resolveToRendered(
|
|
462
533
|
slug: string,
|
|
463
|
-
options:
|
|
464
|
-
): Promise<
|
|
534
|
+
options: RenderOptions = {},
|
|
535
|
+
): Promise<RenderedPrompt> {
|
|
465
536
|
if (this.projectId === undefined || this.projectId.trim() === "") {
|
|
466
537
|
throw new Error(
|
|
467
|
-
"[to11ai/sdk] `prompts.
|
|
538
|
+
"[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).",
|
|
468
539
|
);
|
|
469
540
|
}
|
|
470
541
|
|
|
471
|
-
// R2 precedence: per-call
|
|
472
|
-
const
|
|
542
|
+
// R2 precedence: per-call label > client `env` fallback. No third source.
|
|
543
|
+
const label = options.label ?? this.env;
|
|
473
544
|
|
|
474
|
-
// R4 / AC4: no
|
|
475
|
-
if (
|
|
476
|
-
throw
|
|
545
|
+
// R4 / AC4: no label resolvable → throw eagerly, ZERO transport calls.
|
|
546
|
+
if (label === undefined) {
|
|
547
|
+
throw new MissingLabelError();
|
|
477
548
|
}
|
|
478
549
|
|
|
479
|
-
// Validate the RESOLVED
|
|
480
|
-
// init-time env, but PromptResource has a
|
|
481
|
-
// bypass createClient, so re-validate here so
|
|
482
|
-
// "Prod") never reaches the wire
|
|
483
|
-
if (!ENV_SLUG_REGEX.test(
|
|
550
|
+
// Validate the RESOLVED label (per-call OR the client `env` fallback).
|
|
551
|
+
// createClient validates the init-time env, but PromptResource has a
|
|
552
|
+
// public constructor that can bypass createClient, so re-validate here so
|
|
553
|
+
// a malformed label (e.g. "Prod") never reaches the wire (CodeRabbit).
|
|
554
|
+
if (!ENV_SLUG_REGEX.test(label)) {
|
|
484
555
|
throw new Error(
|
|
485
|
-
`[to11ai/sdk] \`
|
|
556
|
+
`[to11ai/sdk] \`label\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(label)}`,
|
|
486
557
|
);
|
|
487
558
|
}
|
|
488
559
|
|
|
489
|
-
const {
|
|
490
|
-
subject,
|
|
491
|
-
variables,
|
|
492
|
-
cache: useCache = true,
|
|
493
|
-
fallback,
|
|
494
|
-
developerRole,
|
|
495
|
-
} = options;
|
|
496
|
-
|
|
497
|
-
const cacheKey = buildFetchCacheKey(
|
|
498
|
-
this.projectId,
|
|
499
|
-
slug,
|
|
500
|
-
env,
|
|
501
|
-
subject,
|
|
502
|
-
variables,
|
|
503
|
-
developerRole,
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
if (useCache) {
|
|
507
|
-
const cached = this.cache.get(cacheKey);
|
|
508
|
-
// Only enriched (fetch) entries are valid hits here; the fetch key
|
|
509
|
-
// namespace ("fetch|…") never collides with the legacy resolve key.
|
|
510
|
-
if (cached && "messages" in cached) return structuredClone(cached);
|
|
511
|
-
}
|
|
560
|
+
const { variables, fallback } = options;
|
|
512
561
|
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
|
|
562
|
+
// TO11-2743: slug + the single resolved label live in the PATH; the body
|
|
563
|
+
// carries only `variables`. Weighted-release bucketing (`subject`) is not
|
|
564
|
+
// supported by the render endpoint yet, so it's dropped from the surface.
|
|
565
|
+
const body: Record<string, unknown> = {};
|
|
516
566
|
if (variables !== undefined) body.variables = variables;
|
|
517
|
-
if (developerRole !== undefined) body.developerRole = developerRole;
|
|
518
567
|
|
|
519
568
|
try {
|
|
520
569
|
const envelope = await this.transport.request<ResolvedPromptEnvelope>(
|
|
521
570
|
"POST",
|
|
522
|
-
`${getProjectPromptsPath(this.projectId)}
|
|
571
|
+
`${getProjectPromptsPath(this.projectId)}/by-slug/${encodeURIComponent(slug)}/labels/${encodeURIComponent(label)}/render`,
|
|
523
572
|
{
|
|
524
573
|
body,
|
|
525
574
|
decode: (data) =>
|
|
@@ -527,88 +576,17 @@ export class PromptResource {
|
|
|
527
576
|
},
|
|
528
577
|
);
|
|
529
578
|
const raw = unwrap(envelope);
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
// D8: bypass the cache for a weighted release with NO subject —
|
|
533
|
-
// caching a uniform-random assignment would pin one variant for the
|
|
534
|
-
// whole TTL. Cache `single` releases and `subject`-keyed weighted
|
|
535
|
-
// releases (deterministic, safe to cache). Weighted-ness is inferred
|
|
536
|
-
// from `strategy === "weighted"` OR the presence of `variantName`
|
|
537
|
-
// (which the server stamps iff weighted) — so a result that carries a
|
|
538
|
-
// variant but omits `strategy` is still correctly bypassed rather than
|
|
539
|
-
// pinning one random variant for the TTL (Cursor / Greptile).
|
|
540
|
-
const isWeighted =
|
|
541
|
-
raw.strategy === "weighted" || result.variantName !== undefined;
|
|
542
|
-
const cacheable = !(isWeighted && subject === undefined);
|
|
543
|
-
if (useCache && cacheable) {
|
|
544
|
-
this.cache.set(cacheKey, structuredClone(result));
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return result;
|
|
548
|
-
} catch (err) {
|
|
549
|
-
// D7: offline fallback is consulted ONLY on a network outage with an
|
|
550
|
-
// empty cache. A server `ENV_NOT_FOUND` is a typed application error
|
|
551
|
-
// (not a To11aiNetworkError), so it bypasses this branch. R4's
|
|
552
|
-
// client-eager throw already happened above the try, so there is no
|
|
553
|
-
// network error to trigger it on the env-missing path.
|
|
554
|
-
if (err instanceof To11aiNetworkError && fallback !== undefined) {
|
|
555
|
-
return liftFallbackToFetched(fallback);
|
|
556
|
-
}
|
|
557
|
-
throw err;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// ─── Resolve ──────────────────────────────────────────────────────────────
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* @deprecated Use {@link PromptResource.fetch | `prompts.fetch(slug,
|
|
565
|
-
* options?)`} instead. `resolve()` is env-agnostic (no `env` / `subject`,
|
|
566
|
-
* no enriched release/variant provenance, no typed-env errors)
|
|
567
|
-
* and is retained only for back-compat. It is NOT subject to the R4
|
|
568
|
-
* no-silent-fallback env gate (it has no env concept). New code should
|
|
569
|
-
* configure `projectId` + `env` at `createClient` and call `fetch()`.
|
|
570
|
-
*/
|
|
571
|
-
async resolve(input: ResolvePromptInput): Promise<ResolvePromptResult> {
|
|
572
|
-
const {
|
|
573
|
-
projectId,
|
|
574
|
-
slug,
|
|
575
|
-
labels,
|
|
576
|
-
variables,
|
|
577
|
-
cache: useCache = true,
|
|
578
|
-
fallback,
|
|
579
|
-
} = input;
|
|
580
|
-
|
|
581
|
-
const cacheKey = buildCacheKey(projectId, slug, labels, variables);
|
|
582
|
-
|
|
583
|
-
if (useCache) {
|
|
584
|
-
const cached = this.cache.get(cacheKey);
|
|
585
|
-
// The legacy resolve key namespace only ever stores ResolvedPrompt
|
|
586
|
-
// entries (the fetch namespace is "fetch|…"); the `messages` guard
|
|
587
|
-
// keeps the static return type as ResolvedPrompt.
|
|
588
|
-
if (cached && !("messages" in cached)) return structuredClone(cached);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
try {
|
|
592
|
-
const envelope = await this.transport.request<ResolvePromptResult>(
|
|
593
|
-
"POST",
|
|
594
|
-
`${getProjectPromptsPath(projectId)}:resolve`,
|
|
595
|
-
{
|
|
596
|
-
body: { slug, labels, variables },
|
|
597
|
-
decode: (data) =>
|
|
598
|
-
zResolvedPromptLegacy.parse(data) as ResolvePromptResult,
|
|
599
|
-
},
|
|
600
|
-
);
|
|
601
|
-
const result = unwrap(envelope);
|
|
602
|
-
|
|
603
|
-
if (useCache) {
|
|
604
|
-
this.cache.set(cacheKey, structuredClone(result));
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
return result;
|
|
579
|
+
return normalizeResolvedEnvelope(raw, slug);
|
|
608
580
|
} catch (err) {
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
581
|
+
// Serve the offline fallback on a TRANSIENT outage — a network error
|
|
582
|
+
// or a 5xx from the control plane. A typed 4xx (not-found / policy /
|
|
583
|
+
// validation) is a real application error and is NOT masked. The
|
|
584
|
+
// client-eager MissingLabelError already threw above the try.
|
|
585
|
+
const transient =
|
|
586
|
+
err instanceof To11aiNetworkError ||
|
|
587
|
+
(err instanceof To11aiApiError && err.statusCode >= 500);
|
|
588
|
+
if (transient && fallback !== undefined) {
|
|
589
|
+
return liftFallbackToRendered(fallback, slug);
|
|
612
590
|
}
|
|
613
591
|
throw err;
|
|
614
592
|
}
|
|
@@ -624,9 +602,10 @@ export class PromptResource {
|
|
|
624
602
|
* affordances). Gated on `prompt:read` server-side.
|
|
625
603
|
*/
|
|
626
604
|
async listProjectLabels(
|
|
627
|
-
input: ListProjectPromptLabelsInput,
|
|
605
|
+
input: ListProjectPromptLabelsInput = {},
|
|
628
606
|
): Promise<ListProjectPromptLabelsResult> {
|
|
629
|
-
const { projectId, includeArchived } = input;
|
|
607
|
+
const { projectId: perCallProjectId, includeArchived } = input;
|
|
608
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
630
609
|
const envelope =
|
|
631
610
|
await this.transport.request<ListProjectPromptLabelsResult>(
|
|
632
611
|
"GET",
|
|
@@ -648,7 +627,8 @@ export class PromptResource {
|
|
|
648
627
|
async createProjectLabel(
|
|
649
628
|
input: CreateProjectPromptLabelInput,
|
|
650
629
|
): Promise<CreateProjectPromptLabelResult> {
|
|
651
|
-
const { projectId, ...body } = input;
|
|
630
|
+
const { projectId: perCallProjectId, ...body } = input;
|
|
631
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
652
632
|
const envelope =
|
|
653
633
|
await this.transport.request<CreateProjectPromptLabelResult>(
|
|
654
634
|
"POST",
|
|
@@ -661,9 +641,10 @@ export class PromptResource {
|
|
|
661
641
|
async getProjectLabel(
|
|
662
642
|
input: GetProjectPromptLabelInput,
|
|
663
643
|
): Promise<GetProjectPromptLabelResult> {
|
|
644
|
+
const projectId = this.resolveProjectId(input.projectId);
|
|
664
645
|
const envelope = await this.transport.request<GetProjectPromptLabelResult>(
|
|
665
646
|
"GET",
|
|
666
|
-
getProjectPromptLabelPath(
|
|
647
|
+
getProjectPromptLabelPath(projectId, input.label),
|
|
667
648
|
);
|
|
668
649
|
return unwrap(envelope);
|
|
669
650
|
}
|
|
@@ -679,7 +660,8 @@ export class PromptResource {
|
|
|
679
660
|
async updateProjectLabel(
|
|
680
661
|
input: UpdateProjectPromptLabelInput,
|
|
681
662
|
): Promise<UpdateProjectPromptLabelResult> {
|
|
682
|
-
const { projectId, label, ...rest } = input;
|
|
663
|
+
const { projectId: perCallProjectId, label, ...rest } = input;
|
|
664
|
+
const projectId = this.resolveProjectId(perCallProjectId);
|
|
683
665
|
const body: Record<string, unknown> = {};
|
|
684
666
|
if (rest.isDefault !== undefined) body.isDefault = rest.isDefault;
|
|
685
667
|
if (rest.description !== undefined) body.description = rest.description;
|
|
@@ -705,10 +687,11 @@ export class PromptResource {
|
|
|
705
687
|
async deleteProjectLabel(
|
|
706
688
|
input: DeleteProjectPromptLabelInput,
|
|
707
689
|
): Promise<DeleteProjectPromptLabelResult> {
|
|
690
|
+
const projectId = this.resolveProjectId(input.projectId);
|
|
708
691
|
const envelope =
|
|
709
692
|
await this.transport.request<DeleteProjectPromptLabelResult>(
|
|
710
693
|
"DELETE",
|
|
711
|
-
getProjectPromptLabelPath(
|
|
694
|
+
getProjectPromptLabelPath(projectId, input.label),
|
|
712
695
|
);
|
|
713
696
|
return unwrap(envelope);
|
|
714
697
|
}
|
|
@@ -727,7 +710,7 @@ export class PromptResource {
|
|
|
727
710
|
const envelope =
|
|
728
711
|
await this.transport.request<ListProjectPromptLabelUsagesResult>(
|
|
729
712
|
"GET",
|
|
730
|
-
`${getProjectPromptLabelPath(input.projectId, input.label)}/usages`,
|
|
713
|
+
`${getProjectPromptLabelPath(this.resolveProjectId(input.projectId), input.label)}/usages`,
|
|
731
714
|
);
|
|
732
715
|
return unwrap(envelope);
|
|
733
716
|
}
|
|
@@ -752,7 +735,9 @@ export class PromptResource {
|
|
|
752
735
|
async listLabelEvents(
|
|
753
736
|
input: ListLabelEventsInput,
|
|
754
737
|
): Promise<ListLabelEventsResult> {
|
|
755
|
-
const projectSeg = encodeURIComponent(
|
|
738
|
+
const projectSeg = encodeURIComponent(
|
|
739
|
+
this.resolveProjectId(input.projectId),
|
|
740
|
+
);
|
|
756
741
|
let path: string;
|
|
757
742
|
switch (input.scope) {
|
|
758
743
|
case "label":
|