@to11ai/sdk 1.1.0 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Observability context — the `session → conversation → turn` hierarchy that
3
+ * maps onto to11's telemetry grouping (Session → Conversation → Turn →
4
+ * LLM-Call). Each level provides a lazy default for the level below; explicit
5
+ * control is opt-in per use case. See
6
+ * docs/product-specs/2026-07-14-to11-2691-sdk-simpler-setup.md.
7
+ *
8
+ * Header-only: `turn.headers(prompt?)` returns a plain object the caller applies
9
+ * to their own provider client. It never sends a request.
10
+ */
11
+ import { type GatewayPromptProvenance } from "./gateway";
12
+ /** Auth/config the client hands to the context for gateway header building. */
13
+ export interface TurnAuthContext {
14
+ readonly apiKey: string;
15
+ readonly projectId: string;
16
+ readonly env?: string;
17
+ }
18
+ /**
19
+ * A prompt argument accepted by `turn.headers()`. Either the flat provenance
20
+ * (a neutral `RenderedPrompt`) or a `format`-shaped result carrying it under
21
+ * `metadata` — both are accepted so the format and neutral render paths carry
22
+ * provenance identically.
23
+ */
24
+ export type PromptForHeaders = GatewayPromptProvenance | {
25
+ metadata: GatewayPromptProvenance;
26
+ };
27
+ /**
28
+ * One agent turn — a single trace. A turn's `traceparent` is fixed for its
29
+ * lifetime, so every LLM-call made under it groups into one trace.
30
+ */
31
+ export declare class Turn {
32
+ private readonly auth;
33
+ readonly sessionId: string;
34
+ readonly conversationId: string;
35
+ /** The fixed W3C traceparent for this turn (one trace). */
36
+ readonly traceparent: string;
37
+ constructor(auth: TurnAuthContext, sessionId: string, conversationId: string, traceparent?: string);
38
+ /**
39
+ * The complete gateway header bag: auth + session + conversation +
40
+ * traceparent, plus prompt provenance when a `prompt` is supplied. A pure
41
+ * read of the turn's fixed ids — reuse it across a tool-loop or call per
42
+ * request; the result is identical.
43
+ *
44
+ * Only valid for requests targeting the gateway (`GATEWAY_URL`); it carries
45
+ * the to11 API key and must never be attached to a direct provider endpoint.
46
+ *
47
+ * @example One call (inline turn)
48
+ * const res = await openai.chat.completions.create(
49
+ * { ...prompt.config, messages: prompt.messages },
50
+ * { headers: to11.turn().headers(prompt) },
51
+ * );
52
+ *
53
+ * @example Tool loop — hoist the turn so every call shares one trace
54
+ * const turn = to11.turn();
55
+ * const headers = turn.headers(prompt);
56
+ * while (true) {
57
+ * const res = await openai.chat.completions.create({ ...prompt.config, messages, tools }, { headers });
58
+ * // …reuse `headers` for every call in the loop…
59
+ * }
60
+ */
61
+ headers(prompt?: PromptForHeaders): Record<string, string>;
62
+ }
63
+ /** One agent thread — a stable conversation id shared across all its turns. */
64
+ export declare class Conversation {
65
+ private readonly auth;
66
+ readonly sessionId: string;
67
+ /** Stable conversation id, reused by every turn created from this object. */
68
+ readonly id: string;
69
+ constructor(auth: TurnAuthContext, sessionId: string, id?: string);
70
+ /** Start a new turn (fresh trace) in this conversation. */
71
+ turn(): Turn;
72
+ }
73
+ /**
74
+ * One unit of work. Groups conversations under a single session id. A session
75
+ * lazily owns one default conversation, used by `session.turn()`; call
76
+ * `session.conversation()` for additional (side/parallel) agent threads.
77
+ */
78
+ export declare class Session {
79
+ private readonly auth;
80
+ /** Stable session id, carried by every conversation and turn beneath it. */
81
+ readonly id: string;
82
+ private defaultConversation;
83
+ constructor(auth: TurnAuthContext, id?: string);
84
+ /**
85
+ * Create a distinct conversation (a separate agent thread) in this session.
86
+ * Use this when one unit of work runs multiple agents (side / parallel
87
+ * agents): each conversation gets its own `x-to11-conversation-id`, all under
88
+ * the one session.
89
+ *
90
+ * @example Multiple agents in one session
91
+ * const session = to11.session();
92
+ * const main = session.conversation(); // main agent thread
93
+ * const research = session.conversation(); // separate research thread, same session
94
+ *
95
+ * await openai.chat.completions.create(
96
+ * { ...mainPrompt.config, messages: mainMessages },
97
+ * { headers: main.turn().headers(mainPrompt) },
98
+ * );
99
+ * await openai.chat.completions.create(
100
+ * { ...researchPrompt.config, messages: researchMessages },
101
+ * { headers: research.turn().headers(researchPrompt) },
102
+ * );
103
+ */
104
+ conversation(id?: string): Conversation;
105
+ /**
106
+ * Start a turn in the session's single implicit conversation. The default
107
+ * conversation is created lazily on first call and reused, so consecutive
108
+ * `session.turn()` calls share one `x-to11-conversation-id` (each with its
109
+ * own `traceparent`).
110
+ *
111
+ * @example Multi-turn chat — every turn shares the session's conversation
112
+ * const session = to11.session();
113
+ * for await (const input of inputs) {
114
+ * messages.push({ role: "user", content: input });
115
+ * const turn = session.turn(); // fresh trace, same conversation id
116
+ * const res = await openai.chat.completions.create(
117
+ * { ...prompt.config, messages },
118
+ * { headers: turn.headers(prompt) },
119
+ * );
120
+ * messages.push(res.choices[0].message);
121
+ * }
122
+ */
123
+ turn(): Turn;
124
+ }
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,
@@ -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;
@@ -218,7 +235,7 @@ class EnvNotFoundError extends To11aiApiError {
218
235
  Object.setPrototypeOf(this, new.target.prototype);
219
236
  }
220
237
  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);
238
+ return new EnvNotFoundError("[to11ai/sdk] No environment resolvable for prompts.render(): none supplied at client init and none supplied per call. Pass `env` to createClient or render(slug, { env }).", { env, source: "missing" }, undefined, 0);
222
239
  }
223
240
  static is(err) {
224
241
  return err instanceof EnvNotFoundError;
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
  *
@@ -168,7 +185,7 @@ export interface EnvNotFoundReason {
168
185
  source: "missing" | "unknown";
169
186
  }
170
187
  /**
171
- * Thrown when no serving environment can be resolved for a `prompts.fetch()`
188
+ * Thrown when no serving environment can be resolved for a `prompts.render()`
172
189
  * call. One class, two `reason.source` discriminants (see
173
190
  * `EnvNotFoundReason`): client-eager `"missing"` (R4) and server-forwarded
174
191
  * `"unknown"` (`ENV_NOT_FOUND`).
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;
@@ -158,7 +174,7 @@ class EnvNotFoundError extends To11aiApiError {
158
174
  Object.setPrototypeOf(this, new.target.prototype);
159
175
  }
160
176
  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);
177
+ return new EnvNotFoundError("[to11ai/sdk] No environment resolvable for prompts.render(): none supplied at client init and none supplied per call. Pass `env` to createClient or render(slug, { env }).", { env, source: "missing" }, undefined, 0);
162
178
  }
163
179
  static is(err) {
164
180
  return err instanceof EnvNotFoundError;
@@ -221,6 +237,7 @@ class RuleVersionInvalidSnapshotError extends To11aiApiError {
221
237
  }
222
238
  }
223
239
  export {
240
+ UnknownRoleError,
224
241
  To11aiValidationError,
225
242
  To11aiResponseValidationError,
226
243
  To11aiRateLimitError,
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) {
@@ -77,16 +79,17 @@ function gatewayPromptHeaders(prompt) {
77
79
  "x-to11-prompt-id": prompt.promptId,
78
80
  "x-to11-prompt-version": String(prompt.version)
79
81
  };
80
- const releaseId = prompt.releaseId;
81
- if (typeof releaseId === "string" && releaseId.length > 0) {
82
- headers["x-to11-release-id"] = releaseId;
82
+ if (typeof prompt.releaseId === "string" && prompt.releaseId.length > 0) {
83
+ headers["x-to11-release-id"] = prompt.releaseId;
83
84
  }
84
- const variantName = prompt.variantName;
85
- if (typeof variantName === "string" && variantName.length > 0) {
86
- headers["x-to11-variant-name"] = variantName;
85
+ if (typeof prompt.variantName === "string" && prompt.variantName.length > 0) {
86
+ headers["x-to11-variant-name"] = prompt.variantName;
87
87
  }
88
- if (prompt.labels.length > 0) {
89
- headers["x-to11-prompt-labels"] = prompt.labels.join(",");
88
+ if (prompt.label.length > 0) {
89
+ headers["x-to11-prompt-label"] = prompt.label;
90
+ }
91
+ if (typeof prompt.slug === "string" && prompt.slug.length > 0) {
92
+ headers["x-to11-prompt-slug"] = prompt.slug;
90
93
  }
91
94
  return headers;
92
95
  }
@@ -142,22 +145,44 @@ function requireToolCallId(id) {
142
145
  }
143
146
  return id;
144
147
  }
145
- function toOpenAIMessages(messages) {
146
- return messages.map((message) => {
148
+ var KNOWN_RENDERED_ROLES = new Set([
149
+ "system",
150
+ "user",
151
+ "assistant",
152
+ "developer",
153
+ "tool"
154
+ ]);
155
+ function skipUnknownRole(role, index, mode) {
156
+ if (KNOWN_RENDERED_ROLES.has(role))
157
+ return false;
158
+ if (mode === "error")
159
+ throw new import_errors.UnknownRoleError(role, index);
160
+ if (mode === "warn") {
161
+ console.warn(`[to11ai/sdk] dropped message[${index}] with unknown role ${JSON.stringify(role)} (expected system|user|assistant|developer|tool)`);
162
+ }
163
+ return true;
164
+ }
165
+ function toOpenAIMessages(messages, options = {}) {
166
+ const mode = options.onUnknownRole ?? "drop";
167
+ const out = [];
168
+ messages.forEach((message, index) => {
169
+ if (skipUnknownRole(message.role, index, mode))
170
+ return;
147
171
  if (message.role === "tool") {
148
- return {
172
+ out.push({
149
173
  role: "tool",
150
174
  tool_call_id: requireToolCallId(message.toolCallId),
151
175
  content: message.content ?? ""
152
- };
176
+ });
177
+ return;
153
178
  }
154
179
  if (message.role === "assistant") {
155
- const out = {
180
+ const msg = {
156
181
  role: "assistant",
157
182
  content: message.content ?? null
158
183
  };
159
184
  if (message.toolCalls != null && message.toolCalls.length > 0) {
160
- out.tool_calls = message.toolCalls.map((call) => ({
185
+ msg.tool_calls = message.toolCalls.map((call) => ({
161
186
  id: requireToolCallId(call.id),
162
187
  type: "function",
163
188
  function: {
@@ -166,17 +191,22 @@ function toOpenAIMessages(messages) {
166
191
  }
167
192
  }));
168
193
  }
169
- return out;
194
+ out.push(msg);
195
+ return;
170
196
  }
171
- return { role: message.role, content: message.content ?? "" };
197
+ out.push({ role: message.role, content: message.content ?? "" });
172
198
  });
199
+ return out;
173
200
  }
174
- function toAnthropicMessages(messages) {
201
+ function toAnthropicMessages(messages, options = {}) {
202
+ const mode = options.onUnknownRole ?? "drop";
175
203
  const out = [];
176
- for (const message of messages) {
204
+ messages.forEach((message, index) => {
177
205
  if (message.role === "system" || message.role === "developer") {
178
- continue;
206
+ return;
179
207
  }
208
+ if (skipUnknownRole(message.role, index, mode))
209
+ return;
180
210
  if (message.role === "tool") {
181
211
  out.push({
182
212
  role: "user",
@@ -188,7 +218,7 @@ function toAnthropicMessages(messages) {
188
218
  }
189
219
  ]
190
220
  });
191
- continue;
221
+ return;
192
222
  }
193
223
  if (message.role === "assistant") {
194
224
  if (message.toolCalls != null && message.toolCalls.length > 0) {
@@ -208,9 +238,22 @@ function toAnthropicMessages(messages) {
208
238
  } else {
209
239
  out.push({ role: "assistant", content: message.content ?? "" });
210
240
  }
211
- continue;
241
+ return;
212
242
  }
213
243
  out.push({ role: "user", content: message.content ?? "" });
214
- }
244
+ });
215
245
  return out;
216
246
  }
247
+ function toAnthropicSystem(messages) {
248
+ const parts = [];
249
+ for (const message of messages) {
250
+ if (message.role !== "system" && message.role !== "developer")
251
+ continue;
252
+ if (typeof message.content === "string" && message.content.length > 0) {
253
+ parts.push(message.content);
254
+ }
255
+ }
256
+ return parts.length > 0 ? parts.join(`
257
+
258
+ `) : undefined;
259
+ }
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;
@@ -36,23 +36,41 @@ export declare function gatewayAuthHeaders(options: GatewayAuthOptions): Record<
36
36
  * follows from a resolved prompt:
37
37
  * - `x-to11-prompt-id`, `x-to11-prompt-version` — always.
38
38
  * - `x-to11-release-id` — when the result carries a non-null `releaseId`
39
- * (the enriched `fetch()` result; a legacy `resolve()` result has no
39
+ * (the enriched `render()` result; a legacy `resolve()` result has no
40
40
  * `releaseId`, so it is omitted — non-breaking).
41
41
  * - `x-to11-variant-name` — IFF `variantName` is present (i.e. the resolved
42
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.
43
+ * - `x-to11-prompt-label` — the single label the prompt resolved through (the
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
- * The parameter type is WIDENED to accept either the enriched `fetch()`
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
48
51
  * and omitted otherwise, so an old `resolve()` result still produces exactly
49
52
  * the original three headers (non-breaking; D1/D7 shape coexistence).
50
53
  *
51
54
  * @example
52
- * const result = await client.prompts.fetch(slug, { env: "prod" });
55
+ * const result = await client.prompts.render(slug, { env: "prod" });
53
56
  * const headers = gatewayPromptHeaders(result);
54
57
  */
55
- export declare function gatewayPromptHeaders(prompt: ResolvedPrompt | FetchedPrompt): Record<string, string>;
58
+ export interface GatewayPromptProvenance {
59
+ promptId: string;
60
+ version: number;
61
+ /** The single label the prompt resolved through (env slug today). */
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;
68
+ /** Present on an enriched result; a legacy `resolve()` result omits it. */
69
+ releaseId?: string | null;
70
+ /** Present IFF the resolved release was weighted. */
71
+ variantName?: string;
72
+ }
73
+ export declare function gatewayPromptHeaders(prompt: GatewayPromptProvenance): Record<string, string>;
56
74
  /** OpenAI Chat Completions `tools[]` entry (`function` tool). */
57
75
  export interface OpenAITool {
58
76
  type: "function";
@@ -175,6 +193,16 @@ export declare function toAnthropicTools(tools: ReadonlyArray<ResolvedToolDefini
175
193
  * await anthropic.messages.create({ model, system, messages, tools, tool_choice });
176
194
  */
177
195
  export declare function toAnthropicToolChoice(toolChoice: ResolvedPrompt["toolChoice"]): AnthropicToolChoice | undefined;
196
+ /** Options shared by the message converters. */
197
+ export interface ConvertMessagesOptions {
198
+ /**
199
+ * Policy for a message whose role is outside the known set
200
+ * (`system | user | assistant | developer | tool`). `"drop"` (default) omits
201
+ * it silently; `"warn"` omits it and emits a `console.warn`; `"error"` throws
202
+ * {@link UnknownRoleError}. Both converters apply this identically.
203
+ */
204
+ onUnknownRole?: UnknownRoleMode;
205
+ }
178
206
  /**
179
207
  * Shape rendered turns for the OpenAI Chat Completions `messages` field.
180
208
  *
@@ -184,11 +212,15 @@ export declare function toAnthropicToolChoice(toolChoice: ResolvedPrompt["toolCh
184
212
  * become `tool_calls[]` with JSON-stringified `arguments` (the OpenAI wire
185
213
  * shape). A pure tool-call turn keeps `content: null`.
186
214
  * - `tool` (result) → `{ role: "tool", tool_call_id, content }`.
215
+ * - an unrecognized role is handled per `options.onUnknownRole` (default
216
+ * `"drop"`) — dropped, dropped-with-warning, or throws `UnknownRoleError`.
187
217
  *
188
218
  * @example
189
219
  * const messages = toOpenAIMessages(prompt.messages);
220
+ * @example Fail loudly on a malformed message array
221
+ * const messages = toOpenAIMessages(history, { onUnknownRole: "error" });
190
222
  */
191
- export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMessage>): OpenAIChatMessage[];
223
+ export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMessage>, options?: ConvertMessagesOptions): OpenAIChatMessage[];
192
224
  /**
193
225
  * Shape rendered turns for the Anthropic Messages `messages` field.
194
226
  *
@@ -201,8 +233,32 @@ export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMes
201
233
  * ...tool_use] }`; without calls → `{ role: "assistant", content }`.
202
234
  * - `tool` (result) → `{ role: "user", content: [{ type: "tool_result",
203
235
  * tool_use_id, content }] }`.
236
+ * - an unrecognized role is handled per `options.onUnknownRole` (default
237
+ * `"drop"`) — dropped, dropped-with-warning, or throws `UnknownRoleError` —
238
+ * identically to {@link toOpenAIMessages}. (`system`/`developer` are known
239
+ * roles folded into `system`, not "unknown"; they are never dropped here.)
204
240
  *
205
241
  * @example
206
242
  * const messages = toAnthropicMessages(prompt.messages);
243
+ * @example Fail loudly on a malformed message array
244
+ * const messages = toAnthropicMessages(history, { onUnknownRole: "error" });
245
+ */
246
+ export declare function toAnthropicMessages(messages: ReadonlyArray<RenderedChatMessage>, options?: ConvertMessagesOptions): AnthropicMessage[];
247
+ /**
248
+ * Fold the `system` and `developer` instruction turns into Anthropic's
249
+ * top-level `system` string. Anthropic has no `developer` role and takes system
250
+ * instructions as a separate request field, so pair this with
251
+ * {@link toAnthropicMessages} (which omits those turns). Returns `undefined`
252
+ * when there is no system/developer content.
253
+ *
254
+ * @example
255
+ * const res = await anthropic.messages.create(
256
+ * {
257
+ * ...prompt.config,
258
+ * system: toAnthropicSystem(prompt.messages),
259
+ * messages: toAnthropicMessages(prompt.messages),
260
+ * },
261
+ * { headers: to11.turn().headers(prompt) },
262
+ * );
207
263
  */
208
- export declare function toAnthropicMessages(messages: ReadonlyArray<RenderedChatMessage>): AnthropicMessage[];
264
+ export declare function toAnthropicSystem(messages: ReadonlyArray<RenderedChatMessage>): string | undefined;
package/dist/gateway.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/gateway.ts
2
+ import { UnknownRoleError } from "./errors";
2
3
  import {
3
4
  ENV_SLUG_REGEX
4
5
  } from "./prompts/types";
@@ -29,16 +30,17 @@ function gatewayPromptHeaders(prompt) {
29
30
  "x-to11-prompt-id": prompt.promptId,
30
31
  "x-to11-prompt-version": String(prompt.version)
31
32
  };
32
- const releaseId = prompt.releaseId;
33
- if (typeof releaseId === "string" && releaseId.length > 0) {
34
- headers["x-to11-release-id"] = releaseId;
33
+ if (typeof prompt.releaseId === "string" && prompt.releaseId.length > 0) {
34
+ headers["x-to11-release-id"] = prompt.releaseId;
35
35
  }
36
- const variantName = prompt.variantName;
37
- if (typeof variantName === "string" && variantName.length > 0) {
38
- headers["x-to11-variant-name"] = variantName;
36
+ if (typeof prompt.variantName === "string" && prompt.variantName.length > 0) {
37
+ headers["x-to11-variant-name"] = prompt.variantName;
39
38
  }
40
- if (prompt.labels.length > 0) {
41
- headers["x-to11-prompt-labels"] = prompt.labels.join(",");
39
+ if (prompt.label.length > 0) {
40
+ headers["x-to11-prompt-label"] = prompt.label;
41
+ }
42
+ if (typeof prompt.slug === "string" && prompt.slug.length > 0) {
43
+ headers["x-to11-prompt-slug"] = prompt.slug;
42
44
  }
43
45
  return headers;
44
46
  }
@@ -94,22 +96,44 @@ function requireToolCallId(id) {
94
96
  }
95
97
  return id;
96
98
  }
97
- function toOpenAIMessages(messages) {
98
- return messages.map((message) => {
99
+ var KNOWN_RENDERED_ROLES = new Set([
100
+ "system",
101
+ "user",
102
+ "assistant",
103
+ "developer",
104
+ "tool"
105
+ ]);
106
+ function skipUnknownRole(role, index, mode) {
107
+ if (KNOWN_RENDERED_ROLES.has(role))
108
+ return false;
109
+ if (mode === "error")
110
+ throw new UnknownRoleError(role, index);
111
+ if (mode === "warn") {
112
+ console.warn(`[to11ai/sdk] dropped message[${index}] with unknown role ${JSON.stringify(role)} (expected system|user|assistant|developer|tool)`);
113
+ }
114
+ return true;
115
+ }
116
+ function toOpenAIMessages(messages, options = {}) {
117
+ const mode = options.onUnknownRole ?? "drop";
118
+ const out = [];
119
+ messages.forEach((message, index) => {
120
+ if (skipUnknownRole(message.role, index, mode))
121
+ return;
99
122
  if (message.role === "tool") {
100
- return {
123
+ out.push({
101
124
  role: "tool",
102
125
  tool_call_id: requireToolCallId(message.toolCallId),
103
126
  content: message.content ?? ""
104
- };
127
+ });
128
+ return;
105
129
  }
106
130
  if (message.role === "assistant") {
107
- const out = {
131
+ const msg = {
108
132
  role: "assistant",
109
133
  content: message.content ?? null
110
134
  };
111
135
  if (message.toolCalls != null && message.toolCalls.length > 0) {
112
- out.tool_calls = message.toolCalls.map((call) => ({
136
+ msg.tool_calls = message.toolCalls.map((call) => ({
113
137
  id: requireToolCallId(call.id),
114
138
  type: "function",
115
139
  function: {
@@ -118,17 +142,22 @@ function toOpenAIMessages(messages) {
118
142
  }
119
143
  }));
120
144
  }
121
- return out;
145
+ out.push(msg);
146
+ return;
122
147
  }
123
- return { role: message.role, content: message.content ?? "" };
148
+ out.push({ role: message.role, content: message.content ?? "" });
124
149
  });
150
+ return out;
125
151
  }
126
- function toAnthropicMessages(messages) {
152
+ function toAnthropicMessages(messages, options = {}) {
153
+ const mode = options.onUnknownRole ?? "drop";
127
154
  const out = [];
128
- for (const message of messages) {
155
+ messages.forEach((message, index) => {
129
156
  if (message.role === "system" || message.role === "developer") {
130
- continue;
157
+ return;
131
158
  }
159
+ if (skipUnknownRole(message.role, index, mode))
160
+ return;
132
161
  if (message.role === "tool") {
133
162
  out.push({
134
163
  role: "user",
@@ -140,7 +169,7 @@ function toAnthropicMessages(messages) {
140
169
  }
141
170
  ]
142
171
  });
143
- continue;
172
+ return;
144
173
  }
145
174
  if (message.role === "assistant") {
146
175
  if (message.toolCalls != null && message.toolCalls.length > 0) {
@@ -160,18 +189,32 @@ function toAnthropicMessages(messages) {
160
189
  } else {
161
190
  out.push({ role: "assistant", content: message.content ?? "" });
162
191
  }
163
- continue;
192
+ return;
164
193
  }
165
194
  out.push({ role: "user", content: message.content ?? "" });
166
- }
195
+ });
167
196
  return out;
168
197
  }
198
+ function toAnthropicSystem(messages) {
199
+ const parts = [];
200
+ for (const message of messages) {
201
+ if (message.role !== "system" && message.role !== "developer")
202
+ continue;
203
+ if (typeof message.content === "string" && message.content.length > 0) {
204
+ parts.push(message.content);
205
+ }
206
+ }
207
+ return parts.length > 0 ? parts.join(`
208
+
209
+ `) : undefined;
210
+ }
169
211
  export {
170
212
  toOpenAITools,
171
213
  toOpenAIToolChoice,
172
214
  toOpenAIMessages,
173
215
  toAnthropicTools,
174
216
  toAnthropicToolChoice,
217
+ toAnthropicSystem,
175
218
  toAnthropicMessages,
176
219
  gatewayPromptHeaders,
177
220
  gatewayAuthHeaders