@modelrelay/sdk 0.23.0 → 0.25.1
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 +191 -1
- package/dist/index.cjs +569 -7
- package/dist/index.d.cts +1015 -689
- package/dist/index.d.ts +1015 -689
- package/dist/index.js +562 -7
- package/package.json +25 -27
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.
|
|
343
|
+
version: "0.25.1",
|
|
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",
|
|
@@ -1094,10 +1100,68 @@ async function executeWithRetry(registry, toolCalls, options = {}) {
|
|
|
1094
1100
|
return Array.from(successfulResults.values());
|
|
1095
1101
|
}
|
|
1096
1102
|
|
|
1103
|
+
// src/structured.ts
|
|
1104
|
+
var StructuredDecodeError = class extends Error {
|
|
1105
|
+
constructor(message, rawJson, attempt) {
|
|
1106
|
+
super(`structured output decode error (attempt ${attempt}): ${message}`);
|
|
1107
|
+
this.name = "StructuredDecodeError";
|
|
1108
|
+
this.rawJson = rawJson;
|
|
1109
|
+
this.attempt = attempt;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
var StructuredExhaustedError = class extends Error {
|
|
1113
|
+
constructor(lastRawJson, allAttempts, finalError) {
|
|
1114
|
+
const errorMsg = finalError.kind === "decode" ? finalError.message : finalError.issues.map((i) => i.message).join("; ");
|
|
1115
|
+
super(
|
|
1116
|
+
`structured output failed after ${allAttempts.length} attempts: ${errorMsg}`
|
|
1117
|
+
);
|
|
1118
|
+
this.name = "StructuredExhaustedError";
|
|
1119
|
+
this.lastRawJson = lastRawJson;
|
|
1120
|
+
this.allAttempts = allAttempts;
|
|
1121
|
+
this.finalError = finalError;
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
var defaultRetryHandler = {
|
|
1125
|
+
onValidationError(_attempt, _rawJson, error, _originalMessages) {
|
|
1126
|
+
const errorMsg = error.kind === "decode" ? error.message : error.issues.map((i) => `${i.path ?? ""}: ${i.message}`).join("; ");
|
|
1127
|
+
return [
|
|
1128
|
+
{
|
|
1129
|
+
role: "user",
|
|
1130
|
+
content: `The previous response did not match the expected schema. Error: ${errorMsg}. Please provide a response that matches the schema exactly.`
|
|
1131
|
+
}
|
|
1132
|
+
];
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
function responseFormatFromZod(schema, name = "response") {
|
|
1136
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
1137
|
+
return {
|
|
1138
|
+
type: "json_schema",
|
|
1139
|
+
json_schema: {
|
|
1140
|
+
name,
|
|
1141
|
+
schema: jsonSchema,
|
|
1142
|
+
strict: true
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function validateWithZod(schema, data) {
|
|
1147
|
+
const result = schema.safeParse(data);
|
|
1148
|
+
if (result.success) {
|
|
1149
|
+
return { success: true, data: result.data };
|
|
1150
|
+
}
|
|
1151
|
+
const errorMsg = result.error && typeof result.error === "object" && "message" in result.error ? String(result.error.message) : "validation failed";
|
|
1152
|
+
return { success: false, error: errorMsg };
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1097
1155
|
// src/chat.ts
|
|
1156
|
+
var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
|
|
1098
1157
|
var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
|
|
1099
1158
|
var ChatClient = class {
|
|
1100
1159
|
constructor(http, auth, cfg = {}) {
|
|
1160
|
+
this.http = http;
|
|
1161
|
+
this.auth = auth;
|
|
1162
|
+
this.defaultMetadata = cfg.defaultMetadata;
|
|
1163
|
+
this.metrics = cfg.metrics;
|
|
1164
|
+
this.trace = cfg.trace;
|
|
1101
1165
|
this.completions = new ChatCompletionsClient(
|
|
1102
1166
|
http,
|
|
1103
1167
|
auth,
|
|
@@ -1106,6 +1170,30 @@ var ChatClient = class {
|
|
|
1106
1170
|
cfg.trace
|
|
1107
1171
|
);
|
|
1108
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Create a customer-attributed chat client for the given customer ID.
|
|
1175
|
+
* The customer's tier determines the model - no model parameter is needed or allowed.
|
|
1176
|
+
*
|
|
1177
|
+
* @example
|
|
1178
|
+
* ```typescript
|
|
1179
|
+
* const stream = await client.chat.forCustomer("user-123").create({
|
|
1180
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
1181
|
+
* });
|
|
1182
|
+
* ```
|
|
1183
|
+
*/
|
|
1184
|
+
forCustomer(customerId) {
|
|
1185
|
+
if (!customerId?.trim()) {
|
|
1186
|
+
throw new ConfigError("customerId is required");
|
|
1187
|
+
}
|
|
1188
|
+
return new CustomerChatClient(
|
|
1189
|
+
this.http,
|
|
1190
|
+
this.auth,
|
|
1191
|
+
customerId,
|
|
1192
|
+
this.defaultMetadata,
|
|
1193
|
+
this.metrics,
|
|
1194
|
+
this.trace
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1109
1197
|
};
|
|
1110
1198
|
var ChatCompletionsClient = class {
|
|
1111
1199
|
constructor(http, auth, defaultMetadata, metrics, trace) {
|
|
@@ -1125,7 +1213,7 @@ var ChatCompletionsClient = class {
|
|
|
1125
1213
|
if (!hasUserMessage(params.messages)) {
|
|
1126
1214
|
throw new ConfigError("at least one user message is required");
|
|
1127
1215
|
}
|
|
1128
|
-
const authHeaders = await this.auth.authForChat(
|
|
1216
|
+
const authHeaders = await this.auth.authForChat();
|
|
1129
1217
|
const body = buildProxyBody(
|
|
1130
1218
|
params,
|
|
1131
1219
|
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
@@ -1205,7 +1293,7 @@ var ChatCompletionsClient = class {
|
|
|
1205
1293
|
"responseFormat with type=json_object or json_schema is required for structured streaming"
|
|
1206
1294
|
);
|
|
1207
1295
|
}
|
|
1208
|
-
const authHeaders = await this.auth.authForChat(
|
|
1296
|
+
const authHeaders = await this.auth.authForChat();
|
|
1209
1297
|
const body = buildProxyBody(
|
|
1210
1298
|
params,
|
|
1211
1299
|
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
@@ -1261,6 +1349,439 @@ var ChatCompletionsClient = class {
|
|
|
1261
1349
|
trace
|
|
1262
1350
|
);
|
|
1263
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Send a structured output request with a Zod schema.
|
|
1354
|
+
*
|
|
1355
|
+
* Auto-generates JSON schema from the Zod schema, validates the response,
|
|
1356
|
+
* and retries on validation failure if configured.
|
|
1357
|
+
*
|
|
1358
|
+
* @param schema - A Zod schema defining the expected response structure
|
|
1359
|
+
* @param params - Chat completion parameters (excluding responseFormat)
|
|
1360
|
+
* @param options - Request options including retry configuration
|
|
1361
|
+
* @returns A typed result with the parsed value
|
|
1362
|
+
*
|
|
1363
|
+
* @example
|
|
1364
|
+
* ```typescript
|
|
1365
|
+
* import { z } from 'zod';
|
|
1366
|
+
*
|
|
1367
|
+
* const PersonSchema = z.object({
|
|
1368
|
+
* name: z.string(),
|
|
1369
|
+
* age: z.number(),
|
|
1370
|
+
* });
|
|
1371
|
+
*
|
|
1372
|
+
* const result = await client.chat.completions.structured(
|
|
1373
|
+
* PersonSchema,
|
|
1374
|
+
* { model: "claude-sonnet-4-20250514", messages: [...] },
|
|
1375
|
+
* { maxRetries: 2 }
|
|
1376
|
+
* );
|
|
1377
|
+
* ```
|
|
1378
|
+
*/
|
|
1379
|
+
async structured(schema, params, options = {}) {
|
|
1380
|
+
const {
|
|
1381
|
+
maxRetries = 0,
|
|
1382
|
+
retryHandler = defaultRetryHandler,
|
|
1383
|
+
schemaName,
|
|
1384
|
+
...requestOptions
|
|
1385
|
+
} = options;
|
|
1386
|
+
const responseFormat = responseFormatFromZod(schema, schemaName);
|
|
1387
|
+
const fullParams = {
|
|
1388
|
+
...params,
|
|
1389
|
+
responseFormat,
|
|
1390
|
+
stream: false
|
|
1391
|
+
};
|
|
1392
|
+
let messages = [...params.messages];
|
|
1393
|
+
const attempts = [];
|
|
1394
|
+
const maxAttempts = maxRetries + 1;
|
|
1395
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1396
|
+
const response = await this.create(
|
|
1397
|
+
{ ...fullParams, messages },
|
|
1398
|
+
{ ...requestOptions, stream: false }
|
|
1399
|
+
);
|
|
1400
|
+
const rawJson = response.content.join("");
|
|
1401
|
+
const requestId = response.requestId;
|
|
1402
|
+
try {
|
|
1403
|
+
const parsed = JSON.parse(rawJson);
|
|
1404
|
+
const validated = validateWithZod(schema, parsed);
|
|
1405
|
+
if (validated.success) {
|
|
1406
|
+
return {
|
|
1407
|
+
value: validated.data,
|
|
1408
|
+
attempts: attempt,
|
|
1409
|
+
requestId
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
const error = {
|
|
1413
|
+
kind: "validation",
|
|
1414
|
+
issues: [{ message: validated.error }]
|
|
1415
|
+
};
|
|
1416
|
+
attempts.push({ attempt, rawJson, error });
|
|
1417
|
+
if (attempt >= maxAttempts) {
|
|
1418
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1419
|
+
}
|
|
1420
|
+
const retryMessages = retryHandler.onValidationError(
|
|
1421
|
+
attempt,
|
|
1422
|
+
rawJson,
|
|
1423
|
+
error,
|
|
1424
|
+
params.messages
|
|
1425
|
+
);
|
|
1426
|
+
if (!retryMessages) {
|
|
1427
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1428
|
+
}
|
|
1429
|
+
messages = [
|
|
1430
|
+
...params.messages,
|
|
1431
|
+
{ role: "assistant", content: rawJson },
|
|
1432
|
+
...retryMessages
|
|
1433
|
+
];
|
|
1434
|
+
} catch (e) {
|
|
1435
|
+
if (e instanceof StructuredExhaustedError) {
|
|
1436
|
+
throw e;
|
|
1437
|
+
}
|
|
1438
|
+
const error = {
|
|
1439
|
+
kind: "decode",
|
|
1440
|
+
message: e instanceof Error ? e.message : String(e)
|
|
1441
|
+
};
|
|
1442
|
+
attempts.push({ attempt, rawJson, error });
|
|
1443
|
+
if (attempt >= maxAttempts) {
|
|
1444
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1445
|
+
}
|
|
1446
|
+
const retryMessages = retryHandler.onValidationError(
|
|
1447
|
+
attempt,
|
|
1448
|
+
rawJson,
|
|
1449
|
+
error,
|
|
1450
|
+
params.messages
|
|
1451
|
+
);
|
|
1452
|
+
if (!retryMessages) {
|
|
1453
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1454
|
+
}
|
|
1455
|
+
messages = [
|
|
1456
|
+
...params.messages,
|
|
1457
|
+
{ role: "assistant", content: rawJson },
|
|
1458
|
+
...retryMessages
|
|
1459
|
+
];
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
throw new Error(
|
|
1463
|
+
`Internal error: structured output loop exited unexpectedly after ${maxAttempts} attempts (this is a bug, please report it)`
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Stream structured output with a Zod schema.
|
|
1468
|
+
*
|
|
1469
|
+
* Auto-generates JSON schema from the Zod schema. Note that streaming
|
|
1470
|
+
* does not support retries - for retry behavior, use `structured()`.
|
|
1471
|
+
*
|
|
1472
|
+
* @param schema - A Zod schema defining the expected response structure
|
|
1473
|
+
* @param params - Chat completion parameters (excluding responseFormat)
|
|
1474
|
+
* @param options - Request options
|
|
1475
|
+
* @returns A structured JSON stream
|
|
1476
|
+
*
|
|
1477
|
+
* @example
|
|
1478
|
+
* ```typescript
|
|
1479
|
+
* import { z } from 'zod';
|
|
1480
|
+
*
|
|
1481
|
+
* const PersonSchema = z.object({
|
|
1482
|
+
* name: z.string(),
|
|
1483
|
+
* age: z.number(),
|
|
1484
|
+
* });
|
|
1485
|
+
*
|
|
1486
|
+
* const stream = await client.chat.completions.streamStructured(
|
|
1487
|
+
* PersonSchema,
|
|
1488
|
+
* { model: "claude-sonnet-4-20250514", messages: [...] },
|
|
1489
|
+
* );
|
|
1490
|
+
*
|
|
1491
|
+
* for await (const event of stream) {
|
|
1492
|
+
* console.log(event.type, event.payload);
|
|
1493
|
+
* }
|
|
1494
|
+
* ```
|
|
1495
|
+
*/
|
|
1496
|
+
async streamStructured(schema, params, options = {}) {
|
|
1497
|
+
const { schemaName, ...requestOptions } = options;
|
|
1498
|
+
const responseFormat = responseFormatFromZod(schema, schemaName);
|
|
1499
|
+
return this.streamJSON(
|
|
1500
|
+
{ ...params, responseFormat },
|
|
1501
|
+
requestOptions
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
var CustomerChatClient = class {
|
|
1506
|
+
constructor(http, auth, customerId, defaultMetadata, metrics, trace) {
|
|
1507
|
+
this.http = http;
|
|
1508
|
+
this.auth = auth;
|
|
1509
|
+
this.customerId = customerId;
|
|
1510
|
+
this.defaultMetadata = defaultMetadata;
|
|
1511
|
+
this.metrics = metrics;
|
|
1512
|
+
this.trace = trace;
|
|
1513
|
+
}
|
|
1514
|
+
async create(params, options = {}) {
|
|
1515
|
+
const stream = options.stream ?? params.stream ?? true;
|
|
1516
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
1517
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
1518
|
+
if (!params?.messages?.length) {
|
|
1519
|
+
throw new ConfigError("at least one message is required");
|
|
1520
|
+
}
|
|
1521
|
+
if (!hasUserMessage(params.messages)) {
|
|
1522
|
+
throw new ConfigError("at least one user message is required");
|
|
1523
|
+
}
|
|
1524
|
+
const authHeaders = await this.auth.authForChat(this.customerId);
|
|
1525
|
+
const body = buildCustomerProxyBody(
|
|
1526
|
+
params,
|
|
1527
|
+
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
1528
|
+
);
|
|
1529
|
+
const requestId = params.requestId || options.requestId;
|
|
1530
|
+
const headers = {
|
|
1531
|
+
...options.headers || {},
|
|
1532
|
+
[CUSTOMER_ID_HEADER]: this.customerId
|
|
1533
|
+
};
|
|
1534
|
+
if (requestId) {
|
|
1535
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
1536
|
+
}
|
|
1537
|
+
const baseContext = {
|
|
1538
|
+
method: "POST",
|
|
1539
|
+
path: "/llm/proxy",
|
|
1540
|
+
model: void 0,
|
|
1541
|
+
// Model is determined by tier
|
|
1542
|
+
requestId
|
|
1543
|
+
};
|
|
1544
|
+
const response = await this.http.request("/llm/proxy", {
|
|
1545
|
+
method: "POST",
|
|
1546
|
+
body,
|
|
1547
|
+
headers,
|
|
1548
|
+
apiKey: authHeaders.apiKey,
|
|
1549
|
+
accessToken: authHeaders.accessToken,
|
|
1550
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
1551
|
+
raw: true,
|
|
1552
|
+
signal: options.signal,
|
|
1553
|
+
timeoutMs: options.timeoutMs ?? (stream ? 0 : void 0),
|
|
1554
|
+
useDefaultTimeout: !stream,
|
|
1555
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
1556
|
+
retry: options.retry,
|
|
1557
|
+
metrics,
|
|
1558
|
+
trace,
|
|
1559
|
+
context: baseContext
|
|
1560
|
+
});
|
|
1561
|
+
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
1562
|
+
if (!response.ok) {
|
|
1563
|
+
throw await parseErrorResponse(response);
|
|
1564
|
+
}
|
|
1565
|
+
if (!stream) {
|
|
1566
|
+
const payload = await response.json();
|
|
1567
|
+
const result = normalizeChatResponse(payload, resolvedRequestId);
|
|
1568
|
+
if (metrics?.usage) {
|
|
1569
|
+
const ctx = {
|
|
1570
|
+
...baseContext,
|
|
1571
|
+
requestId: resolvedRequestId ?? baseContext.requestId,
|
|
1572
|
+
responseId: result.id
|
|
1573
|
+
};
|
|
1574
|
+
metrics.usage({ usage: result.usage, context: ctx });
|
|
1575
|
+
}
|
|
1576
|
+
return result;
|
|
1577
|
+
}
|
|
1578
|
+
const streamContext = {
|
|
1579
|
+
...baseContext,
|
|
1580
|
+
requestId: resolvedRequestId ?? baseContext.requestId
|
|
1581
|
+
};
|
|
1582
|
+
return new ChatCompletionsStream(
|
|
1583
|
+
response,
|
|
1584
|
+
resolvedRequestId,
|
|
1585
|
+
streamContext,
|
|
1586
|
+
metrics,
|
|
1587
|
+
trace
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Stream structured JSON responses using the NDJSON contract.
|
|
1592
|
+
* The request must include a structured responseFormat.
|
|
1593
|
+
*/
|
|
1594
|
+
async streamJSON(params, options = {}) {
|
|
1595
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
1596
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
1597
|
+
if (!params?.messages?.length) {
|
|
1598
|
+
throw new ConfigError("at least one message is required");
|
|
1599
|
+
}
|
|
1600
|
+
if (!hasUserMessage(params.messages)) {
|
|
1601
|
+
throw new ConfigError("at least one user message is required");
|
|
1602
|
+
}
|
|
1603
|
+
if (!params.responseFormat || params.responseFormat.type !== "json_object" && params.responseFormat.type !== "json_schema") {
|
|
1604
|
+
throw new ConfigError(
|
|
1605
|
+
"responseFormat with type=json_object or json_schema is required for structured streaming"
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
const authHeaders = await this.auth.authForChat(this.customerId);
|
|
1609
|
+
const body = buildCustomerProxyBody(
|
|
1610
|
+
params,
|
|
1611
|
+
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
1612
|
+
);
|
|
1613
|
+
const requestId = params.requestId || options.requestId;
|
|
1614
|
+
const headers = {
|
|
1615
|
+
...options.headers || {},
|
|
1616
|
+
[CUSTOMER_ID_HEADER]: this.customerId
|
|
1617
|
+
};
|
|
1618
|
+
if (requestId) {
|
|
1619
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
1620
|
+
}
|
|
1621
|
+
const baseContext = {
|
|
1622
|
+
method: "POST",
|
|
1623
|
+
path: "/llm/proxy",
|
|
1624
|
+
model: void 0,
|
|
1625
|
+
// Model is determined by tier
|
|
1626
|
+
requestId
|
|
1627
|
+
};
|
|
1628
|
+
const response = await this.http.request("/llm/proxy", {
|
|
1629
|
+
method: "POST",
|
|
1630
|
+
body,
|
|
1631
|
+
headers,
|
|
1632
|
+
apiKey: authHeaders.apiKey,
|
|
1633
|
+
accessToken: authHeaders.accessToken,
|
|
1634
|
+
accept: "application/x-ndjson",
|
|
1635
|
+
raw: true,
|
|
1636
|
+
signal: options.signal,
|
|
1637
|
+
timeoutMs: options.timeoutMs ?? 0,
|
|
1638
|
+
useDefaultTimeout: false,
|
|
1639
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
1640
|
+
retry: options.retry,
|
|
1641
|
+
metrics,
|
|
1642
|
+
trace,
|
|
1643
|
+
context: baseContext
|
|
1644
|
+
});
|
|
1645
|
+
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
1646
|
+
if (!response.ok) {
|
|
1647
|
+
throw await parseErrorResponse(response);
|
|
1648
|
+
}
|
|
1649
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
1650
|
+
if (!/application\/(x-)?ndjson/i.test(contentType)) {
|
|
1651
|
+
throw new TransportError(
|
|
1652
|
+
`expected NDJSON structured stream, got Content-Type ${contentType || "missing"}`,
|
|
1653
|
+
{ kind: "request" }
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
const streamContext = {
|
|
1657
|
+
...baseContext,
|
|
1658
|
+
requestId: resolvedRequestId ?? baseContext.requestId
|
|
1659
|
+
};
|
|
1660
|
+
return new StructuredJSONStream(
|
|
1661
|
+
response,
|
|
1662
|
+
resolvedRequestId,
|
|
1663
|
+
streamContext,
|
|
1664
|
+
metrics,
|
|
1665
|
+
trace
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Send a structured output request with a Zod schema for customer-attributed calls.
|
|
1670
|
+
*
|
|
1671
|
+
* Auto-generates JSON schema from the Zod schema, validates the response,
|
|
1672
|
+
* and retries on validation failure if configured.
|
|
1673
|
+
*
|
|
1674
|
+
* @param schema - A Zod schema defining the expected response structure
|
|
1675
|
+
* @param params - Customer chat parameters (excluding responseFormat)
|
|
1676
|
+
* @param options - Request options including retry configuration
|
|
1677
|
+
* @returns A typed result with the parsed value
|
|
1678
|
+
*/
|
|
1679
|
+
async structured(schema, params, options = {}) {
|
|
1680
|
+
const {
|
|
1681
|
+
maxRetries = 0,
|
|
1682
|
+
retryHandler = defaultRetryHandler,
|
|
1683
|
+
schemaName,
|
|
1684
|
+
...requestOptions
|
|
1685
|
+
} = options;
|
|
1686
|
+
const responseFormat = responseFormatFromZod(schema, schemaName);
|
|
1687
|
+
const fullParams = {
|
|
1688
|
+
...params,
|
|
1689
|
+
responseFormat,
|
|
1690
|
+
stream: false
|
|
1691
|
+
};
|
|
1692
|
+
let messages = [...params.messages];
|
|
1693
|
+
const attempts = [];
|
|
1694
|
+
const maxAttempts = maxRetries + 1;
|
|
1695
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1696
|
+
const response = await this.create(
|
|
1697
|
+
{ ...fullParams, messages },
|
|
1698
|
+
{ ...requestOptions, stream: false }
|
|
1699
|
+
);
|
|
1700
|
+
const rawJson = response.content.join("");
|
|
1701
|
+
const requestId = response.requestId;
|
|
1702
|
+
try {
|
|
1703
|
+
const parsed = JSON.parse(rawJson);
|
|
1704
|
+
const validated = validateWithZod(schema, parsed);
|
|
1705
|
+
if (validated.success) {
|
|
1706
|
+
return {
|
|
1707
|
+
value: validated.data,
|
|
1708
|
+
attempts: attempt,
|
|
1709
|
+
requestId
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const error = {
|
|
1713
|
+
kind: "validation",
|
|
1714
|
+
issues: [{ message: validated.error }]
|
|
1715
|
+
};
|
|
1716
|
+
attempts.push({ attempt, rawJson, error });
|
|
1717
|
+
if (attempt >= maxAttempts) {
|
|
1718
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1719
|
+
}
|
|
1720
|
+
const retryMessages = retryHandler.onValidationError(
|
|
1721
|
+
attempt,
|
|
1722
|
+
rawJson,
|
|
1723
|
+
error,
|
|
1724
|
+
params.messages
|
|
1725
|
+
);
|
|
1726
|
+
if (!retryMessages) {
|
|
1727
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1728
|
+
}
|
|
1729
|
+
messages = [
|
|
1730
|
+
...params.messages,
|
|
1731
|
+
{ role: "assistant", content: rawJson },
|
|
1732
|
+
...retryMessages
|
|
1733
|
+
];
|
|
1734
|
+
} catch (e) {
|
|
1735
|
+
if (e instanceof StructuredExhaustedError) {
|
|
1736
|
+
throw e;
|
|
1737
|
+
}
|
|
1738
|
+
const error = {
|
|
1739
|
+
kind: "decode",
|
|
1740
|
+
message: e instanceof Error ? e.message : String(e)
|
|
1741
|
+
};
|
|
1742
|
+
attempts.push({ attempt, rawJson, error });
|
|
1743
|
+
if (attempt >= maxAttempts) {
|
|
1744
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1745
|
+
}
|
|
1746
|
+
const retryMessages = retryHandler.onValidationError(
|
|
1747
|
+
attempt,
|
|
1748
|
+
rawJson,
|
|
1749
|
+
error,
|
|
1750
|
+
params.messages
|
|
1751
|
+
);
|
|
1752
|
+
if (!retryMessages) {
|
|
1753
|
+
throw new StructuredExhaustedError(rawJson, attempts, error);
|
|
1754
|
+
}
|
|
1755
|
+
messages = [
|
|
1756
|
+
...params.messages,
|
|
1757
|
+
{ role: "assistant", content: rawJson },
|
|
1758
|
+
...retryMessages
|
|
1759
|
+
];
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
throw new Error(
|
|
1763
|
+
`Internal error: structured output loop exited unexpectedly after ${maxAttempts} attempts (this is a bug, please report it)`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Stream structured output with a Zod schema for customer-attributed calls.
|
|
1768
|
+
*
|
|
1769
|
+
* Auto-generates JSON schema from the Zod schema. Note that streaming
|
|
1770
|
+
* does not support retries - for retry behavior, use `structured()`.
|
|
1771
|
+
*
|
|
1772
|
+
* @param schema - A Zod schema defining the expected response structure
|
|
1773
|
+
* @param params - Customer chat parameters (excluding responseFormat)
|
|
1774
|
+
* @param options - Request options
|
|
1775
|
+
* @returns A structured JSON stream
|
|
1776
|
+
*/
|
|
1777
|
+
async streamStructured(schema, params, options = {}) {
|
|
1778
|
+
const { schemaName, ...requestOptions } = options;
|
|
1779
|
+
const responseFormat = responseFormatFromZod(schema, schemaName);
|
|
1780
|
+
return this.streamJSON(
|
|
1781
|
+
{ ...params, responseFormat },
|
|
1782
|
+
requestOptions
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1264
1785
|
};
|
|
1265
1786
|
var ChatCompletionsStream = class {
|
|
1266
1787
|
constructor(response, requestId, context, metrics, trace) {
|
|
@@ -1280,7 +1801,13 @@ var ChatCompletionsStream = class {
|
|
|
1280
1801
|
this.closed = true;
|
|
1281
1802
|
try {
|
|
1282
1803
|
await this.response.body?.cancel(reason);
|
|
1283
|
-
} catch {
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
if (this.trace?.streamError) {
|
|
1806
|
+
this.trace.streamError({
|
|
1807
|
+
context: this.context,
|
|
1808
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1284
1811
|
}
|
|
1285
1812
|
}
|
|
1286
1813
|
async *[Symbol.asyncIterator]() {
|
|
@@ -1379,7 +1906,13 @@ var StructuredJSONStream = class {
|
|
|
1379
1906
|
this.closed = true;
|
|
1380
1907
|
try {
|
|
1381
1908
|
await this.response.body?.cancel(reason);
|
|
1382
|
-
} catch {
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
if (this.trace?.streamError) {
|
|
1911
|
+
this.trace.streamError({
|
|
1912
|
+
context: this.context,
|
|
1913
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1383
1916
|
}
|
|
1384
1917
|
}
|
|
1385
1918
|
async *[Symbol.asyncIterator]() {
|
|
@@ -1580,7 +2113,7 @@ function mapChatEvent(raw, requestId) {
|
|
|
1580
2113
|
if (raw.data) {
|
|
1581
2114
|
try {
|
|
1582
2115
|
parsed = JSON.parse(raw.data);
|
|
1583
|
-
} catch {
|
|
2116
|
+
} catch (err) {
|
|
1584
2117
|
parsed = raw.data;
|
|
1585
2118
|
}
|
|
1586
2119
|
}
|
|
@@ -1752,10 +2285,25 @@ function buildProxyBody(params, metadata) {
|
|
|
1752
2285
|
if (params.responseFormat) body.response_format = params.responseFormat;
|
|
1753
2286
|
return body;
|
|
1754
2287
|
}
|
|
2288
|
+
function buildCustomerProxyBody(params, metadata) {
|
|
2289
|
+
const body = {
|
|
2290
|
+
messages: normalizeMessages(params.messages)
|
|
2291
|
+
};
|
|
2292
|
+
if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
|
|
2293
|
+
if (typeof params.temperature === "number")
|
|
2294
|
+
body.temperature = params.temperature;
|
|
2295
|
+
if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
|
|
2296
|
+
if (params.stop?.length) body.stop = params.stop;
|
|
2297
|
+
if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
|
|
2298
|
+
if (params.tools?.length) body.tools = normalizeTools(params.tools);
|
|
2299
|
+
if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
|
|
2300
|
+
if (params.responseFormat) body.response_format = params.responseFormat;
|
|
2301
|
+
return body;
|
|
2302
|
+
}
|
|
1755
2303
|
function normalizeMessages(messages) {
|
|
1756
2304
|
return messages.map((msg) => {
|
|
1757
2305
|
const normalized = {
|
|
1758
|
-
role: msg.role
|
|
2306
|
+
role: msg.role,
|
|
1759
2307
|
content: msg.content
|
|
1760
2308
|
};
|
|
1761
2309
|
if (msg.toolCalls?.length) {
|
|
@@ -2464,17 +3012,21 @@ export {
|
|
|
2464
3012
|
ChatClient,
|
|
2465
3013
|
ChatCompletionsStream,
|
|
2466
3014
|
ConfigError,
|
|
3015
|
+
CustomerChatClient,
|
|
2467
3016
|
CustomersClient,
|
|
2468
3017
|
DEFAULT_BASE_URL,
|
|
2469
3018
|
DEFAULT_CLIENT_HEADER,
|
|
2470
3019
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
2471
3020
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2472
3021
|
ErrorCodes,
|
|
3022
|
+
MessageRoles,
|
|
2473
3023
|
ModelRelay,
|
|
2474
3024
|
ModelRelayError,
|
|
2475
3025
|
ResponseFormatTypes,
|
|
2476
3026
|
SDK_VERSION,
|
|
2477
3027
|
StopReasons,
|
|
3028
|
+
StructuredDecodeError,
|
|
3029
|
+
StructuredExhaustedError,
|
|
2478
3030
|
StructuredJSONStream,
|
|
2479
3031
|
TiersClient,
|
|
2480
3032
|
ToolArgsError,
|
|
@@ -2497,6 +3049,7 @@ export {
|
|
|
2497
3049
|
createUsage,
|
|
2498
3050
|
createUserMessage,
|
|
2499
3051
|
createWebTool,
|
|
3052
|
+
defaultRetryHandler,
|
|
2500
3053
|
executeWithRetry,
|
|
2501
3054
|
firstToolCall,
|
|
2502
3055
|
formatToolErrorForModel,
|
|
@@ -2517,11 +3070,13 @@ export {
|
|
|
2517
3070
|
parseToolArgs,
|
|
2518
3071
|
parseToolArgsRaw,
|
|
2519
3072
|
respondToToolCall,
|
|
3073
|
+
responseFormatFromZod,
|
|
2520
3074
|
stopReasonToString,
|
|
2521
3075
|
toolChoiceAuto,
|
|
2522
3076
|
toolChoiceNone,
|
|
2523
3077
|
toolChoiceRequired,
|
|
2524
3078
|
toolResultMessage,
|
|
2525
3079
|
tryParseToolArgs,
|
|
3080
|
+
validateWithZod,
|
|
2526
3081
|
zodToJsonSchema
|
|
2527
3082
|
};
|