@modelrelay/sdk 0.23.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,
@@ -430,7 +432,7 @@ function isTokenReusable(token) {
430
432
  // package.json
431
433
  var package_default = {
432
434
  name: "@modelrelay/sdk",
433
- version: "0.22.0",
435
+ version: "0.24.0",
434
436
  description: "TypeScript SDK for the ModelRelay API",
435
437
  type: "module",
436
438
  main: "dist/index.cjs",
@@ -496,6 +498,12 @@ function createUsage(inputTokens, outputTokens, totalTokens) {
496
498
  totalTokens: totalTokens ?? inputTokens + outputTokens
497
499
  };
498
500
  }
501
+ var MessageRoles = {
502
+ User: "user",
503
+ Assistant: "assistant",
504
+ System: "system",
505
+ Tool: "tool"
506
+ };
499
507
  var ToolTypes = {
500
508
  Function: "function",
501
509
  Web: "web",
@@ -1185,9 +1193,15 @@ async function executeWithRetry(registry, toolCalls, options = {}) {
1185
1193
  }
1186
1194
 
1187
1195
  // src/chat.ts
1196
+ var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
1188
1197
  var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
1189
1198
  var ChatClient = class {
1190
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;
1191
1205
  this.completions = new ChatCompletionsClient(
1192
1206
  http,
1193
1207
  auth,
@@ -1196,6 +1210,30 @@ var ChatClient = class {
1196
1210
  cfg.trace
1197
1211
  );
1198
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
+ }
1199
1237
  };
1200
1238
  var ChatCompletionsClient = class {
1201
1239
  constructor(http, auth, defaultMetadata, metrics, trace) {
@@ -1215,7 +1253,7 @@ var ChatCompletionsClient = class {
1215
1253
  if (!hasUserMessage(params.messages)) {
1216
1254
  throw new ConfigError("at least one user message is required");
1217
1255
  }
1218
- const authHeaders = await this.auth.authForChat(params.customerId);
1256
+ const authHeaders = await this.auth.authForChat();
1219
1257
  const body = buildProxyBody(
1220
1258
  params,
1221
1259
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1295,7 +1333,7 @@ var ChatCompletionsClient = class {
1295
1333
  "responseFormat with type=json_object or json_schema is required for structured streaming"
1296
1334
  );
1297
1335
  }
1298
- const authHeaders = await this.auth.authForChat(params.customerId);
1336
+ const authHeaders = await this.auth.authForChat();
1299
1337
  const body = buildProxyBody(
1300
1338
  params,
1301
1339
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1352,6 +1390,170 @@ var ChatCompletionsClient = class {
1352
1390
  );
1353
1391
  }
1354
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
+ };
1355
1557
  var ChatCompletionsStream = class {
1356
1558
  constructor(response, requestId, context, metrics, trace) {
1357
1559
  this.firstTokenEmitted = false;
@@ -1842,10 +2044,25 @@ function buildProxyBody(params, metadata) {
1842
2044
  if (params.responseFormat) body.response_format = params.responseFormat;
1843
2045
  return body;
1844
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
+ }
1845
2062
  function normalizeMessages(messages) {
1846
2063
  return messages.map((msg) => {
1847
2064
  const normalized = {
1848
- role: msg.role || "user",
2065
+ role: msg.role,
1849
2066
  content: msg.content
1850
2067
  };
1851
2068
  if (msg.toolCalls?.length) {
@@ -2555,12 +2772,14 @@ function resolveBaseUrl(override) {
2555
2772
  ChatClient,
2556
2773
  ChatCompletionsStream,
2557
2774
  ConfigError,
2775
+ CustomerChatClient,
2558
2776
  CustomersClient,
2559
2777
  DEFAULT_BASE_URL,
2560
2778
  DEFAULT_CLIENT_HEADER,
2561
2779
  DEFAULT_CONNECT_TIMEOUT_MS,
2562
2780
  DEFAULT_REQUEST_TIMEOUT_MS,
2563
2781
  ErrorCodes,
2782
+ MessageRoles,
2564
2783
  ModelRelay,
2565
2784
  ModelRelayError,
2566
2785
  ResponseFormatTypes,
package/dist/index.d.cts CHANGED
@@ -271,8 +271,18 @@ interface Project {
271
271
  createdAt?: Date;
272
272
  updatedAt?: Date;
273
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];
274
284
  interface ChatMessage {
275
- role: string;
285
+ role: MessageRole;
276
286
  content: string;
277
287
  toolCalls?: ToolCall[];
278
288
  toolCallId?: string;
@@ -354,6 +364,10 @@ interface ResponseFormat {
354
364
  type: ResponseFormatType;
355
365
  json_schema?: ResponseJSONSchemaFormat;
356
366
  }
367
+ /**
368
+ * Parameters for direct chat completions (owner PAYGO mode).
369
+ * Model is required - the developer specifies which model to use.
370
+ */
357
371
  interface ChatCompletionCreateParams {
358
372
  model: ModelId;
359
373
  messages: NonEmptyArray<ChatMessage>;
@@ -371,9 +385,38 @@ interface ChatCompletionCreateParams {
371
385
  */
372
386
  toolChoice?: ToolChoice;
373
387
  /**
374
- * 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.
394
+ */
395
+ stream?: boolean;
396
+ /**
397
+ * Optional request id to set on the call. If omitted, the server will generate one.
375
398
  */
376
- customerId?: string;
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;
377
420
  /**
378
421
  * Structured outputs configuration. When set with type `json_object` or
379
422
  * `json_schema`, the backend validates and returns structured JSON.
@@ -753,11 +796,28 @@ interface ChatRequestOptions {
753
796
  }
754
797
  declare class ChatClient {
755
798
  readonly completions: ChatCompletionsClient;
799
+ private readonly http;
800
+ private readonly auth;
801
+ private readonly defaultMetadata?;
802
+ private readonly metrics?;
803
+ private readonly trace?;
756
804
  constructor(http: HTTPClient, auth: AuthClient, cfg?: {
757
805
  defaultMetadata?: Record<string, string>;
758
806
  metrics?: MetricsCallbacks;
759
807
  trace?: TraceCallbacks;
760
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;
761
821
  }
762
822
  declare class ChatCompletionsClient {
763
823
  private readonly http;
@@ -781,6 +841,33 @@ declare class ChatCompletionsClient {
781
841
  responseFormat: ResponseFormat;
782
842
  }, options?: ChatRequestOptions): Promise<StructuredJSONStream<T>>;
783
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
+ }
784
871
  declare class ChatCompletionsStream implements AsyncIterable<ChatCompletionEvent> {
785
872
  private readonly response;
786
873
  private readonly requestId?;
@@ -1558,4 +1645,4 @@ declare class ModelRelay {
1558
1645
  constructor(options: ModelRelayOptions);
1559
1646
  }
1560
1647
 
1561
- 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 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 };
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
@@ -271,8 +271,18 @@ interface Project {
271
271
  createdAt?: Date;
272
272
  updatedAt?: Date;
273
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];
274
284
  interface ChatMessage {
275
- role: string;
285
+ role: MessageRole;
276
286
  content: string;
277
287
  toolCalls?: ToolCall[];
278
288
  toolCallId?: string;
@@ -354,6 +364,10 @@ interface ResponseFormat {
354
364
  type: ResponseFormatType;
355
365
  json_schema?: ResponseJSONSchemaFormat;
356
366
  }
367
+ /**
368
+ * Parameters for direct chat completions (owner PAYGO mode).
369
+ * Model is required - the developer specifies which model to use.
370
+ */
357
371
  interface ChatCompletionCreateParams {
358
372
  model: ModelId;
359
373
  messages: NonEmptyArray<ChatMessage>;
@@ -371,9 +385,38 @@ interface ChatCompletionCreateParams {
371
385
  */
372
386
  toolChoice?: ToolChoice;
373
387
  /**
374
- * 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.
394
+ */
395
+ stream?: boolean;
396
+ /**
397
+ * Optional request id to set on the call. If omitted, the server will generate one.
375
398
  */
376
- customerId?: string;
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;
377
420
  /**
378
421
  * Structured outputs configuration. When set with type `json_object` or
379
422
  * `json_schema`, the backend validates and returns structured JSON.
@@ -753,11 +796,28 @@ interface ChatRequestOptions {
753
796
  }
754
797
  declare class ChatClient {
755
798
  readonly completions: ChatCompletionsClient;
799
+ private readonly http;
800
+ private readonly auth;
801
+ private readonly defaultMetadata?;
802
+ private readonly metrics?;
803
+ private readonly trace?;
756
804
  constructor(http: HTTPClient, auth: AuthClient, cfg?: {
757
805
  defaultMetadata?: Record<string, string>;
758
806
  metrics?: MetricsCallbacks;
759
807
  trace?: TraceCallbacks;
760
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;
761
821
  }
762
822
  declare class ChatCompletionsClient {
763
823
  private readonly http;
@@ -781,6 +841,33 @@ declare class ChatCompletionsClient {
781
841
  responseFormat: ResponseFormat;
782
842
  }, options?: ChatRequestOptions): Promise<StructuredJSONStream<T>>;
783
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
+ }
784
871
  declare class ChatCompletionsStream implements AsyncIterable<ChatCompletionEvent> {
785
872
  private readonly response;
786
873
  private readonly requestId?;
@@ -1558,4 +1645,4 @@ declare class ModelRelay {
1558
1645
  constructor(options: ModelRelayOptions);
1559
1646
  }
1560
1647
 
1561
- 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 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 };
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
@@ -340,7 +340,7 @@ function isTokenReusable(token) {
340
340
  // package.json
341
341
  var package_default = {
342
342
  name: "@modelrelay/sdk",
343
- version: "0.22.0",
343
+ version: "0.24.0",
344
344
  description: "TypeScript SDK for the ModelRelay API",
345
345
  type: "module",
346
346
  main: "dist/index.cjs",
@@ -406,6 +406,12 @@ function createUsage(inputTokens, outputTokens, totalTokens) {
406
406
  totalTokens: totalTokens ?? inputTokens + outputTokens
407
407
  };
408
408
  }
409
+ var MessageRoles = {
410
+ User: "user",
411
+ Assistant: "assistant",
412
+ System: "system",
413
+ Tool: "tool"
414
+ };
409
415
  var ToolTypes = {
410
416
  Function: "function",
411
417
  Web: "web",
@@ -1095,9 +1101,15 @@ async function executeWithRetry(registry, toolCalls, options = {}) {
1095
1101
  }
1096
1102
 
1097
1103
  // src/chat.ts
1104
+ var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
1098
1105
  var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
1099
1106
  var ChatClient = class {
1100
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;
1101
1113
  this.completions = new ChatCompletionsClient(
1102
1114
  http,
1103
1115
  auth,
@@ -1106,6 +1118,30 @@ var ChatClient = class {
1106
1118
  cfg.trace
1107
1119
  );
1108
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
+ }
1109
1145
  };
1110
1146
  var ChatCompletionsClient = class {
1111
1147
  constructor(http, auth, defaultMetadata, metrics, trace) {
@@ -1125,7 +1161,7 @@ var ChatCompletionsClient = class {
1125
1161
  if (!hasUserMessage(params.messages)) {
1126
1162
  throw new ConfigError("at least one user message is required");
1127
1163
  }
1128
- const authHeaders = await this.auth.authForChat(params.customerId);
1164
+ const authHeaders = await this.auth.authForChat();
1129
1165
  const body = buildProxyBody(
1130
1166
  params,
1131
1167
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1205,7 +1241,7 @@ var ChatCompletionsClient = class {
1205
1241
  "responseFormat with type=json_object or json_schema is required for structured streaming"
1206
1242
  );
1207
1243
  }
1208
- const authHeaders = await this.auth.authForChat(params.customerId);
1244
+ const authHeaders = await this.auth.authForChat();
1209
1245
  const body = buildProxyBody(
1210
1246
  params,
1211
1247
  mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
@@ -1262,6 +1298,170 @@ var ChatCompletionsClient = class {
1262
1298
  );
1263
1299
  }
1264
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
+ };
1265
1465
  var ChatCompletionsStream = class {
1266
1466
  constructor(response, requestId, context, metrics, trace) {
1267
1467
  this.firstTokenEmitted = false;
@@ -1752,10 +1952,25 @@ function buildProxyBody(params, metadata) {
1752
1952
  if (params.responseFormat) body.response_format = params.responseFormat;
1753
1953
  return body;
1754
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
+ }
1755
1970
  function normalizeMessages(messages) {
1756
1971
  return messages.map((msg) => {
1757
1972
  const normalized = {
1758
- role: msg.role || "user",
1973
+ role: msg.role,
1759
1974
  content: msg.content
1760
1975
  };
1761
1976
  if (msg.toolCalls?.length) {
@@ -2464,12 +2679,14 @@ export {
2464
2679
  ChatClient,
2465
2680
  ChatCompletionsStream,
2466
2681
  ConfigError,
2682
+ CustomerChatClient,
2467
2683
  CustomersClient,
2468
2684
  DEFAULT_BASE_URL,
2469
2685
  DEFAULT_CLIENT_HEADER,
2470
2686
  DEFAULT_CONNECT_TIMEOUT_MS,
2471
2687
  DEFAULT_REQUEST_TIMEOUT_MS,
2472
2688
  ErrorCodes,
2689
+ MessageRoles,
2473
2690
  ModelRelay,
2474
2691
  ModelRelayError,
2475
2692
  ResponseFormatTypes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelrelay/sdk",
3
- "version": "0.23.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",
@@ -13,30 +13,28 @@
13
13
  "require": "./dist/index.cjs"
14
14
  }
15
15
  },
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "files": [
20
- "dist"
21
- ],
22
- "scripts": {
23
- "build": "tsup src/index.ts --format esm,cjs --dts",
24
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
25
- "lint": "tsc --noEmit",
26
- "test": "vitest run"
27
- },
28
- "keywords": [
29
- "modelrelay",
30
- "llm",
31
- "sdk",
32
- "typescript"
33
- ],
34
- "author": "Shane Vitarana",
35
- "license": "Apache-2.0",
36
- "devDependencies": {
37
- "tsup": "^8.2.4",
38
- "typescript": "^5.6.3",
39
- "vitest": "^2.1.4",
40
- "zod": "^3.23.0"
41
- }
16
+ "publishConfig": { "access": "public" },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format esm,cjs --dts",
22
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
23
+ "lint": "tsc --noEmit",
24
+ "test": "vitest run"
25
+ },
26
+ "keywords": [
27
+ "modelrelay",
28
+ "llm",
29
+ "sdk",
30
+ "typescript"
31
+ ],
32
+ "author": "Shane Vitarana",
33
+ "license": "Apache-2.0",
34
+ "devDependencies": {
35
+ "tsup": "^8.2.4",
36
+ "typescript": "^5.6.3",
37
+ "vitest": "^2.1.4",
38
+ "zod": "^3.23.0"
39
+ }
42
40
  }