@modelrelay/sdk 0.22.0 → 0.24.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,12 +82,53 @@ const stream = await mr.chat.completions.create(
82
82
  );
83
83
  ```
84
84
 
85
- ### Typed models and stop reasons
85
+ ### Typed models, stop reasons, and message roles
86
86
 
87
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
+ - Message roles use a typed union (`MessageRole`) with constants available via `MessageRoles`.
89
90
  - Usage backfills `totalTokens` when the backend omits it, ensuring consistent accounting.
90
91
 
92
+ ```ts
93
+ import { MessageRoles } from "@modelrelay/sdk";
94
+
95
+ // Use typed role constants
96
+ const messages = [
97
+ { role: MessageRoles.System, content: "You are helpful." },
98
+ { role: MessageRoles.User, content: "Hello!" },
99
+ ];
100
+
101
+ // Available roles: User, Assistant, System, Tool
102
+ ```
103
+
104
+ ### Customer-attributed requests
105
+
106
+ For customer-attributed requests, the customer's tier determines which model to use.
107
+ Use `forCustomer()` instead of providing a model:
108
+
109
+ ```ts
110
+ // Customer-attributed: tier determines model, no model parameter needed
111
+ const stream = await mr.chat.forCustomer("customer-123").create({
112
+ messages: [{ role: "user", content: "Hello!" }]
113
+ });
114
+
115
+ for await (const event of stream) {
116
+ if (event.type === "message_delta" && event.textDelta) {
117
+ console.log(event.textDelta);
118
+ }
119
+ }
120
+
121
+ // Non-streaming
122
+ const completion = await mr.chat.forCustomer("customer-123").create(
123
+ { messages: [{ role: "user", content: "Hello!" }] },
124
+ { stream: false }
125
+ );
126
+ ```
127
+
128
+ This provides compile-time separation between:
129
+ - **Direct/PAYGO requests** (`chat.completions.create({ model, ... })`) — model is required
130
+ - **Customer-attributed requests** (`chat.forCustomer(id).create(...)`) — tier determines model
131
+
91
132
  ### Structured outputs (`response_format`)
92
133
 
93
134
  Request structured JSON instead of free-form text when the backend supports it:
package/dist/index.cjs CHANGED
@@ -25,12 +25,14 @@ __export(index_exports, {
25
25
  ChatClient: () => ChatClient,
26
26
  ChatCompletionsStream: () => ChatCompletionsStream,
27
27
  ConfigError: () => ConfigError,
28
+ CustomerChatClient: () => CustomerChatClient,
28
29
  CustomersClient: () => CustomersClient,
29
30
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
30
31
  DEFAULT_CLIENT_HEADER: () => DEFAULT_CLIENT_HEADER,
31
32
  DEFAULT_CONNECT_TIMEOUT_MS: () => DEFAULT_CONNECT_TIMEOUT_MS,
32
33
  DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
33
34
  ErrorCodes: () => ErrorCodes,
35
+ MessageRoles: () => MessageRoles,
34
36
  ModelRelay: () => ModelRelay,
35
37
  ModelRelayError: () => ModelRelayError,
36
38
  ResponseFormatTypes: () => ResponseFormatTypes,
@@ -354,7 +356,6 @@ var AuthClient = class {
354
356
  );
355
357
  const token = normalizeFrontendToken(response, {
356
358
  publishableKey,
357
- customerId,
358
359
  deviceId
359
360
  });
360
361
  this.cachedFrontend.set(cacheKey, token);
@@ -406,17 +407,17 @@ function isPublishableKey(value) {
406
407
  return value.trim().toLowerCase().startsWith("mr_pk_");
407
408
  }
408
409
  function normalizeFrontendToken(payload, meta) {
409
- const expiresAt = payload.expires_at;
410
410
  return {
411
411
  token: payload.token,
412
- expiresAt: expiresAt ? new Date(expiresAt) : void 0,
412
+ expiresAt: new Date(payload.expires_at),
413
413
  expiresIn: payload.expires_in,
414
414
  tokenType: payload.token_type,
415
415
  keyId: payload.key_id,
416
416
  sessionId: payload.session_id,
417
- tokenScope: payload.token_scope,
418
- tokenSource: payload.token_source,
419
- customerId: meta.customerId,
417
+ projectId: payload.project_id,
418
+ customerId: payload.customer_id,
419
+ customerExternalId: payload.customer_external_id,
420
+ tierCode: payload.tier_code,
420
421
  publishableKey: meta.publishableKey,
421
422
  deviceId: meta.deviceId
422
423
  };
@@ -425,16 +426,13 @@ function isTokenReusable(token) {
425
426
  if (!token.token) {
426
427
  return false;
427
428
  }
428
- if (!token.expiresAt) {
429
- return true;
430
- }
431
429
  return token.expiresAt.getTime() - Date.now() > 6e4;
432
430
  }
433
431
 
434
432
  // package.json
435
433
  var package_default = {
436
434
  name: "@modelrelay/sdk",
437
- version: "0.22.0",
435
+ version: "0.24.0",
438
436
  description: "TypeScript SDK for the ModelRelay API",
439
437
  type: "module",
440
438
  main: "dist/index.cjs",
@@ -500,6 +498,12 @@ function createUsage(inputTokens, outputTokens, totalTokens) {
500
498
  totalTokens: totalTokens ?? inputTokens + outputTokens
501
499
  };
502
500
  }
501
+ var MessageRoles = {
502
+ User: "user",
503
+ Assistant: "assistant",
504
+ System: "system",
505
+ Tool: "tool"
506
+ };
503
507
  var ToolTypes = {
504
508
  Function: "function",
505
509
  Web: "web",
@@ -1189,9 +1193,15 @@ async function executeWithRetry(registry, toolCalls, options = {}) {
1189
1193
  }
1190
1194
 
1191
1195
  // src/chat.ts
1196
+ var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
1192
1197
  var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
1193
1198
  var ChatClient = class {
1194
1199
  constructor(http, auth, cfg = {}) {
1200
+ this.http = http;
1201
+ this.auth = auth;
1202
+ this.defaultMetadata = cfg.defaultMetadata;
1203
+ this.metrics = cfg.metrics;
1204
+ this.trace = cfg.trace;
1195
1205
  this.completions = new ChatCompletionsClient(
1196
1206
  http,
1197
1207
  auth,
@@ -1200,6 +1210,30 @@ var ChatClient = class {
1200
1210
  cfg.trace
1201
1211
  );
1202
1212
  }
1213
+ /**
1214
+ * Create a customer-attributed chat client for the given customer ID.
1215
+ * The customer's tier determines the model - no model parameter is needed or allowed.
1216
+ *
1217
+ * @example
1218
+ * ```typescript
1219
+ * const stream = await client.chat.forCustomer("user-123").create({
1220
+ * messages: [{ role: "user", content: "Hello!" }],
1221
+ * });
1222
+ * ```
1223
+ */
1224
+ forCustomer(customerId) {
1225
+ if (!customerId?.trim()) {
1226
+ throw new ConfigError("customerId is required");
1227
+ }
1228
+ return new CustomerChatClient(
1229
+ this.http,
1230
+ this.auth,
1231
+ customerId,
1232
+ this.defaultMetadata,
1233
+ this.metrics,
1234
+ this.trace
1235
+ );
1236
+ }
1203
1237
  };
1204
1238
  var ChatCompletionsClient = class {
1205
1239
  constructor(http, auth, defaultMetadata, metrics, trace) {
@@ -1219,7 +1253,7 @@ var ChatCompletionsClient = class {
1219
1253
  if (!hasUserMessage(params.messages)) {
1220
1254
  throw new ConfigError("at least one user message is required");
1221
1255
  }
1222
- const authHeaders = await this.auth.authForChat(params.customerId);
1256
+ const authHeaders = await this.auth.authForChat();
1223
1257
  const body = buildProxyBody(
1224
1258
  params,
1225
1259
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1299,7 +1333,7 @@ var ChatCompletionsClient = class {
1299
1333
  "responseFormat with type=json_object or json_schema is required for structured streaming"
1300
1334
  );
1301
1335
  }
1302
- const authHeaders = await this.auth.authForChat(params.customerId);
1336
+ const authHeaders = await this.auth.authForChat();
1303
1337
  const body = buildProxyBody(
1304
1338
  params,
1305
1339
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1356,6 +1390,170 @@ var ChatCompletionsClient = class {
1356
1390
  );
1357
1391
  }
1358
1392
  };
1393
+ var CustomerChatClient = class {
1394
+ constructor(http, auth, customerId, defaultMetadata, metrics, trace) {
1395
+ this.http = http;
1396
+ this.auth = auth;
1397
+ this.customerId = customerId;
1398
+ this.defaultMetadata = defaultMetadata;
1399
+ this.metrics = metrics;
1400
+ this.trace = trace;
1401
+ }
1402
+ async create(params, options = {}) {
1403
+ const stream = options.stream ?? params.stream ?? true;
1404
+ const metrics = mergeMetrics(this.metrics, options.metrics);
1405
+ const trace = mergeTrace(this.trace, options.trace);
1406
+ if (!params?.messages?.length) {
1407
+ throw new ConfigError("at least one message is required");
1408
+ }
1409
+ if (!hasUserMessage(params.messages)) {
1410
+ throw new ConfigError("at least one user message is required");
1411
+ }
1412
+ const authHeaders = await this.auth.authForChat(this.customerId);
1413
+ const body = buildCustomerProxyBody(
1414
+ params,
1415
+ mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
1416
+ );
1417
+ const requestId = params.requestId || options.requestId;
1418
+ const headers = {
1419
+ ...options.headers || {},
1420
+ [CUSTOMER_ID_HEADER]: this.customerId
1421
+ };
1422
+ if (requestId) {
1423
+ headers[REQUEST_ID_HEADER] = requestId;
1424
+ }
1425
+ const baseContext = {
1426
+ method: "POST",
1427
+ path: "/llm/proxy",
1428
+ model: void 0,
1429
+ // Model is determined by tier
1430
+ requestId
1431
+ };
1432
+ const response = await this.http.request("/llm/proxy", {
1433
+ method: "POST",
1434
+ body,
1435
+ headers,
1436
+ apiKey: authHeaders.apiKey,
1437
+ accessToken: authHeaders.accessToken,
1438
+ accept: stream ? "text/event-stream" : "application/json",
1439
+ raw: true,
1440
+ signal: options.signal,
1441
+ timeoutMs: options.timeoutMs ?? (stream ? 0 : void 0),
1442
+ useDefaultTimeout: !stream,
1443
+ connectTimeoutMs: options.connectTimeoutMs,
1444
+ retry: options.retry,
1445
+ metrics,
1446
+ trace,
1447
+ context: baseContext
1448
+ });
1449
+ const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
1450
+ if (!response.ok) {
1451
+ throw await parseErrorResponse(response);
1452
+ }
1453
+ if (!stream) {
1454
+ const payload = await response.json();
1455
+ const result = normalizeChatResponse(payload, resolvedRequestId);
1456
+ if (metrics?.usage) {
1457
+ const ctx = {
1458
+ ...baseContext,
1459
+ requestId: resolvedRequestId ?? baseContext.requestId,
1460
+ responseId: result.id
1461
+ };
1462
+ metrics.usage({ usage: result.usage, context: ctx });
1463
+ }
1464
+ return result;
1465
+ }
1466
+ const streamContext = {
1467
+ ...baseContext,
1468
+ requestId: resolvedRequestId ?? baseContext.requestId
1469
+ };
1470
+ return new ChatCompletionsStream(
1471
+ response,
1472
+ resolvedRequestId,
1473
+ streamContext,
1474
+ metrics,
1475
+ trace
1476
+ );
1477
+ }
1478
+ /**
1479
+ * Stream structured JSON responses using the NDJSON contract.
1480
+ * The request must include a structured responseFormat.
1481
+ */
1482
+ async streamJSON(params, options = {}) {
1483
+ const metrics = mergeMetrics(this.metrics, options.metrics);
1484
+ const trace = mergeTrace(this.trace, options.trace);
1485
+ if (!params?.messages?.length) {
1486
+ throw new ConfigError("at least one message is required");
1487
+ }
1488
+ if (!hasUserMessage(params.messages)) {
1489
+ throw new ConfigError("at least one user message is required");
1490
+ }
1491
+ if (!params.responseFormat || params.responseFormat.type !== "json_object" && params.responseFormat.type !== "json_schema") {
1492
+ throw new ConfigError(
1493
+ "responseFormat with type=json_object or json_schema is required for structured streaming"
1494
+ );
1495
+ }
1496
+ const authHeaders = await this.auth.authForChat(this.customerId);
1497
+ const body = buildCustomerProxyBody(
1498
+ params,
1499
+ mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
1500
+ );
1501
+ const requestId = params.requestId || options.requestId;
1502
+ const headers = {
1503
+ ...options.headers || {},
1504
+ [CUSTOMER_ID_HEADER]: this.customerId
1505
+ };
1506
+ if (requestId) {
1507
+ headers[REQUEST_ID_HEADER] = requestId;
1508
+ }
1509
+ const baseContext = {
1510
+ method: "POST",
1511
+ path: "/llm/proxy",
1512
+ model: void 0,
1513
+ // Model is determined by tier
1514
+ requestId
1515
+ };
1516
+ const response = await this.http.request("/llm/proxy", {
1517
+ method: "POST",
1518
+ body,
1519
+ headers,
1520
+ apiKey: authHeaders.apiKey,
1521
+ accessToken: authHeaders.accessToken,
1522
+ accept: "application/x-ndjson",
1523
+ raw: true,
1524
+ signal: options.signal,
1525
+ timeoutMs: options.timeoutMs ?? 0,
1526
+ useDefaultTimeout: false,
1527
+ connectTimeoutMs: options.connectTimeoutMs,
1528
+ retry: options.retry,
1529
+ metrics,
1530
+ trace,
1531
+ context: baseContext
1532
+ });
1533
+ const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
1534
+ if (!response.ok) {
1535
+ throw await parseErrorResponse(response);
1536
+ }
1537
+ const contentType = response.headers.get("Content-Type") || "";
1538
+ if (!/application\/(x-)?ndjson/i.test(contentType)) {
1539
+ throw new TransportError(
1540
+ `expected NDJSON structured stream, got Content-Type ${contentType || "missing"}`,
1541
+ { kind: "request" }
1542
+ );
1543
+ }
1544
+ const streamContext = {
1545
+ ...baseContext,
1546
+ requestId: resolvedRequestId ?? baseContext.requestId
1547
+ };
1548
+ return new StructuredJSONStream(
1549
+ response,
1550
+ resolvedRequestId,
1551
+ streamContext,
1552
+ metrics,
1553
+ trace
1554
+ );
1555
+ }
1556
+ };
1359
1557
  var ChatCompletionsStream = class {
1360
1558
  constructor(response, requestId, context, metrics, trace) {
1361
1559
  this.firstTokenEmitted = false;
@@ -1846,10 +2044,25 @@ function buildProxyBody(params, metadata) {
1846
2044
  if (params.responseFormat) body.response_format = params.responseFormat;
1847
2045
  return body;
1848
2046
  }
2047
+ function buildCustomerProxyBody(params, metadata) {
2048
+ const body = {
2049
+ messages: normalizeMessages(params.messages)
2050
+ };
2051
+ if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
2052
+ if (typeof params.temperature === "number")
2053
+ body.temperature = params.temperature;
2054
+ if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
2055
+ if (params.stop?.length) body.stop = params.stop;
2056
+ if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
2057
+ if (params.tools?.length) body.tools = normalizeTools(params.tools);
2058
+ if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
2059
+ if (params.responseFormat) body.response_format = params.responseFormat;
2060
+ return body;
2061
+ }
1849
2062
  function normalizeMessages(messages) {
1850
2063
  return messages.map((msg) => {
1851
2064
  const normalized = {
1852
- role: msg.role || "user",
2065
+ role: msg.role,
1853
2066
  content: msg.content
1854
2067
  };
1855
2068
  if (msg.toolCalls?.length) {
@@ -2559,12 +2772,14 @@ function resolveBaseUrl(override) {
2559
2772
  ChatClient,
2560
2773
  ChatCompletionsStream,
2561
2774
  ConfigError,
2775
+ CustomerChatClient,
2562
2776
  CustomersClient,
2563
2777
  DEFAULT_BASE_URL,
2564
2778
  DEFAULT_CLIENT_HEADER,
2565
2779
  DEFAULT_CONNECT_TIMEOUT_MS,
2566
2780
  DEFAULT_REQUEST_TIMEOUT_MS,
2567
2781
  ErrorCodes,
2782
+ MessageRoles,
2568
2783
  ModelRelay,
2569
2784
  ModelRelayError,
2570
2785
  ResponseFormatTypes,
package/dist/index.d.cts CHANGED
@@ -211,19 +211,29 @@ interface FrontendTokenAutoProvisionRequest {
211
211
  /** Optional TTL in seconds for the issued token. */
212
212
  ttlSeconds?: number;
213
213
  }
214
+ /** Token type for OAuth2 bearer tokens. */
215
+ type TokenType = "Bearer";
214
216
  interface FrontendToken {
217
+ /** The bearer token for authenticating LLM requests. */
215
218
  token: string;
216
- expiresAt?: Date;
217
- expiresIn?: number;
218
- tokenType?: string;
219
- keyId?: string;
220
- sessionId?: string;
221
- tokenScope?: string[];
222
- tokenSource?: string;
223
- /**
224
- * The customer identifier used when issuing the token. Added client-side for caching.
225
- */
226
- customerId?: string;
219
+ /** When the token expires. */
220
+ expiresAt: Date;
221
+ /** Seconds until the token expires. */
222
+ expiresIn: number;
223
+ /** Token type, always "Bearer". */
224
+ tokenType: TokenType;
225
+ /** The publishable key ID that issued this token. */
226
+ keyId: string;
227
+ /** Unique session identifier for this token. */
228
+ sessionId: string;
229
+ /** The project ID this token is scoped to. */
230
+ projectId: string;
231
+ /** The internal customer ID (UUID). */
232
+ customerId: string;
233
+ /** The external customer ID provided by the application. */
234
+ customerExternalId: string;
235
+ /** The tier code for the customer (e.g., "free", "pro", "enterprise"). */
236
+ tierCode: string;
227
237
  /**
228
238
  * Publishable key used for issuance. Added client-side for caching.
229
239
  */
@@ -261,8 +271,18 @@ interface Project {
261
271
  createdAt?: Date;
262
272
  updatedAt?: Date;
263
273
  }
274
+ /**
275
+ * Valid roles for chat messages.
276
+ */
277
+ declare const MessageRoles: {
278
+ readonly User: "user";
279
+ readonly Assistant: "assistant";
280
+ readonly System: "system";
281
+ readonly Tool: "tool";
282
+ };
283
+ type MessageRole = (typeof MessageRoles)[keyof typeof MessageRoles];
264
284
  interface ChatMessage {
265
- role: string;
285
+ role: MessageRole;
266
286
  content: string;
267
287
  toolCalls?: ToolCall[];
268
288
  toolCallId?: string;
@@ -344,6 +364,10 @@ interface ResponseFormat {
344
364
  type: ResponseFormatType;
345
365
  json_schema?: ResponseJSONSchemaFormat;
346
366
  }
367
+ /**
368
+ * Parameters for direct chat completions (owner PAYGO mode).
369
+ * Model is required - the developer specifies which model to use.
370
+ */
347
371
  interface ChatCompletionCreateParams {
348
372
  model: ModelId;
349
373
  messages: NonEmptyArray<ChatMessage>;
@@ -361,9 +385,38 @@ interface ChatCompletionCreateParams {
361
385
  */
362
386
  toolChoice?: ToolChoice;
363
387
  /**
364
- * When using publishable keys, a customer id is required to mint a frontend token.
388
+ * Structured outputs configuration. When set with type `json_object` or
389
+ * `json_schema`, the backend validates and returns structured JSON.
390
+ */
391
+ responseFormat?: ResponseFormat;
392
+ /**
393
+ * Opt out of SSE streaming and request a blocking JSON response.
365
394
  */
366
- customerId?: string;
395
+ stream?: boolean;
396
+ /**
397
+ * Optional request id to set on the call. If omitted, the server will generate one.
398
+ */
399
+ requestId?: string;
400
+ }
401
+ /**
402
+ * Parameters for customer-attributed chat completions.
403
+ * Model is NOT included - the customer's tier determines the model.
404
+ */
405
+ interface CustomerChatParams {
406
+ messages: NonEmptyArray<ChatMessage>;
407
+ maxTokens?: number;
408
+ temperature?: number;
409
+ metadata?: Record<string, string>;
410
+ stop?: string[];
411
+ stopSequences?: string[];
412
+ /**
413
+ * Tools available for the model to call.
414
+ */
415
+ tools?: Tool[];
416
+ /**
417
+ * Controls how the model responds to tool calls.
418
+ */
419
+ toolChoice?: ToolChoice;
367
420
  /**
368
421
  * Structured outputs configuration. When set with type `json_object` or
369
422
  * `json_schema`, the backend validates and returns structured JSON.
@@ -514,13 +567,15 @@ interface StructuredJSONEvent<T> {
514
567
  }
515
568
  interface APIFrontendToken {
516
569
  token: string;
517
- expires_at?: string;
518
- expires_in?: number;
519
- token_type?: string;
520
- key_id?: string;
521
- session_id?: string;
522
- token_scope?: string[];
523
- token_source?: string;
570
+ expires_at: string;
571
+ expires_in: number;
572
+ token_type: TokenType;
573
+ key_id: string;
574
+ session_id: string;
575
+ project_id: string;
576
+ customer_id: string;
577
+ customer_external_id: string;
578
+ tier_code: string;
524
579
  }
525
580
  interface APICustomerRef {
526
581
  id: string;
@@ -741,11 +796,28 @@ interface ChatRequestOptions {
741
796
  }
742
797
  declare class ChatClient {
743
798
  readonly completions: ChatCompletionsClient;
799
+ private readonly http;
800
+ private readonly auth;
801
+ private readonly defaultMetadata?;
802
+ private readonly metrics?;
803
+ private readonly trace?;
744
804
  constructor(http: HTTPClient, auth: AuthClient, cfg?: {
745
805
  defaultMetadata?: Record<string, string>;
746
806
  metrics?: MetricsCallbacks;
747
807
  trace?: TraceCallbacks;
748
808
  });
809
+ /**
810
+ * Create a customer-attributed chat client for the given customer ID.
811
+ * The customer's tier determines the model - no model parameter is needed or allowed.
812
+ *
813
+ * @example
814
+ * ```typescript
815
+ * const stream = await client.chat.forCustomer("user-123").create({
816
+ * messages: [{ role: "user", content: "Hello!" }],
817
+ * });
818
+ * ```
819
+ */
820
+ forCustomer(customerId: string): CustomerChatClient;
749
821
  }
750
822
  declare class ChatCompletionsClient {
751
823
  private readonly http;
@@ -769,6 +841,33 @@ declare class ChatCompletionsClient {
769
841
  responseFormat: ResponseFormat;
770
842
  }, options?: ChatRequestOptions): Promise<StructuredJSONStream<T>>;
771
843
  }
844
+ /**
845
+ * Client for customer-attributed chat completions.
846
+ * The customer's tier determines the model - no model parameter is needed or allowed.
847
+ */
848
+ declare class CustomerChatClient {
849
+ private readonly http;
850
+ private readonly auth;
851
+ private readonly customerId;
852
+ private readonly defaultMetadata?;
853
+ private readonly metrics?;
854
+ private readonly trace?;
855
+ constructor(http: HTTPClient, auth: AuthClient, customerId: string, defaultMetadata?: Record<string, string>, metrics?: MetricsCallbacks, trace?: TraceCallbacks);
856
+ create(params: CustomerChatParams & {
857
+ stream: false;
858
+ }, options?: ChatRequestOptions): Promise<ChatCompletionResponse>;
859
+ create(params: CustomerChatParams, options: ChatRequestOptions & {
860
+ stream: false;
861
+ }): Promise<ChatCompletionResponse>;
862
+ create(params: CustomerChatParams, options?: ChatRequestOptions): Promise<ChatCompletionsStream>;
863
+ /**
864
+ * Stream structured JSON responses using the NDJSON contract.
865
+ * The request must include a structured responseFormat.
866
+ */
867
+ streamJSON<T>(params: CustomerChatParams & {
868
+ responseFormat: ResponseFormat;
869
+ }, options?: ChatRequestOptions): Promise<StructuredJSONStream<T>>;
870
+ }
772
871
  declare class ChatCompletionsStream implements AsyncIterable<ChatCompletionEvent> {
773
872
  private readonly response;
774
873
  private readonly requestId?;
@@ -1546,4 +1645,4 @@ declare class ModelRelay {
1546
1645
  constructor(options: ModelRelayOptions);
1547
1646
  }
1548
1647
 
1549
- export { type APIChatResponse, type APIChatUsage, type APICheckoutSession, type APICustomerRef, APIError, type APIFrontendToken, type APIKey, AuthClient, type AuthHeaders, ChatClient, type ChatCompletionCreateParams, type ChatCompletionEvent, type ChatCompletionResponse, ChatCompletionsStream, type ChatEventType, type ChatMessage, type CheckoutSession, type CheckoutSessionRequest, type CodeExecConfig, ConfigError, type Customer, type CustomerClaimRequest, type CustomerCreateRequest, type CustomerMetadata, type CustomerUpsertRequest, CustomersClient, DEFAULT_BASE_URL, DEFAULT_CLIENT_HEADER, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, type ErrorCategory, type ErrorCode, ErrorCodes, type FieldError, type FrontendCustomer, type FrontendToken, type FrontendTokenAutoProvisionRequest, type FrontendTokenRequest, type FunctionCall, type FunctionCallDelta, type FunctionTool, type HttpRequestMetrics, type JsonSchemaOptions, type KnownStopReason, type MessageDeltaData, type MessageStartData, type MessageStopData, type MetricsCallbacks, type ModelId, ModelRelay, type ModelRelayBaseOptions, ModelRelayError, type ModelRelayKeyOptions, type ModelRelayOptions, type ModelRelayOptionsLegacy, type ModelRelayTokenOptions, type NonEmptyArray, type PriceInterval, type Project, type ProviderId, type RequestContext, type ResponseFormat, type ResponseFormatType, ResponseFormatTypes, type ResponseJSONSchemaFormat, type RetryConfig, type RetryMetadata, type RetryOptions, SDK_VERSION, type Schema, type StopReason, StopReasons, type StreamFirstTokenMetrics, type StructuredJSONEvent, type StructuredJSONRecordType, StructuredJSONStream, type SubscriptionStatus, type Tier, type TierCheckoutRequest, type TierCheckoutSession, TiersClient, type TokenUsageMetrics, type Tool, ToolArgsError, type ToolCall, ToolCallAccumulator, type ToolCallDelta, type ToolChoice, type ToolChoiceType, ToolChoiceTypes, type ToolExecutionResult, type ToolHandler, ToolRegistry, type ToolType, ToolTypes, type TraceCallbacks, TransportError, type TransportErrorKind, type Usage, type UsageSummary, type WebSearchConfig, type WebToolMode, WebToolModes, type XSearchConfig, type ZodLikeSchema, assistantMessageWithToolCalls, createAccessTokenAuth, createApiKeyAuth, createAssistantMessage, createFunctionCall, createFunctionTool, createFunctionToolFromSchema, createRetryMessages, createSystemMessage, createToolCall, createUsage, createUserMessage, createWebTool, executeWithRetry, firstToolCall, formatToolErrorForModel, getRetryableErrors, hasRetryableErrors, hasToolCalls, isEmailRequired, isNoFreeTier, isNoTiers, isProvisioningError, isPublishableKey, mergeMetrics, mergeTrace, modelToString, normalizeModelId, normalizeStopReason, parseErrorResponse, parseToolArgs, parseToolArgsRaw, respondToToolCall, stopReasonToString, toolChoiceAuto, toolChoiceNone, toolChoiceRequired, toolResultMessage, tryParseToolArgs, zodToJsonSchema };
1648
+ export { type APIChatResponse, type APIChatUsage, type APICheckoutSession, type APICustomerRef, APIError, type APIFrontendToken, type APIKey, AuthClient, type AuthHeaders, ChatClient, type ChatCompletionCreateParams, type ChatCompletionEvent, type ChatCompletionResponse, ChatCompletionsStream, type ChatEventType, type ChatMessage, type CheckoutSession, type CheckoutSessionRequest, type CodeExecConfig, ConfigError, type Customer, CustomerChatClient, type CustomerChatParams, type CustomerClaimRequest, type CustomerCreateRequest, type CustomerMetadata, type CustomerUpsertRequest, CustomersClient, DEFAULT_BASE_URL, DEFAULT_CLIENT_HEADER, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, type ErrorCategory, type ErrorCode, ErrorCodes, type FieldError, type FrontendCustomer, type FrontendToken, type FrontendTokenAutoProvisionRequest, type FrontendTokenRequest, type FunctionCall, type FunctionCallDelta, type FunctionTool, type HttpRequestMetrics, type JsonSchemaOptions, type KnownStopReason, type MessageDeltaData, type MessageRole, MessageRoles, type MessageStartData, type MessageStopData, type MetricsCallbacks, type ModelId, ModelRelay, type ModelRelayBaseOptions, ModelRelayError, type ModelRelayKeyOptions, type ModelRelayOptions, type ModelRelayOptionsLegacy, type ModelRelayTokenOptions, type NonEmptyArray, type PriceInterval, type Project, type ProviderId, type RequestContext, type ResponseFormat, type ResponseFormatType, ResponseFormatTypes, type ResponseJSONSchemaFormat, type RetryConfig, type RetryMetadata, type RetryOptions, SDK_VERSION, type Schema, type StopReason, StopReasons, type StreamFirstTokenMetrics, type StructuredJSONEvent, type StructuredJSONRecordType, StructuredJSONStream, type SubscriptionStatus, type Tier, type TierCheckoutRequest, type TierCheckoutSession, TiersClient, type TokenType, type TokenUsageMetrics, type Tool, ToolArgsError, type ToolCall, ToolCallAccumulator, type ToolCallDelta, type ToolChoice, type ToolChoiceType, ToolChoiceTypes, type ToolExecutionResult, type ToolHandler, ToolRegistry, type ToolType, ToolTypes, type TraceCallbacks, TransportError, type TransportErrorKind, type Usage, type UsageSummary, type WebSearchConfig, type WebToolMode, WebToolModes, type XSearchConfig, type ZodLikeSchema, assistantMessageWithToolCalls, createAccessTokenAuth, createApiKeyAuth, createAssistantMessage, createFunctionCall, createFunctionTool, createFunctionToolFromSchema, createRetryMessages, createSystemMessage, createToolCall, createUsage, createUserMessage, createWebTool, executeWithRetry, firstToolCall, formatToolErrorForModel, getRetryableErrors, hasRetryableErrors, hasToolCalls, isEmailRequired, isNoFreeTier, isNoTiers, isProvisioningError, isPublishableKey, mergeMetrics, mergeTrace, modelToString, normalizeModelId, normalizeStopReason, parseErrorResponse, parseToolArgs, parseToolArgsRaw, respondToToolCall, stopReasonToString, toolChoiceAuto, toolChoiceNone, toolChoiceRequired, toolResultMessage, tryParseToolArgs, zodToJsonSchema };
package/dist/index.d.ts CHANGED
@@ -211,19 +211,29 @@ interface FrontendTokenAutoProvisionRequest {
211
211
  /** Optional TTL in seconds for the issued token. */
212
212
  ttlSeconds?: number;
213
213
  }
214
+ /** Token type for OAuth2 bearer tokens. */
215
+ type TokenType = "Bearer";
214
216
  interface FrontendToken {
217
+ /** The bearer token for authenticating LLM requests. */
215
218
  token: string;
216
- expiresAt?: Date;
217
- expiresIn?: number;
218
- tokenType?: string;
219
- keyId?: string;
220
- sessionId?: string;
221
- tokenScope?: string[];
222
- tokenSource?: string;
223
- /**
224
- * The customer identifier used when issuing the token. Added client-side for caching.
225
- */
226
- customerId?: string;
219
+ /** When the token expires. */
220
+ expiresAt: Date;
221
+ /** Seconds until the token expires. */
222
+ expiresIn: number;
223
+ /** Token type, always "Bearer". */
224
+ tokenType: TokenType;
225
+ /** The publishable key ID that issued this token. */
226
+ keyId: string;
227
+ /** Unique session identifier for this token. */
228
+ sessionId: string;
229
+ /** The project ID this token is scoped to. */
230
+ projectId: string;
231
+ /** The internal customer ID (UUID). */
232
+ customerId: string;
233
+ /** The external customer ID provided by the application. */
234
+ customerExternalId: string;
235
+ /** The tier code for the customer (e.g., "free", "pro", "enterprise"). */
236
+ tierCode: string;
227
237
  /**
228
238
  * Publishable key used for issuance. Added client-side for caching.
229
239
  */
@@ -261,8 +271,18 @@ interface Project {
261
271
  createdAt?: Date;
262
272
  updatedAt?: Date;
263
273
  }
274
+ /**
275
+ * Valid roles for chat messages.
276
+ */
277
+ declare const MessageRoles: {
278
+ readonly User: "user";
279
+ readonly Assistant: "assistant";
280
+ readonly System: "system";
281
+ readonly Tool: "tool";
282
+ };
283
+ type MessageRole = (typeof MessageRoles)[keyof typeof MessageRoles];
264
284
  interface ChatMessage {
265
- role: string;
285
+ role: MessageRole;
266
286
  content: string;
267
287
  toolCalls?: ToolCall[];
268
288
  toolCallId?: string;
@@ -344,6 +364,10 @@ interface ResponseFormat {
344
364
  type: ResponseFormatType;
345
365
  json_schema?: ResponseJSONSchemaFormat;
346
366
  }
367
+ /**
368
+ * Parameters for direct chat completions (owner PAYGO mode).
369
+ * Model is required - the developer specifies which model to use.
370
+ */
347
371
  interface ChatCompletionCreateParams {
348
372
  model: ModelId;
349
373
  messages: NonEmptyArray<ChatMessage>;
@@ -361,9 +385,38 @@ interface ChatCompletionCreateParams {
361
385
  */
362
386
  toolChoice?: ToolChoice;
363
387
  /**
364
- * When using publishable keys, a customer id is required to mint a frontend token.
388
+ * Structured outputs configuration. When set with type `json_object` or
389
+ * `json_schema`, the backend validates and returns structured JSON.
390
+ */
391
+ responseFormat?: ResponseFormat;
392
+ /**
393
+ * Opt out of SSE streaming and request a blocking JSON response.
365
394
  */
366
- customerId?: string;
395
+ stream?: boolean;
396
+ /**
397
+ * Optional request id to set on the call. If omitted, the server will generate one.
398
+ */
399
+ requestId?: string;
400
+ }
401
+ /**
402
+ * Parameters for customer-attributed chat completions.
403
+ * Model is NOT included - the customer's tier determines the model.
404
+ */
405
+ interface CustomerChatParams {
406
+ messages: NonEmptyArray<ChatMessage>;
407
+ maxTokens?: number;
408
+ temperature?: number;
409
+ metadata?: Record<string, string>;
410
+ stop?: string[];
411
+ stopSequences?: string[];
412
+ /**
413
+ * Tools available for the model to call.
414
+ */
415
+ tools?: Tool[];
416
+ /**
417
+ * Controls how the model responds to tool calls.
418
+ */
419
+ toolChoice?: ToolChoice;
367
420
  /**
368
421
  * Structured outputs configuration. When set with type `json_object` or
369
422
  * `json_schema`, the backend validates and returns structured JSON.
@@ -514,13 +567,15 @@ interface StructuredJSONEvent<T> {
514
567
  }
515
568
  interface APIFrontendToken {
516
569
  token: string;
517
- expires_at?: string;
518
- expires_in?: number;
519
- token_type?: string;
520
- key_id?: string;
521
- session_id?: string;
522
- token_scope?: string[];
523
- token_source?: string;
570
+ expires_at: string;
571
+ expires_in: number;
572
+ token_type: TokenType;
573
+ key_id: string;
574
+ session_id: string;
575
+ project_id: string;
576
+ customer_id: string;
577
+ customer_external_id: string;
578
+ tier_code: string;
524
579
  }
525
580
  interface APICustomerRef {
526
581
  id: string;
@@ -741,11 +796,28 @@ interface ChatRequestOptions {
741
796
  }
742
797
  declare class ChatClient {
743
798
  readonly completions: ChatCompletionsClient;
799
+ private readonly http;
800
+ private readonly auth;
801
+ private readonly defaultMetadata?;
802
+ private readonly metrics?;
803
+ private readonly trace?;
744
804
  constructor(http: HTTPClient, auth: AuthClient, cfg?: {
745
805
  defaultMetadata?: Record<string, string>;
746
806
  metrics?: MetricsCallbacks;
747
807
  trace?: TraceCallbacks;
748
808
  });
809
+ /**
810
+ * Create a customer-attributed chat client for the given customer ID.
811
+ * The customer's tier determines the model - no model parameter is needed or allowed.
812
+ *
813
+ * @example
814
+ * ```typescript
815
+ * const stream = await client.chat.forCustomer("user-123").create({
816
+ * messages: [{ role: "user", content: "Hello!" }],
817
+ * });
818
+ * ```
819
+ */
820
+ forCustomer(customerId: string): CustomerChatClient;
749
821
  }
750
822
  declare class ChatCompletionsClient {
751
823
  private readonly http;
@@ -769,6 +841,33 @@ declare class ChatCompletionsClient {
769
841
  responseFormat: ResponseFormat;
770
842
  }, options?: ChatRequestOptions): Promise<StructuredJSONStream<T>>;
771
843
  }
844
+ /**
845
+ * Client for customer-attributed chat completions.
846
+ * The customer's tier determines the model - no model parameter is needed or allowed.
847
+ */
848
+ declare class CustomerChatClient {
849
+ private readonly http;
850
+ private readonly auth;
851
+ private readonly customerId;
852
+ private readonly defaultMetadata?;
853
+ private readonly metrics?;
854
+ private readonly trace?;
855
+ constructor(http: HTTPClient, auth: AuthClient, customerId: string, defaultMetadata?: Record<string, string>, metrics?: MetricsCallbacks, trace?: TraceCallbacks);
856
+ create(params: CustomerChatParams & {
857
+ stream: false;
858
+ }, options?: ChatRequestOptions): Promise<ChatCompletionResponse>;
859
+ create(params: CustomerChatParams, options: ChatRequestOptions & {
860
+ stream: false;
861
+ }): Promise<ChatCompletionResponse>;
862
+ create(params: CustomerChatParams, options?: ChatRequestOptions): Promise<ChatCompletionsStream>;
863
+ /**
864
+ * Stream structured JSON responses using the NDJSON contract.
865
+ * The request must include a structured responseFormat.
866
+ */
867
+ streamJSON<T>(params: CustomerChatParams & {
868
+ responseFormat: ResponseFormat;
869
+ }, options?: ChatRequestOptions): Promise<StructuredJSONStream<T>>;
870
+ }
772
871
  declare class ChatCompletionsStream implements AsyncIterable<ChatCompletionEvent> {
773
872
  private readonly response;
774
873
  private readonly requestId?;
@@ -1546,4 +1645,4 @@ declare class ModelRelay {
1546
1645
  constructor(options: ModelRelayOptions);
1547
1646
  }
1548
1647
 
1549
- export { type APIChatResponse, type APIChatUsage, type APICheckoutSession, type APICustomerRef, APIError, type APIFrontendToken, type APIKey, AuthClient, type AuthHeaders, ChatClient, type ChatCompletionCreateParams, type ChatCompletionEvent, type ChatCompletionResponse, ChatCompletionsStream, type ChatEventType, type ChatMessage, type CheckoutSession, type CheckoutSessionRequest, type CodeExecConfig, ConfigError, type Customer, type CustomerClaimRequest, type CustomerCreateRequest, type CustomerMetadata, type CustomerUpsertRequest, CustomersClient, DEFAULT_BASE_URL, DEFAULT_CLIENT_HEADER, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, type ErrorCategory, type ErrorCode, ErrorCodes, type FieldError, type FrontendCustomer, type FrontendToken, type FrontendTokenAutoProvisionRequest, type FrontendTokenRequest, type FunctionCall, type FunctionCallDelta, type FunctionTool, type HttpRequestMetrics, type JsonSchemaOptions, type KnownStopReason, type MessageDeltaData, type MessageStartData, type MessageStopData, type MetricsCallbacks, type ModelId, ModelRelay, type ModelRelayBaseOptions, ModelRelayError, type ModelRelayKeyOptions, type ModelRelayOptions, type ModelRelayOptionsLegacy, type ModelRelayTokenOptions, type NonEmptyArray, type PriceInterval, type Project, type ProviderId, type RequestContext, type ResponseFormat, type ResponseFormatType, ResponseFormatTypes, type ResponseJSONSchemaFormat, type RetryConfig, type RetryMetadata, type RetryOptions, SDK_VERSION, type Schema, type StopReason, StopReasons, type StreamFirstTokenMetrics, type StructuredJSONEvent, type StructuredJSONRecordType, StructuredJSONStream, type SubscriptionStatus, type Tier, type TierCheckoutRequest, type TierCheckoutSession, TiersClient, type TokenUsageMetrics, type Tool, ToolArgsError, type ToolCall, ToolCallAccumulator, type ToolCallDelta, type ToolChoice, type ToolChoiceType, ToolChoiceTypes, type ToolExecutionResult, type ToolHandler, ToolRegistry, type ToolType, ToolTypes, type TraceCallbacks, TransportError, type TransportErrorKind, type Usage, type UsageSummary, type WebSearchConfig, type WebToolMode, WebToolModes, type XSearchConfig, type ZodLikeSchema, assistantMessageWithToolCalls, createAccessTokenAuth, createApiKeyAuth, createAssistantMessage, createFunctionCall, createFunctionTool, createFunctionToolFromSchema, createRetryMessages, createSystemMessage, createToolCall, createUsage, createUserMessage, createWebTool, executeWithRetry, firstToolCall, formatToolErrorForModel, getRetryableErrors, hasRetryableErrors, hasToolCalls, isEmailRequired, isNoFreeTier, isNoTiers, isProvisioningError, isPublishableKey, mergeMetrics, mergeTrace, modelToString, normalizeModelId, normalizeStopReason, parseErrorResponse, parseToolArgs, parseToolArgsRaw, respondToToolCall, stopReasonToString, toolChoiceAuto, toolChoiceNone, toolChoiceRequired, toolResultMessage, tryParseToolArgs, zodToJsonSchema };
1648
+ export { type APIChatResponse, type APIChatUsage, type APICheckoutSession, type APICustomerRef, APIError, type APIFrontendToken, type APIKey, AuthClient, type AuthHeaders, ChatClient, type ChatCompletionCreateParams, type ChatCompletionEvent, type ChatCompletionResponse, ChatCompletionsStream, type ChatEventType, type ChatMessage, type CheckoutSession, type CheckoutSessionRequest, type CodeExecConfig, ConfigError, type Customer, CustomerChatClient, type CustomerChatParams, type CustomerClaimRequest, type CustomerCreateRequest, type CustomerMetadata, type CustomerUpsertRequest, CustomersClient, DEFAULT_BASE_URL, DEFAULT_CLIENT_HEADER, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, type ErrorCategory, type ErrorCode, ErrorCodes, type FieldError, type FrontendCustomer, type FrontendToken, type FrontendTokenAutoProvisionRequest, type FrontendTokenRequest, type FunctionCall, type FunctionCallDelta, type FunctionTool, type HttpRequestMetrics, type JsonSchemaOptions, type KnownStopReason, type MessageDeltaData, type MessageRole, MessageRoles, type MessageStartData, type MessageStopData, type MetricsCallbacks, type ModelId, ModelRelay, type ModelRelayBaseOptions, ModelRelayError, type ModelRelayKeyOptions, type ModelRelayOptions, type ModelRelayOptionsLegacy, type ModelRelayTokenOptions, type NonEmptyArray, type PriceInterval, type Project, type ProviderId, type RequestContext, type ResponseFormat, type ResponseFormatType, ResponseFormatTypes, type ResponseJSONSchemaFormat, type RetryConfig, type RetryMetadata, type RetryOptions, SDK_VERSION, type Schema, type StopReason, StopReasons, type StreamFirstTokenMetrics, type StructuredJSONEvent, type StructuredJSONRecordType, StructuredJSONStream, type SubscriptionStatus, type Tier, type TierCheckoutRequest, type TierCheckoutSession, TiersClient, type TokenType, type TokenUsageMetrics, type Tool, ToolArgsError, type ToolCall, ToolCallAccumulator, type ToolCallDelta, type ToolChoice, type ToolChoiceType, ToolChoiceTypes, type ToolExecutionResult, type ToolHandler, ToolRegistry, type ToolType, ToolTypes, type TraceCallbacks, TransportError, type TransportErrorKind, type Usage, type UsageSummary, type WebSearchConfig, type WebToolMode, WebToolModes, type XSearchConfig, type ZodLikeSchema, assistantMessageWithToolCalls, createAccessTokenAuth, createApiKeyAuth, createAssistantMessage, createFunctionCall, createFunctionTool, createFunctionToolFromSchema, createRetryMessages, createSystemMessage, createToolCall, createUsage, createUserMessage, createWebTool, executeWithRetry, firstToolCall, formatToolErrorForModel, getRetryableErrors, hasRetryableErrors, hasToolCalls, isEmailRequired, isNoFreeTier, isNoTiers, isProvisioningError, isPublishableKey, mergeMetrics, mergeTrace, modelToString, normalizeModelId, normalizeStopReason, parseErrorResponse, parseToolArgs, parseToolArgsRaw, respondToToolCall, stopReasonToString, toolChoiceAuto, toolChoiceNone, toolChoiceRequired, toolResultMessage, tryParseToolArgs, zodToJsonSchema };
package/dist/index.js CHANGED
@@ -264,7 +264,6 @@ var AuthClient = class {
264
264
  );
265
265
  const token = normalizeFrontendToken(response, {
266
266
  publishableKey,
267
- customerId,
268
267
  deviceId
269
268
  });
270
269
  this.cachedFrontend.set(cacheKey, token);
@@ -316,17 +315,17 @@ function isPublishableKey(value) {
316
315
  return value.trim().toLowerCase().startsWith("mr_pk_");
317
316
  }
318
317
  function normalizeFrontendToken(payload, meta) {
319
- const expiresAt = payload.expires_at;
320
318
  return {
321
319
  token: payload.token,
322
- expiresAt: expiresAt ? new Date(expiresAt) : void 0,
320
+ expiresAt: new Date(payload.expires_at),
323
321
  expiresIn: payload.expires_in,
324
322
  tokenType: payload.token_type,
325
323
  keyId: payload.key_id,
326
324
  sessionId: payload.session_id,
327
- tokenScope: payload.token_scope,
328
- tokenSource: payload.token_source,
329
- customerId: meta.customerId,
325
+ projectId: payload.project_id,
326
+ customerId: payload.customer_id,
327
+ customerExternalId: payload.customer_external_id,
328
+ tierCode: payload.tier_code,
330
329
  publishableKey: meta.publishableKey,
331
330
  deviceId: meta.deviceId
332
331
  };
@@ -335,16 +334,13 @@ function isTokenReusable(token) {
335
334
  if (!token.token) {
336
335
  return false;
337
336
  }
338
- if (!token.expiresAt) {
339
- return true;
340
- }
341
337
  return token.expiresAt.getTime() - Date.now() > 6e4;
342
338
  }
343
339
 
344
340
  // package.json
345
341
  var package_default = {
346
342
  name: "@modelrelay/sdk",
347
- version: "0.22.0",
343
+ version: "0.24.0",
348
344
  description: "TypeScript SDK for the ModelRelay API",
349
345
  type: "module",
350
346
  main: "dist/index.cjs",
@@ -410,6 +406,12 @@ function createUsage(inputTokens, outputTokens, totalTokens) {
410
406
  totalTokens: totalTokens ?? inputTokens + outputTokens
411
407
  };
412
408
  }
409
+ var MessageRoles = {
410
+ User: "user",
411
+ Assistant: "assistant",
412
+ System: "system",
413
+ Tool: "tool"
414
+ };
413
415
  var ToolTypes = {
414
416
  Function: "function",
415
417
  Web: "web",
@@ -1099,9 +1101,15 @@ async function executeWithRetry(registry, toolCalls, options = {}) {
1099
1101
  }
1100
1102
 
1101
1103
  // src/chat.ts
1104
+ var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
1102
1105
  var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
1103
1106
  var ChatClient = class {
1104
1107
  constructor(http, auth, cfg = {}) {
1108
+ this.http = http;
1109
+ this.auth = auth;
1110
+ this.defaultMetadata = cfg.defaultMetadata;
1111
+ this.metrics = cfg.metrics;
1112
+ this.trace = cfg.trace;
1105
1113
  this.completions = new ChatCompletionsClient(
1106
1114
  http,
1107
1115
  auth,
@@ -1110,6 +1118,30 @@ var ChatClient = class {
1110
1118
  cfg.trace
1111
1119
  );
1112
1120
  }
1121
+ /**
1122
+ * Create a customer-attributed chat client for the given customer ID.
1123
+ * The customer's tier determines the model - no model parameter is needed or allowed.
1124
+ *
1125
+ * @example
1126
+ * ```typescript
1127
+ * const stream = await client.chat.forCustomer("user-123").create({
1128
+ * messages: [{ role: "user", content: "Hello!" }],
1129
+ * });
1130
+ * ```
1131
+ */
1132
+ forCustomer(customerId) {
1133
+ if (!customerId?.trim()) {
1134
+ throw new ConfigError("customerId is required");
1135
+ }
1136
+ return new CustomerChatClient(
1137
+ this.http,
1138
+ this.auth,
1139
+ customerId,
1140
+ this.defaultMetadata,
1141
+ this.metrics,
1142
+ this.trace
1143
+ );
1144
+ }
1113
1145
  };
1114
1146
  var ChatCompletionsClient = class {
1115
1147
  constructor(http, auth, defaultMetadata, metrics, trace) {
@@ -1129,7 +1161,7 @@ var ChatCompletionsClient = class {
1129
1161
  if (!hasUserMessage(params.messages)) {
1130
1162
  throw new ConfigError("at least one user message is required");
1131
1163
  }
1132
- const authHeaders = await this.auth.authForChat(params.customerId);
1164
+ const authHeaders = await this.auth.authForChat();
1133
1165
  const body = buildProxyBody(
1134
1166
  params,
1135
1167
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1209,7 +1241,7 @@ var ChatCompletionsClient = class {
1209
1241
  "responseFormat with type=json_object or json_schema is required for structured streaming"
1210
1242
  );
1211
1243
  }
1212
- const authHeaders = await this.auth.authForChat(params.customerId);
1244
+ const authHeaders = await this.auth.authForChat();
1213
1245
  const body = buildProxyBody(
1214
1246
  params,
1215
1247
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1266,6 +1298,170 @@ var ChatCompletionsClient = class {
1266
1298
  );
1267
1299
  }
1268
1300
  };
1301
+ var CustomerChatClient = class {
1302
+ constructor(http, auth, customerId, defaultMetadata, metrics, trace) {
1303
+ this.http = http;
1304
+ this.auth = auth;
1305
+ this.customerId = customerId;
1306
+ this.defaultMetadata = defaultMetadata;
1307
+ this.metrics = metrics;
1308
+ this.trace = trace;
1309
+ }
1310
+ async create(params, options = {}) {
1311
+ const stream = options.stream ?? params.stream ?? true;
1312
+ const metrics = mergeMetrics(this.metrics, options.metrics);
1313
+ const trace = mergeTrace(this.trace, options.trace);
1314
+ if (!params?.messages?.length) {
1315
+ throw new ConfigError("at least one message is required");
1316
+ }
1317
+ if (!hasUserMessage(params.messages)) {
1318
+ throw new ConfigError("at least one user message is required");
1319
+ }
1320
+ const authHeaders = await this.auth.authForChat(this.customerId);
1321
+ const body = buildCustomerProxyBody(
1322
+ params,
1323
+ mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
1324
+ );
1325
+ const requestId = params.requestId || options.requestId;
1326
+ const headers = {
1327
+ ...options.headers || {},
1328
+ [CUSTOMER_ID_HEADER]: this.customerId
1329
+ };
1330
+ if (requestId) {
1331
+ headers[REQUEST_ID_HEADER] = requestId;
1332
+ }
1333
+ const baseContext = {
1334
+ method: "POST",
1335
+ path: "/llm/proxy",
1336
+ model: void 0,
1337
+ // Model is determined by tier
1338
+ requestId
1339
+ };
1340
+ const response = await this.http.request("/llm/proxy", {
1341
+ method: "POST",
1342
+ body,
1343
+ headers,
1344
+ apiKey: authHeaders.apiKey,
1345
+ accessToken: authHeaders.accessToken,
1346
+ accept: stream ? "text/event-stream" : "application/json",
1347
+ raw: true,
1348
+ signal: options.signal,
1349
+ timeoutMs: options.timeoutMs ?? (stream ? 0 : void 0),
1350
+ useDefaultTimeout: !stream,
1351
+ connectTimeoutMs: options.connectTimeoutMs,
1352
+ retry: options.retry,
1353
+ metrics,
1354
+ trace,
1355
+ context: baseContext
1356
+ });
1357
+ const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
1358
+ if (!response.ok) {
1359
+ throw await parseErrorResponse(response);
1360
+ }
1361
+ if (!stream) {
1362
+ const payload = await response.json();
1363
+ const result = normalizeChatResponse(payload, resolvedRequestId);
1364
+ if (metrics?.usage) {
1365
+ const ctx = {
1366
+ ...baseContext,
1367
+ requestId: resolvedRequestId ?? baseContext.requestId,
1368
+ responseId: result.id
1369
+ };
1370
+ metrics.usage({ usage: result.usage, context: ctx });
1371
+ }
1372
+ return result;
1373
+ }
1374
+ const streamContext = {
1375
+ ...baseContext,
1376
+ requestId: resolvedRequestId ?? baseContext.requestId
1377
+ };
1378
+ return new ChatCompletionsStream(
1379
+ response,
1380
+ resolvedRequestId,
1381
+ streamContext,
1382
+ metrics,
1383
+ trace
1384
+ );
1385
+ }
1386
+ /**
1387
+ * Stream structured JSON responses using the NDJSON contract.
1388
+ * The request must include a structured responseFormat.
1389
+ */
1390
+ async streamJSON(params, options = {}) {
1391
+ const metrics = mergeMetrics(this.metrics, options.metrics);
1392
+ const trace = mergeTrace(this.trace, options.trace);
1393
+ if (!params?.messages?.length) {
1394
+ throw new ConfigError("at least one message is required");
1395
+ }
1396
+ if (!hasUserMessage(params.messages)) {
1397
+ throw new ConfigError("at least one user message is required");
1398
+ }
1399
+ if (!params.responseFormat || params.responseFormat.type !== "json_object" && params.responseFormat.type !== "json_schema") {
1400
+ throw new ConfigError(
1401
+ "responseFormat with type=json_object or json_schema is required for structured streaming"
1402
+ );
1403
+ }
1404
+ const authHeaders = await this.auth.authForChat(this.customerId);
1405
+ const body = buildCustomerProxyBody(
1406
+ params,
1407
+ mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
1408
+ );
1409
+ const requestId = params.requestId || options.requestId;
1410
+ const headers = {
1411
+ ...options.headers || {},
1412
+ [CUSTOMER_ID_HEADER]: this.customerId
1413
+ };
1414
+ if (requestId) {
1415
+ headers[REQUEST_ID_HEADER] = requestId;
1416
+ }
1417
+ const baseContext = {
1418
+ method: "POST",
1419
+ path: "/llm/proxy",
1420
+ model: void 0,
1421
+ // Model is determined by tier
1422
+ requestId
1423
+ };
1424
+ const response = await this.http.request("/llm/proxy", {
1425
+ method: "POST",
1426
+ body,
1427
+ headers,
1428
+ apiKey: authHeaders.apiKey,
1429
+ accessToken: authHeaders.accessToken,
1430
+ accept: "application/x-ndjson",
1431
+ raw: true,
1432
+ signal: options.signal,
1433
+ timeoutMs: options.timeoutMs ?? 0,
1434
+ useDefaultTimeout: false,
1435
+ connectTimeoutMs: options.connectTimeoutMs,
1436
+ retry: options.retry,
1437
+ metrics,
1438
+ trace,
1439
+ context: baseContext
1440
+ });
1441
+ const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
1442
+ if (!response.ok) {
1443
+ throw await parseErrorResponse(response);
1444
+ }
1445
+ const contentType = response.headers.get("Content-Type") || "";
1446
+ if (!/application\/(x-)?ndjson/i.test(contentType)) {
1447
+ throw new TransportError(
1448
+ `expected NDJSON structured stream, got Content-Type ${contentType || "missing"}`,
1449
+ { kind: "request" }
1450
+ );
1451
+ }
1452
+ const streamContext = {
1453
+ ...baseContext,
1454
+ requestId: resolvedRequestId ?? baseContext.requestId
1455
+ };
1456
+ return new StructuredJSONStream(
1457
+ response,
1458
+ resolvedRequestId,
1459
+ streamContext,
1460
+ metrics,
1461
+ trace
1462
+ );
1463
+ }
1464
+ };
1269
1465
  var ChatCompletionsStream = class {
1270
1466
  constructor(response, requestId, context, metrics, trace) {
1271
1467
  this.firstTokenEmitted = false;
@@ -1756,10 +1952,25 @@ function buildProxyBody(params, metadata) {
1756
1952
  if (params.responseFormat) body.response_format = params.responseFormat;
1757
1953
  return body;
1758
1954
  }
1955
+ function buildCustomerProxyBody(params, metadata) {
1956
+ const body = {
1957
+ messages: normalizeMessages(params.messages)
1958
+ };
1959
+ if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
1960
+ if (typeof params.temperature === "number")
1961
+ body.temperature = params.temperature;
1962
+ if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
1963
+ if (params.stop?.length) body.stop = params.stop;
1964
+ if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
1965
+ if (params.tools?.length) body.tools = normalizeTools(params.tools);
1966
+ if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
1967
+ if (params.responseFormat) body.response_format = params.responseFormat;
1968
+ return body;
1969
+ }
1759
1970
  function normalizeMessages(messages) {
1760
1971
  return messages.map((msg) => {
1761
1972
  const normalized = {
1762
- role: msg.role || "user",
1973
+ role: msg.role,
1763
1974
  content: msg.content
1764
1975
  };
1765
1976
  if (msg.toolCalls?.length) {
@@ -2468,12 +2679,14 @@ export {
2468
2679
  ChatClient,
2469
2680
  ChatCompletionsStream,
2470
2681
  ConfigError,
2682
+ CustomerChatClient,
2471
2683
  CustomersClient,
2472
2684
  DEFAULT_BASE_URL,
2473
2685
  DEFAULT_CLIENT_HEADER,
2474
2686
  DEFAULT_CONNECT_TIMEOUT_MS,
2475
2687
  DEFAULT_REQUEST_TIMEOUT_MS,
2476
2688
  ErrorCodes,
2689
+ MessageRoles,
2477
2690
  ModelRelay,
2478
2691
  ModelRelayError,
2479
2692
  ResponseFormatTypes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelrelay/sdk",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "TypeScript SDK for the ModelRelay API",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",