@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/README.md +99 -258
- package/dist/client.cjs +91 -45
- package/dist/client.d.ts +149 -61
- package/dist/client.js +95 -55
- package/dist/config.d.ts +9 -0
- package/dist/context.d.ts +124 -0
- package/dist/errors.cjs +36 -24
- package/dist/errors.d.ts +35 -46
- package/dist/errors.js +36 -24
- package/dist/gateway.cjs +85 -31
- package/dist/gateway.d.ts +102 -27
- package/dist/gateway.js +85 -31
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/types.gen.d.ts +2802 -502
- package/dist/generated/zod.gen.d.ts +1018 -243
- package/dist/prompts/resource.cjs +80 -113
- package/dist/prompts/resource.d.ts +60 -39
- package/dist/prompts/resource.js +84 -113
- package/dist/prompts/shape.d.ts +56 -0
- package/dist/prompts/types.d.ts +76 -61
- package/dist/transport.d.ts +2 -6
- package/package.json +5 -11
- package/src/client.ts +332 -157
- package/src/config.ts +13 -0
- package/src/context.ts +196 -0
- package/src/errors.ts +65 -79
- package/src/gateway.ts +212 -67
- package/src/generated/index.ts +1 -1
- package/src/generated/types.gen.ts +2977 -611
- package/src/generated/zod.gen.ts +1029 -338
- package/src/prompts/resource.ts +211 -226
- package/src/prompts/shape.ts +152 -0
- package/src/prompts/types.ts +77 -65
- package/src/transport.ts +53 -40
- package/dist/prompts/cache.d.ts +0 -59
- package/dist/routingRules/resource.cjs +0 -138
- package/dist/routingRules/resource.js +0 -98
- package/src/prompts/cache.ts +0 -157
package/src/config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal default endpoints. Deliberately NOT re-exported from the package
|
|
3
|
+
* root: `createClient` resolves the URLs (option → env → default) and exposes
|
|
4
|
+
* the resolved data-plane URL as `to11.gatewayUrl`, so app code never imports a
|
|
5
|
+
* bare URL constant. Keeping these out of the public surface avoids a JS-only
|
|
6
|
+
* exported symbol that would need a Python-parity record.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_API_URL = "https://api.to11.ai";
|
|
10
|
+
// The gateway *root* — no `/v1` version path. Provider helpers add the path each
|
|
11
|
+
// provider SDK expects (`openaiOptions()` appends `/v1`; the Anthropic SDK adds
|
|
12
|
+
// `/v1/messages` itself), so users configure only the host.
|
|
13
|
+
export const DEFAULT_GATEWAY_URL = "https://gw.to11.ai";
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
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
|
+
|
|
12
|
+
import {
|
|
13
|
+
type GatewayPromptProvenance,
|
|
14
|
+
gatewayAuthHeaders,
|
|
15
|
+
gatewayPromptHeaders,
|
|
16
|
+
} from "./gateway";
|
|
17
|
+
|
|
18
|
+
/** Auth/config the client hands to the context for gateway header building. */
|
|
19
|
+
export interface TurnAuthContext {
|
|
20
|
+
readonly apiKey: string;
|
|
21
|
+
readonly projectId: string;
|
|
22
|
+
readonly env?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A prompt argument accepted by `turn.headers()`. Either the flat provenance
|
|
27
|
+
* (a neutral `RenderedPrompt`) or a `format`-shaped result carrying it under
|
|
28
|
+
* `metadata` — both are accepted so the format and neutral render paths carry
|
|
29
|
+
* provenance identically.
|
|
30
|
+
*/
|
|
31
|
+
export type PromptForHeaders =
|
|
32
|
+
| GatewayPromptProvenance
|
|
33
|
+
| { metadata: GatewayPromptProvenance };
|
|
34
|
+
|
|
35
|
+
function randomHex(bytes: number): string {
|
|
36
|
+
const buf = new Uint8Array(bytes);
|
|
37
|
+
crypto.getRandomValues(buf);
|
|
38
|
+
let out = "";
|
|
39
|
+
for (const b of buf) out += b.toString(16).padStart(2, "0");
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A fresh, sampled W3C `traceparent` (`00-<32hex>-<16hex>-01`). */
|
|
44
|
+
function newTraceparent(): string {
|
|
45
|
+
return `00-${randomHex(16)}-${randomHex(8)}-01`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function newId(): string {
|
|
49
|
+
return crypto.randomUUID();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One agent turn — a single trace. A turn's `traceparent` is fixed for its
|
|
54
|
+
* lifetime, so every LLM-call made under it groups into one trace.
|
|
55
|
+
*/
|
|
56
|
+
export class Turn {
|
|
57
|
+
/** The fixed W3C traceparent for this turn (one trace). */
|
|
58
|
+
readonly traceparent: string;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
private readonly auth: TurnAuthContext,
|
|
62
|
+
readonly sessionId: string,
|
|
63
|
+
readonly conversationId: string,
|
|
64
|
+
traceparent?: string,
|
|
65
|
+
) {
|
|
66
|
+
this.traceparent = traceparent ?? newTraceparent();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The complete gateway header bag: auth + session + conversation +
|
|
71
|
+
* traceparent, plus prompt provenance when a `prompt` is supplied. A pure
|
|
72
|
+
* read of the turn's fixed ids — reuse it across a tool-loop or call per
|
|
73
|
+
* request; the result is identical.
|
|
74
|
+
*
|
|
75
|
+
* Only valid for requests targeting the gateway (`GATEWAY_URL`); it carries
|
|
76
|
+
* the to11 API key and must never be attached to a direct provider endpoint.
|
|
77
|
+
*
|
|
78
|
+
* @example One call (inline turn)
|
|
79
|
+
* const res = await openai.chat.completions.create(
|
|
80
|
+
* { ...prompt.config, messages: prompt.messages },
|
|
81
|
+
* { headers: to11.turn().headers(prompt) },
|
|
82
|
+
* );
|
|
83
|
+
*
|
|
84
|
+
* @example Tool loop — hoist the turn so every call shares one trace
|
|
85
|
+
* const turn = to11.turn();
|
|
86
|
+
* const headers = turn.headers(prompt);
|
|
87
|
+
* while (true) {
|
|
88
|
+
* const res = await openai.chat.completions.create({ ...prompt.config, messages, tools }, { headers });
|
|
89
|
+
* // …reuse `headers` for every call in the loop…
|
|
90
|
+
* }
|
|
91
|
+
*/
|
|
92
|
+
headers(prompt?: PromptForHeaders): Record<string, string> {
|
|
93
|
+
const headers: Record<string, string> = {
|
|
94
|
+
...gatewayAuthHeaders({
|
|
95
|
+
apiKey: this.auth.apiKey,
|
|
96
|
+
projectId: this.auth.projectId,
|
|
97
|
+
...(this.auth.env !== undefined ? { env: this.auth.env } : {}),
|
|
98
|
+
}),
|
|
99
|
+
"x-to11-session-id": this.sessionId,
|
|
100
|
+
"x-to11-conversation-id": this.conversationId,
|
|
101
|
+
traceparent: this.traceparent,
|
|
102
|
+
};
|
|
103
|
+
if (prompt !== undefined) {
|
|
104
|
+
// gatewayPromptHeaders accepts a RenderedPrompt or a format-shaped
|
|
105
|
+
// result and unwraps `metadata` itself.
|
|
106
|
+
Object.assign(headers, gatewayPromptHeaders(prompt));
|
|
107
|
+
}
|
|
108
|
+
return headers;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** One agent thread — a stable conversation id shared across all its turns. */
|
|
113
|
+
export class Conversation {
|
|
114
|
+
/** Stable conversation id, reused by every turn created from this object. */
|
|
115
|
+
readonly id: string;
|
|
116
|
+
|
|
117
|
+
constructor(
|
|
118
|
+
private readonly auth: TurnAuthContext,
|
|
119
|
+
readonly sessionId: string,
|
|
120
|
+
id?: string,
|
|
121
|
+
) {
|
|
122
|
+
this.id = id ?? newId();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Start a new turn (fresh trace) in this conversation. */
|
|
126
|
+
turn(): Turn {
|
|
127
|
+
return new Turn(this.auth, this.sessionId, this.id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* One unit of work. Groups conversations under a single session id. A session
|
|
133
|
+
* lazily owns one default conversation, used by `session.turn()`; call
|
|
134
|
+
* `session.conversation()` for additional (side/parallel) agent threads.
|
|
135
|
+
*/
|
|
136
|
+
export class Session {
|
|
137
|
+
/** Stable session id, carried by every conversation and turn beneath it. */
|
|
138
|
+
readonly id: string;
|
|
139
|
+
private defaultConversation: Conversation | undefined;
|
|
140
|
+
|
|
141
|
+
constructor(
|
|
142
|
+
private readonly auth: TurnAuthContext,
|
|
143
|
+
id?: string,
|
|
144
|
+
) {
|
|
145
|
+
this.id = id ?? newId();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a distinct conversation (a separate agent thread) in this session.
|
|
150
|
+
* Use this when one unit of work runs multiple agents (side / parallel
|
|
151
|
+
* agents): each conversation gets its own `x-to11-conversation-id`, all under
|
|
152
|
+
* the one session.
|
|
153
|
+
*
|
|
154
|
+
* @example Multiple agents in one session
|
|
155
|
+
* const session = to11.session();
|
|
156
|
+
* const main = session.conversation(); // main agent thread
|
|
157
|
+
* const research = session.conversation(); // separate research thread, same session
|
|
158
|
+
*
|
|
159
|
+
* await openai.chat.completions.create(
|
|
160
|
+
* { ...mainPrompt.config, messages: mainMessages },
|
|
161
|
+
* { headers: main.turn().headers(mainPrompt) },
|
|
162
|
+
* );
|
|
163
|
+
* await openai.chat.completions.create(
|
|
164
|
+
* { ...researchPrompt.config, messages: researchMessages },
|
|
165
|
+
* { headers: research.turn().headers(researchPrompt) },
|
|
166
|
+
* );
|
|
167
|
+
*/
|
|
168
|
+
conversation(id?: string): Conversation {
|
|
169
|
+
return new Conversation(this.auth, this.id, id);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Start a turn in the session's single implicit conversation. The default
|
|
174
|
+
* conversation is created lazily on first call and reused, so consecutive
|
|
175
|
+
* `session.turn()` calls share one `x-to11-conversation-id` (each with its
|
|
176
|
+
* own `traceparent`).
|
|
177
|
+
*
|
|
178
|
+
* @example Multi-turn chat — every turn shares the session's conversation
|
|
179
|
+
* const session = to11.session();
|
|
180
|
+
* for await (const input of inputs) {
|
|
181
|
+
* messages.push({ role: "user", content: input });
|
|
182
|
+
* const turn = session.turn(); // fresh trace, same conversation id
|
|
183
|
+
* const res = await openai.chat.completions.create(
|
|
184
|
+
* { ...prompt.config, messages },
|
|
185
|
+
* { headers: turn.headers(prompt) },
|
|
186
|
+
* );
|
|
187
|
+
* messages.push(res.choices[0].message);
|
|
188
|
+
* }
|
|
189
|
+
*/
|
|
190
|
+
turn(): Turn {
|
|
191
|
+
if (this.defaultConversation === undefined) {
|
|
192
|
+
this.defaultConversation = this.conversation();
|
|
193
|
+
}
|
|
194
|
+
return this.defaultConversation.turn();
|
|
195
|
+
}
|
|
196
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -113,6 +113,36 @@ export class To11aiResponseValidationError extends To11aiError {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Thrown by the provider-shape converters (`toOpenAIMessages` /
|
|
118
|
+
* `toAnthropicMessages`, and `render({ format })`) in `onUnknownRole: "error"`
|
|
119
|
+
* mode when a message carries a role outside the known set
|
|
120
|
+
* (`system | user | assistant | developer | tool`). `code` is the stable
|
|
121
|
+
* `"unknown_role"` slug; `role` / `index` locate the offending turn.
|
|
122
|
+
*
|
|
123
|
+
* A client-side conversion error — it extends `To11aiError` (not
|
|
124
|
+
* `To11aiApiError`), so it never carries an HTTP status.
|
|
125
|
+
*/
|
|
126
|
+
export class UnknownRoleError extends To11aiError {
|
|
127
|
+
readonly code = "unknown_role";
|
|
128
|
+
readonly role: string;
|
|
129
|
+
readonly index: number;
|
|
130
|
+
|
|
131
|
+
constructor(role: string, index: number) {
|
|
132
|
+
super(
|
|
133
|
+
`[to11ai/sdk] message[${index}] has unknown role ${JSON.stringify(role)}; expected system|user|assistant|developer|tool`,
|
|
134
|
+
);
|
|
135
|
+
this.name = "UnknownRoleError";
|
|
136
|
+
this.role = role;
|
|
137
|
+
this.index = index;
|
|
138
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static is(err: unknown): err is UnknownRoleError {
|
|
142
|
+
return err instanceof UnknownRoleError;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
116
146
|
/**
|
|
117
147
|
* Thrown when strict-mode rejects a label move for an unregistered label.
|
|
118
148
|
*
|
|
@@ -252,7 +282,7 @@ export class EnvNotEnabledForProviderError extends To11aiApiError {
|
|
|
252
282
|
|
|
253
283
|
// ─── Prompt resolution typed errors (TO11-980 / S9) ────────────────────────
|
|
254
284
|
//
|
|
255
|
-
// These four subclasses give `prompts.
|
|
285
|
+
// These four subclasses give `prompts.render()` callers a discriminable error
|
|
256
286
|
// family for prompt resolution. Each:
|
|
257
287
|
// - extends `To11aiApiError`, so existing broad `catch (err instanceof
|
|
258
288
|
// To11aiApiError)` blocks keep matching (AC8),
|
|
@@ -312,60 +342,54 @@ export class PromptNotFoundError extends To11aiApiError {
|
|
|
312
342
|
}
|
|
313
343
|
|
|
314
344
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* `
|
|
318
|
-
*
|
|
319
|
-
* CLIENT-side, eagerly, before any request (R4 / AC4); `env` is absent.
|
|
320
|
-
* - `"unknown"` — an env was supplied but the server's registry has no
|
|
321
|
-
* such (active) env. Surfaced from the `ENV_NOT_FOUND` server code; `env`
|
|
322
|
-
* carries the rejected slug when the server echoes it.
|
|
323
|
-
*/
|
|
324
|
-
export interface EnvNotFoundReason {
|
|
325
|
-
env?: string;
|
|
326
|
-
source: "missing" | "unknown";
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Thrown when no serving environment can be resolved for a `prompts.fetch()`
|
|
331
|
-
* call. One class, two `reason.source` discriminants (see
|
|
332
|
-
* `EnvNotFoundReason`): client-eager `"missing"` (R4) and server-forwarded
|
|
333
|
-
* `"unknown"` (`ENV_NOT_FOUND`).
|
|
345
|
+
* Thrown when the label supplied to `prompts.render()` does not resolve to an
|
|
346
|
+
* existing (active) label on the server. Surfaced from the server's
|
|
347
|
+
* `ENV_NOT_FOUND` code; `label` carries the rejected slug when the server
|
|
348
|
+
* echoes it.
|
|
334
349
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
* for a uniform catch surface.
|
|
350
|
+
* This is distinct from `MissingLabelError`, which is raised client-side when
|
|
351
|
+
* no label was supplied at all.
|
|
338
352
|
*/
|
|
339
|
-
export class
|
|
340
|
-
|
|
353
|
+
export class LabelNotFoundError extends To11aiApiError {
|
|
354
|
+
/** The label slug that could not be found, when the server echoes it. */
|
|
355
|
+
readonly label?: string;
|
|
341
356
|
|
|
342
357
|
constructor(
|
|
343
358
|
message: string,
|
|
344
|
-
|
|
359
|
+
label: string | undefined,
|
|
345
360
|
details: unknown,
|
|
346
|
-
// Required (no misleading default): the client-eager `missing()` variant
|
|
347
|
-
// passes 0 (no HTTP call) and the transport mapper passes the real server
|
|
348
|
-
// status, so a hardcoded default would never reflect reality.
|
|
349
361
|
statusCode: number,
|
|
350
362
|
) {
|
|
363
|
+
// The wire code is unchanged (`ENV_NOT_FOUND`); only the SDK class name
|
|
364
|
+
// is label-oriented, decoupled from the server error code.
|
|
351
365
|
super(message, statusCode, "ENV_NOT_FOUND", details);
|
|
352
|
-
this.name = "
|
|
353
|
-
this.
|
|
366
|
+
this.name = "LabelNotFoundError";
|
|
367
|
+
this.label = label;
|
|
354
368
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
355
369
|
}
|
|
356
370
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
371
|
+
static is(err: unknown): err is LabelNotFoundError {
|
|
372
|
+
return err instanceof LabelNotFoundError;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Thrown CLIENT-side, eagerly (before any request), when `prompts.render()` is
|
|
378
|
+
* called but no label is resolvable at all: none supplied per call, and no
|
|
379
|
+
* client `env` (or `TO11_ENV`) to fall back to. A usage error, not a lookup
|
|
380
|
+
* failure — distinct from `LabelNotFoundError`.
|
|
381
|
+
*/
|
|
382
|
+
export class MissingLabelError extends To11aiError {
|
|
383
|
+
constructor(
|
|
384
|
+
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.",
|
|
385
|
+
) {
|
|
386
|
+
super(message);
|
|
387
|
+
this.name = "MissingLabelError";
|
|
388
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
365
389
|
}
|
|
366
390
|
|
|
367
|
-
static is(err: unknown): err is
|
|
368
|
-
return err instanceof
|
|
391
|
+
static is(err: unknown): err is MissingLabelError {
|
|
392
|
+
return err instanceof MissingLabelError;
|
|
369
393
|
}
|
|
370
394
|
}
|
|
371
395
|
|
|
@@ -450,44 +474,6 @@ export class PromptPolicyViolationError extends To11aiApiError {
|
|
|
450
474
|
}
|
|
451
475
|
}
|
|
452
476
|
|
|
453
|
-
/**
|
|
454
|
-
* Reason payload for `AmbiguousEnvLabelsError`.
|
|
455
|
-
*
|
|
456
|
-
* Carried when the supplied env-label matched more than one candidate
|
|
457
|
-
* version and the server cannot pick deterministically
|
|
458
|
-
* (`AMBIGUOUS_ENV_LABELS`).
|
|
459
|
-
*/
|
|
460
|
-
export interface AmbiguousEnvLabelsReason {
|
|
461
|
-
env?: string;
|
|
462
|
-
slug?: string;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Thrown when the env→label resolution is ambiguous on the server
|
|
467
|
-
* (`AMBIGUOUS_ENV_LABELS`) — the label matched multiple candidates and the
|
|
468
|
-
* server declines to guess. Surfaces (rather than 500ing) so callers get a
|
|
469
|
-
* typed, actionable error.
|
|
470
|
-
*/
|
|
471
|
-
export class AmbiguousEnvLabelsError extends To11aiApiError {
|
|
472
|
-
readonly reason: AmbiguousEnvLabelsReason;
|
|
473
|
-
|
|
474
|
-
constructor(
|
|
475
|
-
message: string,
|
|
476
|
-
reason: AmbiguousEnvLabelsReason,
|
|
477
|
-
details?: unknown,
|
|
478
|
-
statusCode = 409,
|
|
479
|
-
) {
|
|
480
|
-
super(message, statusCode, "AMBIGUOUS_ENV_LABELS", details);
|
|
481
|
-
this.name = "AmbiguousEnvLabelsError";
|
|
482
|
-
this.reason = reason;
|
|
483
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
static is(err: unknown): err is AmbiguousEnvLabelsError {
|
|
487
|
-
return err instanceof AmbiguousEnvLabelsError;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
477
|
/**
|
|
492
478
|
* Thrown when version create rejects the snapshot for a structural
|
|
493
479
|
* reason — invalid variant params, malformed `when` clause, illegal
|