@modelrelay/sdk 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,12 +34,26 @@ __export(index_exports, {
34
34
  ModelRelayError: () => ModelRelayError,
35
35
  Models: () => Models,
36
36
  Providers: () => Providers,
37
- SANDBOX_BASE_URL: () => SANDBOX_BASE_URL,
38
37
  SDK_VERSION: () => SDK_VERSION,
39
- STAGING_BASE_URL: () => STAGING_BASE_URL,
40
38
  StopReasons: () => StopReasons,
41
39
  TiersClient: () => TiersClient,
40
+ ToolArgsError: () => ToolArgsError,
41
+ ToolCallAccumulator: () => ToolCallAccumulator,
42
+ ToolChoiceTypes: () => ToolChoiceTypes,
43
+ ToolRegistry: () => ToolRegistry,
44
+ ToolTypes: () => ToolTypes,
42
45
  TransportError: () => TransportError,
46
+ assistantMessageWithToolCalls: () => assistantMessageWithToolCalls,
47
+ createFunctionTool: () => createFunctionTool,
48
+ createFunctionToolFromSchema: () => createFunctionToolFromSchema,
49
+ createRetryMessages: () => createRetryMessages,
50
+ createWebSearchTool: () => createWebSearchTool,
51
+ executeWithRetry: () => executeWithRetry,
52
+ firstToolCall: () => firstToolCall,
53
+ formatToolErrorForModel: () => formatToolErrorForModel,
54
+ getRetryableErrors: () => getRetryableErrors,
55
+ hasRetryableErrors: () => hasRetryableErrors,
56
+ hasToolCalls: () => hasToolCalls,
43
57
  isPublishableKey: () => isPublishableKey,
44
58
  mergeMetrics: () => mergeMetrics,
45
59
  mergeTrace: () => mergeTrace,
@@ -48,8 +62,17 @@ __export(index_exports, {
48
62
  normalizeProvider: () => normalizeProvider,
49
63
  normalizeStopReason: () => normalizeStopReason,
50
64
  parseErrorResponse: () => parseErrorResponse,
65
+ parseToolArgs: () => parseToolArgs,
66
+ parseToolArgsRaw: () => parseToolArgsRaw,
51
67
  providerToString: () => providerToString,
52
- stopReasonToString: () => stopReasonToString
68
+ respondToToolCall: () => respondToToolCall,
69
+ stopReasonToString: () => stopReasonToString,
70
+ toolChoiceAuto: () => toolChoiceAuto,
71
+ toolChoiceNone: () => toolChoiceNone,
72
+ toolChoiceRequired: () => toolChoiceRequired,
73
+ toolResultMessage: () => toolResultMessage,
74
+ tryParseToolArgs: () => tryParseToolArgs,
75
+ zodToJsonSchema: () => zodToJsonSchema
53
76
  });
54
77
  module.exports = __toCommonJS(index_exports);
55
78
 
@@ -273,7 +296,7 @@ function isTokenReusable(token) {
273
296
  // package.json
274
297
  var package_default = {
275
298
  name: "@modelrelay/sdk",
276
- version: "0.5.0",
299
+ version: "0.7.0",
277
300
  description: "TypeScript SDK for the ModelRelay API",
278
301
  type: "module",
279
302
  main: "dist/index.cjs",
@@ -307,15 +330,14 @@ var package_default = {
307
330
  devDependencies: {
308
331
  tsup: "^8.2.4",
309
332
  typescript: "^5.6.3",
310
- vitest: "^2.1.4"
333
+ vitest: "^2.1.4",
334
+ zod: "^3.23.0"
311
335
  }
312
336
  };
313
337
 
314
338
  // src/types.ts
315
339
  var SDK_VERSION = package_default.version || "0.0.0";
316
340
  var DEFAULT_BASE_URL = "https://api.modelrelay.ai/api/v1";
317
- var STAGING_BASE_URL = "https://api-stg.modelrelay.ai/api/v1";
318
- var SANDBOX_BASE_URL = "https://api.sandbox.modelrelay.ai/api/v1";
319
341
  var DEFAULT_CLIENT_HEADER = `modelrelay-ts/${SDK_VERSION}`;
320
342
  var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
321
343
  var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
@@ -337,7 +359,6 @@ var Providers = {
337
359
  OpenAI: "openai",
338
360
  Anthropic: "anthropic",
339
361
  Grok: "grok",
340
- OpenRouter: "openrouter",
341
362
  Echo: "echo"
342
363
  };
343
364
  var Models = {
@@ -347,11 +368,23 @@ var Models = {
347
368
  AnthropicClaude35HaikuLatest: "anthropic/claude-3-5-haiku-latest",
348
369
  AnthropicClaude35SonnetLatest: "anthropic/claude-3-5-sonnet-latest",
349
370
  AnthropicClaudeOpus45: "anthropic/claude-opus-4-5-20251101",
350
- OpenRouterClaude35Haiku: "anthropic/claude-3.5-haiku",
371
+ AnthropicClaude35Haiku: "anthropic/claude-3.5-haiku",
351
372
  Grok2: "grok-2",
352
- Grok4Fast: "grok-4-fast",
373
+ Grok4_1FastNonReasoning: "grok-4-1-fast-non-reasoning",
374
+ Grok4_1FastReasoning: "grok-4-1-fast-reasoning",
353
375
  Echo1: "echo-1"
354
376
  };
377
+ var ToolTypes = {
378
+ Function: "function",
379
+ WebSearch: "web_search",
380
+ XSearch: "x_search",
381
+ CodeExecution: "code_execution"
382
+ };
383
+ var ToolChoiceTypes = {
384
+ Auto: "auto",
385
+ Required: "required",
386
+ None: "none"
387
+ };
355
388
  function mergeMetrics(base, override) {
356
389
  if (!base && !override) return void 0;
357
390
  return {
@@ -588,7 +621,7 @@ var ChatCompletionsStream = class {
588
621
  const context = this.enrichContext(evt);
589
622
  this.context = context;
590
623
  this.trace?.streamEvent?.({ context, event: evt });
591
- if (evt.type === "message_start" || evt.type === "message_delta" || evt.type === "message_stop") {
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") {
592
625
  this.recordFirstToken();
593
626
  }
594
627
  if (evt.type === "message_stop" && evt.usage && this.metrics?.usage) {
@@ -673,11 +706,15 @@ function mapChatEvent(raw, requestId) {
673
706
  const model = normalizeModelId(p.model || p?.message?.model);
674
707
  const stopReason = normalizeStopReason(p.stop_reason);
675
708
  const textDelta = extractTextDelta(p);
709
+ const toolCallDelta = extractToolCallDelta(p, type);
710
+ const toolCalls = extractToolCalls(p, type);
676
711
  return {
677
712
  type,
678
713
  event: raw.event || type,
679
714
  data: p,
680
715
  textDelta,
716
+ toolCallDelta,
717
+ toolCalls,
681
718
  responseId,
682
719
  model,
683
720
  stopReason,
@@ -697,6 +734,12 @@ function normalizeEventType(eventName, payload) {
697
734
  return "message_delta";
698
735
  case "message_stop":
699
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";
700
743
  case "ping":
701
744
  return "ping";
702
745
  default:
@@ -707,6 +750,9 @@ function extractTextDelta(payload) {
707
750
  if (!payload || typeof payload !== "object") {
708
751
  return void 0;
709
752
  }
753
+ if (typeof payload.text_delta === "string" && payload.text_delta !== "") {
754
+ return payload.text_delta;
755
+ }
710
756
  if (typeof payload.delta === "string") {
711
757
  return payload.delta;
712
758
  }
@@ -720,9 +766,56 @@ function extractTextDelta(payload) {
720
766
  }
721
767
  return void 0;
722
768
  }
769
+ function extractToolCallDelta(payload, type) {
770
+ if (!payload || typeof payload !== "object") {
771
+ return void 0;
772
+ }
773
+ if (type !== "tool_use_start" && type !== "tool_use_delta") {
774
+ return void 0;
775
+ }
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;
815
+ }
723
816
  function normalizeChatResponse(payload, requestId) {
724
817
  const p = payload;
725
- return {
818
+ const response = {
726
819
  id: p?.id,
727
820
  provider: normalizeProvider(p?.provider),
728
821
  content: Array.isArray(p?.content) ? p.content : p?.content ? [String(p.content)] : [],
@@ -731,6 +824,17 @@ function normalizeChatResponse(payload, requestId) {
731
824
  usage: normalizeUsage(p?.usage),
732
825
  requestId
733
826
  };
827
+ if (p?.tool_calls?.length) {
828
+ response.toolCalls = normalizeToolCalls(p.tool_calls);
829
+ }
830
+ return response;
831
+ }
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
+ }));
734
838
  }
735
839
  function normalizeUsage(payload) {
736
840
  if (!payload) {
@@ -758,13 +862,65 @@ function buildProxyBody(params, metadata) {
758
862
  if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
759
863
  if (params.stop?.length) body.stop = params.stop;
760
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);
761
867
  return body;
762
868
  }
763
869
  function normalizeMessages(messages) {
764
- return messages.map((msg) => ({
765
- role: msg.role || "user",
766
- content: msg.content
767
- }));
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
+ });
887
+ }
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
+ });
921
+ }
922
+ function normalizeToolChoice(tc) {
923
+ return { type: tc.type };
768
924
  }
769
925
  function requestIdFromHeaders(headers) {
770
926
  return headers.get(REQUEST_ID_HEADER) || headers.get("X-Request-Id") || void 0;
@@ -961,10 +1117,7 @@ var TiersClient = class {
961
1117
  // src/http.ts
962
1118
  var HTTPClient = class {
963
1119
  constructor(cfg) {
964
- const baseFromEnv = baseUrlForEnvironment(cfg.environment);
965
- const resolvedBase = normalizeBaseUrl(
966
- cfg.baseUrl || baseFromEnv || DEFAULT_BASE_URL
967
- );
1120
+ const resolvedBase = normalizeBaseUrl(cfg.baseUrl || DEFAULT_BASE_URL);
968
1121
  if (!isValidHttpUrl(resolvedBase)) {
969
1122
  throw new ConfigError(
970
1123
  "baseUrl must start with http:// or https://"
@@ -1177,12 +1330,6 @@ function normalizeBaseUrl(value) {
1177
1330
  function isValidHttpUrl(value) {
1178
1331
  return /^https?:\/\//i.test(value);
1179
1332
  }
1180
- function baseUrlForEnvironment(env) {
1181
- if (!env || env === "production") return void 0;
1182
- if (env === "staging") return STAGING_BASE_URL;
1183
- if (env === "sandbox") return SANDBOX_BASE_URL;
1184
- return void 0;
1185
- }
1186
1333
  function normalizeRetryConfig(retry) {
1187
1334
  if (retry === false) return void 0;
1188
1335
  const cfg = retry || {};
@@ -1294,6 +1441,610 @@ function withRequestId(context, headers) {
1294
1441
  return { ...context, requestId };
1295
1442
  }
1296
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;
1483
+ }
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
+ }
1533
+ }
1534
+ }
1535
+ if (def.description) {
1536
+ result.description = def.description;
1537
+ }
1538
+ return result;
1539
+ }
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
+ }
1567
+ }
1568
+ }
1569
+ if (def.description) {
1570
+ result.description = def.description;
1571
+ }
1572
+ return result;
1573
+ }
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;
1581
+ }
1582
+ if (def.maxLength !== void 0 && def.maxLength !== null) {
1583
+ result.maxItems = def.maxLength.value;
1584
+ }
1585
+ if (def.description) {
1586
+ result.description = def.description;
1587
+ }
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);
1601
+ }
1602
+ }
1603
+ const result = {
1604
+ type: "object",
1605
+ properties
1606
+ };
1607
+ if (required.length > 0) {
1608
+ result.required = required;
1609
+ }
1610
+ if (def.description) {
1611
+ result.description = def.description;
1612
+ }
1613
+ const unknownKeys = def.unknownKeys;
1614
+ if (unknownKeys === "strict") {
1615
+ result.additionalProperties = false;
1616
+ }
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;
1626
+ }
1627
+ return result;
1628
+ }
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;
1637
+ }
1638
+ return result;
1639
+ }
1640
+ function convertZodUnion(def) {
1641
+ const options = def.options;
1642
+ const result = {
1643
+ anyOf: options.map(convertZodType)
1644
+ };
1645
+ if (def.description) {
1646
+ result.description = def.description;
1647
+ }
1648
+ return result;
1649
+ }
1650
+ function convertZodNullable(def) {
1651
+ const inner = convertZodType(def.innerType);
1652
+ return {
1653
+ anyOf: [inner, { type: "null" }]
1654
+ };
1655
+ }
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;
1663
+ }
1664
+ return result;
1665
+ }
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;
1676
+ }
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;
1687
+ }
1688
+ return {
1689
+ type: ToolTypes.Function,
1690
+ function: fn
1691
+ };
1692
+ }
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
1701
+ };
1702
+ }
1703
+ function toolChoiceAuto() {
1704
+ return { type: ToolChoiceTypes.Auto };
1705
+ }
1706
+ function toolChoiceRequired() {
1707
+ return { type: ToolChoiceTypes.Required };
1708
+ }
1709
+ function toolChoiceNone() {
1710
+ return { type: ToolChoiceTypes.None };
1711
+ }
1712
+ function hasToolCalls(response) {
1713
+ return (response.toolCalls?.length ?? 0) > 0;
1714
+ }
1715
+ function firstToolCall(response) {
1716
+ return response.toolCalls?.[0];
1717
+ }
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
+ };
1725
+ }
1726
+ function respondToToolCall(call, result) {
1727
+ return toolResultMessage(call.id, result);
1728
+ }
1729
+ function assistantMessageWithToolCalls(content, toolCalls) {
1730
+ return {
1731
+ role: "assistant",
1732
+ content,
1733
+ toolCalls
1734
+ };
1735
+ }
1736
+ var ToolCallAccumulator = class {
1737
+ constructor() {
1738
+ this.calls = /* @__PURE__ */ new Map();
1739
+ }
1740
+ /**
1741
+ * Processes a streaming tool call delta.
1742
+ * Returns true if this started a new tool call.
1743
+ */
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;
1768
+ }
1769
+ /**
1770
+ * Returns all accumulated tool calls in index order.
1771
+ */
1772
+ getToolCalls() {
1773
+ if (this.calls.size === 0) {
1774
+ return [];
1775
+ }
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
+ }
1783
+ }
1784
+ return result;
1785
+ }
1786
+ /**
1787
+ * Returns a specific tool call by index, or undefined if not found.
1788
+ */
1789
+ getToolCall(index) {
1790
+ return this.calls.get(index);
1791
+ }
1792
+ /**
1793
+ * Clears all accumulated tool calls.
1794
+ */
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);
1840
+ }
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 };
1856
+ }
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
+ );
1883
+ }
1884
+ }
1885
+ var ToolRegistry = class {
1886
+ constructor() {
1887
+ this.handlers = /* @__PURE__ */ new Map();
1888
+ }
1889
+ /**
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
1894
+ */
1895
+ register(name, handler) {
1896
+ this.handlers.set(name, handler);
1897
+ return this;
1898
+ }
1899
+ /**
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
1903
+ */
1904
+ unregister(name) {
1905
+ return this.handlers.delete(name);
1906
+ }
1907
+ /**
1908
+ * Checks if a handler is registered for the given tool name.
1909
+ */
1910
+ has(name) {
1911
+ return this.handlers.has(name);
1912
+ }
1913
+ /**
1914
+ * Returns the list of registered tool names.
1915
+ */
1916
+ getRegisteredTools() {
1917
+ return Array.from(this.handlers.keys());
1918
+ }
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
+ };
1934
+ }
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
+ };
1947
+ }
1948
+ try {
1949
+ const result = await handler(args, call);
1950
+ return {
1951
+ toolCallId: call.id,
1952
+ toolName,
1953
+ result
1954
+ };
1955
+ } 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
+ };
1965
+ }
1966
+ }
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)));
1974
+ }
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
+ });
1986
+ }
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.");
1995
+ }
1996
+ return lines.join("\n");
1997
+ }
1998
+ function hasRetryableErrors(results) {
1999
+ return results.some((r) => r.error && r.isRetryable);
2000
+ }
2001
+ function getRetryableErrors(results) {
2002
+ return results.filter((r) => r.error && r.isRetryable);
2003
+ }
2004
+ function createRetryMessages(results) {
2005
+ return results.filter((r) => r.error && r.isRetryable).map((r) => toolResultMessage(r.toolCallId, formatToolErrorForModel(r)));
2006
+ }
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());
2034
+ }
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());
2042
+ }
2043
+ currentCalls = newCalls;
2044
+ }
2045
+ return Array.from(successfulResults.values());
2046
+ }
2047
+
1297
2048
  // src/index.ts
1298
2049
  var ModelRelay = class {
1299
2050
  constructor(options) {
@@ -1301,7 +2052,7 @@ var ModelRelay = class {
1301
2052
  if (!cfg.key && !cfg.token) {
1302
2053
  throw new ConfigError("Provide an API key or access token");
1303
2054
  }
1304
- this.baseUrl = resolveBaseUrl(cfg.environment, cfg.baseUrl);
2055
+ this.baseUrl = resolveBaseUrl(cfg.baseUrl);
1305
2056
  const http = new HTTPClient({
1306
2057
  baseUrl: this.baseUrl,
1307
2058
  apiKey: cfg.key,
@@ -1312,7 +2063,6 @@ var ModelRelay = class {
1312
2063
  timeoutMs: cfg.timeoutMs,
1313
2064
  retry: cfg.retry,
1314
2065
  defaultHeaders: cfg.defaultHeaders,
1315
- environment: cfg.environment,
1316
2066
  metrics: cfg.metrics,
1317
2067
  trace: cfg.trace
1318
2068
  });
@@ -1335,8 +2085,8 @@ var ModelRelay = class {
1335
2085
  });
1336
2086
  }
1337
2087
  };
1338
- function resolveBaseUrl(env, override) {
1339
- const base = override || (env === "staging" ? STAGING_BASE_URL : env === "sandbox" ? SANDBOX_BASE_URL : DEFAULT_BASE_URL);
2088
+ function resolveBaseUrl(override) {
2089
+ const base = override || DEFAULT_BASE_URL;
1340
2090
  return base.replace(/\/+$/, "");
1341
2091
  }
1342
2092
  // Annotate the CommonJS export names for ESM import in node:
@@ -1355,12 +2105,26 @@ function resolveBaseUrl(env, override) {
1355
2105
  ModelRelayError,
1356
2106
  Models,
1357
2107
  Providers,
1358
- SANDBOX_BASE_URL,
1359
2108
  SDK_VERSION,
1360
- STAGING_BASE_URL,
1361
2109
  StopReasons,
1362
2110
  TiersClient,
2111
+ ToolArgsError,
2112
+ ToolCallAccumulator,
2113
+ ToolChoiceTypes,
2114
+ ToolRegistry,
2115
+ ToolTypes,
1363
2116
  TransportError,
2117
+ assistantMessageWithToolCalls,
2118
+ createFunctionTool,
2119
+ createFunctionToolFromSchema,
2120
+ createRetryMessages,
2121
+ createWebSearchTool,
2122
+ executeWithRetry,
2123
+ firstToolCall,
2124
+ formatToolErrorForModel,
2125
+ getRetryableErrors,
2126
+ hasRetryableErrors,
2127
+ hasToolCalls,
1364
2128
  isPublishableKey,
1365
2129
  mergeMetrics,
1366
2130
  mergeTrace,
@@ -1369,6 +2133,15 @@ function resolveBaseUrl(env, override) {
1369
2133
  normalizeProvider,
1370
2134
  normalizeStopReason,
1371
2135
  parseErrorResponse,
2136
+ parseToolArgs,
2137
+ parseToolArgsRaw,
1372
2138
  providerToString,
1373
- stopReasonToString
2139
+ respondToToolCall,
2140
+ stopReasonToString,
2141
+ toolChoiceAuto,
2142
+ toolChoiceNone,
2143
+ toolChoiceRequired,
2144
+ toolResultMessage,
2145
+ tryParseToolArgs,
2146
+ zodToJsonSchema
1374
2147
  });