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