@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.
- package/dist/client.cjs +88 -18
- package/dist/client.d.ts +141 -39
- package/dist/client.js +94 -19
- package/dist/config.d.ts +9 -0
- package/dist/context.d.ts +124 -0
- package/dist/errors.cjs +18 -1
- package/dist/errors.d.ts +18 -1
- package/dist/errors.js +18 -1
- package/dist/gateway.cjs +65 -22
- package/dist/gateway.d.ts +65 -9
- package/dist/gateway.js +65 -22
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/types.gen.d.ts +1299 -167
- package/dist/generated/zod.gen.d.ts +393 -232
- package/dist/prompts/cache.d.ts +9 -10
- package/dist/prompts/resource.cjs +29 -67
- package/dist/prompts/resource.d.ts +52 -32
- package/dist/prompts/resource.js +30 -68
- package/dist/prompts/shape.d.ts +56 -0
- package/dist/prompts/types.d.ts +44 -31
- package/package.json +1 -1
- package/src/client.ts +337 -82
- package/src/config.ts +13 -0
- package/src/context.ts +198 -0
- package/src/errors.ts +33 -3
- package/src/gateway.ts +148 -33
- package/src/generated/index.ts +1 -1
- package/src/generated/types.gen.ts +1310 -167
- package/src/generated/zod.gen.ts +361 -271
- package/src/prompts/cache.ts +11 -15
- package/src/prompts/resource.ts +129 -142
- package/src/prompts/shape.ts +152 -0
- package/src/prompts/types.ts +45 -34
package/src/client.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { BehaviorStore } from "./behaviors/store";
|
|
2
|
+
import { DEFAULT_API_URL, DEFAULT_GATEWAY_URL } from "./config";
|
|
3
|
+
import {
|
|
4
|
+
type Conversation,
|
|
5
|
+
Session,
|
|
6
|
+
type Turn,
|
|
7
|
+
type TurnAuthContext,
|
|
8
|
+
} from "./context";
|
|
9
|
+
import { gatewayAuthHeaders } from "./gateway";
|
|
2
10
|
import { ProjectEnvironmentResource } from "./projectEnvironments/resource";
|
|
3
11
|
import type { CacheConfig } from "./prompts/cache";
|
|
4
12
|
import { PromptResource } from "./prompts/resource";
|
|
5
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ENV_SLUG_REGEX,
|
|
15
|
+
type RenderFormat,
|
|
16
|
+
type UnknownRoleMode,
|
|
17
|
+
} from "./prompts/types";
|
|
6
18
|
import { RoutingRulesResource } from "./routingRules/resource";
|
|
7
19
|
import { Transport } from "./transport";
|
|
8
20
|
|
|
@@ -13,6 +25,8 @@ export {
|
|
|
13
25
|
envVarNameForKey,
|
|
14
26
|
type SdkBehaviorEnvelope,
|
|
15
27
|
} from "./behaviors/store";
|
|
28
|
+
export type { TurnAuthContext } from "./context";
|
|
29
|
+
export { Conversation, Session, Turn } from "./context";
|
|
16
30
|
// Re-export all error types for convenience
|
|
17
31
|
export type {
|
|
18
32
|
AmbiguousEnvLabelsReason,
|
|
@@ -40,8 +54,9 @@ export {
|
|
|
40
54
|
To11aiNetworkError,
|
|
41
55
|
To11aiRateLimitError,
|
|
42
56
|
To11aiValidationError,
|
|
57
|
+
UnknownRoleError,
|
|
43
58
|
} from "./errors";
|
|
44
|
-
export type { GatewayAuthOptions } from "./gateway";
|
|
59
|
+
export type { ConvertMessagesOptions, GatewayAuthOptions } from "./gateway";
|
|
45
60
|
export { gatewayAuthHeaders, gatewayPromptHeaders } from "./gateway";
|
|
46
61
|
export type {
|
|
47
62
|
ArchiveProjectEnvironmentInput,
|
|
@@ -63,16 +78,23 @@ export { ProjectEnvironmentResource } from "./projectEnvironments/resource";
|
|
|
63
78
|
export type { CacheConfig } from "./prompts/cache";
|
|
64
79
|
export type { PromptResourceConfig } from "./prompts/resource";
|
|
65
80
|
export { PromptResource } from "./prompts/resource";
|
|
66
|
-
|
|
81
|
+
export type {
|
|
82
|
+
AnthropicRenderedPrompt,
|
|
83
|
+
OpenAiRenderedPrompt,
|
|
84
|
+
PromptConfig,
|
|
85
|
+
PromptMetadata,
|
|
86
|
+
RenderResult,
|
|
87
|
+
} from "./prompts/shape";
|
|
88
|
+
// TO11-980 (S9): env-aware render() surface types at the package entrypoint (AC9).
|
|
67
89
|
export type {
|
|
68
90
|
BlockRenderRecord,
|
|
69
|
-
DeveloperRole,
|
|
70
|
-
FetchedPrompt,
|
|
71
|
-
FetchPromptOptions,
|
|
72
91
|
RenderedChatMessage,
|
|
73
92
|
RenderedPrompt,
|
|
93
|
+
RenderFormat,
|
|
94
|
+
RenderOptions,
|
|
74
95
|
ResolvedPrompt,
|
|
75
96
|
ResolvedPromptEnvelope,
|
|
97
|
+
UnknownRoleMode,
|
|
76
98
|
} from "./prompts/types";
|
|
77
99
|
export { ENV_SLUG_REGEX } from "./prompts/types";
|
|
78
100
|
export type {
|
|
@@ -107,63 +129,106 @@ export {
|
|
|
107
129
|
} from "./routingRules/resource";
|
|
108
130
|
export type { Transport } from "./transport";
|
|
109
131
|
|
|
110
|
-
// ─── Auth option shapes ────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
interface ServerAuthOptions {
|
|
113
|
-
/** API key for server-side use. Must not be used in browser contexts. */
|
|
114
|
-
apiKey: string;
|
|
115
|
-
getAccessToken?: never;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
interface BrowserAuthOptions {
|
|
119
|
-
/** Token callback for browser / edge contexts — avoids static secrets. */
|
|
120
|
-
getAccessToken: () => Promise<string>;
|
|
121
|
-
apiKey?: never;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
type AuthOptions = ServerAuthOptions | BrowserAuthOptions;
|
|
125
|
-
|
|
126
132
|
// ─── Client options ────────────────────────────────────────────────────────
|
|
127
133
|
|
|
128
|
-
export
|
|
129
|
-
|
|
134
|
+
export interface ClientOptions {
|
|
135
|
+
/**
|
|
136
|
+
* Control-plane base URL. Precedence: this option → `TO11_API_URL` env →
|
|
137
|
+
* `https://api.to11.ai`.
|
|
138
|
+
*/
|
|
139
|
+
baseUrl?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Data-plane gateway URL (the app's provider-client `baseURL`). Precedence:
|
|
142
|
+
* this option → `TO11_GATEWAY_URL` env → `https://gw.to11.ai`.
|
|
143
|
+
*/
|
|
144
|
+
gatewayUrl?: string;
|
|
145
|
+
/**
|
|
146
|
+
* API key for server-side use. Precedence: this option → `TO11_API_KEY` env.
|
|
147
|
+
* Rejected in browser contexts — use `getAccessToken` there.
|
|
148
|
+
*/
|
|
149
|
+
apiKey?: string;
|
|
150
|
+
/** Token callback for browser / edge contexts — avoids static secrets. */
|
|
151
|
+
getAccessToken?: () => Promise<string>;
|
|
152
|
+
/**
|
|
153
|
+
* Provider API key (BYOK) carried by `openaiOptions()` / `anthropicOptions()`
|
|
154
|
+
* as the provider client's `apiKey`, so a BYOK call is a single
|
|
155
|
+
* `new OpenAI(to11.openaiOptions())`. When omitted, those helpers fall back to
|
|
156
|
+
* the to11 `apiKey` as a placeholder (the managed / server-side-credential
|
|
157
|
+
* path). Option only — no env fallback. Whether the gateway actually uses this
|
|
158
|
+
* key depends on the provider's passthrough mode; otherwise the project's
|
|
159
|
+
* stored credential wins.
|
|
160
|
+
*/
|
|
161
|
+
providerApiKey?: string;
|
|
130
162
|
/**
|
|
131
|
-
* Project the client is bound to
|
|
132
|
-
* env
|
|
133
|
-
* project-scoped resolve path from it. Validated non-empty at
|
|
134
|
-
* construction. The deprecated `prompts.resolve()` keeps its per-call
|
|
135
|
-
* `projectId` and ignores this.
|
|
163
|
+
* Project the client is bound to. Precedence: this option → `TO11_PROJECT_ID`
|
|
164
|
+
* env. Required for `prompts.render()` and the gateway context helpers.
|
|
136
165
|
*/
|
|
137
166
|
projectId?: string;
|
|
138
167
|
/**
|
|
139
|
-
* Default serving environment for `prompts.
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* uppercase / 64+-char values throw at construction with a loud
|
|
144
|
-
* `[to11ai/sdk]` message before any request hits the wire.
|
|
168
|
+
* Default serving environment for `prompts.render(slug)`, inherited by every
|
|
169
|
+
* render unless overridden per call. Precedence: this option → `TO11_ENV`
|
|
170
|
+
* env. Validated eagerly against the project-environments slug rule
|
|
171
|
+
* (`^[a-z0-9-]{1,63}$`).
|
|
145
172
|
*/
|
|
146
173
|
env?: string;
|
|
147
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Default provider shape for `prompts.render()` output (`"openai"` |
|
|
176
|
+
* `"anthropic"`). Omit for the neutral result. Per-call override allowed.
|
|
177
|
+
*/
|
|
178
|
+
format?: RenderFormat;
|
|
179
|
+
/**
|
|
180
|
+
* Default policy for a message with an unrecognized role while shaping
|
|
181
|
+
* `render({ format })` output: `"drop"` (default, silent), `"warn"` (drop +
|
|
182
|
+
* `console.warn`), or `"error"` (throw `UnknownRoleError`). Per-call override
|
|
183
|
+
* via `render(slug, { onUnknownRole })`.
|
|
184
|
+
*/
|
|
185
|
+
onUnknownRole?: UnknownRoleMode;
|
|
186
|
+
/** LRU cache configuration for render(). Defaults: ttlSeconds=300, maxEntries=100 */
|
|
148
187
|
cache?: CacheConfig;
|
|
149
188
|
/** Request timeout in milliseconds. Default: 30_000 */
|
|
150
189
|
timeoutMs?: number;
|
|
151
190
|
/** Max retry attempts for network errors / 5xx. Default: 3 */
|
|
152
191
|
maxRetries?: number;
|
|
153
192
|
/**
|
|
154
|
-
* Purely local overrides for server-evaluated SDK-behavior flags (TO11-2100)
|
|
155
|
-
*
|
|
156
|
-
* precedence over server-delivered values and the SDK's coded defaults. For
|
|
157
|
-
* the same flag key, an init option here wins over a `TO11_SDK_BEHAVIOR_*`
|
|
158
|
-
* environment variable. Published-SDK only — no LaunchDarkly involved.
|
|
193
|
+
* Purely local overrides for server-evaluated SDK-behavior flags (TO11-2100).
|
|
194
|
+
* An init option here wins over a `TO11_SDK_BEHAVIOR_*` env var. No LaunchDarkly.
|
|
159
195
|
*/
|
|
160
196
|
behaviorOverrides?: Record<string, unknown>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── Provider-client options (openaiOptions / anthropicOptions) ─────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Overrides accepted by `openaiOptions()` / `anthropicOptions()`. The three
|
|
203
|
+
* known fields are merged (`apiKey` / `baseURL` override; `defaultHeaders`
|
|
204
|
+
* shallow-merges over the tenant-auth headers, caller wins); any other keys —
|
|
205
|
+
* `timeout`, `maxRetries`, `fetch`, … — pass through untouched to the provider
|
|
206
|
+
* client, so `new OpenAI(to11.openaiOptions({ apiKey, timeout }))` needs no
|
|
207
|
+
* outer spread.
|
|
208
|
+
*/
|
|
209
|
+
export interface ProviderClientOverrides {
|
|
210
|
+
apiKey?: string;
|
|
211
|
+
baseURL?: string;
|
|
212
|
+
defaultHeaders?: Record<string, string>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The spread-ready result of `openaiOptions()` / `anthropicOptions()`: the three
|
|
217
|
+
* managed fields plus whatever passthrough keys the caller supplied (their types
|
|
218
|
+
* preserved), so it drops straight into `new OpenAI(...)` / `new Anthropic(...)`.
|
|
219
|
+
*/
|
|
220
|
+
export type ProviderClientOptions<
|
|
221
|
+
T extends ProviderClientOverrides = ProviderClientOverrides,
|
|
222
|
+
> = Omit<T, "apiKey" | "baseURL" | "defaultHeaders"> & {
|
|
223
|
+
baseURL: string;
|
|
224
|
+
apiKey: string;
|
|
225
|
+
defaultHeaders: Record<string, string>;
|
|
161
226
|
};
|
|
162
227
|
|
|
163
228
|
// ─── Client interface ──────────────────────────────────────────────────────
|
|
164
229
|
|
|
165
|
-
export interface To11aiClient {
|
|
166
|
-
readonly prompts: PromptResource
|
|
230
|
+
export interface To11aiClient<F extends RenderFormat | undefined = undefined> {
|
|
231
|
+
readonly prompts: PromptResource<F>;
|
|
167
232
|
readonly projectEnvironments: ProjectEnvironmentResource;
|
|
168
233
|
readonly routingRules: RoutingRulesResource;
|
|
169
234
|
/**
|
|
@@ -176,18 +241,79 @@ export interface To11aiClient {
|
|
|
176
241
|
* delivered on the envelope.
|
|
177
242
|
*/
|
|
178
243
|
readonly resolveBehavior: <T>(key: string, codedDefault: T) => T;
|
|
244
|
+
/** Resolved API key (server mode); `undefined` in browser (`getAccessToken`) mode. */
|
|
245
|
+
readonly apiKey: string | undefined;
|
|
246
|
+
/** Resolved data-plane gateway URL (option → `TO11_GATEWAY_URL` → default). */
|
|
247
|
+
readonly gatewayUrl: string;
|
|
248
|
+
/** A new session (unit of work). Pass an id to group telemetry by your own id. */
|
|
249
|
+
session(id?: string): Session;
|
|
250
|
+
/**
|
|
251
|
+
* A conversation in the client's default session (for side / parallel agents).
|
|
252
|
+
*
|
|
253
|
+
* @example Two agents under one session
|
|
254
|
+
* const research = to11.conversation(); // a separate thread from to11.turn()'s default
|
|
255
|
+
* const res = await openai.chat.completions.create(
|
|
256
|
+
* { ...researchPrompt.config, messages },
|
|
257
|
+
* { headers: research.turn().headers(researchPrompt) },
|
|
258
|
+
* );
|
|
259
|
+
*/
|
|
260
|
+
conversation(id?: string): Conversation;
|
|
261
|
+
/** A turn in the client's default session + default conversation. */
|
|
262
|
+
turn(): Turn;
|
|
263
|
+
/**
|
|
264
|
+
* Spread-ready options for `new OpenAI(...)` targeting the gateway:
|
|
265
|
+
* `{ baseURL, apiKey, defaultHeaders }` (baseURL carries `/v1`). `defaultHeaders`
|
|
266
|
+
* holds the static tenant auth, so the client authenticates on its own; add
|
|
267
|
+
* `turn().headers(prompt)` per call for trace + prompt provenance. Pass
|
|
268
|
+
* `overrides` to change `apiKey`/`baseURL`, merge extra `defaultHeaders`, or
|
|
269
|
+
* forward any other provider-SDK option (`timeout`, …).
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* const openai = new OpenAI(to11.openaiOptions());
|
|
273
|
+
* @example BYOK + a provider-SDK option, no outer spread
|
|
274
|
+
* const openai = new OpenAI(to11.openaiOptions({ apiKey: MY_KEY, timeout: 60_000 }));
|
|
275
|
+
*/
|
|
276
|
+
openaiOptions<T extends ProviderClientOverrides = ProviderClientOverrides>(
|
|
277
|
+
overrides?: T,
|
|
278
|
+
): ProviderClientOptions<T>;
|
|
279
|
+
/**
|
|
280
|
+
* Spread-ready options for `new Anthropic(...)` targeting the gateway root
|
|
281
|
+
* (the Anthropic SDK appends `/v1/messages` itself). Same shape and merge
|
|
282
|
+
* rules as {@link To11aiClient.openaiOptions}.
|
|
283
|
+
*/
|
|
284
|
+
anthropicOptions<T extends ProviderClientOverrides = ProviderClientOverrides>(
|
|
285
|
+
overrides?: T,
|
|
286
|
+
): ProviderClientOptions<T>;
|
|
179
287
|
}
|
|
180
288
|
|
|
181
289
|
// ─── Factory ───────────────────────────────────────────────────────────────
|
|
182
290
|
|
|
183
291
|
/**
|
|
184
|
-
* Create a to11ai SDK client.
|
|
292
|
+
* Create a to11ai SDK client. Resolution precedence differs per option:
|
|
293
|
+
* - URLs (`baseUrl` / `gatewayUrl`) — option → env var → built-in default.
|
|
294
|
+
* - `apiKey` / `projectId` — option → env var (no default).
|
|
295
|
+
* - `env` / `format` — option only.
|
|
296
|
+
* Auth mode:
|
|
297
|
+
* - `apiKey` (or `TO11_API_KEY`) → server-side; rejected in browser contexts.
|
|
298
|
+
* - `getAccessToken` → browser-safe; token fetched per request.
|
|
185
299
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
300
|
+
* @example Minimal setup (single call)
|
|
301
|
+
* import { createClient } from "@to11ai/sdk";
|
|
302
|
+
* import OpenAI from "openai";
|
|
303
|
+
*
|
|
304
|
+
* const to11 = createClient({ env: "prod", format: "openai" });
|
|
305
|
+
* const openai = new OpenAI(to11.openaiOptions());
|
|
306
|
+
* const prompt = await to11.prompts.render("weather-concierge", { variables: { city: "NYC" } });
|
|
307
|
+
* const res = await openai.chat.completions.create(
|
|
308
|
+
* { ...prompt.config, messages: prompt.messages },
|
|
309
|
+
* { headers: to11.turn().headers(prompt) },
|
|
310
|
+
* );
|
|
189
311
|
*/
|
|
190
|
-
export function createClient
|
|
312
|
+
export function createClient<
|
|
313
|
+
const F extends RenderFormat | undefined = undefined,
|
|
314
|
+
>(
|
|
315
|
+
options: Omit<ClientOptions, "format"> & { format?: F } = {},
|
|
316
|
+
): To11aiClient<F> {
|
|
191
317
|
const isDomBrowser =
|
|
192
318
|
typeof globalThis === "object" &&
|
|
193
319
|
typeof (globalThis as { document?: unknown }).document !== "undefined";
|
|
@@ -206,86 +332,215 @@ export function createClient(options: ClientOptions): To11aiClient {
|
|
|
206
332
|
(!isBunMainThread &&
|
|
207
333
|
typeof workerLikeGlobal.postMessage === "function" &&
|
|
208
334
|
typeof workerLikeGlobal.onmessage !== "undefined"));
|
|
335
|
+
const isBrowser = isDomBrowser || isWorkerBrowser;
|
|
336
|
+
|
|
337
|
+
// Env vars are read ONLY in a non-browser context, so the published browser
|
|
338
|
+
// bundle never touches `process.env`; explicit options apply everywhere.
|
|
339
|
+
const processEnv =
|
|
340
|
+
!isBrowser &&
|
|
341
|
+
typeof process !== "undefined" &&
|
|
342
|
+
typeof process.env === "object"
|
|
343
|
+
? process.env
|
|
344
|
+
: undefined;
|
|
345
|
+
|
|
346
|
+
// Resolve each value: explicit option → env var → default. `pick` trims and
|
|
347
|
+
// treats empty as absent, so an empty option or env value falls through.
|
|
348
|
+
const pick = (
|
|
349
|
+
opt: string | undefined,
|
|
350
|
+
envKey: string,
|
|
351
|
+
): string | undefined => {
|
|
352
|
+
const fromOpt = opt?.trim();
|
|
353
|
+
if (fromOpt) return fromOpt;
|
|
354
|
+
const fromEnv = processEnv?.[envKey]?.trim();
|
|
355
|
+
return fromEnv ? fromEnv : undefined;
|
|
356
|
+
};
|
|
209
357
|
|
|
210
|
-
|
|
358
|
+
const baseUrl = pick(options.baseUrl, "TO11_API_URL") ?? DEFAULT_API_URL;
|
|
359
|
+
// The gateway root (host, no version path). Normalize away a trailing slash
|
|
360
|
+
// and a trailing `/v1` so both the root form (`https://gw.to11.ai`) and the
|
|
361
|
+
// older `/v1` form resolve identically — the provider helpers add the path
|
|
362
|
+
// each SDK expects.
|
|
363
|
+
const gatewayUrl = (
|
|
364
|
+
pick(options.gatewayUrl, "TO11_GATEWAY_URL") ?? DEFAULT_GATEWAY_URL
|
|
365
|
+
)
|
|
366
|
+
.replace(/\/+$/, "")
|
|
367
|
+
.replace(/\/v1$/, "");
|
|
368
|
+
const getAccessToken = options.getAccessToken;
|
|
369
|
+
// Explicit token auth suppresses an ambient `TO11_API_KEY` so a shared env
|
|
370
|
+
// (SSR / CI / edge) can't defeat an explicit `getAccessToken`. Only an
|
|
371
|
+
// *explicit* `options.apiKey` alongside `getAccessToken` stays ambiguous and
|
|
372
|
+
// is rejected by the exactly-one check below.
|
|
373
|
+
const apiKey =
|
|
374
|
+
getAccessToken !== undefined
|
|
375
|
+
? options.apiKey
|
|
376
|
+
: pick(options.apiKey, "TO11_API_KEY");
|
|
377
|
+
const projectId = pick(options.projectId, "TO11_PROJECT_ID");
|
|
378
|
+
// Default serving env: explicit option → `TO11_ENV` env var — the same
|
|
379
|
+
// `pick(option, "TO11_*")` resolution `apiKey`/`projectId` use (empty option
|
|
380
|
+
// or empty env var trims to absent). An explicit empty option is rejected by
|
|
381
|
+
// the dedicated guard below (mirroring `apiKey`/`projectId`); a non-empty but
|
|
382
|
+
// malformed value fails the `ENV_SLUG_REGEX` check.
|
|
383
|
+
const env = pick(options.env, "TO11_ENV");
|
|
384
|
+
// Provider (BYOK) key — option only, no env fallback (which provider's env
|
|
385
|
+
// var is ambiguous). Empty trims to absent so it falls back to the placeholder.
|
|
386
|
+
const providerApiKey = options.providerApiKey?.trim() || undefined;
|
|
387
|
+
|
|
388
|
+
// A browser must never carry a static API key. `processEnv` is already
|
|
389
|
+
// undefined in the browser, so only the explicit option can reach here.
|
|
390
|
+
if (isBrowser && options.apiKey !== undefined) {
|
|
211
391
|
throw new Error(
|
|
212
392
|
"[to11ai/sdk] Do not use `apiKey` in browser contexts — use `getAccessToken` instead.",
|
|
213
393
|
);
|
|
214
394
|
}
|
|
215
395
|
|
|
396
|
+
// Explicit empty credentials are a misconfiguration — reject loudly.
|
|
216
397
|
if (options.apiKey !== undefined && options.apiKey.trim() === "") {
|
|
217
398
|
throw new Error("[to11ai/sdk] `apiKey` must not be an empty string.");
|
|
218
399
|
}
|
|
219
|
-
|
|
220
|
-
// R0: validate `projectId` eagerly when supplied (loud throw). An empty /
|
|
221
|
-
// whitespace-only value is a misconfiguration; reject before any request.
|
|
222
400
|
if (options.projectId !== undefined && options.projectId.trim() === "") {
|
|
223
401
|
throw new Error(
|
|
224
402
|
"[to11ai/sdk] `projectId` must not be an empty string — omit it or pass a valid project id.",
|
|
225
403
|
);
|
|
226
404
|
}
|
|
227
|
-
|
|
228
|
-
// R1: validate the init-time `env` against the project-environments
|
|
229
|
-
// registry rule (stricter than gatewayAuthHeaders' empty-only check) so a
|
|
230
|
-
// malformed env fails locally rather than round-tripping to ENV_NOT_FOUND.
|
|
231
|
-
if (options.env !== undefined && !ENV_SLUG_REGEX.test(options.env)) {
|
|
405
|
+
if (options.env !== undefined && options.env.trim() === "") {
|
|
232
406
|
throw new Error(
|
|
233
|
-
|
|
407
|
+
"[to11ai/sdk] `env` must not be an empty string — omit it or pass a valid env slug.",
|
|
234
408
|
);
|
|
235
409
|
}
|
|
236
410
|
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
411
|
+
// Validate the init-time `env` against the project-environments slug rule so
|
|
412
|
+
// a malformed env fails locally rather than round-tripping to ENV_NOT_FOUND.
|
|
413
|
+
if (env !== undefined && !ENV_SLUG_REGEX.test(env)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`[to11ai/sdk] \`env\` must match ${ENV_SLUG_REGEX} (lowercase-kebab, 1-63 chars). Received: ${JSON.stringify(env)}`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
242
418
|
|
|
419
|
+
// Exactly one auth source after resolution.
|
|
420
|
+
const hasApiKey = apiKey !== undefined;
|
|
421
|
+
const hasGetAccessToken = getAccessToken !== undefined;
|
|
243
422
|
if (hasApiKey === hasGetAccessToken) {
|
|
244
423
|
throw new Error(
|
|
245
|
-
"[to11ai/sdk] Provide exactly one of `apiKey` (
|
|
424
|
+
"[to11ai/sdk] Provide exactly one of `apiKey` (or `TO11_API_KEY`) or `getAccessToken`.",
|
|
246
425
|
);
|
|
247
426
|
}
|
|
248
427
|
|
|
249
|
-
const auth =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
: ({ getAccessToken: getAccessToken as () => Promise<string> } as const);
|
|
428
|
+
const auth = hasApiKey
|
|
429
|
+
? ({ apiKey: apiKey as string } as const)
|
|
430
|
+
: ({ getAccessToken: getAccessToken as () => Promise<string> } as const);
|
|
253
431
|
|
|
254
|
-
// SDK-behavior store (TO11-2100). Env-var overrides (`TO11_SDK_BEHAVIOR_*`)
|
|
255
|
-
// are read ONLY in a non-browser context so the published browser bundle
|
|
256
|
-
// never touches `process.env`; init-option overrides apply everywhere.
|
|
257
|
-
const isBrowser = isDomBrowser || isWorkerBrowser;
|
|
258
|
-
const processEnv =
|
|
259
|
-
!isBrowser &&
|
|
260
|
-
typeof process !== "undefined" &&
|
|
261
|
-
typeof process.env === "object"
|
|
262
|
-
? process.env
|
|
263
|
-
: undefined;
|
|
264
432
|
const behaviorStore = new BehaviorStore({
|
|
265
433
|
initOverrides: options.behaviorOverrides,
|
|
266
434
|
env: processEnv,
|
|
267
435
|
});
|
|
268
436
|
|
|
269
437
|
const transport = new Transport({
|
|
270
|
-
baseUrl
|
|
438
|
+
baseUrl,
|
|
271
439
|
auth,
|
|
272
440
|
timeoutMs: options.timeoutMs,
|
|
273
441
|
maxRetries: options.maxRetries,
|
|
274
442
|
behaviorStore,
|
|
275
443
|
});
|
|
276
444
|
|
|
277
|
-
const prompts = new PromptResource(transport, options.cache, {
|
|
278
|
-
projectId
|
|
279
|
-
env
|
|
445
|
+
const prompts = new PromptResource<F>(transport, options.cache, {
|
|
446
|
+
projectId,
|
|
447
|
+
env,
|
|
448
|
+
format: options.format,
|
|
449
|
+
onUnknownRole: options.onUnknownRole,
|
|
280
450
|
});
|
|
281
451
|
const projectEnvironments = new ProjectEnvironmentResource(transport);
|
|
282
452
|
const routingRules = new RoutingRulesResource(transport);
|
|
283
453
|
|
|
454
|
+
// ── Gateway observability context (session → conversation → turn) ──
|
|
455
|
+
// These build gateway auth headers, so they require a server-side apiKey +
|
|
456
|
+
// projectId; fail loudly rather than emit a broken header bag.
|
|
457
|
+
const gatewayAuthContext = (): TurnAuthContext => {
|
|
458
|
+
if (apiKey === undefined) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
"[to11ai/sdk] session()/turn()/openaiOptions() need an `apiKey` (or `TO11_API_KEY`) — gateway calls require a server-side key. In the browser use `getAccessToken` for control-plane calls only.",
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (projectId === undefined) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
"[to11ai/sdk] session()/turn() need a `projectId` (or `TO11_PROJECT_ID`).",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
return env !== undefined
|
|
469
|
+
? { apiKey, projectId, env }
|
|
470
|
+
: { apiKey, projectId };
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
let defaultSession: Session | undefined;
|
|
474
|
+
const clientDefaultSession = (): Session => {
|
|
475
|
+
if (defaultSession === undefined) {
|
|
476
|
+
defaultSession = new Session(gatewayAuthContext());
|
|
477
|
+
}
|
|
478
|
+
return defaultSession;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// OpenAI's SDK convention puts `/v1` in `baseURL`; the Anthropic SDK appends
|
|
482
|
+
// `/v1/messages` itself, so it takes the gateway root. Both point at the same
|
|
483
|
+
// gateway; only the base differs per provider convention. The result is a
|
|
484
|
+
// spread-ready provider-client options object: managed fields (baseURL/apiKey/
|
|
485
|
+
// defaultHeaders) are computed+merged, and any other override key passes
|
|
486
|
+
// through untouched to the provider SDK.
|
|
487
|
+
const gatewayClientOptions = <
|
|
488
|
+
T extends ProviderClientOverrides = ProviderClientOverrides,
|
|
489
|
+
>(
|
|
490
|
+
defaultBaseURL: string,
|
|
491
|
+
overrides?: T,
|
|
492
|
+
): ProviderClientOptions<T> => {
|
|
493
|
+
if (apiKey === undefined) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
"[to11ai/sdk] openaiOptions()/anthropicOptions() need an `apiKey` (or `TO11_API_KEY`) — gateway calls require a server-side key. In the browser use `getAccessToken` for control-plane calls only.",
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (projectId === undefined) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
"[to11ai/sdk] openaiOptions()/anthropicOptions() need a `projectId` (or `TO11_PROJECT_ID`) — it authenticates the gateway call via `x-to11-project-id`.",
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
// Static tenant auth for the provider client's defaultHeaders; per-call
|
|
504
|
+
// trace + prompt provenance still come from `turn().headers(prompt)`.
|
|
505
|
+
const authHeaders = gatewayAuthHeaders(
|
|
506
|
+
env !== undefined ? { apiKey, projectId, env } : { apiKey, projectId },
|
|
507
|
+
);
|
|
508
|
+
const {
|
|
509
|
+
apiKey: overrideApiKey,
|
|
510
|
+
baseURL: overrideBaseURL,
|
|
511
|
+
defaultHeaders: overrideHeaders,
|
|
512
|
+
...passthrough
|
|
513
|
+
} = overrides ?? ({} as T);
|
|
514
|
+
return {
|
|
515
|
+
...passthrough,
|
|
516
|
+
baseURL: overrideBaseURL ?? defaultBaseURL,
|
|
517
|
+
// Provider (BYOK) key when supplied, else the to11 key as a placeholder
|
|
518
|
+
// (the managed / server-side-credential path).
|
|
519
|
+
apiKey: overrideApiKey ?? providerApiKey ?? apiKey,
|
|
520
|
+
defaultHeaders: { ...authHeaders, ...overrideHeaders },
|
|
521
|
+
} as ProviderClientOptions<T>;
|
|
522
|
+
};
|
|
523
|
+
|
|
284
524
|
return {
|
|
285
525
|
prompts,
|
|
286
526
|
projectEnvironments,
|
|
287
527
|
routingRules,
|
|
288
528
|
resolveBehavior: (key, codedDefault) =>
|
|
289
529
|
behaviorStore.resolve(key, codedDefault),
|
|
530
|
+
apiKey,
|
|
531
|
+
gatewayUrl,
|
|
532
|
+
session: (id) => new Session(gatewayAuthContext(), id),
|
|
533
|
+
conversation: (id) => clientDefaultSession().conversation(id),
|
|
534
|
+
turn: () => clientDefaultSession().turn(),
|
|
535
|
+
openaiOptions: <
|
|
536
|
+
T extends ProviderClientOverrides = ProviderClientOverrides,
|
|
537
|
+
>(
|
|
538
|
+
overrides?: T,
|
|
539
|
+
) => gatewayClientOptions(`${gatewayUrl}/v1`, overrides),
|
|
540
|
+
anthropicOptions: <
|
|
541
|
+
T extends ProviderClientOverrides = ProviderClientOverrides,
|
|
542
|
+
>(
|
|
543
|
+
overrides?: T,
|
|
544
|
+
) => gatewayClientOptions(gatewayUrl, overrides),
|
|
290
545
|
};
|
|
291
546
|
}
|
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";
|