@modelrelay/sdk 0.14.1 → 0.18.0

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 CHANGED
@@ -82,11 +82,87 @@ const stream = await mr.chat.completions.create(
82
82
  );
83
83
  ```
84
84
 
85
- ### Typed models, providers, and stop reasons
85
+ ### Typed models and stop reasons
86
86
 
87
- - Models and providers use string literal unions with an `Other` escape hatch: pass `{ other: "my-provider" }` or `{ other: "custom/model-x" }` to preserve custom IDs while benefiting from autocomplete on known values (e.g., `Models.Gpt4o`, `Providers.Anthropic`).
87
+ - Models are plain strings (e.g., `"gpt-4o"`), so new models do not require SDK updates.
88
88
  - Stop reasons are parsed into the `StopReason` union (e.g., `StopReasons.EndTurn`); unknown values surface as `{ other: "<raw>" }`.
89
- - Usage backfills `totalTokens` when providers omit it, ensuring consistent accounting.
89
+ - Usage backfills `totalTokens` when the backend omits it, ensuring consistent accounting.
90
+
91
+ ### Structured outputs (`response_format`)
92
+
93
+ Request structured JSON instead of free-form text when the backend supports it:
94
+
95
+ ```ts
96
+ import { ModelRelay, type ResponseFormat } from "@modelrelay/sdk";
97
+
98
+ const mr = new ModelRelay({ key: "mr_sk_..." });
99
+
100
+ const format: ResponseFormat = {
101
+ type: "json_schema",
102
+ json_schema: {
103
+ name: "summary",
104
+ schema: {
105
+ type: "object",
106
+ properties: { headline: { type: "string" } },
107
+ additionalProperties: false,
108
+ },
109
+ strict: true,
110
+ },
111
+ };
112
+
113
+ const completion = await mr.chat.completions.create(
114
+ {
115
+ model: "gpt-4o-mini",
116
+ messages: [{ role: "user", content: "Summarize ModelRelay" }],
117
+ responseFormat: format,
118
+ stream: false,
119
+ },
120
+ { stream: false },
121
+ );
122
+
123
+ console.log(completion.content[0]); // JSON string matching your schema
124
+ ```
125
+
126
+ ### Structured streaming (NDJSON + response_format)
127
+
128
+ Use the structured streaming contract for `/llm/proxy` to stream schema-valid
129
+ JSON payloads over NDJSON:
130
+
131
+ ```ts
132
+ type Item = { id: string; label: string };
133
+ type RecommendationPayload = { items: Item[] };
134
+
135
+ const format: ResponseFormat = {
136
+ type: "json_schema",
137
+ json_schema: {
138
+ name: "recommendations",
139
+ schema: {
140
+ type: "object",
141
+ properties: { items: { type: "array", items: { type: "object" } } },
142
+ },
143
+ },
144
+ };
145
+
146
+ const stream = await mr.chat.completions.streamJSON<RecommendationPayload>({
147
+ model: "grok-4-1-fast",
148
+ messages: [{ role: "user", content: "Recommend items for my user" }],
149
+ responseFormat: format,
150
+ });
151
+
152
+ for await (const evt of stream) {
153
+ if (evt.type === "update") {
154
+ // Progressive UI: evt.payload is a partial but schema-valid payload.
155
+ renderPartial(evt.payload.items);
156
+ }
157
+ if (evt.type === "completion") {
158
+ renderFinal(evt.payload.items);
159
+ }
160
+ }
161
+
162
+ // Prefer a single blocking result but still want structured validation?
163
+ const final = await stream.collect();
164
+ console.log(final.items.length);
165
+ ```
90
166
 
91
167
  ### Telemetry & metrics hooks
92
168
 
package/dist/index.cjs CHANGED
@@ -33,10 +33,10 @@ __export(index_exports, {
33
33
  ErrorCodes: () => ErrorCodes,
34
34
  ModelRelay: () => ModelRelay,
35
35
  ModelRelayError: () => ModelRelayError,
36
- Models: () => Models,
37
- Providers: () => Providers,
36
+ ResponseFormatTypes: () => ResponseFormatTypes,
38
37
  SDK_VERSION: () => SDK_VERSION,
39
38
  StopReasons: () => StopReasons,
39
+ StructuredJSONStream: () => StructuredJSONStream,
40
40
  TiersClient: () => TiersClient,
41
41
  ToolArgsError: () => ToolArgsError,
42
42
  ToolCallAccumulator: () => ToolCallAccumulator,
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  ToolRegistry: () => ToolRegistry,
45
45
  ToolTypes: () => ToolTypes,
46
46
  TransportError: () => TransportError,
47
+ WebToolModes: () => WebToolModes,
47
48
  assistantMessageWithToolCalls: () => assistantMessageWithToolCalls,
48
49
  createAccessTokenAuth: () => createAccessTokenAuth,
49
50
  createApiKeyAuth: () => createApiKeyAuth,
@@ -68,12 +69,10 @@ __export(index_exports, {
68
69
  mergeTrace: () => mergeTrace,
69
70
  modelToString: () => modelToString,
70
71
  normalizeModelId: () => normalizeModelId,
71
- normalizeProvider: () => normalizeProvider,
72
72
  normalizeStopReason: () => normalizeStopReason,
73
73
  parseErrorResponse: () => parseErrorResponse,
74
74
  parseToolArgs: () => parseToolArgs,
75
75
  parseToolArgsRaw: () => parseToolArgsRaw,
76
- providerToString: () => providerToString,
77
76
  respondToToolCall: () => respondToToolCall,
78
77
  stopReasonToString: () => stopReasonToString,
79
78
  toolChoiceAuto: () => toolChoiceAuto,
@@ -348,7 +347,7 @@ function isTokenReusable(token) {
348
347
  // package.json
349
348
  var package_default = {
350
349
  name: "@modelrelay/sdk",
351
- version: "0.14.1",
350
+ version: "0.18.0",
352
351
  description: "TypeScript SDK for the ModelRelay API",
353
352
  type: "module",
354
353
  main: "dist/index.cjs",
@@ -407,30 +406,6 @@ var StopReasons = {
407
406
  Incomplete: "incomplete",
408
407
  Unknown: "unknown"
409
408
  };
410
- var Providers = {
411
- OpenAI: "openai",
412
- Anthropic: "anthropic",
413
- XAI: "xai",
414
- GoogleAIStudio: "google-ai-studio",
415
- Echo: "echo"
416
- };
417
- var Models = {
418
- // OpenAI models (provider-agnostic identifiers)
419
- Gpt4o: "gpt-4o",
420
- Gpt4oMini: "gpt-4o-mini",
421
- Gpt51: "gpt-5.1",
422
- // Anthropic models (provider-agnostic identifiers)
423
- Claude35HaikuLatest: "claude-3-5-haiku-latest",
424
- Claude35SonnetLatest: "claude-3-5-sonnet-latest",
425
- ClaudeOpus45: "claude-opus-4-5",
426
- Claude35Haiku: "claude-3.5-haiku",
427
- // xAI / Grok models
428
- Grok2: "grok-2",
429
- Grok4_1FastNonReasoning: "grok-4-1-fast-non-reasoning",
430
- Grok4_1FastReasoning: "grok-4-1-fast-reasoning",
431
- // Internal echo model for testing.
432
- Echo1: "echo-1"
433
- };
434
409
  function createUsage(inputTokens, outputTokens, totalTokens) {
435
410
  return {
436
411
  inputTokens,
@@ -441,14 +416,24 @@ function createUsage(inputTokens, outputTokens, totalTokens) {
441
416
  var ToolTypes = {
442
417
  Function: "function",
443
418
  Web: "web",
419
+ WebSearch: "web_search",
444
420
  XSearch: "x_search",
445
421
  CodeExecution: "code_execution"
446
422
  };
423
+ var WebToolModes = {
424
+ Search: "search",
425
+ Browse: "browse"
426
+ };
447
427
  var ToolChoiceTypes = {
448
428
  Auto: "auto",
449
429
  Required: "required",
450
430
  None: "none"
451
431
  };
432
+ var ResponseFormatTypes = {
433
+ Text: "text",
434
+ JsonObject: "json_object",
435
+ JsonSchema: "json_schema"
436
+ };
452
437
  function mergeMetrics(base, override) {
453
438
  if (!base && !override) return void 0;
454
439
  return {
@@ -482,34 +467,14 @@ function stopReasonToString(value) {
482
467
  if (typeof value === "string") return value;
483
468
  return value.other?.trim() || void 0;
484
469
  }
485
- function normalizeProvider(value) {
486
- if (value === void 0 || value === null) return void 0;
487
- const str = String(value).trim();
488
- if (!str) return void 0;
489
- const lower = str.toLowerCase();
490
- for (const p of Object.values(Providers)) {
491
- if (lower === p) return p;
492
- }
493
- return { other: str };
494
- }
495
- function providerToString(value) {
496
- if (!value) return void 0;
497
- if (typeof value === "string") return value;
498
- return value.other?.trim() || void 0;
499
- }
500
470
  function normalizeModelId(value) {
501
471
  if (value === void 0 || value === null) return void 0;
502
472
  const str = String(value).trim();
503
473
  if (!str) return void 0;
504
- const lower = str.toLowerCase();
505
- for (const m of Object.values(Models)) {
506
- if (lower === m) return m;
507
- }
508
- return { other: str };
474
+ return str;
509
475
  }
510
476
  function modelToString(value) {
511
- if (typeof value === "string") return value;
512
- return value.other?.trim() || "";
477
+ return String(value).trim();
513
478
  }
514
479
 
515
480
  // src/tools.ts
@@ -1167,7 +1132,6 @@ var ChatCompletionsClient = class {
1167
1132
  if (!hasUserMessage(params.messages)) {
1168
1133
  throw new ConfigError("at least one user message is required");
1169
1134
  }
1170
- validateRequestModel(params.model);
1171
1135
  const authHeaders = await this.auth.authForChat(params.customerId);
1172
1136
  const body = buildProxyBody(
1173
1137
  params,
@@ -1181,7 +1145,6 @@ var ChatCompletionsClient = class {
1181
1145
  const baseContext = {
1182
1146
  method: "POST",
1183
1147
  path: "/llm/proxy",
1184
- provider: params.provider,
1185
1148
  model: params.model,
1186
1149
  requestId
1187
1150
  };
@@ -1231,6 +1194,80 @@ var ChatCompletionsClient = class {
1231
1194
  trace
1232
1195
  );
1233
1196
  }
1197
+ /**
1198
+ * Stream structured JSON responses using the NDJSON contract defined for
1199
+ * /llm/proxy. The request must include a structured responseFormat.
1200
+ */
1201
+ async streamJSON(params, options = {}) {
1202
+ const metrics = mergeMetrics(this.metrics, options.metrics);
1203
+ const trace = mergeTrace(this.trace, options.trace);
1204
+ if (!params?.messages?.length) {
1205
+ throw new ConfigError("at least one message is required");
1206
+ }
1207
+ if (!hasUserMessage(params.messages)) {
1208
+ throw new ConfigError("at least one user message is required");
1209
+ }
1210
+ if (!params.responseFormat || params.responseFormat.type !== "json_object" && params.responseFormat.type !== "json_schema") {
1211
+ throw new ConfigError(
1212
+ "responseFormat with type=json_object or json_schema is required for structured streaming"
1213
+ );
1214
+ }
1215
+ const authHeaders = await this.auth.authForChat(params.customerId);
1216
+ const body = buildProxyBody(
1217
+ params,
1218
+ mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
1219
+ );
1220
+ const requestId = params.requestId || options.requestId;
1221
+ const headers = { ...options.headers || {} };
1222
+ if (requestId) {
1223
+ headers[REQUEST_ID_HEADER] = requestId;
1224
+ }
1225
+ const baseContext = {
1226
+ method: "POST",
1227
+ path: "/llm/proxy",
1228
+ model: params.model,
1229
+ requestId
1230
+ };
1231
+ const response = await this.http.request("/llm/proxy", {
1232
+ method: "POST",
1233
+ body,
1234
+ headers,
1235
+ apiKey: authHeaders.apiKey,
1236
+ accessToken: authHeaders.accessToken,
1237
+ accept: "application/x-ndjson",
1238
+ raw: true,
1239
+ signal: options.signal,
1240
+ timeoutMs: options.timeoutMs ?? 0,
1241
+ useDefaultTimeout: false,
1242
+ connectTimeoutMs: options.connectTimeoutMs,
1243
+ retry: options.retry,
1244
+ metrics,
1245
+ trace,
1246
+ context: baseContext
1247
+ });
1248
+ const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
1249
+ if (!response.ok) {
1250
+ throw await parseErrorResponse(response);
1251
+ }
1252
+ const contentType = response.headers.get("Content-Type") || "";
1253
+ if (!/application\/(x-)?ndjson/i.test(contentType)) {
1254
+ throw new TransportError(
1255
+ `expected NDJSON structured stream, got Content-Type ${contentType || "missing"}`,
1256
+ { kind: "request" }
1257
+ );
1258
+ }
1259
+ const streamContext = {
1260
+ ...baseContext,
1261
+ requestId: resolvedRequestId ?? baseContext.requestId
1262
+ };
1263
+ return new StructuredJSONStream(
1264
+ response,
1265
+ resolvedRequestId,
1266
+ streamContext,
1267
+ metrics,
1268
+ trace
1269
+ );
1270
+ }
1234
1271
  };
1235
1272
  var ChatCompletionsStream = class {
1236
1273
  constructor(response, requestId, context, metrics, trace) {
@@ -1332,6 +1369,164 @@ var ChatCompletionsStream = class {
1332
1369
  });
1333
1370
  }
1334
1371
  };
1372
+ var StructuredJSONStream = class {
1373
+ constructor(response, requestId, context, metrics, trace) {
1374
+ this.closed = false;
1375
+ this.sawTerminal = false;
1376
+ if (!response.body) {
1377
+ throw new ConfigError("streaming response is missing a body");
1378
+ }
1379
+ this.response = response;
1380
+ this.requestId = requestId;
1381
+ this.context = context;
1382
+ this.metrics = metrics;
1383
+ this.trace = trace;
1384
+ }
1385
+ async cancel(reason) {
1386
+ this.closed = true;
1387
+ try {
1388
+ await this.response.body?.cancel(reason);
1389
+ } catch {
1390
+ }
1391
+ }
1392
+ async *[Symbol.asyncIterator]() {
1393
+ if (this.closed) {
1394
+ return;
1395
+ }
1396
+ const body = this.response.body;
1397
+ if (!body) {
1398
+ throw new ConfigError("streaming response is missing a body");
1399
+ }
1400
+ const reader = body.getReader();
1401
+ const decoder = new TextDecoder();
1402
+ let buffer = "";
1403
+ try {
1404
+ while (true) {
1405
+ if (this.closed) {
1406
+ await reader.cancel();
1407
+ return;
1408
+ }
1409
+ const { value, done } = await reader.read();
1410
+ if (done) {
1411
+ const { records: records2 } = consumeNDJSONBuffer(buffer, true);
1412
+ for (const line of records2) {
1413
+ const evt = this.parseRecord(line);
1414
+ if (evt) {
1415
+ this.traceStructuredEvent(evt, line);
1416
+ yield evt;
1417
+ }
1418
+ }
1419
+ if (!this.sawTerminal) {
1420
+ throw new TransportError(
1421
+ "structured stream ended without completion or error",
1422
+ { kind: "request" }
1423
+ );
1424
+ }
1425
+ return;
1426
+ }
1427
+ buffer += decoder.decode(value, { stream: true });
1428
+ const { records, remainder } = consumeNDJSONBuffer(buffer);
1429
+ buffer = remainder;
1430
+ for (const line of records) {
1431
+ const evt = this.parseRecord(line);
1432
+ if (evt) {
1433
+ this.traceStructuredEvent(evt, line);
1434
+ yield evt;
1435
+ }
1436
+ }
1437
+ }
1438
+ } catch (err) {
1439
+ this.trace?.streamError?.({ context: this.context, error: err });
1440
+ throw err;
1441
+ } finally {
1442
+ this.closed = true;
1443
+ reader.releaseLock();
1444
+ }
1445
+ }
1446
+ async collect() {
1447
+ let last;
1448
+ for await (const evt of this) {
1449
+ last = evt;
1450
+ if (evt.type === "completion") {
1451
+ return evt.payload;
1452
+ }
1453
+ }
1454
+ throw new TransportError(
1455
+ "structured stream ended without completion or error",
1456
+ { kind: "request" }
1457
+ );
1458
+ }
1459
+ parseRecord(line) {
1460
+ let parsed;
1461
+ try {
1462
+ parsed = JSON.parse(line);
1463
+ } catch (err) {
1464
+ throw new TransportError("invalid JSON in structured stream", {
1465
+ kind: "request",
1466
+ cause: err
1467
+ });
1468
+ }
1469
+ if (!parsed || typeof parsed !== "object") {
1470
+ throw new TransportError("structured stream record is not an object", {
1471
+ kind: "request"
1472
+ });
1473
+ }
1474
+ const obj = parsed;
1475
+ const rawType = String(obj.type || "").trim().toLowerCase();
1476
+ if (!rawType) return null;
1477
+ if (rawType === "start") {
1478
+ return null;
1479
+ }
1480
+ if (rawType === "error") {
1481
+ this.sawTerminal = true;
1482
+ const status = typeof obj.status === "number" && obj.status > 0 ? obj.status : 500;
1483
+ const message = typeof obj.message === "string" && obj.message.trim() ? obj.message : "structured stream error";
1484
+ const code = typeof obj.code === "string" && obj.code.trim() ? obj.code : void 0;
1485
+ throw new APIError(message, {
1486
+ status,
1487
+ code,
1488
+ requestId: this.requestId
1489
+ });
1490
+ }
1491
+ if (rawType !== "update" && rawType !== "completion") {
1492
+ return null;
1493
+ }
1494
+ if (obj.payload === void 0 || obj.payload === null) {
1495
+ throw new TransportError(
1496
+ "structured stream record missing payload",
1497
+ { kind: "request" }
1498
+ );
1499
+ }
1500
+ if (rawType === "completion") {
1501
+ this.sawTerminal = true;
1502
+ }
1503
+ const event = {
1504
+ type: rawType,
1505
+ // biome-ignore lint/suspicious/noExplicitAny: payload is untyped json
1506
+ payload: obj.payload,
1507
+ requestId: this.requestId
1508
+ };
1509
+ return event;
1510
+ }
1511
+ traceStructuredEvent(evt, raw) {
1512
+ if (!this.trace?.streamEvent) return;
1513
+ const event = {
1514
+ type: "custom",
1515
+ event: "structured",
1516
+ data: { type: evt.type, payload: evt.payload },
1517
+ textDelta: void 0,
1518
+ toolCallDelta: void 0,
1519
+ toolCalls: void 0,
1520
+ responseId: void 0,
1521
+ model: void 0,
1522
+ stopReason: void 0,
1523
+ usage: void 0,
1524
+ requestId: this.requestId,
1525
+ raw
1526
+ };
1527
+ this.trace.streamEvent({ context: this.context, event });
1528
+ }
1529
+ };
1335
1530
  function consumeSSEBuffer(buffer, flush = false) {
1336
1531
  const events = [];
1337
1532
  let eventName = "";
@@ -1374,6 +1569,19 @@ function consumeSSEBuffer(buffer, flush = false) {
1374
1569
  }
1375
1570
  return { events, remainder };
1376
1571
  }
1572
+ function consumeNDJSONBuffer(buffer, flush = false) {
1573
+ const lines = buffer.split(/\r?\n/);
1574
+ const records = [];
1575
+ const lastIndex = lines.length - 1;
1576
+ const limit = flush ? lines.length : Math.max(0, lastIndex);
1577
+ for (let i = 0; i < limit; i++) {
1578
+ const line = lines[i]?.trim();
1579
+ if (!line) continue;
1580
+ records.push(line);
1581
+ }
1582
+ const remainder = flush ? "" : lines[lastIndex] ?? "";
1583
+ return { records, remainder };
1584
+ }
1377
1585
  function mapChatEvent(raw, requestId) {
1378
1586
  let parsed = raw.data;
1379
1587
  if (raw.data) {
@@ -1502,7 +1710,6 @@ function normalizeChatResponse(payload, requestId) {
1502
1710
  const p = payload;
1503
1711
  const response = {
1504
1712
  id: p?.id,
1505
- provider: normalizeProvider(p?.provider),
1506
1713
  content: Array.isArray(p?.content) ? p.content : p?.content ? [String(p.content)] : [],
1507
1714
  stopReason: normalizeStopReason(p?.stop_reason),
1508
1715
  model: normalizeModelId(p?.model),
@@ -1533,19 +1740,6 @@ function normalizeUsage(payload) {
1533
1740
  const totalTokens = Number(payload.total_tokens ?? 0);
1534
1741
  return createUsage(inputTokens, outputTokens, totalTokens || void 0);
1535
1742
  }
1536
- function validateRequestModel(model) {
1537
- if (model === void 0 || model === null) return;
1538
- const value = modelToString(model).trim();
1539
- if (!value) {
1540
- throw new ConfigError("model id must be a non-empty string when provided");
1541
- }
1542
- const knownModels = Object.values(Models);
1543
- if (!knownModels.includes(value)) {
1544
- throw new ConfigError(
1545
- `unsupported model id "${value}". Use one of the SDK Models.* constants or omit model to use the tier's default model.`
1546
- );
1547
- }
1548
- }
1549
1743
  function buildProxyBody(params, metadata) {
1550
1744
  const modelValue = params.model ? modelToString(params.model).trim() : "";
1551
1745
  const body = {
@@ -1555,7 +1749,6 @@ function buildProxyBody(params, metadata) {
1555
1749
  body.model = modelValue;
1556
1750
  }
1557
1751
  if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
1558
- if (params.provider) body.provider = providerToString(params.provider);
1559
1752
  if (typeof params.temperature === "number")
1560
1753
  body.temperature = params.temperature;
1561
1754
  if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
@@ -1563,6 +1756,7 @@ function buildProxyBody(params, metadata) {
1563
1756
  if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
1564
1757
  if (params.tools?.length) body.tools = normalizeTools(params.tools);
1565
1758
  if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
1759
+ if (params.responseFormat) body.response_format = params.responseFormat;
1566
1760
  return body;
1567
1761
  }
1568
1762
  function normalizeMessages(messages) {
@@ -1742,6 +1936,32 @@ var CustomersClient = class {
1742
1936
  });
1743
1937
  return response.customer;
1744
1938
  }
1939
+ /**
1940
+ * Claim a customer by email, setting their external_id.
1941
+ * Used when a customer subscribes via Stripe Checkout (email only) and later
1942
+ * authenticates to the app, needing to link their identity.
1943
+ *
1944
+ * @throws {APIError} with status 404 if customer not found by email
1945
+ * @throws {APIError} with status 409 if customer already claimed or external_id in use
1946
+ */
1947
+ async claim(request) {
1948
+ this.ensureSecretKey();
1949
+ if (!request.email?.trim()) {
1950
+ throw new ConfigError("email is required");
1951
+ }
1952
+ if (!isValidEmail(request.email)) {
1953
+ throw new ConfigError("invalid email format");
1954
+ }
1955
+ if (!request.external_id?.trim()) {
1956
+ throw new ConfigError("external_id is required");
1957
+ }
1958
+ const response = await this.http.json("/customers/claim", {
1959
+ method: "POST",
1960
+ body: request,
1961
+ apiKey: this.apiKey
1962
+ });
1963
+ return response.customer;
1964
+ }
1745
1965
  /**
1746
1966
  * Delete a customer by ID.
1747
1967
  */
@@ -1806,6 +2026,13 @@ var TiersClient = class {
1806
2026
  );
1807
2027
  }
1808
2028
  }
2029
+ ensureSecretKey() {
2030
+ if (!this.apiKey || !this.apiKey.startsWith("mr_sk_")) {
2031
+ throw new ConfigError(
2032
+ "Secret key (mr_sk_*) required for checkout operations"
2033
+ );
2034
+ }
2035
+ }
1809
2036
  /**
1810
2037
  * List all tiers in the project.
1811
2038
  */
@@ -1831,6 +2058,42 @@ var TiersClient = class {
1831
2058
  });
1832
2059
  return response.tier;
1833
2060
  }
2061
+ /**
2062
+ * Create a Stripe checkout session for a tier (Stripe-first flow).
2063
+ *
2064
+ * This enables users to subscribe before authenticating. After checkout
2065
+ * completes, a customer record is created with the provided email. The
2066
+ * customer can later be linked to an identity via POST /customers/claim.
2067
+ *
2068
+ * Requires a secret key (mr_sk_*).
2069
+ *
2070
+ * @param tierId - The tier ID to create a checkout session for
2071
+ * @param request - Checkout session request with email and redirect URLs
2072
+ * @returns Checkout session with Stripe URL
2073
+ */
2074
+ async checkout(tierId, request) {
2075
+ this.ensureSecretKey();
2076
+ if (!tierId?.trim()) {
2077
+ throw new ConfigError("tierId is required");
2078
+ }
2079
+ if (!request.email?.trim()) {
2080
+ throw new ConfigError("email is required");
2081
+ }
2082
+ if (!request.success_url?.trim()) {
2083
+ throw new ConfigError("success_url is required");
2084
+ }
2085
+ if (!request.cancel_url?.trim()) {
2086
+ throw new ConfigError("cancel_url is required");
2087
+ }
2088
+ return await this.http.json(
2089
+ `/tiers/${tierId}/checkout`,
2090
+ {
2091
+ method: "POST",
2092
+ apiKey: this.apiKey,
2093
+ body: request
2094
+ }
2095
+ );
2096
+ }
1834
2097
  };
1835
2098
 
1836
2099
  // src/http.ts
@@ -2219,10 +2482,10 @@ function resolveBaseUrl(override) {
2219
2482
  ErrorCodes,
2220
2483
  ModelRelay,
2221
2484
  ModelRelayError,
2222
- Models,
2223
- Providers,
2485
+ ResponseFormatTypes,
2224
2486
  SDK_VERSION,
2225
2487
  StopReasons,
2488
+ StructuredJSONStream,
2226
2489
  TiersClient,
2227
2490
  ToolArgsError,
2228
2491
  ToolCallAccumulator,
@@ -2230,6 +2493,7 @@ function resolveBaseUrl(override) {
2230
2493
  ToolRegistry,
2231
2494
  ToolTypes,
2232
2495
  TransportError,
2496
+ WebToolModes,
2233
2497
  assistantMessageWithToolCalls,
2234
2498
  createAccessTokenAuth,
2235
2499
  createApiKeyAuth,
@@ -2254,12 +2518,10 @@ function resolveBaseUrl(override) {
2254
2518
  mergeTrace,
2255
2519
  modelToString,
2256
2520
  normalizeModelId,
2257
- normalizeProvider,
2258
2521
  normalizeStopReason,
2259
2522
  parseErrorResponse,
2260
2523
  parseToolArgs,
2261
2524
  parseToolArgsRaw,
2262
- providerToString,
2263
2525
  respondToToolCall,
2264
2526
  stopReasonToString,
2265
2527
  toolChoiceAuto,