@modelrelay/sdk 0.5.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.js CHANGED
@@ -1,4 +1,17 @@
1
1
  // src/errors.ts
2
+ var ErrorCodes = {
3
+ NOT_FOUND: "NOT_FOUND",
4
+ VALIDATION_ERROR: "VALIDATION_ERROR",
5
+ RATE_LIMIT: "RATE_LIMIT",
6
+ UNAUTHORIZED: "UNAUTHORIZED",
7
+ FORBIDDEN: "FORBIDDEN",
8
+ CONFLICT: "CONFLICT",
9
+ INTERNAL_ERROR: "INTERNAL_ERROR",
10
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
11
+ INVALID_INPUT: "INVALID_INPUT",
12
+ PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
13
+ METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED"
14
+ };
2
15
  var ModelRelayError = class extends Error {
3
16
  constructor(message, opts) {
4
17
  super(message);
@@ -42,6 +55,30 @@ var APIError = class extends ModelRelayError {
42
55
  retries: opts.retries
43
56
  });
44
57
  }
58
+ /** Returns true if the error is a not found error. */
59
+ isNotFound() {
60
+ return this.code === ErrorCodes.NOT_FOUND;
61
+ }
62
+ /** Returns true if the error is a validation error. */
63
+ isValidation() {
64
+ return this.code === ErrorCodes.VALIDATION_ERROR || this.code === ErrorCodes.INVALID_INPUT;
65
+ }
66
+ /** Returns true if the error is a rate limit error. */
67
+ isRateLimit() {
68
+ return this.code === ErrorCodes.RATE_LIMIT;
69
+ }
70
+ /** Returns true if the error is an unauthorized error. */
71
+ isUnauthorized() {
72
+ return this.code === ErrorCodes.UNAUTHORIZED;
73
+ }
74
+ /** Returns true if the error is a forbidden error. */
75
+ isForbidden() {
76
+ return this.code === ErrorCodes.FORBIDDEN;
77
+ }
78
+ /** Returns true if the error is a service unavailable error. */
79
+ isUnavailable() {
80
+ return this.code === ErrorCodes.SERVICE_UNAVAILABLE;
81
+ }
45
82
  };
46
83
  async function parseErrorResponse(response, retries) {
47
84
  const requestId = response.headers.get("X-ModelRelay-Chat-Request-Id") || response.headers.get("X-Request-Id") || void 0;
@@ -96,6 +133,12 @@ async function parseErrorResponse(response, retries) {
96
133
  }
97
134
 
98
135
  // src/auth.ts
136
+ function createApiKeyAuth(apiKey) {
137
+ return { apiKey };
138
+ }
139
+ function createAccessTokenAuth(accessToken) {
140
+ return { accessToken };
141
+ }
99
142
  var AuthClient = class {
100
143
  constructor(http, cfg) {
101
144
  this.cachedFrontend = /* @__PURE__ */ new Map();
@@ -155,7 +198,7 @@ var AuthClient = class {
155
198
  */
156
199
  async authForChat(customerId, overrides) {
157
200
  if (this.accessToken) {
158
- return { accessToken: this.accessToken };
201
+ return createAccessTokenAuth(this.accessToken);
159
202
  }
160
203
  if (!this.apiKey) {
161
204
  throw new ConfigError("API key or token is required");
@@ -166,21 +209,21 @@ var AuthClient = class {
166
209
  deviceId: overrides?.deviceId,
167
210
  ttlSeconds: overrides?.ttlSeconds
168
211
  });
169
- return { accessToken: token.token };
212
+ return createAccessTokenAuth(token.token);
170
213
  }
171
- return { apiKey: this.apiKey };
214
+ return createApiKeyAuth(this.apiKey);
172
215
  }
173
216
  /**
174
217
  * Billing calls accept either bearer tokens or API keys (including publishable keys).
175
218
  */
176
219
  authForBilling() {
177
220
  if (this.accessToken) {
178
- return { accessToken: this.accessToken };
221
+ return createAccessTokenAuth(this.accessToken);
179
222
  }
180
223
  if (!this.apiKey) {
181
224
  throw new ConfigError("API key or token is required");
182
225
  }
183
- return { apiKey: this.apiKey };
226
+ return createApiKeyAuth(this.apiKey);
184
227
  }
185
228
  };
186
229
  function isPublishableKey(value) {
@@ -218,7 +261,7 @@ function isTokenReusable(token) {
218
261
  // package.json
219
262
  var package_default = {
220
263
  name: "@modelrelay/sdk",
221
- version: "0.5.0",
264
+ version: "0.14.1",
222
265
  description: "TypeScript SDK for the ModelRelay API",
223
266
  type: "module",
224
267
  main: "dist/index.cjs",
@@ -252,15 +295,14 @@ var package_default = {
252
295
  devDependencies: {
253
296
  tsup: "^8.2.4",
254
297
  typescript: "^5.6.3",
255
- vitest: "^2.1.4"
298
+ vitest: "^2.1.4",
299
+ zod: "^3.23.0"
256
300
  }
257
301
  };
258
302
 
259
303
  // src/types.ts
260
304
  var SDK_VERSION = package_default.version || "0.0.0";
261
305
  var DEFAULT_BASE_URL = "https://api.modelrelay.ai/api/v1";
262
- var STAGING_BASE_URL = "https://api-stg.modelrelay.ai/api/v1";
263
- var SANDBOX_BASE_URL = "https://api.sandbox.modelrelay.ai/api/v1";
264
306
  var DEFAULT_CLIENT_HEADER = `modelrelay-ts/${SDK_VERSION}`;
265
307
  var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
266
308
  var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
@@ -281,22 +323,45 @@ var StopReasons = {
281
323
  var Providers = {
282
324
  OpenAI: "openai",
283
325
  Anthropic: "anthropic",
284
- Grok: "grok",
285
- OpenRouter: "openrouter",
326
+ XAI: "xai",
327
+ GoogleAIStudio: "google-ai-studio",
286
328
  Echo: "echo"
287
329
  };
288
330
  var Models = {
289
- OpenAIGpt4o: "openai/gpt-4o",
290
- OpenAIGpt4oMini: "openai/gpt-4o-mini",
291
- OpenAIGpt51: "openai/gpt-5.1",
292
- AnthropicClaude35HaikuLatest: "anthropic/claude-3-5-haiku-latest",
293
- AnthropicClaude35SonnetLatest: "anthropic/claude-3-5-sonnet-latest",
294
- AnthropicClaudeOpus45: "anthropic/claude-opus-4-5-20251101",
295
- OpenRouterClaude35Haiku: "anthropic/claude-3.5-haiku",
331
+ // OpenAI models (provider-agnostic identifiers)
332
+ Gpt4o: "gpt-4o",
333
+ Gpt4oMini: "gpt-4o-mini",
334
+ Gpt51: "gpt-5.1",
335
+ // Anthropic models (provider-agnostic identifiers)
336
+ Claude35HaikuLatest: "claude-3-5-haiku-latest",
337
+ Claude35SonnetLatest: "claude-3-5-sonnet-latest",
338
+ ClaudeOpus45: "claude-opus-4-5",
339
+ Claude35Haiku: "claude-3.5-haiku",
340
+ // xAI / Grok models
296
341
  Grok2: "grok-2",
297
- Grok4Fast: "grok-4-fast",
342
+ Grok4_1FastNonReasoning: "grok-4-1-fast-non-reasoning",
343
+ Grok4_1FastReasoning: "grok-4-1-fast-reasoning",
344
+ // Internal echo model for testing.
298
345
  Echo1: "echo-1"
299
346
  };
347
+ function createUsage(inputTokens, outputTokens, totalTokens) {
348
+ return {
349
+ inputTokens,
350
+ outputTokens,
351
+ totalTokens: totalTokens ?? inputTokens + outputTokens
352
+ };
353
+ }
354
+ var ToolTypes = {
355
+ Function: "function",
356
+ Web: "web",
357
+ XSearch: "x_search",
358
+ CodeExecution: "code_execution"
359
+ };
360
+ var ToolChoiceTypes = {
361
+ Auto: "auto",
362
+ Required: "required",
363
+ None: "none"
364
+ };
300
365
  function mergeMetrics(base, override) {
301
366
  if (!base && !override) return void 0;
302
367
  return {
@@ -360,6 +425,630 @@ function modelToString(value) {
360
425
  return value.other?.trim() || "";
361
426
  }
362
427
 
428
+ // src/tools.ts
429
+ function createUserMessage(content) {
430
+ return { role: "user", content };
431
+ }
432
+ function createAssistantMessage(content) {
433
+ return { role: "assistant", content };
434
+ }
435
+ function createSystemMessage(content) {
436
+ return { role: "system", content };
437
+ }
438
+ function createToolCall(id, name, args, type = ToolTypes.Function) {
439
+ return {
440
+ id,
441
+ type,
442
+ function: createFunctionCall(name, args)
443
+ };
444
+ }
445
+ function createFunctionCall(name, args) {
446
+ return { name, arguments: args };
447
+ }
448
+ function zodToJsonSchema(schema, options = {}) {
449
+ const result = convertZodType(schema);
450
+ if (options.includeSchema) {
451
+ const schemaVersion = options.target === "draft-04" ? "http://json-schema.org/draft-04/schema#" : options.target === "draft-2019-09" ? "https://json-schema.org/draft/2019-09/schema" : options.target === "draft-2020-12" ? "https://json-schema.org/draft/2020-12/schema" : "http://json-schema.org/draft-07/schema#";
452
+ return { $schema: schemaVersion, ...result };
453
+ }
454
+ return result;
455
+ }
456
+ function convertZodType(schema) {
457
+ const def = schema._def;
458
+ const typeName = def.typeName;
459
+ switch (typeName) {
460
+ case "ZodString":
461
+ return convertZodString(def);
462
+ case "ZodNumber":
463
+ return convertZodNumber(def);
464
+ case "ZodBoolean":
465
+ return { type: "boolean" };
466
+ case "ZodNull":
467
+ return { type: "null" };
468
+ case "ZodArray":
469
+ return convertZodArray(def);
470
+ case "ZodObject":
471
+ return convertZodObject(def);
472
+ case "ZodEnum":
473
+ return convertZodEnum(def);
474
+ case "ZodNativeEnum":
475
+ return convertZodNativeEnum(def);
476
+ case "ZodLiteral":
477
+ return { const: def.value };
478
+ case "ZodUnion":
479
+ return convertZodUnion(def);
480
+ case "ZodOptional": {
481
+ const inner = convertZodType(def.innerType);
482
+ if (def.description && !inner.description) {
483
+ inner.description = def.description;
484
+ }
485
+ return inner;
486
+ }
487
+ case "ZodNullable":
488
+ return convertZodNullable(def);
489
+ case "ZodDefault":
490
+ return { ...convertZodType(def.innerType), default: def.defaultValue() };
491
+ case "ZodEffects":
492
+ return convertZodType(def.schema);
493
+ case "ZodRecord":
494
+ return convertZodRecord(def);
495
+ case "ZodTuple":
496
+ return convertZodTuple(def);
497
+ case "ZodAny":
498
+ case "ZodUnknown":
499
+ return {};
500
+ default:
501
+ return {};
502
+ }
503
+ }
504
+ function convertZodString(def) {
505
+ const result = { type: "string" };
506
+ const checks = def.checks;
507
+ if (checks) {
508
+ for (const check of checks) {
509
+ switch (check.kind) {
510
+ case "min":
511
+ result.minLength = check.value;
512
+ break;
513
+ case "max":
514
+ result.maxLength = check.value;
515
+ break;
516
+ case "length":
517
+ result.minLength = check.value;
518
+ result.maxLength = check.value;
519
+ break;
520
+ case "email":
521
+ result.format = "email";
522
+ break;
523
+ case "url":
524
+ result.format = "uri";
525
+ break;
526
+ case "uuid":
527
+ result.format = "uuid";
528
+ break;
529
+ case "datetime":
530
+ result.format = "date-time";
531
+ break;
532
+ case "regex":
533
+ result.pattern = check.value.source;
534
+ break;
535
+ }
536
+ }
537
+ }
538
+ if (def.description) {
539
+ result.description = def.description;
540
+ }
541
+ return result;
542
+ }
543
+ function convertZodNumber(def) {
544
+ const result = { type: "number" };
545
+ const checks = def.checks;
546
+ if (checks) {
547
+ for (const check of checks) {
548
+ switch (check.kind) {
549
+ case "int":
550
+ result.type = "integer";
551
+ break;
552
+ case "min":
553
+ if (check.inclusive === false) {
554
+ result.exclusiveMinimum = check.value;
555
+ } else {
556
+ result.minimum = check.value;
557
+ }
558
+ break;
559
+ case "max":
560
+ if (check.inclusive === false) {
561
+ result.exclusiveMaximum = check.value;
562
+ } else {
563
+ result.maximum = check.value;
564
+ }
565
+ break;
566
+ case "multipleOf":
567
+ result.multipleOf = check.value;
568
+ break;
569
+ }
570
+ }
571
+ }
572
+ if (def.description) {
573
+ result.description = def.description;
574
+ }
575
+ return result;
576
+ }
577
+ function convertZodArray(def) {
578
+ const result = {
579
+ type: "array",
580
+ items: convertZodType(def.type)
581
+ };
582
+ if (def.minLength !== void 0 && def.minLength !== null) {
583
+ result.minItems = def.minLength.value;
584
+ }
585
+ if (def.maxLength !== void 0 && def.maxLength !== null) {
586
+ result.maxItems = def.maxLength.value;
587
+ }
588
+ if (def.description) {
589
+ result.description = def.description;
590
+ }
591
+ return result;
592
+ }
593
+ function convertZodObject(def) {
594
+ const shape = def.shape;
595
+ const shapeObj = typeof shape === "function" ? shape() : shape;
596
+ const properties = {};
597
+ const required = [];
598
+ for (const [key, value] of Object.entries(shapeObj)) {
599
+ properties[key] = convertZodType(value);
600
+ const valueDef = value._def;
601
+ const isOptional = valueDef.typeName === "ZodOptional" || valueDef.typeName === "ZodDefault" || valueDef.typeName === "ZodNullable" && valueDef.innerType?._def?.typeName === "ZodDefault";
602
+ if (!isOptional) {
603
+ required.push(key);
604
+ }
605
+ }
606
+ const result = {
607
+ type: "object",
608
+ properties
609
+ };
610
+ if (required.length > 0) {
611
+ result.required = required;
612
+ }
613
+ if (def.description) {
614
+ result.description = def.description;
615
+ }
616
+ const unknownKeys = def.unknownKeys;
617
+ if (unknownKeys === "strict") {
618
+ result.additionalProperties = false;
619
+ }
620
+ return result;
621
+ }
622
+ function convertZodEnum(def) {
623
+ const result = {
624
+ type: "string",
625
+ enum: def.values
626
+ };
627
+ if (def.description) {
628
+ result.description = def.description;
629
+ }
630
+ return result;
631
+ }
632
+ function convertZodNativeEnum(def) {
633
+ const enumValues = def.values;
634
+ const values = Object.values(enumValues).filter(
635
+ (v) => typeof v === "string" || typeof v === "number"
636
+ );
637
+ const result = { enum: values };
638
+ if (def.description) {
639
+ result.description = def.description;
640
+ }
641
+ return result;
642
+ }
643
+ function convertZodUnion(def) {
644
+ const options = def.options;
645
+ const result = {
646
+ anyOf: options.map(convertZodType)
647
+ };
648
+ if (def.description) {
649
+ result.description = def.description;
650
+ }
651
+ return result;
652
+ }
653
+ function convertZodNullable(def) {
654
+ const inner = convertZodType(def.innerType);
655
+ return {
656
+ anyOf: [inner, { type: "null" }]
657
+ };
658
+ }
659
+ function convertZodRecord(def) {
660
+ const result = {
661
+ type: "object",
662
+ additionalProperties: convertZodType(def.valueType)
663
+ };
664
+ if (def.description) {
665
+ result.description = def.description;
666
+ }
667
+ return result;
668
+ }
669
+ function convertZodTuple(def) {
670
+ const items = def.items;
671
+ const result = {
672
+ type: "array",
673
+ items: items.map(convertZodType),
674
+ minItems: items.length,
675
+ maxItems: items.length
676
+ };
677
+ if (def.description) {
678
+ result.description = def.description;
679
+ }
680
+ return result;
681
+ }
682
+ function createFunctionToolFromSchema(name, description, schema, options) {
683
+ const jsonSchema = zodToJsonSchema(schema, options);
684
+ return createFunctionTool(name, description, jsonSchema);
685
+ }
686
+ function createFunctionTool(name, description, parameters) {
687
+ const fn = { name, description };
688
+ if (parameters) {
689
+ fn.parameters = parameters;
690
+ }
691
+ return {
692
+ type: ToolTypes.Function,
693
+ function: fn
694
+ };
695
+ }
696
+ function createWebTool(options) {
697
+ return {
698
+ type: ToolTypes.Web,
699
+ web: options ? {
700
+ mode: options.mode,
701
+ allowedDomains: options.allowedDomains,
702
+ excludedDomains: options.excludedDomains,
703
+ maxUses: options.maxUses
704
+ } : void 0
705
+ };
706
+ }
707
+ function toolChoiceAuto() {
708
+ return { type: ToolChoiceTypes.Auto };
709
+ }
710
+ function toolChoiceRequired() {
711
+ return { type: ToolChoiceTypes.Required };
712
+ }
713
+ function toolChoiceNone() {
714
+ return { type: ToolChoiceTypes.None };
715
+ }
716
+ function hasToolCalls(response) {
717
+ return (response.toolCalls?.length ?? 0) > 0;
718
+ }
719
+ function firstToolCall(response) {
720
+ return response.toolCalls?.[0];
721
+ }
722
+ function toolResultMessage(toolCallId, result) {
723
+ const content = typeof result === "string" ? result : JSON.stringify(result);
724
+ return {
725
+ role: "tool",
726
+ content,
727
+ toolCallId
728
+ };
729
+ }
730
+ function respondToToolCall(call, result) {
731
+ return toolResultMessage(call.id, result);
732
+ }
733
+ function assistantMessageWithToolCalls(content, toolCalls) {
734
+ return {
735
+ role: "assistant",
736
+ content,
737
+ toolCalls
738
+ };
739
+ }
740
+ var ToolCallAccumulator = class {
741
+ constructor() {
742
+ this.calls = /* @__PURE__ */ new Map();
743
+ }
744
+ /**
745
+ * Processes a streaming tool call delta.
746
+ * Returns true if this started a new tool call.
747
+ */
748
+ processDelta(delta) {
749
+ const existing = this.calls.get(delta.index);
750
+ if (!existing) {
751
+ this.calls.set(delta.index, {
752
+ id: delta.id ?? "",
753
+ type: delta.type ?? ToolTypes.Function,
754
+ function: {
755
+ name: delta.function?.name ?? "",
756
+ arguments: delta.function?.arguments ?? ""
757
+ }
758
+ });
759
+ return true;
760
+ }
761
+ if (delta.function) {
762
+ if (delta.function.name) {
763
+ existing.function = existing.function ?? { name: "", arguments: "" };
764
+ existing.function.name = delta.function.name;
765
+ }
766
+ if (delta.function.arguments) {
767
+ existing.function = existing.function ?? { name: "", arguments: "" };
768
+ existing.function.arguments += delta.function.arguments;
769
+ }
770
+ }
771
+ return false;
772
+ }
773
+ /**
774
+ * Returns all accumulated tool calls in index order.
775
+ */
776
+ getToolCalls() {
777
+ if (this.calls.size === 0) {
778
+ return [];
779
+ }
780
+ const maxIdx = Math.max(...this.calls.keys());
781
+ const result = [];
782
+ for (let i = 0; i <= maxIdx; i++) {
783
+ const call = this.calls.get(i);
784
+ if (call) {
785
+ result.push(call);
786
+ }
787
+ }
788
+ return result;
789
+ }
790
+ /**
791
+ * Returns a specific tool call by index, or undefined if not found.
792
+ */
793
+ getToolCall(index) {
794
+ return this.calls.get(index);
795
+ }
796
+ /**
797
+ * Clears all accumulated tool calls.
798
+ */
799
+ reset() {
800
+ this.calls.clear();
801
+ }
802
+ };
803
+ var ToolArgsError = class extends Error {
804
+ constructor(message, toolCallId, toolName, rawArguments) {
805
+ super(message);
806
+ this.name = "ToolArgsError";
807
+ this.toolCallId = toolCallId;
808
+ this.toolName = toolName;
809
+ this.rawArguments = rawArguments;
810
+ }
811
+ };
812
+ function parseToolArgs(call, schema) {
813
+ const toolName = call.function?.name ?? "unknown";
814
+ const rawArgs = call.function?.arguments ?? "";
815
+ let parsed;
816
+ try {
817
+ parsed = rawArgs ? JSON.parse(rawArgs) : {};
818
+ } catch (err) {
819
+ const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
820
+ throw new ToolArgsError(
821
+ `Failed to parse arguments for tool '${toolName}': ${message}`,
822
+ call.id,
823
+ toolName,
824
+ rawArgs
825
+ );
826
+ }
827
+ try {
828
+ return schema.parse(parsed);
829
+ } catch (err) {
830
+ let message;
831
+ if (err instanceof Error) {
832
+ const zodErr = err;
833
+ if (zodErr.errors && Array.isArray(zodErr.errors)) {
834
+ const issues = zodErr.errors.map((e) => {
835
+ const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
836
+ return `${path}${e.message}`;
837
+ }).join("; ");
838
+ message = issues;
839
+ } else {
840
+ message = err.message;
841
+ }
842
+ } else {
843
+ message = String(err);
844
+ }
845
+ throw new ToolArgsError(
846
+ `Invalid arguments for tool '${toolName}': ${message}`,
847
+ call.id,
848
+ toolName,
849
+ rawArgs
850
+ );
851
+ }
852
+ }
853
+ function tryParseToolArgs(call, schema) {
854
+ try {
855
+ const data = parseToolArgs(call, schema);
856
+ return { success: true, data };
857
+ } catch (err) {
858
+ if (err instanceof ToolArgsError) {
859
+ return { success: false, error: err };
860
+ }
861
+ const toolName = call.function?.name ?? "unknown";
862
+ const rawArgs = call.function?.arguments ?? "";
863
+ return {
864
+ success: false,
865
+ error: new ToolArgsError(
866
+ err instanceof Error ? err.message : String(err),
867
+ call.id,
868
+ toolName,
869
+ rawArgs
870
+ )
871
+ };
872
+ }
873
+ }
874
+ function parseToolArgsRaw(call) {
875
+ const toolName = call.function?.name ?? "unknown";
876
+ const rawArgs = call.function?.arguments ?? "";
877
+ try {
878
+ return rawArgs ? JSON.parse(rawArgs) : {};
879
+ } catch (err) {
880
+ const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
881
+ throw new ToolArgsError(
882
+ `Failed to parse arguments for tool '${toolName}': ${message}`,
883
+ call.id,
884
+ toolName,
885
+ rawArgs
886
+ );
887
+ }
888
+ }
889
+ var ToolRegistry = class {
890
+ constructor() {
891
+ this.handlers = /* @__PURE__ */ new Map();
892
+ }
893
+ /**
894
+ * Registers a handler function for a tool name.
895
+ * @param name - The tool name (must match the function name in the tool definition)
896
+ * @param handler - Function to execute when this tool is called
897
+ * @returns this for chaining
898
+ */
899
+ register(name, handler) {
900
+ this.handlers.set(name, handler);
901
+ return this;
902
+ }
903
+ /**
904
+ * Unregisters a tool handler.
905
+ * @param name - The tool name to unregister
906
+ * @returns true if the handler was removed, false if it didn't exist
907
+ */
908
+ unregister(name) {
909
+ return this.handlers.delete(name);
910
+ }
911
+ /**
912
+ * Checks if a handler is registered for the given tool name.
913
+ */
914
+ has(name) {
915
+ return this.handlers.has(name);
916
+ }
917
+ /**
918
+ * Returns the list of registered tool names.
919
+ */
920
+ getRegisteredTools() {
921
+ return Array.from(this.handlers.keys());
922
+ }
923
+ /**
924
+ * Executes a single tool call.
925
+ * @param call - The tool call to execute
926
+ * @returns The execution result
927
+ */
928
+ async execute(call) {
929
+ const toolName = call.function?.name ?? "";
930
+ const handler = this.handlers.get(toolName);
931
+ if (!handler) {
932
+ return {
933
+ toolCallId: call.id,
934
+ toolName,
935
+ result: null,
936
+ error: `Unknown tool: '${toolName}'. Available tools: ${this.getRegisteredTools().join(", ") || "none"}`
937
+ };
938
+ }
939
+ let args;
940
+ try {
941
+ args = call.function?.arguments ? JSON.parse(call.function.arguments) : {};
942
+ } catch (err) {
943
+ const errorMessage = err instanceof Error ? err.message : String(err);
944
+ return {
945
+ toolCallId: call.id,
946
+ toolName,
947
+ result: null,
948
+ error: `Invalid JSON in arguments: ${errorMessage}`,
949
+ isRetryable: true
950
+ };
951
+ }
952
+ try {
953
+ const result = await handler(args, call);
954
+ return {
955
+ toolCallId: call.id,
956
+ toolName,
957
+ result
958
+ };
959
+ } catch (err) {
960
+ const isRetryable = err instanceof ToolArgsError;
961
+ const errorMessage = err instanceof Error ? err.message : String(err);
962
+ return {
963
+ toolCallId: call.id,
964
+ toolName,
965
+ result: null,
966
+ error: errorMessage,
967
+ isRetryable
968
+ };
969
+ }
970
+ }
971
+ /**
972
+ * Executes multiple tool calls in parallel.
973
+ * @param calls - Array of tool calls to execute
974
+ * @returns Array of execution results in the same order as input
975
+ */
976
+ async executeAll(calls) {
977
+ return Promise.all(calls.map((call) => this.execute(call)));
978
+ }
979
+ /**
980
+ * Converts execution results to tool result messages.
981
+ * Useful for appending to the conversation history.
982
+ * @param results - Array of execution results
983
+ * @returns Array of ChatMessage objects with role "tool"
984
+ */
985
+ resultsToMessages(results) {
986
+ return results.map((r) => {
987
+ const content = r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result);
988
+ return toolResultMessage(r.toolCallId, content);
989
+ });
990
+ }
991
+ };
992
+ function formatToolErrorForModel(result) {
993
+ const lines = [
994
+ `Tool call error for '${result.toolName}': ${result.error}`
995
+ ];
996
+ if (result.isRetryable) {
997
+ lines.push("");
998
+ lines.push("Please correct the arguments and try again.");
999
+ }
1000
+ return lines.join("\n");
1001
+ }
1002
+ function hasRetryableErrors(results) {
1003
+ return results.some((r) => r.error && r.isRetryable);
1004
+ }
1005
+ function getRetryableErrors(results) {
1006
+ return results.filter((r) => r.error && r.isRetryable);
1007
+ }
1008
+ function createRetryMessages(results) {
1009
+ return results.filter((r) => r.error && r.isRetryable).map((r) => toolResultMessage(r.toolCallId, formatToolErrorForModel(r)));
1010
+ }
1011
+ async function executeWithRetry(registry, toolCalls, options = {}) {
1012
+ const maxRetries = options.maxRetries ?? 2;
1013
+ let currentCalls = toolCalls;
1014
+ let attempt = 0;
1015
+ const successfulResults = /* @__PURE__ */ new Map();
1016
+ while (attempt <= maxRetries) {
1017
+ const results = await registry.executeAll(currentCalls);
1018
+ for (const result of results) {
1019
+ if (!result.error || !result.isRetryable) {
1020
+ successfulResults.set(result.toolCallId, result);
1021
+ }
1022
+ }
1023
+ const retryableResults = getRetryableErrors(results);
1024
+ if (retryableResults.length === 0 || !options.onRetry) {
1025
+ for (const result of results) {
1026
+ if (result.error && result.isRetryable) {
1027
+ successfulResults.set(result.toolCallId, result);
1028
+ }
1029
+ }
1030
+ return Array.from(successfulResults.values());
1031
+ }
1032
+ attempt++;
1033
+ if (attempt > maxRetries) {
1034
+ for (const result of retryableResults) {
1035
+ successfulResults.set(result.toolCallId, result);
1036
+ }
1037
+ return Array.from(successfulResults.values());
1038
+ }
1039
+ const errorMessages = createRetryMessages(retryableResults);
1040
+ const newCalls = await options.onRetry(errorMessages, attempt);
1041
+ if (newCalls.length === 0) {
1042
+ for (const result of retryableResults) {
1043
+ successfulResults.set(result.toolCallId, result);
1044
+ }
1045
+ return Array.from(successfulResults.values());
1046
+ }
1047
+ currentCalls = newCalls;
1048
+ }
1049
+ return Array.from(successfulResults.values());
1050
+ }
1051
+
363
1052
  // src/chat.ts
364
1053
  var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
365
1054
  var ChatClient = class {
@@ -385,16 +1074,13 @@ var ChatCompletionsClient = class {
385
1074
  const stream = options.stream ?? params.stream ?? true;
386
1075
  const metrics = mergeMetrics(this.metrics, options.metrics);
387
1076
  const trace = mergeTrace(this.trace, options.trace);
388
- const modelValue = modelToString(params.model).trim();
389
- if (!modelValue) {
390
- throw new ConfigError("model is required");
391
- }
392
1077
  if (!params?.messages?.length) {
393
1078
  throw new ConfigError("at least one message is required");
394
1079
  }
395
1080
  if (!hasUserMessage(params.messages)) {
396
1081
  throw new ConfigError("at least one user message is required");
397
1082
  }
1083
+ validateRequestModel(params.model);
398
1084
  const authHeaders = await this.auth.authForChat(params.customerId);
399
1085
  const body = buildProxyBody(
400
1086
  params,
@@ -533,7 +1219,7 @@ var ChatCompletionsStream = class {
533
1219
  const context = this.enrichContext(evt);
534
1220
  this.context = context;
535
1221
  this.trace?.streamEvent?.({ context, event: evt });
536
- if (evt.type === "message_start" || evt.type === "message_delta" || evt.type === "message_stop") {
1222
+ if (evt.type === "message_start" || evt.type === "message_delta" || evt.type === "message_stop" || evt.type === "tool_use_start" || evt.type === "tool_use_delta" || evt.type === "tool_use_stop") {
537
1223
  this.recordFirstToken();
538
1224
  }
539
1225
  if (evt.type === "message_stop" && evt.usage && this.metrics?.usage) {
@@ -618,11 +1304,15 @@ function mapChatEvent(raw, requestId) {
618
1304
  const model = normalizeModelId(p.model || p?.message?.model);
619
1305
  const stopReason = normalizeStopReason(p.stop_reason);
620
1306
  const textDelta = extractTextDelta(p);
1307
+ const toolCallDelta = extractToolCallDelta(p, type);
1308
+ const toolCalls = extractToolCalls(p, type);
621
1309
  return {
622
1310
  type,
623
1311
  event: raw.event || type,
624
1312
  data: p,
625
1313
  textDelta,
1314
+ toolCallDelta,
1315
+ toolCalls,
626
1316
  responseId,
627
1317
  model,
628
1318
  stopReason,
@@ -642,6 +1332,12 @@ function normalizeEventType(eventName, payload) {
642
1332
  return "message_delta";
643
1333
  case "message_stop":
644
1334
  return "message_stop";
1335
+ case "tool_use_start":
1336
+ return "tool_use_start";
1337
+ case "tool_use_delta":
1338
+ return "tool_use_delta";
1339
+ case "tool_use_stop":
1340
+ return "tool_use_stop";
645
1341
  case "ping":
646
1342
  return "ping";
647
1343
  default:
@@ -652,6 +1348,9 @@ function extractTextDelta(payload) {
652
1348
  if (!payload || typeof payload !== "object") {
653
1349
  return void 0;
654
1350
  }
1351
+ if (typeof payload.text_delta === "string" && payload.text_delta !== "") {
1352
+ return payload.text_delta;
1353
+ }
655
1354
  if (typeof payload.delta === "string") {
656
1355
  return payload.delta;
657
1356
  }
@@ -665,9 +1364,56 @@ function extractTextDelta(payload) {
665
1364
  }
666
1365
  return void 0;
667
1366
  }
1367
+ function extractToolCallDelta(payload, type) {
1368
+ if (!payload || typeof payload !== "object") {
1369
+ return void 0;
1370
+ }
1371
+ if (type !== "tool_use_start" && type !== "tool_use_delta") {
1372
+ return void 0;
1373
+ }
1374
+ if (payload.tool_call_delta) {
1375
+ const d = payload.tool_call_delta;
1376
+ return {
1377
+ index: d.index ?? 0,
1378
+ id: d.id,
1379
+ type: d.type,
1380
+ function: d.function ? {
1381
+ name: d.function.name,
1382
+ arguments: d.function.arguments
1383
+ } : void 0
1384
+ };
1385
+ }
1386
+ if (typeof payload.index === "number" || payload.id || payload.name) {
1387
+ return {
1388
+ index: payload.index ?? 0,
1389
+ id: payload.id,
1390
+ type: payload.tool_type,
1391
+ function: payload.name || payload.arguments ? {
1392
+ name: payload.name,
1393
+ arguments: payload.arguments
1394
+ } : void 0
1395
+ };
1396
+ }
1397
+ return void 0;
1398
+ }
1399
+ function extractToolCalls(payload, type) {
1400
+ if (!payload || typeof payload !== "object") {
1401
+ return void 0;
1402
+ }
1403
+ if (type !== "tool_use_stop" && type !== "message_stop") {
1404
+ return void 0;
1405
+ }
1406
+ if (payload.tool_calls?.length) {
1407
+ return normalizeToolCalls(payload.tool_calls);
1408
+ }
1409
+ if (payload.tool_call) {
1410
+ return normalizeToolCalls([payload.tool_call]);
1411
+ }
1412
+ return void 0;
1413
+ }
668
1414
  function normalizeChatResponse(payload, requestId) {
669
1415
  const p = payload;
670
- return {
1416
+ const response = {
671
1417
  id: p?.id,
672
1418
  provider: normalizeProvider(p?.provider),
673
1419
  content: Array.isArray(p?.content) ? p.content : p?.content ? [String(p.content)] : [],
@@ -676,26 +1422,51 @@ function normalizeChatResponse(payload, requestId) {
676
1422
  usage: normalizeUsage(p?.usage),
677
1423
  requestId
678
1424
  };
1425
+ if (p?.tool_calls?.length) {
1426
+ response.toolCalls = normalizeToolCalls(p.tool_calls);
1427
+ }
1428
+ return response;
1429
+ }
1430
+ function normalizeToolCalls(toolCalls) {
1431
+ return toolCalls.map(
1432
+ (tc) => createToolCall(
1433
+ tc.id,
1434
+ tc.function?.name ?? "",
1435
+ tc.function?.arguments ?? "",
1436
+ tc.type || ToolTypes.Function
1437
+ )
1438
+ );
679
1439
  }
680
1440
  function normalizeUsage(payload) {
681
1441
  if (!payload) {
682
- return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1442
+ return createUsage(0, 0, 0);
683
1443
  }
684
- const usage = {
685
- inputTokens: Number(payload.input_tokens ?? 0),
686
- outputTokens: Number(payload.output_tokens ?? 0),
687
- totalTokens: Number(payload.total_tokens ?? 0)
688
- };
689
- if (!usage.totalTokens) {
690
- usage.totalTokens = usage.inputTokens + usage.outputTokens;
1444
+ const inputTokens = Number(payload.input_tokens ?? 0);
1445
+ const outputTokens = Number(payload.output_tokens ?? 0);
1446
+ const totalTokens = Number(payload.total_tokens ?? 0);
1447
+ return createUsage(inputTokens, outputTokens, totalTokens || void 0);
1448
+ }
1449
+ function validateRequestModel(model) {
1450
+ if (model === void 0 || model === null) return;
1451
+ const value = modelToString(model).trim();
1452
+ if (!value) {
1453
+ throw new ConfigError("model id must be a non-empty string when provided");
1454
+ }
1455
+ const knownModels = Object.values(Models);
1456
+ if (!knownModels.includes(value)) {
1457
+ throw new ConfigError(
1458
+ `unsupported model id "${value}". Use one of the SDK Models.* constants or omit model to use the tier's default model.`
1459
+ );
691
1460
  }
692
- return usage;
693
1461
  }
694
1462
  function buildProxyBody(params, metadata) {
1463
+ const modelValue = params.model ? modelToString(params.model).trim() : "";
695
1464
  const body = {
696
- model: modelToString(params.model),
697
1465
  messages: normalizeMessages(params.messages)
698
1466
  };
1467
+ if (modelValue) {
1468
+ body.model = modelValue;
1469
+ }
699
1470
  if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
700
1471
  if (params.provider) body.provider = providerToString(params.provider);
701
1472
  if (typeof params.temperature === "number")
@@ -703,13 +1474,69 @@ function buildProxyBody(params, metadata) {
703
1474
  if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
704
1475
  if (params.stop?.length) body.stop = params.stop;
705
1476
  if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
1477
+ if (params.tools?.length) body.tools = normalizeTools(params.tools);
1478
+ if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
706
1479
  return body;
707
1480
  }
708
1481
  function normalizeMessages(messages) {
709
- return messages.map((msg) => ({
710
- role: msg.role || "user",
711
- content: msg.content
712
- }));
1482
+ return messages.map((msg) => {
1483
+ const normalized = {
1484
+ role: msg.role || "user",
1485
+ content: msg.content
1486
+ };
1487
+ if (msg.toolCalls?.length) {
1488
+ normalized.tool_calls = msg.toolCalls.map((tc) => ({
1489
+ id: tc.id,
1490
+ type: tc.type,
1491
+ function: tc.function ? createFunctionCall(tc.function.name, tc.function.arguments) : void 0
1492
+ }));
1493
+ }
1494
+ if (msg.toolCallId) {
1495
+ normalized.tool_call_id = msg.toolCallId;
1496
+ }
1497
+ return normalized;
1498
+ });
1499
+ }
1500
+ function normalizeTools(tools) {
1501
+ return tools.map((tool) => {
1502
+ const normalized = { type: tool.type };
1503
+ if (tool.function) {
1504
+ normalized.function = {
1505
+ name: tool.function.name,
1506
+ description: tool.function.description,
1507
+ parameters: tool.function.parameters
1508
+ };
1509
+ }
1510
+ if (tool.web) {
1511
+ const web = {
1512
+ allowed_domains: tool.web.allowedDomains,
1513
+ excluded_domains: tool.web.excludedDomains,
1514
+ max_uses: tool.web.maxUses
1515
+ };
1516
+ if (tool.web.mode) {
1517
+ web.mode = tool.web.mode;
1518
+ }
1519
+ normalized.web = web;
1520
+ }
1521
+ if (tool.xSearch) {
1522
+ normalized.x_search = {
1523
+ allowed_handles: tool.xSearch.allowedHandles,
1524
+ excluded_handles: tool.xSearch.excludedHandles,
1525
+ from_date: tool.xSearch.fromDate,
1526
+ to_date: tool.xSearch.toDate
1527
+ };
1528
+ }
1529
+ if (tool.codeExecution) {
1530
+ normalized.code_execution = {
1531
+ language: tool.codeExecution.language,
1532
+ timeout_ms: tool.codeExecution.timeoutMs
1533
+ };
1534
+ }
1535
+ return normalized;
1536
+ });
1537
+ }
1538
+ function normalizeToolChoice(tc) {
1539
+ return { type: tc.type };
713
1540
  }
714
1541
  function requestIdFromHeaders(headers) {
715
1542
  return headers.get(REQUEST_ID_HEADER) || headers.get("X-Request-Id") || void 0;
@@ -734,6 +1561,10 @@ function hasUserMessage(messages) {
734
1561
  }
735
1562
 
736
1563
  // src/customers.ts
1564
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1565
+ function isValidEmail(email) {
1566
+ return EMAIL_REGEX.test(email);
1567
+ }
737
1568
  var CustomersClient = class {
738
1569
  constructor(http, cfg) {
739
1570
  this.http = http;
@@ -768,6 +1599,12 @@ var CustomersClient = class {
768
1599
  if (!request.external_id?.trim()) {
769
1600
  throw new ConfigError("external_id is required");
770
1601
  }
1602
+ if (!request.email?.trim()) {
1603
+ throw new ConfigError("email is required");
1604
+ }
1605
+ if (!isValidEmail(request.email)) {
1606
+ throw new ConfigError("invalid email format");
1607
+ }
771
1608
  const response = await this.http.json("/customers", {
772
1609
  method: "POST",
773
1610
  body: request,
@@ -805,6 +1642,12 @@ var CustomersClient = class {
805
1642
  if (!request.external_id?.trim()) {
806
1643
  throw new ConfigError("external_id is required");
807
1644
  }
1645
+ if (!request.email?.trim()) {
1646
+ throw new ConfigError("email is required");
1647
+ }
1648
+ if (!isValidEmail(request.email)) {
1649
+ throw new ConfigError("invalid email format");
1650
+ }
808
1651
  const response = await this.http.json("/customers", {
809
1652
  method: "PUT",
810
1653
  body: request,
@@ -906,10 +1749,7 @@ var TiersClient = class {
906
1749
  // src/http.ts
907
1750
  var HTTPClient = class {
908
1751
  constructor(cfg) {
909
- const baseFromEnv = baseUrlForEnvironment(cfg.environment);
910
- const resolvedBase = normalizeBaseUrl(
911
- cfg.baseUrl || baseFromEnv || DEFAULT_BASE_URL
912
- );
1752
+ const resolvedBase = normalizeBaseUrl(cfg.baseUrl || DEFAULT_BASE_URL);
913
1753
  if (!isValidHttpUrl(resolvedBase)) {
914
1754
  throw new ConfigError(
915
1755
  "baseUrl must start with http:// or https://"
@@ -1122,12 +1962,6 @@ function normalizeBaseUrl(value) {
1122
1962
  function isValidHttpUrl(value) {
1123
1963
  return /^https?:\/\//i.test(value);
1124
1964
  }
1125
- function baseUrlForEnvironment(env) {
1126
- if (!env || env === "production") return void 0;
1127
- if (env === "staging") return STAGING_BASE_URL;
1128
- if (env === "sandbox") return SANDBOX_BASE_URL;
1129
- return void 0;
1130
- }
1131
1965
  function normalizeRetryConfig(retry) {
1132
1966
  if (retry === false) return void 0;
1133
1967
  const cfg = retry || {};
@@ -1246,7 +2080,7 @@ var ModelRelay = class {
1246
2080
  if (!cfg.key && !cfg.token) {
1247
2081
  throw new ConfigError("Provide an API key or access token");
1248
2082
  }
1249
- this.baseUrl = resolveBaseUrl(cfg.environment, cfg.baseUrl);
2083
+ this.baseUrl = resolveBaseUrl(cfg.baseUrl);
1250
2084
  const http = new HTTPClient({
1251
2085
  baseUrl: this.baseUrl,
1252
2086
  apiKey: cfg.key,
@@ -1257,7 +2091,6 @@ var ModelRelay = class {
1257
2091
  timeoutMs: cfg.timeoutMs,
1258
2092
  retry: cfg.retry,
1259
2093
  defaultHeaders: cfg.defaultHeaders,
1260
- environment: cfg.environment,
1261
2094
  metrics: cfg.metrics,
1262
2095
  trace: cfg.trace
1263
2096
  });
@@ -1280,8 +2113,8 @@ var ModelRelay = class {
1280
2113
  });
1281
2114
  }
1282
2115
  };
1283
- function resolveBaseUrl(env, override) {
1284
- const base = override || (env === "staging" ? STAGING_BASE_URL : env === "sandbox" ? SANDBOX_BASE_URL : DEFAULT_BASE_URL);
2116
+ function resolveBaseUrl(override) {
2117
+ const base = override || DEFAULT_BASE_URL;
1285
2118
  return base.replace(/\/+$/, "");
1286
2119
  }
1287
2120
  export {
@@ -1295,16 +2128,39 @@ export {
1295
2128
  DEFAULT_CLIENT_HEADER,
1296
2129
  DEFAULT_CONNECT_TIMEOUT_MS,
1297
2130
  DEFAULT_REQUEST_TIMEOUT_MS,
2131
+ ErrorCodes,
1298
2132
  ModelRelay,
1299
2133
  ModelRelayError,
1300
2134
  Models,
1301
2135
  Providers,
1302
- SANDBOX_BASE_URL,
1303
2136
  SDK_VERSION,
1304
- STAGING_BASE_URL,
1305
2137
  StopReasons,
1306
2138
  TiersClient,
2139
+ ToolArgsError,
2140
+ ToolCallAccumulator,
2141
+ ToolChoiceTypes,
2142
+ ToolRegistry,
2143
+ ToolTypes,
1307
2144
  TransportError,
2145
+ assistantMessageWithToolCalls,
2146
+ createAccessTokenAuth,
2147
+ createApiKeyAuth,
2148
+ createAssistantMessage,
2149
+ createFunctionCall,
2150
+ createFunctionTool,
2151
+ createFunctionToolFromSchema,
2152
+ createRetryMessages,
2153
+ createSystemMessage,
2154
+ createToolCall,
2155
+ createUsage,
2156
+ createUserMessage,
2157
+ createWebTool,
2158
+ executeWithRetry,
2159
+ firstToolCall,
2160
+ formatToolErrorForModel,
2161
+ getRetryableErrors,
2162
+ hasRetryableErrors,
2163
+ hasToolCalls,
1308
2164
  isPublishableKey,
1309
2165
  mergeMetrics,
1310
2166
  mergeTrace,
@@ -1313,6 +2169,15 @@ export {
1313
2169
  normalizeProvider,
1314
2170
  normalizeStopReason,
1315
2171
  parseErrorResponse,
2172
+ parseToolArgs,
2173
+ parseToolArgsRaw,
1316
2174
  providerToString,
1317
- stopReasonToString
2175
+ respondToToolCall,
2176
+ stopReasonToString,
2177
+ toolChoiceAuto,
2178
+ toolChoiceNone,
2179
+ toolChoiceRequired,
2180
+ toolResultMessage,
2181
+ tryParseToolArgs,
2182
+ zodToJsonSchema
1318
2183
  };