@modelrelay/sdk 1.25.0 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,283 +1,76 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
6
-
7
- // src/errors.ts
8
- var ErrorCodes = {
9
- NOT_FOUND: "NOT_FOUND",
10
- VALIDATION_ERROR: "VALIDATION_ERROR",
11
- RATE_LIMIT: "RATE_LIMIT",
12
- UNAUTHORIZED: "UNAUTHORIZED",
13
- FORBIDDEN: "FORBIDDEN",
14
- CONFLICT: "CONFLICT",
15
- INTERNAL_ERROR: "INTERNAL_ERROR",
16
- SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
17
- INVALID_INPUT: "INVALID_INPUT",
18
- PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
19
- METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED",
20
- /** Identity provider + subject required for identity-based auth. */
21
- IDENTITY_REQUIRED: "IDENTITY_REQUIRED",
22
- /** Auto-provision disabled for the project. */
23
- AUTO_PROVISION_DISABLED: "AUTO_PROVISION_DISABLED",
24
- /** Auto-provision tier misconfigured for the project. */
25
- AUTO_PROVISION_MISCONFIGURED: "AUTO_PROVISION_MISCONFIGURED",
26
- /** Email required for auto-provisioning a new customer. */
27
- EMAIL_REQUIRED: "EMAIL_REQUIRED"
28
- };
29
- var ModelRelayError = class extends Error {
30
- constructor(message, opts) {
31
- super(message);
32
- this.name = this.constructor.name;
33
- this.category = opts.category;
34
- this.status = opts.status;
35
- this.code = opts.code;
36
- this.requestId = opts.requestId;
37
- this.fields = opts.fields;
38
- this.data = opts.data;
39
- this.retries = opts.retries;
40
- this.cause = opts.cause;
41
- }
42
- };
43
- var ConfigError = class extends ModelRelayError {
44
- constructor(message, data) {
45
- super(message, { category: "config", status: 400, data });
46
- }
47
- };
48
- var TransportError = class extends ModelRelayError {
49
- constructor(message, opts) {
50
- super(message, {
51
- category: "transport",
52
- status: opts.kind === "timeout" ? 408 : 0,
53
- retries: opts.retries,
54
- cause: opts.cause,
55
- data: opts.cause
56
- });
57
- this.kind = opts.kind;
58
- }
59
- };
60
- var StreamProtocolError = class extends TransportError {
61
- constructor(opts) {
62
- const got = opts.receivedContentType?.trim() || "<missing>";
63
- super(`expected NDJSON stream (${opts.expectedContentType}), got Content-Type ${got}`, {
64
- kind: "request"
65
- });
66
- this.expectedContentType = opts.expectedContentType;
67
- this.receivedContentType = opts.receivedContentType?.trim() || void 0;
68
- this.status = opts.status;
69
- }
70
- };
71
- var StreamTimeoutError = class extends ModelRelayError {
72
- constructor(streamKind, timeoutMs) {
73
- const label = streamKind === "ttft" ? "TTFT" : streamKind === "idle" ? "idle" : "total";
74
- super(`stream ${label} timeout after ${timeoutMs}ms`, { category: "transport", status: 408 });
75
- this.kind = streamKind;
76
- this.timeoutMs = timeoutMs;
77
- }
78
- };
79
- var APIError = class extends ModelRelayError {
80
- constructor(message, opts) {
81
- super(message, {
82
- category: "api",
83
- status: opts.status,
84
- code: opts.code,
85
- requestId: opts.requestId,
86
- fields: opts.fields,
87
- data: opts.data,
88
- retries: opts.retries
89
- });
90
- }
91
- /** Returns true if the error is a not found error. */
92
- isNotFound() {
93
- return this.code === ErrorCodes.NOT_FOUND;
94
- }
95
- /** Returns true if the error is a validation error. */
96
- isValidation() {
97
- return this.code === ErrorCodes.VALIDATION_ERROR || this.code === ErrorCodes.INVALID_INPUT;
98
- }
99
- /** Returns true if the error is a rate limit error. */
100
- isRateLimit() {
101
- return this.code === ErrorCodes.RATE_LIMIT;
102
- }
103
- /** Returns true if the error is an unauthorized error. */
104
- isUnauthorized() {
105
- return this.code === ErrorCodes.UNAUTHORIZED;
106
- }
107
- /** Returns true if the error is a forbidden error. */
108
- isForbidden() {
109
- return this.code === ErrorCodes.FORBIDDEN;
110
- }
111
- /** Returns true if the error is a service unavailable error. */
112
- isUnavailable() {
113
- return this.code === ErrorCodes.SERVICE_UNAVAILABLE;
114
- }
115
- /**
116
- * Returns true if the error indicates identity is missing/invalid for identity-based auth.
117
- */
118
- isIdentityRequired() {
119
- return this.code === ErrorCodes.IDENTITY_REQUIRED;
120
- }
121
- /**
122
- * Returns true if auto-provisioning is disabled for the project.
123
- * To resolve: configure customer auto-provisioning on the project (select a default tier).
124
- */
125
- isAutoProvisionDisabled() {
126
- return this.code === ErrorCodes.AUTO_PROVISION_DISABLED;
127
- }
128
- /**
129
- * Returns true if email is required for auto-provisioning a new customer.
130
- * To resolve: provide the 'email' field in your frontend token request.
131
- */
132
- isEmailRequired() {
133
- return this.code === ErrorCodes.EMAIL_REQUIRED;
134
- }
135
- /**
136
- * Returns true if auto-provisioning is misconfigured for the project.
137
- * To resolve: ensure the configured auto-provision tier exists and belongs to the project.
138
- */
139
- isAutoProvisionMisconfigured() {
140
- return this.code === ErrorCodes.AUTO_PROVISION_MISCONFIGURED;
141
- }
142
- /**
143
- * Returns true if this is a customer provisioning error (identity not found + auto-provision disabled/misconfigured, or email required).
144
- */
145
- isProvisioningError() {
146
- return this.isAutoProvisionDisabled() || this.isAutoProvisionMisconfigured() || this.isEmailRequired();
147
- }
148
- };
149
- var WorkflowValidationError = class extends ModelRelayError {
150
- constructor(opts) {
151
- const msg = opts.issues.length === 0 ? "workflow validation error" : opts.issues[0]?.message || "workflow validation error";
152
- super(msg, {
153
- category: "api",
154
- status: opts.status,
155
- requestId: opts.requestId,
156
- data: opts.data,
157
- retries: opts.retries
158
- });
159
- this.issues = opts.issues;
160
- }
161
- };
162
- var ToolArgumentError = class extends ModelRelayError {
163
- constructor(opts) {
164
- super(opts.message, {
165
- category: "config",
166
- status: 400,
167
- cause: opts.cause
168
- });
169
- this.toolCallId = opts.toolCallId;
170
- this.toolName = opts.toolName;
171
- this.rawArguments = opts.rawArguments;
172
- }
173
- };
174
- var PathEscapeError = class extends ModelRelayError {
175
- constructor(opts) {
176
- super(`path escapes sandbox: ${opts.requestedPath}`, {
177
- category: "config",
178
- status: 403
179
- });
180
- this.requestedPath = opts.requestedPath;
181
- this.resolvedPath = opts.resolvedPath;
182
- }
183
- };
184
- function isEmailRequired(err) {
185
- return err instanceof APIError && err.isEmailRequired();
186
- }
187
- function isIdentityRequired(err) {
188
- return err instanceof APIError && err.isIdentityRequired();
189
- }
190
- function isAutoProvisionDisabled(err) {
191
- return err instanceof APIError && err.isAutoProvisionDisabled();
192
- }
193
- function isAutoProvisionMisconfigured(err) {
194
- return err instanceof APIError && err.isAutoProvisionMisconfigured();
195
- }
196
- function isProvisioningError(err) {
197
- return err instanceof APIError && err.isProvisioningError();
198
- }
199
- async function parseErrorResponse(response, retries) {
200
- const requestId = response.headers.get("X-ModelRelay-Request-Id") || response.headers.get("X-Request-Id") || void 0;
201
- const fallbackMessage = response.statusText || "Request failed";
202
- const status = response.status || 500;
203
- let bodyText = "";
204
- let bodyReadErr;
205
- try {
206
- bodyText = await response.text();
207
- } catch (err) {
208
- bodyReadErr = err;
209
- }
210
- if (!bodyText) {
211
- return new APIError(fallbackMessage, {
212
- status,
213
- requestId,
214
- retries,
215
- data: bodyReadErr ? {
216
- body_read_error: bodyReadErr instanceof Error ? bodyReadErr.message : String(bodyReadErr)
217
- } : void 0
218
- });
219
- }
220
- try {
221
- const parsed = JSON.parse(bodyText);
222
- const parsedObj = typeof parsed === "object" && parsed !== null ? parsed : null;
223
- const issues = Array.isArray(parsedObj?.issues) ? parsedObj?.issues : null;
224
- if (status === 400 && issues && issues.length > 0) {
225
- const normalized = [];
226
- for (const raw of issues) {
227
- if (!raw || typeof raw !== "object") continue;
228
- const obj = raw;
229
- const code = typeof obj.code === "string" ? obj.code : "";
230
- const path2 = typeof obj.path === "string" ? obj.path : "";
231
- const message = typeof obj.message === "string" ? obj.message : "";
232
- if (!code || !path2 || !message) continue;
233
- normalized.push({ code, path: path2, message });
234
- }
235
- if (normalized.length > 0) {
236
- return new WorkflowValidationError({
237
- status,
238
- requestId,
239
- issues: normalized,
240
- retries,
241
- data: parsed
242
- });
243
- }
244
- }
245
- if (parsedObj?.error && typeof parsedObj.error === "object") {
246
- const errPayload = parsedObj.error;
247
- const message = errPayload?.message || fallbackMessage;
248
- const code = errPayload?.code || void 0;
249
- const fields = Array.isArray(errPayload?.fields) ? errPayload?.fields : void 0;
250
- const parsedStatus = typeof errPayload?.status === "number" ? errPayload.status : status;
251
- return new APIError(message, {
252
- status: parsedStatus,
253
- code,
254
- fields,
255
- requestId: parsedObj?.request_id || parsedObj?.requestId || requestId,
256
- data: parsed,
257
- retries
258
- });
259
- }
260
- if (parsedObj?.message || parsedObj?.code) {
261
- const message = parsedObj.message || fallbackMessage;
262
- return new APIError(message, {
263
- status,
264
- code: parsedObj.code,
265
- fields: parsedObj.fields,
266
- requestId: parsedObj?.request_id || parsedObj?.requestId || requestId,
267
- data: parsed,
268
- retries
269
- });
270
- }
271
- return new APIError(fallbackMessage, {
272
- status,
273
- requestId,
274
- data: parsed,
275
- retries
276
- });
277
- } catch {
278
- return new APIError(bodyText, { status, requestId, retries });
279
- }
280
- }
1
+ import {
2
+ APIError,
3
+ BillingProviders,
4
+ ConfigError,
5
+ ContentPartTypes,
6
+ DEFAULT_BASE_URL,
7
+ DEFAULT_CLIENT_HEADER,
8
+ DEFAULT_CONNECT_TIMEOUT_MS,
9
+ DEFAULT_REQUEST_TIMEOUT_MS,
10
+ ErrorCodes,
11
+ InputItemTypes,
12
+ MessageRoles,
13
+ ModelRelayError,
14
+ OutputFormatTypes,
15
+ OutputItemTypes,
16
+ PathEscapeError,
17
+ SDK_VERSION,
18
+ StopReasons,
19
+ StreamProtocolError,
20
+ StreamTimeoutError,
21
+ SubscriptionStatuses,
22
+ ToolArgsError,
23
+ ToolArgumentError,
24
+ ToolCallAccumulator,
25
+ ToolChoiceTypes,
26
+ ToolRegistry,
27
+ ToolTypes,
28
+ TransportError,
29
+ WebToolIntents,
30
+ WorkflowValidationError,
31
+ __export,
32
+ asModelId,
33
+ asProviderId,
34
+ asTierCode,
35
+ assistantMessageWithToolCalls,
36
+ createAssistantMessage,
37
+ createFunctionCall,
38
+ createFunctionTool,
39
+ createFunctionToolFromSchema,
40
+ createRetryMessages,
41
+ createSystemMessage,
42
+ createToolCall,
43
+ createUsage,
44
+ createUserMessage,
45
+ createWebTool,
46
+ executeWithRetry,
47
+ firstToolCall,
48
+ formatToolErrorForModel,
49
+ getRetryableErrors,
50
+ hasRetryableErrors,
51
+ hasToolCalls,
52
+ isAutoProvisionDisabled,
53
+ isAutoProvisionMisconfigured,
54
+ isEmailRequired,
55
+ isIdentityRequired,
56
+ isProvisioningError,
57
+ mergeMetrics,
58
+ mergeTrace,
59
+ modelToString,
60
+ normalizeModelId,
61
+ normalizeStopReason,
62
+ parseErrorResponse,
63
+ parseToolArgs,
64
+ parseToolArgsRaw,
65
+ respondToToolCall,
66
+ stopReasonToString,
67
+ toolChoiceAuto,
68
+ toolChoiceNone,
69
+ toolChoiceRequired,
70
+ toolResultMessage,
71
+ tryParseToolArgs,
72
+ zodToJsonSchema
73
+ } from "./chunk-G5H7EY4F.js";
281
74
 
282
75
  // src/api_keys.ts
283
76
  var PUBLISHABLE_PREFIX = "mr_pk_";
@@ -580,8 +373,8 @@ var AuthClient = class {
580
373
  params.set("provider", request.provider);
581
374
  }
582
375
  const queryString = params.toString();
583
- const path2 = queryString ? `/auth/device/start?${queryString}` : "/auth/device/start";
584
- const apiResp = await this.http.json(path2, {
376
+ const path = queryString ? `/auth/device/start?${queryString}` : "/auth/device/start";
377
+ const apiResp = await this.http.json(path, {
585
378
  method: "POST",
586
379
  apiKey: this.apiKey
587
380
  });
@@ -701,1216 +494,378 @@ function isTokenReusable(token) {
701
494
  return token.expiresAt.getTime() - Date.now() > 6e4;
702
495
  }
703
496
 
704
- // package.json
705
- var package_default = {
706
- name: "@modelrelay/sdk",
707
- version: "1.25.0",
708
- description: "TypeScript SDK for the ModelRelay API",
709
- type: "module",
710
- main: "dist/index.cjs",
711
- module: "dist/index.js",
712
- types: "dist/index.d.ts",
713
- exports: {
714
- ".": {
715
- types: "./dist/index.d.ts",
716
- import: "./dist/index.js",
717
- require: "./dist/index.cjs"
718
- }
719
- },
720
- publishConfig: {
721
- access: "public"
722
- },
723
- files: [
724
- "dist"
725
- ],
726
- scripts: {
727
- build: "tsup src/index.ts --format esm,cjs --dts --external playwright",
728
- dev: "tsup src/index.ts --format esm,cjs --dts --watch",
729
- lint: "tsc --noEmit --project tsconfig.lint.json",
730
- test: "vitest run",
731
- "generate:types": "openapi-typescript ../../api/openapi/api.json -o src/generated/api.ts"
732
- },
733
- keywords: [
734
- "modelrelay",
735
- "llm",
736
- "sdk",
737
- "typescript"
738
- ],
739
- author: "Shane Vitarana",
740
- license: "Apache-2.0",
741
- dependencies: {
742
- "fast-json-patch": "^3.1.1",
743
- zod: "^3.23.0"
744
- },
745
- peerDependencies: {
746
- playwright: ">=1.40.0"
747
- },
748
- peerDependenciesMeta: {
749
- playwright: {
750
- optional: true
751
- }
752
- },
753
- devDependencies: {
754
- "@types/node": "^25.0.3",
755
- "openapi-typescript": "^7.4.4",
756
- playwright: "^1.49.0",
757
- tsup: "^8.2.4",
758
- typescript: "^5.6.3",
759
- vitest: "^2.1.4"
497
+ // src/structured.ts
498
+ var StructuredDecodeError = class extends Error {
499
+ constructor(message, rawJson, attempt) {
500
+ super(`structured output decode error (attempt ${attempt}): ${message}`);
501
+ this.name = "StructuredDecodeError";
502
+ this.rawJson = rawJson;
503
+ this.attempt = attempt;
760
504
  }
761
505
  };
762
-
763
- // src/types.ts
764
- var SDK_VERSION = package_default.version || "0.0.0";
765
- var DEFAULT_BASE_URL = "https://api.modelrelay.ai/api/v1";
766
- var DEFAULT_CLIENT_HEADER = `modelrelay-ts/${SDK_VERSION}`;
767
- var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
768
- var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
769
- var StopReasons = {
770
- Completed: "completed",
771
- Stop: "stop",
772
- StopSequence: "stop_sequence",
773
- EndTurn: "end_turn",
774
- MaxTokens: "max_tokens",
775
- MaxLength: "max_len",
776
- MaxContext: "max_context",
777
- ToolCalls: "tool_calls",
778
- TimeLimit: "time_limit",
779
- ContentFilter: "content_filter",
780
- Incomplete: "incomplete",
781
- Unknown: "unknown"
782
- };
783
- function asProviderId(value) {
784
- return value;
785
- }
786
- function asModelId(value) {
787
- return value;
788
- }
789
- function asTierCode(value) {
790
- return value;
791
- }
792
- var SubscriptionStatuses = {
793
- Active: "active",
794
- Trialing: "trialing",
795
- PastDue: "past_due",
796
- Canceled: "canceled",
797
- Unpaid: "unpaid",
798
- Incomplete: "incomplete",
799
- IncompleteExpired: "incomplete_expired",
800
- Paused: "paused"
801
- };
802
- var BillingProviders = {
803
- Stripe: "stripe",
804
- Crypto: "crypto",
805
- AppStore: "app_store",
806
- External: "external"
807
- };
808
- function createUsage(inputTokens, outputTokens, totalTokens) {
809
- return {
810
- inputTokens,
811
- outputTokens,
812
- totalTokens: totalTokens ?? inputTokens + outputTokens
813
- };
814
- }
815
- var MessageRoles = {
816
- User: "user",
817
- Assistant: "assistant",
818
- System: "system",
819
- Tool: "tool"
820
- };
821
- var ContentPartTypes = {
822
- Text: "text"
823
- };
824
- var InputItemTypes = {
825
- Message: "message"
826
- };
827
- var OutputItemTypes = {
828
- Message: "message"
829
- };
830
- var ToolTypes = {
831
- Function: "function",
832
- Web: "web",
833
- XSearch: "x_search",
834
- CodeExecution: "code_execution"
835
- };
836
- var WebToolModes = {
837
- Auto: "auto",
838
- SearchOnly: "search_only",
839
- FetchOnly: "fetch_only",
840
- SearchAndFetch: "search_and_fetch"
841
- };
842
- var ToolChoiceTypes = {
843
- Auto: "auto",
844
- Required: "required",
845
- None: "none"
506
+ var StructuredExhaustedError = class extends Error {
507
+ constructor(lastRawJson, allAttempts, finalError) {
508
+ const errorMsg = finalError.kind === "decode" ? finalError.message : finalError.issues.map((i) => i.message).join("; ");
509
+ super(
510
+ `structured output failed after ${allAttempts.length} attempts: ${errorMsg}`
511
+ );
512
+ this.name = "StructuredExhaustedError";
513
+ this.lastRawJson = lastRawJson;
514
+ this.allAttempts = allAttempts;
515
+ this.finalError = finalError;
516
+ }
846
517
  };
847
- var OutputFormatTypes = {
848
- Text: "text",
849
- JsonSchema: "json_schema"
518
+ var defaultRetryHandler = {
519
+ onValidationError(_attempt, _rawJson, error, _originalInput) {
520
+ const errorMsg = error.kind === "decode" ? error.message : error.issues.map((i) => `${i.path ?? ""}: ${i.message}`).join("; ");
521
+ return [{
522
+ type: "message",
523
+ role: "user",
524
+ content: [{ type: "text", text: `The previous response did not match the expected schema. Error: ${errorMsg}. Please provide a response that matches the schema exactly.` }]
525
+ }];
526
+ }
850
527
  };
851
- function mergeMetrics(base, override) {
852
- if (!base && !override) return void 0;
853
- return {
854
- ...base || {},
855
- ...override || {}
856
- };
857
- }
858
- function mergeTrace(base, override) {
859
- if (!base && !override) return void 0;
528
+ function outputFormatFromZod(schema, name = "response") {
529
+ const jsonSchema = zodToJsonSchema(schema);
860
530
  return {
861
- ...base || {},
862
- ...override || {}
531
+ type: "json_schema",
532
+ json_schema: {
533
+ name,
534
+ schema: jsonSchema,
535
+ strict: true
536
+ }
863
537
  };
864
538
  }
865
- function normalizeStopReason(value) {
866
- if (value === void 0 || value === null) return void 0;
867
- const str = String(value).trim();
868
- const lower = str.toLowerCase();
869
- for (const reason of Object.values(StopReasons)) {
870
- if (lower === reason) return reason;
871
- }
872
- switch (lower) {
873
- case "length":
874
- return StopReasons.MaxLength;
875
- default:
876
- return { other: str };
539
+ function validateWithZod(schema, data) {
540
+ const result = schema.safeParse(data);
541
+ if (result.success) {
542
+ return { success: true, data: result.data };
877
543
  }
544
+ const err = result.error;
545
+ const issuesRaw = err && typeof err === "object" && "issues" in err ? err.issues : void 0;
546
+ if (Array.isArray(issuesRaw)) {
547
+ return {
548
+ success: false,
549
+ issues: issuesRaw.map((i) => {
550
+ const ii = i && typeof i === "object" ? i : {};
551
+ return {
552
+ path: Array.isArray(ii.path) ? ii.path.filter((p) => typeof p === "string" || typeof p === "number").join(".") : void 0,
553
+ message: typeof ii.message === "string" && ii.message.trim() ? ii.message : "validation failed"
554
+ };
555
+ })
556
+ };
557
+ }
558
+ const message = err && typeof err === "object" && "message" in err ? String(err.message) : "validation failed";
559
+ return { success: false, issues: [{ message }] };
878
560
  }
879
- function stopReasonToString(value) {
880
- if (!value) return void 0;
881
- if (typeof value === "string") return value;
882
- return value.other?.trim() || void 0;
883
- }
884
- function normalizeModelId(value) {
885
- if (value === void 0 || value === null) return void 0;
886
- const str = String(value).trim();
887
- if (!str) return void 0;
888
- return str;
561
+
562
+ // src/responses_request.ts
563
+ var RESPONSES_PATH = "/responses";
564
+ var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
565
+ var REQUEST_ID_HEADER = "X-ModelRelay-Request-Id";
566
+ function makeResponsesRequest(body, options) {
567
+ return { body, options };
889
568
  }
890
- function modelToString(value) {
891
- return String(value).trim();
569
+ function asInternal(req) {
570
+ return req;
892
571
  }
893
-
894
- // src/tools.ts
895
- function createUserMessage(content) {
572
+ function mergeOptions(base, override) {
896
573
  return {
897
- type: "message",
898
- role: "user",
899
- content: [{ type: "text", text: content }]
574
+ ...base,
575
+ ...override,
576
+ headers: {
577
+ ...base.headers || {},
578
+ ...override.headers || {}
579
+ }
900
580
  };
901
581
  }
902
- function createAssistantMessage(content) {
903
- return {
904
- type: "message",
905
- role: "assistant",
906
- content: [{ type: "text", text: content }]
907
- };
908
- }
909
- function createSystemMessage(content) {
910
- return {
911
- type: "message",
912
- role: "system",
913
- content: [{ type: "text", text: content }]
914
- };
915
- }
916
- function createToolCall(id, name, args, type = ToolTypes.Function) {
917
- return {
918
- id,
919
- type,
920
- function: createFunctionCall(name, args)
921
- };
922
- }
923
- function createFunctionCall(name, args) {
924
- return { name, arguments: args };
582
+ function requestIdFromHeaders(headers) {
583
+ return headers.get(REQUEST_ID_HEADER);
925
584
  }
926
- function zodToJsonSchema(schema, options = {}) {
927
- const result = convertZodType(schema);
928
- if (options.includeSchema) {
929
- 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#";
930
- return { $schema: schemaVersion, ...result };
585
+
586
+ // src/responses_builder.ts
587
+ var ResponseBuilder = class _ResponseBuilder {
588
+ constructor(body = { input: [] }, options = {}) {
589
+ this.body = body;
590
+ this.options = options;
931
591
  }
932
- return result;
933
- }
934
- function convertZodType(schema) {
935
- const def = schema._def;
936
- const typeName = def.typeName;
937
- switch (typeName) {
938
- case "ZodString":
939
- return convertZodString(def);
940
- case "ZodNumber":
941
- return convertZodNumber(def);
942
- case "ZodBoolean":
943
- return { type: "boolean" };
944
- case "ZodNull":
945
- return { type: "null" };
946
- case "ZodArray":
947
- return convertZodArray(def);
948
- case "ZodObject":
949
- return convertZodObject(def);
950
- case "ZodEnum":
951
- return convertZodEnum(def);
952
- case "ZodNativeEnum":
953
- return convertZodNativeEnum(def);
954
- case "ZodLiteral":
955
- return { const: def.value };
956
- case "ZodUnion":
957
- return convertZodUnion(def);
958
- case "ZodOptional": {
959
- const inner = convertZodType(def.innerType);
960
- if (def.description && !inner.description) {
961
- inner.description = def.description;
962
- }
963
- return inner;
964
- }
965
- case "ZodNullable":
966
- return convertZodNullable(def);
967
- case "ZodDefault":
968
- return { ...convertZodType(def.innerType), default: def.defaultValue() };
969
- case "ZodEffects":
970
- return convertZodType(def.schema);
971
- case "ZodRecord":
972
- return convertZodRecord(def);
973
- case "ZodTuple":
974
- return convertZodTuple(def);
975
- case "ZodAny":
976
- case "ZodUnknown":
977
- return {};
978
- default:
979
- throw new ConfigError(
980
- `sdk: unsupported Zod schema type ${JSON.stringify(typeName)}; pass JSON Schema directly or use a full converter like zod-to-json-schema`,
981
- { typeName }
982
- );
592
+ with(patch) {
593
+ return new _ResponseBuilder(
594
+ patch.body ? { ...this.body, ...patch.body } : this.body,
595
+ patch.options ? { ...this.options, ...patch.options } : this.options
596
+ );
983
597
  }
984
- }
985
- function convertZodString(def) {
986
- const result = { type: "string" };
987
- const checks = def.checks;
988
- if (checks) {
989
- for (const check of checks) {
990
- switch (check.kind) {
991
- case "min":
992
- result.minLength = check.value;
993
- break;
994
- case "max":
995
- result.maxLength = check.value;
996
- break;
997
- case "length":
998
- result.minLength = check.value;
999
- result.maxLength = check.value;
1000
- break;
1001
- case "email":
1002
- result.format = "email";
1003
- break;
1004
- case "url":
1005
- result.format = "uri";
1006
- break;
1007
- case "uuid":
1008
- result.format = "uuid";
1009
- break;
1010
- case "datetime":
1011
- result.format = "date-time";
1012
- break;
1013
- case "regex":
1014
- result.pattern = check.value.source;
1015
- break;
1016
- }
1017
- }
598
+ /** @returns A new builder with the provider set. */
599
+ provider(provider) {
600
+ return this.with({ body: { provider } });
1018
601
  }
1019
- if (def.description) {
1020
- result.description = def.description;
602
+ /** @returns A new builder with the model set. */
603
+ model(model) {
604
+ return this.with({ body: { model } });
1021
605
  }
1022
- return result;
1023
- }
1024
- function convertZodNumber(def) {
1025
- const result = { type: "number" };
1026
- const checks = def.checks;
1027
- if (checks) {
1028
- for (const check of checks) {
1029
- switch (check.kind) {
1030
- case "int":
1031
- result.type = "integer";
1032
- break;
1033
- case "min":
1034
- if (check.inclusive === false) {
1035
- result.exclusiveMinimum = check.value;
1036
- } else {
1037
- result.minimum = check.value;
1038
- }
1039
- break;
1040
- case "max":
1041
- if (check.inclusive === false) {
1042
- result.exclusiveMaximum = check.value;
1043
- } else {
1044
- result.maximum = check.value;
1045
- }
1046
- break;
1047
- case "multipleOf":
1048
- result.multipleOf = check.value;
1049
- break;
1050
- }
1051
- }
606
+ /** @returns A new builder with the full input array replaced. */
607
+ input(items) {
608
+ return this.with({ body: { input: items.slice() } });
1052
609
  }
1053
- if (def.description) {
1054
- result.description = def.description;
610
+ /** @returns A new builder with the input item appended. */
611
+ item(item) {
612
+ const input = [...this.body.input ?? [], item];
613
+ return this.with({ body: { input } });
1055
614
  }
1056
- return result;
1057
- }
1058
- function convertZodArray(def) {
1059
- const result = {
1060
- type: "array",
1061
- items: convertZodType(def.type)
1062
- };
1063
- if (def.minLength !== void 0 && def.minLength !== null) {
1064
- result.minItems = def.minLength.value;
615
+ /** @returns A new builder with a message input item appended. */
616
+ message(role, content) {
617
+ return this.item({
618
+ type: "message",
619
+ role,
620
+ content: [{ type: "text", text: content }]
621
+ });
1065
622
  }
1066
- if (def.maxLength !== void 0 && def.maxLength !== null) {
1067
- result.maxItems = def.maxLength.value;
623
+ /** @returns A new builder with a system message appended. */
624
+ system(content) {
625
+ return this.message("system", content);
1068
626
  }
1069
- if (def.description) {
1070
- result.description = def.description;
627
+ /** @returns A new builder with a user message appended. */
628
+ user(content) {
629
+ return this.message("user", content);
1071
630
  }
1072
- return result;
1073
- }
1074
- function convertZodObject(def) {
1075
- const shape = def.shape;
1076
- const shapeObj = typeof shape === "function" ? shape() : shape;
1077
- const properties = {};
1078
- const required = [];
1079
- for (const [key, value] of Object.entries(shapeObj)) {
1080
- properties[key] = convertZodType(value);
1081
- const valueDef = value._def;
1082
- const isOptional = valueDef.typeName === "ZodOptional" || valueDef.typeName === "ZodDefault" || valueDef.typeName === "ZodNullable" && valueDef.innerType?._def?.typeName === "ZodDefault";
1083
- if (!isOptional) {
1084
- required.push(key);
1085
- }
1086
- }
1087
- const result = {
1088
- type: "object",
1089
- properties
1090
- };
1091
- if (required.length > 0) {
1092
- result.required = required;
631
+ /** @returns A new builder with an assistant message appended. */
632
+ assistant(content) {
633
+ return this.message("assistant", content);
1093
634
  }
1094
- if (def.description) {
1095
- result.description = def.description;
635
+ /** @returns A new builder with a tool result message appended. */
636
+ toolResultText(toolCallId, content) {
637
+ return this.item({
638
+ type: "message",
639
+ role: "tool",
640
+ toolCallId: toolCallId.trim(),
641
+ content: [{ type: "text", text: content }]
642
+ });
1096
643
  }
1097
- const unknownKeys = def.unknownKeys;
1098
- if (unknownKeys === "strict") {
1099
- result.additionalProperties = false;
644
+ /** @returns A new builder with the output format set. */
645
+ outputFormat(format) {
646
+ return this.with({ body: { output_format: format } });
1100
647
  }
1101
- return result;
1102
- }
1103
- function convertZodEnum(def) {
1104
- const result = {
1105
- type: "string",
1106
- enum: def.values
1107
- };
1108
- if (def.description) {
1109
- result.description = def.description;
648
+ /** @returns A new builder with max output tokens set. */
649
+ maxOutputTokens(max) {
650
+ return this.with({ body: { max_output_tokens: max } });
1110
651
  }
1111
- return result;
1112
- }
1113
- function convertZodNativeEnum(def) {
1114
- const enumValues = def.values;
1115
- const values = Object.values(enumValues).filter(
1116
- (v) => typeof v === "string" || typeof v === "number"
1117
- );
1118
- const result = { enum: values };
1119
- if (def.description) {
1120
- result.description = def.description;
652
+ /** @returns A new builder with temperature set. */
653
+ temperature(temp) {
654
+ return this.with({ body: { temperature: temp } });
1121
655
  }
1122
- return result;
1123
- }
1124
- function convertZodUnion(def) {
1125
- const options = def.options;
1126
- const result = {
1127
- anyOf: options.map(convertZodType)
1128
- };
1129
- if (def.description) {
1130
- result.description = def.description;
656
+ /** @returns A new builder with stop sequences set. */
657
+ stop(...stop) {
658
+ const clean = stop.map((s) => s.trim()).filter(Boolean);
659
+ return this.with({ body: { stop: clean.length ? clean : void 0 } });
1131
660
  }
1132
- return result;
1133
- }
1134
- function convertZodNullable(def) {
1135
- const inner = convertZodType(def.innerType);
1136
- return {
1137
- anyOf: [inner, { type: "null" }]
1138
- };
1139
- }
1140
- function convertZodRecord(def) {
1141
- const result = {
1142
- type: "object",
1143
- additionalProperties: convertZodType(def.valueType)
1144
- };
1145
- if (def.description) {
1146
- result.description = def.description;
661
+ /** @returns A new builder with tools replaced. */
662
+ tools(tools) {
663
+ return this.with({ body: { tools: tools.slice() } });
1147
664
  }
1148
- return result;
1149
- }
1150
- function convertZodTuple(def) {
1151
- const items = def.items;
1152
- const result = {
1153
- type: "array",
1154
- items: items.map(convertZodType),
1155
- minItems: items.length,
1156
- maxItems: items.length
1157
- };
1158
- if (def.description) {
1159
- result.description = def.description;
665
+ /** @returns A new builder with a tool appended. */
666
+ tool(tool) {
667
+ const tools = [...this.body.tools ?? [], tool];
668
+ return this.with({ body: { tools } });
1160
669
  }
1161
- return result;
1162
- }
1163
- function createFunctionToolFromSchema(name, description, schema, options) {
1164
- const jsonSchema = zodToJsonSchema(schema, options);
1165
- return createFunctionTool(name, description, jsonSchema);
1166
- }
1167
- function createFunctionTool(name, description, parameters) {
1168
- const fn = { name, description };
1169
- if (parameters) {
1170
- fn.parameters = parameters;
670
+ /** @returns A new builder with tool choice set. */
671
+ toolChoice(choice) {
672
+ return this.with({ body: { tool_choice: choice } });
1171
673
  }
1172
- return {
1173
- type: ToolTypes.Function,
1174
- function: fn
1175
- };
1176
- }
1177
- function createWebTool(options) {
1178
- return {
1179
- type: ToolTypes.Web,
1180
- web: options ? {
1181
- mode: options.mode,
1182
- allowedDomains: options.allowedDomains,
1183
- excludedDomains: options.excludedDomains,
1184
- maxUses: options.maxUses
1185
- } : void 0
1186
- };
1187
- }
1188
- function toolChoiceAuto() {
1189
- return { type: ToolChoiceTypes.Auto };
1190
- }
1191
- function toolChoiceRequired() {
1192
- return { type: ToolChoiceTypes.Required };
1193
- }
1194
- function toolChoiceNone() {
1195
- return { type: ToolChoiceTypes.None };
1196
- }
1197
- function hasToolCalls(response) {
1198
- for (const item of response.output || []) {
1199
- if (item?.toolCalls?.length) return true;
674
+ /** @returns A new builder with tool choice set to auto. */
675
+ toolChoiceAuto() {
676
+ return this.toolChoice({ type: "auto" });
1200
677
  }
1201
- return false;
1202
- }
1203
- function firstToolCall(response) {
1204
- for (const item of response.output || []) {
1205
- const call = item?.toolCalls?.[0];
1206
- if (call) return call;
678
+ /** @returns A new builder with tool choice set to required. */
679
+ toolChoiceRequired(functionName) {
680
+ return this.toolChoice({ type: "required", function: functionName });
1207
681
  }
1208
- return void 0;
1209
- }
1210
- function toolResultMessage(toolCallId, result) {
1211
- const content = typeof result === "string" ? result : JSON.stringify(result);
1212
- return {
1213
- type: "message",
1214
- role: "tool",
1215
- toolCallId,
1216
- content: [{ type: "text", text: content }]
1217
- };
1218
- }
1219
- function respondToToolCall(call, result) {
1220
- return toolResultMessage(call.id, result);
1221
- }
1222
- function assistantMessageWithToolCalls(content, toolCalls) {
1223
- return {
1224
- type: "message",
1225
- role: "assistant",
1226
- content: [{ type: "text", text: content }],
1227
- toolCalls
1228
- };
1229
- }
1230
- var ToolCallAccumulator = class {
1231
- constructor() {
1232
- this.calls = /* @__PURE__ */ new Map();
682
+ /** @returns A new builder with tool choice set to none. */
683
+ toolChoiceNone() {
684
+ return this.toolChoice({ type: "none" });
1233
685
  }
1234
- /**
1235
- * Processes a streaming tool call delta.
1236
- * Returns true if this started a new tool call.
1237
- */
1238
- processDelta(delta) {
1239
- const existing = this.calls.get(delta.index);
1240
- if (!existing) {
1241
- this.calls.set(delta.index, {
1242
- id: delta.id ?? "",
1243
- type: delta.type ?? ToolTypes.Function,
1244
- function: {
1245
- name: delta.function?.name ?? "",
1246
- arguments: delta.function?.arguments ?? ""
1247
- }
1248
- });
1249
- return true;
1250
- }
1251
- if (delta.function) {
1252
- if (delta.function.name) {
1253
- existing.function = existing.function ?? { name: "", arguments: "" };
1254
- existing.function.name = delta.function.name;
1255
- }
1256
- if (delta.function.arguments) {
1257
- existing.function = existing.function ?? { name: "", arguments: "" };
1258
- existing.function.arguments += delta.function.arguments;
1259
- }
1260
- }
1261
- return false;
686
+ /** @returns A new builder with the customer ID option set. */
687
+ customerId(customerId) {
688
+ return this.with({ options: { customerId: customerId.trim() } });
1262
689
  }
1263
- /**
1264
- * Returns all accumulated tool calls in index order.
1265
- */
1266
- getToolCalls() {
1267
- if (this.calls.size === 0) {
1268
- return [];
1269
- }
1270
- const maxIdx = Math.max(...this.calls.keys());
1271
- const result = [];
1272
- for (let i = 0; i <= maxIdx; i++) {
1273
- const call = this.calls.get(i);
1274
- if (call) {
1275
- result.push(call);
1276
- }
1277
- }
1278
- return result;
690
+ /** @returns A new builder with the request ID option set. */
691
+ requestId(requestId) {
692
+ return this.with({ options: { requestId: requestId.trim() } });
1279
693
  }
1280
- /**
1281
- * Returns a specific tool call by index, or undefined if not found.
1282
- */
1283
- getToolCall(index) {
1284
- return this.calls.get(index);
694
+ /** @returns A new builder with a single header set/overridden. */
695
+ header(key, value) {
696
+ const headers = { ...this.options.headers || {}, [key]: value };
697
+ return this.with({ options: { headers } });
1285
698
  }
1286
- /**
1287
- * Clears all accumulated tool calls.
1288
- */
1289
- reset() {
1290
- this.calls.clear();
699
+ /** @returns A new builder with headers merged. */
700
+ headers(headers) {
701
+ const merged = { ...this.options.headers || {}, ...headers };
702
+ return this.with({ options: { headers: merged } });
1291
703
  }
1292
- };
1293
- var ToolArgsError = class extends Error {
1294
- constructor(message, toolCallId, toolName, rawArguments) {
1295
- super(message);
1296
- this.name = "ToolArgsError";
1297
- this.toolCallId = toolCallId;
1298
- this.toolName = toolName;
1299
- this.rawArguments = rawArguments;
704
+ /** @returns A new builder with the request timeout option set. */
705
+ timeoutMs(timeoutMs) {
706
+ return this.with({ options: { timeoutMs: Math.max(0, timeoutMs) } });
1300
707
  }
1301
- };
1302
- function parseToolArgs(call, schema) {
1303
- const toolName = call.function?.name ?? "unknown";
1304
- const rawArgs = call.function?.arguments ?? "";
1305
- let parsed;
1306
- try {
1307
- parsed = rawArgs ? JSON.parse(rawArgs) : {};
1308
- } catch (err) {
1309
- const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
1310
- throw new ToolArgsError(
1311
- `Failed to parse arguments for tool '${toolName}': ${message}`,
1312
- call.id,
1313
- toolName,
1314
- rawArgs
1315
- );
708
+ /** @returns A new builder with the connect timeout option set. */
709
+ connectTimeoutMs(connectTimeoutMs) {
710
+ return this.with({
711
+ options: { connectTimeoutMs: Math.max(0, connectTimeoutMs) }
712
+ });
1316
713
  }
1317
- try {
1318
- return schema.parse(parsed);
1319
- } catch (err) {
1320
- let message;
1321
- if (err instanceof Error) {
1322
- const zodErr = err;
1323
- if (zodErr.errors && Array.isArray(zodErr.errors)) {
1324
- const issues = zodErr.errors.map((e) => {
1325
- const path2 = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
1326
- return `${path2}${e.message}`;
1327
- }).join("; ");
1328
- message = issues;
1329
- } else {
1330
- message = err.message;
1331
- }
1332
- } else {
1333
- message = String(err);
1334
- }
1335
- throw new ToolArgsError(
1336
- `Invalid arguments for tool '${toolName}': ${message}`,
1337
- call.id,
1338
- toolName,
1339
- rawArgs
1340
- );
714
+ /** @returns A new builder with retry configuration set. */
715
+ retry(cfg) {
716
+ return this.with({ options: { retry: cfg } });
1341
717
  }
1342
- }
1343
- function tryParseToolArgs(call, schema) {
1344
- try {
1345
- const data = parseToolArgs(call, schema);
1346
- return { success: true, data };
1347
- } catch (err) {
1348
- if (err instanceof ToolArgsError) {
1349
- return { success: false, error: err };
1350
- }
1351
- const toolName = call.function?.name ?? "unknown";
1352
- const rawArgs = call.function?.arguments ?? "";
1353
- return {
1354
- success: false,
1355
- error: new ToolArgsError(
1356
- err instanceof Error ? err.message : String(err),
1357
- call.id,
1358
- toolName,
1359
- rawArgs
1360
- )
1361
- };
718
+ /** @returns A new builder with retries disabled. */
719
+ disableRetry() {
720
+ return this.with({ options: { retry: false } });
1362
721
  }
1363
- }
1364
- function parseToolArgsRaw(call) {
1365
- const toolName = call.function?.name ?? "unknown";
1366
- const rawArgs = call.function?.arguments ?? "";
1367
- try {
1368
- return rawArgs ? JSON.parse(rawArgs) : {};
1369
- } catch (err) {
1370
- const message = err instanceof Error ? err.message : "Invalid JSON in arguments";
1371
- throw new ToolArgsError(
1372
- `Failed to parse arguments for tool '${toolName}': ${message}`,
1373
- call.id,
1374
- toolName,
1375
- rawArgs
1376
- );
722
+ /** @returns A new builder with metrics callbacks set. */
723
+ metrics(metrics) {
724
+ return this.with({ options: { metrics } });
1377
725
  }
1378
- }
1379
- var ToolRegistry = class {
1380
- constructor() {
1381
- this.handlers = /* @__PURE__ */ new Map();
726
+ /** @returns A new builder with trace callbacks set. */
727
+ trace(trace) {
728
+ return this.with({ options: { trace } });
1382
729
  }
1383
- /**
1384
- * Registers a handler function for a tool name.
1385
- * @param name - The tool name (must match the function name in the tool definition)
1386
- * @param handler - Function to execute when this tool is called
1387
- * @returns this for chaining
1388
- */
1389
- register(name, handler) {
1390
- this.handlers.set(name, handler);
1391
- return this;
730
+ /** @returns A new builder with stream time-to-first-token timeout set. */
731
+ streamTTFTTimeoutMs(timeoutMs) {
732
+ return this.with({
733
+ options: { streamTTFTTimeoutMs: Math.max(0, timeoutMs) }
734
+ });
1392
735
  }
1393
- /**
1394
- * Unregisters a tool handler.
1395
- * @param name - The tool name to unregister
1396
- * @returns true if the handler was removed, false if it didn't exist
1397
- */
1398
- unregister(name) {
1399
- return this.handlers.delete(name);
736
+ /** @returns A new builder with stream idle timeout set. */
737
+ streamIdleTimeoutMs(timeoutMs) {
738
+ return this.with({
739
+ options: { streamIdleTimeoutMs: Math.max(0, timeoutMs) }
740
+ });
1400
741
  }
1401
- /**
1402
- * Checks if a handler is registered for the given tool name.
1403
- */
1404
- has(name) {
1405
- return this.handlers.has(name);
742
+ /** @returns A new builder with total stream timeout set. */
743
+ streamTotalTimeoutMs(timeoutMs) {
744
+ return this.with({
745
+ options: { streamTotalTimeoutMs: Math.max(0, timeoutMs) }
746
+ });
1406
747
  }
1407
- /**
1408
- * Returns the list of registered tool names.
1409
- */
1410
- getRegisteredTools() {
1411
- return Array.from(this.handlers.keys());
748
+ /** @returns A new builder with the abort signal set. */
749
+ signal(signal) {
750
+ return this.with({ options: { signal } });
1412
751
  }
1413
- /**
1414
- * Executes a single tool call.
1415
- * @param call - The tool call to execute
1416
- * @returns The execution result
1417
- */
1418
- async execute(call) {
1419
- const toolName = call.function?.name ?? "";
1420
- const handler = this.handlers.get(toolName);
1421
- if (!handler) {
1422
- return {
1423
- toolCallId: call.id,
1424
- toolName,
1425
- result: null,
1426
- error: `Unknown tool: '${toolName}'. Available tools: ${this.getRegisteredTools().join(", ") || "none"}`
1427
- };
1428
- }
1429
- let args;
1430
- try {
1431
- args = call.function?.arguments ? JSON.parse(call.function.arguments) : {};
1432
- } catch (err) {
1433
- const errorMessage = err instanceof Error ? err.message : String(err);
1434
- return {
1435
- toolCallId: call.id,
1436
- toolName,
1437
- result: null,
1438
- error: `Invalid JSON in arguments: ${errorMessage}`,
1439
- isRetryable: true
1440
- };
1441
- }
1442
- try {
1443
- const result = await handler(args, call);
1444
- return {
1445
- toolCallId: call.id,
1446
- toolName,
1447
- result
1448
- };
1449
- } catch (err) {
1450
- const isRetryable = err instanceof ToolArgsError || err instanceof ToolArgumentError;
1451
- const errorMessage = err instanceof Error ? err.message : String(err);
1452
- return {
1453
- toolCallId: call.id,
1454
- toolName,
1455
- result: null,
1456
- error: errorMessage,
1457
- isRetryable
1458
- };
752
+ /** @returns A finalized, immutable request payload. */
753
+ build() {
754
+ const input = (this.body.input ?? []).slice();
755
+ if (input.length === 0) {
756
+ throw new ConfigError("at least one input item is required");
1459
757
  }
1460
- }
1461
- /**
1462
- * Executes multiple tool calls in parallel.
1463
- * @param calls - Array of tool calls to execute
1464
- * @returns Array of execution results in the same order as input
1465
- */
1466
- async executeAll(calls) {
1467
- return Promise.all(calls.map((call) => this.execute(call)));
1468
- }
1469
- /**
1470
- * Converts execution results to tool result messages.
1471
- * Useful for appending to the conversation history.
1472
- * @param results - Array of execution results
1473
- * @returns Array of tool result input items (role "tool")
1474
- */
1475
- resultsToMessages(results) {
1476
- return results.map((r) => {
1477
- const content = r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result);
1478
- return toolResultMessage(r.toolCallId, content);
1479
- });
758
+ const body = {
759
+ provider: this.body.provider,
760
+ model: this.body.model,
761
+ input,
762
+ output_format: this.body.output_format,
763
+ max_output_tokens: this.body.max_output_tokens,
764
+ temperature: this.body.temperature,
765
+ stop: this.body.stop,
766
+ tools: this.body.tools,
767
+ tool_choice: this.body.tool_choice
768
+ };
769
+ return makeResponsesRequest(body, { ...this.options || {} });
1480
770
  }
1481
771
  };
1482
- function formatToolErrorForModel(result) {
1483
- const lines = [
1484
- `Tool call error for '${result.toolName}': ${result.error}`
1485
- ];
1486
- if (result.isRetryable) {
1487
- lines.push("");
1488
- lines.push("Please correct the arguments and try again.");
1489
- }
1490
- return lines.join("\n");
1491
- }
1492
- function hasRetryableErrors(results) {
1493
- return results.some((r) => r.error && r.isRetryable);
1494
- }
1495
- function getRetryableErrors(results) {
1496
- return results.filter((r) => r.error && r.isRetryable);
772
+
773
+ // src/responses_normalize.ts
774
+ function isRecord(value) {
775
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1497
776
  }
1498
- function createRetryMessages(results) {
1499
- return results.filter((r) => r.error && r.isRetryable).map((r) => toolResultMessage(r.toolCallId, formatToolErrorForModel(r)));
777
+ function normalizeUsage(value) {
778
+ if (!isRecord(value)) {
779
+ return void 0;
780
+ }
781
+ const input = Number(value.input_tokens ?? 0);
782
+ const output = Number(value.output_tokens ?? 0);
783
+ const total = value.total_tokens === void 0 || value.total_tokens === null ? void 0 : Number(value.total_tokens);
784
+ return createUsage(
785
+ input,
786
+ output,
787
+ Number.isFinite(total) ? total : void 0
788
+ );
1500
789
  }
1501
- async function executeWithRetry(registry, toolCalls, options = {}) {
1502
- const maxRetries = options.maxRetries ?? 2;
1503
- let currentCalls = toolCalls;
1504
- let attempt = 0;
1505
- const successfulResults = /* @__PURE__ */ new Map();
1506
- while (attempt <= maxRetries) {
1507
- const results = await registry.executeAll(currentCalls);
1508
- for (const result of results) {
1509
- if (!result.error || !result.isRetryable) {
1510
- successfulResults.set(result.toolCallId, result);
1511
- }
1512
- }
1513
- const retryableResults = getRetryableErrors(results);
1514
- if (retryableResults.length === 0 || !options.onRetry) {
1515
- for (const result of results) {
1516
- if (result.error && result.isRetryable) {
1517
- successfulResults.set(result.toolCallId, result);
1518
- }
1519
- }
1520
- return Array.from(successfulResults.values());
1521
- }
1522
- attempt++;
1523
- if (attempt > maxRetries) {
1524
- for (const result of retryableResults) {
1525
- successfulResults.set(result.toolCallId, result);
1526
- }
1527
- return Array.from(successfulResults.values());
1528
- }
1529
- const errorMessages = createRetryMessages(retryableResults);
1530
- const newCalls = await options.onRetry(errorMessages, attempt);
1531
- if (newCalls.length === 0) {
1532
- for (const result of retryableResults) {
1533
- successfulResults.set(result.toolCallId, result);
1534
- }
1535
- return Array.from(successfulResults.values());
1536
- }
1537
- currentCalls = newCalls;
790
+ function normalizeCitations(value) {
791
+ if (!Array.isArray(value) || value.length === 0) return void 0;
792
+ const citations = [];
793
+ for (const item of value) {
794
+ if (!isRecord(item)) continue;
795
+ const url = item.url;
796
+ const title = item.title;
797
+ citations.push({
798
+ ...typeof url === "string" && url.trim() ? { url } : {},
799
+ ...typeof title === "string" && title.trim() ? { title } : {}
800
+ });
1538
801
  }
1539
- return Array.from(successfulResults.values());
802
+ return citations.length ? citations : void 0;
1540
803
  }
1541
-
1542
- // src/structured.ts
1543
- var StructuredDecodeError = class extends Error {
1544
- constructor(message, rawJson, attempt) {
1545
- super(`structured output decode error (attempt ${attempt}): ${message}`);
1546
- this.name = "StructuredDecodeError";
1547
- this.rawJson = rawJson;
1548
- this.attempt = attempt;
804
+ function normalizeResponsesResponse(payload, requestId) {
805
+ if (!isRecord(payload)) {
806
+ throw new APIError("invalid response payload", { status: 200, data: payload });
1549
807
  }
1550
- };
1551
- var StructuredExhaustedError = class extends Error {
1552
- constructor(lastRawJson, allAttempts, finalError) {
1553
- const errorMsg = finalError.kind === "decode" ? finalError.message : finalError.issues.map((i) => i.message).join("; ");
1554
- super(
1555
- `structured output failed after ${allAttempts.length} attempts: ${errorMsg}`
1556
- );
1557
- this.name = "StructuredExhaustedError";
1558
- this.lastRawJson = lastRawJson;
1559
- this.allAttempts = allAttempts;
1560
- this.finalError = finalError;
808
+ const output = Array.isArray(payload.output) ? payload.output : [];
809
+ const usage = normalizeUsage(payload.usage);
810
+ if (!usage) {
811
+ throw new APIError("missing usage in response", { status: 200, data: payload });
1561
812
  }
1562
- };
1563
- var defaultRetryHandler = {
1564
- onValidationError(_attempt, _rawJson, error, _originalInput) {
1565
- const errorMsg = error.kind === "decode" ? error.message : error.issues.map((i) => `${i.path ?? ""}: ${i.message}`).join("; ");
1566
- return [{
1567
- type: "message",
1568
- role: "user",
1569
- content: [{ type: "text", text: `The previous response did not match the expected schema. Error: ${errorMsg}. Please provide a response that matches the schema exactly.` }]
1570
- }];
813
+ const model = normalizeModelId(payload.model);
814
+ if (!model) {
815
+ throw new APIError("missing model in response", { status: 200, data: payload });
1571
816
  }
1572
- };
1573
- function outputFormatFromZod(schema, name = "response") {
1574
- const jsonSchema = zodToJsonSchema(schema);
1575
817
  return {
1576
- type: "json_schema",
1577
- json_schema: {
1578
- name,
1579
- schema: jsonSchema,
1580
- strict: true
1581
- }
818
+ id: typeof payload.id === "string" ? payload.id : String(payload.id || ""),
819
+ output,
820
+ stopReason: normalizeStopReason(payload.stop_reason),
821
+ model,
822
+ usage,
823
+ provider: typeof payload.provider === "string" ? asProviderId(payload.provider) : void 0,
824
+ citations: normalizeCitations(payload.citations),
825
+ requestId
1582
826
  };
1583
827
  }
1584
- function validateWithZod(schema, data) {
1585
- const result = schema.safeParse(data);
1586
- if (result.success) {
1587
- return { success: true, data: result.data };
1588
- }
1589
- const err = result.error;
1590
- const issuesRaw = err && typeof err === "object" && "issues" in err ? err.issues : void 0;
1591
- if (Array.isArray(issuesRaw)) {
1592
- return {
1593
- success: false,
1594
- issues: issuesRaw.map((i) => {
1595
- const ii = i && typeof i === "object" ? i : {};
1596
- return {
1597
- path: Array.isArray(ii.path) ? ii.path.filter((p) => typeof p === "string" || typeof p === "number").join(".") : void 0,
1598
- message: typeof ii.message === "string" && ii.message.trim() ? ii.message : "validation failed"
1599
- };
1600
- })
1601
- };
1602
- }
1603
- const message = err && typeof err === "object" && "message" in err ? String(err.message) : "validation failed";
1604
- return { success: false, issues: [{ message }] };
1605
- }
1606
-
1607
- // src/responses_request.ts
1608
- var RESPONSES_PATH = "/responses";
1609
- var CUSTOMER_ID_HEADER = "X-ModelRelay-Customer-Id";
1610
- var REQUEST_ID_HEADER = "X-ModelRelay-Request-Id";
1611
- function makeResponsesRequest(body, options) {
1612
- return { body, options };
1613
- }
1614
- function asInternal(req) {
1615
- return req;
1616
- }
1617
- function mergeOptions(base, override) {
828
+ function assistantItem(content) {
1618
829
  return {
1619
- ...base,
1620
- ...override,
1621
- headers: {
1622
- ...base.headers || {},
1623
- ...override.headers || {}
1624
- }
830
+ type: "message",
831
+ role: "assistant",
832
+ content: [{ type: "text", text: content }]
1625
833
  };
1626
834
  }
1627
- function requestIdFromHeaders(headers) {
1628
- return headers.get(REQUEST_ID_HEADER);
835
+ function extractAssistantText(output) {
836
+ const parts = [];
837
+ for (const item of output || []) {
838
+ if (item.type !== "message") continue;
839
+ if (item.role !== "assistant") continue;
840
+ if (Array.isArray(item.content)) {
841
+ parts.push(...item.content);
842
+ }
843
+ }
844
+ const text = parts.filter((p) => p.type === "text").map((p) => p.type === "text" ? p.text : "").join("");
845
+ if (!text.trim()) {
846
+ throw new TransportError("response contained no assistant text output", {
847
+ kind: "empty_response"
848
+ });
849
+ }
850
+ return text;
1629
851
  }
1630
852
 
1631
- // src/responses_builder.ts
1632
- var ResponseBuilder = class _ResponseBuilder {
1633
- constructor(body = { input: [] }, options = {}) {
1634
- this.body = body;
1635
- this.options = options;
1636
- }
1637
- with(patch) {
1638
- return new _ResponseBuilder(
1639
- patch.body ? { ...this.body, ...patch.body } : this.body,
1640
- patch.options ? { ...this.options, ...patch.options } : this.options
853
+ // src/responses_ndjson.ts
854
+ function mapNDJSONResponseEvent(line, requestId) {
855
+ let parsed;
856
+ try {
857
+ parsed = JSON.parse(line);
858
+ } catch (err) {
859
+ throw new TransportError(
860
+ `Failed to parse NDJSON line: ${err instanceof Error ? err.message : String(err)}`,
861
+ { kind: "request", cause: err }
1641
862
  );
1642
863
  }
1643
- /** @returns A new builder with the provider set. */
1644
- provider(provider) {
1645
- return this.with({ body: { provider } });
1646
- }
1647
- /** @returns A new builder with the model set. */
1648
- model(model) {
1649
- return this.with({ body: { model } });
1650
- }
1651
- /** @returns A new builder with the full input array replaced. */
1652
- input(items) {
1653
- return this.with({ body: { input: items.slice() } });
1654
- }
1655
- /** @returns A new builder with the input item appended. */
1656
- item(item) {
1657
- const input = [...this.body.input ?? [], item];
1658
- return this.with({ body: { input } });
1659
- }
1660
- /** @returns A new builder with a message input item appended. */
1661
- message(role, content) {
1662
- return this.item({
1663
- type: "message",
1664
- role,
1665
- content: [{ type: "text", text: content }]
1666
- });
1667
- }
1668
- /** @returns A new builder with a system message appended. */
1669
- system(content) {
1670
- return this.message("system", content);
1671
- }
1672
- /** @returns A new builder with a user message appended. */
1673
- user(content) {
1674
- return this.message("user", content);
1675
- }
1676
- /** @returns A new builder with an assistant message appended. */
1677
- assistant(content) {
1678
- return this.message("assistant", content);
1679
- }
1680
- /** @returns A new builder with a tool result message appended. */
1681
- toolResultText(toolCallId, content) {
1682
- return this.item({
1683
- type: "message",
1684
- role: "tool",
1685
- toolCallId: toolCallId.trim(),
1686
- content: [{ type: "text", text: content }]
1687
- });
1688
- }
1689
- /** @returns A new builder with the output format set. */
1690
- outputFormat(format) {
1691
- return this.with({ body: { output_format: format } });
1692
- }
1693
- /** @returns A new builder with max output tokens set. */
1694
- maxOutputTokens(max) {
1695
- return this.with({ body: { max_output_tokens: max } });
1696
- }
1697
- /** @returns A new builder with temperature set. */
1698
- temperature(temp) {
1699
- return this.with({ body: { temperature: temp } });
1700
- }
1701
- /** @returns A new builder with stop sequences set. */
1702
- stop(...stop) {
1703
- const clean = stop.map((s) => s.trim()).filter(Boolean);
1704
- return this.with({ body: { stop: clean.length ? clean : void 0 } });
1705
- }
1706
- /** @returns A new builder with tools replaced. */
1707
- tools(tools) {
1708
- return this.with({ body: { tools: tools.slice() } });
1709
- }
1710
- /** @returns A new builder with a tool appended. */
1711
- tool(tool) {
1712
- const tools = [...this.body.tools ?? [], tool];
1713
- return this.with({ body: { tools } });
1714
- }
1715
- /** @returns A new builder with tool choice set. */
1716
- toolChoice(choice) {
1717
- return this.with({ body: { tool_choice: choice } });
1718
- }
1719
- /** @returns A new builder with tool choice set to auto. */
1720
- toolChoiceAuto() {
1721
- return this.toolChoice({ type: "auto" });
1722
- }
1723
- /** @returns A new builder with tool choice set to required. */
1724
- toolChoiceRequired(functionName) {
1725
- return this.toolChoice({ type: "required", function: functionName });
1726
- }
1727
- /** @returns A new builder with tool choice set to none. */
1728
- toolChoiceNone() {
1729
- return this.toolChoice({ type: "none" });
1730
- }
1731
- /** @returns A new builder with the customer ID option set. */
1732
- customerId(customerId) {
1733
- return this.with({ options: { customerId: customerId.trim() } });
1734
- }
1735
- /** @returns A new builder with the request ID option set. */
1736
- requestId(requestId) {
1737
- return this.with({ options: { requestId: requestId.trim() } });
1738
- }
1739
- /** @returns A new builder with a single header set/overridden. */
1740
- header(key, value) {
1741
- const headers = { ...this.options.headers || {}, [key]: value };
1742
- return this.with({ options: { headers } });
1743
- }
1744
- /** @returns A new builder with headers merged. */
1745
- headers(headers) {
1746
- const merged = { ...this.options.headers || {}, ...headers };
1747
- return this.with({ options: { headers: merged } });
1748
- }
1749
- /** @returns A new builder with the request timeout option set. */
1750
- timeoutMs(timeoutMs) {
1751
- return this.with({ options: { timeoutMs: Math.max(0, timeoutMs) } });
1752
- }
1753
- /** @returns A new builder with the connect timeout option set. */
1754
- connectTimeoutMs(connectTimeoutMs) {
1755
- return this.with({
1756
- options: { connectTimeoutMs: Math.max(0, connectTimeoutMs) }
1757
- });
1758
- }
1759
- /** @returns A new builder with retry configuration set. */
1760
- retry(cfg) {
1761
- return this.with({ options: { retry: cfg } });
1762
- }
1763
- /** @returns A new builder with retries disabled. */
1764
- disableRetry() {
1765
- return this.with({ options: { retry: false } });
1766
- }
1767
- /** @returns A new builder with metrics callbacks set. */
1768
- metrics(metrics) {
1769
- return this.with({ options: { metrics } });
1770
- }
1771
- /** @returns A new builder with trace callbacks set. */
1772
- trace(trace) {
1773
- return this.with({ options: { trace } });
1774
- }
1775
- /** @returns A new builder with stream time-to-first-token timeout set. */
1776
- streamTTFTTimeoutMs(timeoutMs) {
1777
- return this.with({
1778
- options: { streamTTFTTimeoutMs: Math.max(0, timeoutMs) }
1779
- });
1780
- }
1781
- /** @returns A new builder with stream idle timeout set. */
1782
- streamIdleTimeoutMs(timeoutMs) {
1783
- return this.with({
1784
- options: { streamIdleTimeoutMs: Math.max(0, timeoutMs) }
1785
- });
1786
- }
1787
- /** @returns A new builder with total stream timeout set. */
1788
- streamTotalTimeoutMs(timeoutMs) {
1789
- return this.with({
1790
- options: { streamTotalTimeoutMs: Math.max(0, timeoutMs) }
1791
- });
1792
- }
1793
- /** @returns A new builder with the abort signal set. */
1794
- signal(signal) {
1795
- return this.with({ options: { signal } });
1796
- }
1797
- /** @returns A finalized, immutable request payload. */
1798
- build() {
1799
- const input = (this.body.input ?? []).slice();
1800
- if (input.length === 0) {
1801
- throw new ConfigError("at least one input item is required");
1802
- }
1803
- const body = {
1804
- provider: this.body.provider,
1805
- model: this.body.model,
1806
- input,
1807
- output_format: this.body.output_format,
1808
- max_output_tokens: this.body.max_output_tokens,
1809
- temperature: this.body.temperature,
1810
- stop: this.body.stop,
1811
- tools: this.body.tools,
1812
- tool_choice: this.body.tool_choice
1813
- };
1814
- return makeResponsesRequest(body, { ...this.options || {} });
1815
- }
1816
- };
1817
-
1818
- // src/responses_normalize.ts
1819
- function isRecord(value) {
1820
- return typeof value === "object" && value !== null && !Array.isArray(value);
1821
- }
1822
- function normalizeUsage(value) {
1823
- if (!isRecord(value)) {
1824
- return void 0;
1825
- }
1826
- const input = Number(value.input_tokens ?? 0);
1827
- const output = Number(value.output_tokens ?? 0);
1828
- const total = value.total_tokens === void 0 || value.total_tokens === null ? void 0 : Number(value.total_tokens);
1829
- return createUsage(
1830
- input,
1831
- output,
1832
- Number.isFinite(total) ? total : void 0
1833
- );
1834
- }
1835
- function normalizeCitations(value) {
1836
- if (!Array.isArray(value) || value.length === 0) return void 0;
1837
- const citations = [];
1838
- for (const item of value) {
1839
- if (!isRecord(item)) continue;
1840
- const url = item.url;
1841
- const title = item.title;
1842
- citations.push({
1843
- ...typeof url === "string" && url.trim() ? { url } : {},
1844
- ...typeof title === "string" && title.trim() ? { title } : {}
1845
- });
1846
- }
1847
- return citations.length ? citations : void 0;
1848
- }
1849
- function normalizeResponsesResponse(payload, requestId) {
1850
- if (!isRecord(payload)) {
1851
- throw new APIError("invalid response payload", { status: 200, data: payload });
1852
- }
1853
- const output = Array.isArray(payload.output) ? payload.output : [];
1854
- const usage = normalizeUsage(payload.usage);
1855
- if (!usage) {
1856
- throw new APIError("missing usage in response", { status: 200, data: payload });
1857
- }
1858
- const model = normalizeModelId(payload.model);
1859
- if (!model) {
1860
- throw new APIError("missing model in response", { status: 200, data: payload });
1861
- }
1862
- return {
1863
- id: typeof payload.id === "string" ? payload.id : String(payload.id || ""),
1864
- output,
1865
- stopReason: normalizeStopReason(payload.stop_reason),
1866
- model,
1867
- usage,
1868
- provider: typeof payload.provider === "string" ? asProviderId(payload.provider) : void 0,
1869
- citations: normalizeCitations(payload.citations),
1870
- requestId
1871
- };
1872
- }
1873
- function assistantItem(content) {
1874
- return {
1875
- type: "message",
1876
- role: "assistant",
1877
- content: [{ type: "text", text: content }]
1878
- };
1879
- }
1880
- function extractAssistantText(output) {
1881
- const parts = [];
1882
- for (const item of output || []) {
1883
- if (item.type !== "message") continue;
1884
- if (item.role !== "assistant") continue;
1885
- if (Array.isArray(item.content)) {
1886
- parts.push(...item.content);
1887
- }
1888
- }
1889
- const text = parts.filter((p) => p.type === "text").map((p) => p.type === "text" ? p.text : "").join("");
1890
- if (!text.trim()) {
1891
- throw new TransportError("response contained no assistant text output", {
1892
- kind: "empty_response"
1893
- });
1894
- }
1895
- return text;
1896
- }
1897
-
1898
- // src/responses_ndjson.ts
1899
- function mapNDJSONResponseEvent(line, requestId) {
1900
- let parsed;
1901
- try {
1902
- parsed = JSON.parse(line);
1903
- } catch (err) {
1904
- throw new TransportError(
1905
- `Failed to parse NDJSON line: ${err instanceof Error ? err.message : String(err)}`,
1906
- { kind: "request", cause: err }
1907
- );
1908
- }
1909
- if (!parsed || typeof parsed !== "object") {
1910
- throw new TransportError(
1911
- `NDJSON record is not an object: ${JSON.stringify(parsed)}`,
1912
- { kind: "request" }
1913
- );
864
+ if (!parsed || typeof parsed !== "object") {
865
+ throw new TransportError(
866
+ `NDJSON record is not an object: ${JSON.stringify(parsed)}`,
867
+ { kind: "request" }
868
+ );
1914
869
  }
1915
870
  if (!isRecord(parsed)) {
1916
871
  throw new TransportError(
@@ -3550,10 +2505,10 @@ var RunsClient = class {
3550
2505
  const metrics = mergeMetrics(this.metrics, options.metrics);
3551
2506
  const trace = mergeTrace(this.trace, options.trace);
3552
2507
  const authHeaders = await this.auth.authForResponses();
3553
- const path2 = runByIdPath(runId);
2508
+ const path = runByIdPath(runId);
3554
2509
  const headers = { ...options.headers || {} };
3555
2510
  this.applyCustomerHeader(headers, options.customerId);
3556
- const out = await this.http.json(path2, {
2511
+ const out = await this.http.json(path, {
3557
2512
  method: "GET",
3558
2513
  headers,
3559
2514
  signal: options.signal,
@@ -3564,7 +2519,7 @@ var RunsClient = class {
3564
2519
  retry: options.retry,
3565
2520
  metrics,
3566
2521
  trace,
3567
- context: { method: "GET", path: path2 }
2522
+ context: { method: "GET", path }
3568
2523
  });
3569
2524
  return {
3570
2525
  ...out,
@@ -3588,10 +2543,10 @@ var RunsClient = class {
3588
2543
  if (options.wait === false) {
3589
2544
  params.set("wait", "0");
3590
2545
  }
3591
- const path2 = params.toString() ? `${basePath}?${params}` : basePath;
2546
+ const path = params.toString() ? `${basePath}?${params}` : basePath;
3592
2547
  const headers = { ...options.headers || {} };
3593
2548
  this.applyCustomerHeader(headers, options.customerId);
3594
- const resp = await this.http.request(path2, {
2549
+ const resp = await this.http.request(path, {
3595
2550
  method: "GET",
3596
2551
  headers,
3597
2552
  signal: options.signal,
@@ -3642,10 +2597,10 @@ var RunsClient = class {
3642
2597
  const metrics = mergeMetrics(this.metrics, options.metrics);
3643
2598
  const trace = mergeTrace(this.trace, options.trace);
3644
2599
  const authHeaders = await this.auth.authForResponses();
3645
- const path2 = runToolResultsPath(runId);
2600
+ const path = runToolResultsPath(runId);
3646
2601
  const headers = { ...options.headers || {} };
3647
2602
  this.applyCustomerHeader(headers, options.customerId);
3648
- const out = await this.http.json(path2, {
2603
+ const out = await this.http.json(path, {
3649
2604
  method: "POST",
3650
2605
  headers,
3651
2606
  body: req,
@@ -3657,7 +2612,7 @@ var RunsClient = class {
3657
2612
  retry: options.retry,
3658
2613
  metrics,
3659
2614
  trace,
3660
- context: { method: "POST", path: path2 }
2615
+ context: { method: "POST", path }
3661
2616
  });
3662
2617
  return out;
3663
2618
  }
@@ -3665,10 +2620,10 @@ var RunsClient = class {
3665
2620
  const metrics = mergeMetrics(this.metrics, options.metrics);
3666
2621
  const trace = mergeTrace(this.trace, options.trace);
3667
2622
  const authHeaders = await this.auth.authForResponses();
3668
- const path2 = runPendingToolsPath(runId);
2623
+ const path = runPendingToolsPath(runId);
3669
2624
  const headers = { ...options.headers || {} };
3670
2625
  this.applyCustomerHeader(headers, options.customerId);
3671
- const out = await this.http.json(path2, {
2626
+ const out = await this.http.json(path, {
3672
2627
  method: "GET",
3673
2628
  headers,
3674
2629
  signal: options.signal,
@@ -3679,7 +2634,7 @@ var RunsClient = class {
3679
2634
  retry: options.retry,
3680
2635
  metrics,
3681
2636
  trace,
3682
- context: { method: "GET", path: path2 }
2637
+ context: { method: "GET", path }
3683
2638
  });
3684
2639
  return {
3685
2640
  ...out,
@@ -4182,8 +3137,8 @@ var ModelsClient = class {
4182
3137
  if (params.capability) {
4183
3138
  qs.set("capability", params.capability);
4184
3139
  }
4185
- const path2 = qs.toString() ? `/models?${qs.toString()}` : "/models";
4186
- const resp = await this.http.json(path2, { method: "GET" });
3140
+ const path = qs.toString() ? `/models?${qs.toString()}` : "/models";
3141
+ const resp = await this.http.json(path, { method: "GET" });
4187
3142
  return resp.models;
4188
3143
  }
4189
3144
  };
@@ -5650,7 +4605,7 @@ var HTTPClient = class {
5650
4605
  this.metrics = cfg.metrics;
5651
4606
  this.trace = cfg.trace;
5652
4607
  }
5653
- async request(path2, options = {}) {
4608
+ async request(path, options = {}) {
5654
4609
  const fetchFn = this.fetchImpl ?? globalThis.fetch;
5655
4610
  if (!fetchFn) {
5656
4611
  throw new ConfigError(
@@ -5658,12 +4613,12 @@ var HTTPClient = class {
5658
4613
  );
5659
4614
  }
5660
4615
  const method = options.method || "GET";
5661
- const url = buildUrl(this.baseUrl, path2);
4616
+ const url = buildUrl(this.baseUrl, path);
5662
4617
  const metrics = mergeMetrics(this.metrics, options.metrics);
5663
4618
  const trace = mergeTrace(this.trace, options.trace);
5664
4619
  const context = {
5665
4620
  method,
5666
- path: path2,
4621
+ path,
5667
4622
  ...options.context || {}
5668
4623
  };
5669
4624
  trace?.requestStart?.(context);
@@ -5804,8 +4759,8 @@ var HTTPClient = class {
5804
4759
  retries: buildRetryMetadata(attempts, lastStatus)
5805
4760
  });
5806
4761
  }
5807
- async json(path2, options = {}) {
5808
- const response = await this.request(path2, {
4762
+ async json(path, options = {}) {
4763
+ const response = await this.request(path, {
5809
4764
  ...options,
5810
4765
  raw: true,
5811
4766
  accept: options.accept || "application/json"
@@ -5826,14 +4781,14 @@ var HTTPClient = class {
5826
4781
  }
5827
4782
  }
5828
4783
  };
5829
- function buildUrl(baseUrl, path2) {
5830
- if (/^https?:\/\//i.test(path2)) {
5831
- return path2;
4784
+ function buildUrl(baseUrl, path) {
4785
+ if (/^https?:\/\//i.test(path)) {
4786
+ return path;
5832
4787
  }
5833
- if (!path2.startsWith("/")) {
5834
- path2 = `/${path2}`;
4788
+ if (!path.startsWith("/")) {
4789
+ path = `/${path}`;
5835
4790
  }
5836
- return `${baseUrl}${path2}`;
4791
+ return `${baseUrl}${path}`;
5837
4792
  }
5838
4793
  function normalizeBaseUrl(value) {
5839
4794
  const trimmed = value.trim();
@@ -5876,7 +4831,7 @@ function backoff(attempt, cfg) {
5876
4831
  const jitter = 0.5 + Math.random();
5877
4832
  const delay = Math.min(cfg.maxBackoffMs, capped * jitter);
5878
4833
  if (delay <= 0) return Promise.resolve();
5879
- return new Promise((resolve2) => setTimeout(resolve2, delay));
4834
+ return new Promise((resolve) => setTimeout(resolve, delay));
5880
4835
  }
5881
4836
  function mergeSignals(...signals) {
5882
4837
  const active = signals.filter(Boolean);
@@ -6322,13 +5277,13 @@ async function sleep(ms, signal) {
6322
5277
  return;
6323
5278
  }
6324
5279
  if (!signal) {
6325
- await new Promise((resolve2) => setTimeout(resolve2, ms));
5280
+ await new Promise((resolve) => setTimeout(resolve, ms));
6326
5281
  return;
6327
5282
  }
6328
5283
  if (signal.aborted) {
6329
5284
  throw new TransportError("oauth device flow aborted", { kind: "request" });
6330
5285
  }
6331
- await new Promise((resolve2, reject) => {
5286
+ await new Promise((resolve, reject) => {
6332
5287
  const onAbort = () => {
6333
5288
  signal.removeEventListener("abort", onAbort);
6334
5289
  reject(new TransportError("oauth device flow aborted", { kind: "request" }));
@@ -6336,15 +5291,15 @@ async function sleep(ms, signal) {
6336
5291
  signal.addEventListener("abort", onAbort);
6337
5292
  setTimeout(() => {
6338
5293
  signal.removeEventListener("abort", onAbort);
6339
- resolve2();
5294
+ resolve();
6340
5295
  }, ms);
6341
5296
  });
6342
5297
  }
6343
5298
 
6344
5299
  // src/json_path.ts
6345
5300
  var LLMOutputPath = class {
6346
- constructor(path2 = "/output") {
6347
- this.path = path2;
5301
+ constructor(path = "/output") {
5302
+ this.path = path;
6348
5303
  }
6349
5304
  /** Select an output by index */
6350
5305
  index(i) {
@@ -6356,8 +5311,8 @@ var LLMOutputPath = class {
6356
5311
  }
6357
5312
  };
6358
5313
  var LLMOutputContentPath = class {
6359
- constructor(path2) {
6360
- this.path = path2;
5314
+ constructor(path) {
5315
+ this.path = path;
6361
5316
  }
6362
5317
  /** Select a content item by index */
6363
5318
  content(i) {
@@ -6365,8 +5320,8 @@ var LLMOutputContentPath = class {
6365
5320
  }
6366
5321
  };
6367
5322
  var LLMOutputContentItemPath = class {
6368
- constructor(path2) {
6369
- this.path = path2;
5323
+ constructor(path) {
5324
+ this.path = path;
6370
5325
  }
6371
5326
  /** Get the text field pointer */
6372
5327
  text() {
@@ -6382,8 +5337,8 @@ var LLMOutputContentItemPath = class {
6382
5337
  }
6383
5338
  };
6384
5339
  var LLMInputPath = class {
6385
- constructor(path2 = "/input") {
6386
- this.path = path2;
5340
+ constructor(path = "/input") {
5341
+ this.path = path;
6387
5342
  }
6388
5343
  /**
6389
5344
  * Select a message by index.
@@ -6402,8 +5357,8 @@ var LLMInputPath = class {
6402
5357
  }
6403
5358
  };
6404
5359
  var LLMInputMessagePath = class {
6405
- constructor(path2) {
6406
- this.path = path2;
5360
+ constructor(path) {
5361
+ this.path = path;
6407
5362
  }
6408
5363
  /** Select a content item by index */
6409
5364
  content(i) {
@@ -6415,8 +5370,8 @@ var LLMInputMessagePath = class {
6415
5370
  }
6416
5371
  };
6417
5372
  var LLMInputContentItemPath = class {
6418
- constructor(path2) {
6419
- this.path = path2;
5373
+ constructor(path) {
5374
+ this.path = path;
6420
5375
  }
6421
5376
  /** Get the text field pointer */
6422
5377
  text() {
@@ -6442,8 +5397,8 @@ var LLMInputSystemText = LLMInput().systemMessage().text();
6442
5397
  var LLMInputUserText = LLMInput().userMessage().text();
6443
5398
  var LLMInputFirstMessageText = LLMInput().message(0).text();
6444
5399
  var JoinOutputPath = class {
6445
- constructor(path2) {
6446
- this.path = path2;
5400
+ constructor(path) {
5401
+ this.path = path;
6447
5402
  }
6448
5403
  /** Access the output array of the node */
6449
5404
  output() {
@@ -8490,173 +7445,16 @@ var ParallelBuilder = class _ParallelBuilder {
8490
7445
  bindings: [
8491
7446
  {
8492
7447
  from: joinId,
8493
- // Empty pointer = full join output
8494
- to: LLM_USER_MESSAGE_TEXT_INTERNAL,
8495
- encoding: "json_string"
8496
- }
8497
- ]
8498
- }
8499
- };
8500
- nodes.push(aggInput);
8501
- edges.push({ from: joinId, to: this.state.aggregate.id });
8502
- }
8503
- return {
8504
- kind: WorkflowKinds.WorkflowV0,
8505
- name: this.state.name,
8506
- ...this.state.execution ? { execution: this.state.execution } : {},
8507
- nodes,
8508
- ...edges.length > 0 ? { edges: sortEdges(edges) } : {},
8509
- outputs: sortOutputs(this.state.outputs)
8510
- };
8511
- }
8512
- };
8513
- function Parallel(name, steps) {
8514
- return ParallelBuilder.create(name, steps);
8515
- }
8516
- function MapItem(id, request) {
8517
- const config = {
8518
- id,
8519
- request: wireRequest2(request),
8520
- stream: false
8521
- };
8522
- return {
8523
- ...config,
8524
- withStream() {
8525
- return { ...config, stream: true };
8526
- }
8527
- };
8528
- }
8529
- var MapReduceBuilder = class _MapReduceBuilder {
8530
- constructor(state) {
8531
- this.state = state;
8532
- }
8533
- static create(name, items) {
8534
- return new _MapReduceBuilder({ name, items, outputs: [] });
8535
- }
8536
- with(patch) {
8537
- return new _MapReduceBuilder({ ...this.state, ...patch });
8538
- }
8539
- /**
8540
- * Adds a mapper item to the workflow.
8541
- * Each item becomes a separate LLM node that runs in parallel.
8542
- */
8543
- item(id, request) {
8544
- return this.with({
8545
- items: [...this.state.items, { id, request: wireRequest2(request), stream: false }]
8546
- });
8547
- }
8548
- /**
8549
- * Adds a mapper item with streaming enabled.
8550
- */
8551
- itemWithStream(id, request) {
8552
- return this.with({
8553
- items: [...this.state.items, { id, request: wireRequest2(request), stream: true }]
8554
- });
8555
- }
8556
- /**
8557
- * Sets the workflow execution configuration.
8558
- */
8559
- execution(exec) {
8560
- return this.with({ execution: exec });
8561
- }
8562
- /**
8563
- * Adds a reducer node that receives all mapper outputs.
8564
- * The reducer receives a JSON object mapping each mapper ID to its text output.
8565
- * The join node ID is automatically generated as "<id>_join".
8566
- */
8567
- reduce(id, request) {
8568
- return this.with({
8569
- reducer: {
8570
- id,
8571
- request: wireRequest2(request),
8572
- stream: false
8573
- }
8574
- });
8575
- }
8576
- /**
8577
- * Like reduce() but enables streaming on the reducer node.
8578
- */
8579
- reduceWithStream(id, request) {
8580
- return this.with({
8581
- reducer: {
8582
- id,
8583
- request: wireRequest2(request),
8584
- stream: true
8585
- }
8586
- });
8587
- }
8588
- /**
8589
- * Adds an output reference from a specific node.
8590
- * Typically used to output from the reducer node.
8591
- */
8592
- output(name, from) {
8593
- return this.with({
8594
- outputs: [
8595
- ...this.state.outputs,
8596
- {
8597
- name,
8598
- from,
8599
- pointer: LLM_TEXT_OUTPUT_INTERNAL
8600
- }
8601
- ]
8602
- });
8603
- }
8604
- /**
8605
- * Builds and returns the compiled workflow spec.
8606
- * @throws Error if no items are provided or no reducer is configured
8607
- */
8608
- build() {
8609
- if (this.state.items.length === 0) {
8610
- throw new Error("map-reduce requires at least one item");
8611
- }
8612
- if (!this.state.reducer) {
8613
- throw new Error("map-reduce requires a reducer (call reduce)");
8614
- }
8615
- const seenIds = /* @__PURE__ */ new Set();
8616
- for (const item of this.state.items) {
8617
- if (!item.id) {
8618
- throw new Error("item ID cannot be empty");
8619
- }
8620
- if (seenIds.has(item.id)) {
8621
- throw new Error(`duplicate item ID: "${item.id}"`);
8622
- }
8623
- seenIds.add(item.id);
8624
- }
8625
- const nodes = [];
8626
- const edges = [];
8627
- const joinId = `${this.state.reducer.id}_join`;
8628
- for (const item of this.state.items) {
8629
- const mapperId = `map_${item.id}`;
8630
- const input = {
8631
- id: mapperId,
8632
- type: WorkflowNodeTypes.LLMResponses,
8633
- input: {
8634
- request: item.request,
8635
- ...item.stream ? { stream: true } : {}
7448
+ // Empty pointer = full join output
7449
+ to: LLM_USER_MESSAGE_TEXT_INTERNAL,
7450
+ encoding: "json_string"
7451
+ }
7452
+ ]
8636
7453
  }
8637
7454
  };
8638
- nodes.push(input);
8639
- edges.push({ from: mapperId, to: joinId });
7455
+ nodes.push(aggInput);
7456
+ edges.push({ from: joinId, to: this.state.aggregate.id });
8640
7457
  }
8641
- nodes.push({ id: joinId, type: WorkflowNodeTypes.JoinAll });
8642
- const reducerInput = {
8643
- id: this.state.reducer.id,
8644
- type: WorkflowNodeTypes.LLMResponses,
8645
- input: {
8646
- request: this.state.reducer.request,
8647
- ...this.state.reducer.stream ? { stream: true } : {},
8648
- bindings: [
8649
- {
8650
- from: joinId,
8651
- // Empty pointer = full join output
8652
- to: LLM_USER_MESSAGE_TEXT_INTERNAL,
8653
- encoding: "json_string"
8654
- }
8655
- ]
8656
- }
8657
- };
8658
- nodes.push(reducerInput);
8659
- edges.push({ from: joinId, to: this.state.reducer.id });
8660
7458
  return {
8661
7459
  kind: WorkflowKinds.WorkflowV0,
8662
7460
  name: this.state.name,
@@ -8667,672 +7465,231 @@ var MapReduceBuilder = class _MapReduceBuilder {
8667
7465
  };
8668
7466
  }
8669
7467
  };
8670
- function MapReduce(name, items = []) {
8671
- return MapReduceBuilder.create(name, items);
8672
- }
8673
-
8674
- // src/testing.ts
8675
- function buildNDJSONResponse(lines, headers = {}, status = 200) {
8676
- const encoder = new TextEncoder();
8677
- const stream = new ReadableStream({
8678
- start(controller) {
8679
- for (const line of lines) {
8680
- controller.enqueue(encoder.encode(`${line}
8681
- `));
8682
- }
8683
- controller.close();
8684
- }
8685
- });
8686
- return new Response(stream, {
8687
- status,
8688
- headers: {
8689
- "Content-Type": "application/x-ndjson",
8690
- ...headers
8691
- }
8692
- });
8693
- }
8694
- function buildDelayedNDJSONResponse(steps, headers = {}, status = 200) {
8695
- const encoder = new TextEncoder();
8696
- const stream = new ReadableStream({
8697
- start(controller) {
8698
- let idx = 0;
8699
- const pushNext = () => {
8700
- if (idx >= steps.length) {
8701
- controller.close();
8702
- return;
8703
- }
8704
- const step = steps[idx++];
8705
- setTimeout(() => {
8706
- controller.enqueue(encoder.encode(`${step.line}
8707
- `));
8708
- pushNext();
8709
- }, Math.max(0, step.delayMs));
8710
- };
8711
- pushNext();
8712
- }
8713
- });
8714
- return new Response(stream, {
8715
- status,
8716
- headers: {
8717
- "Content-Type": "application/x-ndjson",
8718
- ...headers
8719
- }
8720
- });
7468
+ function Parallel(name, steps) {
7469
+ return ParallelBuilder.create(name, steps);
8721
7470
  }
8722
- function createMockFetchQueue(responses) {
8723
- const calls = [];
8724
- const queue = [...responses];
8725
- const fetchImpl = async (url, init) => {
8726
- const call = { url: String(url), init };
8727
- calls.push(call);
8728
- const responder = queue.shift();
8729
- if (!responder) {
8730
- throw new Error("mock fetch queue exhausted");
8731
- }
8732
- if (typeof responder === "function") {
8733
- return responder(call, calls.length - 1);
7471
+ function MapItem(id, request) {
7472
+ const config = {
7473
+ id,
7474
+ request: wireRequest2(request),
7475
+ stream: false
7476
+ };
7477
+ return {
7478
+ ...config,
7479
+ withStream() {
7480
+ return { ...config, stream: true };
8734
7481
  }
8735
- return responder;
8736
7482
  };
8737
- return { fetch: fetchImpl, calls };
8738
7483
  }
8739
-
8740
- // src/tools_local_fs.ts
8741
- import { promises as fs } from "fs";
8742
- import * as path from "path";
8743
- import { spawn } from "child_process";
8744
- var ToolNames = {
8745
- FS_READ_FILE: "fs.read_file",
8746
- FS_LIST_FILES: "fs.list_files",
8747
- FS_SEARCH: "fs.search"
8748
- };
8749
- var FSDefaults = {
8750
- MAX_READ_BYTES: 64e3,
8751
- HARD_MAX_READ_BYTES: 1e6,
8752
- MAX_LIST_ENTRIES: 2e3,
8753
- HARD_MAX_LIST_ENTRIES: 2e4,
8754
- MAX_SEARCH_MATCHES: 100,
8755
- HARD_MAX_SEARCH_MATCHES: 2e3,
8756
- SEARCH_TIMEOUT_MS: 5e3,
8757
- MAX_SEARCH_BYTES_PER_FILE: 1e6
8758
- };
8759
- var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
8760
- ".git",
8761
- "node_modules",
8762
- "vendor",
8763
- "dist",
8764
- "build",
8765
- ".next",
8766
- "target",
8767
- ".idea",
8768
- ".vscode",
8769
- "__pycache__",
8770
- ".pytest_cache",
8771
- "coverage"
8772
- ]);
8773
- var LocalFSToolPack = class {
8774
- constructor(options) {
8775
- this.rgPath = null;
8776
- this.rgChecked = false;
8777
- const root = options.root?.trim();
8778
- if (!root) {
8779
- throw new Error("LocalFSToolPack: root directory required");
8780
- }
8781
- this.rootAbs = path.resolve(root);
8782
- this.cfg = {
8783
- ignoreDirs: options.ignoreDirs ?? new Set(DEFAULT_IGNORE_DIRS),
8784
- maxReadBytes: options.maxReadBytes ?? FSDefaults.MAX_READ_BYTES,
8785
- hardMaxReadBytes: options.hardMaxReadBytes ?? FSDefaults.HARD_MAX_READ_BYTES,
8786
- maxListEntries: options.maxListEntries ?? FSDefaults.MAX_LIST_ENTRIES,
8787
- hardMaxListEntries: options.hardMaxListEntries ?? FSDefaults.HARD_MAX_LIST_ENTRIES,
8788
- maxSearchMatches: options.maxSearchMatches ?? FSDefaults.MAX_SEARCH_MATCHES,
8789
- hardMaxSearchMatches: options.hardMaxSearchMatches ?? FSDefaults.HARD_MAX_SEARCH_MATCHES,
8790
- searchTimeoutMs: options.searchTimeoutMs ?? FSDefaults.SEARCH_TIMEOUT_MS,
8791
- maxSearchBytesPerFile: options.maxSearchBytesPerFile ?? FSDefaults.MAX_SEARCH_BYTES_PER_FILE
8792
- };
7484
+ var MapReduceBuilder = class _MapReduceBuilder {
7485
+ constructor(state) {
7486
+ this.state = state;
8793
7487
  }
8794
- /**
8795
- * Returns the tool definitions for LLM requests.
8796
- * Use these when constructing the tools array for /responses requests.
8797
- */
8798
- getToolDefinitions() {
8799
- return [
8800
- {
8801
- type: "function",
8802
- function: {
8803
- name: ToolNames.FS_READ_FILE,
8804
- description: "Read the contents of a file. Returns the file contents as UTF-8 text.",
8805
- parameters: {
8806
- type: "object",
8807
- properties: {
8808
- path: {
8809
- type: "string",
8810
- description: "Workspace-relative path to the file (e.g., 'src/index.ts')"
8811
- },
8812
- max_bytes: {
8813
- type: "integer",
8814
- description: `Maximum bytes to read. Default: ${this.cfg.maxReadBytes}, max: ${this.cfg.hardMaxReadBytes}`
8815
- }
8816
- },
8817
- required: ["path"]
8818
- }
8819
- }
8820
- },
8821
- {
8822
- type: "function",
8823
- function: {
8824
- name: ToolNames.FS_LIST_FILES,
8825
- description: "List files recursively in a directory. Returns newline-separated workspace-relative paths.",
8826
- parameters: {
8827
- type: "object",
8828
- properties: {
8829
- path: {
8830
- type: "string",
8831
- description: "Workspace-relative directory path. Default: '.' (workspace root)"
8832
- },
8833
- max_entries: {
8834
- type: "integer",
8835
- description: `Maximum files to list. Default: ${this.cfg.maxListEntries}, max: ${this.cfg.hardMaxListEntries}`
8836
- }
8837
- }
8838
- }
8839
- }
8840
- },
8841
- {
8842
- type: "function",
8843
- function: {
8844
- name: ToolNames.FS_SEARCH,
8845
- description: "Search for text matching a regex pattern. Returns matches as 'path:line:content' format.",
8846
- parameters: {
8847
- type: "object",
8848
- properties: {
8849
- query: {
8850
- type: "string",
8851
- description: "Regex pattern to search for"
8852
- },
8853
- path: {
8854
- type: "string",
8855
- description: "Workspace-relative directory to search. Default: '.' (workspace root)"
8856
- },
8857
- max_matches: {
8858
- type: "integer",
8859
- description: `Maximum matches to return. Default: ${this.cfg.maxSearchMatches}, max: ${this.cfg.hardMaxSearchMatches}`
8860
- }
8861
- },
8862
- required: ["query"]
8863
- }
8864
- }
8865
- }
8866
- ];
7488
+ static create(name, items) {
7489
+ return new _MapReduceBuilder({ name, items, outputs: [] });
7490
+ }
7491
+ with(patch) {
7492
+ return new _MapReduceBuilder({ ...this.state, ...patch });
8867
7493
  }
8868
7494
  /**
8869
- * Registers handlers into an existing ToolRegistry.
8870
- * @param registry - The registry to register into
8871
- * @returns The registry for chaining
7495
+ * Adds a mapper item to the workflow.
7496
+ * Each item becomes a separate LLM node that runs in parallel.
8872
7497
  */
8873
- registerInto(registry) {
8874
- registry.register(ToolNames.FS_READ_FILE, this.readFile.bind(this));
8875
- registry.register(ToolNames.FS_LIST_FILES, this.listFiles.bind(this));
8876
- registry.register(ToolNames.FS_SEARCH, this.search.bind(this));
8877
- return registry;
7498
+ item(id, request) {
7499
+ return this.with({
7500
+ items: [...this.state.items, { id, request: wireRequest2(request), stream: false }]
7501
+ });
8878
7502
  }
8879
7503
  /**
8880
- * Creates a new ToolRegistry with fs.* tools pre-registered.
7504
+ * Adds a mapper item with streaming enabled.
8881
7505
  */
8882
- toRegistry() {
8883
- return this.registerInto(new ToolRegistry());
8884
- }
8885
- // ========================================================================
8886
- // Tool Handlers
8887
- // ========================================================================
8888
- async readFile(_args, call) {
8889
- const args = this.parseArgs(call, ["path"]);
8890
- const func = call.function;
8891
- const relPath = this.requireString(args, "path", call);
8892
- const requestedMax = this.optionalPositiveInt(args, "max_bytes", call);
8893
- let maxBytes = this.cfg.maxReadBytes;
8894
- if (requestedMax !== void 0) {
8895
- if (requestedMax > this.cfg.hardMaxReadBytes) {
8896
- throw new ToolArgumentError({
8897
- message: `max_bytes exceeds hard cap (${this.cfg.hardMaxReadBytes})`,
8898
- toolCallId: call.id,
8899
- toolName: func.name,
8900
- rawArguments: func.arguments
8901
- });
8902
- }
8903
- maxBytes = requestedMax;
8904
- }
8905
- const absPath = await this.resolveAndValidatePath(relPath, call);
8906
- const stat = await fs.stat(absPath);
8907
- if (stat.isDirectory()) {
8908
- throw new Error(`fs.read_file: path is a directory: ${relPath}`);
8909
- }
8910
- if (stat.size > maxBytes) {
8911
- throw new Error(`fs.read_file: file exceeds max_bytes (${maxBytes})`);
8912
- }
8913
- const data = await fs.readFile(absPath);
8914
- if (!this.isValidUtf8(data)) {
8915
- throw new Error(`fs.read_file: file is not valid UTF-8: ${relPath}`);
8916
- }
8917
- return data.toString("utf-8");
8918
- }
8919
- async listFiles(_args, call) {
8920
- const args = this.parseArgs(call, []);
8921
- const func = call.function;
8922
- const startPath = this.optionalString(args, "path", call)?.trim() || ".";
8923
- let maxEntries = this.cfg.maxListEntries;
8924
- const requestedMax = this.optionalPositiveInt(args, "max_entries", call);
8925
- if (requestedMax !== void 0) {
8926
- if (requestedMax > this.cfg.hardMaxListEntries) {
8927
- throw new ToolArgumentError({
8928
- message: `max_entries exceeds hard cap (${this.cfg.hardMaxListEntries})`,
8929
- toolCallId: call.id,
8930
- toolName: func.name,
8931
- rawArguments: func.arguments
8932
- });
8933
- }
8934
- maxEntries = requestedMax;
8935
- }
8936
- const absPath = await this.resolveAndValidatePath(startPath, call);
8937
- let rootReal;
8938
- try {
8939
- rootReal = await fs.realpath(this.rootAbs);
8940
- } catch {
8941
- rootReal = this.rootAbs;
8942
- }
8943
- const stat = await fs.stat(absPath);
8944
- if (!stat.isDirectory()) {
8945
- throw new Error(`fs.list_files: path is not a directory: ${startPath}`);
8946
- }
8947
- const files = [];
8948
- await this.walkDir(absPath, async (filePath, dirent) => {
8949
- if (files.length >= maxEntries) {
8950
- return false;
8951
- }
8952
- if (dirent.isDirectory()) {
8953
- if (this.cfg.ignoreDirs.has(dirent.name)) {
8954
- return false;
8955
- }
8956
- return true;
8957
- }
8958
- if (dirent.isFile()) {
8959
- const relPath = path.relative(rootReal, filePath);
8960
- files.push(relPath.split(path.sep).join("/"));
8961
- }
8962
- return true;
7506
+ itemWithStream(id, request) {
7507
+ return this.with({
7508
+ items: [...this.state.items, { id, request: wireRequest2(request), stream: true }]
8963
7509
  });
8964
- return files.join("\n");
8965
- }
8966
- async search(_args, call) {
8967
- const args = this.parseArgs(call, ["query"]);
8968
- const func = call.function;
8969
- const query = this.requireString(args, "query", call);
8970
- const startPath = this.optionalString(args, "path", call)?.trim() || ".";
8971
- let maxMatches = this.cfg.maxSearchMatches;
8972
- const requestedMax = this.optionalPositiveInt(args, "max_matches", call);
8973
- if (requestedMax !== void 0) {
8974
- if (requestedMax > this.cfg.hardMaxSearchMatches) {
8975
- throw new ToolArgumentError({
8976
- message: `max_matches exceeds hard cap (${this.cfg.hardMaxSearchMatches})`,
8977
- toolCallId: call.id,
8978
- toolName: func.name,
8979
- rawArguments: func.arguments
8980
- });
8981
- }
8982
- maxMatches = requestedMax;
8983
- }
8984
- const absPath = await this.resolveAndValidatePath(startPath, call);
8985
- const rgPath = await this.detectRipgrep();
8986
- if (rgPath) {
8987
- return this.searchWithRipgrep(
8988
- rgPath,
8989
- query,
8990
- absPath,
8991
- maxMatches
8992
- );
8993
- }
8994
- return this.searchWithJS(query, absPath, maxMatches, call);
8995
7510
  }
8996
- // ========================================================================
8997
- // Path Safety
8998
- // ========================================================================
8999
7511
  /**
9000
- * Resolves a workspace-relative path and validates it stays within the sandbox.
9001
- * @throws {ToolArgumentError} if path is invalid
9002
- * @throws {PathEscapeError} if resolved path escapes root
7512
+ * Sets the workflow execution configuration.
9003
7513
  */
9004
- async resolveAndValidatePath(relPath, call) {
9005
- const func = call.function;
9006
- const cleanRel = relPath.trim();
9007
- if (!cleanRel) {
9008
- throw new ToolArgumentError({
9009
- message: "path cannot be empty",
9010
- toolCallId: call.id,
9011
- toolName: func.name,
9012
- rawArguments: func.arguments
9013
- });
9014
- }
9015
- if (path.isAbsolute(cleanRel)) {
9016
- throw new ToolArgumentError({
9017
- message: "path must be workspace-relative (not absolute)",
9018
- toolCallId: call.id,
9019
- toolName: func.name,
9020
- rawArguments: func.arguments
9021
- });
9022
- }
9023
- const normalized = path.normalize(cleanRel);
9024
- if (normalized.startsWith("..") || normalized.startsWith(`.${path.sep}..`)) {
9025
- throw new ToolArgumentError({
9026
- message: "path must not escape the workspace root",
9027
- toolCallId: call.id,
9028
- toolName: func.name,
9029
- rawArguments: func.arguments
9030
- });
9031
- }
9032
- const target = path.join(this.rootAbs, normalized);
9033
- let rootReal;
9034
- try {
9035
- rootReal = await fs.realpath(this.rootAbs);
9036
- } catch {
9037
- rootReal = this.rootAbs;
9038
- }
9039
- let resolved;
9040
- try {
9041
- resolved = await fs.realpath(target);
9042
- } catch (err) {
9043
- resolved = path.join(rootReal, normalized);
9044
- }
9045
- const relFromRoot = path.relative(rootReal, resolved);
9046
- if (relFromRoot.startsWith("..") || relFromRoot.startsWith(`.${path.sep}..`) || path.isAbsolute(relFromRoot)) {
9047
- throw new PathEscapeError({
9048
- requestedPath: relPath,
9049
- resolvedPath: resolved
9050
- });
9051
- }
9052
- return resolved;
7514
+ execution(exec) {
7515
+ return this.with({ execution: exec });
9053
7516
  }
9054
- // ========================================================================
9055
- // Ripgrep Search
9056
- // ========================================================================
9057
- async detectRipgrep() {
9058
- if (this.rgChecked) {
9059
- return this.rgPath;
9060
- }
9061
- this.rgChecked = true;
9062
- return new Promise((resolve2) => {
9063
- const proc = spawn("rg", ["--version"], { stdio: "ignore" });
9064
- proc.on("error", () => {
9065
- this.rgPath = null;
9066
- resolve2(null);
9067
- });
9068
- proc.on("close", (code) => {
9069
- if (code === 0) {
9070
- this.rgPath = "rg";
9071
- resolve2("rg");
9072
- } else {
9073
- this.rgPath = null;
9074
- resolve2(null);
9075
- }
9076
- });
7517
+ /**
7518
+ * Adds a reducer node that receives all mapper outputs.
7519
+ * The reducer receives a JSON object mapping each mapper ID to its text output.
7520
+ * The join node ID is automatically generated as "<id>_join".
7521
+ */
7522
+ reduce(id, request) {
7523
+ return this.with({
7524
+ reducer: {
7525
+ id,
7526
+ request: wireRequest2(request),
7527
+ stream: false
7528
+ }
9077
7529
  });
9078
7530
  }
9079
- async searchWithRipgrep(rgPath, query, dirAbs, maxMatches) {
9080
- return new Promise((resolve2, reject) => {
9081
- const args = ["--line-number", "--no-heading", "--color=never"];
9082
- for (const name of this.cfg.ignoreDirs) {
9083
- args.push("--glob", `!**/${name}/**`);
7531
+ /**
7532
+ * Like reduce() but enables streaming on the reducer node.
7533
+ */
7534
+ reduceWithStream(id, request) {
7535
+ return this.with({
7536
+ reducer: {
7537
+ id,
7538
+ request: wireRequest2(request),
7539
+ stream: true
9084
7540
  }
9085
- args.push(query, dirAbs);
9086
- const proc = spawn(rgPath, args, {
9087
- timeout: this.cfg.searchTimeoutMs
9088
- });
9089
- const lines = [];
9090
- let stderr = "";
9091
- let killed = false;
9092
- proc.stdout.on("data", (chunk) => {
9093
- if (killed) return;
9094
- const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
9095
- const newLines = text.split("\n").filter((l) => l.trim());
9096
- for (const line of newLines) {
9097
- lines.push(this.normalizeRipgrepLine(line));
9098
- if (lines.length >= maxMatches) {
9099
- killed = true;
9100
- proc.kill();
9101
- break;
9102
- }
9103
- }
9104
- });
9105
- proc.stderr.on("data", (chunk) => {
9106
- stderr += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
9107
- });
9108
- proc.on("error", (err) => {
9109
- reject(new Error(`fs.search: ripgrep error: ${err.message}`));
9110
- });
9111
- proc.on("close", (code) => {
9112
- if (killed) {
9113
- resolve2(lines.join("\n"));
9114
- return;
9115
- }
9116
- if (code === 0 || code === 1) {
9117
- resolve2(lines.join("\n"));
9118
- } else if (code === 2 && stderr.toLowerCase().includes("regex")) {
9119
- reject(
9120
- new ToolArgumentError({
9121
- message: `invalid query regex: ${stderr.trim()}`,
9122
- toolCallId: "",
9123
- toolName: ToolNames.FS_SEARCH,
9124
- rawArguments: ""
9125
- })
9126
- );
9127
- } else if (stderr) {
9128
- reject(new Error(`fs.search: ripgrep failed: ${stderr.trim()}`));
9129
- } else {
9130
- resolve2(lines.join("\n"));
7541
+ });
7542
+ }
7543
+ /**
7544
+ * Adds an output reference from a specific node.
7545
+ * Typically used to output from the reducer node.
7546
+ */
7547
+ output(name, from) {
7548
+ return this.with({
7549
+ outputs: [
7550
+ ...this.state.outputs,
7551
+ {
7552
+ name,
7553
+ from,
7554
+ pointer: LLM_TEXT_OUTPUT_INTERNAL
9131
7555
  }
9132
- });
7556
+ ]
9133
7557
  });
9134
7558
  }
9135
- normalizeRipgrepLine(line) {
9136
- const trimmed = line.trim();
9137
- if (!trimmed || !trimmed.includes(":")) {
9138
- return trimmed;
9139
- }
9140
- const colonIdx = trimmed.indexOf(":");
9141
- const filePath = trimmed.slice(0, colonIdx);
9142
- const rest = trimmed.slice(colonIdx + 1);
9143
- if (path.isAbsolute(filePath)) {
9144
- const rel = path.relative(this.rootAbs, filePath);
9145
- if (!rel.startsWith("..")) {
9146
- return rel.split(path.sep).join("/") + ":" + rest;
9147
- }
7559
+ /**
7560
+ * Builds and returns the compiled workflow spec.
7561
+ * @throws Error if no items are provided or no reducer is configured
7562
+ */
7563
+ build() {
7564
+ if (this.state.items.length === 0) {
7565
+ throw new Error("map-reduce requires at least one item");
9148
7566
  }
9149
- return filePath.split(path.sep).join("/") + ":" + rest;
9150
- }
9151
- // ========================================================================
9152
- // JavaScript Fallback Search
9153
- // ========================================================================
9154
- async searchWithJS(query, dirAbs, maxMatches, call) {
9155
- const func = call.function;
9156
- let regex;
9157
- try {
9158
- regex = new RegExp(query);
9159
- } catch (err) {
9160
- throw new ToolArgumentError({
9161
- message: `invalid query regex: ${err.message}`,
9162
- toolCallId: call.id,
9163
- toolName: func.name,
9164
- rawArguments: func.arguments
9165
- });
7567
+ if (!this.state.reducer) {
7568
+ throw new Error("map-reduce requires a reducer (call reduce)");
9166
7569
  }
9167
- const matches = [];
9168
- const deadline = Date.now() + this.cfg.searchTimeoutMs;
9169
- await this.walkDir(dirAbs, async (filePath, dirent) => {
9170
- if (Date.now() > deadline) {
9171
- return false;
9172
- }
9173
- if (matches.length >= maxMatches) {
9174
- return false;
9175
- }
9176
- if (dirent.isDirectory()) {
9177
- if (this.cfg.ignoreDirs.has(dirent.name)) {
9178
- return false;
9179
- }
9180
- return true;
7570
+ const seenIds = /* @__PURE__ */ new Set();
7571
+ for (const item of this.state.items) {
7572
+ if (!item.id) {
7573
+ throw new Error("item ID cannot be empty");
9181
7574
  }
9182
- if (!dirent.isFile()) {
9183
- return true;
7575
+ if (seenIds.has(item.id)) {
7576
+ throw new Error(`duplicate item ID: "${item.id}"`);
9184
7577
  }
9185
- try {
9186
- const stat = await fs.stat(filePath);
9187
- if (stat.size > this.cfg.maxSearchBytesPerFile) {
9188
- return true;
7578
+ seenIds.add(item.id);
7579
+ }
7580
+ const nodes = [];
7581
+ const edges = [];
7582
+ const joinId = `${this.state.reducer.id}_join`;
7583
+ for (const item of this.state.items) {
7584
+ const mapperId = `map_${item.id}`;
7585
+ const input = {
7586
+ id: mapperId,
7587
+ type: WorkflowNodeTypes.LLMResponses,
7588
+ input: {
7589
+ request: item.request,
7590
+ ...item.stream ? { stream: true } : {}
9189
7591
  }
9190
- } catch {
9191
- return true;
9192
- }
9193
- try {
9194
- const content = await fs.readFile(filePath, "utf-8");
9195
- const lines = content.split("\n");
9196
- for (let i = 0; i < lines.length && matches.length < maxMatches; i++) {
9197
- if (regex.test(lines[i])) {
9198
- const relPath = path.relative(this.rootAbs, filePath);
9199
- const normalizedPath = relPath.split(path.sep).join("/");
9200
- matches.push(`${normalizedPath}:${i + 1}:${lines[i]}`);
7592
+ };
7593
+ nodes.push(input);
7594
+ edges.push({ from: mapperId, to: joinId });
7595
+ }
7596
+ nodes.push({ id: joinId, type: WorkflowNodeTypes.JoinAll });
7597
+ const reducerInput = {
7598
+ id: this.state.reducer.id,
7599
+ type: WorkflowNodeTypes.LLMResponses,
7600
+ input: {
7601
+ request: this.state.reducer.request,
7602
+ ...this.state.reducer.stream ? { stream: true } : {},
7603
+ bindings: [
7604
+ {
7605
+ from: joinId,
7606
+ // Empty pointer = full join output
7607
+ to: LLM_USER_MESSAGE_TEXT_INTERNAL,
7608
+ encoding: "json_string"
9201
7609
  }
9202
- }
9203
- } catch {
7610
+ ]
9204
7611
  }
9205
- return true;
9206
- });
9207
- return matches.join("\n");
7612
+ };
7613
+ nodes.push(reducerInput);
7614
+ edges.push({ from: joinId, to: this.state.reducer.id });
7615
+ return {
7616
+ kind: WorkflowKinds.WorkflowV0,
7617
+ name: this.state.name,
7618
+ ...this.state.execution ? { execution: this.state.execution } : {},
7619
+ nodes,
7620
+ ...edges.length > 0 ? { edges: sortEdges(edges) } : {},
7621
+ outputs: sortOutputs(this.state.outputs)
7622
+ };
9208
7623
  }
9209
- // ========================================================================
9210
- // Helpers
9211
- // ========================================================================
9212
- parseArgs(call, required) {
9213
- const func = call.function;
9214
- if (!func) {
9215
- throw new ToolArgumentError({
9216
- message: "tool call missing function",
9217
- toolCallId: call.id,
9218
- toolName: "",
9219
- rawArguments: ""
9220
- });
9221
- }
9222
- const rawArgs = func.arguments || "{}";
9223
- let parsed;
9224
- try {
9225
- parsed = JSON.parse(rawArgs);
9226
- } catch (err) {
9227
- throw new ToolArgumentError({
9228
- message: `invalid JSON arguments: ${err.message}`,
9229
- toolCallId: call.id,
9230
- toolName: func.name,
9231
- rawArguments: rawArgs
9232
- });
9233
- }
9234
- if (typeof parsed !== "object" || parsed === null) {
9235
- throw new ToolArgumentError({
9236
- message: "arguments must be an object",
9237
- toolCallId: call.id,
9238
- toolName: func.name,
9239
- rawArguments: rawArgs
9240
- });
9241
- }
9242
- const args = parsed;
9243
- for (const key of required) {
9244
- const value = args[key];
9245
- if (value === void 0 || value === null || value === "") {
9246
- throw new ToolArgumentError({
9247
- message: `${key} is required`,
9248
- toolCallId: call.id,
9249
- toolName: func.name,
9250
- rawArguments: rawArgs
9251
- });
7624
+ };
7625
+ function MapReduce(name, items = []) {
7626
+ return MapReduceBuilder.create(name, items);
7627
+ }
7628
+
7629
+ // src/testing.ts
7630
+ function buildNDJSONResponse(lines, headers = {}, status = 200) {
7631
+ const encoder = new TextEncoder();
7632
+ const stream = new ReadableStream({
7633
+ start(controller) {
7634
+ for (const line of lines) {
7635
+ controller.enqueue(encoder.encode(`${line}
7636
+ `));
9252
7637
  }
7638
+ controller.close();
9253
7639
  }
9254
- return args;
9255
- }
9256
- toolArgumentError(call, message) {
9257
- const func = call.function;
9258
- throw new ToolArgumentError({
9259
- message,
9260
- toolCallId: call.id,
9261
- toolName: func?.name ?? "",
9262
- rawArguments: func?.arguments ?? ""
9263
- });
9264
- }
9265
- requireString(args, key, call) {
9266
- const value = args[key];
9267
- if (typeof value !== "string") {
9268
- this.toolArgumentError(call, `${key} must be a string`);
9269
- }
9270
- if (value.trim() === "") {
9271
- this.toolArgumentError(call, `${key} is required`);
9272
- }
9273
- return value;
9274
- }
9275
- optionalString(args, key, call) {
9276
- const value = args[key];
9277
- if (value === void 0 || value === null) {
9278
- return void 0;
9279
- }
9280
- if (typeof value !== "string") {
9281
- this.toolArgumentError(call, `${key} must be a string`);
9282
- }
9283
- const trimmed = value.trim();
9284
- if (trimmed === "") {
9285
- return void 0;
9286
- }
9287
- return value;
9288
- }
9289
- optionalPositiveInt(args, key, call) {
9290
- const value = args[key];
9291
- if (value === void 0 || value === null) {
9292
- return void 0;
7640
+ });
7641
+ return new Response(stream, {
7642
+ status,
7643
+ headers: {
7644
+ "Content-Type": "application/x-ndjson",
7645
+ ...headers
9293
7646
  }
9294
- if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
9295
- this.toolArgumentError(call, `${key} must be an integer`);
7647
+ });
7648
+ }
7649
+ function buildDelayedNDJSONResponse(steps, headers = {}, status = 200) {
7650
+ const encoder = new TextEncoder();
7651
+ const stream = new ReadableStream({
7652
+ start(controller) {
7653
+ let idx = 0;
7654
+ const pushNext = () => {
7655
+ if (idx >= steps.length) {
7656
+ controller.close();
7657
+ return;
7658
+ }
7659
+ const step = steps[idx++];
7660
+ setTimeout(() => {
7661
+ controller.enqueue(encoder.encode(`${step.line}
7662
+ `));
7663
+ pushNext();
7664
+ }, Math.max(0, step.delayMs));
7665
+ };
7666
+ pushNext();
9296
7667
  }
9297
- if (value <= 0) {
9298
- this.toolArgumentError(call, `${key} must be > 0`);
7668
+ });
7669
+ return new Response(stream, {
7670
+ status,
7671
+ headers: {
7672
+ "Content-Type": "application/x-ndjson",
7673
+ ...headers
9299
7674
  }
9300
- return value;
9301
- }
9302
- isValidUtf8(buffer) {
9303
- try {
9304
- const text = buffer.toString("utf-8");
9305
- return !text.includes("\uFFFD");
9306
- } catch {
9307
- return false;
7675
+ });
7676
+ }
7677
+ function createMockFetchQueue(responses) {
7678
+ const calls = [];
7679
+ const queue = [...responses];
7680
+ const fetchImpl = async (url, init) => {
7681
+ const call = { url: String(url), init };
7682
+ calls.push(call);
7683
+ const responder = queue.shift();
7684
+ if (!responder) {
7685
+ throw new Error("mock fetch queue exhausted");
9308
7686
  }
9309
- }
9310
- /**
9311
- * Recursively walks a directory, calling visitor for each entry.
9312
- * Visitor returns true to continue, false to skip (for dirs) or stop.
9313
- */
9314
- async walkDir(dir, visitor) {
9315
- const entries = await fs.readdir(dir, { withFileTypes: true });
9316
- for (const entry of entries) {
9317
- const fullPath = path.join(dir, entry.name);
9318
- const shouldContinue = await visitor(fullPath, entry);
9319
- if (!shouldContinue) {
9320
- if (entry.isDirectory()) {
9321
- continue;
9322
- }
9323
- return;
9324
- }
9325
- if (entry.isDirectory()) {
9326
- await this.walkDir(fullPath, visitor);
9327
- }
7687
+ if (typeof responder === "function") {
7688
+ return responder(call, calls.length - 1);
9328
7689
  }
9329
- }
9330
- };
9331
- function createLocalFSToolPack(options) {
9332
- return new LocalFSToolPack(options);
9333
- }
9334
- function createLocalFSTools(options) {
9335
- return createLocalFSToolPack(options).toRegistry();
7690
+ return responder;
7691
+ };
7692
+ return { fetch: fetchImpl, calls };
9336
7693
  }
9337
7694
 
9338
7695
  // src/tools_browser.ts
@@ -10087,23 +8444,23 @@ __export(workflow_exports, {
10087
8444
  });
10088
8445
 
10089
8446
  // src/workflow/helpers_v1.ts
10090
- function whenOutputEquals(path2, value) {
10091
- return { source: "node_output", op: "equals", path: path2, value };
8447
+ function whenOutputEquals(path, value) {
8448
+ return { source: "node_output", op: "equals", path, value };
10092
8449
  }
10093
- function whenOutputMatches(path2, pattern) {
10094
- return { source: "node_output", op: "matches", path: path2, value: pattern };
8450
+ function whenOutputMatches(path, pattern) {
8451
+ return { source: "node_output", op: "matches", path, value: pattern };
10095
8452
  }
10096
- function whenOutputExists(path2) {
10097
- return { source: "node_output", op: "exists", path: path2 };
8453
+ function whenOutputExists(path) {
8454
+ return { source: "node_output", op: "exists", path };
10098
8455
  }
10099
- function whenStatusEquals(path2, value) {
10100
- return { source: "node_status", op: "equals", path: path2, value };
8456
+ function whenStatusEquals(path, value) {
8457
+ return { source: "node_status", op: "equals", path, value };
10101
8458
  }
10102
- function whenStatusMatches(path2, pattern) {
10103
- return { source: "node_status", op: "matches", path: path2, value: pattern };
8459
+ function whenStatusMatches(path, pattern) {
8460
+ return { source: "node_status", op: "matches", path, value: pattern };
10104
8461
  }
10105
- function whenStatusExists(path2) {
10106
- return { source: "node_status", op: "exists", path: path2 };
8462
+ function whenStatusExists(path) {
8463
+ return { source: "node_status", op: "exists", path };
10107
8464
  }
10108
8465
  function bindToPlaceholder(from, placeholder, opts) {
10109
8466
  return {
@@ -10407,11 +8764,8 @@ export {
10407
8764
  DEFAULT_BASE_URL,
10408
8765
  DEFAULT_CLIENT_HEADER,
10409
8766
  DEFAULT_CONNECT_TIMEOUT_MS,
10410
- DEFAULT_IGNORE_DIRS,
10411
8767
  DEFAULT_REQUEST_TIMEOUT_MS,
10412
8768
  ErrorCodes,
10413
- FSDefaults,
10414
- ToolNames as FSToolNames,
10415
8769
  FrontendTokenProvider,
10416
8770
  ImagesClient,
10417
8771
  InputItemTypes,
@@ -10433,7 +8787,6 @@ export {
10433
8787
  LLMStep,
10434
8788
  LLM_TEXT_OUTPUT,
10435
8789
  LLM_USER_MESSAGE_TEXT,
10436
- LocalFSToolPack,
10437
8790
  LocalSession,
10438
8791
  MapFanoutInputError,
10439
8792
  MapItem,
@@ -10474,7 +8827,7 @@ export {
10474
8827
  TransformJSONNodeBuilder,
10475
8828
  TransportError,
10476
8829
  WORKFLOWS_COMPILE_PATH,
10477
- WebToolModes,
8830
+ WebToolIntents,
10478
8831
  Workflow,
10479
8832
  WorkflowBuilderV0,
10480
8833
  WorkflowBuilderV1,
@@ -10497,8 +8850,6 @@ export {
10497
8850
  createFunctionCall,
10498
8851
  createFunctionTool,
10499
8852
  createFunctionToolFromSchema,
10500
- createLocalFSToolPack,
10501
- createLocalFSTools,
10502
8853
  createLocalSession,
10503
8854
  createMemorySessionStore,
10504
8855
  createMockFetchQueue,