@modelrelay/sdk 0.7.0 → 0.17.0

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