@to11ai/sdk 1.1.0 → 2.0.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/errors.cjs CHANGED
@@ -39,6 +39,7 @@ var __export = (target, all) => {
39
39
  // src/errors.ts
40
40
  var exports_errors = {};
41
41
  __export(exports_errors, {
42
+ UnknownRoleError: () => UnknownRoleError,
42
43
  To11aiValidationError: () => To11aiValidationError,
43
44
  To11aiResponseValidationError: () => To11aiResponseValidationError,
44
45
  To11aiRateLimitError: () => To11aiRateLimitError,
@@ -53,9 +54,9 @@ __export(exports_errors, {
53
54
  PromptLabelNotRegisteredError: () => PromptLabelNotRegisteredError,
54
55
  NoApplicableRouteError: () => NoApplicableRouteError,
55
56
  NoActiveReleaseError: () => NoActiveReleaseError,
56
- EnvNotFoundError: () => EnvNotFoundError,
57
- EnvNotEnabledForProviderError: () => EnvNotEnabledForProviderError,
58
- AmbiguousEnvLabelsError: () => AmbiguousEnvLabelsError
57
+ MissingLabelError: () => MissingLabelError,
58
+ LabelNotFoundError: () => LabelNotFoundError,
59
+ EnvNotEnabledForProviderError: () => EnvNotEnabledForProviderError
59
60
  });
60
61
  module.exports = __toCommonJS(exports_errors);
61
62
 
@@ -130,6 +131,22 @@ class To11aiResponseValidationError extends To11aiError {
130
131
  }
131
132
  }
132
133
 
134
+ class UnknownRoleError extends To11aiError {
135
+ code = "unknown_role";
136
+ role;
137
+ index;
138
+ constructor(role, index) {
139
+ super(`[to11ai/sdk] message[${index}] has unknown role ${JSON.stringify(role)}; expected system|user|assistant|developer|tool`);
140
+ this.name = "UnknownRoleError";
141
+ this.role = role;
142
+ this.index = index;
143
+ Object.setPrototypeOf(this, new.target.prototype);
144
+ }
145
+ static is(err) {
146
+ return err instanceof UnknownRoleError;
147
+ }
148
+ }
149
+
133
150
  class PromptLabelNotRegisteredError extends To11aiApiError {
134
151
  projectId;
135
152
  label;
@@ -209,19 +226,27 @@ class PromptNotFoundError extends To11aiApiError {
209
226
  }
210
227
  }
211
228
 
212
- class EnvNotFoundError extends To11aiApiError {
213
- reason;
214
- constructor(message, reason, details, statusCode) {
229
+ class LabelNotFoundError extends To11aiApiError {
230
+ label;
231
+ constructor(message, label, details, statusCode) {
215
232
  super(message, statusCode, "ENV_NOT_FOUND", details);
216
- this.name = "EnvNotFoundError";
217
- this.reason = reason;
233
+ this.name = "LabelNotFoundError";
234
+ this.label = label;
218
235
  Object.setPrototypeOf(this, new.target.prototype);
219
236
  }
220
- static missing(env) {
221
- return new EnvNotFoundError("[to11ai/sdk] No environment resolvable for prompts.fetch(): none supplied at client init and none supplied per call. Pass `env` to createClient or fetch(slug, { env }).", { env, source: "missing" }, undefined, 0);
237
+ static is(err) {
238
+ return err instanceof LabelNotFoundError;
239
+ }
240
+ }
241
+
242
+ class MissingLabelError extends To11aiError {
243
+ constructor(message = "[to11ai/sdk] No label resolvable for prompts.render(): none supplied per call, and no client env (or TO11_ENV) to fall back to. Pass `label` to render(slug, { label }) or set `env` on createClient.") {
244
+ super(message);
245
+ this.name = "MissingLabelError";
246
+ Object.setPrototypeOf(this, new.target.prototype);
222
247
  }
223
248
  static is(err) {
224
- return err instanceof EnvNotFoundError;
249
+ return err instanceof MissingLabelError;
225
250
  }
226
251
  }
227
252
 
@@ -251,19 +276,6 @@ class PromptPolicyViolationError extends To11aiApiError {
251
276
  }
252
277
  }
253
278
 
254
- class AmbiguousEnvLabelsError extends To11aiApiError {
255
- reason;
256
- constructor(message, reason, details, statusCode = 409) {
257
- super(message, statusCode, "AMBIGUOUS_ENV_LABELS", details);
258
- this.name = "AmbiguousEnvLabelsError";
259
- this.reason = reason;
260
- Object.setPrototypeOf(this, new.target.prototype);
261
- }
262
- static is(err) {
263
- return err instanceof AmbiguousEnvLabelsError;
264
- }
265
- }
266
-
267
279
  class RuleVersionInvalidSnapshotError extends To11aiApiError {
268
280
  field;
269
281
  reason;
package/dist/errors.d.ts CHANGED
@@ -51,6 +51,23 @@ export declare class To11aiResponseValidationError extends To11aiError {
51
51
  }[];
52
52
  constructor(path: string, zodError: unknown, cause?: unknown);
53
53
  }
54
+ /**
55
+ * Thrown by the provider-shape converters (`toOpenAIMessages` /
56
+ * `toAnthropicMessages`, and `render({ format })`) in `onUnknownRole: "error"`
57
+ * mode when a message carries a role outside the known set
58
+ * (`system | user | assistant | developer | tool`). `code` is the stable
59
+ * `"unknown_role"` slug; `role` / `index` locate the offending turn.
60
+ *
61
+ * A client-side conversion error — it extends `To11aiError` (not
62
+ * `To11aiApiError`), so it never carries an HTTP status.
63
+ */
64
+ export declare class UnknownRoleError extends To11aiError {
65
+ readonly code = "unknown_role";
66
+ readonly role: string;
67
+ readonly index: number;
68
+ constructor(role: string, index: number);
69
+ static is(err: unknown): err is UnknownRoleError;
70
+ }
54
71
  /**
55
72
  * Thrown when strict-mode rejects a label move for an unregistered label.
56
73
  *
@@ -154,35 +171,29 @@ export declare class PromptNotFoundError extends To11aiApiError {
154
171
  static is(err: unknown): err is PromptNotFoundError;
155
172
  }
156
173
  /**
157
- * Reason payload for `EnvNotFoundError`.
174
+ * Thrown when the label supplied to `prompts.render()` does not resolve to an
175
+ * existing (active) label on the server. Surfaced from the server's
176
+ * `ENV_NOT_FOUND` code; `label` carries the rejected slug when the server
177
+ * echoes it.
158
178
  *
159
- * `source` discriminates the two failure modes (D2):
160
- * - `"missing"` — no env was supplied at init OR at call time. Raised
161
- * CLIENT-side, eagerly, before any request (R4 / AC4); `env` is absent.
162
- * - `"unknown"` — an env was supplied but the server's registry has no
163
- * such (active) env. Surfaced from the `ENV_NOT_FOUND` server code; `env`
164
- * carries the rejected slug when the server echoes it.
179
+ * This is distinct from `MissingLabelError`, which is raised client-side when
180
+ * no label was supplied at all.
165
181
  */
166
- export interface EnvNotFoundReason {
167
- env?: string;
168
- source: "missing" | "unknown";
182
+ export declare class LabelNotFoundError extends To11aiApiError {
183
+ /** The label slug that could not be found, when the server echoes it. */
184
+ readonly label?: string;
185
+ constructor(message: string, label: string | undefined, details: unknown, statusCode: number);
186
+ static is(err: unknown): err is LabelNotFoundError;
169
187
  }
170
188
  /**
171
- * Thrown when no serving environment can be resolved for a `prompts.fetch()`
172
- * call. One class, two `reason.source` discriminants (see
173
- * `EnvNotFoundReason`): client-eager `"missing"` (R4) and server-forwarded
174
- * `"unknown"` (`ENV_NOT_FOUND`).
175
- *
176
- * The client-eager variant carries no `statusCode`/`errorCode` from the wire
177
- * (no request was made); it reports status `0` and errorCode `ENV_NOT_FOUND`
178
- * for a uniform catch surface.
189
+ * Thrown CLIENT-side, eagerly (before any request), when `prompts.render()` is
190
+ * called but no label is resolvable at all: none supplied per call, and no
191
+ * client `env` (or `TO11_ENV`) to fall back to. A usage error, not a lookup
192
+ * failure — distinct from `LabelNotFoundError`.
179
193
  */
180
- export declare class EnvNotFoundError extends To11aiApiError {
181
- readonly reason: EnvNotFoundReason;
182
- constructor(message: string, reason: EnvNotFoundReason, details: unknown, statusCode: number);
183
- /** Build the client-eager (missing-at-both) variant — no transport call. */
184
- static missing(env?: string): EnvNotFoundError;
185
- static is(err: unknown): err is EnvNotFoundError;
194
+ export declare class MissingLabelError extends To11aiError {
195
+ constructor(message?: string);
196
+ static is(err: unknown): err is MissingLabelError;
186
197
  }
187
198
  /**
188
199
  * Reason payload for `NoActiveReleaseError`.
@@ -233,28 +244,6 @@ export declare class PromptPolicyViolationError extends To11aiApiError {
233
244
  constructor(message: string, reason: PromptPolicyViolationReason, details?: unknown, statusCode?: number);
234
245
  static is(err: unknown): err is PromptPolicyViolationError;
235
246
  }
236
- /**
237
- * Reason payload for `AmbiguousEnvLabelsError`.
238
- *
239
- * Carried when the supplied env-label matched more than one candidate
240
- * version and the server cannot pick deterministically
241
- * (`AMBIGUOUS_ENV_LABELS`).
242
- */
243
- export interface AmbiguousEnvLabelsReason {
244
- env?: string;
245
- slug?: string;
246
- }
247
- /**
248
- * Thrown when the env→label resolution is ambiguous on the server
249
- * (`AMBIGUOUS_ENV_LABELS`) — the label matched multiple candidates and the
250
- * server declines to guess. Surfaces (rather than 500ing) so callers get a
251
- * typed, actionable error.
252
- */
253
- export declare class AmbiguousEnvLabelsError extends To11aiApiError {
254
- readonly reason: AmbiguousEnvLabelsReason;
255
- constructor(message: string, reason: AmbiguousEnvLabelsReason, details?: unknown, statusCode?: number);
256
- static is(err: unknown): err is AmbiguousEnvLabelsError;
257
- }
258
247
  /**
259
248
  * Thrown when version create rejects the snapshot for a structural
260
249
  * reason — invalid variant params, malformed `when` clause, illegal
package/dist/errors.js CHANGED
@@ -70,6 +70,22 @@ class To11aiResponseValidationError extends To11aiError {
70
70
  }
71
71
  }
72
72
 
73
+ class UnknownRoleError extends To11aiError {
74
+ code = "unknown_role";
75
+ role;
76
+ index;
77
+ constructor(role, index) {
78
+ super(`[to11ai/sdk] message[${index}] has unknown role ${JSON.stringify(role)}; expected system|user|assistant|developer|tool`);
79
+ this.name = "UnknownRoleError";
80
+ this.role = role;
81
+ this.index = index;
82
+ Object.setPrototypeOf(this, new.target.prototype);
83
+ }
84
+ static is(err) {
85
+ return err instanceof UnknownRoleError;
86
+ }
87
+ }
88
+
73
89
  class PromptLabelNotRegisteredError extends To11aiApiError {
74
90
  projectId;
75
91
  label;
@@ -149,19 +165,27 @@ class PromptNotFoundError extends To11aiApiError {
149
165
  }
150
166
  }
151
167
 
152
- class EnvNotFoundError extends To11aiApiError {
153
- reason;
154
- constructor(message, reason, details, statusCode) {
168
+ class LabelNotFoundError extends To11aiApiError {
169
+ label;
170
+ constructor(message, label, details, statusCode) {
155
171
  super(message, statusCode, "ENV_NOT_FOUND", details);
156
- this.name = "EnvNotFoundError";
157
- this.reason = reason;
172
+ this.name = "LabelNotFoundError";
173
+ this.label = label;
158
174
  Object.setPrototypeOf(this, new.target.prototype);
159
175
  }
160
- static missing(env) {
161
- return new EnvNotFoundError("[to11ai/sdk] No environment resolvable for prompts.fetch(): none supplied at client init and none supplied per call. Pass `env` to createClient or fetch(slug, { env }).", { env, source: "missing" }, undefined, 0);
176
+ static is(err) {
177
+ return err instanceof LabelNotFoundError;
178
+ }
179
+ }
180
+
181
+ class MissingLabelError extends To11aiError {
182
+ constructor(message = "[to11ai/sdk] No label resolvable for prompts.render(): none supplied per call, and no client env (or TO11_ENV) to fall back to. Pass `label` to render(slug, { label }) or set `env` on createClient.") {
183
+ super(message);
184
+ this.name = "MissingLabelError";
185
+ Object.setPrototypeOf(this, new.target.prototype);
162
186
  }
163
187
  static is(err) {
164
- return err instanceof EnvNotFoundError;
188
+ return err instanceof MissingLabelError;
165
189
  }
166
190
  }
167
191
 
@@ -191,19 +215,6 @@ class PromptPolicyViolationError extends To11aiApiError {
191
215
  }
192
216
  }
193
217
 
194
- class AmbiguousEnvLabelsError extends To11aiApiError {
195
- reason;
196
- constructor(message, reason, details, statusCode = 409) {
197
- super(message, statusCode, "AMBIGUOUS_ENV_LABELS", details);
198
- this.name = "AmbiguousEnvLabelsError";
199
- this.reason = reason;
200
- Object.setPrototypeOf(this, new.target.prototype);
201
- }
202
- static is(err) {
203
- return err instanceof AmbiguousEnvLabelsError;
204
- }
205
- }
206
-
207
218
  class RuleVersionInvalidSnapshotError extends To11aiApiError {
208
219
  field;
209
220
  reason;
@@ -221,6 +232,7 @@ class RuleVersionInvalidSnapshotError extends To11aiApiError {
221
232
  }
222
233
  }
223
234
  export {
235
+ UnknownRoleError,
224
236
  To11aiValidationError,
225
237
  To11aiResponseValidationError,
226
238
  To11aiRateLimitError,
@@ -235,7 +247,7 @@ export {
235
247
  PromptLabelNotRegisteredError,
236
248
  NoApplicableRouteError,
237
249
  NoActiveReleaseError,
238
- EnvNotFoundError,
239
- EnvNotEnabledForProviderError,
240
- AmbiguousEnvLabelsError
250
+ MissingLabelError,
251
+ LabelNotFoundError,
252
+ EnvNotEnabledForProviderError
241
253
  };
package/dist/gateway.cjs CHANGED
@@ -44,11 +44,13 @@ __export(exports_gateway, {
44
44
  toOpenAIMessages: () => toOpenAIMessages,
45
45
  toAnthropicTools: () => toAnthropicTools,
46
46
  toAnthropicToolChoice: () => toAnthropicToolChoice,
47
+ toAnthropicSystem: () => toAnthropicSystem,
47
48
  toAnthropicMessages: () => toAnthropicMessages,
48
49
  gatewayPromptHeaders: () => gatewayPromptHeaders,
49
50
  gatewayAuthHeaders: () => gatewayAuthHeaders
50
51
  });
51
52
  module.exports = __toCommonJS(exports_gateway);
53
+ var import_errors = require("./errors");
52
54
  var import_types = require("./prompts/types");
53
55
  function gatewayAuthHeaders(options) {
54
56
  if (options.apiKey.trim().length === 0) {
@@ -73,20 +75,27 @@ function gatewayAuthHeaders(options) {
73
75
  return headers;
74
76
  }
75
77
  function gatewayPromptHeaders(prompt) {
76
- const headers = {
77
- "x-to11-prompt-id": prompt.promptId,
78
- "x-to11-prompt-version": String(prompt.version)
79
- };
80
- const releaseId = prompt.releaseId;
81
- if (typeof releaseId === "string" && releaseId.length > 0) {
82
- headers["x-to11-release-id"] = releaseId;
78
+ const p = prompt != null && "metadata" in prompt ? prompt.metadata : prompt;
79
+ const headers = {};
80
+ if (p == null)
81
+ return headers;
82
+ if (typeof p.promptId === "string" && p.promptId.length > 0) {
83
+ headers["x-to11-prompt-id"] = p.promptId;
84
+ }
85
+ if (typeof p.version === "number" && Number.isFinite(p.version)) {
86
+ headers["x-to11-prompt-version"] = String(p.version);
87
+ }
88
+ if (typeof p.releaseId === "string" && p.releaseId.length > 0) {
89
+ headers["x-to11-release-id"] = p.releaseId;
83
90
  }
84
- const variantName = prompt.variantName;
85
- if (typeof variantName === "string" && variantName.length > 0) {
86
- headers["x-to11-variant-name"] = variantName;
91
+ if (typeof p.variantName === "string" && p.variantName.length > 0) {
92
+ headers["x-to11-variant-name"] = p.variantName;
87
93
  }
88
- if (prompt.labels.length > 0) {
89
- headers["x-to11-prompt-labels"] = prompt.labels.join(",");
94
+ if (typeof p.label === "string" && p.label.length > 0) {
95
+ headers["x-to11-prompt-label"] = p.label;
96
+ }
97
+ if (typeof p.slug === "string" && p.slug.length > 0) {
98
+ headers["x-to11-prompt-slug"] = p.slug;
90
99
  }
91
100
  return headers;
92
101
  }
@@ -142,41 +151,73 @@ function requireToolCallId(id) {
142
151
  }
143
152
  return id;
144
153
  }
145
- function toOpenAIMessages(messages) {
146
- return messages.map((message) => {
154
+ var KNOWN_RENDERED_ROLES = new Set([
155
+ "system",
156
+ "user",
157
+ "assistant",
158
+ "developer",
159
+ "tool"
160
+ ]);
161
+ function skipUnknownRole(role, index, mode) {
162
+ if (KNOWN_RENDERED_ROLES.has(role))
163
+ return false;
164
+ if (mode === "error")
165
+ throw new import_errors.UnknownRoleError(role, index);
166
+ if (mode === "warn") {
167
+ console.warn(`[to11ai/sdk] dropped message[${index}] with unknown role ${JSON.stringify(role)} (expected system|user|assistant|developer|tool)`);
168
+ }
169
+ return true;
170
+ }
171
+ function toOpenAIMessages(messages, options = {}) {
172
+ const mode = options.onUnknownRole ?? "drop";
173
+ const out = [];
174
+ messages.forEach((message, index) => {
175
+ if (skipUnknownRole(message.role, index, mode))
176
+ return;
147
177
  if (message.role === "tool") {
148
- return {
178
+ out.push({
149
179
  role: "tool",
150
180
  tool_call_id: requireToolCallId(message.toolCallId),
151
181
  content: message.content ?? ""
152
- };
182
+ });
183
+ return;
153
184
  }
154
185
  if (message.role === "assistant") {
155
- const out = {
186
+ const content2 = message.content ?? null;
187
+ out.push(message.toolCalls != null && message.toolCalls.length > 0 ? {
156
188
  role: "assistant",
157
- content: message.content ?? null
158
- };
159
- if (message.toolCalls != null && message.toolCalls.length > 0) {
160
- out.tool_calls = message.toolCalls.map((call) => ({
189
+ content: content2,
190
+ tool_calls: message.toolCalls.map((call) => ({
161
191
  id: requireToolCallId(call.id),
162
192
  type: "function",
163
193
  function: {
164
194
  name: call.name,
165
195
  arguments: JSON.stringify(call.arguments)
166
196
  }
167
- }));
168
- }
169
- return out;
197
+ }))
198
+ } : { role: "assistant", content: content2 });
199
+ return;
200
+ }
201
+ const content = message.content ?? "";
202
+ if (message.role === "user") {
203
+ out.push({ role: "user", content });
204
+ } else if (message.role === "developer") {
205
+ out.push({ role: "developer", content });
206
+ } else {
207
+ out.push({ role: "system", content });
170
208
  }
171
- return { role: message.role, content: message.content ?? "" };
172
209
  });
210
+ return out;
173
211
  }
174
- function toAnthropicMessages(messages) {
212
+ function toAnthropicMessages(messages, options = {}) {
213
+ const mode = options.onUnknownRole ?? "drop";
175
214
  const out = [];
176
- for (const message of messages) {
215
+ messages.forEach((message, index) => {
177
216
  if (message.role === "system" || message.role === "developer") {
178
- continue;
217
+ return;
179
218
  }
219
+ if (skipUnknownRole(message.role, index, mode))
220
+ return;
180
221
  if (message.role === "tool") {
181
222
  out.push({
182
223
  role: "user",
@@ -188,7 +229,7 @@ function toAnthropicMessages(messages) {
188
229
  }
189
230
  ]
190
231
  });
191
- continue;
232
+ return;
192
233
  }
193
234
  if (message.role === "assistant") {
194
235
  if (message.toolCalls != null && message.toolCalls.length > 0) {
@@ -208,9 +249,22 @@ function toAnthropicMessages(messages) {
208
249
  } else {
209
250
  out.push({ role: "assistant", content: message.content ?? "" });
210
251
  }
211
- continue;
252
+ return;
212
253
  }
213
254
  out.push({ role: "user", content: message.content ?? "" });
214
- }
255
+ });
215
256
  return out;
216
257
  }
258
+ function toAnthropicSystem(messages) {
259
+ const parts = [];
260
+ for (const message of messages) {
261
+ if (message.role !== "system" && message.role !== "developer")
262
+ continue;
263
+ if (typeof message.content === "string" && message.content.length > 0) {
264
+ parts.push(message.content);
265
+ }
266
+ }
267
+ return parts.length > 0 ? parts.join(`
268
+
269
+ `) : undefined;
270
+ }
package/dist/gateway.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type FetchedPrompt, type RenderedChatMessage, type ResolvedPrompt, type ResolvedToolDefinition } from "./prompts/types";
1
+ import { type RenderedChatMessage, type ResolvedPrompt, type ResolvedToolDefinition, type UnknownRoleMode } from "./prompts/types";
2
2
  export interface GatewayAuthOptions {
3
3
  apiKey: string;
4
4
  projectId: string;
@@ -30,29 +30,46 @@ export interface GatewayAuthOptions {
30
30
  */
31
31
  export declare function gatewayAuthHeaders(options: GatewayAuthOptions): Record<string, string>;
32
32
  /**
33
- * Build prompt provenance headers from a resolved prompt (TO11-980 / R7).
34
- *
35
- * Emits the full provenance set for every gateway-bound LLM request that
36
- * follows from a resolved prompt:
37
- * - `x-to11-prompt-id`, `x-to11-prompt-version` — always.
38
- * - `x-to11-release-id` — when the result carries a non-null `releaseId`
39
- * (the enriched `fetch()` result; a legacy `resolve()` result has no
40
- * `releaseId`, so it is omitted — non-breaking).
41
- * - `x-to11-variant-name` — IFF `variantName` is present (i.e. the resolved
42
- * release was weighted); omitted for `single`-strategy releases.
43
- * - `x-to11-prompt-labels` — omitted when empty (the gateway rejects empty
44
- * header values); behavior unchanged.
45
- *
46
- * The parameter type is WIDENED to accept either the enriched `fetch()`
47
- * result or the legacy `ResolvedPrompt`. The new fields are read when present
48
- * and omitted otherwise, so an old `resolve()` result still produces exactly
49
- * the original three headers (non-breaking; D1/D7 shape coexistence).
33
+ * Build prompt provenance headers from a `render()` result (or a format-shaped
34
+ * result's `metadata`, unwrapped automatically).
35
+ *
36
+ * No provenance header is required, and the gateway rejects empty header
37
+ * values — so each header is emitted only when its value is present and valid,
38
+ * and the function never throws:
39
+ * - `x-to11-prompt-id` — a non-empty string.
40
+ * - `x-to11-prompt-version` — a finite number (a non-number/NaN is dropped
41
+ * rather than stringified to junk like `"[object Object]"`).
42
+ * - `x-to11-release-id` — a non-empty `releaseId` (the offline-fallback path
43
+ * can carry an explicit `null`, which is omitted).
44
+ * - `x-to11-variant-name` — present IFF the resolved release was weighted.
45
+ * - `x-to11-prompt-label` — the single label the prompt resolved through.
46
+ * - `x-to11-prompt-slug` — the prompt's human-readable slug.
47
+ *
48
+ * An input with no usable provenance yields an empty header bag — the same as
49
+ * a prompt-less `turn.headers()` call.
50
50
  *
51
51
  * @example
52
- * const result = await client.prompts.fetch(slug, { env: "prod" });
52
+ * const result = await client.prompts.render(slug, { label: "prod" });
53
53
  * const headers = gatewayPromptHeaders(result);
54
54
  */
55
- export declare function gatewayPromptHeaders(prompt: ResolvedPrompt | FetchedPrompt): Record<string, string>;
55
+ export interface GatewayPromptProvenance {
56
+ promptId: string;
57
+ version: number;
58
+ /** The single label the prompt resolved through (env slug today). */
59
+ label: string;
60
+ /**
61
+ * The prompt's human-readable slug (the `render()`/`resolve()` input). Present
62
+ * on an enriched `render()` result; a legacy `ResolvedPrompt` omits it.
63
+ */
64
+ slug?: string;
65
+ /** Present on an enriched result; a legacy `resolve()` result omits it. */
66
+ releaseId?: string | null;
67
+ /** Present IFF the resolved release was weighted. */
68
+ variantName?: string;
69
+ }
70
+ export declare function gatewayPromptHeaders(prompt: GatewayPromptProvenance | {
71
+ metadata: GatewayPromptProvenance;
72
+ }): Record<string, string>;
56
73
  /** OpenAI Chat Completions `tools[]` entry (`function` tool). */
57
74
  export interface OpenAITool {
58
75
  type: "function";
@@ -78,13 +95,33 @@ export interface OpenAIToolCall {
78
95
  arguments: string;
79
96
  };
80
97
  }
81
- /** OpenAI Chat Completions `messages[]` entry. */
82
- export interface OpenAIChatMessage {
83
- role: "system" | "developer" | "user" | "assistant" | "tool";
98
+ /**
99
+ * OpenAI Chat Completions `messages[]` entry — a per-role discriminated union
100
+ * matching what Chat Completions requires for each role, so an
101
+ * `OpenAIChatMessage[]` is structurally assignable to the `openai` package's
102
+ * `ChatCompletionMessageParam[]` (a `render({ format: "openai" })` result spreads
103
+ * straight into `openai.chat.completions.create({ messages })` with no cast).
104
+ * The SDK carries no dependency on the `openai` package — this only mirrors its
105
+ * shape. (Assignability is regression-guarded in `gateway.provider-types.test.ts`.)
106
+ */
107
+ export type OpenAIChatMessage = {
108
+ role: "system";
109
+ content: string;
110
+ } | {
111
+ role: "developer";
112
+ content: string;
113
+ } | {
114
+ role: "user";
115
+ content: string;
116
+ } | {
117
+ role: "assistant";
84
118
  content?: string | null;
85
119
  tool_calls?: OpenAIToolCall[];
86
- tool_call_id?: string;
87
- }
120
+ } | {
121
+ role: "tool";
122
+ tool_call_id: string;
123
+ content: string;
124
+ };
88
125
  /** Anthropic Messages `tools[]` entry. */
89
126
  export interface AnthropicTool {
90
127
  name: string;
@@ -175,6 +212,16 @@ export declare function toAnthropicTools(tools: ReadonlyArray<ResolvedToolDefini
175
212
  * await anthropic.messages.create({ model, system, messages, tools, tool_choice });
176
213
  */
177
214
  export declare function toAnthropicToolChoice(toolChoice: ResolvedPrompt["toolChoice"]): AnthropicToolChoice | undefined;
215
+ /** Options shared by the message converters. */
216
+ export interface ConvertMessagesOptions {
217
+ /**
218
+ * Policy for a message whose role is outside the known set
219
+ * (`system | user | assistant | developer | tool`). `"drop"` (default) omits
220
+ * it silently; `"warn"` omits it and emits a `console.warn`; `"error"` throws
221
+ * {@link UnknownRoleError}. Both converters apply this identically.
222
+ */
223
+ onUnknownRole?: UnknownRoleMode;
224
+ }
178
225
  /**
179
226
  * Shape rendered turns for the OpenAI Chat Completions `messages` field.
180
227
  *
@@ -184,11 +231,15 @@ export declare function toAnthropicToolChoice(toolChoice: ResolvedPrompt["toolCh
184
231
  * become `tool_calls[]` with JSON-stringified `arguments` (the OpenAI wire
185
232
  * shape). A pure tool-call turn keeps `content: null`.
186
233
  * - `tool` (result) → `{ role: "tool", tool_call_id, content }`.
234
+ * - an unrecognized role is handled per `options.onUnknownRole` (default
235
+ * `"drop"`) — dropped, dropped-with-warning, or throws `UnknownRoleError`.
187
236
  *
188
237
  * @example
189
238
  * const messages = toOpenAIMessages(prompt.messages);
239
+ * @example Fail loudly on a malformed message array
240
+ * const messages = toOpenAIMessages(history, { onUnknownRole: "error" });
190
241
  */
191
- export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMessage>): OpenAIChatMessage[];
242
+ export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMessage>, options?: ConvertMessagesOptions): OpenAIChatMessage[];
192
243
  /**
193
244
  * Shape rendered turns for the Anthropic Messages `messages` field.
194
245
  *
@@ -201,8 +252,32 @@ export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMes
201
252
  * ...tool_use] }`; without calls → `{ role: "assistant", content }`.
202
253
  * - `tool` (result) → `{ role: "user", content: [{ type: "tool_result",
203
254
  * tool_use_id, content }] }`.
255
+ * - an unrecognized role is handled per `options.onUnknownRole` (default
256
+ * `"drop"`) — dropped, dropped-with-warning, or throws `UnknownRoleError` —
257
+ * identically to {@link toOpenAIMessages}. (`system`/`developer` are known
258
+ * roles folded into `system`, not "unknown"; they are never dropped here.)
204
259
  *
205
260
  * @example
206
261
  * const messages = toAnthropicMessages(prompt.messages);
262
+ * @example Fail loudly on a malformed message array
263
+ * const messages = toAnthropicMessages(history, { onUnknownRole: "error" });
264
+ */
265
+ export declare function toAnthropicMessages(messages: ReadonlyArray<RenderedChatMessage>, options?: ConvertMessagesOptions): AnthropicMessage[];
266
+ /**
267
+ * Fold the `system` and `developer` instruction turns into Anthropic's
268
+ * top-level `system` string. Anthropic has no `developer` role and takes system
269
+ * instructions as a separate request field, so pair this with
270
+ * {@link toAnthropicMessages} (which omits those turns). Returns `undefined`
271
+ * when there is no system/developer content.
272
+ *
273
+ * @example
274
+ * const res = await anthropic.messages.create(
275
+ * {
276
+ * ...prompt.config,
277
+ * system: toAnthropicSystem(prompt.messages),
278
+ * messages: toAnthropicMessages(prompt.messages),
279
+ * },
280
+ * { headers: to11.turn().headers(prompt) },
281
+ * );
207
282
  */
208
- export declare function toAnthropicMessages(messages: ReadonlyArray<RenderedChatMessage>): AnthropicMessage[];
283
+ export declare function toAnthropicSystem(messages: ReadonlyArray<RenderedChatMessage>): string | undefined;