@modelrelay/sdk 1.14.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  APIError: () => APIError,
34
34
  AuthClient: () => AuthClient,
35
35
  BillingProviders: () => BillingProviders,
36
+ BindingTargetError: () => BindingTargetError,
36
37
  BrowserDefaults: () => BrowserDefaults,
37
38
  BrowserToolNames: () => BrowserToolNames,
38
39
  BrowserToolPack: () => BrowserToolPack,
@@ -47,20 +48,31 @@ __export(index_exports, {
47
48
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
48
49
  DEFAULT_CLIENT_HEADER: () => DEFAULT_CLIENT_HEADER,
49
50
  DEFAULT_CONNECT_TIMEOUT_MS: () => DEFAULT_CONNECT_TIMEOUT_MS,
50
- DEFAULT_IGNORE_DIRS: () => DEFAULT_IGNORE_DIRS,
51
51
  DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
52
52
  ErrorCodes: () => ErrorCodes,
53
- FSDefaults: () => FSDefaults,
54
- FSToolNames: () => ToolNames,
55
53
  FrontendTokenProvider: () => FrontendTokenProvider,
56
54
  ImagesClient: () => ImagesClient,
57
55
  InputItemTypes: () => InputItemTypes,
56
+ JoinOutput: () => JoinOutput,
57
+ JoinOutputPath: () => JoinOutputPath,
58
+ LLMInput: () => LLMInput,
59
+ LLMInputContentItemPath: () => LLMInputContentItemPath,
60
+ LLMInputFirstMessageText: () => LLMInputFirstMessageText,
61
+ LLMInputMessagePath: () => LLMInputMessagePath,
62
+ LLMInputPath: () => LLMInputPath,
63
+ LLMInputSystemText: () => LLMInputSystemText,
64
+ LLMInputUserText: () => LLMInputUserText,
58
65
  LLMNodeBuilder: () => LLMNodeBuilder,
66
+ LLMOutput: () => LLMOutput,
67
+ LLMOutputContentItemPath: () => LLMOutputContentItemPath,
68
+ LLMOutputContentPath: () => LLMOutputContentPath,
69
+ LLMOutputPath: () => LLMOutputPath,
70
+ LLMOutputText: () => LLMOutputText,
59
71
  LLMStep: () => LLMStep,
60
72
  LLM_TEXT_OUTPUT: () => LLM_TEXT_OUTPUT,
61
73
  LLM_USER_MESSAGE_TEXT: () => LLM_USER_MESSAGE_TEXT,
62
- LocalFSToolPack: () => LocalFSToolPack,
63
74
  LocalSession: () => LocalSession,
75
+ MapFanoutInputError: () => MapFanoutInputError,
64
76
  MapItem: () => MapItem,
65
77
  MapReduce: () => MapReduce,
66
78
  MapReduceBuilder: () => MapReduceBuilder,
@@ -99,9 +111,10 @@ __export(index_exports, {
99
111
  TransformJSONNodeBuilder: () => TransformJSONNodeBuilder,
100
112
  TransportError: () => TransportError,
101
113
  WORKFLOWS_COMPILE_PATH: () => WORKFLOWS_COMPILE_PATH,
102
- WebToolModes: () => WebToolModes,
114
+ WebToolIntents: () => WebToolIntents,
103
115
  Workflow: () => Workflow,
104
116
  WorkflowBuilderV0: () => WorkflowBuilderV0,
117
+ WorkflowBuilderV1: () => WorkflowBuilderV1,
105
118
  WorkflowKinds: () => WorkflowKinds,
106
119
  WorkflowNodeTypes: () => WorkflowNodeTypes,
107
120
  WorkflowValidationError: () => WorkflowValidationError,
@@ -111,6 +124,8 @@ __export(index_exports, {
111
124
  asSessionId: () => asSessionId,
112
125
  asTierCode: () => asTierCode,
113
126
  assistantMessageWithToolCalls: () => assistantMessageWithToolCalls,
127
+ buildDelayedNDJSONResponse: () => buildDelayedNDJSONResponse,
128
+ buildNDJSONResponse: () => buildNDJSONResponse,
114
129
  createAccessTokenAuth: () => createAccessTokenAuth,
115
130
  createApiKeyAuth: () => createApiKeyAuth,
116
131
  createAssistantMessage: () => createAssistantMessage,
@@ -119,10 +134,9 @@ __export(index_exports, {
119
134
  createFunctionCall: () => createFunctionCall,
120
135
  createFunctionTool: () => createFunctionTool,
121
136
  createFunctionToolFromSchema: () => createFunctionToolFromSchema,
122
- createLocalFSToolPack: () => createLocalFSToolPack,
123
- createLocalFSTools: () => createLocalFSTools,
124
137
  createLocalSession: () => createLocalSession,
125
138
  createMemorySessionStore: () => createMemorySessionStore,
139
+ createMockFetchQueue: () => createMockFetchQueue,
126
140
  createRetryMessages: () => createRetryMessages,
127
141
  createSystemMessage: () => createSystemMessage,
128
142
  createToolCall: () => createToolCall,
@@ -164,6 +178,7 @@ __export(index_exports, {
164
178
  parseToolArgs: () => parseToolArgs,
165
179
  parseToolArgsRaw: () => parseToolArgsRaw,
166
180
  pollOAuthDeviceToken: () => pollOAuthDeviceToken,
181
+ pollUntil: () => pollUntil,
167
182
  respondToToolCall: () => respondToToolCall,
168
183
  runOAuthDeviceFlowForIDToken: () => runOAuthDeviceFlowForIDToken,
169
184
  startOAuthDeviceAuthorization: () => startOAuthDeviceAuthorization,
@@ -173,13 +188,18 @@ __export(index_exports, {
173
188
  toolChoiceRequired: () => toolChoiceRequired,
174
189
  toolResultMessage: () => toolResultMessage,
175
190
  transformJSONMerge: () => transformJSONMerge,
191
+ transformJSONMergeV1: () => transformJSONMergeV1,
176
192
  transformJSONObject: () => transformJSONObject,
193
+ transformJSONObjectV1: () => transformJSONObjectV1,
177
194
  transformJSONValue: () => transformJSONValue,
195
+ transformJSONValueV1: () => transformJSONValueV1,
178
196
  tryParseToolArgs: () => tryParseToolArgs,
179
197
  validateWithZod: () => validateWithZod,
180
198
  workflow: () => workflow_exports,
181
199
  workflowV0: () => workflowV0,
182
200
  workflowV0Schema: () => workflow_v0_schema_default,
201
+ workflowV1: () => workflowV1,
202
+ workflowV1Schema: () => workflow_v1_schema_default,
183
203
  zodToJsonSchema: () => zodToJsonSchema
184
204
  });
185
205
  module.exports = __toCommonJS(index_exports);
@@ -248,11 +268,11 @@ var StreamProtocolError = class extends TransportError {
248
268
  this.status = opts.status;
249
269
  }
250
270
  };
251
- var StreamTimeoutError = class extends TransportError {
271
+ var StreamTimeoutError = class extends ModelRelayError {
252
272
  constructor(streamKind, timeoutMs) {
253
273
  const label = streamKind === "ttft" ? "TTFT" : streamKind === "idle" ? "idle" : "total";
254
- super(`stream ${label} timeout after ${timeoutMs}ms`, { kind: "timeout" });
255
- this.streamKind = streamKind;
274
+ super(`stream ${label} timeout after ${timeoutMs}ms`, { category: "transport", status: 408 });
275
+ this.kind = streamKind;
256
276
  this.timeoutMs = timeoutMs;
257
277
  }
258
278
  };
@@ -407,10 +427,10 @@ async function parseErrorResponse(response, retries) {
407
427
  if (!raw || typeof raw !== "object") continue;
408
428
  const obj = raw;
409
429
  const code = typeof obj.code === "string" ? obj.code : "";
410
- const path2 = typeof obj.path === "string" ? obj.path : "";
430
+ const path = typeof obj.path === "string" ? obj.path : "";
411
431
  const message = typeof obj.message === "string" ? obj.message : "";
412
- if (!code || !path2 || !message) continue;
413
- normalized.push({ code, path: path2, message });
432
+ if (!code || !path || !message) continue;
433
+ normalized.push({ code, path, message });
414
434
  }
415
435
  if (normalized.length > 0) {
416
436
  return new WorkflowValidationError({
@@ -760,8 +780,8 @@ var AuthClient = class {
760
780
  params.set("provider", request.provider);
761
781
  }
762
782
  const queryString = params.toString();
763
- const path2 = queryString ? `/auth/device/start?${queryString}` : "/auth/device/start";
764
- const apiResp = await this.http.json(path2, {
783
+ const path = queryString ? `/auth/device/start?${queryString}` : "/auth/device/start";
784
+ const apiResp = await this.http.json(path, {
765
785
  method: "POST",
766
786
  apiKey: this.apiKey
767
787
  });
@@ -884,7 +904,7 @@ function isTokenReusable(token) {
884
904
  // package.json
885
905
  var package_default = {
886
906
  name: "@modelrelay/sdk",
887
- version: "1.14.0",
907
+ version: "1.27.0",
888
908
  description: "TypeScript SDK for the ModelRelay API",
889
909
  type: "module",
890
910
  main: "dist/index.cjs",
@@ -895,6 +915,11 @@ var package_default = {
895
915
  types: "./dist/index.d.ts",
896
916
  import: "./dist/index.js",
897
917
  require: "./dist/index.cjs"
918
+ },
919
+ "./node": {
920
+ types: "./dist/node.d.ts",
921
+ import: "./dist/node.js",
922
+ require: "./dist/node.cjs"
898
923
  }
899
924
  },
900
925
  publishConfig: {
@@ -904,8 +929,8 @@ var package_default = {
904
929
  "dist"
905
930
  ],
906
931
  scripts: {
907
- build: "tsup src/index.ts --format esm,cjs --dts --external playwright",
908
- dev: "tsup src/index.ts --format esm,cjs --dts --watch",
932
+ build: "tsup src/index.ts src/node.ts --format esm,cjs --dts --external playwright",
933
+ dev: "tsup src/index.ts src/node.ts --format esm,cjs --dts --watch",
909
934
  lint: "tsc --noEmit --project tsconfig.lint.json",
910
935
  test: "vitest run",
911
936
  "generate:types": "openapi-typescript ../../api/openapi/api.json -o src/generated/api.ts"
@@ -1013,11 +1038,10 @@ var ToolTypes = {
1013
1038
  XSearch: "x_search",
1014
1039
  CodeExecution: "code_execution"
1015
1040
  };
1016
- var WebToolModes = {
1041
+ var WebToolIntents = {
1017
1042
  Auto: "auto",
1018
- SearchOnly: "search_only",
1019
- FetchOnly: "fetch_only",
1020
- SearchAndFetch: "search_and_fetch"
1043
+ SearchWeb: "search_web",
1044
+ FetchURL: "fetch_url"
1021
1045
  };
1022
1046
  var ToolChoiceTypes = {
1023
1047
  Auto: "auto",
@@ -1358,7 +1382,7 @@ function createWebTool(options) {
1358
1382
  return {
1359
1383
  type: ToolTypes.Web,
1360
1384
  web: options ? {
1361
- mode: options.mode,
1385
+ intent: options.intent,
1362
1386
  allowedDomains: options.allowedDomains,
1363
1387
  excludedDomains: options.excludedDomains,
1364
1388
  maxUses: options.maxUses
@@ -1502,8 +1526,8 @@ function parseToolArgs(call, schema) {
1502
1526
  const zodErr = err;
1503
1527
  if (zodErr.errors && Array.isArray(zodErr.errors)) {
1504
1528
  const issues = zodErr.errors.map((e) => {
1505
- const path2 = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
1506
- return `${path2}${e.message}`;
1529
+ const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
1530
+ return `${path}${e.message}`;
1507
1531
  }).join("; ");
1508
1532
  message = issues;
1509
1533
  } else {
@@ -2069,7 +2093,7 @@ function extractAssistantText(output) {
2069
2093
  const text = parts.filter((p) => p.type === "text").map((p) => p.type === "text" ? p.text : "").join("");
2070
2094
  if (!text.trim()) {
2071
2095
  throw new TransportError("response contained no assistant text output", {
2072
- kind: "request"
2096
+ kind: "empty_response"
2073
2097
  });
2074
2098
  }
2075
2099
  return text;
@@ -2763,6 +2787,101 @@ var ResponsesClient = class {
2763
2787
  const resp = await this.create(req, options);
2764
2788
  return extractAssistantText(resp.output);
2765
2789
  }
2790
+ /**
2791
+ * Generate a typed object from a Zod schema with a single function call.
2792
+ *
2793
+ * This is the most ergonomic way to get structured output - all configuration
2794
+ * is passed in a single object argument, matching the Vercel AI SDK pattern.
2795
+ *
2796
+ * @example
2797
+ * ```typescript
2798
+ * import { z } from 'zod';
2799
+ *
2800
+ * const review = await client.responses.object({
2801
+ * model: 'claude-sonnet-4-20250514',
2802
+ * schema: z.object({
2803
+ * vulnerabilities: z.array(z.string()),
2804
+ * riskLevel: z.enum(['low', 'medium', 'high']),
2805
+ * }),
2806
+ * system: 'You are a security expert.',
2807
+ * prompt: `Review this code:\n${code}`,
2808
+ * });
2809
+ *
2810
+ * console.log(review.riskLevel); // 'low' | 'medium' | 'high'
2811
+ * ```
2812
+ *
2813
+ * @example Parallel execution with Promise.all
2814
+ * ```typescript
2815
+ * const [security, performance] = await Promise.all([
2816
+ * client.responses.object({
2817
+ * model,
2818
+ * schema: SecuritySchema,
2819
+ * system: 'Security expert...',
2820
+ * prompt: code,
2821
+ * }),
2822
+ * client.responses.object({
2823
+ * model,
2824
+ * schema: PerformanceSchema,
2825
+ * system: 'Performance expert...',
2826
+ * prompt: code,
2827
+ * }),
2828
+ * ]);
2829
+ * ```
2830
+ */
2831
+ async object(args) {
2832
+ let builder = this.new().model(args.model);
2833
+ if (args.system) {
2834
+ builder = builder.system(args.system);
2835
+ }
2836
+ builder = builder.user(args.prompt);
2837
+ if (args.customerId) {
2838
+ builder = builder.customerId(args.customerId);
2839
+ }
2840
+ const request = builder.build();
2841
+ const result = await this.structured(args.schema, request, {
2842
+ maxRetries: args.maxRetries,
2843
+ retryHandler: args.retryHandler,
2844
+ schemaName: args.schemaName,
2845
+ ...args.options
2846
+ });
2847
+ return result.value;
2848
+ }
2849
+ /**
2850
+ * Generate a typed object with full result metadata.
2851
+ *
2852
+ * Like `object()` but returns the full `StructuredResult<T>` including
2853
+ * attempt count and request ID.
2854
+ *
2855
+ * @example
2856
+ * ```typescript
2857
+ * const result = await client.responses.objectWithMetadata({
2858
+ * model: 'claude-sonnet-4-20250514',
2859
+ * schema: ReviewSchema,
2860
+ * prompt: 'Review this code...',
2861
+ * });
2862
+ *
2863
+ * console.log(result.value); // The parsed object
2864
+ * console.log(result.attempts); // Number of attempts (1 = first try succeeded)
2865
+ * console.log(result.requestId); // Server request ID
2866
+ * ```
2867
+ */
2868
+ async objectWithMetadata(args) {
2869
+ let builder = this.new().model(args.model);
2870
+ if (args.system) {
2871
+ builder = builder.system(args.system);
2872
+ }
2873
+ builder = builder.user(args.prompt);
2874
+ if (args.customerId) {
2875
+ builder = builder.customerId(args.customerId);
2876
+ }
2877
+ const request = builder.build();
2878
+ return this.structured(args.schema, request, {
2879
+ maxRetries: args.maxRetries,
2880
+ retryHandler: args.retryHandler,
2881
+ schemaName: args.schemaName,
2882
+ ...args.options
2883
+ });
2884
+ }
2766
2885
  async textForCustomer(customerIdOrArgs, system, user, options = {}) {
2767
2886
  const args = typeof customerIdOrArgs === "string" ? { customerId: customerIdOrArgs, system, user, options } : customerIdOrArgs;
2768
2887
  if (args.system === void 0 || args.user === void 0) {
@@ -3203,13 +3322,23 @@ function parseOutputName(raw) {
3203
3322
 
3204
3323
  // src/runs_types.ts
3205
3324
  var WorkflowKinds = {
3206
- WorkflowV0: "workflow.v0"
3325
+ WorkflowV0: "workflow.v0",
3326
+ WorkflowV1: "workflow.v1"
3207
3327
  };
3208
3328
  var WorkflowNodeTypes = {
3209
3329
  LLMResponses: "llm.responses",
3210
3330
  JoinAll: "join.all",
3211
3331
  TransformJSON: "transform.json"
3212
3332
  };
3333
+ var WorkflowNodeTypesV1 = {
3334
+ LLMResponses: "llm.responses",
3335
+ RouteSwitch: "route.switch",
3336
+ JoinAll: "join.all",
3337
+ JoinAny: "join.any",
3338
+ JoinCollect: "join.collect",
3339
+ TransformJSON: "transform.json",
3340
+ MapFanout: "map.fanout"
3341
+ };
3213
3342
  var nodeErrorSchema = import_zod.default.object({
3214
3343
  code: import_zod.default.string().optional(),
3215
3344
  message: import_zod.default.string().min(1)
@@ -3567,6 +3696,9 @@ var RunsClient = class {
3567
3696
  const headers = { ...options.headers || {} };
3568
3697
  this.applyCustomerHeader(headers, options.customerId);
3569
3698
  const payload = { spec };
3699
+ if (options.sessionId?.trim()) {
3700
+ payload.session_id = options.sessionId.trim();
3701
+ }
3570
3702
  if (options.idempotencyKey?.trim()) {
3571
3703
  payload.options = { idempotency_key: options.idempotencyKey.trim() };
3572
3704
  }
@@ -3622,10 +3754,10 @@ var RunsClient = class {
3622
3754
  const metrics = mergeMetrics(this.metrics, options.metrics);
3623
3755
  const trace = mergeTrace(this.trace, options.trace);
3624
3756
  const authHeaders = await this.auth.authForResponses();
3625
- const path2 = runByIdPath(runId);
3757
+ const path = runByIdPath(runId);
3626
3758
  const headers = { ...options.headers || {} };
3627
3759
  this.applyCustomerHeader(headers, options.customerId);
3628
- const out = await this.http.json(path2, {
3760
+ const out = await this.http.json(path, {
3629
3761
  method: "GET",
3630
3762
  headers,
3631
3763
  signal: options.signal,
@@ -3636,7 +3768,7 @@ var RunsClient = class {
3636
3768
  retry: options.retry,
3637
3769
  metrics,
3638
3770
  trace,
3639
- context: { method: "GET", path: path2 }
3771
+ context: { method: "GET", path }
3640
3772
  });
3641
3773
  return {
3642
3774
  ...out,
@@ -3660,10 +3792,10 @@ var RunsClient = class {
3660
3792
  if (options.wait === false) {
3661
3793
  params.set("wait", "0");
3662
3794
  }
3663
- const path2 = params.toString() ? `${basePath}?${params}` : basePath;
3795
+ const path = params.toString() ? `${basePath}?${params}` : basePath;
3664
3796
  const headers = { ...options.headers || {} };
3665
3797
  this.applyCustomerHeader(headers, options.customerId);
3666
- const resp = await this.http.request(path2, {
3798
+ const resp = await this.http.request(path, {
3667
3799
  method: "GET",
3668
3800
  headers,
3669
3801
  signal: options.signal,
@@ -3714,10 +3846,10 @@ var RunsClient = class {
3714
3846
  const metrics = mergeMetrics(this.metrics, options.metrics);
3715
3847
  const trace = mergeTrace(this.trace, options.trace);
3716
3848
  const authHeaders = await this.auth.authForResponses();
3717
- const path2 = runToolResultsPath(runId);
3849
+ const path = runToolResultsPath(runId);
3718
3850
  const headers = { ...options.headers || {} };
3719
3851
  this.applyCustomerHeader(headers, options.customerId);
3720
- const out = await this.http.json(path2, {
3852
+ const out = await this.http.json(path, {
3721
3853
  method: "POST",
3722
3854
  headers,
3723
3855
  body: req,
@@ -3729,7 +3861,7 @@ var RunsClient = class {
3729
3861
  retry: options.retry,
3730
3862
  metrics,
3731
3863
  trace,
3732
- context: { method: "POST", path: path2 }
3864
+ context: { method: "POST", path }
3733
3865
  });
3734
3866
  return out;
3735
3867
  }
@@ -3737,10 +3869,10 @@ var RunsClient = class {
3737
3869
  const metrics = mergeMetrics(this.metrics, options.metrics);
3738
3870
  const trace = mergeTrace(this.trace, options.trace);
3739
3871
  const authHeaders = await this.auth.authForResponses();
3740
- const path2 = runPendingToolsPath(runId);
3872
+ const path = runPendingToolsPath(runId);
3741
3873
  const headers = { ...options.headers || {} };
3742
3874
  this.applyCustomerHeader(headers, options.customerId);
3743
- const out = await this.http.json(path2, {
3875
+ const out = await this.http.json(path, {
3744
3876
  method: "GET",
3745
3877
  headers,
3746
3878
  signal: options.signal,
@@ -3751,7 +3883,7 @@ var RunsClient = class {
3751
3883
  retry: options.retry,
3752
3884
  metrics,
3753
3885
  trace,
3754
- context: { method: "GET", path: path2 }
3886
+ context: { method: "GET", path }
3755
3887
  });
3756
3888
  return {
3757
3889
  ...out,
@@ -3835,6 +3967,65 @@ var WorkflowsClient = class {
3835
3967
  throw err;
3836
3968
  }
3837
3969
  }
3970
+ async compileV1(spec, options = {}) {
3971
+ const metrics = mergeMetrics(this.metrics, options.metrics);
3972
+ const trace = mergeTrace(this.trace, options.trace);
3973
+ const authHeaders = await this.auth.authForResponses();
3974
+ const headers = { ...options.headers || {} };
3975
+ const customerId = options.customerId?.trim();
3976
+ if (customerId) {
3977
+ headers[CUSTOMER_ID_HEADER] = customerId;
3978
+ }
3979
+ try {
3980
+ const out = await this.http.json(
3981
+ WORKFLOWS_COMPILE_PATH,
3982
+ {
3983
+ method: "POST",
3984
+ headers,
3985
+ body: spec,
3986
+ signal: options.signal,
3987
+ apiKey: authHeaders.apiKey,
3988
+ accessToken: authHeaders.accessToken,
3989
+ timeoutMs: options.timeoutMs,
3990
+ connectTimeoutMs: options.connectTimeoutMs,
3991
+ retry: options.retry,
3992
+ metrics,
3993
+ trace,
3994
+ context: { method: "POST", path: WORKFLOWS_COMPILE_PATH }
3995
+ }
3996
+ );
3997
+ return {
3998
+ ok: true,
3999
+ plan_json: out.plan_json,
4000
+ plan_hash: parsePlanHash(out.plan_hash)
4001
+ };
4002
+ } catch (err) {
4003
+ if (err instanceof WorkflowValidationError) {
4004
+ return { ok: false, error_type: "validation_error", issues: err.issues };
4005
+ }
4006
+ if (err instanceof APIError) {
4007
+ return {
4008
+ ok: false,
4009
+ error_type: "internal_error",
4010
+ status: err.status ?? 0,
4011
+ message: err.message,
4012
+ code: err.code,
4013
+ requestId: err.requestId
4014
+ };
4015
+ }
4016
+ if (err instanceof ModelRelayError && err.category === "api") {
4017
+ return {
4018
+ ok: false,
4019
+ error_type: "internal_error",
4020
+ status: err.status ?? 0,
4021
+ message: err.message,
4022
+ code: err.code,
4023
+ requestId: err.requestId
4024
+ };
4025
+ }
4026
+ throw err;
4027
+ }
4028
+ }
3838
4029
  };
3839
4030
 
3840
4031
  // src/customers.ts
@@ -4195,8 +4386,8 @@ var ModelsClient = class {
4195
4386
  if (params.capability) {
4196
4387
  qs.set("capability", params.capability);
4197
4388
  }
4198
- const path2 = qs.toString() ? `/models?${qs.toString()}` : "/models";
4199
- const resp = await this.http.json(path2, { method: "GET" });
4389
+ const path = qs.toString() ? `/models?${qs.toString()}` : "/models";
4390
+ const resp = await this.http.json(path, { method: "GET" });
4200
4391
  return resp.models;
4201
4392
  }
4202
4393
  };
@@ -4230,8 +4421,287 @@ var ImagesClient = class {
4230
4421
  accessToken: auth.accessToken
4231
4422
  });
4232
4423
  }
4424
+ /**
4425
+ * Get information about a specific image.
4426
+ *
4427
+ * Returns the image's pinned status, expiration time, and URL.
4428
+ *
4429
+ * @param imageId - The image ID to retrieve
4430
+ * @returns Image details including pinned status and URL
4431
+ * @throws {Error} If imageId is empty
4432
+ */
4433
+ async get(imageId) {
4434
+ if (!imageId?.trim()) {
4435
+ throw new Error("imageId is required");
4436
+ }
4437
+ const auth = await this.auth.authForResponses();
4438
+ return await this.http.json(`/images/${imageId}`, {
4439
+ method: "GET",
4440
+ apiKey: auth.apiKey,
4441
+ accessToken: auth.accessToken
4442
+ });
4443
+ }
4444
+ /**
4445
+ * Pin an image to prevent it from expiring.
4446
+ *
4447
+ * Pinned images remain accessible permanently (subject to tier limits).
4448
+ *
4449
+ * @param imageId - The image ID to pin
4450
+ * @returns Updated image state including permanent URL
4451
+ * @throws {Error} If imageId is empty
4452
+ */
4453
+ async pin(imageId) {
4454
+ if (!imageId?.trim()) {
4455
+ throw new Error("imageId is required");
4456
+ }
4457
+ const auth = await this.auth.authForResponses();
4458
+ return await this.http.json(`/images/${imageId}/pin`, {
4459
+ method: "POST",
4460
+ apiKey: auth.apiKey,
4461
+ accessToken: auth.accessToken
4462
+ });
4463
+ }
4464
+ /**
4465
+ * Unpin an image, allowing it to expire.
4466
+ *
4467
+ * The image will expire after the default ephemeral period (7 days).
4468
+ *
4469
+ * @param imageId - The image ID to unpin
4470
+ * @returns Updated image state including new expiration time
4471
+ * @throws {Error} If imageId is empty
4472
+ */
4473
+ async unpin(imageId) {
4474
+ if (!imageId?.trim()) {
4475
+ throw new Error("imageId is required");
4476
+ }
4477
+ const auth = await this.auth.authForResponses();
4478
+ return await this.http.json(`/images/${imageId}/pin`, {
4479
+ method: "DELETE",
4480
+ apiKey: auth.apiKey,
4481
+ accessToken: auth.accessToken
4482
+ });
4483
+ }
4233
4484
  };
4234
4485
 
4486
+ // src/sessions/context_management.ts
4487
+ var DEFAULT_CONTEXT_BUFFER_TOKENS = 256;
4488
+ var CONTEXT_BUFFER_RATIO = 0.02;
4489
+ var MESSAGE_OVERHEAD_TOKENS = 6;
4490
+ var TOOL_CALL_OVERHEAD_TOKENS = 4;
4491
+ var CHARS_PER_TOKEN = 4;
4492
+ var IMAGE_TOKENS_LOW_DETAIL = 85;
4493
+ var IMAGE_TOKENS_HIGH_DETAIL = 1e3;
4494
+ var modelContextCache = /* @__PURE__ */ new WeakMap();
4495
+ function createModelContextResolver(client) {
4496
+ return async (modelId) => {
4497
+ const entry = getModelContextCacheEntry(client);
4498
+ const key = String(modelId);
4499
+ const cached = entry.byId.get(key);
4500
+ if (cached !== void 0) {
4501
+ return cached;
4502
+ }
4503
+ await populateModelContextCache(client, entry);
4504
+ const resolved = entry.byId.get(key);
4505
+ if (resolved === void 0) {
4506
+ throw new ConfigError(
4507
+ `Unknown model "${key}"; ensure the model exists in the ModelRelay catalog`
4508
+ );
4509
+ }
4510
+ return resolved;
4511
+ };
4512
+ }
4513
+ async function buildSessionInputWithContext(messages, options, defaultModel, resolveModelContext) {
4514
+ const strategy = options.contextManagement ?? "none";
4515
+ if (strategy === "none") {
4516
+ return messagesToInput(messages);
4517
+ }
4518
+ if (strategy === "summarize") {
4519
+ throw new ConfigError("contextManagement 'summarize' is not implemented yet");
4520
+ }
4521
+ if (strategy !== "truncate") {
4522
+ throw new ConfigError(`Unknown contextManagement strategy: ${strategy}`);
4523
+ }
4524
+ const modelId = options.model ?? defaultModel;
4525
+ if (!modelId) {
4526
+ throw new ConfigError(
4527
+ "model is required for context management; set options.model or a session defaultModel"
4528
+ );
4529
+ }
4530
+ const budget = await resolveHistoryBudget(
4531
+ modelId,
4532
+ options,
4533
+ resolveModelContext
4534
+ );
4535
+ const truncated = truncateMessagesByTokens(
4536
+ messages,
4537
+ budget.maxHistoryTokens
4538
+ );
4539
+ if (options.onContextTruncate && truncated.length < messages.length) {
4540
+ const info = {
4541
+ model: modelId,
4542
+ originalMessages: messages.length,
4543
+ keptMessages: truncated.length,
4544
+ maxHistoryTokens: budget.maxHistoryTokens,
4545
+ reservedOutputTokens: budget.reservedOutputTokens
4546
+ };
4547
+ options.onContextTruncate(info);
4548
+ }
4549
+ return messagesToInput(truncated);
4550
+ }
4551
+ function messagesToInput(messages) {
4552
+ return messages.map((m) => ({
4553
+ type: m.type,
4554
+ role: m.role,
4555
+ content: m.content,
4556
+ toolCalls: m.toolCalls,
4557
+ toolCallId: m.toolCallId
4558
+ }));
4559
+ }
4560
+ function truncateMessagesByTokens(messages, maxHistoryTokens) {
4561
+ const maxTokens = normalizePositiveInt(maxHistoryTokens, "maxHistoryTokens");
4562
+ if (messages.length === 0) return [];
4563
+ const tokensByIndex = messages.map((msg) => estimateTokensForMessage(msg));
4564
+ const systemIndices = messages.map((msg, idx) => msg.role === "system" ? idx : -1).filter((idx) => idx >= 0);
4565
+ let selectedSystem = [...systemIndices];
4566
+ let systemTokens = sumTokens(tokensByIndex, selectedSystem);
4567
+ while (systemTokens > maxTokens && selectedSystem.length > 1) {
4568
+ selectedSystem.shift();
4569
+ systemTokens = sumTokens(tokensByIndex, selectedSystem);
4570
+ }
4571
+ if (systemTokens > maxTokens) {
4572
+ throw new ConfigError(
4573
+ "maxHistoryTokens is too small to fit the latest system message"
4574
+ );
4575
+ }
4576
+ const selected = new Set(selectedSystem);
4577
+ let remaining = maxTokens - systemTokens;
4578
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
4579
+ if (selected.has(i)) continue;
4580
+ const tokens = tokensByIndex[i];
4581
+ if (tokens <= remaining) {
4582
+ selected.add(i);
4583
+ remaining -= tokens;
4584
+ }
4585
+ }
4586
+ const result = messages.filter((_, idx) => selected.has(idx));
4587
+ if (result.length === 0) {
4588
+ throw new ConfigError("No messages fit within maxHistoryTokens");
4589
+ }
4590
+ return result;
4591
+ }
4592
+ function estimateTokens(text) {
4593
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
4594
+ }
4595
+ function isImagePart(part) {
4596
+ if (typeof part !== "object" || part === null) return false;
4597
+ const p = part;
4598
+ return p.type === "image" || p.type === "image_url";
4599
+ }
4600
+ function estimateImageTokens(part) {
4601
+ const detail = part.detail ?? "auto";
4602
+ if (detail === "low") return IMAGE_TOKENS_LOW_DETAIL;
4603
+ return IMAGE_TOKENS_HIGH_DETAIL;
4604
+ }
4605
+ function estimateTokensForMessage(message) {
4606
+ const segments = [message.role];
4607
+ let imageTokens = 0;
4608
+ for (const part of message.content || []) {
4609
+ if (part.type === "text" && part.text) {
4610
+ segments.push(part.text);
4611
+ } else if (isImagePart(part)) {
4612
+ imageTokens += estimateImageTokens(part);
4613
+ }
4614
+ }
4615
+ if (message.toolCalls) {
4616
+ for (const call of message.toolCalls) {
4617
+ if (call.function?.name) segments.push(call.function.name);
4618
+ if (call.function?.arguments) segments.push(call.function.arguments);
4619
+ }
4620
+ }
4621
+ if (message.toolCallId) {
4622
+ segments.push(message.toolCallId);
4623
+ }
4624
+ const textTokens = estimateTokens(segments.join("\n"));
4625
+ const toolOverhead = message.toolCalls ? message.toolCalls.length * TOOL_CALL_OVERHEAD_TOKENS : 0;
4626
+ return textTokens + MESSAGE_OVERHEAD_TOKENS + toolOverhead + imageTokens;
4627
+ }
4628
+ function normalizePositiveInt(value, label) {
4629
+ if (!Number.isFinite(value) || value <= 0) {
4630
+ throw new ConfigError(`${label} must be a positive number`);
4631
+ }
4632
+ return Math.floor(value);
4633
+ }
4634
+ function sumTokens(tokensByIndex, indices) {
4635
+ return indices.reduce((sum, idx) => sum + tokensByIndex[idx], 0);
4636
+ }
4637
+ async function resolveHistoryBudget(modelId, options, resolveModelContext) {
4638
+ const reservedOutputTokens = options.reserveOutputTokens === void 0 ? void 0 : normalizeNonNegativeInt(
4639
+ options.reserveOutputTokens,
4640
+ "reserveOutputTokens"
4641
+ );
4642
+ if (options.maxHistoryTokens !== void 0) {
4643
+ return {
4644
+ maxHistoryTokens: normalizePositiveInt(
4645
+ options.maxHistoryTokens,
4646
+ "maxHistoryTokens"
4647
+ ),
4648
+ reservedOutputTokens
4649
+ };
4650
+ }
4651
+ const model = await resolveModelContext(modelId);
4652
+ if (!model) {
4653
+ throw new ConfigError(
4654
+ `Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
4655
+ );
4656
+ }
4657
+ const contextWindow = normalizePositiveInt(model.contextWindow, "context_window");
4658
+ const modelOutputTokens = model.maxOutputTokens === void 0 ? 0 : normalizeNonNegativeInt(model.maxOutputTokens, "max_output_tokens");
4659
+ const effectiveReserve = reservedOutputTokens ?? modelOutputTokens;
4660
+ const buffer = Math.max(
4661
+ DEFAULT_CONTEXT_BUFFER_TOKENS,
4662
+ Math.ceil(contextWindow * CONTEXT_BUFFER_RATIO)
4663
+ );
4664
+ const maxHistoryTokens = contextWindow - effectiveReserve - buffer;
4665
+ if (maxHistoryTokens <= 0) {
4666
+ throw new ConfigError(
4667
+ "model context window is too small after reserving output tokens; set maxHistoryTokens explicitly"
4668
+ );
4669
+ }
4670
+ return {
4671
+ maxHistoryTokens,
4672
+ reservedOutputTokens: effectiveReserve
4673
+ };
4674
+ }
4675
+ function normalizeNonNegativeInt(value, label) {
4676
+ if (!Number.isFinite(value) || value < 0) {
4677
+ throw new ConfigError(`${label} must be a non-negative number`);
4678
+ }
4679
+ return Math.floor(value);
4680
+ }
4681
+ function getModelContextCacheEntry(client) {
4682
+ const existing = modelContextCache.get(client);
4683
+ if (existing) return existing;
4684
+ const entry = { byId: /* @__PURE__ */ new Map() };
4685
+ modelContextCache.set(client, entry);
4686
+ return entry;
4687
+ }
4688
+ async function populateModelContextCache(client, entry) {
4689
+ if (!entry.listPromise) {
4690
+ entry.listPromise = (async () => {
4691
+ const models = await client.models.list();
4692
+ for (const model of models) {
4693
+ entry.byId.set(String(model.model_id), {
4694
+ contextWindow: model.context_window,
4695
+ maxOutputTokens: model.max_output_tokens
4696
+ });
4697
+ }
4698
+ })().finally(() => {
4699
+ entry.listPromise = void 0;
4700
+ });
4701
+ }
4702
+ await entry.listPromise;
4703
+ }
4704
+
4235
4705
  // src/sessions/types.ts
4236
4706
  function asSessionId(value) {
4237
4707
  return value;
@@ -4296,6 +4766,7 @@ var LocalSession = class _LocalSession {
4296
4766
  this.defaultProvider = options.defaultProvider;
4297
4767
  this.defaultTools = options.defaultTools;
4298
4768
  this.metadata = options.metadata || {};
4769
+ this.resolveModelContext = createModelContextResolver(client);
4299
4770
  if (existingState) {
4300
4771
  this.id = existingState.id;
4301
4772
  this.messages = existingState.messages.map((m) => ({
@@ -4362,7 +4833,7 @@ var LocalSession = class _LocalSession {
4362
4833
  this.currentNodeId = void 0;
4363
4834
  this.currentWaiting = void 0;
4364
4835
  try {
4365
- const input = this.buildInput();
4836
+ const input = await this.buildInput(options);
4366
4837
  const tools = mergeTools(this.defaultTools, options.tools);
4367
4838
  const spec = {
4368
4839
  kind: "workflow.v0",
@@ -4424,6 +4895,68 @@ var LocalSession = class _LocalSession {
4424
4895
  await this.persist();
4425
4896
  await this.store.close();
4426
4897
  }
4898
+ /**
4899
+ * Sync this local session's messages to a remote session.
4900
+ *
4901
+ * This uploads all local messages to the remote session, enabling
4902
+ * cross-device access and server-side backup. Messages are synced
4903
+ * in order and the remote session's history will contain all local
4904
+ * messages after sync completes.
4905
+ *
4906
+ * @param remoteSession - The remote session to sync to
4907
+ * @param options - Optional sync configuration
4908
+ * @returns Sync result with message count
4909
+ *
4910
+ * @example
4911
+ * ```typescript
4912
+ * // Create local session and work offline
4913
+ * const local = LocalSession.create(client, { ... });
4914
+ * await local.run("Implement the feature");
4915
+ *
4916
+ * // Later, sync to remote for backup/sharing
4917
+ * const remote = await RemoteSession.create(client);
4918
+ * const result = await local.syncTo(remote, {
4919
+ * onProgress: (synced, total) => console.log(`${synced}/${total}`),
4920
+ * });
4921
+ * ```
4922
+ */
4923
+ async syncTo(remoteSession, options = {}) {
4924
+ const { onProgress, signal } = options;
4925
+ const total = this.messages.length;
4926
+ if (total === 0) {
4927
+ return {
4928
+ messagesSynced: 0,
4929
+ remoteSessionId: remoteSession.id
4930
+ };
4931
+ }
4932
+ if (remoteSession.history.length > 0) {
4933
+ throw new ConfigError(
4934
+ `Cannot sync to non-empty remote session (has ${remoteSession.history.length} messages). syncTo() is for initial migration only. Create a new remote session or use bidirectional sync.`
4935
+ );
4936
+ }
4937
+ const http = this.client.http;
4938
+ let synced = 0;
4939
+ for (const message of this.messages) {
4940
+ if (signal?.aborted) {
4941
+ throw new Error("Sync aborted");
4942
+ }
4943
+ await http.request(`/sessions/${remoteSession.id}/messages`, {
4944
+ method: "POST",
4945
+ body: {
4946
+ role: message.role,
4947
+ content: message.content,
4948
+ run_id: message.runId ? String(message.runId) : void 0
4949
+ }
4950
+ });
4951
+ synced++;
4952
+ onProgress?.(synced, total);
4953
+ }
4954
+ await remoteSession.refresh();
4955
+ return {
4956
+ messagesSynced: synced,
4957
+ remoteSessionId: remoteSession.id
4958
+ };
4959
+ }
4427
4960
  // ============================================================================
4428
4961
  // Private Methods
4429
4962
  // ============================================================================
@@ -4438,14 +4971,13 @@ var LocalSession = class _LocalSession {
4438
4971
  this.updatedAt = /* @__PURE__ */ new Date();
4439
4972
  return message;
4440
4973
  }
4441
- buildInput() {
4442
- return this.messages.map((m) => ({
4443
- type: m.type,
4444
- role: m.role,
4445
- content: m.content,
4446
- toolCalls: m.toolCalls,
4447
- toolCallId: m.toolCallId
4448
- }));
4974
+ async buildInput(options) {
4975
+ return buildSessionInputWithContext(
4976
+ this.messages,
4977
+ options,
4978
+ this.defaultModel,
4979
+ this.resolveModelContext
4980
+ );
4449
4981
  }
4450
4982
  async processRunEvents(signal) {
4451
4983
  if (!this.currentRunId) {
@@ -4670,6 +5202,7 @@ var RemoteSession = class _RemoteSession {
4670
5202
  this.defaultModel = options.defaultModel;
4671
5203
  this.defaultProvider = options.defaultProvider;
4672
5204
  this.defaultTools = options.defaultTools;
5205
+ this.resolveModelContext = createModelContextResolver(client);
4673
5206
  if ("messages" in sessionData && sessionData.messages) {
4674
5207
  this.messages = sessionData.messages.map((m) => ({
4675
5208
  type: "message",
@@ -4780,7 +5313,7 @@ var RemoteSession = class _RemoteSession {
4780
5313
  });
4781
5314
  this.resetRunState();
4782
5315
  try {
4783
- const input = this.buildInput();
5316
+ const input = await this.buildInput(options);
4784
5317
  const tools = mergeTools2(this.defaultTools, options.tools);
4785
5318
  const spec = {
4786
5319
  kind: "workflow.v0",
@@ -4803,7 +5336,8 @@ var RemoteSession = class _RemoteSession {
4803
5336
  outputs: [{ name: "result", from: "main" }]
4804
5337
  };
4805
5338
  const run = await this.client.runs.create(spec, {
4806
- customerId: options.customerId || this.endUserId
5339
+ customerId: options.customerId || this.endUserId,
5340
+ sessionId: String(this.id)
4807
5341
  });
4808
5342
  this.currentRunId = run.run_id;
4809
5343
  return await this.processRunEvents(options.signal);
@@ -4888,14 +5422,13 @@ var RemoteSession = class _RemoteSession {
4888
5422
  this.updatedAt = /* @__PURE__ */ new Date();
4889
5423
  return message;
4890
5424
  }
4891
- buildInput() {
4892
- return this.messages.map((m) => ({
4893
- type: m.type,
4894
- role: m.role,
4895
- content: m.content,
4896
- toolCalls: m.toolCalls,
4897
- toolCallId: m.toolCallId
4898
- }));
5425
+ async buildInput(options) {
5426
+ return buildSessionInputWithContext(
5427
+ this.messages,
5428
+ options,
5429
+ this.defaultModel,
5430
+ this.resolveModelContext
5431
+ );
4899
5432
  }
4900
5433
  resetRunState() {
4901
5434
  this.currentRunId = void 0;
@@ -4972,10 +5505,7 @@ var RemoteSession = class _RemoteSession {
4972
5505
  break;
4973
5506
  case "run_completed": {
4974
5507
  const runState2 = await this.client.runs.get(this.currentRunId);
4975
- let output2;
4976
- if (runState2.outputs && Array.isArray(runState2.outputs)) {
4977
- output2 = runState2.outputs.filter((o) => o.type === "text").map((o) => o.text || "").join("");
4978
- }
5508
+ const output2 = this.extractOutputText(runState2.outputs);
4979
5509
  if (output2) {
4980
5510
  this.addMessage(
4981
5511
  {
@@ -4983,7 +5513,8 @@ var RemoteSession = class _RemoteSession {
4983
5513
  role: "assistant",
4984
5514
  content: [{ type: "text", text: output2 }]
4985
5515
  },
4986
- this.currentRunId
5516
+ this.currentRunId,
5517
+ false
4987
5518
  );
4988
5519
  }
4989
5520
  await this.flushPendingMessages();
@@ -5013,10 +5544,7 @@ var RemoteSession = class _RemoteSession {
5013
5544
  }
5014
5545
  }
5015
5546
  const runState = await this.client.runs.get(this.currentRunId);
5016
- let output;
5017
- if (runState.outputs && Array.isArray(runState.outputs)) {
5018
- output = runState.outputs.filter((o) => o.type === "text").map((o) => o.text || "").join("");
5019
- }
5547
+ const output = this.extractOutputText(runState.outputs);
5020
5548
  if (output) {
5021
5549
  this.addMessage(
5022
5550
  {
@@ -5024,7 +5552,8 @@ var RemoteSession = class _RemoteSession {
5024
5552
  role: "assistant",
5025
5553
  content: [{ type: "text", text: output }]
5026
5554
  },
5027
- this.currentRunId
5555
+ this.currentRunId,
5556
+ false
5028
5557
  );
5029
5558
  }
5030
5559
  await this.flushPendingMessages();
@@ -5111,6 +5640,45 @@ var RemoteSession = class _RemoteSession {
5111
5640
  runId: data.run_id ? parseRunId(data.run_id) : void 0
5112
5641
  };
5113
5642
  }
5643
+ extractOutputText(outputs) {
5644
+ if (!outputs) return void 0;
5645
+ for (const value of Object.values(outputs)) {
5646
+ const text = this.extractTextFromOutputValue(value);
5647
+ if (text) return text;
5648
+ }
5649
+ return void 0;
5650
+ }
5651
+ extractTextFromOutputValue(value) {
5652
+ if (!value || typeof value !== "object") return void 0;
5653
+ if ("output" in value && Array.isArray(value.output)) {
5654
+ return this.extractTextFromOutputItems(value.output);
5655
+ }
5656
+ if (Array.isArray(value)) {
5657
+ const arr = value;
5658
+ if (arr.length === 0 || typeof arr[0] !== "object") return void 0;
5659
+ if ("content" in arr[0]) {
5660
+ return this.extractTextFromOutputItems(arr);
5661
+ }
5662
+ if ("type" in arr[0] && "text" in arr[0]) {
5663
+ return this.extractTextFromContentParts(arr);
5664
+ }
5665
+ }
5666
+ return void 0;
5667
+ }
5668
+ extractTextFromOutputItems(items) {
5669
+ const texts = [];
5670
+ for (const item of items) {
5671
+ if (item.type !== "message" || item.role !== "assistant") continue;
5672
+ texts.push(this.extractTextFromContentParts(item.content) || "");
5673
+ }
5674
+ const combined = texts.join("");
5675
+ return combined.trim() ? combined : void 0;
5676
+ }
5677
+ extractTextFromContentParts(parts) {
5678
+ if (!parts || parts.length === 0) return void 0;
5679
+ const text = parts.filter((p) => p.type === "text").map((p) => p.text || "").join("");
5680
+ return text.trim() ? text : void 0;
5681
+ }
5114
5682
  };
5115
5683
  function getHTTPClient(client) {
5116
5684
  return client.http;
@@ -5286,7 +5854,7 @@ var HTTPClient = class {
5286
5854
  this.metrics = cfg.metrics;
5287
5855
  this.trace = cfg.trace;
5288
5856
  }
5289
- async request(path2, options = {}) {
5857
+ async request(path, options = {}) {
5290
5858
  const fetchFn = this.fetchImpl ?? globalThis.fetch;
5291
5859
  if (!fetchFn) {
5292
5860
  throw new ConfigError(
@@ -5294,12 +5862,12 @@ var HTTPClient = class {
5294
5862
  );
5295
5863
  }
5296
5864
  const method = options.method || "GET";
5297
- const url = buildUrl(this.baseUrl, path2);
5865
+ const url = buildUrl(this.baseUrl, path);
5298
5866
  const metrics = mergeMetrics(this.metrics, options.metrics);
5299
5867
  const trace = mergeTrace(this.trace, options.trace);
5300
5868
  const context = {
5301
5869
  method,
5302
- path: path2,
5870
+ path,
5303
5871
  ...options.context || {}
5304
5872
  };
5305
5873
  trace?.requestStart?.(context);
@@ -5440,8 +6008,8 @@ var HTTPClient = class {
5440
6008
  retries: buildRetryMetadata(attempts, lastStatus)
5441
6009
  });
5442
6010
  }
5443
- async json(path2, options = {}) {
5444
- const response = await this.request(path2, {
6011
+ async json(path, options = {}) {
6012
+ const response = await this.request(path, {
5445
6013
  ...options,
5446
6014
  raw: true,
5447
6015
  accept: options.accept || "application/json"
@@ -5462,14 +6030,14 @@ var HTTPClient = class {
5462
6030
  }
5463
6031
  }
5464
6032
  };
5465
- function buildUrl(baseUrl, path2) {
5466
- if (/^https?:\/\//i.test(path2)) {
5467
- return path2;
6033
+ function buildUrl(baseUrl, path) {
6034
+ if (/^https?:\/\//i.test(path)) {
6035
+ return path;
5468
6036
  }
5469
- if (!path2.startsWith("/")) {
5470
- path2 = `/${path2}`;
6037
+ if (!path.startsWith("/")) {
6038
+ path = `/${path}`;
5471
6039
  }
5472
- return `${baseUrl}${path2}`;
6040
+ return `${baseUrl}${path}`;
5473
6041
  }
5474
6042
  function normalizeBaseUrl(value) {
5475
6043
  const trimmed = value.trim();
@@ -5512,7 +6080,7 @@ function backoff(attempt, cfg) {
5512
6080
  const jitter = 0.5 + Math.random();
5513
6081
  const delay = Math.min(cfg.maxBackoffMs, capped * jitter);
5514
6082
  if (delay <= 0) return Promise.resolve();
5515
- return new Promise((resolve2) => setTimeout(resolve2, delay));
6083
+ return new Promise((resolve) => setTimeout(resolve, delay));
5516
6084
  }
5517
6085
  function mergeSignals(...signals) {
5518
6086
  const active = signals.filter(Boolean);
@@ -5768,6 +6336,23 @@ var OIDCExchangeTokenProvider = class {
5768
6336
  };
5769
6337
 
5770
6338
  // src/device_flow.ts
6339
+ async function pollUntil(opts) {
6340
+ let intervalMs = Math.max(1, opts.intervalMs);
6341
+ let attempt = 0;
6342
+ while (true) {
6343
+ if (opts.deadline && Date.now() >= opts.deadline.getTime()) {
6344
+ throw opts.onTimeout?.() ?? new TransportError("polling timed out", { kind: "timeout" });
6345
+ }
6346
+ const result = await opts.poll(attempt);
6347
+ if (result.done) {
6348
+ return result.value;
6349
+ }
6350
+ const delay = Math.max(1, result.retryAfterMs ?? intervalMs);
6351
+ intervalMs = delay;
6352
+ await sleep(delay, opts.signal);
6353
+ attempt += 1;
6354
+ }
6355
+ }
5771
6356
  async function startOAuthDeviceAuthorization(req) {
5772
6357
  const deviceAuthorizationEndpoint = req.deviceAuthorizationEndpoint?.trim();
5773
6358
  if (!deviceAuthorizationEndpoint) {
@@ -5825,58 +6410,59 @@ async function pollOAuthDeviceToken(req) {
5825
6410
  }
5826
6411
  const deadline = req.deadline ?? new Date(Date.now() + 10 * 60 * 1e3);
5827
6412
  let intervalMs = Math.max(1, req.intervalSeconds ?? 5) * 1e3;
5828
- while (true) {
5829
- if (Date.now() >= deadline.getTime()) {
5830
- throw new TransportError("oauth device flow timed out", { kind: "timeout" });
5831
- }
5832
- const form = new URLSearchParams();
5833
- form.set("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
5834
- form.set("device_code", deviceCode);
5835
- form.set("client_id", clientId);
5836
- const payload = await postOAuthForm(tokenEndpoint, form, {
5837
- fetch: req.fetch,
5838
- signal: req.signal,
5839
- allowErrorPayload: true
5840
- });
5841
- const err = String(payload.error || "").trim();
5842
- if (err) {
5843
- switch (err) {
5844
- case "authorization_pending":
5845
- await sleep(intervalMs, req.signal);
5846
- continue;
5847
- case "slow_down":
5848
- intervalMs += 5e3;
5849
- await sleep(intervalMs, req.signal);
5850
- continue;
5851
- case "expired_token":
5852
- case "access_denied":
5853
- case "invalid_grant":
5854
- throw new TransportError(`oauth device flow failed: ${err}`, {
5855
- kind: "request",
5856
- cause: payload
5857
- });
5858
- default:
5859
- throw new TransportError(`oauth device flow error: ${err}`, {
5860
- kind: "request",
5861
- cause: payload
5862
- });
5863
- }
5864
- }
5865
- const accessToken = String(payload.access_token || "").trim() || void 0;
5866
- const idToken = String(payload.id_token || "").trim() || void 0;
5867
- const refreshToken = String(payload.refresh_token || "").trim() || void 0;
5868
- const tokenType = String(payload.token_type || "").trim() || void 0;
5869
- const scope = String(payload.scope || "").trim() || void 0;
5870
- const expiresIn = payload.expires_in !== void 0 ? Number(payload.expires_in) : void 0;
5871
- const expiresAt = typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? new Date(Date.now() + expiresIn * 1e3) : void 0;
5872
- if (!accessToken && !idToken) {
5873
- throw new TransportError("oauth device flow returned an invalid token response", {
5874
- kind: "request",
5875
- cause: payload
6413
+ return pollUntil({
6414
+ intervalMs,
6415
+ deadline,
6416
+ signal: req.signal,
6417
+ onTimeout: () => new TransportError("oauth device flow timed out", { kind: "timeout" }),
6418
+ poll: async () => {
6419
+ const form = new URLSearchParams();
6420
+ form.set("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
6421
+ form.set("device_code", deviceCode);
6422
+ form.set("client_id", clientId);
6423
+ const payload = await postOAuthForm(tokenEndpoint, form, {
6424
+ fetch: req.fetch,
6425
+ signal: req.signal,
6426
+ allowErrorPayload: true
5876
6427
  });
6428
+ const err = String(payload.error || "").trim();
6429
+ if (err) {
6430
+ switch (err) {
6431
+ case "authorization_pending":
6432
+ return { done: false };
6433
+ case "slow_down":
6434
+ intervalMs += 5e3;
6435
+ return { done: false, retryAfterMs: intervalMs };
6436
+ case "expired_token":
6437
+ case "access_denied":
6438
+ case "invalid_grant":
6439
+ throw new TransportError(`oauth device flow failed: ${err}`, {
6440
+ kind: "request",
6441
+ cause: payload
6442
+ });
6443
+ default:
6444
+ throw new TransportError(`oauth device flow error: ${err}`, {
6445
+ kind: "request",
6446
+ cause: payload
6447
+ });
6448
+ }
6449
+ }
6450
+ const accessToken = String(payload.access_token || "").trim() || void 0;
6451
+ const idToken = String(payload.id_token || "").trim() || void 0;
6452
+ const refreshToken = String(payload.refresh_token || "").trim() || void 0;
6453
+ const tokenType = String(payload.token_type || "").trim() || void 0;
6454
+ const scope = String(payload.scope || "").trim() || void 0;
6455
+ const expiresIn = payload.expires_in !== void 0 ? Number(payload.expires_in) : void 0;
6456
+ const expiresAt = typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? new Date(Date.now() + expiresIn * 1e3) : void 0;
6457
+ if (!accessToken && !idToken) {
6458
+ throw new TransportError("oauth device flow returned an invalid token response", {
6459
+ kind: "request",
6460
+ cause: payload
6461
+ });
6462
+ }
6463
+ return { done: true, value: { accessToken, idToken, refreshToken, tokenType, scope, expiresAt } };
5877
6464
  }
5878
- return { accessToken, idToken, refreshToken, tokenType, scope, expiresAt };
5879
- }
6465
+ });
5880
6466
  }
5881
6467
  async function runOAuthDeviceFlowForIDToken(cfg) {
5882
6468
  const auth = await startOAuthDeviceAuthorization({
@@ -5940,13 +6526,13 @@ async function sleep(ms, signal) {
5940
6526
  return;
5941
6527
  }
5942
6528
  if (!signal) {
5943
- await new Promise((resolve2) => setTimeout(resolve2, ms));
6529
+ await new Promise((resolve) => setTimeout(resolve, ms));
5944
6530
  return;
5945
6531
  }
5946
6532
  if (signal.aborted) {
5947
6533
  throw new TransportError("oauth device flow aborted", { kind: "request" });
5948
6534
  }
5949
- await new Promise((resolve2, reject) => {
6535
+ await new Promise((resolve, reject) => {
5950
6536
  const onAbort = () => {
5951
6537
  signal.removeEventListener("abort", onAbort);
5952
6538
  reject(new TransportError("oauth device flow aborted", { kind: "request" }));
@@ -5954,27 +6540,154 @@ async function sleep(ms, signal) {
5954
6540
  signal.addEventListener("abort", onAbort);
5955
6541
  setTimeout(() => {
5956
6542
  signal.removeEventListener("abort", onAbort);
5957
- resolve2();
6543
+ resolve();
5958
6544
  }, ms);
5959
6545
  });
5960
6546
  }
5961
6547
 
5962
- // src/workflow_builder.ts
5963
- var LLM_TEXT_OUTPUT = "/output/0/content/0/text";
5964
- var LLM_USER_MESSAGE_TEXT = "/request/input/1/content/0/text";
5965
- function transformJSONValue(from, pointer) {
5966
- return pointer ? { from, pointer } : { from };
5967
- }
5968
- function transformJSONObject(object) {
5969
- return { object };
5970
- }
5971
- function transformJSONMerge(merge) {
5972
- return { merge: merge.slice() };
5973
- }
5974
- function wireRequest(req) {
5975
- const raw = req;
5976
- if (raw && typeof raw === "object") {
5977
- if ("input" in raw) {
6548
+ // src/json_path.ts
6549
+ var LLMOutputPath = class {
6550
+ constructor(path = "/output") {
6551
+ this.path = path;
6552
+ }
6553
+ /** Select an output by index */
6554
+ index(i) {
6555
+ return new LLMOutputContentPath(`${this.path}/${i}`);
6556
+ }
6557
+ /** Shorthand for index(0).content(i) */
6558
+ content(i) {
6559
+ return this.index(0).content(i);
6560
+ }
6561
+ };
6562
+ var LLMOutputContentPath = class {
6563
+ constructor(path) {
6564
+ this.path = path;
6565
+ }
6566
+ /** Select a content item by index */
6567
+ content(i) {
6568
+ return new LLMOutputContentItemPath(`${this.path}/content/${i}`);
6569
+ }
6570
+ };
6571
+ var LLMOutputContentItemPath = class {
6572
+ constructor(path) {
6573
+ this.path = path;
6574
+ }
6575
+ /** Get the text field pointer */
6576
+ text() {
6577
+ return `${this.path}/text`;
6578
+ }
6579
+ /** Get the type field pointer */
6580
+ type() {
6581
+ return `${this.path}/type`;
6582
+ }
6583
+ /** Get the path as a string */
6584
+ toString() {
6585
+ return this.path;
6586
+ }
6587
+ };
6588
+ var LLMInputPath = class {
6589
+ constructor(path = "/input") {
6590
+ this.path = path;
6591
+ }
6592
+ /**
6593
+ * Select a message by index.
6594
+ * Index 0 is typically the system message, index 1 is the first user message.
6595
+ */
6596
+ message(i) {
6597
+ return new LLMInputMessagePath(`${this.path}/${i}`);
6598
+ }
6599
+ /** Shorthand for message(0) - the first message slot */
6600
+ systemMessage() {
6601
+ return this.message(0);
6602
+ }
6603
+ /** Shorthand for message(1) - typically the user message after system */
6604
+ userMessage() {
6605
+ return this.message(1);
6606
+ }
6607
+ };
6608
+ var LLMInputMessagePath = class {
6609
+ constructor(path) {
6610
+ this.path = path;
6611
+ }
6612
+ /** Select a content item by index */
6613
+ content(i) {
6614
+ return new LLMInputContentItemPath(`${this.path}/content/${i}`);
6615
+ }
6616
+ /** Shorthand for content(0).text() */
6617
+ text() {
6618
+ return this.content(0).text();
6619
+ }
6620
+ };
6621
+ var LLMInputContentItemPath = class {
6622
+ constructor(path) {
6623
+ this.path = path;
6624
+ }
6625
+ /** Get the text field pointer */
6626
+ text() {
6627
+ return `${this.path}/text`;
6628
+ }
6629
+ /** Get the type field pointer */
6630
+ type() {
6631
+ return `${this.path}/type`;
6632
+ }
6633
+ /** Get the path as a string */
6634
+ toString() {
6635
+ return this.path;
6636
+ }
6637
+ };
6638
+ function LLMOutput() {
6639
+ return new LLMOutputPath();
6640
+ }
6641
+ function LLMInput() {
6642
+ return new LLMInputPath();
6643
+ }
6644
+ var LLMOutputText = LLMOutput().content(0).text();
6645
+ var LLMInputSystemText = LLMInput().systemMessage().text();
6646
+ var LLMInputUserText = LLMInput().userMessage().text();
6647
+ var LLMInputFirstMessageText = LLMInput().message(0).text();
6648
+ var JoinOutputPath = class {
6649
+ constructor(path) {
6650
+ this.path = path;
6651
+ }
6652
+ /** Access the output array of the node */
6653
+ output() {
6654
+ return new LLMOutputPath(`${this.path}/output`);
6655
+ }
6656
+ /**
6657
+ * Shorthand for accessing the first text content from the node.
6658
+ * Equivalent to: JoinOutput(nodeId).output().content(0).text()
6659
+ */
6660
+ text() {
6661
+ return this.output().content(0).text();
6662
+ }
6663
+ /** Get the path as a string */
6664
+ toString() {
6665
+ return this.path;
6666
+ }
6667
+ };
6668
+ function JoinOutput(nodeId) {
6669
+ return new JoinOutputPath(`/${nodeId}`);
6670
+ }
6671
+
6672
+ // src/workflow_builder.ts
6673
+ var LLM_TEXT_OUTPUT = LLMOutputText;
6674
+ var LLM_USER_MESSAGE_TEXT = LLMInputUserText;
6675
+ function transformJSONValue(from, pointer) {
6676
+ return pointer ? { from, pointer } : { from };
6677
+ }
6678
+ function transformJSONObject(object) {
6679
+ return { object };
6680
+ }
6681
+ function transformJSONMerge(merge) {
6682
+ return { merge: merge.slice() };
6683
+ }
6684
+ var transformJSONValueV1 = transformJSONValue;
6685
+ var transformJSONObjectV1 = transformJSONObject;
6686
+ var transformJSONMergeV1 = transformJSONMerge;
6687
+ function wireRequest(req) {
6688
+ const raw = req;
6689
+ if (raw && typeof raw === "object") {
6690
+ if ("input" in raw) {
5978
6691
  return req;
5979
6692
  }
5980
6693
  if ("body" in raw) {
@@ -5983,6 +6696,98 @@ function wireRequest(req) {
5983
6696
  }
5984
6697
  return asInternal(req).body;
5985
6698
  }
6699
+ var INPUT_POINTER_PATTERN = /^\/input\/(\d+)(?:\/content\/(\d+))?/;
6700
+ var BindingTargetError = class extends Error {
6701
+ constructor(nodeId, bindingIndex, pointer, message) {
6702
+ super(`node "${nodeId}" binding ${bindingIndex}: ${message}`);
6703
+ this.name = "BindingTargetError";
6704
+ this.nodeId = nodeId;
6705
+ this.bindingIndex = bindingIndex;
6706
+ this.pointer = pointer;
6707
+ }
6708
+ };
6709
+ var MapFanoutInputError = class extends Error {
6710
+ constructor(nodeId, message) {
6711
+ super(`node "${nodeId}": ${message}`);
6712
+ this.name = "MapFanoutInputError";
6713
+ this.nodeId = nodeId;
6714
+ }
6715
+ };
6716
+ function validateBindingTargets(nodeId, input, bindings) {
6717
+ for (let i = 0; i < bindings.length; i++) {
6718
+ const binding = bindings[i];
6719
+ if (!binding.to) continue;
6720
+ const error = validateInputPointer(binding.to, input);
6721
+ if (error) {
6722
+ throw new BindingTargetError(nodeId, i, binding.to, error);
6723
+ }
6724
+ }
6725
+ }
6726
+ function validateInputPointer(pointer, input) {
6727
+ if (!pointer.startsWith("/input/")) {
6728
+ return void 0;
6729
+ }
6730
+ const match = pointer.match(INPUT_POINTER_PATTERN);
6731
+ if (!match) {
6732
+ return void 0;
6733
+ }
6734
+ const msgIndex = parseInt(match[1], 10);
6735
+ if (msgIndex >= input.length) {
6736
+ return `targets ${pointer} but request only has ${input.length} messages (indices 0-${input.length - 1}); add placeholder messages or adjust binding target`;
6737
+ }
6738
+ if (match[2] !== void 0) {
6739
+ const contentIndex = parseInt(match[2], 10);
6740
+ const msg = input[msgIndex];
6741
+ if (contentIndex >= msg.content.length) {
6742
+ return `targets ${pointer} but message ${msgIndex} only has ${msg.content.length} content blocks (indices 0-${msg.content.length - 1})`;
6743
+ }
6744
+ }
6745
+ return void 0;
6746
+ }
6747
+ function validateMapFanoutInput(nodeId, input) {
6748
+ const subnode = input.subnode;
6749
+ if ((subnode.type === WorkflowNodeTypesV1.LLMResponses || subnode.type === WorkflowNodeTypesV1.RouteSwitch) && subnode.input.bindings && subnode.input.bindings.length > 0) {
6750
+ throw new MapFanoutInputError(nodeId, "map.fanout subnode bindings are not allowed");
6751
+ }
6752
+ if (subnode.type !== WorkflowNodeTypesV1.TransformJSON) {
6753
+ return;
6754
+ }
6755
+ if (input.item_bindings && input.item_bindings.length > 0) {
6756
+ throw new MapFanoutInputError(
6757
+ nodeId,
6758
+ "map.fanout transform.json subnode cannot use item_bindings"
6759
+ );
6760
+ }
6761
+ const hasObject = !!subnode.input.object && Object.keys(subnode.input.object).length > 0;
6762
+ const hasMerge = !!subnode.input.merge && subnode.input.merge.length > 0;
6763
+ if (hasObject === hasMerge) {
6764
+ throw new MapFanoutInputError(
6765
+ nodeId,
6766
+ "map.fanout transform.json must provide exactly one of object or merge"
6767
+ );
6768
+ }
6769
+ if (hasObject) {
6770
+ for (const [key, value] of Object.entries(subnode.input.object ?? {})) {
6771
+ if (!key.trim()) continue;
6772
+ if (String(value.from) !== "item") {
6773
+ throw new MapFanoutInputError(
6774
+ nodeId,
6775
+ `map.fanout transform.json object.${key}.from must be "item"`
6776
+ );
6777
+ }
6778
+ }
6779
+ }
6780
+ if (hasMerge) {
6781
+ for (const [index, value] of (subnode.input.merge ?? []).entries()) {
6782
+ if (String(value.from) !== "item") {
6783
+ throw new MapFanoutInputError(
6784
+ nodeId,
6785
+ `map.fanout transform.json merge[${index}].from must be "item"`
6786
+ );
6787
+ }
6788
+ }
6789
+ }
6790
+ }
5986
6791
  var WorkflowBuilderV0 = class _WorkflowBuilderV0 {
5987
6792
  constructor(state = { nodes: [], edges: [], outputs: [] }) {
5988
6793
  this.state = state;
@@ -6006,8 +6811,12 @@ var WorkflowBuilderV0 = class _WorkflowBuilderV0 {
6006
6811
  return this.with({ nodes: [...this.state.nodes, node] });
6007
6812
  }
6008
6813
  llmResponses(id, request, options = {}) {
6814
+ const wiredRequest = wireRequest(request);
6815
+ if (options.bindings) {
6816
+ validateBindingTargets(id, wiredRequest.input, options.bindings);
6817
+ }
6009
6818
  const input = {
6010
- request: wireRequest(request),
6819
+ request: wiredRequest,
6011
6820
  ...options.stream === void 0 ? {} : { stream: options.stream },
6012
6821
  ...options.toolExecution === void 0 ? {} : { tool_execution: { mode: options.toolExecution } },
6013
6822
  ...options.toolLimits === void 0 ? {} : { tool_limits: { ...options.toolLimits } },
@@ -6076,6 +6885,141 @@ var WorkflowBuilderV0 = class _WorkflowBuilderV0 {
6076
6885
  function workflowV0() {
6077
6886
  return WorkflowBuilderV0.new();
6078
6887
  }
6888
+ var WorkflowBuilderV1 = class _WorkflowBuilderV1 {
6889
+ constructor(state = { nodes: [], edges: [], outputs: [] }) {
6890
+ this.state = state;
6891
+ }
6892
+ static new() {
6893
+ return new _WorkflowBuilderV1();
6894
+ }
6895
+ with(patch) {
6896
+ return new _WorkflowBuilderV1({
6897
+ ...this.state,
6898
+ ...patch
6899
+ });
6900
+ }
6901
+ name(name) {
6902
+ return this.with({ name: name.trim() || void 0 });
6903
+ }
6904
+ execution(execution) {
6905
+ return this.with({ execution });
6906
+ }
6907
+ node(node) {
6908
+ return this.with({ nodes: [...this.state.nodes, node] });
6909
+ }
6910
+ llmResponses(id, request, options = {}) {
6911
+ const wiredRequest = wireRequest(request);
6912
+ if (options.bindings) {
6913
+ validateBindingTargets(id, wiredRequest.input, options.bindings);
6914
+ }
6915
+ const input = {
6916
+ request: wiredRequest,
6917
+ ...options.stream === void 0 ? {} : { stream: options.stream },
6918
+ ...options.toolExecution === void 0 ? {} : { tool_execution: { mode: options.toolExecution } },
6919
+ ...options.toolLimits === void 0 ? {} : { tool_limits: { ...options.toolLimits } },
6920
+ ...options.bindings === void 0 ? {} : { bindings: options.bindings.slice() }
6921
+ };
6922
+ return this.node({
6923
+ id,
6924
+ type: WorkflowNodeTypesV1.LLMResponses,
6925
+ input
6926
+ });
6927
+ }
6928
+ routeSwitch(id, request, options = {}) {
6929
+ const wiredRequest = wireRequest(request);
6930
+ if (options.bindings) {
6931
+ validateBindingTargets(id, wiredRequest.input, options.bindings);
6932
+ }
6933
+ const input = {
6934
+ request: wiredRequest,
6935
+ ...options.stream === void 0 ? {} : { stream: options.stream },
6936
+ ...options.toolExecution === void 0 ? {} : { tool_execution: { mode: options.toolExecution } },
6937
+ ...options.toolLimits === void 0 ? {} : { tool_limits: { ...options.toolLimits } },
6938
+ ...options.bindings === void 0 ? {} : { bindings: options.bindings.slice() }
6939
+ };
6940
+ return this.node({
6941
+ id,
6942
+ type: WorkflowNodeTypesV1.RouteSwitch,
6943
+ input
6944
+ });
6945
+ }
6946
+ joinAll(id) {
6947
+ return this.node({ id, type: WorkflowNodeTypesV1.JoinAll });
6948
+ }
6949
+ joinAny(id, input) {
6950
+ return this.node({
6951
+ id,
6952
+ type: WorkflowNodeTypesV1.JoinAny,
6953
+ ...input ? { input } : {}
6954
+ });
6955
+ }
6956
+ joinCollect(id, input) {
6957
+ return this.node({ id, type: WorkflowNodeTypesV1.JoinCollect, input });
6958
+ }
6959
+ transformJSON(id, input) {
6960
+ return this.node({ id, type: WorkflowNodeTypesV1.TransformJSON, input });
6961
+ }
6962
+ mapFanout(id, input) {
6963
+ validateMapFanoutInput(id, input);
6964
+ return this.node({ id, type: WorkflowNodeTypesV1.MapFanout, input });
6965
+ }
6966
+ edge(from, to, when) {
6967
+ return this.with({
6968
+ edges: [...this.state.edges, { from, to, ...when ? { when } : {} }]
6969
+ });
6970
+ }
6971
+ output(name, from, pointer) {
6972
+ return this.with({
6973
+ outputs: [
6974
+ ...this.state.outputs,
6975
+ { name, from, ...pointer ? { pointer } : {} }
6976
+ ]
6977
+ });
6978
+ }
6979
+ build() {
6980
+ const edges = this.state.edges.slice().sort((a, b) => {
6981
+ const af = String(a.from);
6982
+ const bf = String(b.from);
6983
+ if (af < bf) return -1;
6984
+ if (af > bf) return 1;
6985
+ const at = String(a.to);
6986
+ const bt = String(b.to);
6987
+ if (at < bt) return -1;
6988
+ if (at > bt) return 1;
6989
+ const aw = a.when ? JSON.stringify(a.when) : "";
6990
+ const bw = b.when ? JSON.stringify(b.when) : "";
6991
+ if (aw < bw) return -1;
6992
+ if (aw > bw) return 1;
6993
+ return 0;
6994
+ });
6995
+ const outputs = this.state.outputs.slice().sort((a, b) => {
6996
+ const an = String(a.name);
6997
+ const bn = String(b.name);
6998
+ if (an < bn) return -1;
6999
+ if (an > bn) return 1;
7000
+ const af = String(a.from);
7001
+ const bf = String(b.from);
7002
+ if (af < bf) return -1;
7003
+ if (af > bf) return 1;
7004
+ const ap = a.pointer ?? "";
7005
+ const bp = b.pointer ?? "";
7006
+ if (ap < bp) return -1;
7007
+ if (ap > bp) return 1;
7008
+ return 0;
7009
+ });
7010
+ return {
7011
+ kind: WorkflowKinds.WorkflowV1,
7012
+ ...this.state.name ? { name: this.state.name } : {},
7013
+ ...this.state.execution ? { execution: this.state.execution } : {},
7014
+ nodes: this.state.nodes.slice(),
7015
+ ...edges.length ? { edges } : {},
7016
+ outputs
7017
+ };
7018
+ }
7019
+ };
7020
+ function workflowV1() {
7021
+ return WorkflowBuilderV1.new();
7022
+ }
6079
7023
  var Workflow = class _Workflow {
6080
7024
  constructor(name) {
6081
7025
  this._nodes = [];
@@ -6208,6 +7152,9 @@ var Workflow = class _Workflow {
6208
7152
  const pending = this._pendingNode;
6209
7153
  if (!pending) return;
6210
7154
  this._pendingNode = null;
7155
+ if (pending.bindings.length > 0) {
7156
+ validateBindingTargets(pending.id, pending.request.input, pending.bindings);
7157
+ }
6211
7158
  const input = {
6212
7159
  id: pending.id,
6213
7160
  type: WorkflowNodeTypes.LLMResponses,
@@ -6271,6 +7218,31 @@ var LLMNodeBuilder = class {
6271
7218
  }
6272
7219
  return this;
6273
7220
  }
7221
+ /**
7222
+ * Add a binding that replaces a {{placeholder}} in the prompt text.
7223
+ * This is useful when the prompt contains placeholder markers like {{tier_data}}.
7224
+ * The edge from the source node is automatically inferred.
7225
+ */
7226
+ bindToPlaceholder(from, fromPointer, placeholder) {
7227
+ const pending = this.workflow._getPendingNode();
7228
+ if (pending) {
7229
+ pending.bindings.push({
7230
+ from,
7231
+ ...fromPointer ? { pointer: fromPointer } : {},
7232
+ to_placeholder: placeholder,
7233
+ encoding: "json_string"
7234
+ });
7235
+ }
7236
+ return this;
7237
+ }
7238
+ /**
7239
+ * Add a binding from an LLM node's text output to a placeholder.
7240
+ * This is the most common placeholder binding: LLM text → {{placeholder}}.
7241
+ * The edge from the source node is automatically inferred.
7242
+ */
7243
+ bindTextToPlaceholder(from, placeholder) {
7244
+ return this.bindToPlaceholder(from, LLM_TEXT_OUTPUT, placeholder);
7245
+ }
6274
7246
  /**
6275
7247
  * Set the tool execution mode (server or client).
6276
7248
  */
@@ -6412,6 +7384,18 @@ var workflow_v0_schema_default = {
6412
7384
  },
6413
7385
  llmResponsesBinding: {
6414
7386
  additionalProperties: false,
7387
+ oneOf: [
7388
+ {
7389
+ required: [
7390
+ "to"
7391
+ ]
7392
+ },
7393
+ {
7394
+ required: [
7395
+ "to_placeholder"
7396
+ ]
7397
+ }
7398
+ ],
6415
7399
  properties: {
6416
7400
  encoding: {
6417
7401
  enum: [
@@ -6431,11 +7415,14 @@ var workflow_v0_schema_default = {
6431
7415
  to: {
6432
7416
  pattern: "^/.*$",
6433
7417
  type: "string"
7418
+ },
7419
+ to_placeholder: {
7420
+ minLength: 1,
7421
+ type: "string"
6434
7422
  }
6435
7423
  },
6436
7424
  required: [
6437
- "from",
6438
- "to"
7425
+ "from"
6439
7426
  ],
6440
7427
  type: "object"
6441
7428
  },
@@ -6702,41 +7689,808 @@ var workflow_v0_schema_default = {
6702
7689
  type: "object"
6703
7690
  };
6704
7691
 
6705
- // src/workflow_patterns.ts
6706
- var LLM_TEXT_OUTPUT_INTERNAL = "/output/0/content/0/text";
6707
- var LLM_USER_MESSAGE_TEXT_INTERNAL = "/request/input/1/content/0/text";
6708
- function wireRequest2(req) {
6709
- const raw = req;
6710
- if (raw && typeof raw === "object") {
6711
- if ("input" in raw) {
6712
- return req;
6713
- }
6714
- if ("body" in raw) {
6715
- return raw.body ?? {};
6716
- }
6717
- }
6718
- return asInternal(req).body;
6719
- }
6720
- function sortEdges(edges) {
6721
- return edges.slice().sort((a, b) => {
6722
- const af = String(a.from);
6723
- const bf = String(b.from);
6724
- if (af < bf) return -1;
6725
- if (af > bf) return 1;
6726
- const at = String(a.to);
6727
- const bt = String(b.to);
6728
- if (at < bt) return -1;
6729
- if (at > bt) return 1;
6730
- return 0;
6731
- });
6732
- }
6733
- function sortOutputs(outputs) {
6734
- return outputs.slice().sort((a, b) => {
6735
- const an = String(a.name);
6736
- const bn = String(b.name);
6737
- if (an < bn) return -1;
6738
- if (an > bn) return 1;
6739
- const af = String(a.from);
7692
+ // src/workflow_v1.schema.json
7693
+ var workflow_v1_schema_default = {
7694
+ $id: "https://modelrelay.ai/schemas/workflow_v1.schema.json",
7695
+ $schema: "http://json-schema.org/draft-07/schema#",
7696
+ additionalProperties: false,
7697
+ definitions: {
7698
+ condition: {
7699
+ additionalProperties: false,
7700
+ properties: {
7701
+ op: {
7702
+ enum: [
7703
+ "equals",
7704
+ "matches",
7705
+ "exists"
7706
+ ],
7707
+ type: "string"
7708
+ },
7709
+ path: {
7710
+ pattern: "^\\$.*$",
7711
+ type: "string"
7712
+ },
7713
+ source: {
7714
+ enum: [
7715
+ "node_output",
7716
+ "node_status"
7717
+ ],
7718
+ type: "string"
7719
+ },
7720
+ value: {}
7721
+ },
7722
+ required: [
7723
+ "source",
7724
+ "op"
7725
+ ],
7726
+ type: "object"
7727
+ },
7728
+ edge: {
7729
+ additionalProperties: false,
7730
+ properties: {
7731
+ from: {
7732
+ minLength: 1,
7733
+ type: "string"
7734
+ },
7735
+ to: {
7736
+ minLength: 1,
7737
+ type: "string"
7738
+ },
7739
+ when: {
7740
+ $ref: "#/definitions/condition"
7741
+ }
7742
+ },
7743
+ required: [
7744
+ "from",
7745
+ "to"
7746
+ ],
7747
+ type: "object"
7748
+ },
7749
+ fragmentBindInput: {
7750
+ additionalProperties: false,
7751
+ oneOf: [
7752
+ {
7753
+ required: [
7754
+ "from_node"
7755
+ ]
7756
+ },
7757
+ {
7758
+ required: [
7759
+ "from_input"
7760
+ ]
7761
+ }
7762
+ ],
7763
+ properties: {
7764
+ from_input: {
7765
+ minLength: 1,
7766
+ type: "string"
7767
+ },
7768
+ from_node: {
7769
+ minLength: 1,
7770
+ type: "string"
7771
+ },
7772
+ pointer: {
7773
+ pattern: "^(/.*)?$",
7774
+ type: "string"
7775
+ }
7776
+ },
7777
+ type: "object"
7778
+ },
7779
+ fragmentDef: {
7780
+ additionalProperties: false,
7781
+ properties: {
7782
+ edges: {
7783
+ items: {
7784
+ $ref: "#/definitions/edge"
7785
+ },
7786
+ type: "array"
7787
+ },
7788
+ inputs: {
7789
+ items: {
7790
+ $ref: "#/definitions/fragmentInput"
7791
+ },
7792
+ type: "array"
7793
+ },
7794
+ nodes: {
7795
+ items: {
7796
+ $ref: "#/definitions/node"
7797
+ },
7798
+ minItems: 1,
7799
+ type: "array"
7800
+ },
7801
+ outputs: {
7802
+ items: {
7803
+ $ref: "#/definitions/fragmentOutput"
7804
+ },
7805
+ minItems: 1,
7806
+ type: "array"
7807
+ }
7808
+ },
7809
+ required: [
7810
+ "outputs",
7811
+ "nodes"
7812
+ ],
7813
+ type: "object"
7814
+ },
7815
+ fragmentInput: {
7816
+ additionalProperties: false,
7817
+ properties: {
7818
+ name: {
7819
+ minLength: 1,
7820
+ type: "string"
7821
+ },
7822
+ type: {
7823
+ type: "string"
7824
+ }
7825
+ },
7826
+ required: [
7827
+ "name"
7828
+ ],
7829
+ type: "object"
7830
+ },
7831
+ fragmentOutput: {
7832
+ additionalProperties: false,
7833
+ properties: {
7834
+ from_node: {
7835
+ minLength: 1,
7836
+ type: "string"
7837
+ },
7838
+ name: {
7839
+ minLength: 1,
7840
+ type: "string"
7841
+ },
7842
+ pointer: {
7843
+ pattern: "^(/.*)?$",
7844
+ type: "string"
7845
+ }
7846
+ },
7847
+ required: [
7848
+ "name",
7849
+ "from_node"
7850
+ ],
7851
+ type: "object"
7852
+ },
7853
+ llmResponsesBinding: {
7854
+ additionalProperties: false,
7855
+ oneOf: [
7856
+ {
7857
+ required: [
7858
+ "to"
7859
+ ]
7860
+ },
7861
+ {
7862
+ required: [
7863
+ "to_placeholder"
7864
+ ]
7865
+ }
7866
+ ],
7867
+ properties: {
7868
+ encoding: {
7869
+ enum: [
7870
+ "json",
7871
+ "json_string"
7872
+ ],
7873
+ type: "string"
7874
+ },
7875
+ from: {
7876
+ minLength: 1,
7877
+ type: "string"
7878
+ },
7879
+ pointer: {
7880
+ pattern: "^(/.*)?$",
7881
+ type: "string"
7882
+ },
7883
+ to: {
7884
+ pattern: "^/.*$",
7885
+ type: "string"
7886
+ },
7887
+ to_placeholder: {
7888
+ minLength: 1,
7889
+ type: "string"
7890
+ }
7891
+ },
7892
+ required: [
7893
+ "from"
7894
+ ],
7895
+ type: "object"
7896
+ },
7897
+ mapFanoutItemBinding: {
7898
+ additionalProperties: false,
7899
+ oneOf: [
7900
+ {
7901
+ required: [
7902
+ "to"
7903
+ ]
7904
+ },
7905
+ {
7906
+ required: [
7907
+ "to_placeholder"
7908
+ ]
7909
+ }
7910
+ ],
7911
+ properties: {
7912
+ encoding: {
7913
+ enum: [
7914
+ "json",
7915
+ "json_string"
7916
+ ],
7917
+ type: "string"
7918
+ },
7919
+ path: {
7920
+ pattern: "^(/.*)?$",
7921
+ type: "string"
7922
+ },
7923
+ to: {
7924
+ pattern: "^/.*$",
7925
+ type: "string"
7926
+ },
7927
+ to_placeholder: {
7928
+ minLength: 1,
7929
+ type: "string"
7930
+ }
7931
+ },
7932
+ type: "object"
7933
+ },
7934
+ node: {
7935
+ additionalProperties: false,
7936
+ oneOf: [
7937
+ {
7938
+ allOf: [
7939
+ {
7940
+ properties: {
7941
+ input: {
7942
+ properties: {
7943
+ bindings: {
7944
+ items: {
7945
+ $ref: "#/definitions/llmResponsesBinding"
7946
+ },
7947
+ type: "array"
7948
+ },
7949
+ request: {
7950
+ type: "object"
7951
+ },
7952
+ stream: {
7953
+ type: "boolean"
7954
+ },
7955
+ tool_execution: {
7956
+ additionalProperties: false,
7957
+ properties: {
7958
+ mode: {
7959
+ default: "server",
7960
+ enum: [
7961
+ "server",
7962
+ "client"
7963
+ ],
7964
+ type: "string"
7965
+ }
7966
+ },
7967
+ required: [
7968
+ "mode"
7969
+ ],
7970
+ type: "object"
7971
+ },
7972
+ tool_limits: {
7973
+ additionalProperties: false,
7974
+ properties: {
7975
+ max_llm_calls: {
7976
+ default: 8,
7977
+ maximum: 64,
7978
+ minimum: 1,
7979
+ type: "integer"
7980
+ },
7981
+ max_tool_calls_per_step: {
7982
+ default: 16,
7983
+ maximum: 64,
7984
+ minimum: 1,
7985
+ type: "integer"
7986
+ },
7987
+ wait_ttl_ms: {
7988
+ default: 9e5,
7989
+ maximum: 864e5,
7990
+ minimum: 1,
7991
+ type: "integer"
7992
+ }
7993
+ },
7994
+ type: "object"
7995
+ }
7996
+ },
7997
+ required: [
7998
+ "request"
7999
+ ],
8000
+ type: "object"
8001
+ }
8002
+ }
8003
+ }
8004
+ ],
8005
+ properties: {
8006
+ type: {
8007
+ const: "llm.responses"
8008
+ }
8009
+ },
8010
+ required: [
8011
+ "input"
8012
+ ]
8013
+ },
8014
+ {
8015
+ allOf: [
8016
+ {
8017
+ properties: {
8018
+ input: {
8019
+ properties: {
8020
+ bindings: {
8021
+ items: {
8022
+ $ref: "#/definitions/llmResponsesBinding"
8023
+ },
8024
+ type: "array"
8025
+ },
8026
+ request: {
8027
+ type: "object"
8028
+ },
8029
+ stream: {
8030
+ type: "boolean"
8031
+ },
8032
+ tool_execution: {
8033
+ additionalProperties: false,
8034
+ properties: {
8035
+ mode: {
8036
+ default: "server",
8037
+ enum: [
8038
+ "server",
8039
+ "client"
8040
+ ],
8041
+ type: "string"
8042
+ }
8043
+ },
8044
+ required: [
8045
+ "mode"
8046
+ ],
8047
+ type: "object"
8048
+ },
8049
+ tool_limits: {
8050
+ additionalProperties: false,
8051
+ properties: {
8052
+ max_llm_calls: {
8053
+ default: 8,
8054
+ maximum: 64,
8055
+ minimum: 1,
8056
+ type: "integer"
8057
+ },
8058
+ max_tool_calls_per_step: {
8059
+ default: 16,
8060
+ maximum: 64,
8061
+ minimum: 1,
8062
+ type: "integer"
8063
+ },
8064
+ wait_ttl_ms: {
8065
+ default: 9e5,
8066
+ maximum: 864e5,
8067
+ minimum: 1,
8068
+ type: "integer"
8069
+ }
8070
+ },
8071
+ type: "object"
8072
+ }
8073
+ },
8074
+ required: [
8075
+ "request"
8076
+ ],
8077
+ type: "object"
8078
+ }
8079
+ }
8080
+ }
8081
+ ],
8082
+ properties: {
8083
+ type: {
8084
+ const: "route.switch"
8085
+ }
8086
+ },
8087
+ required: [
8088
+ "input"
8089
+ ]
8090
+ },
8091
+ {
8092
+ properties: {
8093
+ type: {
8094
+ const: "join.all"
8095
+ }
8096
+ }
8097
+ },
8098
+ {
8099
+ allOf: [
8100
+ {
8101
+ properties: {
8102
+ input: {
8103
+ additionalProperties: false,
8104
+ properties: {
8105
+ predicate: {
8106
+ $ref: "#/definitions/condition"
8107
+ }
8108
+ },
8109
+ type: "object"
8110
+ }
8111
+ }
8112
+ }
8113
+ ],
8114
+ properties: {
8115
+ type: {
8116
+ const: "join.any"
8117
+ }
8118
+ }
8119
+ },
8120
+ {
8121
+ allOf: [
8122
+ {
8123
+ properties: {
8124
+ input: {
8125
+ additionalProperties: false,
8126
+ properties: {
8127
+ limit: {
8128
+ minimum: 1,
8129
+ type: "integer"
8130
+ },
8131
+ predicate: {
8132
+ $ref: "#/definitions/condition"
8133
+ },
8134
+ timeout_ms: {
8135
+ minimum: 1,
8136
+ type: "integer"
8137
+ }
8138
+ },
8139
+ type: "object"
8140
+ }
8141
+ }
8142
+ }
8143
+ ],
8144
+ properties: {
8145
+ type: {
8146
+ const: "join.collect"
8147
+ }
8148
+ },
8149
+ required: [
8150
+ "input"
8151
+ ]
8152
+ },
8153
+ {
8154
+ allOf: [
8155
+ {
8156
+ properties: {
8157
+ input: {
8158
+ additionalProperties: false,
8159
+ oneOf: [
8160
+ {
8161
+ not: {
8162
+ required: [
8163
+ "merge"
8164
+ ]
8165
+ },
8166
+ required: [
8167
+ "object"
8168
+ ]
8169
+ },
8170
+ {
8171
+ not: {
8172
+ required: [
8173
+ "object"
8174
+ ]
8175
+ },
8176
+ required: [
8177
+ "merge"
8178
+ ]
8179
+ }
8180
+ ],
8181
+ properties: {
8182
+ merge: {
8183
+ items: {
8184
+ $ref: "#/definitions/transformValue"
8185
+ },
8186
+ minItems: 1,
8187
+ type: "array"
8188
+ },
8189
+ object: {
8190
+ additionalProperties: {
8191
+ $ref: "#/definitions/transformValue"
8192
+ },
8193
+ minProperties: 1,
8194
+ type: "object"
8195
+ }
8196
+ },
8197
+ type: "object"
8198
+ }
8199
+ }
8200
+ }
8201
+ ],
8202
+ properties: {
8203
+ type: {
8204
+ const: "transform.json"
8205
+ }
8206
+ },
8207
+ required: [
8208
+ "input"
8209
+ ]
8210
+ },
8211
+ {
8212
+ allOf: [
8213
+ {
8214
+ properties: {
8215
+ input: {
8216
+ additionalProperties: false,
8217
+ properties: {
8218
+ item_bindings: {
8219
+ items: {
8220
+ $ref: "#/definitions/mapFanoutItemBinding"
8221
+ },
8222
+ type: "array"
8223
+ },
8224
+ items: {
8225
+ additionalProperties: false,
8226
+ properties: {
8227
+ from: {
8228
+ minLength: 1,
8229
+ type: "string"
8230
+ },
8231
+ path: {
8232
+ pattern: "^(/.*)?$",
8233
+ type: "string"
8234
+ },
8235
+ pointer: {
8236
+ pattern: "^(/.*)?$",
8237
+ type: "string"
8238
+ }
8239
+ },
8240
+ required: [
8241
+ "from"
8242
+ ],
8243
+ type: "object"
8244
+ },
8245
+ max_parallelism: {
8246
+ minimum: 1,
8247
+ type: "integer"
8248
+ },
8249
+ subnode: {
8250
+ additionalProperties: false,
8251
+ properties: {
8252
+ id: {
8253
+ minLength: 1,
8254
+ type: "string"
8255
+ },
8256
+ input: {},
8257
+ type: {
8258
+ enum: [
8259
+ "llm.responses",
8260
+ "route.switch",
8261
+ "transform.json"
8262
+ ],
8263
+ type: "string"
8264
+ }
8265
+ },
8266
+ required: [
8267
+ "id",
8268
+ "type"
8269
+ ],
8270
+ type: "object"
8271
+ }
8272
+ },
8273
+ required: [
8274
+ "items",
8275
+ "subnode"
8276
+ ],
8277
+ type: "object"
8278
+ }
8279
+ }
8280
+ }
8281
+ ],
8282
+ properties: {
8283
+ type: {
8284
+ const: "map.fanout"
8285
+ }
8286
+ },
8287
+ required: [
8288
+ "input"
8289
+ ]
8290
+ },
8291
+ {
8292
+ allOf: [
8293
+ {
8294
+ properties: {
8295
+ input: {
8296
+ additionalProperties: false,
8297
+ properties: {
8298
+ bind_inputs: {
8299
+ additionalProperties: {
8300
+ $ref: "#/definitions/fragmentBindInput"
8301
+ },
8302
+ type: "object"
8303
+ },
8304
+ ref: {
8305
+ minLength: 1,
8306
+ type: "string"
8307
+ }
8308
+ },
8309
+ required: [
8310
+ "ref"
8311
+ ],
8312
+ type: "object"
8313
+ }
8314
+ }
8315
+ }
8316
+ ],
8317
+ properties: {
8318
+ type: {
8319
+ const: "fragment"
8320
+ }
8321
+ },
8322
+ required: [
8323
+ "input"
8324
+ ]
8325
+ }
8326
+ ],
8327
+ properties: {
8328
+ id: {
8329
+ minLength: 1,
8330
+ type: "string"
8331
+ },
8332
+ input: {},
8333
+ type: {
8334
+ enum: [
8335
+ "llm.responses",
8336
+ "route.switch",
8337
+ "join.all",
8338
+ "join.any",
8339
+ "join.collect",
8340
+ "transform.json",
8341
+ "map.fanout",
8342
+ "fragment"
8343
+ ],
8344
+ type: "string"
8345
+ }
8346
+ },
8347
+ required: [
8348
+ "id",
8349
+ "type"
8350
+ ],
8351
+ type: "object"
8352
+ },
8353
+ output: {
8354
+ additionalProperties: false,
8355
+ properties: {
8356
+ from: {
8357
+ minLength: 1,
8358
+ type: "string"
8359
+ },
8360
+ name: {
8361
+ minLength: 1,
8362
+ type: "string"
8363
+ },
8364
+ output: {
8365
+ minLength: 1,
8366
+ type: "string"
8367
+ },
8368
+ pointer: {
8369
+ pattern: "^(/.*)?$",
8370
+ type: "string"
8371
+ }
8372
+ },
8373
+ required: [
8374
+ "name",
8375
+ "from"
8376
+ ],
8377
+ type: "object"
8378
+ },
8379
+ transformValue: {
8380
+ additionalProperties: false,
8381
+ properties: {
8382
+ from: {
8383
+ minLength: 1,
8384
+ type: "string"
8385
+ },
8386
+ pointer: {
8387
+ pattern: "^(/.*)?$",
8388
+ type: "string"
8389
+ }
8390
+ },
8391
+ required: [
8392
+ "from"
8393
+ ],
8394
+ type: "object"
8395
+ }
8396
+ },
8397
+ properties: {
8398
+ edges: {
8399
+ items: {
8400
+ $ref: "#/definitions/edge"
8401
+ },
8402
+ type: "array"
8403
+ },
8404
+ execution: {
8405
+ additionalProperties: false,
8406
+ properties: {
8407
+ max_parallelism: {
8408
+ minimum: 1,
8409
+ type: "integer"
8410
+ },
8411
+ node_timeout_ms: {
8412
+ minimum: 1,
8413
+ type: "integer"
8414
+ },
8415
+ run_timeout_ms: {
8416
+ minimum: 1,
8417
+ type: "integer"
8418
+ }
8419
+ },
8420
+ type: "object"
8421
+ },
8422
+ fragments: {
8423
+ additionalProperties: {
8424
+ $ref: "#/definitions/fragmentDef"
8425
+ },
8426
+ type: "object"
8427
+ },
8428
+ kind: {
8429
+ const: "workflow.v1",
8430
+ type: "string"
8431
+ },
8432
+ name: {
8433
+ type: "string"
8434
+ },
8435
+ nodes: {
8436
+ items: {
8437
+ $ref: "#/definitions/node"
8438
+ },
8439
+ minItems: 1,
8440
+ type: "array"
8441
+ },
8442
+ outputs: {
8443
+ items: {
8444
+ $ref: "#/definitions/output"
8445
+ },
8446
+ minItems: 1,
8447
+ type: "array"
8448
+ }
8449
+ },
8450
+ required: [
8451
+ "kind",
8452
+ "nodes",
8453
+ "outputs"
8454
+ ],
8455
+ title: "ModelRelay workflow.v1",
8456
+ type: "object"
8457
+ };
8458
+
8459
+ // src/workflow_patterns.ts
8460
+ var LLM_TEXT_OUTPUT_INTERNAL = "/output/0/content/0/text";
8461
+ var LLM_USER_MESSAGE_TEXT_INTERNAL = "/input/1/content/0/text";
8462
+ function wireRequest2(req) {
8463
+ const raw = req;
8464
+ if (raw && typeof raw === "object") {
8465
+ if ("input" in raw) {
8466
+ return req;
8467
+ }
8468
+ if ("body" in raw) {
8469
+ return raw.body ?? {};
8470
+ }
8471
+ }
8472
+ return asInternal(req).body;
8473
+ }
8474
+ function sortEdges(edges) {
8475
+ return edges.slice().sort((a, b) => {
8476
+ const af = String(a.from);
8477
+ const bf = String(b.from);
8478
+ if (af < bf) return -1;
8479
+ if (af > bf) return 1;
8480
+ const at = String(a.to);
8481
+ const bt = String(b.to);
8482
+ if (at < bt) return -1;
8483
+ if (at > bt) return 1;
8484
+ return 0;
8485
+ });
8486
+ }
8487
+ function sortOutputs(outputs) {
8488
+ return outputs.slice().sort((a, b) => {
8489
+ const an = String(a.name);
8490
+ const bn = String(b.name);
8491
+ if (an < bn) return -1;
8492
+ if (an > bn) return 1;
8493
+ const af = String(a.from);
6740
8494
  const bf = String(b.from);
6741
8495
  if (af < bf) return -1;
6742
8496
  if (af > bf) return 1;
@@ -7042,681 +8796,149 @@ var MapReduceBuilder = class _MapReduceBuilder {
7042
8796
  output(name, from) {
7043
8797
  return this.with({
7044
8798
  outputs: [
7045
- ...this.state.outputs,
7046
- {
7047
- name,
7048
- from,
7049
- pointer: LLM_TEXT_OUTPUT_INTERNAL
7050
- }
7051
- ]
7052
- });
7053
- }
7054
- /**
7055
- * Builds and returns the compiled workflow spec.
7056
- * @throws Error if no items are provided or no reducer is configured
7057
- */
7058
- build() {
7059
- if (this.state.items.length === 0) {
7060
- throw new Error("map-reduce requires at least one item");
7061
- }
7062
- if (!this.state.reducer) {
7063
- throw new Error("map-reduce requires a reducer (call reduce)");
7064
- }
7065
- const seenIds = /* @__PURE__ */ new Set();
7066
- for (const item of this.state.items) {
7067
- if (!item.id) {
7068
- throw new Error("item ID cannot be empty");
7069
- }
7070
- if (seenIds.has(item.id)) {
7071
- throw new Error(`duplicate item ID: "${item.id}"`);
7072
- }
7073
- seenIds.add(item.id);
7074
- }
7075
- const nodes = [];
7076
- const edges = [];
7077
- const joinId = `${this.state.reducer.id}_join`;
7078
- for (const item of this.state.items) {
7079
- const mapperId = `map_${item.id}`;
7080
- const input = {
7081
- id: mapperId,
7082
- type: WorkflowNodeTypes.LLMResponses,
7083
- input: {
7084
- request: item.request,
7085
- ...item.stream ? { stream: true } : {}
7086
- }
7087
- };
7088
- nodes.push(input);
7089
- edges.push({ from: mapperId, to: joinId });
7090
- }
7091
- nodes.push({ id: joinId, type: WorkflowNodeTypes.JoinAll });
7092
- const reducerInput = {
7093
- id: this.state.reducer.id,
7094
- type: WorkflowNodeTypes.LLMResponses,
7095
- input: {
7096
- request: this.state.reducer.request,
7097
- ...this.state.reducer.stream ? { stream: true } : {},
7098
- bindings: [
7099
- {
7100
- from: joinId,
7101
- // Empty pointer = full join output
7102
- to: LLM_USER_MESSAGE_TEXT_INTERNAL,
7103
- encoding: "json_string"
7104
- }
7105
- ]
7106
- }
7107
- };
7108
- nodes.push(reducerInput);
7109
- edges.push({ from: joinId, to: this.state.reducer.id });
7110
- return {
7111
- kind: WorkflowKinds.WorkflowV0,
7112
- name: this.state.name,
7113
- ...this.state.execution ? { execution: this.state.execution } : {},
7114
- nodes,
7115
- ...edges.length > 0 ? { edges: sortEdges(edges) } : {},
7116
- outputs: sortOutputs(this.state.outputs)
7117
- };
7118
- }
7119
- };
7120
- function MapReduce(name, items = []) {
7121
- return MapReduceBuilder.create(name, items);
7122
- }
7123
-
7124
- // src/tools_local_fs.ts
7125
- var import_fs = require("fs");
7126
- var path = __toESM(require("path"), 1);
7127
- var import_child_process = require("child_process");
7128
- var ToolNames = {
7129
- FS_READ_FILE: "fs.read_file",
7130
- FS_LIST_FILES: "fs.list_files",
7131
- FS_SEARCH: "fs.search"
7132
- };
7133
- var FSDefaults = {
7134
- MAX_READ_BYTES: 64e3,
7135
- HARD_MAX_READ_BYTES: 1e6,
7136
- MAX_LIST_ENTRIES: 2e3,
7137
- HARD_MAX_LIST_ENTRIES: 2e4,
7138
- MAX_SEARCH_MATCHES: 100,
7139
- HARD_MAX_SEARCH_MATCHES: 2e3,
7140
- SEARCH_TIMEOUT_MS: 5e3,
7141
- MAX_SEARCH_BYTES_PER_FILE: 1e6
7142
- };
7143
- var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
7144
- ".git",
7145
- "node_modules",
7146
- "vendor",
7147
- "dist",
7148
- "build",
7149
- ".next",
7150
- "target",
7151
- ".idea",
7152
- ".vscode",
7153
- "__pycache__",
7154
- ".pytest_cache",
7155
- "coverage"
7156
- ]);
7157
- var LocalFSToolPack = class {
7158
- constructor(options) {
7159
- this.rgPath = null;
7160
- this.rgChecked = false;
7161
- const root = options.root?.trim();
7162
- if (!root) {
7163
- throw new Error("LocalFSToolPack: root directory required");
7164
- }
7165
- this.rootAbs = path.resolve(root);
7166
- this.cfg = {
7167
- ignoreDirs: options.ignoreDirs ?? new Set(DEFAULT_IGNORE_DIRS),
7168
- maxReadBytes: options.maxReadBytes ?? FSDefaults.MAX_READ_BYTES,
7169
- hardMaxReadBytes: options.hardMaxReadBytes ?? FSDefaults.HARD_MAX_READ_BYTES,
7170
- maxListEntries: options.maxListEntries ?? FSDefaults.MAX_LIST_ENTRIES,
7171
- hardMaxListEntries: options.hardMaxListEntries ?? FSDefaults.HARD_MAX_LIST_ENTRIES,
7172
- maxSearchMatches: options.maxSearchMatches ?? FSDefaults.MAX_SEARCH_MATCHES,
7173
- hardMaxSearchMatches: options.hardMaxSearchMatches ?? FSDefaults.HARD_MAX_SEARCH_MATCHES,
7174
- searchTimeoutMs: options.searchTimeoutMs ?? FSDefaults.SEARCH_TIMEOUT_MS,
7175
- maxSearchBytesPerFile: options.maxSearchBytesPerFile ?? FSDefaults.MAX_SEARCH_BYTES_PER_FILE
7176
- };
7177
- }
7178
- /**
7179
- * Returns the tool definitions for LLM requests.
7180
- * Use these when constructing the tools array for /responses requests.
7181
- */
7182
- getToolDefinitions() {
7183
- return [
7184
- {
7185
- type: "function",
7186
- function: {
7187
- name: ToolNames.FS_READ_FILE,
7188
- description: "Read the contents of a file. Returns the file contents as UTF-8 text.",
7189
- parameters: {
7190
- type: "object",
7191
- properties: {
7192
- path: {
7193
- type: "string",
7194
- description: "Workspace-relative path to the file (e.g., 'src/index.ts')"
7195
- },
7196
- max_bytes: {
7197
- type: "integer",
7198
- description: `Maximum bytes to read. Default: ${this.cfg.maxReadBytes}, max: ${this.cfg.hardMaxReadBytes}`
7199
- }
7200
- },
7201
- required: ["path"]
7202
- }
7203
- }
7204
- },
7205
- {
7206
- type: "function",
7207
- function: {
7208
- name: ToolNames.FS_LIST_FILES,
7209
- description: "List files recursively in a directory. Returns newline-separated workspace-relative paths.",
7210
- parameters: {
7211
- type: "object",
7212
- properties: {
7213
- path: {
7214
- type: "string",
7215
- description: "Workspace-relative directory path. Default: '.' (workspace root)"
7216
- },
7217
- max_entries: {
7218
- type: "integer",
7219
- description: `Maximum files to list. Default: ${this.cfg.maxListEntries}, max: ${this.cfg.hardMaxListEntries}`
7220
- }
7221
- }
7222
- }
7223
- }
7224
- },
7225
- {
7226
- type: "function",
7227
- function: {
7228
- name: ToolNames.FS_SEARCH,
7229
- description: "Search for text matching a regex pattern. Returns matches as 'path:line:content' format.",
7230
- parameters: {
7231
- type: "object",
7232
- properties: {
7233
- query: {
7234
- type: "string",
7235
- description: "Regex pattern to search for"
7236
- },
7237
- path: {
7238
- type: "string",
7239
- description: "Workspace-relative directory to search. Default: '.' (workspace root)"
7240
- },
7241
- max_matches: {
7242
- type: "integer",
7243
- description: `Maximum matches to return. Default: ${this.cfg.maxSearchMatches}, max: ${this.cfg.hardMaxSearchMatches}`
7244
- }
7245
- },
7246
- required: ["query"]
7247
- }
7248
- }
7249
- }
7250
- ];
7251
- }
7252
- /**
7253
- * Registers handlers into an existing ToolRegistry.
7254
- * @param registry - The registry to register into
7255
- * @returns The registry for chaining
7256
- */
7257
- registerInto(registry) {
7258
- registry.register(ToolNames.FS_READ_FILE, this.readFile.bind(this));
7259
- registry.register(ToolNames.FS_LIST_FILES, this.listFiles.bind(this));
7260
- registry.register(ToolNames.FS_SEARCH, this.search.bind(this));
7261
- return registry;
7262
- }
7263
- /**
7264
- * Creates a new ToolRegistry with fs.* tools pre-registered.
7265
- */
7266
- toRegistry() {
7267
- return this.registerInto(new ToolRegistry());
7268
- }
7269
- // ========================================================================
7270
- // Tool Handlers
7271
- // ========================================================================
7272
- async readFile(_args, call) {
7273
- const args = this.parseArgs(call, ["path"]);
7274
- const func = call.function;
7275
- const relPath = this.requireString(args, "path", call);
7276
- const requestedMax = this.optionalPositiveInt(args, "max_bytes", call);
7277
- let maxBytes = this.cfg.maxReadBytes;
7278
- if (requestedMax !== void 0) {
7279
- if (requestedMax > this.cfg.hardMaxReadBytes) {
7280
- throw new ToolArgumentError({
7281
- message: `max_bytes exceeds hard cap (${this.cfg.hardMaxReadBytes})`,
7282
- toolCallId: call.id,
7283
- toolName: func.name,
7284
- rawArguments: func.arguments
7285
- });
7286
- }
7287
- maxBytes = requestedMax;
7288
- }
7289
- const absPath = await this.resolveAndValidatePath(relPath, call);
7290
- const stat = await import_fs.promises.stat(absPath);
7291
- if (stat.isDirectory()) {
7292
- throw new Error(`fs.read_file: path is a directory: ${relPath}`);
7293
- }
7294
- if (stat.size > maxBytes) {
7295
- throw new Error(`fs.read_file: file exceeds max_bytes (${maxBytes})`);
7296
- }
7297
- const data = await import_fs.promises.readFile(absPath);
7298
- if (!this.isValidUtf8(data)) {
7299
- throw new Error(`fs.read_file: file is not valid UTF-8: ${relPath}`);
7300
- }
7301
- return data.toString("utf-8");
7302
- }
7303
- async listFiles(_args, call) {
7304
- const args = this.parseArgs(call, []);
7305
- const func = call.function;
7306
- const startPath = this.optionalString(args, "path", call)?.trim() || ".";
7307
- let maxEntries = this.cfg.maxListEntries;
7308
- const requestedMax = this.optionalPositiveInt(args, "max_entries", call);
7309
- if (requestedMax !== void 0) {
7310
- if (requestedMax > this.cfg.hardMaxListEntries) {
7311
- throw new ToolArgumentError({
7312
- message: `max_entries exceeds hard cap (${this.cfg.hardMaxListEntries})`,
7313
- toolCallId: call.id,
7314
- toolName: func.name,
7315
- rawArguments: func.arguments
7316
- });
7317
- }
7318
- maxEntries = requestedMax;
7319
- }
7320
- const absPath = await this.resolveAndValidatePath(startPath, call);
7321
- let rootReal;
7322
- try {
7323
- rootReal = await import_fs.promises.realpath(this.rootAbs);
7324
- } catch {
7325
- rootReal = this.rootAbs;
7326
- }
7327
- const stat = await import_fs.promises.stat(absPath);
7328
- if (!stat.isDirectory()) {
7329
- throw new Error(`fs.list_files: path is not a directory: ${startPath}`);
7330
- }
7331
- const files = [];
7332
- await this.walkDir(absPath, async (filePath, dirent) => {
7333
- if (files.length >= maxEntries) {
7334
- return false;
7335
- }
7336
- if (dirent.isDirectory()) {
7337
- if (this.cfg.ignoreDirs.has(dirent.name)) {
7338
- return false;
7339
- }
7340
- return true;
7341
- }
7342
- if (dirent.isFile()) {
7343
- const relPath = path.relative(rootReal, filePath);
7344
- files.push(relPath.split(path.sep).join("/"));
7345
- }
7346
- return true;
7347
- });
7348
- return files.join("\n");
7349
- }
7350
- async search(_args, call) {
7351
- const args = this.parseArgs(call, ["query"]);
7352
- const func = call.function;
7353
- const query = this.requireString(args, "query", call);
7354
- const startPath = this.optionalString(args, "path", call)?.trim() || ".";
7355
- let maxMatches = this.cfg.maxSearchMatches;
7356
- const requestedMax = this.optionalPositiveInt(args, "max_matches", call);
7357
- if (requestedMax !== void 0) {
7358
- if (requestedMax > this.cfg.hardMaxSearchMatches) {
7359
- throw new ToolArgumentError({
7360
- message: `max_matches exceeds hard cap (${this.cfg.hardMaxSearchMatches})`,
7361
- toolCallId: call.id,
7362
- toolName: func.name,
7363
- rawArguments: func.arguments
7364
- });
7365
- }
7366
- maxMatches = requestedMax;
7367
- }
7368
- const absPath = await this.resolveAndValidatePath(startPath, call);
7369
- const rgPath = await this.detectRipgrep();
7370
- if (rgPath) {
7371
- return this.searchWithRipgrep(
7372
- rgPath,
7373
- query,
7374
- absPath,
7375
- maxMatches
7376
- );
7377
- }
7378
- return this.searchWithJS(query, absPath, maxMatches, call);
7379
- }
7380
- // ========================================================================
7381
- // Path Safety
7382
- // ========================================================================
7383
- /**
7384
- * Resolves a workspace-relative path and validates it stays within the sandbox.
7385
- * @throws {ToolArgumentError} if path is invalid
7386
- * @throws {PathEscapeError} if resolved path escapes root
7387
- */
7388
- async resolveAndValidatePath(relPath, call) {
7389
- const func = call.function;
7390
- const cleanRel = relPath.trim();
7391
- if (!cleanRel) {
7392
- throw new ToolArgumentError({
7393
- message: "path cannot be empty",
7394
- toolCallId: call.id,
7395
- toolName: func.name,
7396
- rawArguments: func.arguments
7397
- });
7398
- }
7399
- if (path.isAbsolute(cleanRel)) {
7400
- throw new ToolArgumentError({
7401
- message: "path must be workspace-relative (not absolute)",
7402
- toolCallId: call.id,
7403
- toolName: func.name,
7404
- rawArguments: func.arguments
7405
- });
7406
- }
7407
- const normalized = path.normalize(cleanRel);
7408
- if (normalized.startsWith("..") || normalized.startsWith(`.${path.sep}..`)) {
7409
- throw new ToolArgumentError({
7410
- message: "path must not escape the workspace root",
7411
- toolCallId: call.id,
7412
- toolName: func.name,
7413
- rawArguments: func.arguments
7414
- });
7415
- }
7416
- const target = path.join(this.rootAbs, normalized);
7417
- let rootReal;
7418
- try {
7419
- rootReal = await import_fs.promises.realpath(this.rootAbs);
7420
- } catch {
7421
- rootReal = this.rootAbs;
7422
- }
7423
- let resolved;
7424
- try {
7425
- resolved = await import_fs.promises.realpath(target);
7426
- } catch (err) {
7427
- resolved = path.join(rootReal, normalized);
7428
- }
7429
- const relFromRoot = path.relative(rootReal, resolved);
7430
- if (relFromRoot.startsWith("..") || relFromRoot.startsWith(`.${path.sep}..`) || path.isAbsolute(relFromRoot)) {
7431
- throw new PathEscapeError({
7432
- requestedPath: relPath,
7433
- resolvedPath: resolved
7434
- });
7435
- }
7436
- return resolved;
7437
- }
7438
- // ========================================================================
7439
- // Ripgrep Search
7440
- // ========================================================================
7441
- async detectRipgrep() {
7442
- if (this.rgChecked) {
7443
- return this.rgPath;
7444
- }
7445
- this.rgChecked = true;
7446
- return new Promise((resolve2) => {
7447
- const proc = (0, import_child_process.spawn)("rg", ["--version"], { stdio: "ignore" });
7448
- proc.on("error", () => {
7449
- this.rgPath = null;
7450
- resolve2(null);
7451
- });
7452
- proc.on("close", (code) => {
7453
- if (code === 0) {
7454
- this.rgPath = "rg";
7455
- resolve2("rg");
7456
- } else {
7457
- this.rgPath = null;
7458
- resolve2(null);
7459
- }
7460
- });
7461
- });
7462
- }
7463
- async searchWithRipgrep(rgPath, query, dirAbs, maxMatches) {
7464
- return new Promise((resolve2, reject) => {
7465
- const args = ["--line-number", "--no-heading", "--color=never"];
7466
- for (const name of this.cfg.ignoreDirs) {
7467
- args.push("--glob", `!**/${name}/**`);
7468
- }
7469
- args.push(query, dirAbs);
7470
- const proc = (0, import_child_process.spawn)(rgPath, args, {
7471
- timeout: this.cfg.searchTimeoutMs
7472
- });
7473
- const lines = [];
7474
- let stderr = "";
7475
- let killed = false;
7476
- proc.stdout.on("data", (chunk) => {
7477
- if (killed) return;
7478
- const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
7479
- const newLines = text.split("\n").filter((l) => l.trim());
7480
- for (const line of newLines) {
7481
- lines.push(this.normalizeRipgrepLine(line));
7482
- if (lines.length >= maxMatches) {
7483
- killed = true;
7484
- proc.kill();
7485
- break;
7486
- }
7487
- }
7488
- });
7489
- proc.stderr.on("data", (chunk) => {
7490
- stderr += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
7491
- });
7492
- proc.on("error", (err) => {
7493
- reject(new Error(`fs.search: ripgrep error: ${err.message}`));
7494
- });
7495
- proc.on("close", (code) => {
7496
- if (killed) {
7497
- resolve2(lines.join("\n"));
7498
- return;
7499
- }
7500
- if (code === 0 || code === 1) {
7501
- resolve2(lines.join("\n"));
7502
- } else if (code === 2 && stderr.toLowerCase().includes("regex")) {
7503
- reject(
7504
- new ToolArgumentError({
7505
- message: `invalid query regex: ${stderr.trim()}`,
7506
- toolCallId: "",
7507
- toolName: ToolNames.FS_SEARCH,
7508
- rawArguments: ""
7509
- })
7510
- );
7511
- } else if (stderr) {
7512
- reject(new Error(`fs.search: ripgrep failed: ${stderr.trim()}`));
7513
- } else {
7514
- resolve2(lines.join("\n"));
8799
+ ...this.state.outputs,
8800
+ {
8801
+ name,
8802
+ from,
8803
+ pointer: LLM_TEXT_OUTPUT_INTERNAL
7515
8804
  }
7516
- });
8805
+ ]
7517
8806
  });
7518
8807
  }
7519
- normalizeRipgrepLine(line) {
7520
- const trimmed = line.trim();
7521
- if (!trimmed || !trimmed.includes(":")) {
7522
- return trimmed;
7523
- }
7524
- const colonIdx = trimmed.indexOf(":");
7525
- const filePath = trimmed.slice(0, colonIdx);
7526
- const rest = trimmed.slice(colonIdx + 1);
7527
- if (path.isAbsolute(filePath)) {
7528
- const rel = path.relative(this.rootAbs, filePath);
7529
- if (!rel.startsWith("..")) {
7530
- return rel.split(path.sep).join("/") + ":" + rest;
7531
- }
8808
+ /**
8809
+ * Builds and returns the compiled workflow spec.
8810
+ * @throws Error if no items are provided or no reducer is configured
8811
+ */
8812
+ build() {
8813
+ if (this.state.items.length === 0) {
8814
+ throw new Error("map-reduce requires at least one item");
7532
8815
  }
7533
- return filePath.split(path.sep).join("/") + ":" + rest;
7534
- }
7535
- // ========================================================================
7536
- // JavaScript Fallback Search
7537
- // ========================================================================
7538
- async searchWithJS(query, dirAbs, maxMatches, call) {
7539
- const func = call.function;
7540
- let regex;
7541
- try {
7542
- regex = new RegExp(query);
7543
- } catch (err) {
7544
- throw new ToolArgumentError({
7545
- message: `invalid query regex: ${err.message}`,
7546
- toolCallId: call.id,
7547
- toolName: func.name,
7548
- rawArguments: func.arguments
7549
- });
8816
+ if (!this.state.reducer) {
8817
+ throw new Error("map-reduce requires a reducer (call reduce)");
7550
8818
  }
7551
- const matches = [];
7552
- const deadline = Date.now() + this.cfg.searchTimeoutMs;
7553
- await this.walkDir(dirAbs, async (filePath, dirent) => {
7554
- if (Date.now() > deadline) {
7555
- return false;
7556
- }
7557
- if (matches.length >= maxMatches) {
7558
- return false;
7559
- }
7560
- if (dirent.isDirectory()) {
7561
- if (this.cfg.ignoreDirs.has(dirent.name)) {
7562
- return false;
7563
- }
7564
- return true;
8819
+ const seenIds = /* @__PURE__ */ new Set();
8820
+ for (const item of this.state.items) {
8821
+ if (!item.id) {
8822
+ throw new Error("item ID cannot be empty");
7565
8823
  }
7566
- if (!dirent.isFile()) {
7567
- return true;
8824
+ if (seenIds.has(item.id)) {
8825
+ throw new Error(`duplicate item ID: "${item.id}"`);
7568
8826
  }
7569
- try {
7570
- const stat = await import_fs.promises.stat(filePath);
7571
- if (stat.size > this.cfg.maxSearchBytesPerFile) {
7572
- return true;
8827
+ seenIds.add(item.id);
8828
+ }
8829
+ const nodes = [];
8830
+ const edges = [];
8831
+ const joinId = `${this.state.reducer.id}_join`;
8832
+ for (const item of this.state.items) {
8833
+ const mapperId = `map_${item.id}`;
8834
+ const input = {
8835
+ id: mapperId,
8836
+ type: WorkflowNodeTypes.LLMResponses,
8837
+ input: {
8838
+ request: item.request,
8839
+ ...item.stream ? { stream: true } : {}
7573
8840
  }
7574
- } catch {
7575
- return true;
7576
- }
7577
- try {
7578
- const content = await import_fs.promises.readFile(filePath, "utf-8");
7579
- const lines = content.split("\n");
7580
- for (let i = 0; i < lines.length && matches.length < maxMatches; i++) {
7581
- if (regex.test(lines[i])) {
7582
- const relPath = path.relative(this.rootAbs, filePath);
7583
- const normalizedPath = relPath.split(path.sep).join("/");
7584
- matches.push(`${normalizedPath}:${i + 1}:${lines[i]}`);
8841
+ };
8842
+ nodes.push(input);
8843
+ edges.push({ from: mapperId, to: joinId });
8844
+ }
8845
+ nodes.push({ id: joinId, type: WorkflowNodeTypes.JoinAll });
8846
+ const reducerInput = {
8847
+ id: this.state.reducer.id,
8848
+ type: WorkflowNodeTypes.LLMResponses,
8849
+ input: {
8850
+ request: this.state.reducer.request,
8851
+ ...this.state.reducer.stream ? { stream: true } : {},
8852
+ bindings: [
8853
+ {
8854
+ from: joinId,
8855
+ // Empty pointer = full join output
8856
+ to: LLM_USER_MESSAGE_TEXT_INTERNAL,
8857
+ encoding: "json_string"
7585
8858
  }
7586
- }
7587
- } catch {
8859
+ ]
7588
8860
  }
7589
- return true;
7590
- });
7591
- return matches.join("\n");
8861
+ };
8862
+ nodes.push(reducerInput);
8863
+ edges.push({ from: joinId, to: this.state.reducer.id });
8864
+ return {
8865
+ kind: WorkflowKinds.WorkflowV0,
8866
+ name: this.state.name,
8867
+ ...this.state.execution ? { execution: this.state.execution } : {},
8868
+ nodes,
8869
+ ...edges.length > 0 ? { edges: sortEdges(edges) } : {},
8870
+ outputs: sortOutputs(this.state.outputs)
8871
+ };
7592
8872
  }
7593
- // ========================================================================
7594
- // Helpers
7595
- // ========================================================================
7596
- parseArgs(call, required) {
7597
- const func = call.function;
7598
- if (!func) {
7599
- throw new ToolArgumentError({
7600
- message: "tool call missing function",
7601
- toolCallId: call.id,
7602
- toolName: "",
7603
- rawArguments: ""
7604
- });
7605
- }
7606
- const rawArgs = func.arguments || "{}";
7607
- let parsed;
7608
- try {
7609
- parsed = JSON.parse(rawArgs);
7610
- } catch (err) {
7611
- throw new ToolArgumentError({
7612
- message: `invalid JSON arguments: ${err.message}`,
7613
- toolCallId: call.id,
7614
- toolName: func.name,
7615
- rawArguments: rawArgs
7616
- });
7617
- }
7618
- if (typeof parsed !== "object" || parsed === null) {
7619
- throw new ToolArgumentError({
7620
- message: "arguments must be an object",
7621
- toolCallId: call.id,
7622
- toolName: func.name,
7623
- rawArguments: rawArgs
7624
- });
7625
- }
7626
- const args = parsed;
7627
- for (const key of required) {
7628
- const value = args[key];
7629
- if (value === void 0 || value === null || value === "") {
7630
- throw new ToolArgumentError({
7631
- message: `${key} is required`,
7632
- toolCallId: call.id,
7633
- toolName: func.name,
7634
- rawArguments: rawArgs
7635
- });
8873
+ };
8874
+ function MapReduce(name, items = []) {
8875
+ return MapReduceBuilder.create(name, items);
8876
+ }
8877
+
8878
+ // src/testing.ts
8879
+ function buildNDJSONResponse(lines, headers = {}, status = 200) {
8880
+ const encoder = new TextEncoder();
8881
+ const stream = new ReadableStream({
8882
+ start(controller) {
8883
+ for (const line of lines) {
8884
+ controller.enqueue(encoder.encode(`${line}
8885
+ `));
7636
8886
  }
8887
+ controller.close();
7637
8888
  }
7638
- return args;
7639
- }
7640
- toolArgumentError(call, message) {
7641
- const func = call.function;
7642
- throw new ToolArgumentError({
7643
- message,
7644
- toolCallId: call.id,
7645
- toolName: func?.name ?? "",
7646
- rawArguments: func?.arguments ?? ""
7647
- });
7648
- }
7649
- requireString(args, key, call) {
7650
- const value = args[key];
7651
- if (typeof value !== "string") {
7652
- this.toolArgumentError(call, `${key} must be a string`);
7653
- }
7654
- if (value.trim() === "") {
7655
- this.toolArgumentError(call, `${key} is required`);
7656
- }
7657
- return value;
7658
- }
7659
- optionalString(args, key, call) {
7660
- const value = args[key];
7661
- if (value === void 0 || value === null) {
7662
- return void 0;
7663
- }
7664
- if (typeof value !== "string") {
7665
- this.toolArgumentError(call, `${key} must be a string`);
7666
- }
7667
- const trimmed = value.trim();
7668
- if (trimmed === "") {
7669
- return void 0;
7670
- }
7671
- return value;
7672
- }
7673
- optionalPositiveInt(args, key, call) {
7674
- const value = args[key];
7675
- if (value === void 0 || value === null) {
7676
- return void 0;
8889
+ });
8890
+ return new Response(stream, {
8891
+ status,
8892
+ headers: {
8893
+ "Content-Type": "application/x-ndjson",
8894
+ ...headers
7677
8895
  }
7678
- if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
7679
- this.toolArgumentError(call, `${key} must be an integer`);
8896
+ });
8897
+ }
8898
+ function buildDelayedNDJSONResponse(steps, headers = {}, status = 200) {
8899
+ const encoder = new TextEncoder();
8900
+ const stream = new ReadableStream({
8901
+ start(controller) {
8902
+ let idx = 0;
8903
+ const pushNext = () => {
8904
+ if (idx >= steps.length) {
8905
+ controller.close();
8906
+ return;
8907
+ }
8908
+ const step = steps[idx++];
8909
+ setTimeout(() => {
8910
+ controller.enqueue(encoder.encode(`${step.line}
8911
+ `));
8912
+ pushNext();
8913
+ }, Math.max(0, step.delayMs));
8914
+ };
8915
+ pushNext();
7680
8916
  }
7681
- if (value <= 0) {
7682
- this.toolArgumentError(call, `${key} must be > 0`);
8917
+ });
8918
+ return new Response(stream, {
8919
+ status,
8920
+ headers: {
8921
+ "Content-Type": "application/x-ndjson",
8922
+ ...headers
7683
8923
  }
7684
- return value;
7685
- }
7686
- isValidUtf8(buffer) {
7687
- try {
7688
- const text = buffer.toString("utf-8");
7689
- return !text.includes("\uFFFD");
7690
- } catch {
7691
- return false;
8924
+ });
8925
+ }
8926
+ function createMockFetchQueue(responses) {
8927
+ const calls = [];
8928
+ const queue = [...responses];
8929
+ const fetchImpl = async (url, init) => {
8930
+ const call = { url: String(url), init };
8931
+ calls.push(call);
8932
+ const responder = queue.shift();
8933
+ if (!responder) {
8934
+ throw new Error("mock fetch queue exhausted");
7692
8935
  }
7693
- }
7694
- /**
7695
- * Recursively walks a directory, calling visitor for each entry.
7696
- * Visitor returns true to continue, false to skip (for dirs) or stop.
7697
- */
7698
- async walkDir(dir, visitor) {
7699
- const entries = await import_fs.promises.readdir(dir, { withFileTypes: true });
7700
- for (const entry of entries) {
7701
- const fullPath = path.join(dir, entry.name);
7702
- const shouldContinue = await visitor(fullPath, entry);
7703
- if (!shouldContinue) {
7704
- if (entry.isDirectory()) {
7705
- continue;
7706
- }
7707
- return;
7708
- }
7709
- if (entry.isDirectory()) {
7710
- await this.walkDir(fullPath, visitor);
7711
- }
8936
+ if (typeof responder === "function") {
8937
+ return responder(call, calls.length - 1);
7712
8938
  }
7713
- }
7714
- };
7715
- function createLocalFSToolPack(options) {
7716
- return new LocalFSToolPack(options);
7717
- }
7718
- function createLocalFSTools(options) {
7719
- return createLocalFSToolPack(options).toRegistry();
8939
+ return responder;
8940
+ };
8941
+ return { fetch: fetchImpl, calls };
7720
8942
  }
7721
8943
 
7722
8944
  // src/tools_browser.ts
@@ -8444,23 +9666,259 @@ var generated_exports = {};
8444
9666
  // src/workflow/index.ts
8445
9667
  var workflow_exports = {};
8446
9668
  __export(workflow_exports, {
9669
+ BindingBuilder: () => BindingBuilder,
8447
9670
  BindingEncodings: () => BindingEncodings,
9671
+ FanoutReduceV1: () => FanoutReduceV1,
8448
9672
  KindV0: () => KindV0,
8449
- LLM_TEXT_OUTPUT: () => LLM_TEXT_OUTPUT2,
8450
- LLM_USER_MESSAGE_TEXT: () => LLM_USER_MESSAGE_TEXT2,
9673
+ KindV1: () => KindV1,
9674
+ LLM_TEXT_OUTPUT: () => LLM_TEXT_OUTPUT,
9675
+ LLM_USER_MESSAGE_TEXT: () => LLM_USER_MESSAGE_TEXT,
8451
9676
  NodeTypes: () => NodeTypes,
9677
+ NodeTypesV1: () => NodeTypesV1,
9678
+ RouterV1: () => RouterV1,
8452
9679
  ToolExecutionModes: () => ToolExecutionModes,
9680
+ bindFrom: () => bindFrom,
9681
+ bindToPlaceholder: () => bindToPlaceholder,
9682
+ bindToPointer: () => bindToPointer,
8453
9683
  parseNodeId: () => parseNodeId,
8454
9684
  parseOutputName: () => parseOutputName,
8455
9685
  parsePlanHash: () => parsePlanHash,
8456
- parseRunId: () => parseRunId
9686
+ parseRunId: () => parseRunId,
9687
+ whenOutputEquals: () => whenOutputEquals,
9688
+ whenOutputExists: () => whenOutputExists,
9689
+ whenOutputMatches: () => whenOutputMatches,
9690
+ whenStatusEquals: () => whenStatusEquals,
9691
+ whenStatusExists: () => whenStatusExists,
9692
+ whenStatusMatches: () => whenStatusMatches
8457
9693
  });
9694
+
9695
+ // src/workflow/helpers_v1.ts
9696
+ function whenOutputEquals(path, value) {
9697
+ return { source: "node_output", op: "equals", path, value };
9698
+ }
9699
+ function whenOutputMatches(path, pattern) {
9700
+ return { source: "node_output", op: "matches", path, value: pattern };
9701
+ }
9702
+ function whenOutputExists(path) {
9703
+ return { source: "node_output", op: "exists", path };
9704
+ }
9705
+ function whenStatusEquals(path, value) {
9706
+ return { source: "node_status", op: "equals", path, value };
9707
+ }
9708
+ function whenStatusMatches(path, pattern) {
9709
+ return { source: "node_status", op: "matches", path, value: pattern };
9710
+ }
9711
+ function whenStatusExists(path) {
9712
+ return { source: "node_status", op: "exists", path };
9713
+ }
9714
+ function bindToPlaceholder(from, placeholder, opts) {
9715
+ return {
9716
+ from,
9717
+ ...opts?.pointer ? { pointer: opts.pointer } : {},
9718
+ to_placeholder: placeholder,
9719
+ encoding: opts?.encoding ?? "json_string"
9720
+ };
9721
+ }
9722
+ function bindToPointer(from, to, opts) {
9723
+ return {
9724
+ from,
9725
+ ...opts?.pointer ? { pointer: opts.pointer } : {},
9726
+ to,
9727
+ encoding: opts?.encoding ?? "json_string"
9728
+ };
9729
+ }
9730
+ function bindFrom(from) {
9731
+ return new BindingBuilder(from);
9732
+ }
9733
+ var BindingBuilder = class {
9734
+ constructor(from) {
9735
+ this._encoding = "json_string";
9736
+ this._from = from;
9737
+ }
9738
+ /**
9739
+ * Set the source pointer to extract from the node's output.
9740
+ */
9741
+ pointer(ptr) {
9742
+ this._pointer = ptr;
9743
+ return this;
9744
+ }
9745
+ /**
9746
+ * Set the destination JSON pointer in the request.
9747
+ */
9748
+ to(ptr) {
9749
+ this._to = ptr;
9750
+ this._toPlaceholder = void 0;
9751
+ return this;
9752
+ }
9753
+ /**
9754
+ * Set the destination placeholder name.
9755
+ */
9756
+ toPlaceholder(name) {
9757
+ this._toPlaceholder = name;
9758
+ this._to = void 0;
9759
+ return this;
9760
+ }
9761
+ /**
9762
+ * Set the encoding for the binding value.
9763
+ */
9764
+ encoding(enc) {
9765
+ this._encoding = enc;
9766
+ return this;
9767
+ }
9768
+ /**
9769
+ * Build the binding object.
9770
+ */
9771
+ build() {
9772
+ const binding = {
9773
+ from: this._from,
9774
+ encoding: this._encoding
9775
+ };
9776
+ if (this._pointer) binding.pointer = this._pointer;
9777
+ if (this._to) binding.to = this._to;
9778
+ if (this._toPlaceholder) binding.to_placeholder = this._toPlaceholder;
9779
+ return binding;
9780
+ }
9781
+ };
9782
+
9783
+ // src/workflow/patterns_v1.ts
9784
+ function wireRequest3(req) {
9785
+ const raw = req;
9786
+ if (raw && typeof raw === "object") {
9787
+ if ("input" in raw) {
9788
+ return req;
9789
+ }
9790
+ if ("body" in raw) {
9791
+ return raw.body ?? {};
9792
+ }
9793
+ }
9794
+ return asInternal(req).body;
9795
+ }
9796
+ var RouterV1 = class {
9797
+ constructor(config) {
9798
+ this.config = config;
9799
+ }
9800
+ /**
9801
+ * Build the workflow specification.
9802
+ */
9803
+ build() {
9804
+ const {
9805
+ name,
9806
+ classifier,
9807
+ classifierId = "router",
9808
+ routePath = "$.route",
9809
+ routes,
9810
+ aggregator,
9811
+ outputName = "final"
9812
+ } = this.config;
9813
+ let builder = new WorkflowBuilderV1();
9814
+ if (name) {
9815
+ builder = builder.name(name);
9816
+ }
9817
+ builder = builder.routeSwitch(classifierId, classifier);
9818
+ const joinId = "__router_join";
9819
+ builder = builder.joinAny(joinId);
9820
+ for (let i = 0; i < routes.length; i++) {
9821
+ const route = routes[i];
9822
+ const handlerId = route.id ?? `handler_${i}`;
9823
+ builder = builder.llmResponses(handlerId, route.handler, {
9824
+ bindings: route.bindings
9825
+ });
9826
+ builder = builder.edge(classifierId, handlerId, whenOutputEquals(routePath, route.value));
9827
+ builder = builder.edge(handlerId, joinId);
9828
+ }
9829
+ if (aggregator) {
9830
+ const aggId = aggregator.id ?? "aggregate";
9831
+ const placeholder = aggregator.placeholder ?? "route_output";
9832
+ builder = builder.llmResponses(aggId, aggregator.request, {
9833
+ bindings: [bindToPlaceholder(joinId, placeholder)]
9834
+ });
9835
+ builder = builder.edge(joinId, aggId);
9836
+ builder = builder.output(outputName, aggId);
9837
+ } else {
9838
+ builder = builder.output(outputName, joinId);
9839
+ }
9840
+ return builder.build();
9841
+ }
9842
+ };
9843
+ var FanoutReduceV1 = class {
9844
+ constructor(config) {
9845
+ this.config = config;
9846
+ }
9847
+ /**
9848
+ * Build the workflow specification.
9849
+ */
9850
+ build() {
9851
+ const {
9852
+ name,
9853
+ generator,
9854
+ generatorId = "generator",
9855
+ itemsPath = "/items",
9856
+ mapper,
9857
+ mapperPlaceholder = "item",
9858
+ maxParallelism = 4,
9859
+ reducer,
9860
+ reducerId = "reducer",
9861
+ reducerBinding,
9862
+ outputName = "final"
9863
+ } = this.config;
9864
+ let builder = new WorkflowBuilderV1();
9865
+ if (name) {
9866
+ builder = builder.name(name);
9867
+ }
9868
+ builder = builder.llmResponses(generatorId, generator);
9869
+ const fanoutId = "__fanout";
9870
+ const itemBindings = [
9871
+ {
9872
+ path: "$",
9873
+ to_placeholder: mapperPlaceholder,
9874
+ encoding: "json_string"
9875
+ }
9876
+ ];
9877
+ const mapperRequest = wireRequest3(mapper);
9878
+ builder = builder.mapFanout(fanoutId, {
9879
+ items: { from: generatorId, path: itemsPath },
9880
+ item_bindings: itemBindings,
9881
+ subnode: {
9882
+ id: "__mapper",
9883
+ type: "llm.responses",
9884
+ input: { request: mapperRequest }
9885
+ },
9886
+ max_parallelism: maxParallelism
9887
+ });
9888
+ builder = builder.edge(generatorId, fanoutId);
9889
+ const pointer = reducerBinding?.pointer ?? "/results";
9890
+ const binding = reducerBinding?.placeholder ? bindToPlaceholder(fanoutId, reducerBinding.placeholder, { pointer }) : {
9891
+ from: fanoutId,
9892
+ pointer,
9893
+ to: reducerBinding?.to ?? "/input/0/content/0/text",
9894
+ encoding: "json_string"
9895
+ };
9896
+ builder = builder.llmResponses(reducerId, reducer, {
9897
+ bindings: [binding]
9898
+ });
9899
+ builder = builder.edge(fanoutId, reducerId);
9900
+ builder = builder.output(outputName, reducerId);
9901
+ return builder.build();
9902
+ }
9903
+ };
9904
+
9905
+ // src/workflow/index.ts
8458
9906
  var KindV0 = WorkflowKinds.WorkflowV0;
9907
+ var KindV1 = WorkflowKinds.WorkflowV1;
8459
9908
  var NodeTypes = {
8460
9909
  LLMResponses: WorkflowNodeTypes.LLMResponses,
8461
9910
  JoinAll: WorkflowNodeTypes.JoinAll,
8462
9911
  TransformJSON: WorkflowNodeTypes.TransformJSON
8463
9912
  };
9913
+ var NodeTypesV1 = {
9914
+ LLMResponses: WorkflowNodeTypesV1.LLMResponses,
9915
+ RouteSwitch: WorkflowNodeTypesV1.RouteSwitch,
9916
+ JoinAll: WorkflowNodeTypesV1.JoinAll,
9917
+ JoinAny: WorkflowNodeTypesV1.JoinAny,
9918
+ JoinCollect: WorkflowNodeTypesV1.JoinCollect,
9919
+ TransformJSON: WorkflowNodeTypesV1.TransformJSON,
9920
+ MapFanout: WorkflowNodeTypesV1.MapFanout
9921
+ };
8464
9922
  var BindingEncodings = {
8465
9923
  JSON: "json",
8466
9924
  JSONString: "json_string"
@@ -8469,8 +9927,6 @@ var ToolExecutionModes = {
8469
9927
  Server: "server",
8470
9928
  Client: "client"
8471
9929
  };
8472
- var LLM_TEXT_OUTPUT2 = "/output/0/content/0/text";
8473
- var LLM_USER_MESSAGE_TEXT2 = "/request/input/1/content/0/text";
8474
9930
 
8475
9931
  // src/index.ts
8476
9932
  var ModelRelay = class _ModelRelay {
@@ -8492,7 +9948,7 @@ var ModelRelay = class _ModelRelay {
8492
9948
  const accessToken = "token" in cfg ? cfg.token : void 0;
8493
9949
  const tokenProvider = "tokenProvider" in cfg ? cfg.tokenProvider : void 0;
8494
9950
  this.baseUrl = resolveBaseUrl(cfg.baseUrl);
8495
- const http = new HTTPClient({
9951
+ this.http = new HTTPClient({
8496
9952
  baseUrl: this.baseUrl,
8497
9953
  apiKey,
8498
9954
  accessToken,
@@ -8505,30 +9961,30 @@ var ModelRelay = class _ModelRelay {
8505
9961
  metrics: cfg.metrics,
8506
9962
  trace: cfg.trace
8507
9963
  });
8508
- const auth = new AuthClient(http, {
9964
+ const auth = new AuthClient(this.http, {
8509
9965
  apiKey,
8510
9966
  accessToken,
8511
9967
  customer: cfg.customer,
8512
9968
  tokenProvider
8513
9969
  });
8514
9970
  this.auth = auth;
8515
- this.responses = new ResponsesClient(http, auth, {
9971
+ this.responses = new ResponsesClient(this.http, auth, {
8516
9972
  metrics: cfg.metrics,
8517
9973
  trace: cfg.trace
8518
9974
  });
8519
- this.runs = new RunsClient(http, auth, {
9975
+ this.runs = new RunsClient(this.http, auth, {
8520
9976
  metrics: cfg.metrics,
8521
9977
  trace: cfg.trace
8522
9978
  });
8523
- this.workflows = new WorkflowsClient(http, auth, {
9979
+ this.workflows = new WorkflowsClient(this.http, auth, {
8524
9980
  metrics: cfg.metrics,
8525
9981
  trace: cfg.trace
8526
9982
  });
8527
- this.images = new ImagesClient(http, auth);
8528
- this.customers = new CustomersClient(http, { apiKey, accessToken, tokenProvider });
8529
- this.tiers = new TiersClient(http, { apiKey });
8530
- this.models = new ModelsClient(http);
8531
- this.sessions = new SessionsClient(this, http, auth);
9983
+ this.images = new ImagesClient(this.http, auth);
9984
+ this.customers = new CustomersClient(this.http, { apiKey, accessToken, tokenProvider });
9985
+ this.tiers = new TiersClient(this.http, { apiKey });
9986
+ this.models = new ModelsClient(this.http);
9987
+ this.sessions = new SessionsClient(this, this.http, auth);
8532
9988
  }
8533
9989
  forCustomer(customerId) {
8534
9990
  return new CustomerScopedModelRelay(this.responses, customerId, this.baseUrl);
@@ -8543,6 +9999,7 @@ function resolveBaseUrl(override) {
8543
9999
  APIError,
8544
10000
  AuthClient,
8545
10001
  BillingProviders,
10002
+ BindingTargetError,
8546
10003
  BrowserDefaults,
8547
10004
  BrowserToolNames,
8548
10005
  BrowserToolPack,
@@ -8557,20 +10014,31 @@ function resolveBaseUrl(override) {
8557
10014
  DEFAULT_BASE_URL,
8558
10015
  DEFAULT_CLIENT_HEADER,
8559
10016
  DEFAULT_CONNECT_TIMEOUT_MS,
8560
- DEFAULT_IGNORE_DIRS,
8561
10017
  DEFAULT_REQUEST_TIMEOUT_MS,
8562
10018
  ErrorCodes,
8563
- FSDefaults,
8564
- FSToolNames,
8565
10019
  FrontendTokenProvider,
8566
10020
  ImagesClient,
8567
10021
  InputItemTypes,
10022
+ JoinOutput,
10023
+ JoinOutputPath,
10024
+ LLMInput,
10025
+ LLMInputContentItemPath,
10026
+ LLMInputFirstMessageText,
10027
+ LLMInputMessagePath,
10028
+ LLMInputPath,
10029
+ LLMInputSystemText,
10030
+ LLMInputUserText,
8568
10031
  LLMNodeBuilder,
10032
+ LLMOutput,
10033
+ LLMOutputContentItemPath,
10034
+ LLMOutputContentPath,
10035
+ LLMOutputPath,
10036
+ LLMOutputText,
8569
10037
  LLMStep,
8570
10038
  LLM_TEXT_OUTPUT,
8571
10039
  LLM_USER_MESSAGE_TEXT,
8572
- LocalFSToolPack,
8573
10040
  LocalSession,
10041
+ MapFanoutInputError,
8574
10042
  MapItem,
8575
10043
  MapReduce,
8576
10044
  MapReduceBuilder,
@@ -8609,9 +10077,10 @@ function resolveBaseUrl(override) {
8609
10077
  TransformJSONNodeBuilder,
8610
10078
  TransportError,
8611
10079
  WORKFLOWS_COMPILE_PATH,
8612
- WebToolModes,
10080
+ WebToolIntents,
8613
10081
  Workflow,
8614
10082
  WorkflowBuilderV0,
10083
+ WorkflowBuilderV1,
8615
10084
  WorkflowKinds,
8616
10085
  WorkflowNodeTypes,
8617
10086
  WorkflowValidationError,
@@ -8621,6 +10090,8 @@ function resolveBaseUrl(override) {
8621
10090
  asSessionId,
8622
10091
  asTierCode,
8623
10092
  assistantMessageWithToolCalls,
10093
+ buildDelayedNDJSONResponse,
10094
+ buildNDJSONResponse,
8624
10095
  createAccessTokenAuth,
8625
10096
  createApiKeyAuth,
8626
10097
  createAssistantMessage,
@@ -8629,10 +10100,9 @@ function resolveBaseUrl(override) {
8629
10100
  createFunctionCall,
8630
10101
  createFunctionTool,
8631
10102
  createFunctionToolFromSchema,
8632
- createLocalFSToolPack,
8633
- createLocalFSTools,
8634
10103
  createLocalSession,
8635
10104
  createMemorySessionStore,
10105
+ createMockFetchQueue,
8636
10106
  createRetryMessages,
8637
10107
  createSystemMessage,
8638
10108
  createToolCall,
@@ -8674,6 +10144,7 @@ function resolveBaseUrl(override) {
8674
10144
  parseToolArgs,
8675
10145
  parseToolArgsRaw,
8676
10146
  pollOAuthDeviceToken,
10147
+ pollUntil,
8677
10148
  respondToToolCall,
8678
10149
  runOAuthDeviceFlowForIDToken,
8679
10150
  startOAuthDeviceAuthorization,
@@ -8683,12 +10154,17 @@ function resolveBaseUrl(override) {
8683
10154
  toolChoiceRequired,
8684
10155
  toolResultMessage,
8685
10156
  transformJSONMerge,
10157
+ transformJSONMergeV1,
8686
10158
  transformJSONObject,
10159
+ transformJSONObjectV1,
8687
10160
  transformJSONValue,
10161
+ transformJSONValueV1,
8688
10162
  tryParseToolArgs,
8689
10163
  validateWithZod,
8690
10164
  workflow,
8691
10165
  workflowV0,
8692
10166
  workflowV0Schema,
10167
+ workflowV1,
10168
+ workflowV1Schema,
8693
10169
  zodToJsonSchema
8694
10170
  });