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