@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.
- package/dist/client.cjs +88 -18
- package/dist/client.d.ts +141 -39
- package/dist/client.js +94 -19
- package/dist/config.d.ts +9 -0
- package/dist/context.d.ts +124 -0
- package/dist/errors.cjs +18 -1
- package/dist/errors.d.ts +18 -1
- package/dist/errors.js +18 -1
- package/dist/gateway.cjs +65 -22
- package/dist/gateway.d.ts +65 -9
- package/dist/gateway.js +65 -22
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/types.gen.d.ts +1299 -167
- package/dist/generated/zod.gen.d.ts +393 -232
- package/dist/prompts/cache.d.ts +9 -10
- package/dist/prompts/resource.cjs +29 -67
- package/dist/prompts/resource.d.ts +52 -32
- package/dist/prompts/resource.js +30 -68
- package/dist/prompts/shape.d.ts +56 -0
- package/dist/prompts/types.d.ts +44 -31
- package/package.json +1 -1
- package/src/client.ts +337 -82
- package/src/config.ts +13 -0
- package/src/context.ts +198 -0
- package/src/errors.ts +33 -3
- package/src/gateway.ts +148 -33
- package/src/generated/index.ts +1 -1
- package/src/generated/types.gen.ts +1310 -167
- package/src/generated/zod.gen.ts +361 -271
- package/src/prompts/cache.ts +11 -15
- package/src/prompts/resource.ts +129 -142
- package/src/prompts/shape.ts +152 -0
- package/src/prompts/types.ts +45 -34
package/src/prompts/cache.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RenderedPrompt } from "./types";
|
|
2
2
|
|
|
3
3
|
export interface CacheConfig {
|
|
4
4
|
/** Time-to-live in seconds. Default: 300 (5 minutes) */
|
|
@@ -8,18 +8,17 @@ export interface CacheConfig {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* The cache stores
|
|
12
|
-
* `fetch()` result. Both are JSON-cloneable; the cache is shape-agnostic.
|
|
11
|
+
* The cache stores enriched `render()` results, which are JSON-cloneable.
|
|
13
12
|
*/
|
|
14
|
-
export type
|
|
13
|
+
export type CacheableRendered = RenderedPrompt;
|
|
15
14
|
|
|
16
15
|
interface CacheEntry {
|
|
17
|
-
value:
|
|
16
|
+
value: CacheableRendered;
|
|
18
17
|
expiresAt: number;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
|
-
* Build a canonical cache key
|
|
21
|
+
* Build a canonical base cache key from project/slug/labels/variables.
|
|
23
22
|
*
|
|
24
23
|
* - projectId is included to prevent cross-project cache collisions: a single
|
|
25
24
|
* PromptResource can serve multiple projects, so the same slug+labels must
|
|
@@ -44,7 +43,7 @@ export function buildCacheKey(
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
|
-
* Build a cache key for an env-aware `
|
|
46
|
+
* Build a cache key for an env-aware `render()` call.
|
|
48
47
|
*
|
|
49
48
|
* Folds in `env` and `subject` ON TOP of the base
|
|
50
49
|
* project/slug/labels/variables key so two calls that differ only by
|
|
@@ -58,18 +57,15 @@ export function buildCacheKey(
|
|
|
58
57
|
* caches as the same deterministic variant on repeat (D8); the
|
|
59
58
|
* weighted-no-subject bypass is handled in the resource, not here.
|
|
60
59
|
*/
|
|
61
|
-
export function
|
|
60
|
+
export function buildRenderCacheKey(
|
|
62
61
|
projectId: string,
|
|
63
62
|
slug: string,
|
|
64
63
|
env: string,
|
|
65
64
|
subject?: string,
|
|
66
65
|
variables?: Record<string, unknown>,
|
|
67
|
-
developerRole?: string,
|
|
68
66
|
): string {
|
|
69
67
|
const base = buildCacheKey(projectId, slug, [env], variables);
|
|
70
|
-
|
|
71
|
-
// `"user"` (the documented default) share one entry, not two.
|
|
72
|
-
return `fetch|${base}|${JSON.stringify(env)}|${JSON.stringify(subject ?? null)}|${JSON.stringify(developerRole ?? "user")}`;
|
|
68
|
+
return `render|${base}|${JSON.stringify(env)}|${JSON.stringify(subject ?? null)}`;
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
function sortObjectKeys(obj: unknown): unknown {
|
|
@@ -83,7 +79,7 @@ function sortObjectKeys(obj: unknown): unknown {
|
|
|
83
79
|
}
|
|
84
80
|
|
|
85
81
|
/**
|
|
86
|
-
* In-memory LRU cache for
|
|
82
|
+
* In-memory LRU cache for RenderedPrompt objects.
|
|
87
83
|
*
|
|
88
84
|
* Uses a Map (insertion-ordered) as the backing store. On every access, the
|
|
89
85
|
* entry is deleted and re-inserted to maintain LRU order — oldest entries sit
|
|
@@ -100,7 +96,7 @@ export class PromptCache {
|
|
|
100
96
|
this.store = new Map();
|
|
101
97
|
}
|
|
102
98
|
|
|
103
|
-
get(key: string):
|
|
99
|
+
get(key: string): CacheableRendered | undefined {
|
|
104
100
|
const entry = this.store.get(key);
|
|
105
101
|
if (!entry) return undefined;
|
|
106
102
|
|
|
@@ -116,7 +112,7 @@ export class PromptCache {
|
|
|
116
112
|
return entry.value;
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
set(key: string, value:
|
|
115
|
+
set(key: string, value: CacheableRendered): void {
|
|
120
116
|
// maxEntries: 0 (or negative) means caching is disabled — drop the entry.
|
|
121
117
|
if (this.maxEntries <= 0) {
|
|
122
118
|
return;
|
package/src/prompts/resource.ts
CHANGED
|
@@ -3,7 +3,13 @@ import { EnvNotFoundError, To11aiNetworkError } from "../errors";
|
|
|
3
3
|
import { zResolvedPrompt } from "../generated/zod.gen";
|
|
4
4
|
import type { ApiEnvelope, Transport } from "../transport";
|
|
5
5
|
import type { CacheConfig } from "./cache";
|
|
6
|
-
import {
|
|
6
|
+
import { buildRenderCacheKey, PromptCache } from "./cache";
|
|
7
|
+
import type {
|
|
8
|
+
AnthropicRenderedPrompt,
|
|
9
|
+
OpenAiRenderedPrompt,
|
|
10
|
+
RenderResult,
|
|
11
|
+
} from "./shape";
|
|
12
|
+
import { shapePrompt } from "./shape";
|
|
7
13
|
import type {
|
|
8
14
|
ArchivePromptInput,
|
|
9
15
|
ArchivePromptResult,
|
|
@@ -15,8 +21,6 @@ import type {
|
|
|
15
21
|
CreatePromptVersionResult,
|
|
16
22
|
DeleteProjectPromptLabelInput,
|
|
17
23
|
DeleteProjectPromptLabelResult,
|
|
18
|
-
FetchedPrompt,
|
|
19
|
-
FetchPromptOptions,
|
|
20
24
|
GetProjectPromptLabelInput,
|
|
21
25
|
GetProjectPromptLabelResult,
|
|
22
26
|
GetPromptInput,
|
|
@@ -40,10 +44,11 @@ import type {
|
|
|
40
44
|
PaginatedResult,
|
|
41
45
|
RenderedChatMessage,
|
|
42
46
|
RenderedPrompt,
|
|
47
|
+
RenderFormat,
|
|
48
|
+
RenderOptions,
|
|
43
49
|
ResolvedPrompt,
|
|
44
50
|
ResolvedPromptEnvelope,
|
|
45
|
-
|
|
46
|
-
ResolvePromptResult,
|
|
51
|
+
UnknownRoleMode,
|
|
47
52
|
UpdateProjectPromptLabelInput,
|
|
48
53
|
UpdateProjectPromptLabelResult,
|
|
49
54
|
UpdatePromptInput,
|
|
@@ -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,32 +104,22 @@ 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
114
|
TextChatMessage,
|
|
112
115
|
TextChatRole,
|
|
116
|
+
UnknownRoleMode,
|
|
113
117
|
UpdateProjectPromptLabelInput,
|
|
114
118
|
UpdateProjectPromptLabelResult,
|
|
115
119
|
UpdatePromptInput,
|
|
116
120
|
UpdatePromptResult,
|
|
117
121
|
} from "./types";
|
|
118
122
|
|
|
119
|
-
const zResolvedPromptLegacy = zResolvedPrompt.pick({
|
|
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
123
|
const zResolvedPromptEnvelope = zResolvedPrompt
|
|
131
124
|
.partial({ releaseId: true })
|
|
132
125
|
.extend({ strategy: z.enum(["single", "weighted"]).optional() });
|
|
@@ -143,22 +136,28 @@ function getProjectPromptsPath(projectId: string): string {
|
|
|
143
136
|
}
|
|
144
137
|
|
|
145
138
|
/**
|
|
146
|
-
* Project a server `rendered` payload into the `messages
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
139
|
+
* Project a server `rendered` payload into the enriched result's `messages`.
|
|
140
|
+
*
|
|
141
|
+
* Prompts resolve as chat blocks. The deprecated `text` format is no longer a
|
|
142
|
+
* first-class case: a non-chat payload throws rather than being silently
|
|
143
|
+
* coerced (a text prompt is sugar for a single-block chat and is being removed
|
|
144
|
+
* from the API).
|
|
150
145
|
*/
|
|
151
|
-
function renderedToMessages(
|
|
152
|
-
|
|
153
|
-
|
|
146
|
+
function renderedToMessages(
|
|
147
|
+
rendered: ResolvedPrompt["rendered"],
|
|
148
|
+
): RenderedChatMessage[] {
|
|
149
|
+
if (rendered.format !== "chat") {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"[to11ai/sdk] Unsupported non-chat prompt format; author the prompt as chat blocks.",
|
|
152
|
+
);
|
|
154
153
|
}
|
|
155
|
-
return
|
|
154
|
+
return rendered.messages;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
/**
|
|
159
|
-
* Normalize the server resolve envelope into the enriched
|
|
158
|
+
* Normalize the server resolve envelope into the enriched `RenderedPrompt`.
|
|
160
159
|
*
|
|
161
|
-
* - `messages` is
|
|
160
|
+
* - `messages` is derived from the `rendered` chat payload.
|
|
162
161
|
* - `releaseId` is ALWAYS a key: a string when the server sends one, else
|
|
163
162
|
* explicit `null` (R5 `string | null`).
|
|
164
163
|
* - `variantName` is present IFF the server sent one (i.e. weighted release);
|
|
@@ -167,9 +166,10 @@ function renderedToMessages(rendered: RenderedPrompt): RenderedChatMessage[] {
|
|
|
167
166
|
*/
|
|
168
167
|
function normalizeResolvedEnvelope(
|
|
169
168
|
raw: ResolvedPrompt | ResolvedPromptEnvelope,
|
|
170
|
-
|
|
169
|
+
slug: string,
|
|
170
|
+
): RenderedPrompt {
|
|
171
171
|
const provenance = raw as ResolvedPromptEnvelope;
|
|
172
|
-
const result:
|
|
172
|
+
const result: RenderedPrompt = {
|
|
173
173
|
messages: renderedToMessages(raw.rendered),
|
|
174
174
|
version: raw.version,
|
|
175
175
|
releaseId: provenance.releaseId ?? null,
|
|
@@ -177,8 +177,12 @@ function normalizeResolvedEnvelope(
|
|
|
177
177
|
promptId: raw.promptId,
|
|
178
178
|
versionId: raw.versionId,
|
|
179
179
|
contentHash: raw.contentHash,
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
label: raw.label,
|
|
181
|
+
// The requested slug — the API resolve envelope does not echo it, so it
|
|
182
|
+
// is threaded from the `render()` input (TO11-2727). Surfaced as
|
|
183
|
+
// `x-to11-prompt-slug` for trace readability.
|
|
184
|
+
slug,
|
|
185
|
+
modelConfig: provenance.modelConfig,
|
|
182
186
|
};
|
|
183
187
|
// `tools` is present IFF the resolved version declares tool definitions; the
|
|
184
188
|
// wire may send `null` (or omit it) — normalize both to absent so callers
|
|
@@ -208,20 +212,26 @@ function normalizeResolvedEnvelope(
|
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
/**
|
|
211
|
-
* Lift a caller-supplied offline `fallback` into the
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* own value or explicit `null`; `blockRenderRecord` → `{}`; `variantName`
|
|
216
|
-
* stays absent).
|
|
215
|
+
* Lift a caller-supplied offline `fallback` into the returned shape (D7), so
|
|
216
|
+
* `render()` returns an identical shape on the online and offline paths. The
|
|
217
|
+
* fallback's own slug is preferred, falling back to the `render()` input when it
|
|
218
|
+
* carries none, so `x-to11-prompt-slug` is still emitted.
|
|
217
219
|
*/
|
|
218
|
-
function
|
|
219
|
-
fallback:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
function liftFallbackToRendered(
|
|
221
|
+
fallback: RenderedPrompt,
|
|
222
|
+
slug: string,
|
|
223
|
+
): RenderedPrompt {
|
|
224
|
+
// Defensive: `fallback` is typed `RenderedPrompt`, but an untyped JS caller,
|
|
225
|
+
// an `as` cast, or a persisted pre-2.0 `resolve()` result could pass the old
|
|
226
|
+
// `ResolvedPrompt` shape (no top-level `messages`). Fail loudly rather than
|
|
227
|
+
// return a malformed result with `undefined` messages.
|
|
228
|
+
if (!Array.isArray((fallback as { messages?: unknown }).messages)) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
"[to11ai/sdk] `fallback` must be a RenderedPrompt (as returned by render()); the legacy resolve() shape is no longer supported.",
|
|
231
|
+
);
|
|
223
232
|
}
|
|
224
|
-
|
|
233
|
+
// `||` (not `??`) so an empty-string slug also falls through to the input.
|
|
234
|
+
return { ...structuredClone(fallback), slug: fallback.slug || slug };
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
function getProjectPromptPath(projectId: string, promptId: string): string {
|
|
@@ -261,34 +271,48 @@ function buildLabelEventsQuery(input: ListLabelEventsInput): string {
|
|
|
261
271
|
}
|
|
262
272
|
|
|
263
273
|
/**
|
|
264
|
-
* Init-time config for the env-aware `
|
|
274
|
+
* Init-time config for the env-aware `render()` surface (TO11-980 / S9).
|
|
265
275
|
*
|
|
266
276
|
* `projectId` and `env` come from `createClient`. `projectId` is required for
|
|
267
|
-
* `
|
|
268
|
-
* serving environment inherited by every `
|
|
277
|
+
* `render()` (it builds the project-scoped resolve path); `env` is the default
|
|
278
|
+
* serving environment inherited by every `render(slug)` call.
|
|
269
279
|
*/
|
|
270
280
|
export interface PromptResourceConfig {
|
|
271
|
-
/** Project the client is bound to. Required for `
|
|
281
|
+
/** Project the client is bound to. Required for `render()`; validated at createClient. */
|
|
272
282
|
projectId?: string;
|
|
273
|
-
/** Default serving env for `
|
|
283
|
+
/** Default serving env for `render()`. Validated against ENV_SLUG_REGEX at createClient. */
|
|
274
284
|
env?: string;
|
|
285
|
+
/** Default provider shape for `render()` output. Per-call override allowed. */
|
|
286
|
+
format?: RenderFormat;
|
|
287
|
+
/**
|
|
288
|
+
* Default unknown-role policy for `render({ format })` output conversion.
|
|
289
|
+
* Per-call override allowed; defaults to `"drop"`.
|
|
290
|
+
*/
|
|
291
|
+
onUnknownRole?: UnknownRoleMode;
|
|
275
292
|
}
|
|
276
293
|
|
|
277
|
-
export class PromptResource {
|
|
294
|
+
export class PromptResource<F extends RenderFormat | undefined = undefined> {
|
|
278
295
|
private readonly transport: Transport;
|
|
279
296
|
private readonly cache: PromptCache;
|
|
280
297
|
private readonly projectId: string | undefined;
|
|
281
298
|
private readonly env: string | undefined;
|
|
299
|
+
private readonly format: RenderFormat | undefined;
|
|
300
|
+
private readonly onUnknownRole: UnknownRoleMode | undefined;
|
|
282
301
|
|
|
283
302
|
constructor(
|
|
284
303
|
transport: Transport,
|
|
285
304
|
cacheConfig?: CacheConfig,
|
|
286
|
-
|
|
305
|
+
// `format` is bound to the class generic `F` (not the wider `RenderFormat`)
|
|
306
|
+
// so the constructed default shape cannot disagree with what `render()`'s
|
|
307
|
+
// return type (`RenderResult<F>`) promises.
|
|
308
|
+
config?: Omit<PromptResourceConfig, "format"> & { format?: F },
|
|
287
309
|
) {
|
|
288
310
|
this.transport = transport;
|
|
289
311
|
this.cache = new PromptCache(cacheConfig);
|
|
290
312
|
this.projectId = config?.projectId;
|
|
291
313
|
this.env = config?.env;
|
|
314
|
+
this.format = config?.format;
|
|
315
|
+
this.onUnknownRole = config?.onUnknownRole;
|
|
292
316
|
}
|
|
293
317
|
|
|
294
318
|
// ─── Create ──────────────────────────────────────────────────────────────
|
|
@@ -436,35 +460,64 @@ export class PromptResource {
|
|
|
436
460
|
return unwrap(envelope);
|
|
437
461
|
}
|
|
438
462
|
|
|
439
|
-
// ───
|
|
463
|
+
// ─── Render (env-aware resolution + format shaping, TO11-980 / TO11-2691) ──
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Resolve and (optionally) shape a prompt for the client's configured
|
|
467
|
+
* `projectId` + `env`, in one control-plane request. With a `format` (per
|
|
468
|
+
* call or inherited from the client) the result is provider-shaped
|
|
469
|
+
* `{ messages, config, metadata }`; without one it is the neutral
|
|
470
|
+
* `RenderedPrompt`. Resolves by the client's configured `env`.
|
|
471
|
+
*
|
|
472
|
+
* @example Render an OpenAI-shaped prompt and send it through the gateway
|
|
473
|
+
* const prompt = await to11.prompts.render("weather-concierge", {
|
|
474
|
+
* variables: { city: "NYC" },
|
|
475
|
+
* });
|
|
476
|
+
* const res = await openai.chat.completions.create(
|
|
477
|
+
* { ...prompt.config, messages: prompt.messages },
|
|
478
|
+
* { headers: to11.turn().headers(prompt) },
|
|
479
|
+
* );
|
|
480
|
+
*/
|
|
481
|
+
async render(
|
|
482
|
+
slug: string,
|
|
483
|
+
options: RenderOptions & { format: "openai" },
|
|
484
|
+
): Promise<OpenAiRenderedPrompt>;
|
|
485
|
+
async render(
|
|
486
|
+
slug: string,
|
|
487
|
+
options: RenderOptions & { format: "anthropic" },
|
|
488
|
+
): Promise<AnthropicRenderedPrompt>;
|
|
489
|
+
async render(slug: string, options?: RenderOptions): Promise<RenderResult<F>>;
|
|
490
|
+
async render(
|
|
491
|
+
slug: string,
|
|
492
|
+
options: RenderOptions = {},
|
|
493
|
+
): Promise<RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt> {
|
|
494
|
+
const rendered = await this.resolveToRendered(slug, options);
|
|
495
|
+
return shapePrompt(rendered, options.format ?? this.format, {
|
|
496
|
+
onUnknownRole: options.onUnknownRole ?? this.onUnknownRole,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
440
499
|
|
|
441
500
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* `createClient` and inherited here, with an optional per-call override.
|
|
501
|
+
* Env-aware resolution → neutral `RenderedPrompt`. Carries the resolve /
|
|
502
|
+
* cache / offline-fallback logic; `render()` is the public shaping surface.
|
|
445
503
|
*
|
|
446
|
-
* - **env→label wire mapping
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* env slug (lowercase-kebab — a valid label name) is therefore sent as
|
|
450
|
-
* the single resolution label: body `{ slug, labels: [env], subject?,
|
|
451
|
-
* variables? }`.
|
|
504
|
+
* - **env→label wire mapping:** the resolve route is label-based
|
|
505
|
+
* (`/v1/projects/:projectId/prompts:resolve`, body `labels[]`); the
|
|
506
|
+
* resolved env slug is sent as the single resolution label.
|
|
452
507
|
* - **No silent fallback (R4):** if no env is resolvable (none at init AND
|
|
453
|
-
* none per call) this throws `EnvNotFoundError`
|
|
454
|
-
*
|
|
508
|
+
* none per call) this throws `EnvNotFoundError` CLIENT-side, eagerly,
|
|
509
|
+
* before any transport call.
|
|
455
510
|
* - **Cache (D8):** caches `single`-strategy releases and weighted releases
|
|
456
511
|
* WITH a `subject`; BYPASSES the cache for weighted releases with NO
|
|
457
512
|
* `subject` (a uniform-random assignment must not be pinned for the TTL).
|
|
458
|
-
*
|
|
459
|
-
* Prefer this over the deprecated `resolve()`.
|
|
460
513
|
*/
|
|
461
|
-
async
|
|
514
|
+
private async resolveToRendered(
|
|
462
515
|
slug: string,
|
|
463
|
-
options:
|
|
464
|
-
): Promise<
|
|
516
|
+
options: RenderOptions = {},
|
|
517
|
+
): Promise<RenderedPrompt> {
|
|
465
518
|
if (this.projectId === undefined || this.projectId.trim() === "") {
|
|
466
519
|
throw new Error(
|
|
467
|
-
"[to11ai/sdk] `prompts.
|
|
520
|
+
"[to11ai/sdk] `prompts.render()` requires a `projectId` on the client — pass it to createClient({ projectId, ... }).",
|
|
468
521
|
);
|
|
469
522
|
}
|
|
470
523
|
|
|
@@ -486,35 +539,25 @@ export class PromptResource {
|
|
|
486
539
|
);
|
|
487
540
|
}
|
|
488
541
|
|
|
489
|
-
const {
|
|
490
|
-
subject,
|
|
491
|
-
variables,
|
|
492
|
-
cache: useCache = true,
|
|
493
|
-
fallback,
|
|
494
|
-
developerRole,
|
|
495
|
-
} = options;
|
|
542
|
+
const { subject, variables, cache: useCache = true, fallback } = options;
|
|
496
543
|
|
|
497
|
-
const cacheKey =
|
|
544
|
+
const cacheKey = buildRenderCacheKey(
|
|
498
545
|
this.projectId,
|
|
499
546
|
slug,
|
|
500
547
|
env,
|
|
501
548
|
subject,
|
|
502
549
|
variables,
|
|
503
|
-
developerRole,
|
|
504
550
|
);
|
|
505
551
|
|
|
506
552
|
if (useCache) {
|
|
507
553
|
const cached = this.cache.get(cacheKey);
|
|
508
|
-
|
|
509
|
-
// namespace ("fetch|…") never collides with the legacy resolve key.
|
|
510
|
-
if (cached && "messages" in cached) return structuredClone(cached);
|
|
554
|
+
if (cached) return structuredClone(cached);
|
|
511
555
|
}
|
|
512
556
|
|
|
513
557
|
// env→label mapping: the env slug is the single resolution label.
|
|
514
558
|
const body: Record<string, unknown> = { slug, labels: [env] };
|
|
515
559
|
if (subject !== undefined) body.subject = subject;
|
|
516
560
|
if (variables !== undefined) body.variables = variables;
|
|
517
|
-
if (developerRole !== undefined) body.developerRole = developerRole;
|
|
518
561
|
|
|
519
562
|
try {
|
|
520
563
|
const envelope = await this.transport.request<ResolvedPromptEnvelope>(
|
|
@@ -527,7 +570,7 @@ export class PromptResource {
|
|
|
527
570
|
},
|
|
528
571
|
);
|
|
529
572
|
const raw = unwrap(envelope);
|
|
530
|
-
const result = normalizeResolvedEnvelope(raw);
|
|
573
|
+
const result = normalizeResolvedEnvelope(raw, slug);
|
|
531
574
|
|
|
532
575
|
// D8: bypass the cache for a weighted release with NO subject —
|
|
533
576
|
// caching a uniform-random assignment would pin one variant for the
|
|
@@ -552,63 +595,7 @@ export class PromptResource {
|
|
|
552
595
|
// client-eager throw already happened above the try, so there is no
|
|
553
596
|
// network error to trigger it on the env-missing path.
|
|
554
597
|
if (err instanceof To11aiNetworkError && fallback !== undefined) {
|
|
555
|
-
return
|
|
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;
|
|
608
|
-
} catch (err) {
|
|
609
|
-
// If network error and we have a fallback, serve it
|
|
610
|
-
if (err instanceof To11aiNetworkError && fallback !== undefined) {
|
|
611
|
-
return fallback;
|
|
598
|
+
return liftFallbackToRendered(fallback, slug);
|
|
612
599
|
}
|
|
613
600
|
throw err;
|
|
614
601
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
|
|
8
|
+
import {
|
|
9
|
+
type AnthropicMessage,
|
|
10
|
+
type AnthropicTool,
|
|
11
|
+
type AnthropicToolChoice,
|
|
12
|
+
type ConvertMessagesOptions,
|
|
13
|
+
type OpenAIChatMessage,
|
|
14
|
+
type OpenAITool,
|
|
15
|
+
type OpenAIToolChoice,
|
|
16
|
+
toAnthropicMessages,
|
|
17
|
+
toAnthropicSystem,
|
|
18
|
+
toAnthropicToolChoice,
|
|
19
|
+
toAnthropicTools,
|
|
20
|
+
toOpenAIMessages,
|
|
21
|
+
toOpenAIToolChoice,
|
|
22
|
+
toOpenAITools,
|
|
23
|
+
} from "../gateway";
|
|
24
|
+
import type { RenderedPrompt, RenderFormat } from "./types";
|
|
25
|
+
|
|
26
|
+
/** Model params passed through verbatim from the resolved version's modelConfig. */
|
|
27
|
+
export interface PromptConfig {
|
|
28
|
+
model?: string;
|
|
29
|
+
temperature?: number;
|
|
30
|
+
// Snake_case to match the stored modelConfig and the provider wire key
|
|
31
|
+
// (spread verbatim into e.g. OpenAI's `create()`), per TO11-2691.
|
|
32
|
+
max_tokens?: number;
|
|
33
|
+
/** Any additional stored model-config keys are passed through unchanged. */
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Prompt provenance for `turn.headers()` — kept apart from the request body. */
|
|
38
|
+
export interface PromptMetadata {
|
|
39
|
+
promptId: string;
|
|
40
|
+
versionId: string;
|
|
41
|
+
version: number;
|
|
42
|
+
releaseId: string | null;
|
|
43
|
+
/** The single label the prompt resolved through (env slug today). */
|
|
44
|
+
label: string;
|
|
45
|
+
/** The prompt's human-readable slug — surfaced as `x-to11-prompt-slug`. */
|
|
46
|
+
slug: string;
|
|
47
|
+
variantName?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** OpenAI-shaped render result: spread `{ ...config, messages }` into create(). */
|
|
51
|
+
export interface OpenAiRenderedPrompt {
|
|
52
|
+
messages: OpenAIChatMessage[];
|
|
53
|
+
config: PromptConfig;
|
|
54
|
+
metadata: PromptMetadata;
|
|
55
|
+
/** Authored tools, provider-shaped. Present only when the prompt declares tools. */
|
|
56
|
+
tools?: OpenAITool[];
|
|
57
|
+
/** Authored tool-choice directive. Present only when the prompt sets one. */
|
|
58
|
+
tool_choice?: OpenAIToolChoice;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Anthropic-shaped render result: system/developer folded into `system`. */
|
|
62
|
+
export interface AnthropicRenderedPrompt {
|
|
63
|
+
/** Folded system/developer instruction text (Anthropic's top-level `system`). */
|
|
64
|
+
system?: string;
|
|
65
|
+
messages: AnthropicMessage[];
|
|
66
|
+
config: PromptConfig;
|
|
67
|
+
metadata: PromptMetadata;
|
|
68
|
+
/** Authored tools, provider-shaped. Present only when the prompt declares tools. */
|
|
69
|
+
tools?: AnthropicTool[];
|
|
70
|
+
/** Authored tool-choice directive. Present only when the prompt sets one. */
|
|
71
|
+
tool_choice?: AnthropicToolChoice;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The render return type for a given client/call format. */
|
|
75
|
+
export type RenderResult<F extends RenderFormat | undefined> =
|
|
76
|
+
F extends "openai"
|
|
77
|
+
? OpenAiRenderedPrompt
|
|
78
|
+
: F extends "anthropic"
|
|
79
|
+
? AnthropicRenderedPrompt
|
|
80
|
+
: RenderedPrompt;
|
|
81
|
+
|
|
82
|
+
function toPromptConfig(modelConfig: unknown): PromptConfig {
|
|
83
|
+
return modelConfig !== null && typeof modelConfig === "object"
|
|
84
|
+
? { ...(modelConfig as Record<string, unknown>) }
|
|
85
|
+
: {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toMetadata(prompt: RenderedPrompt): PromptMetadata {
|
|
89
|
+
const metadata: PromptMetadata = {
|
|
90
|
+
promptId: prompt.promptId,
|
|
91
|
+
versionId: prompt.versionId,
|
|
92
|
+
version: prompt.version,
|
|
93
|
+
releaseId: prompt.releaseId,
|
|
94
|
+
label: prompt.label,
|
|
95
|
+
slug: prompt.slug,
|
|
96
|
+
};
|
|
97
|
+
if (prompt.variantName !== undefined)
|
|
98
|
+
metadata.variantName = prompt.variantName;
|
|
99
|
+
return metadata;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function shapeOpenAI(
|
|
103
|
+
prompt: RenderedPrompt,
|
|
104
|
+
options: ConvertMessagesOptions = {},
|
|
105
|
+
): OpenAiRenderedPrompt {
|
|
106
|
+
const result: OpenAiRenderedPrompt = {
|
|
107
|
+
messages: toOpenAIMessages(prompt.messages, options),
|
|
108
|
+
config: toPromptConfig(prompt.modelConfig),
|
|
109
|
+
metadata: toMetadata(prompt),
|
|
110
|
+
};
|
|
111
|
+
// Carry authored tools so the spread-ready result is complete for
|
|
112
|
+
// tool-using prompts. Omit when empty/absent so we never send `tools: []`.
|
|
113
|
+
const tools = toOpenAITools(prompt.tools);
|
|
114
|
+
if (tools.length > 0) result.tools = tools;
|
|
115
|
+
const toolChoice = toOpenAIToolChoice(prompt.toolChoice);
|
|
116
|
+
if (toolChoice !== undefined) result.tool_choice = toolChoice;
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function shapeAnthropic(
|
|
121
|
+
prompt: RenderedPrompt,
|
|
122
|
+
options: ConvertMessagesOptions = {},
|
|
123
|
+
): AnthropicRenderedPrompt {
|
|
124
|
+
const result: AnthropicRenderedPrompt = {
|
|
125
|
+
messages: toAnthropicMessages(prompt.messages, options),
|
|
126
|
+
config: toPromptConfig(prompt.modelConfig),
|
|
127
|
+
metadata: toMetadata(prompt),
|
|
128
|
+
};
|
|
129
|
+
const system = toAnthropicSystem(prompt.messages);
|
|
130
|
+
if (system !== undefined) result.system = system;
|
|
131
|
+
const tools = toAnthropicTools(prompt.tools);
|
|
132
|
+
if (tools.length > 0) result.tools = tools;
|
|
133
|
+
const toolChoice = toAnthropicToolChoice(prompt.toolChoice);
|
|
134
|
+
if (toolChoice !== undefined) result.tool_choice = toolChoice;
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Shape a fetched prompt for `format`, or return it neutral when unset. */
|
|
139
|
+
export function shapePrompt(
|
|
140
|
+
prompt: RenderedPrompt,
|
|
141
|
+
format: RenderFormat | undefined,
|
|
142
|
+
options: ConvertMessagesOptions = {},
|
|
143
|
+
): RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt {
|
|
144
|
+
switch (format) {
|
|
145
|
+
case "openai":
|
|
146
|
+
return shapeOpenAI(prompt, options);
|
|
147
|
+
case "anthropic":
|
|
148
|
+
return shapeAnthropic(prompt, options);
|
|
149
|
+
default:
|
|
150
|
+
return prompt;
|
|
151
|
+
}
|
|
152
|
+
}
|