@modelrelay/sdk 0.7.0 → 0.14.1

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