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