@to11ai/sdk 1.0.0 → 2.0.0-rc.1

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 `FetchedPrompt`) 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,14 @@ 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
90
  }
91
91
  return headers;
92
92
  }
@@ -142,22 +142,44 @@ function requireToolCallId(id) {
142
142
  }
143
143
  return id;
144
144
  }
145
- function toOpenAIMessages(messages) {
146
- return messages.map((message) => {
145
+ var KNOWN_RENDERED_ROLES = new Set([
146
+ "system",
147
+ "user",
148
+ "assistant",
149
+ "developer",
150
+ "tool"
151
+ ]);
152
+ function skipUnknownRole(role, index, mode) {
153
+ if (KNOWN_RENDERED_ROLES.has(role))
154
+ return false;
155
+ if (mode === "error")
156
+ throw new import_errors.UnknownRoleError(role, index);
157
+ if (mode === "warn") {
158
+ console.warn(`[to11ai/sdk] dropped message[${index}] with unknown role ${JSON.stringify(role)} (expected system|user|assistant|developer|tool)`);
159
+ }
160
+ return true;
161
+ }
162
+ function toOpenAIMessages(messages, options = {}) {
163
+ const mode = options.onUnknownRole ?? "drop";
164
+ const out = [];
165
+ messages.forEach((message, index) => {
166
+ if (skipUnknownRole(message.role, index, mode))
167
+ return;
147
168
  if (message.role === "tool") {
148
- return {
169
+ out.push({
149
170
  role: "tool",
150
171
  tool_call_id: requireToolCallId(message.toolCallId),
151
172
  content: message.content ?? ""
152
- };
173
+ });
174
+ return;
153
175
  }
154
176
  if (message.role === "assistant") {
155
- const out = {
177
+ const msg = {
156
178
  role: "assistant",
157
179
  content: message.content ?? null
158
180
  };
159
181
  if (message.toolCalls != null && message.toolCalls.length > 0) {
160
- out.tool_calls = message.toolCalls.map((call) => ({
182
+ msg.tool_calls = message.toolCalls.map((call) => ({
161
183
  id: requireToolCallId(call.id),
162
184
  type: "function",
163
185
  function: {
@@ -166,17 +188,22 @@ function toOpenAIMessages(messages) {
166
188
  }
167
189
  }));
168
190
  }
169
- return out;
191
+ out.push(msg);
192
+ return;
170
193
  }
171
- return { role: message.role, content: message.content ?? "" };
194
+ out.push({ role: message.role, content: message.content ?? "" });
172
195
  });
196
+ return out;
173
197
  }
174
- function toAnthropicMessages(messages) {
198
+ function toAnthropicMessages(messages, options = {}) {
199
+ const mode = options.onUnknownRole ?? "drop";
175
200
  const out = [];
176
- for (const message of messages) {
201
+ messages.forEach((message, index) => {
177
202
  if (message.role === "system" || message.role === "developer") {
178
- continue;
203
+ return;
179
204
  }
205
+ if (skipUnknownRole(message.role, index, mode))
206
+ return;
180
207
  if (message.role === "tool") {
181
208
  out.push({
182
209
  role: "user",
@@ -188,7 +215,7 @@ function toAnthropicMessages(messages) {
188
215
  }
189
216
  ]
190
217
  });
191
- continue;
218
+ return;
192
219
  }
193
220
  if (message.role === "assistant") {
194
221
  if (message.toolCalls != null && message.toolCalls.length > 0) {
@@ -208,9 +235,22 @@ function toAnthropicMessages(messages) {
208
235
  } else {
209
236
  out.push({ role: "assistant", content: message.content ?? "" });
210
237
  }
211
- continue;
238
+ return;
212
239
  }
213
240
  out.push({ role: "user", content: message.content ?? "" });
214
- }
241
+ });
215
242
  return out;
216
243
  }
244
+ function toAnthropicSystem(messages) {
245
+ const parts = [];
246
+ for (const message of messages) {
247
+ if (message.role !== "system" && message.role !== "developer")
248
+ continue;
249
+ if (typeof message.content === "string" && message.content.length > 0) {
250
+ parts.push(message.content);
251
+ }
252
+ }
253
+ return parts.length > 0 ? parts.join(`
254
+
255
+ `) : undefined;
256
+ }
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,33 @@ 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
45
  *
46
- * The parameter type is WIDENED to accept either the enriched `fetch()`
46
+ * The parameter type is WIDENED to accept either the enriched `render()`
47
47
  * result or the legacy `ResolvedPrompt`. The new fields are read when present
48
48
  * and omitted otherwise, so an old `resolve()` result still produces exactly
49
49
  * the original three headers (non-breaking; D1/D7 shape coexistence).
50
50
  *
51
51
  * @example
52
- * const result = await client.prompts.fetch(slug, { env: "prod" });
52
+ * const result = await client.prompts.render(slug, { env: "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
+ /** Present on an enriched result; a legacy `resolve()` result omits it. */
61
+ releaseId?: string | null;
62
+ /** Present IFF the resolved release was weighted. */
63
+ variantName?: string;
64
+ }
65
+ export declare function gatewayPromptHeaders(prompt: GatewayPromptProvenance): Record<string, string>;
56
66
  /** OpenAI Chat Completions `tools[]` entry (`function` tool). */
57
67
  export interface OpenAITool {
58
68
  type: "function";
@@ -175,6 +185,16 @@ export declare function toAnthropicTools(tools: ReadonlyArray<ResolvedToolDefini
175
185
  * await anthropic.messages.create({ model, system, messages, tools, tool_choice });
176
186
  */
177
187
  export declare function toAnthropicToolChoice(toolChoice: ResolvedPrompt["toolChoice"]): AnthropicToolChoice | undefined;
188
+ /** Options shared by the message converters. */
189
+ export interface ConvertMessagesOptions {
190
+ /**
191
+ * Policy for a message whose role is outside the known set
192
+ * (`system | user | assistant | developer | tool`). `"drop"` (default) omits
193
+ * it silently; `"warn"` omits it and emits a `console.warn`; `"error"` throws
194
+ * {@link UnknownRoleError}. Both converters apply this identically.
195
+ */
196
+ onUnknownRole?: UnknownRoleMode;
197
+ }
178
198
  /**
179
199
  * Shape rendered turns for the OpenAI Chat Completions `messages` field.
180
200
  *
@@ -184,11 +204,15 @@ export declare function toAnthropicToolChoice(toolChoice: ResolvedPrompt["toolCh
184
204
  * become `tool_calls[]` with JSON-stringified `arguments` (the OpenAI wire
185
205
  * shape). A pure tool-call turn keeps `content: null`.
186
206
  * - `tool` (result) → `{ role: "tool", tool_call_id, content }`.
207
+ * - an unrecognized role is handled per `options.onUnknownRole` (default
208
+ * `"drop"`) — dropped, dropped-with-warning, or throws `UnknownRoleError`.
187
209
  *
188
210
  * @example
189
211
  * const messages = toOpenAIMessages(prompt.messages);
212
+ * @example Fail loudly on a malformed message array
213
+ * const messages = toOpenAIMessages(history, { onUnknownRole: "error" });
190
214
  */
191
- export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMessage>): OpenAIChatMessage[];
215
+ export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMessage>, options?: ConvertMessagesOptions): OpenAIChatMessage[];
192
216
  /**
193
217
  * Shape rendered turns for the Anthropic Messages `messages` field.
194
218
  *
@@ -201,8 +225,32 @@ export declare function toOpenAIMessages(messages: ReadonlyArray<RenderedChatMes
201
225
  * ...tool_use] }`; without calls → `{ role: "assistant", content }`.
202
226
  * - `tool` (result) → `{ role: "user", content: [{ type: "tool_result",
203
227
  * tool_use_id, content }] }`.
228
+ * - an unrecognized role is handled per `options.onUnknownRole` (default
229
+ * `"drop"`) — dropped, dropped-with-warning, or throws `UnknownRoleError` —
230
+ * identically to {@link toOpenAIMessages}. (`system`/`developer` are known
231
+ * roles folded into `system`, not "unknown"; they are never dropped here.)
204
232
  *
205
233
  * @example
206
234
  * const messages = toAnthropicMessages(prompt.messages);
235
+ * @example Fail loudly on a malformed message array
236
+ * const messages = toAnthropicMessages(history, { onUnknownRole: "error" });
237
+ */
238
+ export declare function toAnthropicMessages(messages: ReadonlyArray<RenderedChatMessage>, options?: ConvertMessagesOptions): AnthropicMessage[];
239
+ /**
240
+ * Fold the `system` and `developer` instruction turns into Anthropic's
241
+ * top-level `system` string. Anthropic has no `developer` role and takes system
242
+ * instructions as a separate request field, so pair this with
243
+ * {@link toAnthropicMessages} (which omits those turns). Returns `undefined`
244
+ * when there is no system/developer content.
245
+ *
246
+ * @example
247
+ * const res = await anthropic.messages.create(
248
+ * {
249
+ * ...prompt.config,
250
+ * system: toAnthropicSystem(prompt.messages),
251
+ * messages: toAnthropicMessages(prompt.messages),
252
+ * },
253
+ * { headers: to11.turn().headers(prompt) },
254
+ * );
207
255
  */
208
- export declare function toAnthropicMessages(messages: ReadonlyArray<RenderedChatMessage>): AnthropicMessage[];
256
+ 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,14 @@ 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;
42
41
  }
43
42
  return headers;
44
43
  }
@@ -94,22 +93,44 @@ function requireToolCallId(id) {
94
93
  }
95
94
  return id;
96
95
  }
97
- function toOpenAIMessages(messages) {
98
- return messages.map((message) => {
96
+ var KNOWN_RENDERED_ROLES = new Set([
97
+ "system",
98
+ "user",
99
+ "assistant",
100
+ "developer",
101
+ "tool"
102
+ ]);
103
+ function skipUnknownRole(role, index, mode) {
104
+ if (KNOWN_RENDERED_ROLES.has(role))
105
+ return false;
106
+ if (mode === "error")
107
+ throw new UnknownRoleError(role, index);
108
+ if (mode === "warn") {
109
+ console.warn(`[to11ai/sdk] dropped message[${index}] with unknown role ${JSON.stringify(role)} (expected system|user|assistant|developer|tool)`);
110
+ }
111
+ return true;
112
+ }
113
+ function toOpenAIMessages(messages, options = {}) {
114
+ const mode = options.onUnknownRole ?? "drop";
115
+ const out = [];
116
+ messages.forEach((message, index) => {
117
+ if (skipUnknownRole(message.role, index, mode))
118
+ return;
99
119
  if (message.role === "tool") {
100
- return {
120
+ out.push({
101
121
  role: "tool",
102
122
  tool_call_id: requireToolCallId(message.toolCallId),
103
123
  content: message.content ?? ""
104
- };
124
+ });
125
+ return;
105
126
  }
106
127
  if (message.role === "assistant") {
107
- const out = {
128
+ const msg = {
108
129
  role: "assistant",
109
130
  content: message.content ?? null
110
131
  };
111
132
  if (message.toolCalls != null && message.toolCalls.length > 0) {
112
- out.tool_calls = message.toolCalls.map((call) => ({
133
+ msg.tool_calls = message.toolCalls.map((call) => ({
113
134
  id: requireToolCallId(call.id),
114
135
  type: "function",
115
136
  function: {
@@ -118,17 +139,22 @@ function toOpenAIMessages(messages) {
118
139
  }
119
140
  }));
120
141
  }
121
- return out;
142
+ out.push(msg);
143
+ return;
122
144
  }
123
- return { role: message.role, content: message.content ?? "" };
145
+ out.push({ role: message.role, content: message.content ?? "" });
124
146
  });
147
+ return out;
125
148
  }
126
- function toAnthropicMessages(messages) {
149
+ function toAnthropicMessages(messages, options = {}) {
150
+ const mode = options.onUnknownRole ?? "drop";
127
151
  const out = [];
128
- for (const message of messages) {
152
+ messages.forEach((message, index) => {
129
153
  if (message.role === "system" || message.role === "developer") {
130
- continue;
154
+ return;
131
155
  }
156
+ if (skipUnknownRole(message.role, index, mode))
157
+ return;
132
158
  if (message.role === "tool") {
133
159
  out.push({
134
160
  role: "user",
@@ -140,7 +166,7 @@ function toAnthropicMessages(messages) {
140
166
  }
141
167
  ]
142
168
  });
143
- continue;
169
+ return;
144
170
  }
145
171
  if (message.role === "assistant") {
146
172
  if (message.toolCalls != null && message.toolCalls.length > 0) {
@@ -160,18 +186,32 @@ function toAnthropicMessages(messages) {
160
186
  } else {
161
187
  out.push({ role: "assistant", content: message.content ?? "" });
162
188
  }
163
- continue;
189
+ return;
164
190
  }
165
191
  out.push({ role: "user", content: message.content ?? "" });
166
- }
192
+ });
167
193
  return out;
168
194
  }
195
+ function toAnthropicSystem(messages) {
196
+ const parts = [];
197
+ for (const message of messages) {
198
+ if (message.role !== "system" && message.role !== "developer")
199
+ continue;
200
+ if (typeof message.content === "string" && message.content.length > 0) {
201
+ parts.push(message.content);
202
+ }
203
+ }
204
+ return parts.length > 0 ? parts.join(`
205
+
206
+ `) : undefined;
207
+ }
169
208
  export {
170
209
  toOpenAITools,
171
210
  toOpenAIToolChoice,
172
211
  toOpenAIMessages,
173
212
  toAnthropicTools,
174
213
  toAnthropicToolChoice,
214
+ toAnthropicSystem,
175
215
  toAnthropicMessages,
176
216
  gatewayPromptHeaders,
177
217
  gatewayAuthHeaders