@modelrelay/sdk 0.7.0 → 0.17.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 +79 -3
- package/dist/index.cjs +1798 -1455
- package/dist/index.d.cts +147 -39
- package/dist/index.d.ts +147 -39
- package/dist/index.js +1785 -1450
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,13 @@ __export(index_exports, {
|
|
|
30
30
|
DEFAULT_CLIENT_HEADER: () => DEFAULT_CLIENT_HEADER,
|
|
31
31
|
DEFAULT_CONNECT_TIMEOUT_MS: () => DEFAULT_CONNECT_TIMEOUT_MS,
|
|
32
32
|
DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
|
|
33
|
+
ErrorCodes: () => ErrorCodes,
|
|
33
34
|
ModelRelay: () => ModelRelay,
|
|
34
35
|
ModelRelayError: () => ModelRelayError,
|
|
35
|
-
|
|
36
|
-
Providers: () => Providers,
|
|
36
|
+
ResponseFormatTypes: () => ResponseFormatTypes,
|
|
37
37
|
SDK_VERSION: () => SDK_VERSION,
|
|
38
38
|
StopReasons: () => StopReasons,
|
|
39
|
+
StructuredJSONStream: () => StructuredJSONStream,
|
|
39
40
|
TiersClient: () => TiersClient,
|
|
40
41
|
ToolArgsError: () => ToolArgsError,
|
|
41
42
|
ToolCallAccumulator: () => ToolCallAccumulator,
|
|
@@ -43,11 +44,20 @@ __export(index_exports, {
|
|
|
43
44
|
ToolRegistry: () => ToolRegistry,
|
|
44
45
|
ToolTypes: () => ToolTypes,
|
|
45
46
|
TransportError: () => TransportError,
|
|
47
|
+
WebToolModes: () => WebToolModes,
|
|
46
48
|
assistantMessageWithToolCalls: () => assistantMessageWithToolCalls,
|
|
49
|
+
createAccessTokenAuth: () => createAccessTokenAuth,
|
|
50
|
+
createApiKeyAuth: () => createApiKeyAuth,
|
|
51
|
+
createAssistantMessage: () => createAssistantMessage,
|
|
52
|
+
createFunctionCall: () => createFunctionCall,
|
|
47
53
|
createFunctionTool: () => createFunctionTool,
|
|
48
54
|
createFunctionToolFromSchema: () => createFunctionToolFromSchema,
|
|
49
55
|
createRetryMessages: () => createRetryMessages,
|
|
50
|
-
|
|
56
|
+
createSystemMessage: () => createSystemMessage,
|
|
57
|
+
createToolCall: () => createToolCall,
|
|
58
|
+
createUsage: () => createUsage,
|
|
59
|
+
createUserMessage: () => createUserMessage,
|
|
60
|
+
createWebTool: () => createWebTool,
|
|
51
61
|
executeWithRetry: () => executeWithRetry,
|
|
52
62
|
firstToolCall: () => firstToolCall,
|
|
53
63
|
formatToolErrorForModel: () => formatToolErrorForModel,
|
|
@@ -59,12 +69,10 @@ __export(index_exports, {
|
|
|
59
69
|
mergeTrace: () => mergeTrace,
|
|
60
70
|
modelToString: () => modelToString,
|
|
61
71
|
normalizeModelId: () => normalizeModelId,
|
|
62
|
-
normalizeProvider: () => normalizeProvider,
|
|
63
72
|
normalizeStopReason: () => normalizeStopReason,
|
|
64
73
|
parseErrorResponse: () => parseErrorResponse,
|
|
65
74
|
parseToolArgs: () => parseToolArgs,
|
|
66
75
|
parseToolArgsRaw: () => parseToolArgsRaw,
|
|
67
|
-
providerToString: () => providerToString,
|
|
68
76
|
respondToToolCall: () => respondToToolCall,
|
|
69
77
|
stopReasonToString: () => stopReasonToString,
|
|
70
78
|
toolChoiceAuto: () => toolChoiceAuto,
|
|
@@ -77,6 +85,19 @@ __export(index_exports, {
|
|
|
77
85
|
module.exports = __toCommonJS(index_exports);
|
|
78
86
|
|
|
79
87
|
// src/errors.ts
|
|
88
|
+
var ErrorCodes = {
|
|
89
|
+
NOT_FOUND: "NOT_FOUND",
|
|
90
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
91
|
+
RATE_LIMIT: "RATE_LIMIT",
|
|
92
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
93
|
+
FORBIDDEN: "FORBIDDEN",
|
|
94
|
+
CONFLICT: "CONFLICT",
|
|
95
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
96
|
+
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
|
|
97
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
98
|
+
PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
|
|
99
|
+
METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED"
|
|
100
|
+
};
|
|
80
101
|
var ModelRelayError = class extends Error {
|
|
81
102
|
constructor(message, opts) {
|
|
82
103
|
super(message);
|
|
@@ -120,6 +141,30 @@ var APIError = class extends ModelRelayError {
|
|
|
120
141
|
retries: opts.retries
|
|
121
142
|
});
|
|
122
143
|
}
|
|
144
|
+
/** Returns true if the error is a not found error. */
|
|
145
|
+
isNotFound() {
|
|
146
|
+
return this.code === ErrorCodes.NOT_FOUND;
|
|
147
|
+
}
|
|
148
|
+
/** Returns true if the error is a validation error. */
|
|
149
|
+
isValidation() {
|
|
150
|
+
return this.code === ErrorCodes.VALIDATION_ERROR || this.code === ErrorCodes.INVALID_INPUT;
|
|
151
|
+
}
|
|
152
|
+
/** Returns true if the error is a rate limit error. */
|
|
153
|
+
isRateLimit() {
|
|
154
|
+
return this.code === ErrorCodes.RATE_LIMIT;
|
|
155
|
+
}
|
|
156
|
+
/** Returns true if the error is an unauthorized error. */
|
|
157
|
+
isUnauthorized() {
|
|
158
|
+
return this.code === ErrorCodes.UNAUTHORIZED;
|
|
159
|
+
}
|
|
160
|
+
/** Returns true if the error is a forbidden error. */
|
|
161
|
+
isForbidden() {
|
|
162
|
+
return this.code === ErrorCodes.FORBIDDEN;
|
|
163
|
+
}
|
|
164
|
+
/** Returns true if the error is a service unavailable error. */
|
|
165
|
+
isUnavailable() {
|
|
166
|
+
return this.code === ErrorCodes.SERVICE_UNAVAILABLE;
|
|
167
|
+
}
|
|
123
168
|
};
|
|
124
169
|
async function parseErrorResponse(response, retries) {
|
|
125
170
|
const requestId = response.headers.get("X-ModelRelay-Chat-Request-Id") || response.headers.get("X-Request-Id") || void 0;
|
|
@@ -174,6 +219,12 @@ async function parseErrorResponse(response, retries) {
|
|
|
174
219
|
}
|
|
175
220
|
|
|
176
221
|
// src/auth.ts
|
|
222
|
+
function createApiKeyAuth(apiKey) {
|
|
223
|
+
return { apiKey };
|
|
224
|
+
}
|
|
225
|
+
function createAccessTokenAuth(accessToken) {
|
|
226
|
+
return { accessToken };
|
|
227
|
+
}
|
|
177
228
|
var AuthClient = class {
|
|
178
229
|
constructor(http, cfg) {
|
|
179
230
|
this.cachedFrontend = /* @__PURE__ */ new Map();
|
|
@@ -233,7 +284,7 @@ var AuthClient = class {
|
|
|
233
284
|
*/
|
|
234
285
|
async authForChat(customerId, overrides) {
|
|
235
286
|
if (this.accessToken) {
|
|
236
|
-
return
|
|
287
|
+
return createAccessTokenAuth(this.accessToken);
|
|
237
288
|
}
|
|
238
289
|
if (!this.apiKey) {
|
|
239
290
|
throw new ConfigError("API key or token is required");
|
|
@@ -244,21 +295,21 @@ var AuthClient = class {
|
|
|
244
295
|
deviceId: overrides?.deviceId,
|
|
245
296
|
ttlSeconds: overrides?.ttlSeconds
|
|
246
297
|
});
|
|
247
|
-
return
|
|
298
|
+
return createAccessTokenAuth(token.token);
|
|
248
299
|
}
|
|
249
|
-
return
|
|
300
|
+
return createApiKeyAuth(this.apiKey);
|
|
250
301
|
}
|
|
251
302
|
/**
|
|
252
303
|
* Billing calls accept either bearer tokens or API keys (including publishable keys).
|
|
253
304
|
*/
|
|
254
305
|
authForBilling() {
|
|
255
306
|
if (this.accessToken) {
|
|
256
|
-
return
|
|
307
|
+
return createAccessTokenAuth(this.accessToken);
|
|
257
308
|
}
|
|
258
309
|
if (!this.apiKey) {
|
|
259
310
|
throw new ConfigError("API key or token is required");
|
|
260
311
|
}
|
|
261
|
-
return
|
|
312
|
+
return createApiKeyAuth(this.apiKey);
|
|
262
313
|
}
|
|
263
314
|
};
|
|
264
315
|
function isPublishableKey(value) {
|
|
@@ -296,7 +347,7 @@ function isTokenReusable(token) {
|
|
|
296
347
|
// package.json
|
|
297
348
|
var package_default = {
|
|
298
349
|
name: "@modelrelay/sdk",
|
|
299
|
-
version: "0.
|
|
350
|
+
version: "0.17.0",
|
|
300
351
|
description: "TypeScript SDK for the ModelRelay API",
|
|
301
352
|
type: "module",
|
|
302
353
|
main: "dist/index.cjs",
|
|
@@ -355,36 +406,34 @@ var StopReasons = {
|
|
|
355
406
|
Incomplete: "incomplete",
|
|
356
407
|
Unknown: "unknown"
|
|
357
408
|
};
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
OpenAIGpt4o: "openai/gpt-4o",
|
|
366
|
-
OpenAIGpt4oMini: "openai/gpt-4o-mini",
|
|
367
|
-
OpenAIGpt51: "openai/gpt-5.1",
|
|
368
|
-
AnthropicClaude35HaikuLatest: "anthropic/claude-3-5-haiku-latest",
|
|
369
|
-
AnthropicClaude35SonnetLatest: "anthropic/claude-3-5-sonnet-latest",
|
|
370
|
-
AnthropicClaudeOpus45: "anthropic/claude-opus-4-5-20251101",
|
|
371
|
-
AnthropicClaude35Haiku: "anthropic/claude-3.5-haiku",
|
|
372
|
-
Grok2: "grok-2",
|
|
373
|
-
Grok4_1FastNonReasoning: "grok-4-1-fast-non-reasoning",
|
|
374
|
-
Grok4_1FastReasoning: "grok-4-1-fast-reasoning",
|
|
375
|
-
Echo1: "echo-1"
|
|
376
|
-
};
|
|
409
|
+
function createUsage(inputTokens, outputTokens, totalTokens) {
|
|
410
|
+
return {
|
|
411
|
+
inputTokens,
|
|
412
|
+
outputTokens,
|
|
413
|
+
totalTokens: totalTokens ?? inputTokens + outputTokens
|
|
414
|
+
};
|
|
415
|
+
}
|
|
377
416
|
var ToolTypes = {
|
|
378
417
|
Function: "function",
|
|
418
|
+
Web: "web",
|
|
379
419
|
WebSearch: "web_search",
|
|
380
420
|
XSearch: "x_search",
|
|
381
421
|
CodeExecution: "code_execution"
|
|
382
422
|
};
|
|
423
|
+
var WebToolModes = {
|
|
424
|
+
Search: "search",
|
|
425
|
+
Browse: "browse"
|
|
426
|
+
};
|
|
383
427
|
var ToolChoiceTypes = {
|
|
384
428
|
Auto: "auto",
|
|
385
429
|
Required: "required",
|
|
386
430
|
None: "none"
|
|
387
431
|
};
|
|
432
|
+
var ResponseFormatTypes = {
|
|
433
|
+
Text: "text",
|
|
434
|
+
JsonObject: "json_object",
|
|
435
|
+
JsonSchema: "json_schema"
|
|
436
|
+
};
|
|
388
437
|
function mergeMetrics(base, override) {
|
|
389
438
|
if (!base && !override) return void 0;
|
|
390
439
|
return {
|
|
@@ -418,1631 +467,1917 @@ function stopReasonToString(value) {
|
|
|
418
467
|
if (typeof value === "string") return value;
|
|
419
468
|
return value.other?.trim() || void 0;
|
|
420
469
|
}
|
|
421
|
-
function normalizeProvider(value) {
|
|
422
|
-
if (value === void 0 || value === null) return void 0;
|
|
423
|
-
const str = String(value).trim();
|
|
424
|
-
if (!str) return void 0;
|
|
425
|
-
const lower = str.toLowerCase();
|
|
426
|
-
for (const p of Object.values(Providers)) {
|
|
427
|
-
if (lower === p) return p;
|
|
428
|
-
}
|
|
429
|
-
return { other: str };
|
|
430
|
-
}
|
|
431
|
-
function providerToString(value) {
|
|
432
|
-
if (!value) return void 0;
|
|
433
|
-
if (typeof value === "string") return value;
|
|
434
|
-
return value.other?.trim() || void 0;
|
|
435
|
-
}
|
|
436
470
|
function normalizeModelId(value) {
|
|
437
471
|
if (value === void 0 || value === null) return void 0;
|
|
438
472
|
const str = String(value).trim();
|
|
439
473
|
if (!str) return void 0;
|
|
440
|
-
|
|
441
|
-
for (const m of Object.values(Models)) {
|
|
442
|
-
if (lower === m) return m;
|
|
443
|
-
}
|
|
444
|
-
return { other: str };
|
|
474
|
+
return str;
|
|
445
475
|
}
|
|
446
476
|
function modelToString(value) {
|
|
447
|
-
|
|
448
|
-
return value.other?.trim() || "";
|
|
477
|
+
return String(value).trim();
|
|
449
478
|
}
|
|
450
479
|
|
|
451
|
-
// src/
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
480
|
+
// src/tools.ts
|
|
481
|
+
function createUserMessage(content) {
|
|
482
|
+
return { role: "user", content };
|
|
483
|
+
}
|
|
484
|
+
function createAssistantMessage(content) {
|
|
485
|
+
return { role: "assistant", content };
|
|
486
|
+
}
|
|
487
|
+
function createSystemMessage(content) {
|
|
488
|
+
return { role: "system", content };
|
|
489
|
+
}
|
|
490
|
+
function createToolCall(id, name, args, type = ToolTypes.Function) {
|
|
491
|
+
return {
|
|
492
|
+
id,
|
|
493
|
+
type,
|
|
494
|
+
function: createFunctionCall(name, args)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function createFunctionCall(name, args) {
|
|
498
|
+
return { name, arguments: args };
|
|
499
|
+
}
|
|
500
|
+
function zodToJsonSchema(schema, options = {}) {
|
|
501
|
+
const result = convertZodType(schema);
|
|
502
|
+
if (options.includeSchema) {
|
|
503
|
+
const schemaVersion = options.target === "draft-04" ? "http://json-schema.org/draft-04/schema#" : options.target === "draft-2019-09" ? "https://json-schema.org/draft/2019-09/schema" : options.target === "draft-2020-12" ? "https://json-schema.org/draft/2020-12/schema" : "http://json-schema.org/draft-07/schema#";
|
|
504
|
+
return { $schema: schemaVersion, ...result };
|
|
471
505
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
};
|
|
503
|
-
const response = await this.http.request("/llm/proxy", {
|
|
504
|
-
method: "POST",
|
|
505
|
-
body,
|
|
506
|
-
headers,
|
|
507
|
-
apiKey: authHeaders.apiKey,
|
|
508
|
-
accessToken: authHeaders.accessToken,
|
|
509
|
-
accept: stream ? "text/event-stream" : "application/json",
|
|
510
|
-
raw: true,
|
|
511
|
-
signal: options.signal,
|
|
512
|
-
timeoutMs: options.timeoutMs ?? (stream ? 0 : void 0),
|
|
513
|
-
useDefaultTimeout: !stream,
|
|
514
|
-
connectTimeoutMs: options.connectTimeoutMs,
|
|
515
|
-
retry: options.retry,
|
|
516
|
-
metrics,
|
|
517
|
-
trace,
|
|
518
|
-
context: baseContext
|
|
519
|
-
});
|
|
520
|
-
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
521
|
-
if (!response.ok) {
|
|
522
|
-
throw await parseErrorResponse(response);
|
|
523
|
-
}
|
|
524
|
-
if (!stream) {
|
|
525
|
-
const payload = await response.json();
|
|
526
|
-
const result = normalizeChatResponse(payload, resolvedRequestId);
|
|
527
|
-
if (metrics?.usage) {
|
|
528
|
-
const ctx = {
|
|
529
|
-
...baseContext,
|
|
530
|
-
requestId: resolvedRequestId ?? baseContext.requestId,
|
|
531
|
-
responseId: result.id
|
|
532
|
-
};
|
|
533
|
-
metrics.usage({ usage: result.usage, context: ctx });
|
|
506
|
+
return result;
|
|
507
|
+
}
|
|
508
|
+
function convertZodType(schema) {
|
|
509
|
+
const def = schema._def;
|
|
510
|
+
const typeName = def.typeName;
|
|
511
|
+
switch (typeName) {
|
|
512
|
+
case "ZodString":
|
|
513
|
+
return convertZodString(def);
|
|
514
|
+
case "ZodNumber":
|
|
515
|
+
return convertZodNumber(def);
|
|
516
|
+
case "ZodBoolean":
|
|
517
|
+
return { type: "boolean" };
|
|
518
|
+
case "ZodNull":
|
|
519
|
+
return { type: "null" };
|
|
520
|
+
case "ZodArray":
|
|
521
|
+
return convertZodArray(def);
|
|
522
|
+
case "ZodObject":
|
|
523
|
+
return convertZodObject(def);
|
|
524
|
+
case "ZodEnum":
|
|
525
|
+
return convertZodEnum(def);
|
|
526
|
+
case "ZodNativeEnum":
|
|
527
|
+
return convertZodNativeEnum(def);
|
|
528
|
+
case "ZodLiteral":
|
|
529
|
+
return { const: def.value };
|
|
530
|
+
case "ZodUnion":
|
|
531
|
+
return convertZodUnion(def);
|
|
532
|
+
case "ZodOptional": {
|
|
533
|
+
const inner = convertZodType(def.innerType);
|
|
534
|
+
if (def.description && !inner.description) {
|
|
535
|
+
inner.description = def.description;
|
|
534
536
|
}
|
|
535
|
-
return
|
|
537
|
+
return inner;
|
|
536
538
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
539
|
+
case "ZodNullable":
|
|
540
|
+
return convertZodNullable(def);
|
|
541
|
+
case "ZodDefault":
|
|
542
|
+
return { ...convertZodType(def.innerType), default: def.defaultValue() };
|
|
543
|
+
case "ZodEffects":
|
|
544
|
+
return convertZodType(def.schema);
|
|
545
|
+
case "ZodRecord":
|
|
546
|
+
return convertZodRecord(def);
|
|
547
|
+
case "ZodTuple":
|
|
548
|
+
return convertZodTuple(def);
|
|
549
|
+
case "ZodAny":
|
|
550
|
+
case "ZodUnknown":
|
|
551
|
+
return {};
|
|
552
|
+
default:
|
|
553
|
+
return {};
|
|
548
554
|
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
555
|
+
}
|
|
556
|
+
function convertZodString(def) {
|
|
557
|
+
const result = { type: "string" };
|
|
558
|
+
const checks = def.checks;
|
|
559
|
+
if (checks) {
|
|
560
|
+
for (const check of checks) {
|
|
561
|
+
switch (check.kind) {
|
|
562
|
+
case "min":
|
|
563
|
+
result.minLength = check.value;
|
|
564
|
+
break;
|
|
565
|
+
case "max":
|
|
566
|
+
result.maxLength = check.value;
|
|
567
|
+
break;
|
|
568
|
+
case "length":
|
|
569
|
+
result.minLength = check.value;
|
|
570
|
+
result.maxLength = check.value;
|
|
571
|
+
break;
|
|
572
|
+
case "email":
|
|
573
|
+
result.format = "email";
|
|
574
|
+
break;
|
|
575
|
+
case "url":
|
|
576
|
+
result.format = "uri";
|
|
577
|
+
break;
|
|
578
|
+
case "uuid":
|
|
579
|
+
result.format = "uuid";
|
|
580
|
+
break;
|
|
581
|
+
case "datetime":
|
|
582
|
+
result.format = "date-time";
|
|
583
|
+
break;
|
|
584
|
+
case "regex":
|
|
585
|
+
result.pattern = check.value.source;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
556
588
|
}
|
|
557
|
-
this.response = response;
|
|
558
|
-
this.requestId = requestId;
|
|
559
|
-
this.context = context;
|
|
560
|
-
this.metrics = metrics;
|
|
561
|
-
this.trace = trace;
|
|
562
|
-
this.startedAt = this.metrics?.streamFirstToken || this.trace?.streamEvent || this.trace?.streamError ? Date.now() : 0;
|
|
563
589
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
await this.response.body?.cancel(reason);
|
|
568
|
-
} catch {
|
|
569
|
-
}
|
|
590
|
+
if (def.description) {
|
|
591
|
+
result.description = def.description;
|
|
570
592
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
const { value, done } = await reader.read();
|
|
589
|
-
if (done) {
|
|
590
|
-
const { events: events2 } = consumeSSEBuffer(buffer, true);
|
|
591
|
-
for (const evt of events2) {
|
|
592
|
-
const parsed = mapChatEvent(evt, this.requestId);
|
|
593
|
-
if (parsed) {
|
|
594
|
-
this.handleStreamEvent(parsed);
|
|
595
|
-
yield parsed;
|
|
596
|
-
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
function convertZodNumber(def) {
|
|
596
|
+
const result = { type: "number" };
|
|
597
|
+
const checks = def.checks;
|
|
598
|
+
if (checks) {
|
|
599
|
+
for (const check of checks) {
|
|
600
|
+
switch (check.kind) {
|
|
601
|
+
case "int":
|
|
602
|
+
result.type = "integer";
|
|
603
|
+
break;
|
|
604
|
+
case "min":
|
|
605
|
+
if (check.inclusive === false) {
|
|
606
|
+
result.exclusiveMinimum = check.value;
|
|
607
|
+
} else {
|
|
608
|
+
result.minimum = check.value;
|
|
597
609
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const parsed = mapChatEvent(evt, this.requestId);
|
|
605
|
-
if (parsed) {
|
|
606
|
-
this.handleStreamEvent(parsed);
|
|
607
|
-
yield parsed;
|
|
610
|
+
break;
|
|
611
|
+
case "max":
|
|
612
|
+
if (check.inclusive === false) {
|
|
613
|
+
result.exclusiveMaximum = check.value;
|
|
614
|
+
} else {
|
|
615
|
+
result.maximum = check.value;
|
|
608
616
|
}
|
|
609
|
-
|
|
617
|
+
break;
|
|
618
|
+
case "multipleOf":
|
|
619
|
+
result.multipleOf = check.value;
|
|
620
|
+
break;
|
|
610
621
|
}
|
|
611
|
-
} catch (err) {
|
|
612
|
-
this.recordFirstToken(err);
|
|
613
|
-
this.trace?.streamError?.({ context: this.context, error: err });
|
|
614
|
-
throw err;
|
|
615
|
-
} finally {
|
|
616
|
-
this.closed = true;
|
|
617
|
-
reader.releaseLock();
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
handleStreamEvent(evt) {
|
|
621
|
-
const context = this.enrichContext(evt);
|
|
622
|
-
this.context = context;
|
|
623
|
-
this.trace?.streamEvent?.({ context, event: evt });
|
|
624
|
-
if (evt.type === "message_start" || evt.type === "message_delta" || evt.type === "message_stop" || evt.type === "tool_use_start" || evt.type === "tool_use_delta" || evt.type === "tool_use_stop") {
|
|
625
|
-
this.recordFirstToken();
|
|
626
|
-
}
|
|
627
|
-
if (evt.type === "message_stop" && evt.usage && this.metrics?.usage) {
|
|
628
|
-
this.metrics.usage({ usage: evt.usage, context });
|
|
629
622
|
}
|
|
630
623
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
...this.context,
|
|
634
|
-
responseId: evt.responseId || this.context.responseId,
|
|
635
|
-
requestId: evt.requestId || this.context.requestId,
|
|
636
|
-
model: evt.model || this.context.model
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
recordFirstToken(error) {
|
|
640
|
-
if (!this.metrics?.streamFirstToken || this.firstTokenEmitted) return;
|
|
641
|
-
this.firstTokenEmitted = true;
|
|
642
|
-
const latencyMs = this.startedAt ? Date.now() - this.startedAt : 0;
|
|
643
|
-
this.metrics.streamFirstToken({
|
|
644
|
-
latencyMs,
|
|
645
|
-
error: error ? String(error) : void 0,
|
|
646
|
-
context: this.context
|
|
647
|
-
});
|
|
624
|
+
if (def.description) {
|
|
625
|
+
result.description = def.description;
|
|
648
626
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const lines = buffer.split(/\r?\n/);
|
|
656
|
-
const lastIndex = lines.length - 1;
|
|
657
|
-
const limit = flush ? lines.length : Math.max(0, lastIndex);
|
|
658
|
-
const pushEvent = () => {
|
|
659
|
-
if (!eventName && dataLines.length === 0) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
events.push({
|
|
663
|
-
event: eventName || "message",
|
|
664
|
-
data: dataLines.join("\n")
|
|
665
|
-
});
|
|
666
|
-
eventName = "";
|
|
667
|
-
dataLines = [];
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
function convertZodArray(def) {
|
|
630
|
+
const result = {
|
|
631
|
+
type: "array",
|
|
632
|
+
items: convertZodType(def.type)
|
|
668
633
|
};
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (line === "") {
|
|
672
|
-
pushEvent();
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
if (line.startsWith(":")) {
|
|
676
|
-
continue;
|
|
677
|
-
}
|
|
678
|
-
if (line.startsWith("event:")) {
|
|
679
|
-
eventName = line.slice(6).trim();
|
|
680
|
-
} else if (line.startsWith("data:")) {
|
|
681
|
-
dataLines.push(line.slice(5).trimStart());
|
|
682
|
-
}
|
|
634
|
+
if (def.minLength !== void 0 && def.minLength !== null) {
|
|
635
|
+
result.minItems = def.minLength.value;
|
|
683
636
|
}
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
remainder = "";
|
|
687
|
-
} else {
|
|
688
|
-
remainder = lines[lastIndex] ?? "";
|
|
637
|
+
if (def.maxLength !== void 0 && def.maxLength !== null) {
|
|
638
|
+
result.maxItems = def.maxLength.value;
|
|
689
639
|
}
|
|
690
|
-
|
|
640
|
+
if (def.description) {
|
|
641
|
+
result.description = def.description;
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
691
644
|
}
|
|
692
|
-
function
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
645
|
+
function convertZodObject(def) {
|
|
646
|
+
const shape = def.shape;
|
|
647
|
+
const shapeObj = typeof shape === "function" ? shape() : shape;
|
|
648
|
+
const properties = {};
|
|
649
|
+
const required = [];
|
|
650
|
+
for (const [key, value] of Object.entries(shapeObj)) {
|
|
651
|
+
properties[key] = convertZodType(value);
|
|
652
|
+
const valueDef = value._def;
|
|
653
|
+
const isOptional = valueDef.typeName === "ZodOptional" || valueDef.typeName === "ZodDefault" || valueDef.typeName === "ZodNullable" && valueDef.innerType?._def?.typeName === "ZodDefault";
|
|
654
|
+
if (!isOptional) {
|
|
655
|
+
required.push(key);
|
|
699
656
|
}
|
|
700
657
|
}
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const usage = normalizeUsage(p.usage);
|
|
705
|
-
const responseId = p.response_id || p.id || p?.message?.id;
|
|
706
|
-
const model = normalizeModelId(p.model || p?.message?.model);
|
|
707
|
-
const stopReason = normalizeStopReason(p.stop_reason);
|
|
708
|
-
const textDelta = extractTextDelta(p);
|
|
709
|
-
const toolCallDelta = extractToolCallDelta(p, type);
|
|
710
|
-
const toolCalls = extractToolCalls(p, type);
|
|
711
|
-
return {
|
|
712
|
-
type,
|
|
713
|
-
event: raw.event || type,
|
|
714
|
-
data: p,
|
|
715
|
-
textDelta,
|
|
716
|
-
toolCallDelta,
|
|
717
|
-
toolCalls,
|
|
718
|
-
responseId,
|
|
719
|
-
model,
|
|
720
|
-
stopReason,
|
|
721
|
-
usage,
|
|
722
|
-
requestId,
|
|
723
|
-
raw: raw.data || ""
|
|
658
|
+
const result = {
|
|
659
|
+
type: "object",
|
|
660
|
+
properties
|
|
724
661
|
};
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const hint = String(
|
|
728
|
-
payload?.type || payload?.event || eventName || ""
|
|
729
|
-
).trim();
|
|
730
|
-
switch (hint) {
|
|
731
|
-
case "message_start":
|
|
732
|
-
return "message_start";
|
|
733
|
-
case "message_delta":
|
|
734
|
-
return "message_delta";
|
|
735
|
-
case "message_stop":
|
|
736
|
-
return "message_stop";
|
|
737
|
-
case "tool_use_start":
|
|
738
|
-
return "tool_use_start";
|
|
739
|
-
case "tool_use_delta":
|
|
740
|
-
return "tool_use_delta";
|
|
741
|
-
case "tool_use_stop":
|
|
742
|
-
return "tool_use_stop";
|
|
743
|
-
case "ping":
|
|
744
|
-
return "ping";
|
|
745
|
-
default:
|
|
746
|
-
return "custom";
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
function extractTextDelta(payload) {
|
|
750
|
-
if (!payload || typeof payload !== "object") {
|
|
751
|
-
return void 0;
|
|
662
|
+
if (required.length > 0) {
|
|
663
|
+
result.required = required;
|
|
752
664
|
}
|
|
753
|
-
if (
|
|
754
|
-
|
|
665
|
+
if (def.description) {
|
|
666
|
+
result.description = def.description;
|
|
755
667
|
}
|
|
756
|
-
|
|
757
|
-
|
|
668
|
+
const unknownKeys = def.unknownKeys;
|
|
669
|
+
if (unknownKeys === "strict") {
|
|
670
|
+
result.additionalProperties = false;
|
|
758
671
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
function convertZodEnum(def) {
|
|
675
|
+
const result = {
|
|
676
|
+
type: "string",
|
|
677
|
+
enum: def.values
|
|
678
|
+
};
|
|
679
|
+
if (def.description) {
|
|
680
|
+
result.description = def.description;
|
|
766
681
|
}
|
|
767
|
-
return
|
|
682
|
+
return result;
|
|
768
683
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
771
|
-
|
|
684
|
+
function convertZodNativeEnum(def) {
|
|
685
|
+
const enumValues = def.values;
|
|
686
|
+
const values = Object.values(enumValues).filter(
|
|
687
|
+
(v) => typeof v === "string" || typeof v === "number"
|
|
688
|
+
);
|
|
689
|
+
const result = { enum: values };
|
|
690
|
+
if (def.description) {
|
|
691
|
+
result.description = def.description;
|
|
772
692
|
}
|
|
773
|
-
|
|
774
|
-
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
function convertZodUnion(def) {
|
|
696
|
+
const options = def.options;
|
|
697
|
+
const result = {
|
|
698
|
+
anyOf: options.map(convertZodType)
|
|
699
|
+
};
|
|
700
|
+
if (def.description) {
|
|
701
|
+
result.description = def.description;
|
|
775
702
|
}
|
|
776
|
-
|
|
777
|
-
const d = payload.tool_call_delta;
|
|
778
|
-
return {
|
|
779
|
-
index: d.index ?? 0,
|
|
780
|
-
id: d.id,
|
|
781
|
-
type: d.type,
|
|
782
|
-
function: d.function ? {
|
|
783
|
-
name: d.function.name,
|
|
784
|
-
arguments: d.function.arguments
|
|
785
|
-
} : void 0
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
if (typeof payload.index === "number" || payload.id || payload.name) {
|
|
789
|
-
return {
|
|
790
|
-
index: payload.index ?? 0,
|
|
791
|
-
id: payload.id,
|
|
792
|
-
type: payload.tool_type,
|
|
793
|
-
function: payload.name || payload.arguments ? {
|
|
794
|
-
name: payload.name,
|
|
795
|
-
arguments: payload.arguments
|
|
796
|
-
} : void 0
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
return void 0;
|
|
703
|
+
return result;
|
|
800
704
|
}
|
|
801
|
-
function
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
705
|
+
function convertZodNullable(def) {
|
|
706
|
+
const inner = convertZodType(def.innerType);
|
|
707
|
+
return {
|
|
708
|
+
anyOf: [inner, { type: "null" }]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function convertZodRecord(def) {
|
|
712
|
+
const result = {
|
|
713
|
+
type: "object",
|
|
714
|
+
additionalProperties: convertZodType(def.valueType)
|
|
715
|
+
};
|
|
716
|
+
if (def.description) {
|
|
717
|
+
result.description = def.description;
|
|
813
718
|
}
|
|
814
|
-
return
|
|
719
|
+
return result;
|
|
815
720
|
}
|
|
816
|
-
function
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
model: normalizeModelId(p?.model),
|
|
824
|
-
usage: normalizeUsage(p?.usage),
|
|
825
|
-
requestId
|
|
721
|
+
function convertZodTuple(def) {
|
|
722
|
+
const items = def.items;
|
|
723
|
+
const result = {
|
|
724
|
+
type: "array",
|
|
725
|
+
items: items.map(convertZodType),
|
|
726
|
+
minItems: items.length,
|
|
727
|
+
maxItems: items.length
|
|
826
728
|
};
|
|
827
|
-
if (
|
|
828
|
-
|
|
729
|
+
if (def.description) {
|
|
730
|
+
result.description = def.description;
|
|
829
731
|
}
|
|
830
|
-
return
|
|
732
|
+
return result;
|
|
831
733
|
}
|
|
832
|
-
function
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
type: tc.type || ToolTypes.Function,
|
|
836
|
-
function: tc.function ? { name: tc.function.name, arguments: tc.function.arguments } : void 0
|
|
837
|
-
}));
|
|
734
|
+
function createFunctionToolFromSchema(name, description, schema, options) {
|
|
735
|
+
const jsonSchema = zodToJsonSchema(schema, options);
|
|
736
|
+
return createFunctionTool(name, description, jsonSchema);
|
|
838
737
|
}
|
|
839
|
-
function
|
|
840
|
-
|
|
841
|
-
|
|
738
|
+
function createFunctionTool(name, description, parameters) {
|
|
739
|
+
const fn = { name, description };
|
|
740
|
+
if (parameters) {
|
|
741
|
+
fn.parameters = parameters;
|
|
842
742
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
totalTokens: Number(payload.total_tokens ?? 0)
|
|
743
|
+
return {
|
|
744
|
+
type: ToolTypes.Function,
|
|
745
|
+
function: fn
|
|
847
746
|
};
|
|
848
|
-
if (!usage.totalTokens) {
|
|
849
|
-
usage.totalTokens = usage.inputTokens + usage.outputTokens;
|
|
850
|
-
}
|
|
851
|
-
return usage;
|
|
852
747
|
}
|
|
853
|
-
function
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
748
|
+
function createWebTool(options) {
|
|
749
|
+
return {
|
|
750
|
+
type: ToolTypes.Web,
|
|
751
|
+
web: options ? {
|
|
752
|
+
mode: options.mode,
|
|
753
|
+
allowedDomains: options.allowedDomains,
|
|
754
|
+
excludedDomains: options.excludedDomains,
|
|
755
|
+
maxUses: options.maxUses
|
|
756
|
+
} : void 0
|
|
857
757
|
};
|
|
858
|
-
if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
|
|
859
|
-
if (params.provider) body.provider = providerToString(params.provider);
|
|
860
|
-
if (typeof params.temperature === "number")
|
|
861
|
-
body.temperature = params.temperature;
|
|
862
|
-
if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
|
|
863
|
-
if (params.stop?.length) body.stop = params.stop;
|
|
864
|
-
if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
|
|
865
|
-
if (params.tools?.length) body.tools = normalizeTools(params.tools);
|
|
866
|
-
if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
|
|
867
|
-
return body;
|
|
868
758
|
}
|
|
869
|
-
function
|
|
870
|
-
return
|
|
871
|
-
const normalized = {
|
|
872
|
-
role: msg.role || "user",
|
|
873
|
-
content: msg.content
|
|
874
|
-
};
|
|
875
|
-
if (msg.toolCalls?.length) {
|
|
876
|
-
normalized.tool_calls = msg.toolCalls.map((tc) => ({
|
|
877
|
-
id: tc.id,
|
|
878
|
-
type: tc.type,
|
|
879
|
-
function: tc.function ? { name: tc.function.name, arguments: tc.function.arguments } : void 0
|
|
880
|
-
}));
|
|
881
|
-
}
|
|
882
|
-
if (msg.toolCallId) {
|
|
883
|
-
normalized.tool_call_id = msg.toolCallId;
|
|
884
|
-
}
|
|
885
|
-
return normalized;
|
|
886
|
-
});
|
|
759
|
+
function toolChoiceAuto() {
|
|
760
|
+
return { type: ToolChoiceTypes.Auto };
|
|
887
761
|
}
|
|
888
|
-
function
|
|
889
|
-
return
|
|
890
|
-
const normalized = { type: tool.type };
|
|
891
|
-
if (tool.function) {
|
|
892
|
-
normalized.function = {
|
|
893
|
-
name: tool.function.name,
|
|
894
|
-
description: tool.function.description,
|
|
895
|
-
parameters: tool.function.parameters
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
if (tool.webSearch) {
|
|
899
|
-
normalized.web_search = {
|
|
900
|
-
allowed_domains: tool.webSearch.allowedDomains,
|
|
901
|
-
excluded_domains: tool.webSearch.excludedDomains,
|
|
902
|
-
max_uses: tool.webSearch.maxUses
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
if (tool.xSearch) {
|
|
906
|
-
normalized.x_search = {
|
|
907
|
-
allowed_handles: tool.xSearch.allowedHandles,
|
|
908
|
-
excluded_handles: tool.xSearch.excludedHandles,
|
|
909
|
-
from_date: tool.xSearch.fromDate,
|
|
910
|
-
to_date: tool.xSearch.toDate
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
if (tool.codeExecution) {
|
|
914
|
-
normalized.code_execution = {
|
|
915
|
-
language: tool.codeExecution.language,
|
|
916
|
-
timeout_ms: tool.codeExecution.timeoutMs
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
return normalized;
|
|
920
|
-
});
|
|
762
|
+
function toolChoiceRequired() {
|
|
763
|
+
return { type: ToolChoiceTypes.Required };
|
|
921
764
|
}
|
|
922
|
-
function
|
|
923
|
-
return { type:
|
|
765
|
+
function toolChoiceNone() {
|
|
766
|
+
return { type: ToolChoiceTypes.None };
|
|
924
767
|
}
|
|
925
|
-
function
|
|
926
|
-
return
|
|
768
|
+
function hasToolCalls(response) {
|
|
769
|
+
return (response.toolCalls?.length ?? 0) > 0;
|
|
927
770
|
}
|
|
928
|
-
function
|
|
929
|
-
|
|
930
|
-
for (const src of sources) {
|
|
931
|
-
if (!src) continue;
|
|
932
|
-
for (const [key, value] of Object.entries(src)) {
|
|
933
|
-
const k = key?.trim();
|
|
934
|
-
const v = value?.trim();
|
|
935
|
-
if (!k || !v) continue;
|
|
936
|
-
merged[k] = v;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
return Object.keys(merged).length ? merged : void 0;
|
|
771
|
+
function firstToolCall(response) {
|
|
772
|
+
return response.toolCalls?.[0];
|
|
940
773
|
}
|
|
941
|
-
function
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
774
|
+
function toolResultMessage(toolCallId, result) {
|
|
775
|
+
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
776
|
+
return {
|
|
777
|
+
role: "tool",
|
|
778
|
+
content,
|
|
779
|
+
toolCallId
|
|
780
|
+
};
|
|
945
781
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
782
|
+
function respondToToolCall(call, result) {
|
|
783
|
+
return toolResultMessage(call.id, result);
|
|
784
|
+
}
|
|
785
|
+
function assistantMessageWithToolCalls(content, toolCalls) {
|
|
786
|
+
return {
|
|
787
|
+
role: "assistant",
|
|
788
|
+
content,
|
|
789
|
+
toolCalls
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
var ToolCallAccumulator = class {
|
|
793
|
+
constructor() {
|
|
794
|
+
this.calls = /* @__PURE__ */ new Map();
|
|
959
795
|
}
|
|
960
796
|
/**
|
|
961
|
-
*
|
|
797
|
+
* Processes a streaming tool call delta.
|
|
798
|
+
* Returns true if this started a new tool call.
|
|
962
799
|
*/
|
|
963
|
-
|
|
964
|
-
this.
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
800
|
+
processDelta(delta) {
|
|
801
|
+
const existing = this.calls.get(delta.index);
|
|
802
|
+
if (!existing) {
|
|
803
|
+
this.calls.set(delta.index, {
|
|
804
|
+
id: delta.id ?? "",
|
|
805
|
+
type: delta.type ?? ToolTypes.Function,
|
|
806
|
+
function: {
|
|
807
|
+
name: delta.function?.name ?? "",
|
|
808
|
+
arguments: delta.function?.arguments ?? ""
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
if (delta.function) {
|
|
814
|
+
if (delta.function.name) {
|
|
815
|
+
existing.function = existing.function ?? { name: "", arguments: "" };
|
|
816
|
+
existing.function.name = delta.function.name;
|
|
817
|
+
}
|
|
818
|
+
if (delta.function.arguments) {
|
|
819
|
+
existing.function = existing.function ?? { name: "", arguments: "" };
|
|
820
|
+
existing.function.arguments += delta.function.arguments;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return false;
|
|
970
824
|
}
|
|
971
825
|
/**
|
|
972
|
-
*
|
|
826
|
+
* Returns all accumulated tool calls in index order.
|
|
973
827
|
*/
|
|
974
|
-
|
|
975
|
-
this.
|
|
976
|
-
|
|
977
|
-
throw new ConfigError("tier_id is required");
|
|
978
|
-
}
|
|
979
|
-
if (!request.external_id?.trim()) {
|
|
980
|
-
throw new ConfigError("external_id is required");
|
|
828
|
+
getToolCalls() {
|
|
829
|
+
if (this.calls.size === 0) {
|
|
830
|
+
return [];
|
|
981
831
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
832
|
+
const maxIdx = Math.max(...this.calls.keys());
|
|
833
|
+
const result = [];
|
|
834
|
+
for (let i = 0; i <= maxIdx; i++) {
|
|
835
|
+
const call = this.calls.get(i);
|
|
836
|
+
if (call) {
|
|
837
|
+
result.push(call);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return result;
|
|
988
841
|
}
|
|
989
842
|
/**
|
|
990
|
-
*
|
|
843
|
+
* Returns a specific tool call by index, or undefined if not found.
|
|
991
844
|
*/
|
|
992
|
-
|
|
993
|
-
this.
|
|
994
|
-
if (!customerId?.trim()) {
|
|
995
|
-
throw new ConfigError("customerId is required");
|
|
996
|
-
}
|
|
997
|
-
const response = await this.http.json(
|
|
998
|
-
`/customers/${customerId}`,
|
|
999
|
-
{
|
|
1000
|
-
method: "GET",
|
|
1001
|
-
apiKey: this.apiKey
|
|
1002
|
-
}
|
|
1003
|
-
);
|
|
1004
|
-
return response.customer;
|
|
845
|
+
getToolCall(index) {
|
|
846
|
+
return this.calls.get(index);
|
|
1005
847
|
}
|
|
1006
848
|
/**
|
|
1007
|
-
*
|
|
1008
|
-
* If a customer with the given external_id exists, it is updated.
|
|
1009
|
-
* Otherwise, a new customer is created.
|
|
849
|
+
* Clears all accumulated tool calls.
|
|
1010
850
|
*/
|
|
1011
|
-
|
|
1012
|
-
this.
|
|
1013
|
-
|
|
1014
|
-
|
|
851
|
+
reset() {
|
|
852
|
+
this.calls.clear();
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
var ToolArgsError = class extends Error {
|
|
856
|
+
constructor(message, toolCallId, toolName, rawArguments) {
|
|
857
|
+
super(message);
|
|
858
|
+
this.name = "ToolArgsError";
|
|
859
|
+
this.toolCallId = toolCallId;
|
|
860
|
+
this.toolName = toolName;
|
|
861
|
+
this.rawArguments = rawArguments;
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
function parseToolArgs(call, schema) {
|
|
865
|
+
const toolName = call.function?.name ?? "unknown";
|
|
866
|
+
const rawArgs = call.function?.arguments ?? "";
|
|
867
|
+
let parsed;
|
|
868
|
+
try {
|
|
869
|
+
parsed = rawArgs ? JSON.parse(rawArgs) : {};
|
|
870
|
+
} catch (err) {
|
|
871
|
+
const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
|
|
872
|
+
throw new ToolArgsError(
|
|
873
|
+
`Failed to parse arguments for tool '${toolName}': ${message}`,
|
|
874
|
+
call.id,
|
|
875
|
+
toolName,
|
|
876
|
+
rawArgs
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
return schema.parse(parsed);
|
|
881
|
+
} catch (err) {
|
|
882
|
+
let message;
|
|
883
|
+
if (err instanceof Error) {
|
|
884
|
+
const zodErr = err;
|
|
885
|
+
if (zodErr.errors && Array.isArray(zodErr.errors)) {
|
|
886
|
+
const issues = zodErr.errors.map((e) => {
|
|
887
|
+
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
888
|
+
return `${path}${e.message}`;
|
|
889
|
+
}).join("; ");
|
|
890
|
+
message = issues;
|
|
891
|
+
} else {
|
|
892
|
+
message = err.message;
|
|
893
|
+
}
|
|
894
|
+
} else {
|
|
895
|
+
message = String(err);
|
|
1015
896
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
897
|
+
throw new ToolArgsError(
|
|
898
|
+
`Invalid arguments for tool '${toolName}': ${message}`,
|
|
899
|
+
call.id,
|
|
900
|
+
toolName,
|
|
901
|
+
rawArgs
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function tryParseToolArgs(call, schema) {
|
|
906
|
+
try {
|
|
907
|
+
const data = parseToolArgs(call, schema);
|
|
908
|
+
return { success: true, data };
|
|
909
|
+
} catch (err) {
|
|
910
|
+
if (err instanceof ToolArgsError) {
|
|
911
|
+
return { success: false, error: err };
|
|
1018
912
|
}
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
913
|
+
const toolName = call.function?.name ?? "unknown";
|
|
914
|
+
const rawArgs = call.function?.arguments ?? "";
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
error: new ToolArgsError(
|
|
918
|
+
err instanceof Error ? err.message : String(err),
|
|
919
|
+
call.id,
|
|
920
|
+
toolName,
|
|
921
|
+
rawArgs
|
|
922
|
+
)
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function parseToolArgsRaw(call) {
|
|
927
|
+
const toolName = call.function?.name ?? "unknown";
|
|
928
|
+
const rawArgs = call.function?.arguments ?? "";
|
|
929
|
+
try {
|
|
930
|
+
return rawArgs ? JSON.parse(rawArgs) : {};
|
|
931
|
+
} catch (err) {
|
|
932
|
+
const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
|
|
933
|
+
throw new ToolArgsError(
|
|
934
|
+
`Failed to parse arguments for tool '${toolName}': ${message}`,
|
|
935
|
+
call.id,
|
|
936
|
+
toolName,
|
|
937
|
+
rawArgs
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
var ToolRegistry = class {
|
|
942
|
+
constructor() {
|
|
943
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
1025
944
|
}
|
|
1026
945
|
/**
|
|
1027
|
-
*
|
|
946
|
+
* Registers a handler function for a tool name.
|
|
947
|
+
* @param name - The tool name (must match the function name in the tool definition)
|
|
948
|
+
* @param handler - Function to execute when this tool is called
|
|
949
|
+
* @returns this for chaining
|
|
1028
950
|
*/
|
|
1029
|
-
|
|
1030
|
-
this.
|
|
1031
|
-
|
|
1032
|
-
throw new ConfigError("customerId is required");
|
|
1033
|
-
}
|
|
1034
|
-
await this.http.request(`/customers/${customerId}`, {
|
|
1035
|
-
method: "DELETE",
|
|
1036
|
-
apiKey: this.apiKey
|
|
1037
|
-
});
|
|
951
|
+
register(name, handler) {
|
|
952
|
+
this.handlers.set(name, handler);
|
|
953
|
+
return this;
|
|
1038
954
|
}
|
|
1039
955
|
/**
|
|
1040
|
-
*
|
|
956
|
+
* Unregisters a tool handler.
|
|
957
|
+
* @param name - The tool name to unregister
|
|
958
|
+
* @returns true if the handler was removed, false if it didn't exist
|
|
1041
959
|
*/
|
|
1042
|
-
|
|
1043
|
-
this.
|
|
1044
|
-
if (!customerId?.trim()) {
|
|
1045
|
-
throw new ConfigError("customerId is required");
|
|
1046
|
-
}
|
|
1047
|
-
if (!request.success_url?.trim() || !request.cancel_url?.trim()) {
|
|
1048
|
-
throw new ConfigError("success_url and cancel_url are required");
|
|
1049
|
-
}
|
|
1050
|
-
return await this.http.json(
|
|
1051
|
-
`/customers/${customerId}/checkout`,
|
|
1052
|
-
{
|
|
1053
|
-
method: "POST",
|
|
1054
|
-
body: request,
|
|
1055
|
-
apiKey: this.apiKey
|
|
1056
|
-
}
|
|
1057
|
-
);
|
|
960
|
+
unregister(name) {
|
|
961
|
+
return this.handlers.delete(name);
|
|
1058
962
|
}
|
|
1059
963
|
/**
|
|
1060
|
-
*
|
|
964
|
+
* Checks if a handler is registered for the given tool name.
|
|
1061
965
|
*/
|
|
1062
|
-
|
|
1063
|
-
this.
|
|
1064
|
-
if (!customerId?.trim()) {
|
|
1065
|
-
throw new ConfigError("customerId is required");
|
|
1066
|
-
}
|
|
1067
|
-
return await this.http.json(
|
|
1068
|
-
`/customers/${customerId}/subscription`,
|
|
1069
|
-
{
|
|
1070
|
-
method: "GET",
|
|
1071
|
-
apiKey: this.apiKey
|
|
1072
|
-
}
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
|
|
1077
|
-
// src/tiers.ts
|
|
1078
|
-
var TiersClient = class {
|
|
1079
|
-
constructor(http, cfg) {
|
|
1080
|
-
this.http = http;
|
|
1081
|
-
this.apiKey = cfg.apiKey;
|
|
1082
|
-
}
|
|
1083
|
-
ensureApiKey() {
|
|
1084
|
-
if (!this.apiKey || !this.apiKey.startsWith("mr_pk_") && !this.apiKey.startsWith("mr_sk_")) {
|
|
1085
|
-
throw new ConfigError(
|
|
1086
|
-
"API key (mr_pk_* or mr_sk_*) required for tier operations"
|
|
1087
|
-
);
|
|
1088
|
-
}
|
|
966
|
+
has(name) {
|
|
967
|
+
return this.handlers.has(name);
|
|
1089
968
|
}
|
|
1090
969
|
/**
|
|
1091
|
-
*
|
|
970
|
+
* Returns the list of registered tool names.
|
|
1092
971
|
*/
|
|
1093
|
-
|
|
1094
|
-
this.
|
|
1095
|
-
const response = await this.http.json("/tiers", {
|
|
1096
|
-
method: "GET",
|
|
1097
|
-
apiKey: this.apiKey
|
|
1098
|
-
});
|
|
1099
|
-
return response.tiers;
|
|
972
|
+
getRegisteredTools() {
|
|
973
|
+
return Array.from(this.handlers.keys());
|
|
1100
974
|
}
|
|
1101
975
|
/**
|
|
1102
|
-
*
|
|
976
|
+
* Executes a single tool call.
|
|
977
|
+
* @param call - The tool call to execute
|
|
978
|
+
* @returns The execution result
|
|
1103
979
|
*/
|
|
1104
|
-
async
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
980
|
+
async execute(call) {
|
|
981
|
+
const toolName = call.function?.name ?? "";
|
|
982
|
+
const handler = this.handlers.get(toolName);
|
|
983
|
+
if (!handler) {
|
|
984
|
+
return {
|
|
985
|
+
toolCallId: call.id,
|
|
986
|
+
toolName,
|
|
987
|
+
result: null,
|
|
988
|
+
error: `Unknown tool: '${toolName}'. Available tools: ${this.getRegisteredTools().join(", ") || "none"}`
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
let args;
|
|
992
|
+
try {
|
|
993
|
+
args = call.function?.arguments ? JSON.parse(call.function.arguments) : {};
|
|
994
|
+
} catch (err) {
|
|
995
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
996
|
+
return {
|
|
997
|
+
toolCallId: call.id,
|
|
998
|
+
toolName,
|
|
999
|
+
result: null,
|
|
1000
|
+
error: `Invalid JSON in arguments: ${errorMessage}`,
|
|
1001
|
+
isRetryable: true
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
try {
|
|
1005
|
+
const result = await handler(args, call);
|
|
1006
|
+
return {
|
|
1007
|
+
toolCallId: call.id,
|
|
1008
|
+
toolName,
|
|
1009
|
+
result
|
|
1010
|
+
};
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
const isRetryable = err instanceof ToolArgsError;
|
|
1013
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1014
|
+
return {
|
|
1015
|
+
toolCallId: call.id,
|
|
1016
|
+
toolName,
|
|
1017
|
+
result: null,
|
|
1018
|
+
error: errorMessage,
|
|
1019
|
+
isRetryable
|
|
1020
|
+
};
|
|
1125
1021
|
}
|
|
1126
|
-
this.baseUrl = resolvedBase;
|
|
1127
|
-
this.apiKey = cfg.apiKey?.trim();
|
|
1128
|
-
this.accessToken = cfg.accessToken?.trim();
|
|
1129
|
-
this.fetchImpl = cfg.fetchImpl;
|
|
1130
|
-
this.clientHeader = cfg.clientHeader?.trim() || DEFAULT_CLIENT_HEADER;
|
|
1131
|
-
this.defaultConnectTimeoutMs = cfg.connectTimeoutMs === void 0 ? DEFAULT_CONNECT_TIMEOUT_MS : Math.max(0, cfg.connectTimeoutMs);
|
|
1132
|
-
this.defaultTimeoutMs = cfg.timeoutMs === void 0 ? DEFAULT_REQUEST_TIMEOUT_MS : Math.max(0, cfg.timeoutMs);
|
|
1133
|
-
this.retry = normalizeRetryConfig(cfg.retry);
|
|
1134
|
-
this.defaultHeaders = normalizeHeaders(cfg.defaultHeaders);
|
|
1135
|
-
this.metrics = cfg.metrics;
|
|
1136
|
-
this.trace = cfg.trace;
|
|
1137
1022
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1023
|
+
/**
|
|
1024
|
+
* Executes multiple tool calls in parallel.
|
|
1025
|
+
* @param calls - Array of tool calls to execute
|
|
1026
|
+
* @returns Array of execution results in the same order as input
|
|
1027
|
+
*/
|
|
1028
|
+
async executeAll(calls) {
|
|
1029
|
+
return Promise.all(calls.map((call) => this.execute(call)));
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Converts execution results to tool result messages.
|
|
1033
|
+
* Useful for appending to the conversation history.
|
|
1034
|
+
* @param results - Array of execution results
|
|
1035
|
+
* @returns Array of ChatMessage objects with role "tool"
|
|
1036
|
+
*/
|
|
1037
|
+
resultsToMessages(results) {
|
|
1038
|
+
return results.map((r) => {
|
|
1039
|
+
const content = r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result);
|
|
1040
|
+
return toolResultMessage(r.toolCallId, content);
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
function formatToolErrorForModel(result) {
|
|
1045
|
+
const lines = [
|
|
1046
|
+
`Tool call error for '${result.toolName}': ${result.error}`
|
|
1047
|
+
];
|
|
1048
|
+
if (result.isRetryable) {
|
|
1049
|
+
lines.push("");
|
|
1050
|
+
lines.push("Please correct the arguments and try again.");
|
|
1051
|
+
}
|
|
1052
|
+
return lines.join("\n");
|
|
1053
|
+
}
|
|
1054
|
+
function hasRetryableErrors(results) {
|
|
1055
|
+
return results.some((r) => r.error && r.isRetryable);
|
|
1056
|
+
}
|
|
1057
|
+
function getRetryableErrors(results) {
|
|
1058
|
+
return results.filter((r) => r.error && r.isRetryable);
|
|
1059
|
+
}
|
|
1060
|
+
function createRetryMessages(results) {
|
|
1061
|
+
return results.filter((r) => r.error && r.isRetryable).map((r) => toolResultMessage(r.toolCallId, formatToolErrorForModel(r)));
|
|
1062
|
+
}
|
|
1063
|
+
async function executeWithRetry(registry, toolCalls, options = {}) {
|
|
1064
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
1065
|
+
let currentCalls = toolCalls;
|
|
1066
|
+
let attempt = 0;
|
|
1067
|
+
const successfulResults = /* @__PURE__ */ new Map();
|
|
1068
|
+
while (attempt <= maxRetries) {
|
|
1069
|
+
const results = await registry.executeAll(currentCalls);
|
|
1070
|
+
for (const result of results) {
|
|
1071
|
+
if (!result.error || !result.isRetryable) {
|
|
1072
|
+
successfulResults.set(result.toolCallId, result);
|
|
1073
|
+
}
|
|
1144
1074
|
}
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1075
|
+
const retryableResults = getRetryableErrors(results);
|
|
1076
|
+
if (retryableResults.length === 0 || !options.onRetry) {
|
|
1077
|
+
for (const result of results) {
|
|
1078
|
+
if (result.error && result.isRetryable) {
|
|
1079
|
+
successfulResults.set(result.toolCallId, result);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return Array.from(successfulResults.values());
|
|
1083
|
+
}
|
|
1084
|
+
attempt++;
|
|
1085
|
+
if (attempt > maxRetries) {
|
|
1086
|
+
for (const result of retryableResults) {
|
|
1087
|
+
successfulResults.set(result.toolCallId, result);
|
|
1088
|
+
}
|
|
1089
|
+
return Array.from(successfulResults.values());
|
|
1090
|
+
}
|
|
1091
|
+
const errorMessages = createRetryMessages(retryableResults);
|
|
1092
|
+
const newCalls = await options.onRetry(errorMessages, attempt);
|
|
1093
|
+
if (newCalls.length === 0) {
|
|
1094
|
+
for (const result of retryableResults) {
|
|
1095
|
+
successfulResults.set(result.toolCallId, result);
|
|
1096
|
+
}
|
|
1097
|
+
return Array.from(successfulResults.values());
|
|
1098
|
+
}
|
|
1099
|
+
currentCalls = newCalls;
|
|
1100
|
+
}
|
|
1101
|
+
return Array.from(successfulResults.values());
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/chat.ts
|
|
1105
|
+
var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
|
|
1106
|
+
var ChatClient = class {
|
|
1107
|
+
constructor(http, auth, cfg = {}) {
|
|
1108
|
+
this.completions = new ChatCompletionsClient(
|
|
1109
|
+
http,
|
|
1110
|
+
auth,
|
|
1111
|
+
cfg.defaultMetadata,
|
|
1112
|
+
cfg.metrics,
|
|
1113
|
+
cfg.trace
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
var ChatCompletionsClient = class {
|
|
1118
|
+
constructor(http, auth, defaultMetadata, metrics, trace) {
|
|
1119
|
+
this.http = http;
|
|
1120
|
+
this.auth = auth;
|
|
1121
|
+
this.defaultMetadata = defaultMetadata;
|
|
1122
|
+
this.metrics = metrics;
|
|
1123
|
+
this.trace = trace;
|
|
1124
|
+
}
|
|
1125
|
+
async create(params, options = {}) {
|
|
1126
|
+
const stream = options.stream ?? params.stream ?? true;
|
|
1147
1127
|
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
1148
1128
|
const trace = mergeTrace(this.trace, options.trace);
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1129
|
+
if (!params?.messages?.length) {
|
|
1130
|
+
throw new ConfigError("at least one message is required");
|
|
1131
|
+
}
|
|
1132
|
+
if (!hasUserMessage(params.messages)) {
|
|
1133
|
+
throw new ConfigError("at least one user message is required");
|
|
1134
|
+
}
|
|
1135
|
+
const authHeaders = await this.auth.authForChat(params.customerId);
|
|
1136
|
+
const body = buildProxyBody(
|
|
1137
|
+
params,
|
|
1138
|
+
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
1139
|
+
);
|
|
1140
|
+
const requestId = params.requestId || options.requestId;
|
|
1141
|
+
const headers = { ...options.headers || {} };
|
|
1142
|
+
if (requestId) {
|
|
1143
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
1144
|
+
}
|
|
1145
|
+
const baseContext = {
|
|
1146
|
+
method: "POST",
|
|
1147
|
+
path: "/llm/proxy",
|
|
1148
|
+
model: params.model,
|
|
1149
|
+
requestId
|
|
1153
1150
|
};
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1151
|
+
const response = await this.http.request("/llm/proxy", {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
body,
|
|
1154
|
+
headers,
|
|
1155
|
+
apiKey: authHeaders.apiKey,
|
|
1156
|
+
accessToken: authHeaders.accessToken,
|
|
1157
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
1158
|
+
raw: true,
|
|
1159
|
+
signal: options.signal,
|
|
1160
|
+
timeoutMs: options.timeoutMs ?? (stream ? 0 : void 0),
|
|
1161
|
+
useDefaultTimeout: !stream,
|
|
1162
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
1163
|
+
retry: options.retry,
|
|
1164
|
+
metrics,
|
|
1165
|
+
trace,
|
|
1166
|
+
context: baseContext
|
|
1159
1167
|
});
|
|
1160
|
-
const
|
|
1161
|
-
if (
|
|
1162
|
-
|
|
1168
|
+
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
1169
|
+
if (!response.ok) {
|
|
1170
|
+
throw await parseErrorResponse(response);
|
|
1163
1171
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1172
|
+
if (!stream) {
|
|
1173
|
+
const payload = await response.json();
|
|
1174
|
+
const result = normalizeChatResponse(payload, resolvedRequestId);
|
|
1175
|
+
if (metrics?.usage) {
|
|
1176
|
+
const ctx = {
|
|
1177
|
+
...baseContext,
|
|
1178
|
+
requestId: resolvedRequestId ?? baseContext.requestId,
|
|
1179
|
+
responseId: result.id
|
|
1180
|
+
};
|
|
1181
|
+
metrics.usage({ usage: result.usage, context: ctx });
|
|
1182
|
+
}
|
|
1183
|
+
return result;
|
|
1169
1184
|
}
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1185
|
+
const streamContext = {
|
|
1186
|
+
...baseContext,
|
|
1187
|
+
requestId: resolvedRequestId ?? baseContext.requestId
|
|
1188
|
+
};
|
|
1189
|
+
return new ChatCompletionsStream(
|
|
1190
|
+
response,
|
|
1191
|
+
resolvedRequestId,
|
|
1192
|
+
streamContext,
|
|
1193
|
+
metrics,
|
|
1194
|
+
trace
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Stream structured JSON responses using the NDJSON contract defined for
|
|
1199
|
+
* /llm/proxy. The request must include a structured responseFormat.
|
|
1200
|
+
*/
|
|
1201
|
+
async streamJSON(params, options = {}) {
|
|
1202
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
1203
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
1204
|
+
if (!params?.messages?.length) {
|
|
1205
|
+
throw new ConfigError("at least one message is required");
|
|
1174
1206
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
headers.set("X-ModelRelay-Api-Key", apiKey);
|
|
1207
|
+
if (!hasUserMessage(params.messages)) {
|
|
1208
|
+
throw new ConfigError("at least one user message is required");
|
|
1178
1209
|
}
|
|
1179
|
-
if (
|
|
1180
|
-
|
|
1210
|
+
if (!params.responseFormat || params.responseFormat.type !== "json_object" && params.responseFormat.type !== "json_schema") {
|
|
1211
|
+
throw new ConfigError(
|
|
1212
|
+
"responseFormat with type=json_object or json_schema is required for structured streaming"
|
|
1213
|
+
);
|
|
1181
1214
|
}
|
|
1182
|
-
const
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1215
|
+
const authHeaders = await this.auth.authForChat(params.customerId);
|
|
1216
|
+
const body = buildProxyBody(
|
|
1217
|
+
params,
|
|
1218
|
+
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
1186
1219
|
);
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
let connectTimedOut = false;
|
|
1192
|
-
let requestTimedOut = false;
|
|
1193
|
-
const connectController = connectTimeoutMs && connectTimeoutMs > 0 ? new AbortController() : void 0;
|
|
1194
|
-
const requestController = timeoutMs && timeoutMs > 0 ? new AbortController() : void 0;
|
|
1195
|
-
const signal = mergeSignals(
|
|
1196
|
-
options.signal,
|
|
1197
|
-
connectController?.signal,
|
|
1198
|
-
requestController?.signal
|
|
1199
|
-
);
|
|
1200
|
-
const connectTimer = connectController && setTimeout(() => {
|
|
1201
|
-
connectTimedOut = true;
|
|
1202
|
-
connectController.abort(
|
|
1203
|
-
new DOMException("connect timeout", "AbortError")
|
|
1204
|
-
);
|
|
1205
|
-
}, connectTimeoutMs);
|
|
1206
|
-
const requestTimer = requestController && setTimeout(() => {
|
|
1207
|
-
requestTimedOut = true;
|
|
1208
|
-
requestController.abort(
|
|
1209
|
-
new DOMException("timeout", "AbortError")
|
|
1210
|
-
);
|
|
1211
|
-
}, timeoutMs);
|
|
1212
|
-
try {
|
|
1213
|
-
const response = await fetchFn(url, {
|
|
1214
|
-
method,
|
|
1215
|
-
headers,
|
|
1216
|
-
body: payload,
|
|
1217
|
-
signal
|
|
1218
|
-
});
|
|
1219
|
-
if (connectTimer) {
|
|
1220
|
-
clearTimeout(connectTimer);
|
|
1221
|
-
}
|
|
1222
|
-
if (!response.ok) {
|
|
1223
|
-
const shouldRetry = retryCfg && shouldRetryStatus(
|
|
1224
|
-
response.status,
|
|
1225
|
-
method,
|
|
1226
|
-
retryCfg.retryPost
|
|
1227
|
-
) && attempt < attempts;
|
|
1228
|
-
if (shouldRetry) {
|
|
1229
|
-
lastStatus = response.status;
|
|
1230
|
-
await backoff(attempt, retryCfg);
|
|
1231
|
-
continue;
|
|
1232
|
-
}
|
|
1233
|
-
const retries = buildRetryMetadata(attempt, response.status, lastError);
|
|
1234
|
-
const finishedCtx2 = withRequestId(context, response.headers);
|
|
1235
|
-
recordHttpMetrics(metrics, trace, start, retries, {
|
|
1236
|
-
status: response.status,
|
|
1237
|
-
context: finishedCtx2
|
|
1238
|
-
});
|
|
1239
|
-
throw options.raw ? await parseErrorResponse(response, retries) : await parseErrorResponse(response, retries);
|
|
1240
|
-
}
|
|
1241
|
-
const finishedCtx = withRequestId(context, response.headers);
|
|
1242
|
-
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
1243
|
-
status: response.status,
|
|
1244
|
-
context: finishedCtx
|
|
1245
|
-
});
|
|
1246
|
-
return response;
|
|
1247
|
-
} catch (err) {
|
|
1248
|
-
if (options.signal?.aborted) {
|
|
1249
|
-
throw err;
|
|
1250
|
-
}
|
|
1251
|
-
if (err instanceof ModelRelayError) {
|
|
1252
|
-
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
1253
|
-
error: err,
|
|
1254
|
-
context
|
|
1255
|
-
});
|
|
1256
|
-
throw err;
|
|
1257
|
-
}
|
|
1258
|
-
const transportKind = classifyTransportErrorKind(
|
|
1259
|
-
err,
|
|
1260
|
-
connectTimedOut,
|
|
1261
|
-
requestTimedOut
|
|
1262
|
-
);
|
|
1263
|
-
const shouldRetry = retryCfg && isRetryableError(err, transportKind) && (method !== "POST" || retryCfg.retryPost) && attempt < attempts;
|
|
1264
|
-
if (!shouldRetry) {
|
|
1265
|
-
const retries = buildRetryMetadata(
|
|
1266
|
-
attempt,
|
|
1267
|
-
lastStatus,
|
|
1268
|
-
err instanceof Error ? err.message : String(err)
|
|
1269
|
-
);
|
|
1270
|
-
recordHttpMetrics(metrics, trace, start, retries, {
|
|
1271
|
-
error: err,
|
|
1272
|
-
context
|
|
1273
|
-
});
|
|
1274
|
-
throw toTransportError(err, transportKind, retries);
|
|
1275
|
-
}
|
|
1276
|
-
lastError = err;
|
|
1277
|
-
await backoff(attempt, retryCfg);
|
|
1278
|
-
} finally {
|
|
1279
|
-
if (connectTimer) {
|
|
1280
|
-
clearTimeout(connectTimer);
|
|
1281
|
-
}
|
|
1282
|
-
if (requestTimer) {
|
|
1283
|
-
clearTimeout(requestTimer);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1220
|
+
const requestId = params.requestId || options.requestId;
|
|
1221
|
+
const headers = { ...options.headers || {} };
|
|
1222
|
+
if (requestId) {
|
|
1223
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
1286
1224
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const response = await this.request(
|
|
1294
|
-
|
|
1225
|
+
const baseContext = {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
path: "/llm/proxy",
|
|
1228
|
+
model: params.model,
|
|
1229
|
+
requestId
|
|
1230
|
+
};
|
|
1231
|
+
const response = await this.http.request("/llm/proxy", {
|
|
1232
|
+
method: "POST",
|
|
1233
|
+
body,
|
|
1234
|
+
headers,
|
|
1235
|
+
apiKey: authHeaders.apiKey,
|
|
1236
|
+
accessToken: authHeaders.accessToken,
|
|
1237
|
+
accept: "application/x-ndjson",
|
|
1295
1238
|
raw: true,
|
|
1296
|
-
|
|
1239
|
+
signal: options.signal,
|
|
1240
|
+
timeoutMs: options.timeoutMs ?? 0,
|
|
1241
|
+
useDefaultTimeout: false,
|
|
1242
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
1243
|
+
retry: options.retry,
|
|
1244
|
+
metrics,
|
|
1245
|
+
trace,
|
|
1246
|
+
context: baseContext
|
|
1297
1247
|
});
|
|
1248
|
+
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
1298
1249
|
if (!response.ok) {
|
|
1299
1250
|
throw await parseErrorResponse(response);
|
|
1300
1251
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
throw new APIError("failed to parse response JSON", {
|
|
1308
|
-
status: response.status,
|
|
1309
|
-
data: err
|
|
1310
|
-
});
|
|
1252
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
1253
|
+
if (!/application\/(x-)?ndjson/i.test(contentType)) {
|
|
1254
|
+
throw new TransportError(
|
|
1255
|
+
`expected NDJSON structured stream, got Content-Type ${contentType || "missing"}`,
|
|
1256
|
+
{ kind: "request" }
|
|
1257
|
+
);
|
|
1311
1258
|
}
|
|
1259
|
+
const streamContext = {
|
|
1260
|
+
...baseContext,
|
|
1261
|
+
requestId: resolvedRequestId ?? baseContext.requestId
|
|
1262
|
+
};
|
|
1263
|
+
return new StructuredJSONStream(
|
|
1264
|
+
response,
|
|
1265
|
+
resolvedRequestId,
|
|
1266
|
+
streamContext,
|
|
1267
|
+
metrics,
|
|
1268
|
+
trace
|
|
1269
|
+
);
|
|
1312
1270
|
}
|
|
1313
1271
|
};
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
}
|
|
1328
|
-
return trimmed;
|
|
1329
|
-
}
|
|
1330
|
-
function isValidHttpUrl(value) {
|
|
1331
|
-
return /^https?:\/\//i.test(value);
|
|
1332
|
-
}
|
|
1333
|
-
function normalizeRetryConfig(retry) {
|
|
1334
|
-
if (retry === false) return void 0;
|
|
1335
|
-
const cfg = retry || {};
|
|
1336
|
-
return {
|
|
1337
|
-
maxAttempts: Math.max(1, cfg.maxAttempts ?? 3),
|
|
1338
|
-
baseBackoffMs: Math.max(0, cfg.baseBackoffMs ?? 300),
|
|
1339
|
-
maxBackoffMs: Math.max(0, cfg.maxBackoffMs ?? 5e3),
|
|
1340
|
-
retryPost: cfg.retryPost ?? true
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
function shouldRetryStatus(status, method, retryPost) {
|
|
1344
|
-
if (status === 408 || status === 429) {
|
|
1345
|
-
return method !== "POST" || retryPost;
|
|
1272
|
+
var ChatCompletionsStream = class {
|
|
1273
|
+
constructor(response, requestId, context, metrics, trace) {
|
|
1274
|
+
this.firstTokenEmitted = false;
|
|
1275
|
+
this.closed = false;
|
|
1276
|
+
if (!response.body) {
|
|
1277
|
+
throw new ConfigError("streaming response is missing a body");
|
|
1278
|
+
}
|
|
1279
|
+
this.response = response;
|
|
1280
|
+
this.requestId = requestId;
|
|
1281
|
+
this.context = context;
|
|
1282
|
+
this.metrics = metrics;
|
|
1283
|
+
this.trace = trace;
|
|
1284
|
+
this.startedAt = this.metrics?.streamFirstToken || this.trace?.streamEvent || this.trace?.streamError ? Date.now() : 0;
|
|
1346
1285
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1286
|
+
async cancel(reason) {
|
|
1287
|
+
this.closed = true;
|
|
1288
|
+
try {
|
|
1289
|
+
await this.response.body?.cancel(reason);
|
|
1290
|
+
} catch {
|
|
1291
|
+
}
|
|
1349
1292
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1293
|
+
async *[Symbol.asyncIterator]() {
|
|
1294
|
+
if (this.closed) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const body = this.response.body;
|
|
1298
|
+
if (!body) {
|
|
1299
|
+
throw new ConfigError("streaming response is missing a body");
|
|
1300
|
+
}
|
|
1301
|
+
const reader = body.getReader();
|
|
1302
|
+
const decoder = new TextDecoder();
|
|
1303
|
+
let buffer = "";
|
|
1304
|
+
try {
|
|
1305
|
+
while (true) {
|
|
1306
|
+
if (this.closed) {
|
|
1307
|
+
await reader.cancel();
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
const { value, done } = await reader.read();
|
|
1311
|
+
if (done) {
|
|
1312
|
+
const { events: events2 } = consumeSSEBuffer(buffer, true);
|
|
1313
|
+
for (const evt of events2) {
|
|
1314
|
+
const parsed = mapChatEvent(evt, this.requestId);
|
|
1315
|
+
if (parsed) {
|
|
1316
|
+
this.handleStreamEvent(parsed);
|
|
1317
|
+
yield parsed;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1323
|
+
const { events, remainder } = consumeSSEBuffer(buffer);
|
|
1324
|
+
buffer = remainder;
|
|
1325
|
+
for (const evt of events) {
|
|
1326
|
+
const parsed = mapChatEvent(evt, this.requestId);
|
|
1327
|
+
if (parsed) {
|
|
1328
|
+
this.handleStreamEvent(parsed);
|
|
1329
|
+
yield parsed;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
} catch (err) {
|
|
1334
|
+
this.recordFirstToken(err);
|
|
1335
|
+
this.trace?.streamError?.({ context: this.context, error: err });
|
|
1336
|
+
throw err;
|
|
1337
|
+
} finally {
|
|
1338
|
+
this.closed = true;
|
|
1339
|
+
reader.releaseLock();
|
|
1375
1340
|
}
|
|
1376
|
-
src.addEventListener(
|
|
1377
|
-
"abort",
|
|
1378
|
-
() => controller.abort(src.reason),
|
|
1379
|
-
{ once: true }
|
|
1380
|
-
);
|
|
1381
1341
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
if (k && v) {
|
|
1392
|
-
normalized[k] = v;
|
|
1342
|
+
handleStreamEvent(evt) {
|
|
1343
|
+
const context = this.enrichContext(evt);
|
|
1344
|
+
this.context = context;
|
|
1345
|
+
this.trace?.streamEvent?.({ context, event: evt });
|
|
1346
|
+
if (evt.type === "message_start" || evt.type === "message_delta" || evt.type === "message_stop" || evt.type === "tool_use_start" || evt.type === "tool_use_delta" || evt.type === "tool_use_stop") {
|
|
1347
|
+
this.recordFirstToken();
|
|
1348
|
+
}
|
|
1349
|
+
if (evt.type === "message_stop" && evt.usage && this.metrics?.usage) {
|
|
1350
|
+
this.metrics.usage({ usage: evt.usage, context });
|
|
1393
1351
|
}
|
|
1394
1352
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
lastError: typeof lastError === "string" ? lastError : lastError instanceof Error ? lastError.message : lastError ? String(lastError) : void 0
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
function classifyTransportErrorKind(err, connectTimedOut, requestTimedOut) {
|
|
1406
|
-
if (connectTimedOut) return "connect";
|
|
1407
|
-
if (requestTimedOut) return "timeout";
|
|
1408
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
1409
|
-
return requestTimedOut ? "timeout" : "request";
|
|
1353
|
+
enrichContext(evt) {
|
|
1354
|
+
return {
|
|
1355
|
+
...this.context,
|
|
1356
|
+
responseId: evt.responseId || this.context.responseId,
|
|
1357
|
+
requestId: evt.requestId || this.context.requestId,
|
|
1358
|
+
model: evt.model || this.context.model
|
|
1359
|
+
};
|
|
1410
1360
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
return new TransportError(message, { kind, retries, cause: err });
|
|
1417
|
-
}
|
|
1418
|
-
function recordHttpMetrics(metrics, trace, start, retries, info) {
|
|
1419
|
-
if (!metrics?.httpRequest && !trace?.requestFinish) return;
|
|
1420
|
-
const latencyMs = start ? Date.now() - start : 0;
|
|
1421
|
-
if (metrics?.httpRequest) {
|
|
1422
|
-
metrics.httpRequest({
|
|
1361
|
+
recordFirstToken(error) {
|
|
1362
|
+
if (!this.metrics?.streamFirstToken || this.firstTokenEmitted) return;
|
|
1363
|
+
this.firstTokenEmitted = true;
|
|
1364
|
+
const latencyMs = this.startedAt ? Date.now() - this.startedAt : 0;
|
|
1365
|
+
this.metrics.streamFirstToken({
|
|
1423
1366
|
latencyMs,
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
retries,
|
|
1427
|
-
context: info.context
|
|
1367
|
+
error: error ? String(error) : void 0,
|
|
1368
|
+
context: this.context
|
|
1428
1369
|
});
|
|
1429
1370
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
// src/tools.ts
|
|
1445
|
-
function zodToJsonSchema(schema, options = {}) {
|
|
1446
|
-
const result = convertZodType(schema);
|
|
1447
|
-
if (options.includeSchema) {
|
|
1448
|
-
const schemaVersion = options.target === "draft-04" ? "http://json-schema.org/draft-04/schema#" : options.target === "draft-2019-09" ? "https://json-schema.org/draft/2019-09/schema" : options.target === "draft-2020-12" ? "https://json-schema.org/draft/2020-12/schema" : "http://json-schema.org/draft-07/schema#";
|
|
1449
|
-
return { $schema: schemaVersion, ...result };
|
|
1371
|
+
};
|
|
1372
|
+
var StructuredJSONStream = class {
|
|
1373
|
+
constructor(response, requestId, context, metrics, trace) {
|
|
1374
|
+
this.closed = false;
|
|
1375
|
+
this.sawTerminal = false;
|
|
1376
|
+
if (!response.body) {
|
|
1377
|
+
throw new ConfigError("streaming response is missing a body");
|
|
1378
|
+
}
|
|
1379
|
+
this.response = response;
|
|
1380
|
+
this.requestId = requestId;
|
|
1381
|
+
this.context = context;
|
|
1382
|
+
this.metrics = metrics;
|
|
1383
|
+
this.trace = trace;
|
|
1450
1384
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
return
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1385
|
+
async cancel(reason) {
|
|
1386
|
+
this.closed = true;
|
|
1387
|
+
try {
|
|
1388
|
+
await this.response.body?.cancel(reason);
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async *[Symbol.asyncIterator]() {
|
|
1393
|
+
if (this.closed) {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const body = this.response.body;
|
|
1397
|
+
if (!body) {
|
|
1398
|
+
throw new ConfigError("streaming response is missing a body");
|
|
1399
|
+
}
|
|
1400
|
+
const reader = body.getReader();
|
|
1401
|
+
const decoder = new TextDecoder();
|
|
1402
|
+
let buffer = "";
|
|
1403
|
+
try {
|
|
1404
|
+
while (true) {
|
|
1405
|
+
if (this.closed) {
|
|
1406
|
+
await reader.cancel();
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const { value, done } = await reader.read();
|
|
1410
|
+
if (done) {
|
|
1411
|
+
const { records: records2 } = consumeNDJSONBuffer(buffer, true);
|
|
1412
|
+
for (const line of records2) {
|
|
1413
|
+
const evt = this.parseRecord(line);
|
|
1414
|
+
if (evt) {
|
|
1415
|
+
this.traceStructuredEvent(evt, line);
|
|
1416
|
+
yield evt;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (!this.sawTerminal) {
|
|
1420
|
+
throw new TransportError(
|
|
1421
|
+
"structured stream ended without completion or error",
|
|
1422
|
+
{ kind: "request" }
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1428
|
+
const { records, remainder } = consumeNDJSONBuffer(buffer);
|
|
1429
|
+
buffer = remainder;
|
|
1430
|
+
for (const line of records) {
|
|
1431
|
+
const evt = this.parseRecord(line);
|
|
1432
|
+
if (evt) {
|
|
1433
|
+
this.traceStructuredEvent(evt, line);
|
|
1434
|
+
yield evt;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1481
1437
|
}
|
|
1482
|
-
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
this.trace?.streamError?.({ context: this.context, error: err });
|
|
1440
|
+
throw err;
|
|
1441
|
+
} finally {
|
|
1442
|
+
this.closed = true;
|
|
1443
|
+
reader.releaseLock();
|
|
1483
1444
|
}
|
|
1484
|
-
case "ZodNullable":
|
|
1485
|
-
return convertZodNullable(def);
|
|
1486
|
-
case "ZodDefault":
|
|
1487
|
-
return { ...convertZodType(def.innerType), default: def.defaultValue() };
|
|
1488
|
-
case "ZodEffects":
|
|
1489
|
-
return convertZodType(def.schema);
|
|
1490
|
-
case "ZodRecord":
|
|
1491
|
-
return convertZodRecord(def);
|
|
1492
|
-
case "ZodTuple":
|
|
1493
|
-
return convertZodTuple(def);
|
|
1494
|
-
case "ZodAny":
|
|
1495
|
-
case "ZodUnknown":
|
|
1496
|
-
return {};
|
|
1497
|
-
default:
|
|
1498
|
-
return {};
|
|
1499
1445
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
switch (check.kind) {
|
|
1507
|
-
case "min":
|
|
1508
|
-
result.minLength = check.value;
|
|
1509
|
-
break;
|
|
1510
|
-
case "max":
|
|
1511
|
-
result.maxLength = check.value;
|
|
1512
|
-
break;
|
|
1513
|
-
case "length":
|
|
1514
|
-
result.minLength = check.value;
|
|
1515
|
-
result.maxLength = check.value;
|
|
1516
|
-
break;
|
|
1517
|
-
case "email":
|
|
1518
|
-
result.format = "email";
|
|
1519
|
-
break;
|
|
1520
|
-
case "url":
|
|
1521
|
-
result.format = "uri";
|
|
1522
|
-
break;
|
|
1523
|
-
case "uuid":
|
|
1524
|
-
result.format = "uuid";
|
|
1525
|
-
break;
|
|
1526
|
-
case "datetime":
|
|
1527
|
-
result.format = "date-time";
|
|
1528
|
-
break;
|
|
1529
|
-
case "regex":
|
|
1530
|
-
result.pattern = check.value.source;
|
|
1531
|
-
break;
|
|
1446
|
+
async collect() {
|
|
1447
|
+
let last;
|
|
1448
|
+
for await (const evt of this) {
|
|
1449
|
+
last = evt;
|
|
1450
|
+
if (evt.type === "completion") {
|
|
1451
|
+
return evt.payload;
|
|
1532
1452
|
}
|
|
1533
1453
|
}
|
|
1454
|
+
throw new TransportError(
|
|
1455
|
+
"structured stream ended without completion or error",
|
|
1456
|
+
{ kind: "request" }
|
|
1457
|
+
);
|
|
1534
1458
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1459
|
+
parseRecord(line) {
|
|
1460
|
+
let parsed;
|
|
1461
|
+
try {
|
|
1462
|
+
parsed = JSON.parse(line);
|
|
1463
|
+
} catch (err) {
|
|
1464
|
+
throw new TransportError("invalid JSON in structured stream", {
|
|
1465
|
+
kind: "request",
|
|
1466
|
+
cause: err
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1470
|
+
throw new TransportError("structured stream record is not an object", {
|
|
1471
|
+
kind: "request"
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
const obj = parsed;
|
|
1475
|
+
const rawType = String(obj.type || "").trim().toLowerCase();
|
|
1476
|
+
if (!rawType) return null;
|
|
1477
|
+
if (rawType === "start") {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
if (rawType === "error") {
|
|
1481
|
+
this.sawTerminal = true;
|
|
1482
|
+
const status = typeof obj.status === "number" && obj.status > 0 ? obj.status : 500;
|
|
1483
|
+
const message = typeof obj.message === "string" && obj.message.trim() ? obj.message : "structured stream error";
|
|
1484
|
+
const code = typeof obj.code === "string" && obj.code.trim() ? obj.code : void 0;
|
|
1485
|
+
throw new APIError(message, {
|
|
1486
|
+
status,
|
|
1487
|
+
code,
|
|
1488
|
+
requestId: this.requestId
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
if (rawType !== "update" && rawType !== "completion") {
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
if (obj.payload === void 0 || obj.payload === null) {
|
|
1495
|
+
throw new TransportError(
|
|
1496
|
+
"structured stream record missing payload",
|
|
1497
|
+
{ kind: "request" }
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
if (rawType === "completion") {
|
|
1501
|
+
this.sawTerminal = true;
|
|
1502
|
+
}
|
|
1503
|
+
const event = {
|
|
1504
|
+
type: rawType,
|
|
1505
|
+
// biome-ignore lint/suspicious/noExplicitAny: payload is untyped json
|
|
1506
|
+
payload: obj.payload,
|
|
1507
|
+
requestId: this.requestId
|
|
1508
|
+
};
|
|
1509
|
+
return event;
|
|
1510
|
+
}
|
|
1511
|
+
traceStructuredEvent(evt, raw) {
|
|
1512
|
+
if (!this.trace?.streamEvent) return;
|
|
1513
|
+
const event = {
|
|
1514
|
+
type: "custom",
|
|
1515
|
+
event: "structured",
|
|
1516
|
+
data: { type: evt.type, payload: evt.payload },
|
|
1517
|
+
textDelta: void 0,
|
|
1518
|
+
toolCallDelta: void 0,
|
|
1519
|
+
toolCalls: void 0,
|
|
1520
|
+
responseId: void 0,
|
|
1521
|
+
model: void 0,
|
|
1522
|
+
stopReason: void 0,
|
|
1523
|
+
usage: void 0,
|
|
1524
|
+
requestId: this.requestId,
|
|
1525
|
+
raw
|
|
1526
|
+
};
|
|
1527
|
+
this.trace.streamEvent({ context: this.context, event });
|
|
1537
1528
|
}
|
|
1538
|
-
|
|
1529
|
+
};
|
|
1530
|
+
function consumeSSEBuffer(buffer, flush = false) {
|
|
1531
|
+
const events = [];
|
|
1532
|
+
let eventName = "";
|
|
1533
|
+
let dataLines = [];
|
|
1534
|
+
let remainder = "";
|
|
1535
|
+
const lines = buffer.split(/\r?\n/);
|
|
1536
|
+
const lastIndex = lines.length - 1;
|
|
1537
|
+
const limit = flush ? lines.length : Math.max(0, lastIndex);
|
|
1538
|
+
const pushEvent = () => {
|
|
1539
|
+
if (!eventName && dataLines.length === 0) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
events.push({
|
|
1543
|
+
event: eventName || "message",
|
|
1544
|
+
data: dataLines.join("\n")
|
|
1545
|
+
});
|
|
1546
|
+
eventName = "";
|
|
1547
|
+
dataLines = [];
|
|
1548
|
+
};
|
|
1549
|
+
for (let i = 0; i < limit; i++) {
|
|
1550
|
+
const line = lines[i];
|
|
1551
|
+
if (line === "") {
|
|
1552
|
+
pushEvent();
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
if (line.startsWith(":")) {
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (line.startsWith("event:")) {
|
|
1559
|
+
eventName = line.slice(6).trim();
|
|
1560
|
+
} else if (line.startsWith("data:")) {
|
|
1561
|
+
dataLines.push(line.slice(5).trimStart());
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
if (flush) {
|
|
1565
|
+
pushEvent();
|
|
1566
|
+
remainder = "";
|
|
1567
|
+
} else {
|
|
1568
|
+
remainder = lines[lastIndex] ?? "";
|
|
1569
|
+
}
|
|
1570
|
+
return { events, remainder };
|
|
1539
1571
|
}
|
|
1540
|
-
function
|
|
1541
|
-
const
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
result.maximum = check.value;
|
|
1561
|
-
}
|
|
1562
|
-
break;
|
|
1563
|
-
case "multipleOf":
|
|
1564
|
-
result.multipleOf = check.value;
|
|
1565
|
-
break;
|
|
1566
|
-
}
|
|
1572
|
+
function consumeNDJSONBuffer(buffer, flush = false) {
|
|
1573
|
+
const lines = buffer.split(/\r?\n/);
|
|
1574
|
+
const records = [];
|
|
1575
|
+
const lastIndex = lines.length - 1;
|
|
1576
|
+
const limit = flush ? lines.length : Math.max(0, lastIndex);
|
|
1577
|
+
for (let i = 0; i < limit; i++) {
|
|
1578
|
+
const line = lines[i]?.trim();
|
|
1579
|
+
if (!line) continue;
|
|
1580
|
+
records.push(line);
|
|
1581
|
+
}
|
|
1582
|
+
const remainder = flush ? "" : lines[lastIndex] ?? "";
|
|
1583
|
+
return { records, remainder };
|
|
1584
|
+
}
|
|
1585
|
+
function mapChatEvent(raw, requestId) {
|
|
1586
|
+
let parsed = raw.data;
|
|
1587
|
+
if (raw.data) {
|
|
1588
|
+
try {
|
|
1589
|
+
parsed = JSON.parse(raw.data);
|
|
1590
|
+
} catch {
|
|
1591
|
+
parsed = raw.data;
|
|
1567
1592
|
}
|
|
1568
1593
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1594
|
+
const payload = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1595
|
+
const p = payload;
|
|
1596
|
+
const type = normalizeEventType(raw.event, p);
|
|
1597
|
+
const usage = normalizeUsage(p.usage);
|
|
1598
|
+
const responseId = p.response_id || p.id || p?.message?.id;
|
|
1599
|
+
const model = normalizeModelId(p.model || p?.message?.model);
|
|
1600
|
+
const stopReason = normalizeStopReason(p.stop_reason);
|
|
1601
|
+
const textDelta = extractTextDelta(p);
|
|
1602
|
+
const toolCallDelta = extractToolCallDelta(p, type);
|
|
1603
|
+
const toolCalls = extractToolCalls(p, type);
|
|
1604
|
+
return {
|
|
1605
|
+
type,
|
|
1606
|
+
event: raw.event || type,
|
|
1607
|
+
data: p,
|
|
1608
|
+
textDelta,
|
|
1609
|
+
toolCallDelta,
|
|
1610
|
+
toolCalls,
|
|
1611
|
+
responseId,
|
|
1612
|
+
model,
|
|
1613
|
+
stopReason,
|
|
1614
|
+
usage,
|
|
1615
|
+
requestId,
|
|
1616
|
+
raw: raw.data || ""
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
function normalizeEventType(eventName, payload) {
|
|
1620
|
+
const hint = String(
|
|
1621
|
+
payload?.type || payload?.event || eventName || ""
|
|
1622
|
+
).trim();
|
|
1623
|
+
switch (hint) {
|
|
1624
|
+
case "message_start":
|
|
1625
|
+
return "message_start";
|
|
1626
|
+
case "message_delta":
|
|
1627
|
+
return "message_delta";
|
|
1628
|
+
case "message_stop":
|
|
1629
|
+
return "message_stop";
|
|
1630
|
+
case "tool_use_start":
|
|
1631
|
+
return "tool_use_start";
|
|
1632
|
+
case "tool_use_delta":
|
|
1633
|
+
return "tool_use_delta";
|
|
1634
|
+
case "tool_use_stop":
|
|
1635
|
+
return "tool_use_stop";
|
|
1636
|
+
case "ping":
|
|
1637
|
+
return "ping";
|
|
1638
|
+
default:
|
|
1639
|
+
return "custom";
|
|
1571
1640
|
}
|
|
1572
|
-
return result;
|
|
1573
1641
|
}
|
|
1574
|
-
function
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
items: convertZodType(def.type)
|
|
1578
|
-
};
|
|
1579
|
-
if (def.minLength !== void 0 && def.minLength !== null) {
|
|
1580
|
-
result.minItems = def.minLength.value;
|
|
1642
|
+
function extractTextDelta(payload) {
|
|
1643
|
+
if (!payload || typeof payload !== "object") {
|
|
1644
|
+
return void 0;
|
|
1581
1645
|
}
|
|
1582
|
-
if (
|
|
1583
|
-
|
|
1646
|
+
if (typeof payload.text_delta === "string" && payload.text_delta !== "") {
|
|
1647
|
+
return payload.text_delta;
|
|
1584
1648
|
}
|
|
1585
|
-
if (
|
|
1586
|
-
|
|
1649
|
+
if (typeof payload.delta === "string") {
|
|
1650
|
+
return payload.delta;
|
|
1587
1651
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const required = [];
|
|
1595
|
-
for (const [key, value] of Object.entries(shapeObj)) {
|
|
1596
|
-
properties[key] = convertZodType(value);
|
|
1597
|
-
const valueDef = value._def;
|
|
1598
|
-
const isOptional = valueDef.typeName === "ZodOptional" || valueDef.typeName === "ZodDefault" || valueDef.typeName === "ZodNullable" && valueDef.innerType?._def?.typeName === "ZodDefault";
|
|
1599
|
-
if (!isOptional) {
|
|
1600
|
-
required.push(key);
|
|
1652
|
+
if (payload.delta && typeof payload.delta === "object") {
|
|
1653
|
+
if (typeof payload.delta.text === "string") {
|
|
1654
|
+
return payload.delta.text;
|
|
1655
|
+
}
|
|
1656
|
+
if (typeof payload.delta.content === "string") {
|
|
1657
|
+
return payload.delta.content;
|
|
1601
1658
|
}
|
|
1602
1659
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
result.required = required;
|
|
1660
|
+
return void 0;
|
|
1661
|
+
}
|
|
1662
|
+
function extractToolCallDelta(payload, type) {
|
|
1663
|
+
if (!payload || typeof payload !== "object") {
|
|
1664
|
+
return void 0;
|
|
1609
1665
|
}
|
|
1610
|
-
if (
|
|
1611
|
-
|
|
1666
|
+
if (type !== "tool_use_start" && type !== "tool_use_delta") {
|
|
1667
|
+
return void 0;
|
|
1612
1668
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1669
|
+
if (payload.tool_call_delta) {
|
|
1670
|
+
const d = payload.tool_call_delta;
|
|
1671
|
+
return {
|
|
1672
|
+
index: d.index ?? 0,
|
|
1673
|
+
id: d.id,
|
|
1674
|
+
type: d.type,
|
|
1675
|
+
function: d.function ? {
|
|
1676
|
+
name: d.function.name,
|
|
1677
|
+
arguments: d.function.arguments
|
|
1678
|
+
} : void 0
|
|
1679
|
+
};
|
|
1616
1680
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1681
|
+
if (typeof payload.index === "number" || payload.id || payload.name) {
|
|
1682
|
+
return {
|
|
1683
|
+
index: payload.index ?? 0,
|
|
1684
|
+
id: payload.id,
|
|
1685
|
+
type: payload.tool_type,
|
|
1686
|
+
function: payload.name || payload.arguments ? {
|
|
1687
|
+
name: payload.name,
|
|
1688
|
+
arguments: payload.arguments
|
|
1689
|
+
} : void 0
|
|
1690
|
+
};
|
|
1626
1691
|
}
|
|
1627
|
-
return
|
|
1692
|
+
return void 0;
|
|
1628
1693
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
(v) => typeof v === "string" || typeof v === "number"
|
|
1633
|
-
);
|
|
1634
|
-
const result = { enum: values };
|
|
1635
|
-
if (def.description) {
|
|
1636
|
-
result.description = def.description;
|
|
1694
|
+
function extractToolCalls(payload, type) {
|
|
1695
|
+
if (!payload || typeof payload !== "object") {
|
|
1696
|
+
return void 0;
|
|
1637
1697
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
function convertZodUnion(def) {
|
|
1641
|
-
const options = def.options;
|
|
1642
|
-
const result = {
|
|
1643
|
-
anyOf: options.map(convertZodType)
|
|
1644
|
-
};
|
|
1645
|
-
if (def.description) {
|
|
1646
|
-
result.description = def.description;
|
|
1698
|
+
if (type !== "tool_use_stop" && type !== "message_stop") {
|
|
1699
|
+
return void 0;
|
|
1647
1700
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
function convertZodNullable(def) {
|
|
1651
|
-
const inner = convertZodType(def.innerType);
|
|
1652
|
-
return {
|
|
1653
|
-
anyOf: [inner, { type: "null" }]
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
function convertZodRecord(def) {
|
|
1657
|
-
const result = {
|
|
1658
|
-
type: "object",
|
|
1659
|
-
additionalProperties: convertZodType(def.valueType)
|
|
1660
|
-
};
|
|
1661
|
-
if (def.description) {
|
|
1662
|
-
result.description = def.description;
|
|
1701
|
+
if (payload.tool_calls?.length) {
|
|
1702
|
+
return normalizeToolCalls(payload.tool_calls);
|
|
1663
1703
|
}
|
|
1664
|
-
|
|
1704
|
+
if (payload.tool_call) {
|
|
1705
|
+
return normalizeToolCalls([payload.tool_call]);
|
|
1706
|
+
}
|
|
1707
|
+
return void 0;
|
|
1665
1708
|
}
|
|
1666
|
-
function
|
|
1667
|
-
const
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1709
|
+
function normalizeChatResponse(payload, requestId) {
|
|
1710
|
+
const p = payload;
|
|
1711
|
+
const response = {
|
|
1712
|
+
id: p?.id,
|
|
1713
|
+
content: Array.isArray(p?.content) ? p.content : p?.content ? [String(p.content)] : [],
|
|
1714
|
+
stopReason: normalizeStopReason(p?.stop_reason),
|
|
1715
|
+
model: normalizeModelId(p?.model),
|
|
1716
|
+
usage: normalizeUsage(p?.usage),
|
|
1717
|
+
requestId
|
|
1673
1718
|
};
|
|
1674
|
-
if (
|
|
1675
|
-
|
|
1719
|
+
if (p?.tool_calls?.length) {
|
|
1720
|
+
response.toolCalls = normalizeToolCalls(p.tool_calls);
|
|
1676
1721
|
}
|
|
1677
|
-
return
|
|
1722
|
+
return response;
|
|
1678
1723
|
}
|
|
1679
|
-
function
|
|
1680
|
-
|
|
1681
|
-
|
|
1724
|
+
function normalizeToolCalls(toolCalls) {
|
|
1725
|
+
return toolCalls.map(
|
|
1726
|
+
(tc) => createToolCall(
|
|
1727
|
+
tc.id,
|
|
1728
|
+
tc.function?.name ?? "",
|
|
1729
|
+
tc.function?.arguments ?? "",
|
|
1730
|
+
tc.type || ToolTypes.Function
|
|
1731
|
+
)
|
|
1732
|
+
);
|
|
1682
1733
|
}
|
|
1683
|
-
function
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
fn.parameters = parameters;
|
|
1734
|
+
function normalizeUsage(payload) {
|
|
1735
|
+
if (!payload) {
|
|
1736
|
+
return createUsage(0, 0, 0);
|
|
1687
1737
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1738
|
+
const inputTokens = Number(payload.input_tokens ?? 0);
|
|
1739
|
+
const outputTokens = Number(payload.output_tokens ?? 0);
|
|
1740
|
+
const totalTokens = Number(payload.total_tokens ?? 0);
|
|
1741
|
+
return createUsage(inputTokens, outputTokens, totalTokens || void 0);
|
|
1692
1742
|
}
|
|
1693
|
-
function
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
allowedDomains: options.allowedDomains,
|
|
1698
|
-
excludedDomains: options.excludedDomains,
|
|
1699
|
-
maxUses: options.maxUses
|
|
1700
|
-
} : void 0
|
|
1743
|
+
function buildProxyBody(params, metadata) {
|
|
1744
|
+
const modelValue = params.model ? modelToString(params.model).trim() : "";
|
|
1745
|
+
const body = {
|
|
1746
|
+
messages: normalizeMessages(params.messages)
|
|
1701
1747
|
};
|
|
1748
|
+
if (modelValue) {
|
|
1749
|
+
body.model = modelValue;
|
|
1750
|
+
}
|
|
1751
|
+
if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
|
|
1752
|
+
if (typeof params.temperature === "number")
|
|
1753
|
+
body.temperature = params.temperature;
|
|
1754
|
+
if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
|
|
1755
|
+
if (params.stop?.length) body.stop = params.stop;
|
|
1756
|
+
if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
|
|
1757
|
+
if (params.tools?.length) body.tools = normalizeTools(params.tools);
|
|
1758
|
+
if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
|
|
1759
|
+
if (params.responseFormat) body.response_format = params.responseFormat;
|
|
1760
|
+
return body;
|
|
1702
1761
|
}
|
|
1703
|
-
function
|
|
1704
|
-
return
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1762
|
+
function normalizeMessages(messages) {
|
|
1763
|
+
return messages.map((msg) => {
|
|
1764
|
+
const normalized = {
|
|
1765
|
+
role: msg.role || "user",
|
|
1766
|
+
content: msg.content
|
|
1767
|
+
};
|
|
1768
|
+
if (msg.toolCalls?.length) {
|
|
1769
|
+
normalized.tool_calls = msg.toolCalls.map((tc) => ({
|
|
1770
|
+
id: tc.id,
|
|
1771
|
+
type: tc.type,
|
|
1772
|
+
function: tc.function ? createFunctionCall(tc.function.name, tc.function.arguments) : void 0
|
|
1773
|
+
}));
|
|
1774
|
+
}
|
|
1775
|
+
if (msg.toolCallId) {
|
|
1776
|
+
normalized.tool_call_id = msg.toolCallId;
|
|
1777
|
+
}
|
|
1778
|
+
return normalized;
|
|
1779
|
+
});
|
|
1708
1780
|
}
|
|
1709
|
-
function
|
|
1710
|
-
return
|
|
1781
|
+
function normalizeTools(tools) {
|
|
1782
|
+
return tools.map((tool) => {
|
|
1783
|
+
const normalized = { type: tool.type };
|
|
1784
|
+
if (tool.function) {
|
|
1785
|
+
normalized.function = {
|
|
1786
|
+
name: tool.function.name,
|
|
1787
|
+
description: tool.function.description,
|
|
1788
|
+
parameters: tool.function.parameters
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
if (tool.web) {
|
|
1792
|
+
const web = {
|
|
1793
|
+
allowed_domains: tool.web.allowedDomains,
|
|
1794
|
+
excluded_domains: tool.web.excludedDomains,
|
|
1795
|
+
max_uses: tool.web.maxUses
|
|
1796
|
+
};
|
|
1797
|
+
if (tool.web.mode) {
|
|
1798
|
+
web.mode = tool.web.mode;
|
|
1799
|
+
}
|
|
1800
|
+
normalized.web = web;
|
|
1801
|
+
}
|
|
1802
|
+
if (tool.xSearch) {
|
|
1803
|
+
normalized.x_search = {
|
|
1804
|
+
allowed_handles: tool.xSearch.allowedHandles,
|
|
1805
|
+
excluded_handles: tool.xSearch.excludedHandles,
|
|
1806
|
+
from_date: tool.xSearch.fromDate,
|
|
1807
|
+
to_date: tool.xSearch.toDate
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
if (tool.codeExecution) {
|
|
1811
|
+
normalized.code_execution = {
|
|
1812
|
+
language: tool.codeExecution.language,
|
|
1813
|
+
timeout_ms: tool.codeExecution.timeoutMs
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
return normalized;
|
|
1817
|
+
});
|
|
1711
1818
|
}
|
|
1712
|
-
function
|
|
1713
|
-
return
|
|
1819
|
+
function normalizeToolChoice(tc) {
|
|
1820
|
+
return { type: tc.type };
|
|
1714
1821
|
}
|
|
1715
|
-
function
|
|
1716
|
-
return
|
|
1822
|
+
function requestIdFromHeaders(headers) {
|
|
1823
|
+
return headers.get(REQUEST_ID_HEADER) || headers.get("X-Request-Id") || void 0;
|
|
1717
1824
|
}
|
|
1718
|
-
function
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1825
|
+
function mergeMetadata(...sources) {
|
|
1826
|
+
const merged = {};
|
|
1827
|
+
for (const src of sources) {
|
|
1828
|
+
if (!src) continue;
|
|
1829
|
+
for (const [key, value] of Object.entries(src)) {
|
|
1830
|
+
const k = key?.trim();
|
|
1831
|
+
const v = value?.trim();
|
|
1832
|
+
if (!k || !v) continue;
|
|
1833
|
+
merged[k] = v;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return Object.keys(merged).length ? merged : void 0;
|
|
1725
1837
|
}
|
|
1726
|
-
function
|
|
1727
|
-
return
|
|
1838
|
+
function hasUserMessage(messages) {
|
|
1839
|
+
return messages.some(
|
|
1840
|
+
(msg) => msg.role?.toLowerCase?.() === "user" && !!msg.content
|
|
1841
|
+
);
|
|
1728
1842
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
};
|
|
1843
|
+
|
|
1844
|
+
// src/customers.ts
|
|
1845
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1846
|
+
function isValidEmail(email) {
|
|
1847
|
+
return EMAIL_REGEX.test(email);
|
|
1735
1848
|
}
|
|
1736
|
-
var
|
|
1737
|
-
constructor() {
|
|
1738
|
-
this.
|
|
1849
|
+
var CustomersClient = class {
|
|
1850
|
+
constructor(http, cfg) {
|
|
1851
|
+
this.http = http;
|
|
1852
|
+
this.apiKey = cfg.apiKey;
|
|
1853
|
+
}
|
|
1854
|
+
ensureSecretKey() {
|
|
1855
|
+
if (!this.apiKey || !this.apiKey.startsWith("mr_sk_")) {
|
|
1856
|
+
throw new ConfigError(
|
|
1857
|
+
"Secret key (mr_sk_*) required for customer operations"
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1739
1860
|
}
|
|
1740
1861
|
/**
|
|
1741
|
-
*
|
|
1742
|
-
* Returns true if this started a new tool call.
|
|
1862
|
+
* List all customers in the project.
|
|
1743
1863
|
*/
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1864
|
+
async list() {
|
|
1865
|
+
this.ensureSecretKey();
|
|
1866
|
+
const response = await this.http.json("/customers", {
|
|
1867
|
+
method: "GET",
|
|
1868
|
+
apiKey: this.apiKey
|
|
1869
|
+
});
|
|
1870
|
+
return response.customers;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Create a new customer in the project.
|
|
1874
|
+
*/
|
|
1875
|
+
async create(request) {
|
|
1876
|
+
this.ensureSecretKey();
|
|
1877
|
+
if (!request.tier_id?.trim()) {
|
|
1878
|
+
throw new ConfigError("tier_id is required");
|
|
1756
1879
|
}
|
|
1757
|
-
if (
|
|
1758
|
-
|
|
1759
|
-
existing.function = existing.function ?? { name: "", arguments: "" };
|
|
1760
|
-
existing.function.name = delta.function.name;
|
|
1761
|
-
}
|
|
1762
|
-
if (delta.function.arguments) {
|
|
1763
|
-
existing.function = existing.function ?? { name: "", arguments: "" };
|
|
1764
|
-
existing.function.arguments += delta.function.arguments;
|
|
1765
|
-
}
|
|
1880
|
+
if (!request.external_id?.trim()) {
|
|
1881
|
+
throw new ConfigError("external_id is required");
|
|
1766
1882
|
}
|
|
1767
|
-
|
|
1883
|
+
if (!request.email?.trim()) {
|
|
1884
|
+
throw new ConfigError("email is required");
|
|
1885
|
+
}
|
|
1886
|
+
if (!isValidEmail(request.email)) {
|
|
1887
|
+
throw new ConfigError("invalid email format");
|
|
1888
|
+
}
|
|
1889
|
+
const response = await this.http.json("/customers", {
|
|
1890
|
+
method: "POST",
|
|
1891
|
+
body: request,
|
|
1892
|
+
apiKey: this.apiKey
|
|
1893
|
+
});
|
|
1894
|
+
return response.customer;
|
|
1768
1895
|
}
|
|
1769
1896
|
/**
|
|
1770
|
-
*
|
|
1897
|
+
* Get a customer by ID.
|
|
1771
1898
|
*/
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1899
|
+
async get(customerId) {
|
|
1900
|
+
this.ensureSecretKey();
|
|
1901
|
+
if (!customerId?.trim()) {
|
|
1902
|
+
throw new ConfigError("customerId is required");
|
|
1775
1903
|
}
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
result.push(call);
|
|
1904
|
+
const response = await this.http.json(
|
|
1905
|
+
`/customers/${customerId}`,
|
|
1906
|
+
{
|
|
1907
|
+
method: "GET",
|
|
1908
|
+
apiKey: this.apiKey
|
|
1782
1909
|
}
|
|
1783
|
-
|
|
1784
|
-
return
|
|
1910
|
+
);
|
|
1911
|
+
return response.customer;
|
|
1785
1912
|
}
|
|
1786
1913
|
/**
|
|
1787
|
-
*
|
|
1914
|
+
* Upsert a customer by external_id.
|
|
1915
|
+
* If a customer with the given external_id exists, it is updated.
|
|
1916
|
+
* Otherwise, a new customer is created.
|
|
1788
1917
|
*/
|
|
1789
|
-
|
|
1790
|
-
|
|
1918
|
+
async upsert(request) {
|
|
1919
|
+
this.ensureSecretKey();
|
|
1920
|
+
if (!request.tier_id?.trim()) {
|
|
1921
|
+
throw new ConfigError("tier_id is required");
|
|
1922
|
+
}
|
|
1923
|
+
if (!request.external_id?.trim()) {
|
|
1924
|
+
throw new ConfigError("external_id is required");
|
|
1925
|
+
}
|
|
1926
|
+
if (!request.email?.trim()) {
|
|
1927
|
+
throw new ConfigError("email is required");
|
|
1928
|
+
}
|
|
1929
|
+
if (!isValidEmail(request.email)) {
|
|
1930
|
+
throw new ConfigError("invalid email format");
|
|
1931
|
+
}
|
|
1932
|
+
const response = await this.http.json("/customers", {
|
|
1933
|
+
method: "PUT",
|
|
1934
|
+
body: request,
|
|
1935
|
+
apiKey: this.apiKey
|
|
1936
|
+
});
|
|
1937
|
+
return response.customer;
|
|
1791
1938
|
}
|
|
1792
1939
|
/**
|
|
1793
|
-
*
|
|
1940
|
+
* Claim a customer by email, setting their external_id.
|
|
1941
|
+
* Used when a customer subscribes via Stripe Checkout (email only) and later
|
|
1942
|
+
* authenticates to the app, needing to link their identity.
|
|
1943
|
+
*
|
|
1944
|
+
* @throws {APIError} with status 404 if customer not found by email
|
|
1945
|
+
* @throws {APIError} with status 409 if customer already claimed or external_id in use
|
|
1794
1946
|
*/
|
|
1795
|
-
|
|
1796
|
-
this.
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
var ToolArgsError = class extends Error {
|
|
1800
|
-
constructor(message, toolCallId, toolName, rawArguments) {
|
|
1801
|
-
super(message);
|
|
1802
|
-
this.name = "ToolArgsError";
|
|
1803
|
-
this.toolCallId = toolCallId;
|
|
1804
|
-
this.toolName = toolName;
|
|
1805
|
-
this.rawArguments = rawArguments;
|
|
1806
|
-
}
|
|
1807
|
-
};
|
|
1808
|
-
function parseToolArgs(call, schema) {
|
|
1809
|
-
const toolName = call.function?.name ?? "unknown";
|
|
1810
|
-
const rawArgs = call.function?.arguments ?? "";
|
|
1811
|
-
let parsed;
|
|
1812
|
-
try {
|
|
1813
|
-
parsed = rawArgs ? JSON.parse(rawArgs) : {};
|
|
1814
|
-
} catch (err) {
|
|
1815
|
-
const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
|
|
1816
|
-
throw new ToolArgsError(
|
|
1817
|
-
`Failed to parse arguments for tool '${toolName}': ${message}`,
|
|
1818
|
-
call.id,
|
|
1819
|
-
toolName,
|
|
1820
|
-
rawArgs
|
|
1821
|
-
);
|
|
1822
|
-
}
|
|
1823
|
-
try {
|
|
1824
|
-
return schema.parse(parsed);
|
|
1825
|
-
} catch (err) {
|
|
1826
|
-
let message;
|
|
1827
|
-
if (err instanceof Error) {
|
|
1828
|
-
const zodErr = err;
|
|
1829
|
-
if (zodErr.errors && Array.isArray(zodErr.errors)) {
|
|
1830
|
-
const issues = zodErr.errors.map((e) => {
|
|
1831
|
-
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
1832
|
-
return `${path}${e.message}`;
|
|
1833
|
-
}).join("; ");
|
|
1834
|
-
message = issues;
|
|
1835
|
-
} else {
|
|
1836
|
-
message = err.message;
|
|
1837
|
-
}
|
|
1838
|
-
} else {
|
|
1839
|
-
message = String(err);
|
|
1947
|
+
async claim(request) {
|
|
1948
|
+
this.ensureSecretKey();
|
|
1949
|
+
if (!request.email?.trim()) {
|
|
1950
|
+
throw new ConfigError("email is required");
|
|
1840
1951
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
call.id,
|
|
1844
|
-
toolName,
|
|
1845
|
-
rawArgs
|
|
1846
|
-
);
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
function tryParseToolArgs(call, schema) {
|
|
1850
|
-
try {
|
|
1851
|
-
const data = parseToolArgs(call, schema);
|
|
1852
|
-
return { success: true, data };
|
|
1853
|
-
} catch (err) {
|
|
1854
|
-
if (err instanceof ToolArgsError) {
|
|
1855
|
-
return { success: false, error: err };
|
|
1952
|
+
if (!isValidEmail(request.email)) {
|
|
1953
|
+
throw new ConfigError("invalid email format");
|
|
1856
1954
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
)
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
function parseToolArgsRaw(call) {
|
|
1871
|
-
const toolName = call.function?.name ?? "unknown";
|
|
1872
|
-
const rawArgs = call.function?.arguments ?? "";
|
|
1873
|
-
try {
|
|
1874
|
-
return rawArgs ? JSON.parse(rawArgs) : {};
|
|
1875
|
-
} catch (err) {
|
|
1876
|
-
const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
|
|
1877
|
-
throw new ToolArgsError(
|
|
1878
|
-
`Failed to parse arguments for tool '${toolName}': ${message}`,
|
|
1879
|
-
call.id,
|
|
1880
|
-
toolName,
|
|
1881
|
-
rawArgs
|
|
1882
|
-
);
|
|
1955
|
+
if (!request.external_id?.trim()) {
|
|
1956
|
+
throw new ConfigError("external_id is required");
|
|
1957
|
+
}
|
|
1958
|
+
const response = await this.http.json("/customers/claim", {
|
|
1959
|
+
method: "POST",
|
|
1960
|
+
body: request,
|
|
1961
|
+
apiKey: this.apiKey
|
|
1962
|
+
});
|
|
1963
|
+
return response.customer;
|
|
1883
1964
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1965
|
+
/**
|
|
1966
|
+
* Delete a customer by ID.
|
|
1967
|
+
*/
|
|
1968
|
+
async delete(customerId) {
|
|
1969
|
+
this.ensureSecretKey();
|
|
1970
|
+
if (!customerId?.trim()) {
|
|
1971
|
+
throw new ConfigError("customerId is required");
|
|
1972
|
+
}
|
|
1973
|
+
await this.http.request(`/customers/${customerId}`, {
|
|
1974
|
+
method: "DELETE",
|
|
1975
|
+
apiKey: this.apiKey
|
|
1976
|
+
});
|
|
1888
1977
|
}
|
|
1889
1978
|
/**
|
|
1890
|
-
*
|
|
1891
|
-
* @param name - The tool name (must match the function name in the tool definition)
|
|
1892
|
-
* @param handler - Function to execute when this tool is called
|
|
1893
|
-
* @returns this for chaining
|
|
1979
|
+
* Create a Stripe checkout session for a customer.
|
|
1894
1980
|
*/
|
|
1895
|
-
|
|
1896
|
-
this.
|
|
1897
|
-
|
|
1981
|
+
async createCheckoutSession(customerId, request) {
|
|
1982
|
+
this.ensureSecretKey();
|
|
1983
|
+
if (!customerId?.trim()) {
|
|
1984
|
+
throw new ConfigError("customerId is required");
|
|
1985
|
+
}
|
|
1986
|
+
if (!request.success_url?.trim() || !request.cancel_url?.trim()) {
|
|
1987
|
+
throw new ConfigError("success_url and cancel_url are required");
|
|
1988
|
+
}
|
|
1989
|
+
return await this.http.json(
|
|
1990
|
+
`/customers/${customerId}/checkout`,
|
|
1991
|
+
{
|
|
1992
|
+
method: "POST",
|
|
1993
|
+
body: request,
|
|
1994
|
+
apiKey: this.apiKey
|
|
1995
|
+
}
|
|
1996
|
+
);
|
|
1898
1997
|
}
|
|
1899
1998
|
/**
|
|
1900
|
-
*
|
|
1901
|
-
* @param name - The tool name to unregister
|
|
1902
|
-
* @returns true if the handler was removed, false if it didn't exist
|
|
1999
|
+
* Get the subscription status for a customer.
|
|
1903
2000
|
*/
|
|
1904
|
-
|
|
1905
|
-
|
|
2001
|
+
async getSubscription(customerId) {
|
|
2002
|
+
this.ensureSecretKey();
|
|
2003
|
+
if (!customerId?.trim()) {
|
|
2004
|
+
throw new ConfigError("customerId is required");
|
|
2005
|
+
}
|
|
2006
|
+
return await this.http.json(
|
|
2007
|
+
`/customers/${customerId}/subscription`,
|
|
2008
|
+
{
|
|
2009
|
+
method: "GET",
|
|
2010
|
+
apiKey: this.apiKey
|
|
2011
|
+
}
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
|
|
2016
|
+
// src/tiers.ts
|
|
2017
|
+
var TiersClient = class {
|
|
2018
|
+
constructor(http, cfg) {
|
|
2019
|
+
this.http = http;
|
|
2020
|
+
this.apiKey = cfg.apiKey;
|
|
2021
|
+
}
|
|
2022
|
+
ensureApiKey() {
|
|
2023
|
+
if (!this.apiKey || !this.apiKey.startsWith("mr_pk_") && !this.apiKey.startsWith("mr_sk_")) {
|
|
2024
|
+
throw new ConfigError(
|
|
2025
|
+
"API key (mr_pk_* or mr_sk_*) required for tier operations"
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
1906
2028
|
}
|
|
1907
2029
|
/**
|
|
1908
|
-
*
|
|
2030
|
+
* List all tiers in the project.
|
|
1909
2031
|
*/
|
|
1910
|
-
|
|
1911
|
-
|
|
2032
|
+
async list() {
|
|
2033
|
+
this.ensureApiKey();
|
|
2034
|
+
const response = await this.http.json("/tiers", {
|
|
2035
|
+
method: "GET",
|
|
2036
|
+
apiKey: this.apiKey
|
|
2037
|
+
});
|
|
2038
|
+
return response.tiers;
|
|
1912
2039
|
}
|
|
1913
2040
|
/**
|
|
1914
|
-
*
|
|
2041
|
+
* Get a tier by ID.
|
|
1915
2042
|
*/
|
|
1916
|
-
|
|
1917
|
-
|
|
2043
|
+
async get(tierId) {
|
|
2044
|
+
this.ensureApiKey();
|
|
2045
|
+
if (!tierId?.trim()) {
|
|
2046
|
+
throw new ConfigError("tierId is required");
|
|
2047
|
+
}
|
|
2048
|
+
const response = await this.http.json(`/tiers/${tierId}`, {
|
|
2049
|
+
method: "GET",
|
|
2050
|
+
apiKey: this.apiKey
|
|
2051
|
+
});
|
|
2052
|
+
return response.tier;
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
// src/http.ts
|
|
2057
|
+
var HTTPClient = class {
|
|
2058
|
+
constructor(cfg) {
|
|
2059
|
+
const resolvedBase = normalizeBaseUrl(cfg.baseUrl || DEFAULT_BASE_URL);
|
|
2060
|
+
if (!isValidHttpUrl(resolvedBase)) {
|
|
2061
|
+
throw new ConfigError(
|
|
2062
|
+
"baseUrl must start with http:// or https://"
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
this.baseUrl = resolvedBase;
|
|
2066
|
+
this.apiKey = cfg.apiKey?.trim();
|
|
2067
|
+
this.accessToken = cfg.accessToken?.trim();
|
|
2068
|
+
this.fetchImpl = cfg.fetchImpl;
|
|
2069
|
+
this.clientHeader = cfg.clientHeader?.trim() || DEFAULT_CLIENT_HEADER;
|
|
2070
|
+
this.defaultConnectTimeoutMs = cfg.connectTimeoutMs === void 0 ? DEFAULT_CONNECT_TIMEOUT_MS : Math.max(0, cfg.connectTimeoutMs);
|
|
2071
|
+
this.defaultTimeoutMs = cfg.timeoutMs === void 0 ? DEFAULT_REQUEST_TIMEOUT_MS : Math.max(0, cfg.timeoutMs);
|
|
2072
|
+
this.retry = normalizeRetryConfig(cfg.retry);
|
|
2073
|
+
this.defaultHeaders = normalizeHeaders(cfg.defaultHeaders);
|
|
2074
|
+
this.metrics = cfg.metrics;
|
|
2075
|
+
this.trace = cfg.trace;
|
|
2076
|
+
}
|
|
2077
|
+
async request(path, options = {}) {
|
|
2078
|
+
const fetchFn = this.fetchImpl ?? globalThis.fetch;
|
|
2079
|
+
if (!fetchFn) {
|
|
2080
|
+
throw new ConfigError(
|
|
2081
|
+
"fetch is not available; provide a fetch implementation"
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
const method = options.method || "GET";
|
|
2085
|
+
const url = buildUrl(this.baseUrl, path);
|
|
2086
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
2087
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
2088
|
+
const context = {
|
|
2089
|
+
method,
|
|
2090
|
+
path,
|
|
2091
|
+
...options.context || {}
|
|
2092
|
+
};
|
|
2093
|
+
trace?.requestStart?.(context);
|
|
2094
|
+
const start = metrics?.httpRequest || trace?.requestFinish ? Date.now() : 0;
|
|
2095
|
+
const headers = new Headers({
|
|
2096
|
+
...this.defaultHeaders,
|
|
2097
|
+
...options.headers || {}
|
|
2098
|
+
});
|
|
2099
|
+
const accepts = options.accept || (options.raw ? void 0 : "application/json");
|
|
2100
|
+
if (accepts && !headers.has("Accept")) {
|
|
2101
|
+
headers.set("Accept", accepts);
|
|
2102
|
+
}
|
|
2103
|
+
const body = options.body;
|
|
2104
|
+
const shouldEncodeJSON = body !== void 0 && body !== null && typeof body === "object" && !(body instanceof FormData) && !(body instanceof Blob);
|
|
2105
|
+
const payload = shouldEncodeJSON ? JSON.stringify(body) : body;
|
|
2106
|
+
if (shouldEncodeJSON && !headers.has("Content-Type")) {
|
|
2107
|
+
headers.set("Content-Type", "application/json");
|
|
2108
|
+
}
|
|
2109
|
+
const accessToken = options.accessToken ?? this.accessToken;
|
|
2110
|
+
if (accessToken) {
|
|
2111
|
+
const bearer = accessToken.toLowerCase().startsWith("bearer ") ? accessToken : `Bearer ${accessToken}`;
|
|
2112
|
+
headers.set("Authorization", bearer);
|
|
2113
|
+
}
|
|
2114
|
+
const apiKey = options.apiKey ?? this.apiKey;
|
|
2115
|
+
if (apiKey) {
|
|
2116
|
+
headers.set("X-ModelRelay-Api-Key", apiKey);
|
|
2117
|
+
}
|
|
2118
|
+
if (this.clientHeader && !headers.has("X-ModelRelay-Client")) {
|
|
2119
|
+
headers.set("X-ModelRelay-Client", this.clientHeader);
|
|
2120
|
+
}
|
|
2121
|
+
const timeoutMs = options.useDefaultTimeout === false ? options.timeoutMs : options.timeoutMs ?? this.defaultTimeoutMs;
|
|
2122
|
+
const connectTimeoutMs = options.useDefaultConnectTimeout === false ? options.connectTimeoutMs : options.connectTimeoutMs ?? this.defaultConnectTimeoutMs;
|
|
2123
|
+
const retryCfg = normalizeRetryConfig(
|
|
2124
|
+
options.retry === void 0 ? this.retry : options.retry
|
|
2125
|
+
);
|
|
2126
|
+
const attempts = retryCfg ? Math.max(1, retryCfg.maxAttempts) : 1;
|
|
2127
|
+
let lastError;
|
|
2128
|
+
let lastStatus;
|
|
2129
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
2130
|
+
let connectTimedOut = false;
|
|
2131
|
+
let requestTimedOut = false;
|
|
2132
|
+
const connectController = connectTimeoutMs && connectTimeoutMs > 0 ? new AbortController() : void 0;
|
|
2133
|
+
const requestController = timeoutMs && timeoutMs > 0 ? new AbortController() : void 0;
|
|
2134
|
+
const signal = mergeSignals(
|
|
2135
|
+
options.signal,
|
|
2136
|
+
connectController?.signal,
|
|
2137
|
+
requestController?.signal
|
|
2138
|
+
);
|
|
2139
|
+
const connectTimer = connectController && setTimeout(() => {
|
|
2140
|
+
connectTimedOut = true;
|
|
2141
|
+
connectController.abort(
|
|
2142
|
+
new DOMException("connect timeout", "AbortError")
|
|
2143
|
+
);
|
|
2144
|
+
}, connectTimeoutMs);
|
|
2145
|
+
const requestTimer = requestController && setTimeout(() => {
|
|
2146
|
+
requestTimedOut = true;
|
|
2147
|
+
requestController.abort(
|
|
2148
|
+
new DOMException("timeout", "AbortError")
|
|
2149
|
+
);
|
|
2150
|
+
}, timeoutMs);
|
|
2151
|
+
try {
|
|
2152
|
+
const response = await fetchFn(url, {
|
|
2153
|
+
method,
|
|
2154
|
+
headers,
|
|
2155
|
+
body: payload,
|
|
2156
|
+
signal
|
|
2157
|
+
});
|
|
2158
|
+
if (connectTimer) {
|
|
2159
|
+
clearTimeout(connectTimer);
|
|
2160
|
+
}
|
|
2161
|
+
if (!response.ok) {
|
|
2162
|
+
const shouldRetry = retryCfg && shouldRetryStatus(
|
|
2163
|
+
response.status,
|
|
2164
|
+
method,
|
|
2165
|
+
retryCfg.retryPost
|
|
2166
|
+
) && attempt < attempts;
|
|
2167
|
+
if (shouldRetry) {
|
|
2168
|
+
lastStatus = response.status;
|
|
2169
|
+
await backoff(attempt, retryCfg);
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
const retries = buildRetryMetadata(attempt, response.status, lastError);
|
|
2173
|
+
const finishedCtx2 = withRequestId(context, response.headers);
|
|
2174
|
+
recordHttpMetrics(metrics, trace, start, retries, {
|
|
2175
|
+
status: response.status,
|
|
2176
|
+
context: finishedCtx2
|
|
2177
|
+
});
|
|
2178
|
+
throw options.raw ? await parseErrorResponse(response, retries) : await parseErrorResponse(response, retries);
|
|
2179
|
+
}
|
|
2180
|
+
const finishedCtx = withRequestId(context, response.headers);
|
|
2181
|
+
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
2182
|
+
status: response.status,
|
|
2183
|
+
context: finishedCtx
|
|
2184
|
+
});
|
|
2185
|
+
return response;
|
|
2186
|
+
} catch (err) {
|
|
2187
|
+
if (options.signal?.aborted) {
|
|
2188
|
+
throw err;
|
|
2189
|
+
}
|
|
2190
|
+
if (err instanceof ModelRelayError) {
|
|
2191
|
+
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
2192
|
+
error: err,
|
|
2193
|
+
context
|
|
2194
|
+
});
|
|
2195
|
+
throw err;
|
|
2196
|
+
}
|
|
2197
|
+
const transportKind = classifyTransportErrorKind(
|
|
2198
|
+
err,
|
|
2199
|
+
connectTimedOut,
|
|
2200
|
+
requestTimedOut
|
|
2201
|
+
);
|
|
2202
|
+
const shouldRetry = retryCfg && isRetryableError(err, transportKind) && (method !== "POST" || retryCfg.retryPost) && attempt < attempts;
|
|
2203
|
+
if (!shouldRetry) {
|
|
2204
|
+
const retries = buildRetryMetadata(
|
|
2205
|
+
attempt,
|
|
2206
|
+
lastStatus,
|
|
2207
|
+
err instanceof Error ? err.message : String(err)
|
|
2208
|
+
);
|
|
2209
|
+
recordHttpMetrics(metrics, trace, start, retries, {
|
|
2210
|
+
error: err,
|
|
2211
|
+
context
|
|
2212
|
+
});
|
|
2213
|
+
throw toTransportError(err, transportKind, retries);
|
|
2214
|
+
}
|
|
2215
|
+
lastError = err;
|
|
2216
|
+
await backoff(attempt, retryCfg);
|
|
2217
|
+
} finally {
|
|
2218
|
+
if (connectTimer) {
|
|
2219
|
+
clearTimeout(connectTimer);
|
|
2220
|
+
}
|
|
2221
|
+
if (requestTimer) {
|
|
2222
|
+
clearTimeout(requestTimer);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
throw lastError instanceof Error ? lastError : new TransportError("request failed", {
|
|
2227
|
+
kind: "other",
|
|
2228
|
+
retries: buildRetryMetadata(attempts, lastStatus)
|
|
2229
|
+
});
|
|
1918
2230
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
if (!handler) {
|
|
1928
|
-
return {
|
|
1929
|
-
toolCallId: call.id,
|
|
1930
|
-
toolName,
|
|
1931
|
-
result: null,
|
|
1932
|
-
error: `Unknown tool: '${toolName}'. Available tools: ${this.getRegisteredTools().join(", ") || "none"}`
|
|
1933
|
-
};
|
|
2231
|
+
async json(path, options = {}) {
|
|
2232
|
+
const response = await this.request(path, {
|
|
2233
|
+
...options,
|
|
2234
|
+
raw: true,
|
|
2235
|
+
accept: options.accept || "application/json"
|
|
2236
|
+
});
|
|
2237
|
+
if (!response.ok) {
|
|
2238
|
+
throw await parseErrorResponse(response);
|
|
1934
2239
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
args = call.function?.arguments ? JSON.parse(call.function.arguments) : {};
|
|
1938
|
-
} catch (err) {
|
|
1939
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1940
|
-
return {
|
|
1941
|
-
toolCallId: call.id,
|
|
1942
|
-
toolName,
|
|
1943
|
-
result: null,
|
|
1944
|
-
error: `Invalid JSON in arguments: ${errorMessage}`,
|
|
1945
|
-
isRetryable: true
|
|
1946
|
-
};
|
|
2240
|
+
if (response.status === 204) {
|
|
2241
|
+
return void 0;
|
|
1947
2242
|
}
|
|
1948
2243
|
try {
|
|
1949
|
-
|
|
1950
|
-
return {
|
|
1951
|
-
toolCallId: call.id,
|
|
1952
|
-
toolName,
|
|
1953
|
-
result
|
|
1954
|
-
};
|
|
2244
|
+
return await response.json();
|
|
1955
2245
|
} catch (err) {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
toolName,
|
|
1961
|
-
result: null,
|
|
1962
|
-
error: errorMessage,
|
|
1963
|
-
isRetryable
|
|
1964
|
-
};
|
|
2246
|
+
throw new APIError("failed to parse response JSON", {
|
|
2247
|
+
status: response.status,
|
|
2248
|
+
data: err
|
|
2249
|
+
});
|
|
1965
2250
|
}
|
|
1966
2251
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
*/
|
|
1972
|
-
async executeAll(calls) {
|
|
1973
|
-
return Promise.all(calls.map((call) => this.execute(call)));
|
|
2252
|
+
};
|
|
2253
|
+
function buildUrl(baseUrl, path) {
|
|
2254
|
+
if (/^https?:\/\//i.test(path)) {
|
|
2255
|
+
return path;
|
|
1974
2256
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
* Useful for appending to the conversation history.
|
|
1978
|
-
* @param results - Array of execution results
|
|
1979
|
-
* @returns Array of ChatMessage objects with role "tool"
|
|
1980
|
-
*/
|
|
1981
|
-
resultsToMessages(results) {
|
|
1982
|
-
return results.map((r) => {
|
|
1983
|
-
const content = r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result);
|
|
1984
|
-
return toolResultMessage(r.toolCallId, content);
|
|
1985
|
-
});
|
|
2257
|
+
if (!path.startsWith("/")) {
|
|
2258
|
+
path = `/${path}`;
|
|
1986
2259
|
}
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
lines.push("");
|
|
1994
|
-
lines.push("Please correct the arguments and try again.");
|
|
2260
|
+
return `${baseUrl}${path}`;
|
|
2261
|
+
}
|
|
2262
|
+
function normalizeBaseUrl(value) {
|
|
2263
|
+
const trimmed = value.trim();
|
|
2264
|
+
if (trimmed.endsWith("/")) {
|
|
2265
|
+
return trimmed.slice(0, -1);
|
|
1995
2266
|
}
|
|
1996
|
-
return
|
|
2267
|
+
return trimmed;
|
|
1997
2268
|
}
|
|
1998
|
-
function
|
|
1999
|
-
return
|
|
2269
|
+
function isValidHttpUrl(value) {
|
|
2270
|
+
return /^https?:\/\//i.test(value);
|
|
2000
2271
|
}
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2272
|
+
function normalizeRetryConfig(retry) {
|
|
2273
|
+
if (retry === false) return void 0;
|
|
2274
|
+
const cfg = retry || {};
|
|
2275
|
+
return {
|
|
2276
|
+
maxAttempts: Math.max(1, cfg.maxAttempts ?? 3),
|
|
2277
|
+
baseBackoffMs: Math.max(0, cfg.baseBackoffMs ?? 300),
|
|
2278
|
+
maxBackoffMs: Math.max(0, cfg.maxBackoffMs ?? 5e3),
|
|
2279
|
+
retryPost: cfg.retryPost ?? true
|
|
2280
|
+
};
|
|
2003
2281
|
}
|
|
2004
|
-
function
|
|
2005
|
-
|
|
2282
|
+
function shouldRetryStatus(status, method, retryPost) {
|
|
2283
|
+
if (status === 408 || status === 429) {
|
|
2284
|
+
return method !== "POST" || retryPost;
|
|
2285
|
+
}
|
|
2286
|
+
if (status >= 500 && status < 600) {
|
|
2287
|
+
return method !== "POST" || retryPost;
|
|
2288
|
+
}
|
|
2289
|
+
return false;
|
|
2006
2290
|
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
for (const result of retryableResults) {
|
|
2031
|
-
successfulResults.set(result.toolCallId, result);
|
|
2032
|
-
}
|
|
2033
|
-
return Array.from(successfulResults.values());
|
|
2291
|
+
function isRetryableError(err, kind) {
|
|
2292
|
+
if (!err) return false;
|
|
2293
|
+
if (kind === "timeout" || kind === "connect") return true;
|
|
2294
|
+
return err instanceof DOMException || err instanceof TypeError;
|
|
2295
|
+
}
|
|
2296
|
+
function backoff(attempt, cfg) {
|
|
2297
|
+
const exp = Math.max(0, attempt - 1);
|
|
2298
|
+
const base = cfg.baseBackoffMs * Math.pow(2, Math.min(exp, 10));
|
|
2299
|
+
const capped = Math.min(base, cfg.maxBackoffMs);
|
|
2300
|
+
const jitter = 0.5 + Math.random();
|
|
2301
|
+
const delay = Math.min(cfg.maxBackoffMs, capped * jitter);
|
|
2302
|
+
if (delay <= 0) return Promise.resolve();
|
|
2303
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
2304
|
+
}
|
|
2305
|
+
function mergeSignals(...signals) {
|
|
2306
|
+
const active = signals.filter(Boolean);
|
|
2307
|
+
if (active.length === 0) return void 0;
|
|
2308
|
+
if (active.length === 1) return active[0];
|
|
2309
|
+
const controller = new AbortController();
|
|
2310
|
+
for (const src of active) {
|
|
2311
|
+
if (src.aborted) {
|
|
2312
|
+
controller.abort(src.reason);
|
|
2313
|
+
break;
|
|
2034
2314
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2315
|
+
src.addEventListener(
|
|
2316
|
+
"abort",
|
|
2317
|
+
() => controller.abort(src.reason),
|
|
2318
|
+
{ once: true }
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
return controller.signal;
|
|
2322
|
+
}
|
|
2323
|
+
function normalizeHeaders(headers) {
|
|
2324
|
+
if (!headers) return {};
|
|
2325
|
+
const normalized = {};
|
|
2326
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2327
|
+
if (!key || !value) continue;
|
|
2328
|
+
const k = key.trim();
|
|
2329
|
+
const v = value.trim();
|
|
2330
|
+
if (k && v) {
|
|
2331
|
+
normalized[k] = v;
|
|
2042
2332
|
}
|
|
2043
|
-
currentCalls = newCalls;
|
|
2044
2333
|
}
|
|
2045
|
-
return
|
|
2334
|
+
return normalized;
|
|
2335
|
+
}
|
|
2336
|
+
function buildRetryMetadata(attempt, lastStatus, lastError) {
|
|
2337
|
+
if (!attempt || attempt <= 1) return void 0;
|
|
2338
|
+
return {
|
|
2339
|
+
attempts: attempt,
|
|
2340
|
+
lastStatus,
|
|
2341
|
+
lastError: typeof lastError === "string" ? lastError : lastError instanceof Error ? lastError.message : lastError ? String(lastError) : void 0
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
function classifyTransportErrorKind(err, connectTimedOut, requestTimedOut) {
|
|
2345
|
+
if (connectTimedOut) return "connect";
|
|
2346
|
+
if (requestTimedOut) return "timeout";
|
|
2347
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
2348
|
+
return requestTimedOut ? "timeout" : "request";
|
|
2349
|
+
}
|
|
2350
|
+
if (err instanceof TypeError) return "request";
|
|
2351
|
+
return "other";
|
|
2352
|
+
}
|
|
2353
|
+
function toTransportError(err, kind, retries) {
|
|
2354
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : "request failed";
|
|
2355
|
+
return new TransportError(message, { kind, retries, cause: err });
|
|
2356
|
+
}
|
|
2357
|
+
function recordHttpMetrics(metrics, trace, start, retries, info) {
|
|
2358
|
+
if (!metrics?.httpRequest && !trace?.requestFinish) return;
|
|
2359
|
+
const latencyMs = start ? Date.now() - start : 0;
|
|
2360
|
+
if (metrics?.httpRequest) {
|
|
2361
|
+
metrics.httpRequest({
|
|
2362
|
+
latencyMs,
|
|
2363
|
+
status: info.status,
|
|
2364
|
+
error: info.error ? String(info.error) : void 0,
|
|
2365
|
+
retries,
|
|
2366
|
+
context: info.context
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
trace?.requestFinish?.({
|
|
2370
|
+
context: info.context,
|
|
2371
|
+
status: info.status,
|
|
2372
|
+
error: info.error,
|
|
2373
|
+
retries,
|
|
2374
|
+
latencyMs
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
function withRequestId(context, headers) {
|
|
2378
|
+
const requestId = headers.get("X-ModelRelay-Chat-Request-Id") || headers.get("X-Request-Id") || context.requestId;
|
|
2379
|
+
if (!requestId) return context;
|
|
2380
|
+
return { ...context, requestId };
|
|
2046
2381
|
}
|
|
2047
2382
|
|
|
2048
2383
|
// src/index.ts
|
|
@@ -2101,12 +2436,13 @@ function resolveBaseUrl(override) {
|
|
|
2101
2436
|
DEFAULT_CLIENT_HEADER,
|
|
2102
2437
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
2103
2438
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2439
|
+
ErrorCodes,
|
|
2104
2440
|
ModelRelay,
|
|
2105
2441
|
ModelRelayError,
|
|
2106
|
-
|
|
2107
|
-
Providers,
|
|
2442
|
+
ResponseFormatTypes,
|
|
2108
2443
|
SDK_VERSION,
|
|
2109
2444
|
StopReasons,
|
|
2445
|
+
StructuredJSONStream,
|
|
2110
2446
|
TiersClient,
|
|
2111
2447
|
ToolArgsError,
|
|
2112
2448
|
ToolCallAccumulator,
|
|
@@ -2114,11 +2450,20 @@ function resolveBaseUrl(override) {
|
|
|
2114
2450
|
ToolRegistry,
|
|
2115
2451
|
ToolTypes,
|
|
2116
2452
|
TransportError,
|
|
2453
|
+
WebToolModes,
|
|
2117
2454
|
assistantMessageWithToolCalls,
|
|
2455
|
+
createAccessTokenAuth,
|
|
2456
|
+
createApiKeyAuth,
|
|
2457
|
+
createAssistantMessage,
|
|
2458
|
+
createFunctionCall,
|
|
2118
2459
|
createFunctionTool,
|
|
2119
2460
|
createFunctionToolFromSchema,
|
|
2120
2461
|
createRetryMessages,
|
|
2121
|
-
|
|
2462
|
+
createSystemMessage,
|
|
2463
|
+
createToolCall,
|
|
2464
|
+
createUsage,
|
|
2465
|
+
createUserMessage,
|
|
2466
|
+
createWebTool,
|
|
2122
2467
|
executeWithRetry,
|
|
2123
2468
|
firstToolCall,
|
|
2124
2469
|
formatToolErrorForModel,
|
|
@@ -2130,12 +2475,10 @@ function resolveBaseUrl(override) {
|
|
|
2130
2475
|
mergeTrace,
|
|
2131
2476
|
modelToString,
|
|
2132
2477
|
normalizeModelId,
|
|
2133
|
-
normalizeProvider,
|
|
2134
2478
|
normalizeStopReason,
|
|
2135
2479
|
parseErrorResponse,
|
|
2136
2480
|
parseToolArgs,
|
|
2137
2481
|
parseToolArgsRaw,
|
|
2138
|
-
providerToString,
|
|
2139
2482
|
respondToToolCall,
|
|
2140
2483
|
stopReasonToString,
|
|
2141
2484
|
toolChoiceAuto,
|