@modelrelay/sdk 5.3.0 → 8.1.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.
Files changed (50) hide show
  1. package/README.md +122 -0
  2. package/dist/{api-7TVb2cnl.d.cts → api-B7SmXjnr.d.cts} +599 -1
  3. package/dist/{api-7TVb2cnl.d.ts → api-B7SmXjnr.d.ts} +599 -1
  4. package/dist/api-CdHqjsU_.d.cts +6062 -0
  5. package/dist/api-CdHqjsU_.d.ts +6062 -0
  6. package/dist/api-CyuI9lx0.d.cts +6249 -0
  7. package/dist/api-CyuI9lx0.d.ts +6249 -0
  8. package/dist/api-DP9MoKHy.d.cts +5993 -0
  9. package/dist/api-DP9MoKHy.d.ts +5993 -0
  10. package/dist/api-R_gUMD1L.d.cts +6243 -0
  11. package/dist/api-R_gUMD1L.d.ts +6243 -0
  12. package/dist/{api-BRAJe_xm.d.cts → api-c1j5ycVR.d.cts} +0 -54
  13. package/dist/{api-BRAJe_xm.d.ts → api-c1j5ycVR.d.ts} +0 -54
  14. package/dist/billing/index.d.cts +1 -1
  15. package/dist/billing/index.d.ts +1 -1
  16. package/dist/chunk-27KGKJLT.js +1194 -0
  17. package/dist/{chunk-5O4NJXLJ.js → chunk-HHBAD7FF.js} +1 -1
  18. package/dist/{chunk-LZDGY24E.js → chunk-PKGWFDGU.js} +111 -59
  19. package/dist/{chunk-HLJAMT7F.js → chunk-RVHKBQ7X.js} +1 -1
  20. package/dist/chunk-SJC7Q4NK.js +1199 -0
  21. package/dist/chunk-URFLODQC.js +1199 -0
  22. package/dist/{chunk-JZRSCFQH.js → chunk-YQWOQ74P.js} +53 -20
  23. package/dist/index.cjs +3468 -1156
  24. package/dist/index.d.cts +916 -85
  25. package/dist/index.d.ts +916 -85
  26. package/dist/index.js +3106 -881
  27. package/dist/node.cjs +11 -7
  28. package/dist/node.d.cts +11 -11
  29. package/dist/node.d.ts +11 -11
  30. package/dist/node.js +6 -6
  31. package/dist/{tools-Db-F5rIL.d.cts → tools-Bxdv0Np2.d.cts} +101 -218
  32. package/dist/{tools-Db-F5rIL.d.ts → tools-Bxdv0Np2.d.ts} +101 -218
  33. package/dist/tools-DHCGz_lx.d.cts +1041 -0
  34. package/dist/tools-DHCGz_lx.d.ts +1041 -0
  35. package/dist/tools-ZpcYacSo.d.cts +1000 -0
  36. package/dist/tools-ZpcYacSo.d.ts +1000 -0
  37. package/package.json +6 -1
  38. package/dist/api-B9x3HA3Z.d.cts +0 -5292
  39. package/dist/api-B9x3HA3Z.d.ts +0 -5292
  40. package/dist/api-Bitsm1tl.d.cts +0 -5290
  41. package/dist/api-Bitsm1tl.d.ts +0 -5290
  42. package/dist/api-D0wnVpwn.d.cts +0 -5292
  43. package/dist/api-D0wnVpwn.d.ts +0 -5292
  44. package/dist/api-HVh8Lusf.d.cts +0 -5300
  45. package/dist/api-HVh8Lusf.d.ts +0 -5300
  46. package/dist/chunk-BL7GWXRZ.js +0 -1196
  47. package/dist/chunk-CV3DTA6P.js +0 -1196
  48. package/dist/chunk-G5H7EY4F.js +0 -1196
  49. package/dist/chunk-OJFVI3QJ.js +0 -1133
  50. package/dist/chunk-Z6R4G2TU.js +0 -1133
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  APIError,
3
+ AgentMaxTurnsError,
3
4
  BillingProviders,
4
5
  ConfigError,
5
6
  ContentPartTypes,
@@ -26,7 +27,6 @@ import {
26
27
  ToolRegistry,
27
28
  ToolTypes,
28
29
  TransportError,
29
- WebToolIntents,
30
30
  WorkflowValidationError,
31
31
  asModelId,
32
32
  asProviderId,
@@ -39,13 +39,20 @@ import {
39
39
  createRetryMessages,
40
40
  createSystemMessage,
41
41
  createToolCall,
42
+ createTypedTool,
42
43
  createUsage,
43
44
  createUserMessage,
44
- createWebTool,
45
45
  executeWithRetry,
46
46
  firstToolCall,
47
47
  formatToolErrorForModel,
48
+ getAllToolCalls,
49
+ getAssistantText,
48
50
  getRetryableErrors,
51
+ getToolArgs,
52
+ getToolArgsRaw,
53
+ getToolName,
54
+ getTypedToolCall,
55
+ getTypedToolCalls,
49
56
  hasRetryableErrors,
50
57
  hasToolCalls,
51
58
  mergeMetrics,
@@ -54,17 +61,15 @@ import {
54
61
  normalizeModelId,
55
62
  normalizeStopReason,
56
63
  parseErrorResponse,
57
- parseToolArgs,
58
- parseToolArgsRaw,
64
+ parseTypedToolCall,
59
65
  respondToToolCall,
60
66
  stopReasonToString,
61
67
  toolChoiceAuto,
62
68
  toolChoiceNone,
63
69
  toolChoiceRequired,
64
70
  toolResultMessage,
65
- tryParseToolArgs,
66
71
  zodToJsonSchema
67
- } from "./chunk-LZDGY24E.js";
72
+ } from "./chunk-URFLODQC.js";
68
73
  import {
69
74
  __export
70
75
  } from "./chunk-MLKGABMK.js";
@@ -154,6 +159,9 @@ var AuthClient = class {
154
159
  if (typeof request.ttlSeconds === "number") {
155
160
  payload.ttl_seconds = request.ttlSeconds;
156
161
  }
162
+ if (request.tierCode) {
163
+ payload.tier_code = request.tierCode;
164
+ }
157
165
  const apiResp = await this.http.json("/auth/customer-token", {
158
166
  method: "POST",
159
167
  body: payload,
@@ -209,7 +217,8 @@ var AuthClient = class {
209
217
  });
210
218
  return this.customerToken({
211
219
  customerExternalId: externalId,
212
- ttlSeconds: request.ttlSeconds
220
+ ttlSeconds: request.ttlSeconds,
221
+ tierCode: request.tierCode
213
222
  });
214
223
  }
215
224
  /**
@@ -327,13 +336,38 @@ var ResponseBuilder = class _ResponseBuilder {
327
336
  patch.options ? { ...this.options, ...patch.options } : this.options
328
337
  );
329
338
  }
330
- /** @returns A new builder with the provider set. */
339
+ /**
340
+ * Set the provider for this request.
341
+ *
342
+ * Accepts either a string or ProviderId for convenience.
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * .provider("anthropic") // String works
347
+ * .provider(asProviderId("anthropic")) // ProviderId also works
348
+ * ```
349
+ */
331
350
  provider(provider) {
332
- return this.with({ body: { provider } });
351
+ return this.with({ body: { provider: asProviderId(provider) } });
333
352
  }
334
- /** @returns A new builder with the model set. */
353
+ /**
354
+ * Set the model for this request.
355
+ *
356
+ * Accepts either a string or ModelId for convenience.
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * .model("claude-sonnet-4-5") // String works
361
+ * .model(asModelId("claude-sonnet-4-5")) // ModelId also works
362
+ * ```
363
+ */
335
364
  model(model) {
336
- return this.with({ body: { model } });
365
+ return this.with({ body: { model: asModelId(model) } });
366
+ }
367
+ /** @returns A new builder with state-scoped tool state. */
368
+ stateId(stateId) {
369
+ const state_id = stateId.trim();
370
+ return this.with({ body: { state_id: state_id || void 0 } });
337
371
  }
338
372
  /** @returns A new builder with the full input array replaced. */
339
373
  input(items) {
@@ -481,6 +515,111 @@ var ResponseBuilder = class _ResponseBuilder {
481
515
  signal(signal) {
482
516
  return this.with({ options: { signal } });
483
517
  }
518
+ // =========================================================================
519
+ // Conversation Continuation Helpers
520
+ // =========================================================================
521
+ /**
522
+ * Add an assistant message with tool calls from a previous response.
523
+ *
524
+ * This is useful for continuing a conversation after handling tool calls.
525
+ *
526
+ * @example
527
+ * ```typescript
528
+ * const response = await mr.responses.create(request);
529
+ * if (hasToolCalls(response)) {
530
+ * const toolCalls = response.output[0].toolCalls!;
531
+ * const results = await registry.executeAll(toolCalls);
532
+ *
533
+ * const followUp = await mr.responses.create(
534
+ * mr.responses.new()
535
+ * .model("claude-sonnet-4-5")
536
+ * .user("What's the weather in Paris?")
537
+ * .assistantToolCalls(toolCalls)
538
+ * .toolResults(results.map(r => ({ id: r.toolCallId, result: r.result })))
539
+ * .build()
540
+ * );
541
+ * }
542
+ * ```
543
+ */
544
+ assistantToolCalls(toolCalls, content) {
545
+ return this.item(assistantMessageWithToolCalls(content ?? "", toolCalls));
546
+ }
547
+ /**
548
+ * Add tool results to the conversation.
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * .toolResults([
553
+ * { id: "call_123", result: { temp: 72 } },
554
+ * { id: "call_456", result: "File contents here" },
555
+ * ])
556
+ * ```
557
+ */
558
+ toolResults(results) {
559
+ let builder = this;
560
+ for (const r of results) {
561
+ const content = typeof r.result === "string" ? r.result : JSON.stringify(r.result);
562
+ builder = builder.item(toolResultMessage(r.id, content));
563
+ }
564
+ return builder;
565
+ }
566
+ /**
567
+ * Add a single tool result to the conversation.
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * .toolResult("call_123", { temp: 72, unit: "fahrenheit" })
572
+ * ```
573
+ */
574
+ toolResult(toolCallId, result) {
575
+ const content = typeof result === "string" ? result : JSON.stringify(result);
576
+ return this.item(toolResultMessage(toolCallId, content));
577
+ }
578
+ /**
579
+ * Continue from a previous response that contains tool calls.
580
+ *
581
+ * This is the most ergonomic way to continue a conversation after handling tools.
582
+ * It automatically adds the assistant's tool call message and your tool results.
583
+ *
584
+ * @example
585
+ * ```typescript
586
+ * const response = await mr.responses.create(request);
587
+ *
588
+ * if (hasToolCalls(response)) {
589
+ * const toolCalls = response.output[0].toolCalls!;
590
+ * const results = await registry.executeAll(toolCalls);
591
+ *
592
+ * const followUp = await mr.responses.create(
593
+ * mr.responses.new()
594
+ * .model("claude-sonnet-4-5")
595
+ * .tools(myTools)
596
+ * .user("What's the weather in Paris?")
597
+ * .continueFrom(response, results.map(r => ({
598
+ * id: r.toolCallId,
599
+ * result: r.result,
600
+ * })))
601
+ * .build()
602
+ * );
603
+ * }
604
+ * ```
605
+ */
606
+ continueFrom(response, toolResults) {
607
+ const toolCalls = [];
608
+ for (const item of response.output || []) {
609
+ if (item.toolCalls) {
610
+ toolCalls.push(...item.toolCalls);
611
+ }
612
+ }
613
+ if (toolCalls.length === 0) {
614
+ throw new ConfigError(
615
+ "continueFrom requires a response with tool calls"
616
+ );
617
+ }
618
+ const assistantText = getAssistantText(response);
619
+ return this.assistantToolCalls(toolCalls, assistantText).toolResults(
620
+ toolResults
621
+ );
622
+ }
484
623
  /** @returns A finalized, immutable request payload. */
485
624
  build() {
486
625
  const input = (this.body.input ?? []).slice();
@@ -490,6 +629,7 @@ var ResponseBuilder = class _ResponseBuilder {
490
629
  const body = {
491
630
  provider: this.body.provider,
492
631
  model: this.body.model,
632
+ state_id: this.body.state_id,
493
633
  input,
494
634
  output_format: this.body.output_format,
495
635
  max_output_tokens: this.body.max_output_tokens,
@@ -1806,7 +1946,7 @@ function parseOutputName(raw) {
1806
1946
  var WorkflowKinds = {
1807
1947
  WorkflowIntent: "workflow"
1808
1948
  };
1809
- var WorkflowNodeTypesLite = {
1949
+ var WorkflowNodeTypesIntent = {
1810
1950
  LLM: "llm",
1811
1951
  JoinAll: "join.all",
1812
1952
  JoinAny: "join.any",
@@ -1876,6 +2016,25 @@ var nodeWaitingSchema = z.object({
1876
2016
  pending_tool_calls: z.array(pendingToolCallSchema).min(1),
1877
2017
  reason: z.string().min(1)
1878
2018
  }).strict();
2019
+ var userAskOptionSchema = z.object({
2020
+ label: z.string().min(1),
2021
+ description: z.string().optional()
2022
+ }).strict();
2023
+ var nodeUserAskSchema = z.object({
2024
+ step: z.number().int().nonnegative(),
2025
+ request_id: z.string().min(1),
2026
+ tool_call: toolCallWithArgumentsSchema,
2027
+ question: z.string().min(1),
2028
+ options: z.array(userAskOptionSchema).optional(),
2029
+ allow_freeform: z.boolean()
2030
+ }).strict();
2031
+ var nodeUserAnswerSchema = z.object({
2032
+ step: z.number().int().nonnegative(),
2033
+ request_id: z.string().min(1),
2034
+ tool_call: toolCallSchema,
2035
+ answer: z.string(),
2036
+ is_freeform: z.boolean()
2037
+ }).strict();
1879
2038
  var baseSchema = {
1880
2039
  envelope_version: z.literal("v2").optional().default("v2"),
1881
2040
  run_id: z.string().min(1),
@@ -1927,6 +2086,18 @@ var runEventWireSchema = z.discriminatedUnion("type", [
1927
2086
  node_id: z.string().min(1),
1928
2087
  waiting: nodeWaitingSchema
1929
2088
  }).strict(),
2089
+ z.object({
2090
+ ...baseSchema,
2091
+ type: z.literal("node_user_ask"),
2092
+ node_id: z.string().min(1),
2093
+ user_ask: nodeUserAskSchema
2094
+ }).strict(),
2095
+ z.object({
2096
+ ...baseSchema,
2097
+ type: z.literal("node_user_answer"),
2098
+ node_id: z.string().min(1),
2099
+ user_answer: nodeUserAnswerSchema
2100
+ }).strict(),
1930
2101
  z.object({ ...baseSchema, type: z.literal("node_started"), node_id: z.string().min(1) }).strict(),
1931
2102
  z.object({ ...baseSchema, type: z.literal("node_succeeded"), node_id: z.string().min(1) }).strict(),
1932
2103
  z.object({
@@ -2020,6 +2191,10 @@ function parseRunEventV0(line) {
2020
2191
  return { ...base, type: "node_tool_result", node_id: parseNodeId(res.data.node_id), tool_result: res.data.tool_result };
2021
2192
  case "node_waiting":
2022
2193
  return { ...base, type: "node_waiting", node_id: parseNodeId(res.data.node_id), waiting: res.data.waiting };
2194
+ case "node_user_ask":
2195
+ return { ...base, type: "node_user_ask", node_id: parseNodeId(res.data.node_id), user_ask: res.data.user_ask };
2196
+ case "node_user_answer":
2197
+ return { ...base, type: "node_user_answer", node_id: parseNodeId(res.data.node_id), user_answer: res.data.user_answer };
2023
2198
  case "node_succeeded":
2024
2199
  return { ...base, type: "node_succeeded", node_id: parseNodeId(res.data.node_id) };
2025
2200
  case "node_failed":
@@ -2181,6 +2356,90 @@ var RunsClient = class {
2181
2356
  if (options.input) {
2182
2357
  payload.input = options.input;
2183
2358
  }
2359
+ if (options.modelOverride?.trim()) {
2360
+ payload.model_override = options.modelOverride.trim();
2361
+ }
2362
+ if (options.modelOverrides) {
2363
+ const nodes = options.modelOverrides.nodes;
2364
+ const fanoutSubnodes = options.modelOverrides.fanoutSubnodes;
2365
+ if (nodes && Object.keys(nodes).length > 0 || fanoutSubnodes && fanoutSubnodes.length > 0) {
2366
+ payload.model_overrides = {
2367
+ nodes,
2368
+ fanout_subnodes: fanoutSubnodes?.map((entry) => ({
2369
+ parent_id: entry.parentId,
2370
+ subnode_id: entry.subnodeId,
2371
+ model: entry.model
2372
+ }))
2373
+ };
2374
+ }
2375
+ }
2376
+ if (options.stream !== void 0) {
2377
+ payload.stream = options.stream;
2378
+ }
2379
+ const out = await this.http.json(RUNS_PATH, {
2380
+ method: "POST",
2381
+ headers,
2382
+ body: payload,
2383
+ signal: options.signal,
2384
+ apiKey: authHeaders.apiKey,
2385
+ accessToken: authHeaders.accessToken,
2386
+ timeoutMs: options.timeoutMs,
2387
+ connectTimeoutMs: options.connectTimeoutMs,
2388
+ retry: options.retry,
2389
+ metrics,
2390
+ trace,
2391
+ context: { method: "POST", path: RUNS_PATH }
2392
+ });
2393
+ return { ...out, run_id: parseRunId(out.run_id), plan_hash: parsePlanHash(out.plan_hash) };
2394
+ }
2395
+ /**
2396
+ * Starts a workflow run using a precompiled plan hash.
2397
+ *
2398
+ * Use workflows.compile() to compile a workflow spec and obtain a plan_hash,
2399
+ * then use this method to start runs without re-compiling each time.
2400
+ * This is useful for workflows that are run repeatedly with the same structure
2401
+ * but different inputs.
2402
+ *
2403
+ * The plan_hash must have been compiled in the current server session;
2404
+ * if the server has restarted since compilation, the plan will not be found
2405
+ * and you'll need to recompile.
2406
+ */
2407
+ async createFromPlan(planHash, options = {}) {
2408
+ const metrics = mergeMetrics(this.metrics, options.metrics);
2409
+ const trace = mergeTrace(this.trace, options.trace);
2410
+ const authHeaders = await this.auth.authForResponses();
2411
+ const headers = { ...options.headers || {} };
2412
+ this.applyCustomerHeader(headers, options.customerId);
2413
+ const payload = { plan_hash: planHash };
2414
+ if (options.sessionId?.trim()) {
2415
+ payload.session_id = options.sessionId.trim();
2416
+ }
2417
+ if (options.idempotencyKey?.trim()) {
2418
+ payload.options = { idempotency_key: options.idempotencyKey.trim() };
2419
+ }
2420
+ if (options.input) {
2421
+ payload.input = options.input;
2422
+ }
2423
+ if (options.modelOverride?.trim()) {
2424
+ payload.model_override = options.modelOverride.trim();
2425
+ }
2426
+ if (options.modelOverrides) {
2427
+ const nodes = options.modelOverrides.nodes;
2428
+ const fanoutSubnodes = options.modelOverrides.fanoutSubnodes;
2429
+ if (nodes && Object.keys(nodes).length > 0 || fanoutSubnodes && fanoutSubnodes.length > 0) {
2430
+ payload.model_overrides = {
2431
+ nodes,
2432
+ fanout_subnodes: fanoutSubnodes?.map((entry) => ({
2433
+ parent_id: entry.parentId,
2434
+ subnode_id: entry.subnodeId,
2435
+ model: entry.model
2436
+ }))
2437
+ };
2438
+ }
2439
+ }
2440
+ if (options.stream !== void 0) {
2441
+ payload.stream = options.stream;
2442
+ }
2184
2443
  const out = await this.http.json(RUNS_PATH, {
2185
2444
  method: "POST",
2186
2445
  headers,
@@ -2523,235 +2782,210 @@ var ImagesClient = class {
2523
2782
  }
2524
2783
  };
2525
2784
 
2526
- // src/sessions/context_management.ts
2527
- var DEFAULT_CONTEXT_BUFFER_TOKENS = 256;
2528
- var CONTEXT_BUFFER_RATIO = 0.02;
2529
- var MESSAGE_OVERHEAD_TOKENS = 6;
2530
- var TOOL_CALL_OVERHEAD_TOKENS = 4;
2531
- var CHARS_PER_TOKEN = 4;
2532
- var IMAGE_TOKENS_LOW_DETAIL = 85;
2533
- var IMAGE_TOKENS_HIGH_DETAIL = 1e3;
2534
- var modelContextCache = /* @__PURE__ */ new WeakMap();
2535
- function createModelContextResolver(client) {
2536
- return async (modelId) => {
2537
- const entry = getModelContextCacheEntry(client);
2538
- const key = String(modelId);
2539
- const cached = entry.byId.get(key);
2540
- if (cached !== void 0) {
2541
- return cached;
2785
+ // src/state_handles.ts
2786
+ var MAX_STATE_HANDLE_TTL_SECONDS = 31536e3;
2787
+ var STATE_HANDLES_PATH = "/state-handles";
2788
+ var StateHandlesClient = class {
2789
+ constructor(http, auth) {
2790
+ this.http = http;
2791
+ this.auth = auth;
2792
+ }
2793
+ /** Make an authenticated request to the state handles API. */
2794
+ async request(method, path, body) {
2795
+ const auth = await this.auth.authForResponses();
2796
+ return this.http.json(path, {
2797
+ method,
2798
+ body,
2799
+ apiKey: auth.apiKey,
2800
+ accessToken: auth.accessToken
2801
+ });
2802
+ }
2803
+ async create(request = {}) {
2804
+ if (request.ttl_seconds !== void 0) {
2805
+ if (request.ttl_seconds <= 0) {
2806
+ throw new Error("ttl_seconds must be positive");
2807
+ }
2808
+ if (request.ttl_seconds > MAX_STATE_HANDLE_TTL_SECONDS) {
2809
+ throw new Error("ttl_seconds exceeds maximum (1 year)");
2810
+ }
2542
2811
  }
2543
- await populateModelContextCache(client, entry);
2544
- const resolved = entry.byId.get(key);
2545
- if (resolved === void 0) {
2546
- throw new ConfigError(
2547
- `Unknown model "${key}"; ensure the model exists in the ModelRelay catalog`
2548
- );
2812
+ return this.request("POST", STATE_HANDLES_PATH, request);
2813
+ }
2814
+ async list(params = {}) {
2815
+ const { limit, offset } = params;
2816
+ if (limit !== void 0 && (limit <= 0 || limit > 100)) {
2817
+ throw new Error("limit must be between 1 and 100");
2549
2818
  }
2550
- return resolved;
2551
- };
2552
- }
2553
- async function buildSessionInputWithContext(messages, options, defaultModel, resolveModelContext) {
2554
- const strategy = options.contextManagement ?? "none";
2555
- if (strategy === "none") {
2556
- return messagesToInput(messages);
2819
+ if (offset !== void 0 && offset < 0) {
2820
+ throw new Error("offset must be non-negative");
2821
+ }
2822
+ const query = new URLSearchParams();
2823
+ if (limit !== void 0) {
2824
+ query.set("limit", String(limit));
2825
+ }
2826
+ if (offset !== void 0 && offset > 0) {
2827
+ query.set("offset", String(offset));
2828
+ }
2829
+ const path = query.toString() ? `${STATE_HANDLES_PATH}?${query.toString()}` : STATE_HANDLES_PATH;
2830
+ return this.request("GET", path);
2557
2831
  }
2558
- if (strategy === "summarize") {
2559
- throw new ConfigError("contextManagement 'summarize' is not implemented yet");
2832
+ async delete(stateId) {
2833
+ if (!stateId?.trim()) {
2834
+ throw new Error("state_id is required");
2835
+ }
2836
+ await this.request("DELETE", `${STATE_HANDLES_PATH}/${stateId}`);
2560
2837
  }
2561
- if (strategy !== "truncate") {
2562
- throw new ConfigError(`Unknown contextManagement strategy: ${strategy}`);
2838
+ };
2839
+
2840
+ // src/messages.ts
2841
+ var MESSAGES_PATH = "/messages";
2842
+ var MessagesClient = class {
2843
+ constructor(http, auth) {
2844
+ this.http = http;
2845
+ this.auth = auth;
2563
2846
  }
2564
- const modelId = options.model ?? defaultModel;
2565
- if (!modelId) {
2566
- throw new ConfigError(
2567
- "model is required for context management; set options.model or a session defaultModel"
2568
- );
2847
+ async request(method, path, body) {
2848
+ const auth = await this.auth.authForResponses();
2849
+ return this.http.json(path, {
2850
+ method,
2851
+ body,
2852
+ apiKey: auth.apiKey,
2853
+ accessToken: auth.accessToken
2854
+ });
2569
2855
  }
2570
- const budget = await resolveHistoryBudget(
2571
- modelId,
2572
- options,
2573
- resolveModelContext
2574
- );
2575
- const truncated = truncateMessagesByTokens(
2576
- messages,
2577
- budget.maxHistoryTokens
2578
- );
2579
- if (options.onContextTruncate && truncated.length < messages.length) {
2580
- const info = {
2581
- model: modelId,
2582
- originalMessages: messages.length,
2583
- keptMessages: truncated.length,
2584
- maxHistoryTokens: budget.maxHistoryTokens,
2585
- reservedOutputTokens: budget.reservedOutputTokens
2586
- };
2587
- options.onContextTruncate(info);
2856
+ async send(request) {
2857
+ if (!request?.to?.trim()) {
2858
+ throw new Error("to is required");
2859
+ }
2860
+ if (!request?.subject?.trim()) {
2861
+ throw new Error("subject is required");
2862
+ }
2863
+ if (request.body === void 0 || request.body === null) {
2864
+ throw new Error("body is required");
2865
+ }
2866
+ return this.request("POST", MESSAGES_PATH, request);
2588
2867
  }
2589
- return messagesToInput(truncated);
2590
- }
2591
- function messagesToInput(messages) {
2592
- return messages.map((m) => ({
2593
- type: m.type,
2594
- role: m.role,
2595
- content: m.content,
2596
- toolCalls: m.toolCalls,
2597
- toolCallId: m.toolCallId
2598
- }));
2599
- }
2600
- function truncateMessagesByTokens(messages, maxHistoryTokens) {
2601
- const maxTokens = normalizePositiveInt(maxHistoryTokens, "maxHistoryTokens");
2602
- if (messages.length === 0) return [];
2603
- const tokensByIndex = messages.map((msg) => estimateTokensForMessage(msg));
2604
- const systemIndices = messages.map((msg, idx) => msg.role === "system" ? idx : -1).filter((idx) => idx >= 0);
2605
- let selectedSystem = [...systemIndices];
2606
- let systemTokens = sumTokens(tokensByIndex, selectedSystem);
2607
- while (systemTokens > maxTokens && selectedSystem.length > 1) {
2608
- selectedSystem.shift();
2609
- systemTokens = sumTokens(tokensByIndex, selectedSystem);
2868
+ async list(options = {}) {
2869
+ const { to, threadId, unread, limit, offset } = options;
2870
+ if (!to?.trim() && !threadId?.trim()) {
2871
+ throw new Error("to or threadId is required");
2872
+ }
2873
+ if (limit !== void 0 && (limit <= 0 || limit > 200)) {
2874
+ throw new Error("limit must be between 1 and 200");
2875
+ }
2876
+ if (offset !== void 0 && offset < 0) {
2877
+ throw new Error("offset must be non-negative");
2878
+ }
2879
+ const query = new URLSearchParams();
2880
+ if (to?.trim()) {
2881
+ query.set("to", to.trim());
2882
+ }
2883
+ if (threadId?.trim()) {
2884
+ query.set("thread_id", threadId.trim());
2885
+ }
2886
+ if (unread !== void 0) {
2887
+ query.set("unread", String(unread));
2888
+ }
2889
+ if (limit !== void 0) {
2890
+ query.set("limit", String(limit));
2891
+ }
2892
+ if (offset !== void 0 && offset > 0) {
2893
+ query.set("offset", String(offset));
2894
+ }
2895
+ const path = query.toString() ? `${MESSAGES_PATH}?${query.toString()}` : MESSAGES_PATH;
2896
+ return this.request("GET", path);
2610
2897
  }
2611
- if (systemTokens > maxTokens) {
2612
- throw new ConfigError(
2613
- "maxHistoryTokens is too small to fit the latest system message"
2614
- );
2898
+ async get(messageId) {
2899
+ if (!messageId?.trim()) {
2900
+ throw new Error("messageId is required");
2901
+ }
2902
+ return this.request("GET", `${MESSAGES_PATH}/${messageId}`);
2615
2903
  }
2616
- const selected = new Set(selectedSystem);
2617
- let remaining = maxTokens - systemTokens;
2618
- for (let i = messages.length - 1; i >= 0; i -= 1) {
2619
- if (selected.has(i)) continue;
2620
- const tokens = tokensByIndex[i];
2621
- if (tokens <= remaining) {
2622
- selected.add(i);
2623
- remaining -= tokens;
2904
+ async markRead(messageId) {
2905
+ if (!messageId?.trim()) {
2906
+ throw new Error("messageId is required");
2624
2907
  }
2908
+ await this.request("POST", `${MESSAGES_PATH}/${messageId}/read`);
2625
2909
  }
2626
- const result = messages.filter((_, idx) => selected.has(idx));
2627
- if (result.length === 0) {
2628
- throw new ConfigError("No messages fit within maxHistoryTokens");
2910
+ };
2911
+
2912
+ // src/tool_loop.ts
2913
+ var DEFAULT_MAX_TURNS = 100;
2914
+ async function runToolLoop(config) {
2915
+ const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
2916
+ if (!Number.isFinite(maxTurns) || maxTurns <= 0) {
2917
+ throw new ConfigError("maxTurns must be a positive number");
2918
+ }
2919
+ const tools = config.tools ?? [];
2920
+ const history = config.input.slice();
2921
+ const usage = {
2922
+ inputTokens: 0,
2923
+ outputTokens: 0,
2924
+ totalTokens: 0,
2925
+ llmCalls: 0,
2926
+ toolCalls: 0
2927
+ };
2928
+ for (let turn = 0; turn < maxTurns; turn += 1) {
2929
+ let builder = config.client.new().input(history);
2930
+ if (tools.length > 0) {
2931
+ builder = builder.tools(tools);
2932
+ }
2933
+ if (config.buildRequest) {
2934
+ builder = config.buildRequest(builder);
2935
+ }
2936
+ const response = await config.client.create(
2937
+ builder.build(),
2938
+ config.requestOptions
2939
+ );
2940
+ usage.llmCalls += 1;
2941
+ usage.inputTokens += response.usage.inputTokens;
2942
+ usage.outputTokens += response.usage.outputTokens;
2943
+ usage.totalTokens += response.usage.totalTokens;
2944
+ const toolCalls = getAllToolCalls(response);
2945
+ if (toolCalls.length === 0) {
2946
+ const assistantText = getAssistantText(response);
2947
+ if (assistantText) {
2948
+ history.push(createAssistantMessage(assistantText));
2949
+ }
2950
+ return {
2951
+ status: "complete",
2952
+ output: assistantText,
2953
+ response,
2954
+ usage,
2955
+ input: history,
2956
+ turnsUsed: turn + 1
2957
+ };
2958
+ }
2959
+ usage.toolCalls += toolCalls.length;
2960
+ history.push(
2961
+ assistantMessageWithToolCalls(getAssistantText(response), toolCalls)
2962
+ );
2963
+ if (!config.registry) {
2964
+ return {
2965
+ status: "waiting_for_tools",
2966
+ pendingToolCalls: toolCalls,
2967
+ response,
2968
+ usage,
2969
+ input: history,
2970
+ turnsUsed: turn + 1
2971
+ };
2972
+ }
2973
+ const results = await config.registry.executeAll(toolCalls);
2974
+ history.push(...config.registry.resultsToMessages(results));
2629
2975
  }
2630
- return result;
2631
- }
2632
- function estimateTokens(text) {
2633
- return Math.ceil(text.length / CHARS_PER_TOKEN);
2976
+ throw new AgentMaxTurnsError(maxTurns);
2634
2977
  }
2635
- function isImagePart(part) {
2636
- if (typeof part !== "object" || part === null) return false;
2637
- const p = part;
2638
- return p.type === "image" || p.type === "image_url";
2978
+
2979
+ // src/sessions/types.ts
2980
+ function asSessionId(value) {
2981
+ return value;
2639
2982
  }
2640
- function estimateImageTokens(part) {
2641
- const detail = part.detail ?? "auto";
2642
- if (detail === "low") return IMAGE_TOKENS_LOW_DETAIL;
2643
- return IMAGE_TOKENS_HIGH_DETAIL;
2644
- }
2645
- function estimateTokensForMessage(message) {
2646
- const segments = [message.role];
2647
- let imageTokens = 0;
2648
- for (const part of message.content || []) {
2649
- if (part.type === "text" && part.text) {
2650
- segments.push(part.text);
2651
- } else if (isImagePart(part)) {
2652
- imageTokens += estimateImageTokens(part);
2653
- }
2654
- }
2655
- if (message.toolCalls) {
2656
- for (const call of message.toolCalls) {
2657
- if (call.function?.name) segments.push(call.function.name);
2658
- if (call.function?.arguments) segments.push(call.function.arguments);
2659
- }
2660
- }
2661
- if (message.toolCallId) {
2662
- segments.push(message.toolCallId);
2663
- }
2664
- const textTokens = estimateTokens(segments.join("\n"));
2665
- const toolOverhead = message.toolCalls ? message.toolCalls.length * TOOL_CALL_OVERHEAD_TOKENS : 0;
2666
- return textTokens + MESSAGE_OVERHEAD_TOKENS + toolOverhead + imageTokens;
2667
- }
2668
- function normalizePositiveInt(value, label) {
2669
- if (!Number.isFinite(value) || value <= 0) {
2670
- throw new ConfigError(`${label} must be a positive number`);
2671
- }
2672
- return Math.floor(value);
2673
- }
2674
- function sumTokens(tokensByIndex, indices) {
2675
- return indices.reduce((sum, idx) => sum + tokensByIndex[idx], 0);
2676
- }
2677
- async function resolveHistoryBudget(modelId, options, resolveModelContext) {
2678
- const reservedOutputTokens = options.reserveOutputTokens === void 0 ? void 0 : normalizeNonNegativeInt(
2679
- options.reserveOutputTokens,
2680
- "reserveOutputTokens"
2681
- );
2682
- if (options.maxHistoryTokens !== void 0) {
2683
- return {
2684
- maxHistoryTokens: normalizePositiveInt(
2685
- options.maxHistoryTokens,
2686
- "maxHistoryTokens"
2687
- ),
2688
- reservedOutputTokens
2689
- };
2690
- }
2691
- const model = await resolveModelContext(modelId);
2692
- if (!model) {
2693
- throw new ConfigError(
2694
- `Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
2695
- );
2696
- }
2697
- const contextWindow = normalizePositiveInt(model.contextWindow, "context_window");
2698
- const modelOutputTokens = model.maxOutputTokens === void 0 ? 0 : normalizeNonNegativeInt(model.maxOutputTokens, "max_output_tokens");
2699
- const effectiveReserve = reservedOutputTokens ?? modelOutputTokens;
2700
- const buffer = Math.max(
2701
- DEFAULT_CONTEXT_BUFFER_TOKENS,
2702
- Math.ceil(contextWindow * CONTEXT_BUFFER_RATIO)
2703
- );
2704
- const maxHistoryTokens = contextWindow - effectiveReserve - buffer;
2705
- if (maxHistoryTokens <= 0) {
2706
- throw new ConfigError(
2707
- "model context window is too small after reserving output tokens; set maxHistoryTokens explicitly"
2708
- );
2709
- }
2710
- return {
2711
- maxHistoryTokens,
2712
- reservedOutputTokens: effectiveReserve
2713
- };
2714
- }
2715
- function normalizeNonNegativeInt(value, label) {
2716
- if (!Number.isFinite(value) || value < 0) {
2717
- throw new ConfigError(`${label} must be a non-negative number`);
2718
- }
2719
- return Math.floor(value);
2720
- }
2721
- function getModelContextCacheEntry(client) {
2722
- const existing = modelContextCache.get(client);
2723
- if (existing) return existing;
2724
- const entry = { byId: /* @__PURE__ */ new Map() };
2725
- modelContextCache.set(client, entry);
2726
- return entry;
2727
- }
2728
- async function populateModelContextCache(client, entry) {
2729
- if (!entry.listPromise) {
2730
- entry.listPromise = (async () => {
2731
- const response = await client.http.json("/models");
2732
- for (const model of response.models) {
2733
- entry.byId.set(String(model.model_id), {
2734
- contextWindow: model.context_window,
2735
- maxOutputTokens: model.max_output_tokens
2736
- });
2737
- }
2738
- })().finally(() => {
2739
- entry.listPromise = void 0;
2740
- });
2741
- }
2742
- await entry.listPromise;
2743
- }
2744
-
2745
- // src/sessions/types.ts
2746
- function asSessionId(value) {
2747
- return value;
2748
- }
2749
- function generateSessionId() {
2750
- return crypto.randomUUID();
2983
+ function generateSessionId() {
2984
+ return crypto.randomUUID();
2751
2985
  }
2752
2986
 
2753
2987
  // src/sessions/stores/memory_store.ts
2754
- var MemorySessionStore = class {
2988
+ var MemoryConversationStore = class {
2755
2989
  constructor() {
2756
2990
  this.sessions = /* @__PURE__ */ new Map();
2757
2991
  }
@@ -2780,33 +3014,300 @@ var MemorySessionStore = class {
2780
3014
  return this.sessions.size;
2781
3015
  }
2782
3016
  };
2783
- function createMemorySessionStore() {
2784
- return new MemorySessionStore();
3017
+ function createMemoryConversationStore() {
3018
+ return new MemoryConversationStore();
3019
+ }
3020
+
3021
+ // src/sessions/stores/serialization.ts
3022
+ function serializeConversationState(state) {
3023
+ return {
3024
+ ...state,
3025
+ messages: state.messages.map((message) => ({
3026
+ ...message,
3027
+ createdAt: message.createdAt.toISOString()
3028
+ }))
3029
+ };
3030
+ }
3031
+ function deserializeConversationState(state) {
3032
+ return {
3033
+ ...state,
3034
+ messages: state.messages.map((message) => ({
3035
+ ...message,
3036
+ createdAt: new Date(message.createdAt)
3037
+ }))
3038
+ };
3039
+ }
3040
+
3041
+ // src/sessions/stores/file_store.ts
3042
+ var DEFAULT_SESSION_DIR = ".modelrelay/sessions";
3043
+ async function loadNodeDeps() {
3044
+ try {
3045
+ const fs = await import("fs/promises");
3046
+ const path = await import("path");
3047
+ const os = await import("os");
3048
+ return { fs, path, os };
3049
+ } catch (err) {
3050
+ throw new ConfigError("file persistence requires a Node.js-compatible runtime");
3051
+ }
3052
+ }
3053
+ var FileConversationStore = class {
3054
+ constructor(storagePath) {
3055
+ this.storagePath = storagePath;
3056
+ }
3057
+ async load(id) {
3058
+ const { fs, path, os } = await loadNodeDeps();
3059
+ const filePath = await this.resolveSessionPath(id, path, os);
3060
+ try {
3061
+ const raw = await fs.readFile(filePath, "utf8");
3062
+ const parsed = JSON.parse(raw);
3063
+ return deserializeConversationState(parsed);
3064
+ } catch (err) {
3065
+ if (isNotFoundError(err)) return null;
3066
+ throw err;
3067
+ }
3068
+ }
3069
+ async save(state) {
3070
+ const { fs, path, os } = await loadNodeDeps();
3071
+ const dirPath = await this.resolveSessionDir(path, os);
3072
+ await fs.mkdir(dirPath, { recursive: true, mode: 448 });
3073
+ const filePath = path.join(dirPath, `${state.id}.json`);
3074
+ const payload = JSON.stringify(serializeConversationState(state), null, 2);
3075
+ await fs.writeFile(filePath, payload, { mode: 384 });
3076
+ }
3077
+ async delete(id) {
3078
+ const { fs, path, os } = await loadNodeDeps();
3079
+ const filePath = await this.resolveSessionPath(id, path, os);
3080
+ try {
3081
+ await fs.unlink(filePath);
3082
+ } catch (err) {
3083
+ if (isNotFoundError(err)) return;
3084
+ throw err;
3085
+ }
3086
+ }
3087
+ async list() {
3088
+ const { fs, path, os } = await loadNodeDeps();
3089
+ const dirPath = await this.resolveSessionDir(path, os);
3090
+ try {
3091
+ const entries = await fs.readdir(dirPath);
3092
+ return entries.filter((entry) => path.extname(entry) === ".json").map((entry) => entry.replace(/\.json$/, ""));
3093
+ } catch (err) {
3094
+ if (isNotFoundError(err)) return [];
3095
+ throw err;
3096
+ }
3097
+ }
3098
+ async close() {
3099
+ }
3100
+ async resolveSessionPath(id, path, os) {
3101
+ const dirPath = await this.resolveSessionDir(path, os);
3102
+ return path.join(dirPath, `${id}.json`);
3103
+ }
3104
+ async resolveSessionDir(path, os) {
3105
+ if (this.storagePath && this.storagePath.trim()) {
3106
+ return this.storagePath;
3107
+ }
3108
+ return path.join(os.homedir(), DEFAULT_SESSION_DIR);
3109
+ }
3110
+ };
3111
+ function createFileConversationStore(storagePath) {
3112
+ return new FileConversationStore(storagePath);
3113
+ }
3114
+ function isNotFoundError(err) {
3115
+ return Boolean(
3116
+ err && typeof err === "object" && "code" in err && err.code === "ENOENT"
3117
+ );
3118
+ }
3119
+
3120
+ // src/sessions/stores/sqlite_store.ts
3121
+ var DEFAULT_DB_PATH = ".modelrelay/sessions.sqlite";
3122
+ async function loadNodeDeps2() {
3123
+ try {
3124
+ const path = await import("path");
3125
+ const os = await import("os");
3126
+ return { path, os };
3127
+ } catch (err) {
3128
+ throw new ConfigError("sqlite persistence requires a Node.js-compatible runtime");
3129
+ }
3130
+ }
3131
+ async function loadSqlite() {
3132
+ try {
3133
+ const mod = await import("better-sqlite3");
3134
+ const Database = mod.default ?? mod;
3135
+ if (typeof Database !== "function") {
3136
+ throw new Error("better-sqlite3 export missing");
3137
+ }
3138
+ return Database;
3139
+ } catch (err) {
3140
+ throw new ConfigError(
3141
+ "sqlite persistence requires the optional 'better-sqlite3' dependency"
3142
+ );
3143
+ }
3144
+ }
3145
+ var SqliteConversationStore = class {
3146
+ constructor(storagePath) {
3147
+ this.storagePath = storagePath;
3148
+ }
3149
+ async load(id) {
3150
+ const statements = await this.getStatements();
3151
+ const row = statements.get.get({ id });
3152
+ if (!row) return null;
3153
+ const parsed = {
3154
+ id: row.id,
3155
+ messages: JSON.parse(row.messages),
3156
+ artifacts: JSON.parse(row.artifacts),
3157
+ metadata: JSON.parse(row.metadata),
3158
+ createdAt: row.createdAt,
3159
+ updatedAt: row.updatedAt
3160
+ };
3161
+ return deserializeConversationState(parsed);
3162
+ }
3163
+ async save(state) {
3164
+ const statements = await this.getStatements();
3165
+ const payload = serializeConversationState(state);
3166
+ statements.save.run({
3167
+ id: payload.id,
3168
+ messages: JSON.stringify(payload.messages),
3169
+ artifacts: JSON.stringify(payload.artifacts ?? {}),
3170
+ metadata: JSON.stringify(payload.metadata ?? {}),
3171
+ created_at: payload.createdAt,
3172
+ updated_at: payload.updatedAt
3173
+ });
3174
+ }
3175
+ async delete(id) {
3176
+ const statements = await this.getStatements();
3177
+ statements.delete.run({ id });
3178
+ }
3179
+ async list() {
3180
+ const statements = await this.getStatements();
3181
+ const rows = statements.list.all();
3182
+ return rows.map((row) => row.id);
3183
+ }
3184
+ async close() {
3185
+ if (this.db) {
3186
+ this.db.close();
3187
+ this.db = void 0;
3188
+ this.statements = void 0;
3189
+ this.initPromise = void 0;
3190
+ }
3191
+ }
3192
+ async ensureInitialized() {
3193
+ if (this.db) return;
3194
+ if (!this.initPromise) {
3195
+ this.initPromise = this.initialize();
3196
+ }
3197
+ await this.initPromise;
3198
+ }
3199
+ async getStatements() {
3200
+ await this.ensureInitialized();
3201
+ if (!this.statements) {
3202
+ throw new Error("Database initialization failed");
3203
+ }
3204
+ return this.statements;
3205
+ }
3206
+ async initialize() {
3207
+ const { path, os } = await loadNodeDeps2();
3208
+ const Database = await loadSqlite();
3209
+ const dbPath = this.resolveDbPath(path, os);
3210
+ this.db = new Database(dbPath);
3211
+ this.db.exec(`
3212
+ CREATE TABLE IF NOT EXISTS conversations (
3213
+ id TEXT PRIMARY KEY,
3214
+ messages TEXT NOT NULL,
3215
+ artifacts TEXT NOT NULL,
3216
+ metadata TEXT NOT NULL,
3217
+ created_at TEXT NOT NULL,
3218
+ updated_at TEXT NOT NULL
3219
+ )
3220
+ `);
3221
+ this.statements = {
3222
+ get: this.db.prepare(
3223
+ "SELECT id, messages, artifacts, metadata, created_at as createdAt, updated_at as updatedAt FROM conversations WHERE id = @id"
3224
+ ),
3225
+ save: this.db.prepare(
3226
+ "INSERT INTO conversations (id, messages, artifacts, metadata, created_at, updated_at) VALUES (@id, @messages, @artifacts, @metadata, @created_at, @updated_at) ON CONFLICT(id) DO UPDATE SET messages = excluded.messages, artifacts = excluded.artifacts, metadata = excluded.metadata, updated_at = excluded.updated_at"
3227
+ ),
3228
+ delete: this.db.prepare("DELETE FROM conversations WHERE id = @id"),
3229
+ list: this.db.prepare("SELECT id FROM conversations ORDER BY id")
3230
+ };
3231
+ }
3232
+ resolveDbPath(path, os) {
3233
+ if (this.storagePath && this.storagePath.trim()) {
3234
+ return this.storagePath;
3235
+ }
3236
+ return path.join(os.homedir(), DEFAULT_DB_PATH);
3237
+ }
3238
+ };
3239
+ function createSqliteConversationStore(storagePath) {
3240
+ return new SqliteConversationStore(storagePath);
3241
+ }
3242
+
3243
+ // src/sessions/utils.ts
3244
+ function messagesToInput(messages) {
3245
+ return messages.map((m) => ({
3246
+ type: m.type,
3247
+ role: m.role,
3248
+ content: m.content,
3249
+ toolCalls: m.toolCalls,
3250
+ toolCallId: m.toolCallId
3251
+ }));
3252
+ }
3253
+ function mergeTools(defaults, overrides) {
3254
+ if (!defaults && !overrides) return void 0;
3255
+ if (!defaults) return overrides;
3256
+ if (!overrides) return defaults;
3257
+ const merged = /* @__PURE__ */ new Map();
3258
+ for (const tool of defaults) {
3259
+ if (tool.type === "function" && tool.function) {
3260
+ merged.set(tool.function.name, tool);
3261
+ }
3262
+ }
3263
+ for (const tool of overrides) {
3264
+ if (tool.type === "function" && tool.function) {
3265
+ merged.set(tool.function.name, tool);
3266
+ }
3267
+ }
3268
+ return Array.from(merged.values());
3269
+ }
3270
+ function emptyUsage() {
3271
+ return {
3272
+ inputTokens: 0,
3273
+ outputTokens: 0,
3274
+ totalTokens: 0,
3275
+ llmCalls: 0,
3276
+ toolCalls: 0
3277
+ };
3278
+ }
3279
+ function createRequestBuilder(config) {
3280
+ return (builder) => {
3281
+ let next = builder;
3282
+ if (config.model) {
3283
+ next = next.model(config.model);
3284
+ }
3285
+ if (config.provider) {
3286
+ next = next.provider(config.provider);
3287
+ }
3288
+ if (config.customerId) {
3289
+ next = next.customerId(config.customerId);
3290
+ }
3291
+ return next;
3292
+ };
2785
3293
  }
2786
3294
 
2787
3295
  // src/sessions/local_session.ts
3296
+ var DEFAULT_MAX_TURNS2 = 100;
2788
3297
  var LocalSession = class _LocalSession {
2789
3298
  constructor(client, store, options, existingState) {
2790
3299
  this.type = "local";
2791
3300
  this.messages = [];
2792
3301
  this.artifacts = /* @__PURE__ */ new Map();
2793
- this.nextSeq = 1;
2794
- this.currentEvents = [];
2795
- this.currentUsage = {
2796
- inputTokens: 0,
2797
- outputTokens: 0,
2798
- totalTokens: 0,
2799
- llmCalls: 0,
2800
- toolCalls: 0
2801
- };
2802
3302
  this.client = client;
2803
3303
  this.store = store;
2804
3304
  this.toolRegistry = options.toolRegistry;
3305
+ this.contextManager = options.contextManager;
2805
3306
  this.defaultModel = options.defaultModel;
2806
3307
  this.defaultProvider = options.defaultProvider;
2807
3308
  this.defaultTools = options.defaultTools;
3309
+ this.systemPrompt = options.systemPrompt;
2808
3310
  this.metadata = options.metadata || {};
2809
- this.resolveModelContext = createModelContextResolver(client);
2810
3311
  if (existingState) {
2811
3312
  this.id = existingState.id;
2812
3313
  this.messages = existingState.messages.map((m) => ({
@@ -2814,7 +3315,6 @@ var LocalSession = class _LocalSession {
2814
3315
  createdAt: new Date(m.createdAt)
2815
3316
  }));
2816
3317
  this.artifacts = new Map(Object.entries(existingState.artifacts));
2817
- this.nextSeq = this.messages.length + 1;
2818
3318
  this.createdAt = new Date(existingState.createdAt);
2819
3319
  this.updatedAt = new Date(existingState.updatedAt);
2820
3320
  } else {
@@ -2831,7 +3331,11 @@ var LocalSession = class _LocalSession {
2831
3331
  * @returns A new LocalSession instance
2832
3332
  */
2833
3333
  static create(client, options = {}) {
2834
- const store = createStore(options.persistence || "memory", options.storagePath);
3334
+ const store = createStore(
3335
+ options.conversationStore,
3336
+ options.persistence || "memory",
3337
+ options.storagePath
3338
+ );
2835
3339
  return new _LocalSession(client, store, options);
2836
3340
  }
2837
3341
  /**
@@ -2844,7 +3348,11 @@ var LocalSession = class _LocalSession {
2844
3348
  */
2845
3349
  static async resume(client, sessionId, options = {}) {
2846
3350
  const id = typeof sessionId === "string" ? asSessionId(sessionId) : sessionId;
2847
- const store = createStore(options.persistence || "memory", options.storagePath);
3351
+ const store = createStore(
3352
+ options.conversationStore,
3353
+ options.persistence || "memory",
3354
+ options.storagePath
3355
+ );
2848
3356
  const state = await store.load(id);
2849
3357
  if (!state) {
2850
3358
  await store.close();
@@ -2856,74 +3364,137 @@ var LocalSession = class _LocalSession {
2856
3364
  return this.messages;
2857
3365
  }
2858
3366
  async run(prompt, options = {}) {
2859
- const userMessage = this.addMessage({
2860
- type: "message",
2861
- role: "user",
2862
- content: [{ type: "text", text: prompt }]
2863
- });
2864
- this.currentEvents = [];
2865
- this.currentUsage = {
2866
- inputTokens: 0,
2867
- outputTokens: 0,
2868
- totalTokens: 0,
2869
- llmCalls: 0,
2870
- toolCalls: 0
2871
- };
2872
- this.currentRunId = void 0;
2873
- this.currentNodeId = void 0;
2874
- this.currentWaiting = void 0;
3367
+ this.pendingLoop = void 0;
3368
+ this.messages.push(buildMessage(createUserMessage(prompt), this.messages.length + 1));
3369
+ this.updatedAt = /* @__PURE__ */ new Date();
3370
+ const baseInput = messagesToInput(this.messages);
3371
+ const contextOptions = this.buildContextOptions(options);
2875
3372
  try {
2876
- const input = await this.buildInput(options);
3373
+ const prepared = await this.prepareInput(baseInput, contextOptions);
2877
3374
  const tools = mergeTools(this.defaultTools, options.tools);
2878
- const spec = {
2879
- kind: "workflow",
2880
- name: `session-${this.id}-turn-${this.nextSeq}`,
2881
- model: options.model || this.defaultModel,
2882
- nodes: [
2883
- {
2884
- id: "main",
2885
- type: "llm",
2886
- input,
3375
+ const modelId = options.model ?? this.defaultModel;
3376
+ const providerId = options.provider ?? this.defaultProvider;
3377
+ const requestOptions = options.signal ? { signal: options.signal } : {};
3378
+ const outcome = await runToolLoop({
3379
+ client: this.client.responses,
3380
+ input: prepared,
3381
+ tools,
3382
+ registry: this.toolRegistry,
3383
+ maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS2,
3384
+ requestOptions,
3385
+ buildRequest: createRequestBuilder({
3386
+ model: modelId,
3387
+ provider: providerId,
3388
+ customerId: options.customerId
3389
+ })
3390
+ });
3391
+ const cleanInput = stripSystemPrompt(outcome.input, this.systemPrompt);
3392
+ this.replaceHistory(cleanInput);
3393
+ await this.persist();
3394
+ const usage = outcome.usage;
3395
+ if (outcome.status === "waiting_for_tools") {
3396
+ const pendingRequestOptions = { ...requestOptions };
3397
+ delete pendingRequestOptions.signal;
3398
+ this.pendingLoop = {
3399
+ input: cleanInput,
3400
+ usage,
3401
+ remainingTurns: remainingTurns(
3402
+ options.maxTurns ?? DEFAULT_MAX_TURNS2,
3403
+ outcome.turnsUsed
3404
+ ),
3405
+ config: {
3406
+ model: modelId,
3407
+ provider: providerId,
2887
3408
  tools,
2888
- tool_execution: this.toolRegistry ? { mode: "client" } : void 0
3409
+ customerId: options.customerId,
3410
+ requestOptions: pendingRequestOptions,
3411
+ contextOptions
2889
3412
  }
2890
- ],
2891
- outputs: [{ name: "result", from: "main" }]
2892
- };
2893
- const run = await this.client.runs.create(spec, {
2894
- customerId: options.customerId
2895
- });
2896
- this.currentRunId = run.run_id;
2897
- return await this.processRunEvents(options.signal);
3413
+ };
3414
+ return {
3415
+ status: "waiting_for_tools",
3416
+ pendingTools: mapPendingToolCalls(outcome.pendingToolCalls),
3417
+ response: outcome.response,
3418
+ usage
3419
+ };
3420
+ }
3421
+ return {
3422
+ status: "complete",
3423
+ output: outcome.output,
3424
+ response: outcome.response,
3425
+ usage
3426
+ };
2898
3427
  } catch (err) {
2899
3428
  const error = err instanceof Error ? err : new Error(String(err));
2900
3429
  return {
2901
3430
  status: "error",
2902
3431
  error: error.message,
2903
- runId: this.currentRunId || parseRunId("unknown"),
2904
- usage: this.currentUsage,
2905
- events: this.currentEvents
3432
+ cause: error,
3433
+ usage: emptyUsage()
2906
3434
  };
2907
3435
  }
2908
3436
  }
2909
3437
  async submitToolResults(results) {
2910
- if (!this.currentRunId || !this.currentNodeId || !this.currentWaiting) {
3438
+ if (!this.pendingLoop) {
2911
3439
  throw new Error("No pending tool calls to submit results for");
2912
3440
  }
2913
- await this.client.runs.submitToolResults(this.currentRunId, {
2914
- node_id: this.currentNodeId,
2915
- step: this.currentWaiting.step,
2916
- request_id: this.currentWaiting.request_id,
2917
- results: results.map((r) => ({
2918
- tool_call: {
2919
- id: r.toolCallId,
2920
- name: r.toolName
2921
- },
2922
- output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
2923
- }))
3441
+ const pending = this.pendingLoop;
3442
+ this.pendingLoop = void 0;
3443
+ if (pending.remainingTurns <= 0) {
3444
+ throw new AgentMaxTurnsError(0);
3445
+ }
3446
+ const resultItems = results.map((result) => {
3447
+ const content = result.error ? `Error: ${result.error}` : typeof result.result === "string" ? result.result : JSON.stringify(result.result);
3448
+ return toolResultMessage(result.toolCallId, content);
2924
3449
  });
2925
- this.currentWaiting = void 0;
2926
- return await this.processRunEvents();
3450
+ const baseInput = [...pending.input, ...resultItems];
3451
+ try {
3452
+ const prepared = await this.prepareInput(baseInput, pending.config.contextOptions);
3453
+ const outcome = await runToolLoop({
3454
+ client: this.client.responses,
3455
+ input: prepared,
3456
+ tools: pending.config.tools,
3457
+ registry: this.toolRegistry,
3458
+ maxTurns: pending.remainingTurns,
3459
+ requestOptions: pending.config.requestOptions,
3460
+ buildRequest: createRequestBuilder(pending.config)
3461
+ });
3462
+ const cleanInput = stripSystemPrompt(outcome.input, this.systemPrompt);
3463
+ this.replaceHistory(cleanInput);
3464
+ await this.persist();
3465
+ const usage = mergeUsage(pending.usage, outcome.usage);
3466
+ if (outcome.status === "waiting_for_tools") {
3467
+ this.pendingLoop = {
3468
+ input: cleanInput,
3469
+ usage,
3470
+ remainingTurns: remainingTurns(
3471
+ pending.remainingTurns,
3472
+ outcome.turnsUsed
3473
+ ),
3474
+ config: pending.config
3475
+ };
3476
+ return {
3477
+ status: "waiting_for_tools",
3478
+ pendingTools: mapPendingToolCalls(outcome.pendingToolCalls),
3479
+ response: outcome.response,
3480
+ usage
3481
+ };
3482
+ }
3483
+ return {
3484
+ status: "complete",
3485
+ output: outcome.output,
3486
+ response: outcome.response,
3487
+ usage
3488
+ };
3489
+ } catch (err) {
3490
+ const error = err instanceof Error ? err : new Error(String(err));
3491
+ return {
3492
+ status: "error",
3493
+ error: error.message,
3494
+ cause: error,
3495
+ usage: pending.usage
3496
+ };
3497
+ }
2927
3498
  }
2928
3499
  getArtifacts() {
2929
3500
  return new Map(this.artifacts);
@@ -2934,28 +3505,6 @@ var LocalSession = class _LocalSession {
2934
3505
  }
2935
3506
  /**
2936
3507
  * Sync this local session's messages to a remote session.
2937
- *
2938
- * This uploads all local messages to the remote session, enabling
2939
- * cross-device access and server-side backup. Messages are synced
2940
- * in order and the remote session's history will contain all local
2941
- * messages after sync completes.
2942
- *
2943
- * @param remoteSession - The remote session to sync to
2944
- * @param options - Optional sync configuration
2945
- * @returns Sync result with message count
2946
- *
2947
- * @example
2948
- * ```typescript
2949
- * // Create local session and work offline
2950
- * const local = LocalSession.create(client, { ... });
2951
- * await local.run("Implement the feature");
2952
- *
2953
- * // Later, sync to remote for backup/sharing
2954
- * const remote = await RemoteSession.create(client);
2955
- * const result = await local.syncTo(remote, {
2956
- * onProgress: (synced, total) => console.log(`${synced}/${total}`),
2957
- * });
2958
- * ```
2959
3508
  */
2960
3509
  async syncTo(remoteSession, options = {}) {
2961
3510
  const { onProgress, signal } = options;
@@ -2997,150 +3546,35 @@ var LocalSession = class _LocalSession {
2997
3546
  // ============================================================================
2998
3547
  // Private Methods
2999
3548
  // ============================================================================
3000
- addMessage(input, runId) {
3001
- const message = {
3002
- ...input,
3003
- seq: this.nextSeq++,
3004
- createdAt: /* @__PURE__ */ new Date(),
3005
- runId
3006
- };
3007
- this.messages.push(message);
3008
- this.updatedAt = /* @__PURE__ */ new Date();
3009
- return message;
3010
- }
3011
- async buildInput(options) {
3012
- return buildSessionInputWithContext(
3013
- this.messages,
3014
- options,
3015
- this.defaultModel,
3016
- this.resolveModelContext
3017
- );
3018
- }
3019
- async processRunEvents(signal) {
3020
- if (!this.currentRunId) {
3021
- throw new Error("No current run");
3022
- }
3023
- const eventStream = await this.client.runs.events(this.currentRunId, {
3024
- afterSeq: this.currentEvents.length
3025
- });
3026
- for await (const event of eventStream) {
3027
- if (signal?.aborted) {
3028
- return {
3029
- status: "canceled",
3030
- runId: this.currentRunId,
3031
- usage: this.currentUsage,
3032
- events: this.currentEvents
3033
- };
3034
- }
3035
- this.currentEvents.push(event);
3036
- switch (event.type) {
3037
- case "node_llm_call":
3038
- this.currentUsage = {
3039
- ...this.currentUsage,
3040
- llmCalls: this.currentUsage.llmCalls + 1,
3041
- inputTokens: this.currentUsage.inputTokens + (event.llm_call.usage?.input_tokens || 0),
3042
- outputTokens: this.currentUsage.outputTokens + (event.llm_call.usage?.output_tokens || 0),
3043
- totalTokens: this.currentUsage.totalTokens + (event.llm_call.usage?.total_tokens || 0)
3044
- };
3045
- break;
3046
- case "node_tool_call":
3047
- this.currentUsage = {
3048
- ...this.currentUsage,
3049
- toolCalls: this.currentUsage.toolCalls + 1
3050
- };
3051
- break;
3052
- case "node_waiting":
3053
- this.currentNodeId = event.node_id;
3054
- this.currentWaiting = event.waiting;
3055
- if (this.toolRegistry) {
3056
- const results = await this.executeTools(event.waiting.pending_tool_calls);
3057
- return await this.submitToolResults(results);
3058
- }
3059
- return {
3060
- status: "waiting_for_tools",
3061
- pendingTools: event.waiting.pending_tool_calls.map((tc) => ({
3062
- toolCallId: tc.tool_call.id,
3063
- name: tc.tool_call.name,
3064
- arguments: tc.tool_call.arguments
3065
- })),
3066
- runId: this.currentRunId,
3067
- usage: this.currentUsage,
3068
- events: this.currentEvents
3069
- };
3070
- case "run_completed":
3071
- const runState = await this.client.runs.get(this.currentRunId);
3072
- const output = extractTextOutput(runState.outputs || {});
3073
- if (output) {
3074
- this.addMessage(
3075
- {
3076
- type: "message",
3077
- role: "assistant",
3078
- content: [{ type: "text", text: output }]
3079
- },
3080
- this.currentRunId
3081
- );
3082
- }
3083
- await this.persist();
3084
- return {
3085
- status: "complete",
3086
- output,
3087
- runId: this.currentRunId,
3088
- usage: this.currentUsage,
3089
- events: this.currentEvents
3090
- };
3091
- case "run_failed":
3092
- return {
3093
- status: "error",
3094
- error: event.error.message,
3095
- runId: this.currentRunId,
3096
- usage: this.currentUsage,
3097
- events: this.currentEvents
3098
- };
3099
- case "run_canceled":
3100
- return {
3101
- status: "canceled",
3102
- error: event.error.message,
3103
- runId: this.currentRunId,
3104
- usage: this.currentUsage,
3105
- events: this.currentEvents
3106
- };
3107
- }
3108
- }
3549
+ buildContextOptions(options) {
3550
+ if (!this.contextManager) return null;
3551
+ if (options.contextManagement === "none") return null;
3109
3552
  return {
3110
- status: "error",
3111
- error: "Run event stream ended unexpectedly",
3112
- runId: this.currentRunId,
3113
- usage: this.currentUsage,
3114
- events: this.currentEvents
3553
+ model: options.model ?? this.defaultModel,
3554
+ strategy: options.contextManagement,
3555
+ maxHistoryTokens: options.maxHistoryTokens,
3556
+ reserveOutputTokens: options.reserveOutputTokens,
3557
+ onTruncate: options.onContextTruncate
3115
3558
  };
3116
3559
  }
3117
- async executeTools(pendingTools) {
3118
- if (!this.toolRegistry) {
3119
- throw new Error("No tool registry configured");
3560
+ async prepareInput(input, contextOptions) {
3561
+ let prepared = input;
3562
+ if (this.systemPrompt) {
3563
+ prepared = [createSystemMessage(this.systemPrompt), ...prepared];
3120
3564
  }
3121
- const results = [];
3122
- for (const pending of pendingTools) {
3123
- try {
3124
- const result = await this.toolRegistry.execute({
3125
- id: pending.tool_call.id,
3126
- type: "function",
3127
- function: {
3128
- name: pending.tool_call.name,
3129
- arguments: pending.tool_call.arguments
3130
- }
3131
- });
3132
- results.push(result);
3133
- } catch (err) {
3134
- const error = err instanceof Error ? err : new Error(String(err));
3135
- results.push({
3136
- toolCallId: pending.tool_call.id,
3137
- toolName: pending.tool_call.name,
3138
- result: null,
3139
- error: error.message
3140
- });
3141
- }
3565
+ if (!this.contextManager || !contextOptions) {
3566
+ return prepared;
3142
3567
  }
3143
- return results;
3568
+ return this.contextManager.prepare(prepared, contextOptions);
3569
+ }
3570
+ replaceHistory(input) {
3571
+ const now = /* @__PURE__ */ new Date();
3572
+ this.messages = input.map((item, idx) => ({
3573
+ ...item,
3574
+ seq: idx + 1,
3575
+ createdAt: now
3576
+ }));
3577
+ this.updatedAt = now;
3144
3578
  }
3145
3579
  async persist() {
3146
3580
  const state = {
@@ -3157,143 +3591,357 @@ var LocalSession = class _LocalSession {
3157
3591
  await this.store.save(state);
3158
3592
  }
3159
3593
  };
3160
- function createStore(persistence, storagePath) {
3594
+ function createStore(custom, persistence, storagePath) {
3595
+ if (custom) {
3596
+ return custom;
3597
+ }
3161
3598
  switch (persistence) {
3162
3599
  case "memory":
3163
- return createMemorySessionStore();
3600
+ return createMemoryConversationStore();
3164
3601
  case "file":
3165
- throw new Error("File persistence not yet implemented");
3602
+ return createFileConversationStore(storagePath);
3166
3603
  case "sqlite":
3167
- throw new Error("SQLite persistence not yet implemented");
3604
+ return createSqliteConversationStore(storagePath);
3168
3605
  default:
3169
3606
  throw new Error(`Unknown persistence mode: ${persistence}`);
3170
3607
  }
3171
3608
  }
3172
- function mergeTools(defaults, overrides) {
3173
- if (!defaults && !overrides) return void 0;
3174
- if (!defaults) return overrides;
3175
- if (!overrides) return defaults;
3176
- const merged = /* @__PURE__ */ new Map();
3177
- for (const tool of defaults) {
3178
- if (tool.type === "function" && tool.function) {
3179
- merged.set(tool.function.name, tool);
3180
- }
3609
+ function buildMessage(item, seq) {
3610
+ return {
3611
+ ...item,
3612
+ seq,
3613
+ createdAt: /* @__PURE__ */ new Date()
3614
+ };
3615
+ }
3616
+ function stripSystemPrompt(input, systemPrompt) {
3617
+ if (!systemPrompt || input.length === 0) {
3618
+ return input;
3181
3619
  }
3182
- for (const tool of overrides) {
3183
- if (tool.type === "function" && tool.function) {
3184
- merged.set(tool.function.name, tool);
3185
- }
3620
+ const [first, ...rest] = input;
3621
+ if (first.role === "system" && first.content?.length === 1 && first.content[0].type === "text" && first.content[0].text === systemPrompt) {
3622
+ return rest;
3186
3623
  }
3187
- return Array.from(merged.values());
3624
+ return input;
3188
3625
  }
3189
- function isOutputMessage(item) {
3190
- return typeof item === "object" && item !== null && "type" in item && typeof item.type === "string";
3191
- }
3192
- function isContentPiece(c) {
3193
- return typeof c === "object" && c !== null && "type" in c && typeof c.type === "string";
3194
- }
3195
- function hasOutputArray(obj) {
3196
- return "output" in obj && Array.isArray(obj.output);
3197
- }
3198
- function hasContentArray(obj) {
3199
- return "content" in obj && Array.isArray(obj.content);
3200
- }
3201
- function extractTextOutput(outputs) {
3202
- const result = outputs.result;
3203
- if (typeof result === "string") return result;
3204
- if (result && typeof result === "object") {
3205
- if (hasOutputArray(result)) {
3206
- const textParts = result.output.filter(
3207
- (item) => isOutputMessage(item) && item.type === "message" && item.role === "assistant"
3208
- ).flatMap(
3209
- (item) => (item.content || []).filter((c) => isContentPiece(c) && c.type === "text").map((c) => c.text ?? "")
3210
- ).filter((text) => text.length > 0);
3211
- if (textParts.length > 0) {
3212
- return textParts.join("\n");
3213
- }
3214
- }
3215
- if (hasContentArray(result)) {
3216
- const textParts = result.content.filter((c) => isContentPiece(c) && c.type === "text").map((c) => c.text ?? "").filter((text) => text.length > 0);
3217
- if (textParts.length > 0) {
3218
- return textParts.join("\n");
3219
- }
3626
+ function mapPendingToolCalls(calls) {
3627
+ return calls.map((call) => {
3628
+ if (!call.function?.name) {
3629
+ throw new Error(`Tool call ${call.id} missing function name`);
3220
3630
  }
3631
+ return {
3632
+ toolCallId: call.id,
3633
+ name: call.function.name,
3634
+ arguments: call.function.arguments ?? "{}"
3635
+ };
3636
+ });
3637
+ }
3638
+ function remainingTurns(maxTurns, turnsUsed) {
3639
+ if (maxTurns === Number.MAX_SAFE_INTEGER) {
3640
+ return maxTurns;
3221
3641
  }
3222
- return void 0;
3642
+ return Math.max(0, maxTurns - turnsUsed);
3643
+ }
3644
+ function mergeUsage(base, add) {
3645
+ return {
3646
+ inputTokens: base.inputTokens + add.inputTokens,
3647
+ outputTokens: base.outputTokens + add.outputTokens,
3648
+ totalTokens: base.totalTokens + add.totalTokens,
3649
+ llmCalls: base.llmCalls + add.llmCalls,
3650
+ toolCalls: base.toolCalls + add.toolCalls
3651
+ };
3223
3652
  }
3224
3653
  function createLocalSession(client, options = {}) {
3225
3654
  return LocalSession.create(client, options);
3226
3655
  }
3227
3656
 
3228
- // src/sessions/remote_session.ts
3229
- var RemoteSession = class _RemoteSession {
3230
- constructor(client, http, sessionData, options = {}) {
3231
- this.type = "remote";
3232
- this.messages = [];
3233
- this.artifacts = /* @__PURE__ */ new Map();
3234
- this.nextSeq = 1;
3235
- this.pendingMessages = [];
3236
- this.currentEvents = [];
3237
- this.currentUsage = {
3238
- inputTokens: 0,
3239
- outputTokens: 0,
3240
- totalTokens: 0,
3241
- llmCalls: 0,
3242
- toolCalls: 0
3243
- };
3244
- this.client = client;
3245
- this.http = http;
3246
- this.id = asSessionId(sessionData.id);
3247
- this.metadata = sessionData.metadata;
3248
- this.customerId = sessionData.customer_id || options.customerId;
3249
- this.createdAt = new Date(sessionData.created_at);
3250
- this.updatedAt = new Date(sessionData.updated_at);
3251
- this.toolRegistry = options.toolRegistry;
3252
- this.defaultModel = options.defaultModel;
3253
- this.defaultProvider = options.defaultProvider;
3254
- this.defaultTools = options.defaultTools;
3255
- this.resolveModelContext = createModelContextResolver(client);
3256
- if ("messages" in sessionData && sessionData.messages) {
3257
- this.messages = sessionData.messages.map((m) => ({
3258
- type: "message",
3259
- role: m.role,
3260
- content: m.content,
3261
- seq: m.seq,
3262
- createdAt: new Date(m.created_at),
3263
- runId: m.run_id ? parseRunId(m.run_id) : void 0
3264
- }));
3265
- this.nextSeq = this.messages.length + 1;
3657
+ // src/context_manager.ts
3658
+ var DEFAULT_CONTEXT_BUFFER_TOKENS = 256;
3659
+ var CONTEXT_BUFFER_RATIO = 0.02;
3660
+ var MESSAGE_OVERHEAD_TOKENS = 6;
3661
+ var TOOL_CALL_OVERHEAD_TOKENS = 4;
3662
+ var CHARS_PER_TOKEN = 4;
3663
+ var IMAGE_TOKENS_LOW_DETAIL = 85;
3664
+ var IMAGE_TOKENS_HIGH_DETAIL = 1e3;
3665
+ var modelContextCache = /* @__PURE__ */ new WeakMap();
3666
+ function createModelContextResolver(client) {
3667
+ return async (modelId) => {
3668
+ const entry = getModelContextCacheEntry(client);
3669
+ const key = String(modelId);
3670
+ const cached = entry.byId.get(key);
3671
+ if (cached !== void 0) {
3672
+ return cached;
3673
+ }
3674
+ await populateModelContextCache(client, entry);
3675
+ const resolved = entry.byId.get(key);
3676
+ if (resolved === void 0) {
3677
+ throw new ConfigError(
3678
+ `Unknown model "${key}"; ensure the model exists in the ModelRelay catalog`
3679
+ );
3266
3680
  }
3681
+ return resolved;
3682
+ };
3683
+ }
3684
+ var ContextManager = class {
3685
+ constructor(resolveModelContext, defaults = {}) {
3686
+ this.resolveModelContext = resolveModelContext;
3687
+ this.defaults = defaults;
3688
+ }
3689
+ async prepare(input, options = {}) {
3690
+ const merged = {
3691
+ ...this.defaults,
3692
+ ...options
3693
+ };
3694
+ return prepareInputWithContext(input, merged, this.resolveModelContext);
3267
3695
  }
3268
- /**
3269
- * Create a new remote session on the server.
3270
- *
3271
- * @param client - ModelRelay client
3272
- * @param options - Session configuration
3273
- * @returns A new RemoteSession instance
3274
- */
3275
- static async create(client, options = {}) {
3276
- const http = getHTTPClient(client);
3277
- const response = await http.request("/sessions", {
3278
- method: "POST",
3279
- body: {
3280
- customer_id: options.customerId,
3281
- metadata: options.metadata || {}
3282
- }
3283
- });
3284
- const data = await response.json();
3285
- return new _RemoteSession(client, http, data, options);
3696
+ };
3697
+ async function prepareInputWithContext(input, options, resolveModelContext) {
3698
+ const strategy = options.strategy ?? "truncate";
3699
+ if (strategy === "summarize") {
3700
+ throw new ConfigError("context management 'summarize' is not implemented yet");
3286
3701
  }
3287
- /**
3288
- * Get an existing remote session by ID.
3289
- *
3290
- * @param client - ModelRelay client
3702
+ if (strategy !== "truncate") {
3703
+ throw new ConfigError(`Unknown context management strategy: ${strategy}`);
3704
+ }
3705
+ const budget = await resolveHistoryBudget(
3706
+ options.model,
3707
+ options,
3708
+ resolveModelContext
3709
+ );
3710
+ const truncated = truncateInputByTokens(input, budget.maxHistoryTokens);
3711
+ if (options.onTruncate && truncated.length < input.length) {
3712
+ if (!options.model) {
3713
+ throw new ConfigError(
3714
+ "model is required for context management; set options.model"
3715
+ );
3716
+ }
3717
+ const info = {
3718
+ model: options.model,
3719
+ originalMessages: input.length,
3720
+ keptMessages: truncated.length,
3721
+ maxHistoryTokens: budget.maxHistoryTokens,
3722
+ reservedOutputTokens: budget.reservedOutputTokens
3723
+ };
3724
+ options.onTruncate(info);
3725
+ }
3726
+ return truncated;
3727
+ }
3728
+ function truncateInputByTokens(input, maxHistoryTokens) {
3729
+ const maxTokens = normalizePositiveInt(maxHistoryTokens, "maxHistoryTokens");
3730
+ if (input.length === 0) return [];
3731
+ const tokensByIndex = input.map((msg) => estimateTokensForMessage(msg));
3732
+ const systemIndices = input.map((msg, idx) => msg.role === "system" ? idx : -1).filter((idx) => idx >= 0);
3733
+ let selectedSystem = [...systemIndices];
3734
+ let systemTokens = sumTokens(tokensByIndex, selectedSystem);
3735
+ while (systemTokens > maxTokens && selectedSystem.length > 1) {
3736
+ selectedSystem.shift();
3737
+ systemTokens = sumTokens(tokensByIndex, selectedSystem);
3738
+ }
3739
+ if (systemTokens > maxTokens) {
3740
+ throw new ConfigError(
3741
+ "maxHistoryTokens is too small to fit the latest system message"
3742
+ );
3743
+ }
3744
+ const selected = new Set(selectedSystem);
3745
+ let remaining = maxTokens - systemTokens;
3746
+ for (let i = input.length - 1; i >= 0; i -= 1) {
3747
+ if (selected.has(i)) continue;
3748
+ const tokens = tokensByIndex[i];
3749
+ if (tokens <= remaining) {
3750
+ selected.add(i);
3751
+ remaining -= tokens;
3752
+ }
3753
+ }
3754
+ const result = input.filter((_, idx) => selected.has(idx));
3755
+ if (result.length === 0) {
3756
+ throw new ConfigError("No messages fit within maxHistoryTokens");
3757
+ }
3758
+ return result;
3759
+ }
3760
+ function estimateTokens(text) {
3761
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
3762
+ }
3763
+ function isImagePart(part) {
3764
+ if (typeof part !== "object" || part === null) return false;
3765
+ const p = part;
3766
+ return p.type === "image" || p.type === "image_url";
3767
+ }
3768
+ function estimateImageTokens(part) {
3769
+ const detail = part.detail ?? "auto";
3770
+ if (detail === "low") return IMAGE_TOKENS_LOW_DETAIL;
3771
+ return IMAGE_TOKENS_HIGH_DETAIL;
3772
+ }
3773
+ function estimateTokensForMessage(message) {
3774
+ const segments = [message.role];
3775
+ let imageTokens = 0;
3776
+ for (const part of message.content || []) {
3777
+ if (part.type === "text" && part.text) {
3778
+ segments.push(part.text);
3779
+ } else if (isImagePart(part)) {
3780
+ imageTokens += estimateImageTokens(part);
3781
+ }
3782
+ }
3783
+ if (message.toolCalls) {
3784
+ for (const call of message.toolCalls) {
3785
+ if (call.function?.name) segments.push(call.function.name);
3786
+ if (call.function?.arguments) segments.push(call.function.arguments);
3787
+ }
3788
+ }
3789
+ if (message.toolCallId) {
3790
+ segments.push(message.toolCallId);
3791
+ }
3792
+ const textTokens = estimateTokens(segments.join("\n"));
3793
+ const toolOverhead = message.toolCalls ? message.toolCalls.length * TOOL_CALL_OVERHEAD_TOKENS : 0;
3794
+ return textTokens + MESSAGE_OVERHEAD_TOKENS + toolOverhead + imageTokens;
3795
+ }
3796
+ function normalizePositiveInt(value, label) {
3797
+ if (!Number.isFinite(value) || value <= 0) {
3798
+ throw new ConfigError(`${label} must be a positive number`);
3799
+ }
3800
+ return Math.floor(value);
3801
+ }
3802
+ function sumTokens(tokensByIndex, indices) {
3803
+ return indices.reduce((sum, idx) => sum + tokensByIndex[idx], 0);
3804
+ }
3805
+ async function resolveHistoryBudget(modelId, options, resolveModelContext) {
3806
+ const reservedOutputTokens = options.reserveOutputTokens === void 0 ? void 0 : normalizeNonNegativeInt(
3807
+ options.reserveOutputTokens,
3808
+ "reserveOutputTokens"
3809
+ );
3810
+ if (options.maxHistoryTokens !== void 0) {
3811
+ return {
3812
+ maxHistoryTokens: normalizePositiveInt(
3813
+ options.maxHistoryTokens,
3814
+ "maxHistoryTokens"
3815
+ ),
3816
+ reservedOutputTokens
3817
+ };
3818
+ }
3819
+ if (!modelId) {
3820
+ throw new ConfigError(
3821
+ "model is required for context management when maxHistoryTokens is not set"
3822
+ );
3823
+ }
3824
+ const model = await resolveModelContext(modelId);
3825
+ if (!model) {
3826
+ throw new ConfigError(
3827
+ `Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
3828
+ );
3829
+ }
3830
+ const contextWindow = normalizePositiveInt(model.contextWindow, "context_window");
3831
+ const modelOutputTokens = model.maxOutputTokens === void 0 ? 0 : normalizeNonNegativeInt(model.maxOutputTokens, "max_output_tokens");
3832
+ const effectiveReserve = reservedOutputTokens ?? modelOutputTokens;
3833
+ const buffer = Math.max(
3834
+ DEFAULT_CONTEXT_BUFFER_TOKENS,
3835
+ Math.ceil(contextWindow * CONTEXT_BUFFER_RATIO)
3836
+ );
3837
+ const maxHistoryTokens = contextWindow - effectiveReserve - buffer;
3838
+ if (maxHistoryTokens <= 0) {
3839
+ throw new ConfigError(
3840
+ "model context window is too small after reserving output tokens; set maxHistoryTokens explicitly"
3841
+ );
3842
+ }
3843
+ return {
3844
+ maxHistoryTokens,
3845
+ reservedOutputTokens: effectiveReserve
3846
+ };
3847
+ }
3848
+ function normalizeNonNegativeInt(value, label) {
3849
+ if (!Number.isFinite(value) || value < 0) {
3850
+ throw new ConfigError(`${label} must be a non-negative number`);
3851
+ }
3852
+ return Math.floor(value);
3853
+ }
3854
+ function getModelContextCacheEntry(client) {
3855
+ const existing = modelContextCache.get(client);
3856
+ if (existing) return existing;
3857
+ const entry = { byId: /* @__PURE__ */ new Map() };
3858
+ modelContextCache.set(client, entry);
3859
+ return entry;
3860
+ }
3861
+ async function populateModelContextCache(client, entry) {
3862
+ if (!entry.listPromise) {
3863
+ entry.listPromise = (async () => {
3864
+ const response = await client.http.json("/models");
3865
+ for (const model of response.models) {
3866
+ entry.byId.set(model.model_id, {
3867
+ contextWindow: model.context_window,
3868
+ maxOutputTokens: model.max_output_tokens ?? void 0
3869
+ });
3870
+ }
3871
+ })();
3872
+ }
3873
+ await entry.listPromise;
3874
+ }
3875
+
3876
+ // src/sessions/remote_session.ts
3877
+ var RemoteSession = class _RemoteSession {
3878
+ constructor(client, http, sessionData, options = {}) {
3879
+ this.type = "remote";
3880
+ this.messages = [];
3881
+ this.artifacts = /* @__PURE__ */ new Map();
3882
+ this.nextSeq = 1;
3883
+ this.pendingMessages = [];
3884
+ this.currentEvents = [];
3885
+ this.currentUsage = {
3886
+ inputTokens: 0,
3887
+ outputTokens: 0,
3888
+ totalTokens: 0,
3889
+ llmCalls: 0,
3890
+ toolCalls: 0
3891
+ };
3892
+ this.client = client;
3893
+ this.http = http;
3894
+ this.id = asSessionId(sessionData.id);
3895
+ this.metadata = sessionData.metadata;
3896
+ this.customerId = sessionData.customer_id || options.customerId;
3897
+ this.createdAt = new Date(sessionData.created_at);
3898
+ this.updatedAt = new Date(sessionData.updated_at);
3899
+ this.toolRegistry = options.toolRegistry;
3900
+ this.defaultModel = options.defaultModel;
3901
+ this.defaultProvider = options.defaultProvider;
3902
+ this.defaultTools = options.defaultTools;
3903
+ this.resolveModelContext = createModelContextResolver(client);
3904
+ if ("messages" in sessionData && sessionData.messages) {
3905
+ this.messages = sessionData.messages.map((m) => ({
3906
+ type: "message",
3907
+ role: m.role,
3908
+ content: m.content,
3909
+ seq: m.seq,
3910
+ createdAt: new Date(m.created_at),
3911
+ runId: m.run_id ? parseRunId(m.run_id) : void 0
3912
+ }));
3913
+ this.nextSeq = this.messages.length + 1;
3914
+ }
3915
+ }
3916
+ /**
3917
+ * Create a new remote session on the server.
3918
+ *
3919
+ * @param client - ModelRelay client
3920
+ * @param options - Session configuration
3921
+ * @returns A new RemoteSession instance
3922
+ */
3923
+ static async create(client, options = {}) {
3924
+ const http = client.http;
3925
+ const response = await http.request("/sessions", {
3926
+ method: "POST",
3927
+ body: {
3928
+ customer_id: options.customerId,
3929
+ metadata: options.metadata || {}
3930
+ }
3931
+ });
3932
+ const data = await response.json();
3933
+ return new _RemoteSession(client, http, data, options);
3934
+ }
3935
+ /**
3936
+ * Get an existing remote session by ID.
3937
+ *
3938
+ * @param client - ModelRelay client
3291
3939
  * @param sessionId - ID of the session to retrieve
3292
3940
  * @param options - Optional configuration (toolRegistry, defaults)
3293
3941
  * @returns The RemoteSession instance
3294
3942
  */
3295
3943
  static async get(client, sessionId, options = {}) {
3296
- const http = getHTTPClient(client);
3944
+ const http = client.http;
3297
3945
  const id = typeof sessionId === "string" ? sessionId : String(sessionId);
3298
3946
  const response = await http.request(`/sessions/${id}`, {
3299
3947
  method: "GET"
@@ -3309,7 +3957,7 @@ var RemoteSession = class _RemoteSession {
3309
3957
  * @returns Paginated list of session info
3310
3958
  */
3311
3959
  static async list(client, options = {}) {
3312
- const http = getHTTPClient(client);
3960
+ const http = client.http;
3313
3961
  const params = new URLSearchParams();
3314
3962
  if (options.limit) params.set("limit", String(options.limit));
3315
3963
  if (options.offset) params.set("offset", String(options.offset));
@@ -3337,7 +3985,7 @@ var RemoteSession = class _RemoteSession {
3337
3985
  * @param sessionId - ID of the session to delete
3338
3986
  */
3339
3987
  static async delete(client, sessionId) {
3340
- const http = getHTTPClient(client);
3988
+ const http = client.http;
3341
3989
  const id = typeof sessionId === "string" ? sessionId : String(sessionId);
3342
3990
  await http.request(`/sessions/${id}`, {
3343
3991
  method: "DELETE"
@@ -3364,21 +4012,22 @@ var RemoteSession = class _RemoteSession {
3364
4012
  this.resetRunState();
3365
4013
  try {
3366
4014
  const input = await this.buildInput(options);
3367
- const tools = mergeTools2(this.defaultTools, options.tools);
4015
+ const tools = mergeTools(this.defaultTools, options.tools);
4016
+ const mainNodeId = parseNodeId("main");
3368
4017
  const spec = {
3369
4018
  kind: "workflow",
3370
4019
  name: `session-${this.id}-turn-${this.nextSeq}`,
3371
4020
  model: options.model || this.defaultModel,
3372
4021
  nodes: [
3373
4022
  {
3374
- id: "main",
4023
+ id: mainNodeId,
3375
4024
  type: "llm",
3376
4025
  input,
3377
4026
  tools,
3378
4027
  tool_execution: this.toolRegistry ? { mode: "client" } : void 0
3379
4028
  }
3380
4029
  ],
3381
- outputs: [{ name: "result", from: "main" }]
4030
+ outputs: [{ name: parseOutputName("result"), from: mainNodeId }]
3382
4031
  };
3383
4032
  const run = await this.client.runs.create(spec, {
3384
4033
  customerId: options.customerId || this.customerId,
@@ -3391,7 +4040,8 @@ var RemoteSession = class _RemoteSession {
3391
4040
  return {
3392
4041
  status: "error",
3393
4042
  error: error.message,
3394
- runId: this.currentRunId || parseRunId("unknown"),
4043
+ cause: error,
4044
+ runId: this.currentRunId,
3395
4045
  usage: { ...this.currentUsage },
3396
4046
  events: [...this.currentEvents]
3397
4047
  };
@@ -3470,10 +4120,25 @@ var RemoteSession = class _RemoteSession {
3470
4120
  return message;
3471
4121
  }
3472
4122
  async buildInput(options) {
3473
- return buildSessionInputWithContext(
3474
- this.messages,
3475
- options,
3476
- this.defaultModel,
4123
+ const baseInput = messagesToInput(this.messages);
4124
+ if (!options.contextManagement || options.contextManagement === "none") {
4125
+ return baseInput;
4126
+ }
4127
+ const modelId = options.model ?? this.defaultModel;
4128
+ if (!modelId) {
4129
+ throw new ConfigError(
4130
+ "model is required for context management; set options.model or a session defaultModel"
4131
+ );
4132
+ }
4133
+ return prepareInputWithContext(
4134
+ baseInput,
4135
+ {
4136
+ model: modelId,
4137
+ strategy: options.contextManagement,
4138
+ maxHistoryTokens: options.maxHistoryTokens,
4139
+ reserveOutputTokens: options.reserveOutputTokens,
4140
+ onTruncate: options.onContextTruncate
4141
+ },
3477
4142
  this.resolveModelContext
3478
4143
  );
3479
4144
  }
@@ -3482,13 +4147,7 @@ var RemoteSession = class _RemoteSession {
3482
4147
  this.currentNodeId = void 0;
3483
4148
  this.currentWaiting = void 0;
3484
4149
  this.currentEvents = [];
3485
- this.currentUsage = {
3486
- inputTokens: 0,
3487
- outputTokens: 0,
3488
- totalTokens: 0,
3489
- llmCalls: 0,
3490
- toolCalls: 0
3491
- };
4150
+ this.currentUsage = emptyUsage();
3492
4151
  }
3493
4152
  async processRunEvents(signal) {
3494
4153
  if (!this.currentRunId) {
@@ -3727,26 +4386,6 @@ var RemoteSession = class _RemoteSession {
3727
4386
  return text.trim() ? text : void 0;
3728
4387
  }
3729
4388
  };
3730
- function getHTTPClient(client) {
3731
- return client.http;
3732
- }
3733
- function mergeTools2(defaults, overrides) {
3734
- if (!defaults && !overrides) return void 0;
3735
- if (!defaults) return overrides;
3736
- if (!overrides) return defaults;
3737
- const merged = /* @__PURE__ */ new Map();
3738
- for (const tool of defaults) {
3739
- if (tool.type === "function" && tool.function) {
3740
- merged.set(tool.function.name, tool);
3741
- }
3742
- }
3743
- for (const tool of overrides) {
3744
- if (tool.type === "function" && tool.function) {
3745
- merged.set(tool.function.name, tool);
3746
- }
3747
- }
3748
- return Array.from(merged.values());
3749
- }
3750
4389
 
3751
4390
  // src/sessions/client.ts
3752
4391
  var SessionsClient = class {
@@ -3863,111 +4502,1487 @@ var SessionsClient = class {
3863
4502
  customerId: options.customerId
3864
4503
  });
3865
4504
  }
3866
- /**
3867
- * Delete a remote session.
3868
- *
3869
- * Requires a secret key.
3870
- *
3871
- * @param sessionId - ID of the session to delete
3872
- *
3873
- * @example
3874
- * ```typescript
3875
- * await client.sessions.delete("session-id");
3876
- * ```
3877
- */
3878
- async delete(sessionId) {
3879
- return RemoteSession.delete(this.modelRelay, sessionId);
4505
+ /**
4506
+ * Delete a remote session.
4507
+ *
4508
+ * Requires a secret key.
4509
+ *
4510
+ * @param sessionId - ID of the session to delete
4511
+ *
4512
+ * @example
4513
+ * ```typescript
4514
+ * await client.sessions.delete("session-id");
4515
+ * ```
4516
+ */
4517
+ async delete(sessionId) {
4518
+ return RemoteSession.delete(this.modelRelay, sessionId);
4519
+ }
4520
+ };
4521
+
4522
+ // src/tiers.ts
4523
+ function defaultTierModelId(tier) {
4524
+ const def = tier.models.find((m) => m.is_default);
4525
+ if (def) return def.model_id;
4526
+ if (tier.models.length === 1) return tier.models[0].model_id;
4527
+ return void 0;
4528
+ }
4529
+ var TiersClient = class {
4530
+ constructor(http, cfg) {
4531
+ this.http = http;
4532
+ this.apiKey = cfg.apiKey ? parseApiKey(cfg.apiKey) : void 0;
4533
+ this.hasSecretKey = this.apiKey ? isSecretKey(this.apiKey) : false;
4534
+ this.hasAccessToken = !!cfg.accessToken?.trim();
4535
+ }
4536
+ ensureAuth() {
4537
+ if (!this.apiKey && !this.hasAccessToken) {
4538
+ throw new ConfigError(
4539
+ "API key (mr_sk_*) or bearer token required for tier operations"
4540
+ );
4541
+ }
4542
+ }
4543
+ ensureSecretKey() {
4544
+ if (!this.apiKey || !this.hasSecretKey) {
4545
+ throw new ConfigError(
4546
+ "Secret key (mr_sk_*) required for checkout operations"
4547
+ );
4548
+ }
4549
+ }
4550
+ /**
4551
+ * List all tiers in the project.
4552
+ */
4553
+ async list() {
4554
+ this.ensureAuth();
4555
+ const response = await this.http.json("/tiers", {
4556
+ method: "GET",
4557
+ apiKey: this.apiKey
4558
+ });
4559
+ return response.tiers;
4560
+ }
4561
+ /**
4562
+ * Get a tier by ID.
4563
+ */
4564
+ async get(tierId) {
4565
+ this.ensureAuth();
4566
+ if (!tierId?.trim()) {
4567
+ throw new ConfigError("tierId is required");
4568
+ }
4569
+ const response = await this.http.json(`/tiers/${tierId}`, {
4570
+ method: "GET",
4571
+ apiKey: this.apiKey
4572
+ });
4573
+ return response.tier;
4574
+ }
4575
+ /**
4576
+ * Create a Stripe checkout session for a tier (Stripe-first flow).
4577
+ *
4578
+ * This enables users to subscribe before authenticating. Stripe collects
4579
+ * the customer's email during checkout. After checkout completes, a
4580
+ * customer record is created with the email from Stripe. Your backend
4581
+ * can map it to your app user and mint customer tokens as needed.
4582
+ *
4583
+ * Requires a secret key (mr_sk_*).
4584
+ *
4585
+ * @param tierId - The tier ID to create a checkout session for
4586
+ * @param request - Checkout session request with redirect URLs
4587
+ * @returns Checkout session with Stripe URL
4588
+ */
4589
+ async checkout(tierId, request) {
4590
+ this.ensureSecretKey();
4591
+ if (!tierId?.trim()) {
4592
+ throw new ConfigError("tierId is required");
4593
+ }
4594
+ if (!request.success_url?.trim()) {
4595
+ throw new ConfigError("success_url is required");
4596
+ }
4597
+ if (!request.cancel_url?.trim()) {
4598
+ throw new ConfigError("cancel_url is required");
4599
+ }
4600
+ return await this.http.json(
4601
+ `/tiers/${tierId}/checkout`,
4602
+ {
4603
+ method: "POST",
4604
+ apiKey: this.apiKey,
4605
+ body: request
4606
+ }
4607
+ );
4608
+ }
4609
+ };
4610
+
4611
+ // src/plugins.ts
4612
+ import { z as z2 } from "zod";
4613
+
4614
+ // src/tools_user_ask.ts
4615
+ var USER_ASK_TOOL_NAME = "user_ask";
4616
+ var userAskSchema = {
4617
+ type: "object",
4618
+ properties: {
4619
+ question: {
4620
+ type: "string",
4621
+ minLength: 1,
4622
+ description: "The question to ask the user."
4623
+ },
4624
+ options: {
4625
+ type: "array",
4626
+ items: {
4627
+ type: "object",
4628
+ properties: {
4629
+ label: { type: "string", minLength: 1 },
4630
+ description: { type: "string" }
4631
+ },
4632
+ required: ["label"]
4633
+ },
4634
+ description: "Optional multiple choice options."
4635
+ },
4636
+ allow_freeform: {
4637
+ type: "boolean",
4638
+ default: true,
4639
+ description: "Allow user to type a custom response."
4640
+ }
4641
+ },
4642
+ required: ["question"]
4643
+ };
4644
+ function createUserAskTool() {
4645
+ return createFunctionTool(
4646
+ USER_ASK_TOOL_NAME,
4647
+ "Ask the user a clarifying question.",
4648
+ userAskSchema
4649
+ );
4650
+ }
4651
+ function isUserAskToolCall(call) {
4652
+ return call.type === "function" && call.function?.name === USER_ASK_TOOL_NAME;
4653
+ }
4654
+ function parseUserAskArgs(call) {
4655
+ const raw = getToolArgsRaw(call);
4656
+ if (!raw) {
4657
+ throw new ToolArgumentError({
4658
+ message: "user_ask arguments required",
4659
+ toolCallId: call.id,
4660
+ toolName: USER_ASK_TOOL_NAME,
4661
+ rawArguments: raw
4662
+ });
4663
+ }
4664
+ let parsed;
4665
+ try {
4666
+ parsed = JSON.parse(raw);
4667
+ } catch (err) {
4668
+ throw new ToolArgumentError({
4669
+ message: "user_ask arguments must be valid JSON",
4670
+ toolCallId: call.id,
4671
+ toolName: USER_ASK_TOOL_NAME,
4672
+ rawArguments: raw,
4673
+ cause: err
4674
+ });
4675
+ }
4676
+ const question = parsed.question?.trim?.() ?? "";
4677
+ if (!question) {
4678
+ throw new ToolArgumentError({
4679
+ message: "user_ask question required",
4680
+ toolCallId: call.id,
4681
+ toolName: USER_ASK_TOOL_NAME,
4682
+ rawArguments: raw
4683
+ });
4684
+ }
4685
+ if (parsed.options?.length) {
4686
+ for (const opt of parsed.options) {
4687
+ if (!opt?.label?.trim?.()) {
4688
+ throw new ToolArgumentError({
4689
+ message: "user_ask options require label",
4690
+ toolCallId: call.id,
4691
+ toolName: USER_ASK_TOOL_NAME,
4692
+ rawArguments: raw
4693
+ });
4694
+ }
4695
+ }
4696
+ }
4697
+ return {
4698
+ question,
4699
+ options: parsed.options,
4700
+ allow_freeform: parsed.allow_freeform
4701
+ };
4702
+ }
4703
+ function serializeUserAskResult(result) {
4704
+ const answer = result.answer?.trim?.() ?? "";
4705
+ if (!answer) {
4706
+ throw new ToolArgumentError({
4707
+ message: "user_ask answer required",
4708
+ toolCallId: "",
4709
+ toolName: USER_ASK_TOOL_NAME,
4710
+ rawArguments: ""
4711
+ });
4712
+ }
4713
+ return JSON.stringify({ answer, is_freeform: result.is_freeform });
4714
+ }
4715
+ function userAskResultFreeform(answer) {
4716
+ return serializeUserAskResult({ answer, is_freeform: true });
4717
+ }
4718
+ function userAskResultChoice(answer) {
4719
+ return serializeUserAskResult({ answer, is_freeform: false });
4720
+ }
4721
+
4722
+ // src/tools_runner.ts
4723
+ var ToolRunner = class {
4724
+ constructor(options) {
4725
+ this.registry = options.registry;
4726
+ this.runsClient = options.runsClient;
4727
+ this.customerId = options.customerId;
4728
+ this.onBeforeExecute = options.onBeforeExecute;
4729
+ this.onAfterExecute = options.onAfterExecute;
4730
+ this.onSubmitted = options.onSubmitted;
4731
+ this.onUserAsk = options.onUserAsk;
4732
+ this.onError = options.onError;
4733
+ }
4734
+ /**
4735
+ * Handles a node_waiting event by executing tools and submitting results.
4736
+ *
4737
+ * @param runId - The run ID
4738
+ * @param nodeId - The node ID that is waiting
4739
+ * @param waiting - The waiting state with pending tool calls
4740
+ * @returns The submission response with accepted count and new status
4741
+ *
4742
+ * @example
4743
+ * ```typescript
4744
+ * for await (const event of client.runs.events(runId)) {
4745
+ * if (event.type === "node_waiting") {
4746
+ * const result = await runner.handleNodeWaiting(
4747
+ * runId,
4748
+ * event.node_id,
4749
+ * event.waiting
4750
+ * );
4751
+ * console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
4752
+ * }
4753
+ * }
4754
+ * ```
4755
+ */
4756
+ async handleNodeWaiting(runId, nodeId, waiting) {
4757
+ const results = [];
4758
+ for (const pending of waiting.pending_tool_calls) {
4759
+ try {
4760
+ await this.onBeforeExecute?.(pending);
4761
+ const toolCall = createToolCall(
4762
+ pending.tool_call.id,
4763
+ pending.tool_call.name,
4764
+ pending.tool_call.arguments
4765
+ );
4766
+ let result;
4767
+ if (pending.tool_call.name === USER_ASK_TOOL_NAME) {
4768
+ if (!this.onUserAsk) {
4769
+ throw new Error("user_ask requires onUserAsk handler");
4770
+ }
4771
+ const args = parseUserAskArgs(toolCall);
4772
+ const response2 = await this.onUserAsk(pending, args);
4773
+ const output = typeof response2 === "string" ? serializeUserAskResult({ answer: response2, is_freeform: true }) : serializeUserAskResult(response2);
4774
+ result = {
4775
+ toolCallId: pending.tool_call.id,
4776
+ toolName: pending.tool_call.name,
4777
+ result: output,
4778
+ isRetryable: false
4779
+ };
4780
+ } else {
4781
+ result = await this.registry.execute(toolCall);
4782
+ }
4783
+ results.push(result);
4784
+ await this.onAfterExecute?.(result);
4785
+ } catch (err) {
4786
+ const error = err instanceof Error ? err : new Error(String(err));
4787
+ await this.onError?.(error, pending);
4788
+ results.push({
4789
+ toolCallId: pending.tool_call.id,
4790
+ toolName: pending.tool_call.name,
4791
+ result: null,
4792
+ error: error.message
4793
+ });
4794
+ }
4795
+ }
4796
+ const response = await this.runsClient.submitToolResults(
4797
+ runId,
4798
+ {
4799
+ node_id: nodeId,
4800
+ step: waiting.step,
4801
+ request_id: waiting.request_id,
4802
+ results: results.map((r) => ({
4803
+ tool_call: {
4804
+ id: r.toolCallId,
4805
+ name: r.toolName
4806
+ },
4807
+ output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
4808
+ }))
4809
+ },
4810
+ { customerId: this.customerId }
4811
+ );
4812
+ await this.onSubmitted?.(runId, response.accepted, response.status);
4813
+ return {
4814
+ accepted: response.accepted,
4815
+ status: response.status,
4816
+ results
4817
+ };
4818
+ }
4819
+ /**
4820
+ * Processes a stream of run events, automatically handling node_waiting events.
4821
+ *
4822
+ * This is the main entry point for running a workflow with client-side tools.
4823
+ * It yields all events through (including node_waiting after handling).
4824
+ *
4825
+ * @param runId - The run ID to process
4826
+ * @param events - AsyncIterable of run events (from RunsClient.events())
4827
+ * @yields All run events, with node_waiting events handled automatically
4828
+ *
4829
+ * @example
4830
+ * ```typescript
4831
+ * const run = await client.runs.create(workflowSpec);
4832
+ * const eventStream = client.runs.events(run.run_id);
4833
+ *
4834
+ * for await (const event of runner.processEvents(run.run_id, eventStream)) {
4835
+ * switch (event.type) {
4836
+ * case "node_started":
4837
+ * console.log(`Node ${event.node_id} started`);
4838
+ * break;
4839
+ * case "node_succeeded":
4840
+ * console.log(`Node ${event.node_id} succeeded`);
4841
+ * break;
4842
+ * case "run_succeeded":
4843
+ * console.log("Run completed!");
4844
+ * break;
4845
+ * }
4846
+ * }
4847
+ * ```
4848
+ */
4849
+ async *processEvents(runId, events) {
4850
+ for await (const event of events) {
4851
+ if (event.type === "node_waiting") {
4852
+ const waitingEvent = event;
4853
+ try {
4854
+ await this.handleNodeWaiting(
4855
+ runId,
4856
+ waitingEvent.node_id,
4857
+ waitingEvent.waiting
4858
+ );
4859
+ } catch (err) {
4860
+ const error = err instanceof Error ? err : new Error(String(err));
4861
+ await this.onError?.(error);
4862
+ throw error;
4863
+ }
4864
+ }
4865
+ yield event;
4866
+ }
4867
+ }
4868
+ /**
4869
+ * Checks if a run event is a node_waiting event.
4870
+ * Utility for filtering events when not using processEvents().
4871
+ */
4872
+ static isNodeWaiting(event) {
4873
+ return event.type === "node_waiting";
4874
+ }
4875
+ /**
4876
+ * Checks if a run status is terminal (succeeded, failed, or canceled).
4877
+ * Utility for determining when to stop polling.
4878
+ */
4879
+ static isTerminalStatus(status) {
4880
+ return status === "succeeded" || status === "failed" || status === "canceled";
4881
+ }
4882
+ };
4883
+ function createToolRunner(options) {
4884
+ return new ToolRunner(options);
4885
+ }
4886
+
4887
+ // src/plugins.ts
4888
+ var PluginToolNames = {
4889
+ FS_READ_FILE: "fs_read_file",
4890
+ FS_LIST_FILES: "fs_list_files",
4891
+ FS_SEARCH: "fs_search",
4892
+ FS_EDIT: "fs_edit",
4893
+ BASH: "bash",
4894
+ WRITE_FILE: "write_file",
4895
+ USER_ASK: "user_ask"
4896
+ };
4897
+ var OrchestrationModes = {
4898
+ DAG: "dag",
4899
+ Dynamic: "dynamic"
4900
+ };
4901
+ var PluginOrchestrationErrorCodes = {
4902
+ InvalidPlan: "INVALID_PLAN",
4903
+ UnknownAgent: "UNKNOWN_AGENT",
4904
+ MissingDescription: "MISSING_DESCRIPTION",
4905
+ UnknownTool: "UNKNOWN_TOOL",
4906
+ InvalidDependency: "INVALID_DEPENDENCY",
4907
+ InvalidToolConfig: "INVALID_TOOL_CONFIG"
4908
+ };
4909
+ var PluginOrchestrationError = class extends Error {
4910
+ constructor(code, message) {
4911
+ super(`plugin orchestration: ${message}`);
4912
+ this.code = code;
4913
+ }
4914
+ };
4915
+ var DEFAULT_PLUGIN_REF = "HEAD";
4916
+ var DEFAULT_CACHE_TTL_MS = 5 * 6e4;
4917
+ var DEFAULT_GITHUB_API_BASE = "https://api.github.com";
4918
+ var DEFAULT_GITHUB_RAW_BASE = "https://raw.githubusercontent.com";
4919
+ var defaultDynamicToolNames = [
4920
+ PluginToolNames.FS_READ_FILE,
4921
+ PluginToolNames.FS_LIST_FILES,
4922
+ PluginToolNames.FS_SEARCH
4923
+ ];
4924
+ var allowedToolSet = new Set(Object.values(PluginToolNames));
4925
+ var workflowIntentSchema = z2.object({
4926
+ kind: z2.literal(WorkflowKinds.WorkflowIntent),
4927
+ name: z2.string().optional(),
4928
+ model: z2.string().optional(),
4929
+ max_parallelism: z2.number().int().positive().optional(),
4930
+ inputs: z2.array(
4931
+ z2.object({
4932
+ name: z2.string().min(1),
4933
+ type: z2.string().optional(),
4934
+ required: z2.boolean().optional(),
4935
+ description: z2.string().optional(),
4936
+ default: z2.unknown().optional()
4937
+ })
4938
+ ).optional(),
4939
+ nodes: z2.array(
4940
+ z2.object({
4941
+ id: z2.string().min(1),
4942
+ type: z2.enum([
4943
+ WorkflowNodeTypesIntent.LLM,
4944
+ WorkflowNodeTypesIntent.JoinAll,
4945
+ WorkflowNodeTypesIntent.JoinAny,
4946
+ WorkflowNodeTypesIntent.JoinCollect,
4947
+ WorkflowNodeTypesIntent.TransformJSON,
4948
+ WorkflowNodeTypesIntent.MapFanout
4949
+ ]),
4950
+ depends_on: z2.array(z2.string().min(1)).optional(),
4951
+ model: z2.string().optional(),
4952
+ system: z2.string().optional(),
4953
+ user: z2.string().optional(),
4954
+ input: z2.array(z2.unknown()).optional(),
4955
+ stream: z2.boolean().optional(),
4956
+ tools: z2.array(
4957
+ z2.union([
4958
+ z2.string(),
4959
+ z2.object({}).passthrough()
4960
+ ])
4961
+ ).optional(),
4962
+ tool_execution: z2.object({ mode: z2.enum(["server", "client", "agentic"]) }).optional(),
4963
+ limit: z2.number().int().positive().optional(),
4964
+ timeout_ms: z2.number().int().positive().optional(),
4965
+ predicate: z2.object({}).passthrough().optional(),
4966
+ items_from: z2.string().optional(),
4967
+ items_from_input: z2.string().optional(),
4968
+ items_pointer: z2.string().optional(),
4969
+ items_path: z2.string().optional(),
4970
+ subnode: z2.object({}).passthrough().optional(),
4971
+ max_parallelism: z2.number().int().positive().optional(),
4972
+ object: z2.record(z2.unknown()).optional(),
4973
+ merge: z2.array(z2.unknown()).optional()
4974
+ }).passthrough()
4975
+ ).min(1),
4976
+ outputs: z2.array(
4977
+ z2.object({
4978
+ name: z2.string().min(1),
4979
+ from: z2.string().min(1),
4980
+ pointer: z2.string().optional()
4981
+ })
4982
+ ).min(1)
4983
+ }).passthrough();
4984
+ var orchestrationPlanSchema = z2.object({
4985
+ kind: z2.literal("orchestration.plan.v1"),
4986
+ max_parallelism: z2.number().int().positive().optional(),
4987
+ steps: z2.array(
4988
+ z2.object({
4989
+ id: z2.string().min(1).optional(),
4990
+ depends_on: z2.array(z2.string().min(1)).optional(),
4991
+ agents: z2.array(
4992
+ z2.object({
4993
+ id: z2.string().min(1),
4994
+ reason: z2.string().min(1)
4995
+ })
4996
+ ).min(1)
4997
+ }).strict()
4998
+ ).min(1)
4999
+ }).strict();
5000
+ var PluginLoader = class {
5001
+ constructor(options = {}) {
5002
+ this.cache = /* @__PURE__ */ new Map();
5003
+ this.fetchFn = options.fetch || globalThis.fetch;
5004
+ if (!this.fetchFn) {
5005
+ throw new ConfigError("fetch is required to load plugins");
5006
+ }
5007
+ this.apiBaseUrl = options.apiBaseUrl || DEFAULT_GITHUB_API_BASE;
5008
+ this.rawBaseUrl = options.rawBaseUrl || DEFAULT_GITHUB_RAW_BASE;
5009
+ this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
5010
+ this.now = options.now || (() => /* @__PURE__ */ new Date());
5011
+ }
5012
+ async load(sourceUrl, options = {}) {
5013
+ const ref = parseGitHubPluginRef(sourceUrl);
5014
+ const key = ref.canonical;
5015
+ const cached = this.cache.get(key);
5016
+ if (cached && cached.expiresAt > this.now().getTime()) {
5017
+ return clonePlugin(cached.plugin);
5018
+ }
5019
+ const manifestCandidates = ["PLUGIN.md", "SKILL.md"];
5020
+ let manifestPath = "";
5021
+ let manifestMd = "";
5022
+ for (const candidate of manifestCandidates) {
5023
+ const path = joinRepoPath(ref.repoPath, candidate);
5024
+ const url = this.rawUrl(ref, path);
5025
+ const res = await this.fetchText(url, options.signal);
5026
+ if (res.status === 404) {
5027
+ continue;
5028
+ }
5029
+ if (!res.ok) {
5030
+ throw new ConfigError(`fetch ${path}: ${res.statusText}`);
5031
+ }
5032
+ manifestPath = path;
5033
+ manifestMd = res.body;
5034
+ break;
5035
+ }
5036
+ if (!manifestPath) {
5037
+ throw new ConfigError("plugin manifest not found");
5038
+ }
5039
+ const commandsDir = joinRepoPath(ref.repoPath, "commands");
5040
+ const agentsDir = joinRepoPath(ref.repoPath, "agents");
5041
+ const commandFiles = await this.listMarkdownFiles(ref, commandsDir, options.signal);
5042
+ const agentFiles = await this.listMarkdownFiles(ref, agentsDir, options.signal);
5043
+ const plugin = {
5044
+ id: asPluginId(`${ref.owner}/${ref.repo}${ref.repoPath ? `/${ref.repoPath}` : ""}`),
5045
+ url: asPluginUrl(ref.canonical),
5046
+ manifest: parsePluginManifest(manifestMd),
5047
+ commands: {},
5048
+ agents: {},
5049
+ rawFiles: { [manifestPath]: manifestMd },
5050
+ ref: {
5051
+ owner: ref.owner,
5052
+ repo: ref.repo,
5053
+ ref: ref.ref,
5054
+ path: ref.repoPath || void 0
5055
+ },
5056
+ loadedAt: this.now()
5057
+ };
5058
+ for (const filePath of commandFiles) {
5059
+ const res = await this.fetchText(this.rawUrl(ref, filePath), options.signal);
5060
+ if (!res.ok) {
5061
+ throw new ConfigError(`fetch ${filePath}: ${res.statusText}`);
5062
+ }
5063
+ const { tools, body } = parseMarkdownFrontMatter(res.body);
5064
+ const name = asPluginCommandName(basename(filePath));
5065
+ plugin.commands[String(name)] = {
5066
+ name,
5067
+ prompt: body,
5068
+ agentRefs: extractAgentRefs(body),
5069
+ tools
5070
+ };
5071
+ plugin.rawFiles[filePath] = res.body;
5072
+ }
5073
+ for (const filePath of agentFiles) {
5074
+ const res = await this.fetchText(this.rawUrl(ref, filePath), options.signal);
5075
+ if (!res.ok) {
5076
+ throw new ConfigError(`fetch ${filePath}: ${res.statusText}`);
5077
+ }
5078
+ const { description, tools, body } = parseMarkdownFrontMatter(res.body);
5079
+ const name = asPluginAgentName(basename(filePath));
5080
+ plugin.agents[String(name)] = {
5081
+ name,
5082
+ systemPrompt: body,
5083
+ description,
5084
+ tools
5085
+ };
5086
+ plugin.rawFiles[filePath] = res.body;
5087
+ }
5088
+ plugin.manifest.commands = sortedKeys(Object.values(plugin.commands).map((c) => c.name));
5089
+ plugin.manifest.agents = sortedKeys(Object.values(plugin.agents).map((a) => a.name));
5090
+ this.cache.set(key, {
5091
+ expiresAt: this.now().getTime() + this.cacheTtlMs,
5092
+ plugin: clonePlugin(plugin)
5093
+ });
5094
+ return clonePlugin(plugin);
5095
+ }
5096
+ async listMarkdownFiles(ref, repoDir, signal) {
5097
+ const path = `/repos/${ref.owner}/${ref.repo}/contents/${repoDir}`;
5098
+ const url = `${this.apiBaseUrl}${path}?ref=${encodeURIComponent(ref.ref)}`;
5099
+ const res = await this.fetchJson(url, signal);
5100
+ if (res.status === 404) {
5101
+ return [];
5102
+ }
5103
+ if (!res.ok) {
5104
+ throw new ConfigError(`fetch ${repoDir}: ${res.statusText}`);
5105
+ }
5106
+ return res.body.filter((entry) => entry.type === "file" && entry.name.endsWith(".md")).map((entry) => entry.path);
5107
+ }
5108
+ rawUrl(ref, repoPath) {
5109
+ const cleaned = repoPath.replace(/^\/+/, "");
5110
+ return `${this.rawBaseUrl}/${ref.owner}/${ref.repo}/${ref.ref}/${cleaned}`;
5111
+ }
5112
+ async fetchText(url, signal) {
5113
+ const res = await this.fetchFn(url, { signal });
5114
+ const body = await res.text();
5115
+ return { ok: res.ok, status: res.status, statusText: res.statusText, body };
5116
+ }
5117
+ async fetchJson(url, signal) {
5118
+ const res = await this.fetchFn(url, { signal });
5119
+ if (!res.ok) {
5120
+ return {
5121
+ ok: res.ok,
5122
+ status: res.status,
5123
+ statusText: res.statusText,
5124
+ body: []
5125
+ };
5126
+ }
5127
+ const body = await res.json();
5128
+ return { ok: res.ok, status: res.status, statusText: res.statusText, body };
5129
+ }
5130
+ };
5131
+ var PluginConverter = class {
5132
+ constructor(responses, http, auth, options = {}) {
5133
+ this.responses = responses;
5134
+ this.http = http;
5135
+ this.auth = auth;
5136
+ this.converterModel = asModelId(options.converterModel || "claude-3-5-haiku-latest");
5137
+ }
5138
+ async toWorkflow(plugin, commandName, task) {
5139
+ const command = resolveCommand(plugin, commandName);
5140
+ const prompt = buildPluginConversionPrompt(plugin, command, task);
5141
+ const schemaName = "workflow";
5142
+ const result = await this.responses.object({
5143
+ model: this.converterModel,
5144
+ schema: workflowIntentSchema,
5145
+ schemaName,
5146
+ system: pluginToWorkflowSystemPrompt,
5147
+ prompt
5148
+ });
5149
+ const spec = normalizeWorkflowIntent(result);
5150
+ validateWorkflowTools(spec);
5151
+ return spec;
5152
+ }
5153
+ async toWorkflowDynamic(plugin, commandName, task) {
5154
+ const command = resolveCommand(plugin, commandName);
5155
+ const { candidates, lookup } = buildOrchestrationCandidates(plugin, command);
5156
+ const prompt = buildPluginOrchestrationPrompt(plugin, command, task, candidates);
5157
+ const plan = await this.responses.object({
5158
+ model: this.converterModel,
5159
+ schema: orchestrationPlanSchema,
5160
+ schemaName: "orchestration_plan",
5161
+ system: pluginOrchestrationSystemPrompt,
5162
+ prompt
5163
+ });
5164
+ validateOrchestrationPlan(plan, lookup);
5165
+ const spec = buildDynamicWorkflowFromPlan(plugin, command, task, plan, lookup, this.converterModel);
5166
+ if (specRequiresTools(spec)) {
5167
+ await ensureModelSupportsTools(this.http, this.auth, this.converterModel);
5168
+ }
5169
+ validateWorkflowTools(spec);
5170
+ return spec;
5171
+ }
5172
+ };
5173
+ var PluginRunner = class {
5174
+ constructor(runs) {
5175
+ this.runs = runs;
5176
+ }
5177
+ async run(spec, config) {
5178
+ const created = await this.runs.create(spec, config.runOptions);
5179
+ return this.wait(created.run_id, config);
5180
+ }
5181
+ async wait(runId, config) {
5182
+ const events = [];
5183
+ const toolRegistry = config.toolRegistry;
5184
+ const eventStream = await this.runs.events(runId, config.runOptions);
5185
+ const runner = toolRegistry ? new ToolRunner({ registry: toolRegistry, runsClient: this.runs }) : null;
5186
+ const stream = runner ? runner.processEvents(runId, eventStream) : eventStream;
5187
+ let terminal = null;
5188
+ for await (const event of stream) {
5189
+ events.push(event);
5190
+ if (event.type === "run_completed") {
5191
+ terminal = "succeeded";
5192
+ break;
5193
+ }
5194
+ if (event.type === "run_failed") {
5195
+ terminal = "failed";
5196
+ break;
5197
+ }
5198
+ if (event.type === "run_canceled") {
5199
+ terminal = "canceled";
5200
+ break;
5201
+ }
5202
+ }
5203
+ const snapshot = await this.runs.get(runId, config.runOptions);
5204
+ return {
5205
+ runId: snapshot.run_id,
5206
+ status: terminal || snapshot.status,
5207
+ outputs: snapshot.outputs,
5208
+ costSummary: snapshot.cost_summary,
5209
+ events
5210
+ };
5211
+ }
5212
+ };
5213
+ var PluginsClient = class {
5214
+ constructor(deps) {
5215
+ this.responses = deps.responses;
5216
+ this.http = deps.http;
5217
+ this.auth = deps.auth;
5218
+ this.loader = new PluginLoader(deps.options);
5219
+ this.converter = new PluginConverter(deps.responses, deps.http, deps.auth);
5220
+ this.runner = new PluginRunner(deps.runs);
5221
+ }
5222
+ load(url, options) {
5223
+ return this.loader.load(url, options);
5224
+ }
5225
+ async run(plugin, command, config) {
5226
+ const task = config.userTask?.trim();
5227
+ if (!task) {
5228
+ throw new ConfigError("userTask is required");
5229
+ }
5230
+ const mode = normalizeOrchestrationMode(config.orchestrationMode);
5231
+ const converterModel = config.converterModel ? asModelId(String(config.converterModel)) : void 0;
5232
+ const converter = converterModel ? new PluginConverter(this.responses, this.http, this.auth, {
5233
+ converterModel
5234
+ }) : this.converter;
5235
+ let spec;
5236
+ if (mode === OrchestrationModes.Dynamic) {
5237
+ spec = await converter.toWorkflowDynamic(plugin, command, task);
5238
+ } else {
5239
+ spec = await converter.toWorkflow(plugin, command, task);
5240
+ }
5241
+ if (config.model) {
5242
+ spec = { ...spec, model: String(config.model) };
5243
+ }
5244
+ return this.runner.run(spec, config);
5245
+ }
5246
+ async quickRun(pluginUrl, command, userTask, config = {}) {
5247
+ const plugin = await this.load(pluginUrl);
5248
+ return this.run(plugin, command, { ...config, userTask });
5249
+ }
5250
+ };
5251
+ function normalizeOrchestrationMode(mode) {
5252
+ if (!mode) return OrchestrationModes.DAG;
5253
+ if (mode !== OrchestrationModes.DAG && mode !== OrchestrationModes.Dynamic) {
5254
+ throw new ConfigError(`invalid orchestration mode: ${mode}`);
5255
+ }
5256
+ return mode;
5257
+ }
5258
+ function resolveCommand(plugin, commandName) {
5259
+ const trimmed = commandName.trim();
5260
+ if (!trimmed) {
5261
+ throw new ConfigError("command is required");
5262
+ }
5263
+ const command = plugin.commands[trimmed];
5264
+ if (!command) {
5265
+ throw new ConfigError("unknown command");
5266
+ }
5267
+ return command;
5268
+ }
5269
+ var pluginToWorkflowSystemPrompt = `You convert a ModelRelay plugin (markdown files) into a single workflow JSON spec.
5270
+
5271
+ Rules:
5272
+ - Output MUST be a single JSON object and MUST validate as workflow.
5273
+ - Do NOT output markdown, commentary, or code fences.
5274
+ - Use a DAG with parallelism when multiple agents are independent.
5275
+ - Use join.all to aggregate parallel branches and then a final synthesizer node.
5276
+ - Use depends_on for edges between nodes.
5277
+ - Bind node outputs using {{placeholders}} when passing data forward.
5278
+ - Tool contract:
5279
+ - Target tools.v0 client tools (see docs/reference/tools.md).
5280
+ - Workspace access MUST use these exact function tool names:
5281
+ - ${Object.values(PluginToolNames).join(", ")}
5282
+ - Prefer fs_* tools for reading/listing/searching the workspace (use bash only when necessary).
5283
+ - Do NOT invent ad-hoc tool names (no repo.*, github.*, filesystem.*, etc.).
5284
+ - All client tools MUST be represented as type="function" tools.
5285
+ - Any node that includes tools MUST set tool_execution.mode="client".
5286
+ - Prefer minimal nodes needed to satisfy the task.
5287
+ `;
5288
+ var pluginOrchestrationSystemPrompt = `You plan which plugin agents to run based only on their descriptions.
5289
+
5290
+ Rules:
5291
+ - Output MUST be a single JSON object that matches orchestration.plan.v1.
5292
+ - Do NOT output markdown, commentary, or code fences.
5293
+ - Select only from the provided agent IDs.
5294
+ - Prefer minimal agents needed to satisfy the user task.
5295
+ - Use multiple steps only when later agents must build on earlier results.
5296
+ - Each step can run agents in parallel.
5297
+ - Use "id" + "depends_on" if you need non-sequential step ordering.
5298
+ `;
5299
+ function buildPluginConversionPrompt(plugin, command, userTask) {
5300
+ const out = [];
5301
+ out.push(`PLUGIN_URL: ${plugin.url}`);
5302
+ out.push(`COMMAND: ${command.name}`);
5303
+ out.push("USER_TASK:");
5304
+ out.push(userTask.trim());
5305
+ out.push("");
5306
+ out.push(`PLUGIN_MANIFEST:`);
5307
+ out.push(JSON.stringify(plugin.manifest));
5308
+ out.push("");
5309
+ out.push(`COMMAND_MARKDOWN (commands/${command.name}.md):`);
5310
+ out.push(command.prompt);
5311
+ out.push("");
5312
+ const agentNames = Object.keys(plugin.agents).sort();
5313
+ if (agentNames.length) {
5314
+ out.push("AGENTS_MARKDOWN:");
5315
+ for (const name of agentNames) {
5316
+ out.push(`---- agents/${name}.md ----`);
5317
+ out.push(plugin.agents[name].systemPrompt);
5318
+ out.push("");
5319
+ }
5320
+ }
5321
+ return out.join("\n");
5322
+ }
5323
+ function buildOrchestrationCandidates(plugin, command) {
5324
+ const names = command.agentRefs?.length ? command.agentRefs : Object.values(plugin.agents).map((agent) => agent.name);
5325
+ if (!names.length) {
5326
+ throw new PluginOrchestrationError(
5327
+ PluginOrchestrationErrorCodes.InvalidPlan,
5328
+ "no agents available for dynamic orchestration"
5329
+ );
5330
+ }
5331
+ const lookup = /* @__PURE__ */ new Map();
5332
+ const candidates = [];
5333
+ for (const name of names) {
5334
+ const agent = plugin.agents[String(name)];
5335
+ if (!agent) {
5336
+ throw new PluginOrchestrationError(
5337
+ PluginOrchestrationErrorCodes.UnknownAgent,
5338
+ `agent "${name}" not found`
5339
+ );
5340
+ }
5341
+ const desc = agent.description?.trim();
5342
+ if (!desc) {
5343
+ throw new PluginOrchestrationError(
5344
+ PluginOrchestrationErrorCodes.MissingDescription,
5345
+ `agent "${name}" missing description`
5346
+ );
5347
+ }
5348
+ lookup.set(String(name), agent);
5349
+ candidates.push({ name, description: desc, agent });
5350
+ }
5351
+ return { candidates, lookup };
5352
+ }
5353
+ function buildPluginOrchestrationPrompt(plugin, command, userTask, candidates) {
5354
+ const out = [];
5355
+ if (plugin.manifest.name) {
5356
+ out.push(`PLUGIN_NAME: ${plugin.manifest.name}`);
5357
+ }
5358
+ if (plugin.manifest.description) {
5359
+ out.push(`PLUGIN_DESCRIPTION: ${plugin.manifest.description}`);
5360
+ }
5361
+ out.push(`COMMAND: ${command.name}`);
5362
+ out.push("USER_TASK:");
5363
+ out.push(userTask.trim());
5364
+ out.push("");
5365
+ if (command.prompt.trim()) {
5366
+ out.push("COMMAND_MARKDOWN:");
5367
+ out.push(command.prompt);
5368
+ out.push("");
5369
+ }
5370
+ out.push("CANDIDATE_AGENTS:");
5371
+ for (const c of candidates) {
5372
+ out.push(`- id: ${c.name}`);
5373
+ out.push(` description: ${c.description}`);
5374
+ }
5375
+ return out.join("\n");
5376
+ }
5377
+ function validateOrchestrationPlan(plan, lookup) {
5378
+ if (plan.max_parallelism && plan.max_parallelism < 1) {
5379
+ throw new PluginOrchestrationError(
5380
+ PluginOrchestrationErrorCodes.InvalidPlan,
5381
+ "max_parallelism must be >= 1"
5382
+ );
5383
+ }
5384
+ const stepIds = /* @__PURE__ */ new Map();
5385
+ let hasExplicitDeps = false;
5386
+ plan.steps.forEach((step, idx) => {
5387
+ if (step.depends_on?.length) {
5388
+ hasExplicitDeps = true;
5389
+ }
5390
+ if (step.id) {
5391
+ const key = step.id.trim();
5392
+ if (stepIds.has(key)) {
5393
+ throw new PluginOrchestrationError(
5394
+ PluginOrchestrationErrorCodes.InvalidPlan,
5395
+ `duplicate step id "${key}"`
5396
+ );
5397
+ }
5398
+ stepIds.set(key, idx);
5399
+ }
5400
+ });
5401
+ if (hasExplicitDeps) {
5402
+ plan.steps.forEach((step) => {
5403
+ if (!step.id?.trim()) {
5404
+ throw new PluginOrchestrationError(
5405
+ PluginOrchestrationErrorCodes.InvalidPlan,
5406
+ "step id required when depends_on is used"
5407
+ );
5408
+ }
5409
+ });
5410
+ }
5411
+ const seen = /* @__PURE__ */ new Set();
5412
+ plan.steps.forEach((step, idx) => {
5413
+ if (!step.agents.length) {
5414
+ throw new PluginOrchestrationError(
5415
+ PluginOrchestrationErrorCodes.InvalidPlan,
5416
+ `step ${idx + 1} must include at least one agent`
5417
+ );
5418
+ }
5419
+ if (step.depends_on) {
5420
+ for (const dep of step.depends_on) {
5421
+ const depId = dep.trim();
5422
+ const depIndex = depId ? stepIds.get(depId) : void 0;
5423
+ if (!depId) {
5424
+ throw new PluginOrchestrationError(
5425
+ PluginOrchestrationErrorCodes.InvalidDependency,
5426
+ `step ${idx + 1} has empty depends_on`
5427
+ );
5428
+ }
5429
+ if (depIndex === void 0) {
5430
+ throw new PluginOrchestrationError(
5431
+ PluginOrchestrationErrorCodes.InvalidDependency,
5432
+ `step ${idx + 1} depends on unknown step "${depId}"`
5433
+ );
5434
+ }
5435
+ if (depIndex >= idx) {
5436
+ throw new PluginOrchestrationError(
5437
+ PluginOrchestrationErrorCodes.InvalidDependency,
5438
+ `step ${idx + 1} depends on future step "${depId}"`
5439
+ );
5440
+ }
5441
+ }
5442
+ }
5443
+ for (const agent of step.agents) {
5444
+ const id = agent.id.trim();
5445
+ if (!id) {
5446
+ throw new PluginOrchestrationError(
5447
+ PluginOrchestrationErrorCodes.InvalidPlan,
5448
+ `step ${idx + 1} agent id required`
5449
+ );
5450
+ }
5451
+ if (!lookup.has(id)) {
5452
+ throw new PluginOrchestrationError(
5453
+ PluginOrchestrationErrorCodes.UnknownAgent,
5454
+ `unknown agent "${id}"`
5455
+ );
5456
+ }
5457
+ if (!agent.reason?.trim()) {
5458
+ throw new PluginOrchestrationError(
5459
+ PluginOrchestrationErrorCodes.InvalidPlan,
5460
+ `agent "${id}" must include a reason`
5461
+ );
5462
+ }
5463
+ if (seen.has(id)) {
5464
+ throw new PluginOrchestrationError(
5465
+ PluginOrchestrationErrorCodes.InvalidPlan,
5466
+ `agent "${id}" referenced more than once`
5467
+ );
5468
+ }
5469
+ seen.add(id);
5470
+ }
5471
+ });
5472
+ }
5473
+ function buildDynamicWorkflowFromPlan(plugin, command, userTask, plan, lookup, model) {
5474
+ const stepKeys = plan.steps.map((step, idx) => step.id?.trim() || `step_${idx + 1}`);
5475
+ const stepOrder = new Map(stepKeys.map((key, idx) => [key, idx]));
5476
+ const stepOutputs = /* @__PURE__ */ new Map();
5477
+ const usedNodeIds = /* @__PURE__ */ new Set();
5478
+ const nodes = [];
5479
+ const hasExplicitDeps = plan.steps.some((step) => (step.depends_on?.length ?? 0) > 0);
5480
+ for (let i = 0; i < plan.steps.length; i += 1) {
5481
+ const step = plan.steps[i];
5482
+ const stepKey = stepKeys[i];
5483
+ const dependencyKeys = hasExplicitDeps ? step.depends_on || [] : i > 0 ? [stepKeys[i - 1]] : [];
5484
+ const deps = dependencyKeys.map((raw) => {
5485
+ const key = raw.trim();
5486
+ const nodeId = stepOutputs.get(key);
5487
+ if (!nodeId) {
5488
+ throw new PluginOrchestrationError(
5489
+ PluginOrchestrationErrorCodes.InvalidDependency,
5490
+ `missing output for dependency "${key}"`
5491
+ );
5492
+ }
5493
+ const depIndex = stepOrder.get(key);
5494
+ if (depIndex === void 0 || depIndex >= i) {
5495
+ throw new PluginOrchestrationError(
5496
+ PluginOrchestrationErrorCodes.InvalidDependency,
5497
+ `invalid dependency "${key}"`
5498
+ );
5499
+ }
5500
+ return { stepId: key, nodeId };
5501
+ });
5502
+ const stepNodeIds = [];
5503
+ for (const selection of step.agents) {
5504
+ const agentName = selection.id.trim();
5505
+ const agent = lookup.get(agentName);
5506
+ if (!agent) {
5507
+ throw new PluginOrchestrationError(
5508
+ PluginOrchestrationErrorCodes.UnknownAgent,
5509
+ `unknown agent "${agentName}"`
5510
+ );
5511
+ }
5512
+ const nodeId = parseNodeId(formatAgentNodeId(agentName));
5513
+ if (usedNodeIds.has(nodeId)) {
5514
+ throw new PluginOrchestrationError(
5515
+ PluginOrchestrationErrorCodes.InvalidPlan,
5516
+ `duplicate node id "${nodeId}"`
5517
+ );
5518
+ }
5519
+ const tools = buildToolRefs(agent, command);
5520
+ const node = {
5521
+ id: nodeId,
5522
+ type: WorkflowNodeTypesIntent.LLM,
5523
+ system: agent.systemPrompt.trim() || void 0,
5524
+ user: buildDynamicAgentUserPrompt(command, userTask, deps),
5525
+ depends_on: deps.length ? deps.map((d) => d.nodeId) : void 0,
5526
+ tools: tools.length ? tools : void 0,
5527
+ tool_execution: tools.length ? { mode: "client" } : void 0
5528
+ };
5529
+ nodes.push(node);
5530
+ stepNodeIds.push(nodeId);
5531
+ usedNodeIds.add(nodeId);
5532
+ }
5533
+ let outputNodeId = stepNodeIds[0];
5534
+ if (stepNodeIds.length > 1) {
5535
+ const joinId = parseNodeId(formatStepJoinNodeId(stepKey));
5536
+ if (usedNodeIds.has(joinId)) {
5537
+ throw new PluginOrchestrationError(
5538
+ PluginOrchestrationErrorCodes.InvalidPlan,
5539
+ `duplicate node id "${joinId}"`
5540
+ );
5541
+ }
5542
+ nodes.push({
5543
+ id: joinId,
5544
+ type: WorkflowNodeTypesIntent.JoinAll,
5545
+ depends_on: stepNodeIds
5546
+ });
5547
+ usedNodeIds.add(joinId);
5548
+ outputNodeId = joinId;
5549
+ }
5550
+ stepOutputs.set(stepKey, outputNodeId);
5551
+ }
5552
+ const terminalOutputs = findTerminalOutputs(stepKeys, plan, stepOutputs, hasExplicitDeps);
5553
+ const synthId = parseNodeId("orchestrator_synthesize");
5554
+ const synthNode = {
5555
+ id: synthId,
5556
+ type: WorkflowNodeTypesIntent.LLM,
5557
+ user: buildDynamicSynthesisPrompt(command, userTask, terminalOutputs),
5558
+ depends_on: terminalOutputs
5559
+ };
5560
+ const spec = {
5561
+ kind: WorkflowKinds.WorkflowIntent,
5562
+ name: plugin.manifest.name?.trim() || command.name,
5563
+ model: String(model),
5564
+ max_parallelism: plan.max_parallelism,
5565
+ nodes: [...nodes, synthNode],
5566
+ outputs: [{ name: parseOutputName("result"), from: synthId }]
5567
+ };
5568
+ return spec;
5569
+ }
5570
+ function buildDynamicAgentUserPrompt(command, task, deps) {
5571
+ const parts = [];
5572
+ if (command.prompt.trim()) {
5573
+ parts.push(command.prompt.trim());
5574
+ }
5575
+ parts.push("USER_TASK:");
5576
+ parts.push(task.trim());
5577
+ if (deps.length) {
5578
+ parts.push("", "PREVIOUS_STEP_OUTPUTS:");
5579
+ for (const dep of deps) {
5580
+ parts.push(`- ${dep.stepId}: {{${dep.nodeId}}}`);
5581
+ }
3880
5582
  }
3881
- };
3882
-
3883
- // src/tiers.ts
3884
- function defaultTierModelId(tier) {
3885
- const def = tier.models.find((m) => m.is_default);
3886
- if (def) return def.model_id;
3887
- if (tier.models.length === 1) return tier.models[0].model_id;
3888
- return void 0;
5583
+ return parts.join("\n");
3889
5584
  }
3890
- var TiersClient = class {
3891
- constructor(http, cfg) {
3892
- this.http = http;
3893
- this.apiKey = cfg.apiKey ? parseApiKey(cfg.apiKey) : void 0;
3894
- this.hasSecretKey = this.apiKey ? isSecretKey(this.apiKey) : false;
3895
- this.hasAccessToken = !!cfg.accessToken?.trim();
5585
+ function buildDynamicSynthesisPrompt(command, task, outputs) {
5586
+ const parts = ["Synthesize the results and complete the task."];
5587
+ if (command.prompt.trim()) {
5588
+ parts.push("", "COMMAND:");
5589
+ parts.push(command.prompt.trim());
3896
5590
  }
3897
- ensureAuth() {
3898
- if (!this.apiKey && !this.hasAccessToken) {
3899
- throw new ConfigError(
3900
- "API key (mr_sk_*) or bearer token required for tier operations"
3901
- );
5591
+ parts.push("", "USER_TASK:");
5592
+ parts.push(task.trim());
5593
+ if (outputs.length) {
5594
+ parts.push("", "RESULTS:");
5595
+ for (const id of outputs) {
5596
+ parts.push(`- {{${id}}}`);
3902
5597
  }
3903
5598
  }
3904
- ensureSecretKey() {
3905
- if (!this.apiKey || !this.hasSecretKey) {
3906
- throw new ConfigError(
3907
- "Secret key (mr_sk_*) required for checkout operations"
5599
+ return parts.join("\n");
5600
+ }
5601
+ function buildToolRefs(agent, command) {
5602
+ const names = agent.tools?.length ? agent.tools : command.tools?.length ? command.tools : defaultDynamicToolNames;
5603
+ const unique = /* @__PURE__ */ new Set();
5604
+ for (const name of names) {
5605
+ if (!allowedToolSet.has(name)) {
5606
+ throw new PluginOrchestrationError(
5607
+ PluginOrchestrationErrorCodes.UnknownTool,
5608
+ `unknown tool "${name}"`
3908
5609
  );
3909
5610
  }
5611
+ unique.add(name);
3910
5612
  }
3911
- /**
3912
- * List all tiers in the project.
3913
- */
3914
- async list() {
3915
- this.ensureAuth();
3916
- const response = await this.http.json("/tiers", {
3917
- method: "GET",
3918
- apiKey: this.apiKey
3919
- });
3920
- return response.tiers;
5613
+ return Array.from(unique.values());
5614
+ }
5615
+ function findTerminalOutputs(stepKeys, plan, outputs, explicit) {
5616
+ if (!explicit) {
5617
+ const lastKey = stepKeys[stepKeys.length - 1];
5618
+ const out = outputs.get(lastKey);
5619
+ return out ? [out] : [];
5620
+ }
5621
+ const depended = /* @__PURE__ */ new Set();
5622
+ plan.steps.forEach((step) => {
5623
+ (step.depends_on || []).forEach((dep) => depended.add(dep.trim()));
5624
+ });
5625
+ return stepKeys.filter((key) => !depended.has(key)).map((key) => outputs.get(key)).filter((value) => Boolean(value));
5626
+ }
5627
+ function formatAgentNodeId(name) {
5628
+ const token = sanitizeNodeToken(name);
5629
+ if (!token) {
5630
+ throw new PluginOrchestrationError(
5631
+ PluginOrchestrationErrorCodes.InvalidPlan,
5632
+ "agent id must contain alphanumeric characters"
5633
+ );
3921
5634
  }
3922
- /**
3923
- * Get a tier by ID.
3924
- */
3925
- async get(tierId) {
3926
- this.ensureAuth();
3927
- if (!tierId?.trim()) {
3928
- throw new ConfigError("tierId is required");
5635
+ return `agent_${token}`;
5636
+ }
5637
+ function formatStepJoinNodeId(stepKey) {
5638
+ const token = sanitizeNodeToken(stepKey);
5639
+ if (!token) {
5640
+ throw new PluginOrchestrationError(
5641
+ PluginOrchestrationErrorCodes.InvalidPlan,
5642
+ "step id must contain alphanumeric characters"
5643
+ );
5644
+ }
5645
+ return token.startsWith("step_") ? `${token}_join` : `step_${token}_join`;
5646
+ }
5647
+ function sanitizeNodeToken(raw) {
5648
+ const trimmed = raw.trim();
5649
+ if (!trimmed) return "";
5650
+ let out = "";
5651
+ for (const ch of trimmed) {
5652
+ if (/[a-zA-Z0-9]/.test(ch)) {
5653
+ out += ch.toLowerCase();
5654
+ } else {
5655
+ out += "_";
3929
5656
  }
3930
- const response = await this.http.json(`/tiers/${tierId}`, {
5657
+ }
5658
+ out = out.replace(/_+/g, "_");
5659
+ return out.replace(/^_+|_+$/g, "");
5660
+ }
5661
+ async function ensureModelSupportsTools(http, auth, model) {
5662
+ const authHeaders = await auth.authForResponses();
5663
+ const query = encodeURIComponent("tools");
5664
+ const response = await http.json(
5665
+ `/models?capability=${query}`,
5666
+ {
3931
5667
  method: "GET",
3932
- apiKey: this.apiKey
3933
- });
3934
- return response.tier;
5668
+ apiKey: authHeaders.apiKey,
5669
+ accessToken: authHeaders.accessToken
5670
+ }
5671
+ );
5672
+ const modelId = String(model).trim();
5673
+ const found = response.models?.some((entry) => entry.model_id?.trim() === modelId);
5674
+ if (!found) {
5675
+ throw new PluginOrchestrationError(
5676
+ PluginOrchestrationErrorCodes.InvalidToolConfig,
5677
+ `model "${modelId}" does not support tool calling`
5678
+ );
3935
5679
  }
3936
- /**
3937
- * Create a Stripe checkout session for a tier (Stripe-first flow).
3938
- *
3939
- * This enables users to subscribe before authenticating. Stripe collects
3940
- * the customer's email during checkout. After checkout completes, a
3941
- * customer record is created with the email from Stripe. Your backend
3942
- * can map it to your app user and mint customer tokens as needed.
3943
- *
3944
- * Requires a secret key (mr_sk_*).
3945
- *
3946
- * @param tierId - The tier ID to create a checkout session for
3947
- * @param request - Checkout session request with redirect URLs
3948
- * @returns Checkout session with Stripe URL
3949
- */
3950
- async checkout(tierId, request) {
3951
- this.ensureSecretKey();
3952
- if (!tierId?.trim()) {
3953
- throw new ConfigError("tierId is required");
5680
+ }
5681
+ function normalizeWorkflowIntent(spec) {
5682
+ const validation = validateWithZod(workflowIntentSchema, spec);
5683
+ if (!validation.success) {
5684
+ throw new ConfigError("workflow intent validation failed");
5685
+ }
5686
+ validateWorkflowTools(spec);
5687
+ return spec;
5688
+ }
5689
+ function validateWorkflowTools(spec) {
5690
+ for (const node of spec.nodes) {
5691
+ if (node.type !== WorkflowNodeTypesIntent.LLM || !node.tools?.length) {
5692
+ continue;
5693
+ }
5694
+ const tools = node.tools || [];
5695
+ let mode = node.tool_execution?.mode;
5696
+ for (const tool of tools) {
5697
+ if (typeof tool !== "string") {
5698
+ throw new ConfigError(`plugin conversion only supports tools.v0 function tools`);
5699
+ }
5700
+ if (!allowedToolSet.has(tool)) {
5701
+ throw new ConfigError(`unsupported tool "${tool}" (plugin conversion targets tools.v0)`);
5702
+ }
5703
+ mode = "client";
3954
5704
  }
3955
- if (!request.success_url?.trim()) {
3956
- throw new ConfigError("success_url is required");
5705
+ if (mode && mode !== "client") {
5706
+ throw new ConfigError(`tool_execution.mode must be "client" for plugin conversion`);
3957
5707
  }
3958
- if (!request.cancel_url?.trim()) {
3959
- throw new ConfigError("cancel_url is required");
5708
+ node.tool_execution = { mode: "client" };
5709
+ }
5710
+ }
5711
+ function parsePluginManifest(markdown) {
5712
+ const trimmed = markdown.trim();
5713
+ if (!trimmed) return {};
5714
+ const frontMatter = parseManifestFrontMatter(trimmed);
5715
+ if (frontMatter) return frontMatter;
5716
+ const lines = splitLines(trimmed);
5717
+ let name = "";
5718
+ for (const line of lines) {
5719
+ if (line.startsWith("# ")) {
5720
+ name = line.slice(2).trim();
5721
+ break;
3960
5722
  }
3961
- return await this.http.json(
3962
- `/tiers/${tierId}/checkout`,
3963
- {
3964
- method: "POST",
3965
- apiKey: this.apiKey,
3966
- body: request
5723
+ }
5724
+ let description = "";
5725
+ if (name) {
5726
+ let after = false;
5727
+ for (const line of lines) {
5728
+ const trimmedLine = line.trim();
5729
+ if (trimmedLine.startsWith("# ")) {
5730
+ after = true;
5731
+ continue;
5732
+ }
5733
+ if (!after) continue;
5734
+ if (!trimmedLine) continue;
5735
+ if (trimmedLine.startsWith("## ")) break;
5736
+ description = trimmedLine;
5737
+ break;
5738
+ }
5739
+ }
5740
+ return { name, description };
5741
+ }
5742
+ function parseManifestFrontMatter(markdown) {
5743
+ const lines = splitLines(markdown);
5744
+ if (!lines.length || lines[0].trim() !== "---") return null;
5745
+ const end = lines.findIndex((line, idx) => idx > 0 && line.trim() === "---");
5746
+ if (end === -1) return null;
5747
+ const manifest = {};
5748
+ let currentList = null;
5749
+ for (const line of lines.slice(1, end)) {
5750
+ const raw = line.trim();
5751
+ if (!raw || raw.startsWith("#")) continue;
5752
+ if (raw.startsWith("- ") && currentList) {
5753
+ const item = raw.slice(2).trim();
5754
+ if (item) {
5755
+ if (currentList === "commands") {
5756
+ manifest.commands = [...manifest.commands || [], asPluginCommandName(item)];
5757
+ } else {
5758
+ manifest.agents = [...manifest.agents || [], asPluginAgentName(item)];
5759
+ }
3967
5760
  }
5761
+ continue;
5762
+ }
5763
+ currentList = null;
5764
+ const [keyRaw, ...rest] = raw.split(":");
5765
+ if (!keyRaw || rest.length === 0) continue;
5766
+ const key = keyRaw.trim().toLowerCase();
5767
+ const val = rest.join(":").trim().replace(/^['"]|['"]$/g, "");
5768
+ if (key === "name") manifest.name = val;
5769
+ if (key === "description") manifest.description = val;
5770
+ if (key === "version") manifest.version = val;
5771
+ if (key === "commands") currentList = "commands";
5772
+ if (key === "agents") currentList = "agents";
5773
+ }
5774
+ manifest.commands = manifest.commands?.sort();
5775
+ manifest.agents = manifest.agents?.sort();
5776
+ return manifest;
5777
+ }
5778
+ function parseMarkdownFrontMatter(markdown) {
5779
+ const trimmed = markdown.trim();
5780
+ if (!trimmed.startsWith("---")) {
5781
+ return { body: markdown };
5782
+ }
5783
+ const lines = splitLines(trimmed);
5784
+ const endIdx = lines.findIndex((line, idx) => idx > 0 && line.trim() === "---");
5785
+ if (endIdx === -1) {
5786
+ return { body: markdown };
5787
+ }
5788
+ let description;
5789
+ let tools;
5790
+ let currentList = null;
5791
+ const toolItems = [];
5792
+ for (const line of lines.slice(1, endIdx)) {
5793
+ const raw = line.trim();
5794
+ if (!raw || raw.startsWith("#")) continue;
5795
+ if (raw.startsWith("- ") && currentList === "tools") {
5796
+ const item = raw.slice(2).trim();
5797
+ if (item) toolItems.push(item);
5798
+ continue;
5799
+ }
5800
+ currentList = null;
5801
+ const [keyRaw, ...rest] = raw.split(":");
5802
+ if (!keyRaw || rest.length === 0) continue;
5803
+ const key = keyRaw.trim().toLowerCase();
5804
+ const val = rest.join(":").trim().replace(/^['"]|['"]$/g, "");
5805
+ if (key === "description") description = val;
5806
+ if (key === "tools") {
5807
+ if (!val) {
5808
+ currentList = "tools";
5809
+ continue;
5810
+ }
5811
+ toolItems.push(...splitFrontMatterList(val));
5812
+ }
5813
+ }
5814
+ if (toolItems.length) {
5815
+ tools = toolItems.map((item) => parseToolName(item));
5816
+ }
5817
+ const body = lines.slice(endIdx + 1).join("\n").replace(/^[\n\r]+/, "");
5818
+ return { description, tools, body };
5819
+ }
5820
+ function parseToolName(raw) {
5821
+ const val = raw.trim();
5822
+ if (!allowedToolSet.has(val)) {
5823
+ throw new PluginOrchestrationError(
5824
+ PluginOrchestrationErrorCodes.UnknownTool,
5825
+ `unknown tool "${raw}"`
3968
5826
  );
3969
5827
  }
3970
- };
5828
+ return val;
5829
+ }
5830
+ function splitFrontMatterList(raw) {
5831
+ const cleaned = raw.trim().replace(/^\[/, "").replace(/\]$/, "");
5832
+ if (!cleaned) return [];
5833
+ return cleaned.split(",").map((part) => part.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
5834
+ }
5835
+ function extractAgentRefs(markdown) {
5836
+ const seen = /* @__PURE__ */ new Set();
5837
+ const out = [];
5838
+ const lines = splitLines(markdown);
5839
+ for (const line of lines) {
5840
+ const lower = line.toLowerCase();
5841
+ const idx = lower.indexOf("agents/");
5842
+ if (idx === -1) continue;
5843
+ if (!lower.includes(".md", idx)) continue;
5844
+ let seg = line.slice(idx).trim();
5845
+ seg = seg.replace(/^agents\//, "");
5846
+ seg = seg.split(".md")[0];
5847
+ seg = seg.replace(/[`* _]/g, "").trim();
5848
+ if (!seg || seen.has(seg)) continue;
5849
+ seen.add(seg);
5850
+ out.push(asPluginAgentName(seg));
5851
+ }
5852
+ return out.sort();
5853
+ }
5854
+ function splitLines(input) {
5855
+ return input.replace(/\r\n/g, "\n").split("\n");
5856
+ }
5857
+ function basename(path) {
5858
+ const parts = path.split("/");
5859
+ const last = parts[parts.length - 1] || "";
5860
+ return last.replace(/\.md$/i, "");
5861
+ }
5862
+ function joinRepoPath(base, elem) {
5863
+ const clean = (value) => value.replace(/^\/+|\/+$/g, "");
5864
+ const b = clean(base || "");
5865
+ const e = clean(elem || "");
5866
+ if (!b) return e;
5867
+ if (!e) return b;
5868
+ return `${b}/${e}`;
5869
+ }
5870
+ function sortedKeys(items) {
5871
+ return items.slice().sort();
5872
+ }
5873
+ function clonePlugin(plugin) {
5874
+ return {
5875
+ ...plugin,
5876
+ manifest: { ...plugin.manifest },
5877
+ commands: { ...plugin.commands },
5878
+ agents: { ...plugin.agents },
5879
+ rawFiles: { ...plugin.rawFiles },
5880
+ loadedAt: new Date(plugin.loadedAt)
5881
+ };
5882
+ }
5883
+ function asPluginId(value) {
5884
+ const trimmed = value.trim();
5885
+ if (!trimmed) throw new ConfigError("plugin id required");
5886
+ return trimmed;
5887
+ }
5888
+ function asPluginUrl(value) {
5889
+ const trimmed = value.trim();
5890
+ if (!trimmed) throw new ConfigError("plugin url required");
5891
+ return trimmed;
5892
+ }
5893
+ function asPluginCommandName(value) {
5894
+ const trimmed = value.trim();
5895
+ if (!trimmed) throw new ConfigError("plugin command name required");
5896
+ return trimmed;
5897
+ }
5898
+ function asPluginAgentName(value) {
5899
+ const trimmed = value.trim();
5900
+ if (!trimmed) throw new ConfigError("plugin agent name required");
5901
+ return trimmed;
5902
+ }
5903
+ function parseGitHubPluginRef(raw) {
5904
+ let url = raw.trim();
5905
+ if (!url) {
5906
+ throw new ConfigError("source url required");
5907
+ }
5908
+ if (url.startsWith("git@github.com:")) {
5909
+ url = `https://github.com/${url.replace("git@github.com:", "")}`;
5910
+ }
5911
+ if (!url.includes("://")) {
5912
+ url = `https://${url}`;
5913
+ }
5914
+ const parsed = new URL(url);
5915
+ const host = parsed.hostname.replace(/^www\./, "").toLowerCase();
5916
+ if (host !== "github.com" && host !== "raw.githubusercontent.com") {
5917
+ throw new ConfigError(`unsupported host: ${parsed.hostname}`);
5918
+ }
5919
+ let ref = parsed.searchParams.get("ref")?.trim() || "";
5920
+ const parts = parsed.pathname.split("/").filter(Boolean);
5921
+ if (parts.length < 2) {
5922
+ throw new ConfigError("invalid github url: expected /owner/repo");
5923
+ }
5924
+ const owner = parts[0];
5925
+ let repoPart = parts[1].replace(/\.git$/i, "");
5926
+ const atIdx = repoPart.indexOf("@");
5927
+ if (atIdx > 0 && atIdx < repoPart.length - 1) {
5928
+ if (!ref) {
5929
+ ref = repoPart.slice(atIdx + 1);
5930
+ }
5931
+ repoPart = repoPart.slice(0, atIdx);
5932
+ }
5933
+ const repo = repoPart;
5934
+ let rest = parts.slice(2);
5935
+ if (host === "github.com" && rest.length >= 2 && (rest[0] === "tree" || rest[0] === "blob")) {
5936
+ if (!ref) {
5937
+ ref = rest[1];
5938
+ }
5939
+ rest = rest.slice(2);
5940
+ }
5941
+ if (host === "raw.githubusercontent.com") {
5942
+ if (!rest.length) {
5943
+ throw new ConfigError("invalid raw github url");
5944
+ }
5945
+ if (!ref) {
5946
+ ref = rest[0];
5947
+ }
5948
+ rest = rest.slice(1);
5949
+ }
5950
+ let repoPath = rest.join("/");
5951
+ repoPath = repoPath.replace(/^\/+|\/+$/g, "");
5952
+ if (/plugin\.md$/i.test(repoPath) || /skill\.md$/i.test(repoPath)) {
5953
+ repoPath = repoPath.split("/").slice(0, -1).join("/");
5954
+ }
5955
+ if (/\.md$/i.test(repoPath)) {
5956
+ const commandsIdx = repoPath.indexOf("/commands/");
5957
+ if (commandsIdx >= 0) {
5958
+ repoPath = repoPath.slice(0, commandsIdx);
5959
+ }
5960
+ const agentsIdx = repoPath.indexOf("/agents/");
5961
+ if (agentsIdx >= 0) {
5962
+ repoPath = repoPath.slice(0, agentsIdx);
5963
+ }
5964
+ repoPath = repoPath.replace(/^\/+|\/+$/g, "");
5965
+ }
5966
+ if (!ref) {
5967
+ ref = DEFAULT_PLUGIN_REF;
5968
+ }
5969
+ const canonical = repoPath ? `github.com/${owner}/${repo}@${ref}/${repoPath}` : `github.com/${owner}/${repo}@${ref}`;
5970
+ return { owner, repo, ref, repoPath, canonical };
5971
+ }
5972
+ function specRequiresTools(spec) {
5973
+ for (const node of spec.nodes) {
5974
+ if (node.type === WorkflowNodeTypesIntent.LLM && node.tools?.length) {
5975
+ return true;
5976
+ }
5977
+ if (node.type === WorkflowNodeTypesIntent.MapFanout && node.subnode) {
5978
+ const sub = node.subnode;
5979
+ if (sub.tools?.length) {
5980
+ return true;
5981
+ }
5982
+ }
5983
+ }
5984
+ return false;
5985
+ }
3971
5986
 
3972
5987
  // src/http.ts
3973
5988
  var HTTPClient = class {
@@ -4349,29 +6364,184 @@ var CustomerResponsesClient = class {
4349
6364
  mergeCustomerOptions(this.customerId, options)
4350
6365
  );
4351
6366
  }
4352
- async text(system, user, options = {}) {
4353
- return this.base.textForCustomer(
4354
- this.customerId,
4355
- system,
4356
- user,
4357
- mergeCustomerOptions(this.customerId, options)
4358
- );
6367
+ async text(system, user, options = {}) {
6368
+ return this.base.textForCustomer(
6369
+ this.customerId,
6370
+ system,
6371
+ user,
6372
+ mergeCustomerOptions(this.customerId, options)
6373
+ );
6374
+ }
6375
+ async streamTextDeltas(system, user, options = {}) {
6376
+ return this.base.streamTextDeltasForCustomer(
6377
+ this.customerId,
6378
+ system,
6379
+ user,
6380
+ mergeCustomerOptions(this.customerId, options)
6381
+ );
6382
+ }
6383
+ };
6384
+ var CustomerScopedModelRelay = class {
6385
+ constructor(responses, customerId, baseUrl) {
6386
+ const normalized = normalizeCustomerId(customerId);
6387
+ this.responses = new CustomerResponsesClient(responses, normalized);
6388
+ this.customerId = normalized;
6389
+ this.baseUrl = baseUrl;
6390
+ }
6391
+ };
6392
+
6393
+ // src/tool_builder.ts
6394
+ function formatZodError(error) {
6395
+ if (error && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
6396
+ const issues = error.issues;
6397
+ return issues.map((issue) => {
6398
+ const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
6399
+ const msg = issue.message || "invalid";
6400
+ return path ? `${path}: ${msg}` : msg;
6401
+ }).join("; ");
6402
+ }
6403
+ return String(error);
6404
+ }
6405
+ var ToolBuilder = class {
6406
+ constructor() {
6407
+ this.entries = [];
6408
+ }
6409
+ /**
6410
+ * Add a tool with a Zod schema and handler.
6411
+ *
6412
+ * The handler receives parsed and validated arguments matching the schema.
6413
+ *
6414
+ * @param name - Tool name (must be unique)
6415
+ * @param description - Human-readable description of what the tool does
6416
+ * @param schema - Zod schema for the tool's parameters
6417
+ * @param handler - Function to execute when the tool is called
6418
+ * @returns this for chaining
6419
+ *
6420
+ * @example
6421
+ * ```typescript
6422
+ * tools.add(
6423
+ * "search_web",
6424
+ * "Search the web for information",
6425
+ * z.object({
6426
+ * query: z.string().describe("Search query"),
6427
+ * maxResults: z.number().optional().describe("Max results to return"),
6428
+ * }),
6429
+ * async (args) => {
6430
+ * // args is typed as { query: string; maxResults?: number }
6431
+ * return await searchAPI(args.query, args.maxResults);
6432
+ * }
6433
+ * );
6434
+ * ```
6435
+ */
6436
+ add(name, description, schema, handler) {
6437
+ const tool = createFunctionToolFromSchema(name, description, schema);
6438
+ this.entries.push({
6439
+ name,
6440
+ description,
6441
+ schema,
6442
+ handler,
6443
+ tool
6444
+ });
6445
+ return this;
6446
+ }
6447
+ /**
6448
+ * Get tool definitions for use with ResponseBuilder.tools().
6449
+ *
6450
+ * @example
6451
+ * ```typescript
6452
+ * const response = await mr.responses.create(
6453
+ * mr.responses.new()
6454
+ * .model("claude-sonnet-4-5")
6455
+ * .tools(tools.definitions())
6456
+ * .user("What's the weather in Paris?")
6457
+ * .build()
6458
+ * );
6459
+ * ```
6460
+ */
6461
+ definitions() {
6462
+ return this.entries.map((e) => e.tool);
6463
+ }
6464
+ /**
6465
+ * Get a ToolRegistry with all handlers registered.
6466
+ *
6467
+ * The handlers are wrapped to validate arguments against the schema
6468
+ * before invoking the user's handler. If validation fails, a
6469
+ * ToolArgsError is thrown (which ToolRegistry marks as retryable).
6470
+ *
6471
+ * Note: For mr.agent(), pass the ToolBuilder directly instead of calling
6472
+ * registry(). The agent method extracts both definitions and registry.
6473
+ *
6474
+ * @example
6475
+ * ```typescript
6476
+ * const registry = tools.registry();
6477
+ *
6478
+ * // Use with LocalSession (also pass definitions via defaultTools)
6479
+ * const session = mr.sessions.createLocal({
6480
+ * toolRegistry: registry,
6481
+ * defaultTools: tools.definitions(),
6482
+ * defaultModel: "claude-sonnet-4-5",
6483
+ * });
6484
+ * ```
6485
+ */
6486
+ registry() {
6487
+ const reg = new ToolRegistry();
6488
+ for (const entry of this.entries) {
6489
+ const validatingHandler = async (args, call) => {
6490
+ const result = entry.schema.safeParse(args);
6491
+ if (!result.success) {
6492
+ throw new ToolArgsError(
6493
+ `Invalid arguments for tool '${entry.name}': ${formatZodError(result.error)}`,
6494
+ call.id,
6495
+ entry.name,
6496
+ call.function?.arguments ?? ""
6497
+ );
6498
+ }
6499
+ return entry.handler(result.data, call);
6500
+ };
6501
+ reg.register(entry.name, validatingHandler);
6502
+ }
6503
+ return reg;
6504
+ }
6505
+ /**
6506
+ * Get both definitions and registry.
6507
+ *
6508
+ * Useful when you need both for manual tool handling.
6509
+ *
6510
+ * @example
6511
+ * ```typescript
6512
+ * const { definitions, registry } = tools.build();
6513
+ *
6514
+ * const response = await mr.responses.create(
6515
+ * mr.responses.new()
6516
+ * .model("claude-sonnet-4-5")
6517
+ * .tools(definitions)
6518
+ * .user("What's the weather?")
6519
+ * .build()
6520
+ * );
6521
+ *
6522
+ * if (hasToolCalls(response)) {
6523
+ * const results = await registry.executeAll(response.output[0].toolCalls!);
6524
+ * // ...
6525
+ * }
6526
+ * ```
6527
+ */
6528
+ build() {
6529
+ return {
6530
+ definitions: this.definitions(),
6531
+ registry: this.registry()
6532
+ };
4359
6533
  }
4360
- async streamTextDeltas(system, user, options = {}) {
4361
- return this.base.streamTextDeltasForCustomer(
4362
- this.customerId,
4363
- system,
4364
- user,
4365
- mergeCustomerOptions(this.customerId, options)
4366
- );
6534
+ /**
6535
+ * Get the number of tools defined.
6536
+ */
6537
+ get size() {
6538
+ return this.entries.length;
4367
6539
  }
4368
- };
4369
- var CustomerScopedModelRelay = class {
4370
- constructor(responses, customerId, baseUrl) {
4371
- const normalized = normalizeCustomerId(customerId);
4372
- this.responses = new CustomerResponsesClient(responses, normalized);
4373
- this.customerId = normalized;
4374
- this.baseUrl = baseUrl;
6540
+ /**
6541
+ * Check if a tool is defined.
6542
+ */
6543
+ has(name) {
6544
+ return this.entries.some((e) => e.name === name);
4375
6545
  }
4376
6546
  };
4377
6547
 
@@ -4550,6 +6720,12 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
4550
6720
  model(model) {
4551
6721
  return this.with({ model: model.trim() });
4552
6722
  }
6723
+ maxParallelism(n) {
6724
+ return this.with({ maxParallelism: n });
6725
+ }
6726
+ inputs(decls) {
6727
+ return this.with({ inputs: decls });
6728
+ }
4553
6729
  node(node) {
4554
6730
  return this.with({ nodes: [...this.state.nodes, node] });
4555
6731
  }
@@ -4559,15 +6735,15 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
4559
6735
  return this.node(configured.build());
4560
6736
  }
4561
6737
  joinAll(id) {
4562
- return this.node({ id, type: WorkflowNodeTypesLite.JoinAll });
6738
+ return this.node({ id, type: WorkflowNodeTypesIntent.JoinAll });
4563
6739
  }
4564
6740
  joinAny(id, predicate) {
4565
- return this.node({ id, type: WorkflowNodeTypesLite.JoinAny, predicate });
6741
+ return this.node({ id, type: WorkflowNodeTypesIntent.JoinAny, predicate });
4566
6742
  }
4567
6743
  joinCollect(id, options) {
4568
6744
  return this.node({
4569
6745
  id,
4570
- type: WorkflowNodeTypesLite.JoinCollect,
6746
+ type: WorkflowNodeTypesIntent.JoinCollect,
4571
6747
  limit: options.limit,
4572
6748
  timeout_ms: options.timeoutMs,
4573
6749
  predicate: options.predicate
@@ -4576,7 +6752,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
4576
6752
  transformJSON(id, object, merge) {
4577
6753
  return this.node({
4578
6754
  id,
4579
- type: WorkflowNodeTypesLite.TransformJSON,
6755
+ type: WorkflowNodeTypesIntent.TransformJSON,
4580
6756
  object,
4581
6757
  merge
4582
6758
  });
@@ -4584,7 +6760,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
4584
6760
  mapFanout(id, options) {
4585
6761
  return this.node({
4586
6762
  id,
4587
- type: WorkflowNodeTypesLite.MapFanout,
6763
+ type: WorkflowNodeTypesIntent.MapFanout,
4588
6764
  items_from: options.itemsFrom,
4589
6765
  items_from_input: options.itemsFromInput,
4590
6766
  items_path: options.itemsPath,
@@ -4628,6 +6804,8 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
4628
6804
  kind: WorkflowKinds.WorkflowIntent,
4629
6805
  name: this.state.name,
4630
6806
  model: this.state.model,
6807
+ max_parallelism: this.state.maxParallelism,
6808
+ inputs: this.state.inputs,
4631
6809
  nodes,
4632
6810
  outputs: [...this.state.outputs]
4633
6811
  };
@@ -4635,7 +6813,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
4635
6813
  };
4636
6814
  var LLMNodeBuilder = class {
4637
6815
  constructor(id) {
4638
- this.node = { id, type: WorkflowNodeTypesLite.LLM };
6816
+ this.node = { id, type: WorkflowNodeTypesIntent.LLM };
4639
6817
  }
4640
6818
  system(text) {
4641
6819
  this.node.system = text;
@@ -4778,154 +6956,6 @@ function createMockFetchQueue(responses) {
4778
6956
  return { fetch: fetchImpl, calls };
4779
6957
  }
4780
6958
 
4781
- // src/tools_runner.ts
4782
- var ToolRunner = class {
4783
- constructor(options) {
4784
- this.registry = options.registry;
4785
- this.runsClient = options.runsClient;
4786
- this.customerId = options.customerId;
4787
- this.onBeforeExecute = options.onBeforeExecute;
4788
- this.onAfterExecute = options.onAfterExecute;
4789
- this.onSubmitted = options.onSubmitted;
4790
- this.onError = options.onError;
4791
- }
4792
- /**
4793
- * Handles a node_waiting event by executing tools and submitting results.
4794
- *
4795
- * @param runId - The run ID
4796
- * @param nodeId - The node ID that is waiting
4797
- * @param waiting - The waiting state with pending tool calls
4798
- * @returns The submission response with accepted count and new status
4799
- *
4800
- * @example
4801
- * ```typescript
4802
- * for await (const event of client.runs.events(runId)) {
4803
- * if (event.type === "node_waiting") {
4804
- * const result = await runner.handleNodeWaiting(
4805
- * runId,
4806
- * event.node_id,
4807
- * event.waiting
4808
- * );
4809
- * console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
4810
- * }
4811
- * }
4812
- * ```
4813
- */
4814
- async handleNodeWaiting(runId, nodeId, waiting) {
4815
- const results = [];
4816
- for (const pending of waiting.pending_tool_calls) {
4817
- try {
4818
- await this.onBeforeExecute?.(pending);
4819
- const toolCall = createToolCall(
4820
- pending.tool_call.id,
4821
- pending.tool_call.name,
4822
- pending.tool_call.arguments
4823
- );
4824
- const result = await this.registry.execute(toolCall);
4825
- results.push(result);
4826
- await this.onAfterExecute?.(result);
4827
- } catch (err) {
4828
- const error = err instanceof Error ? err : new Error(String(err));
4829
- await this.onError?.(error, pending);
4830
- results.push({
4831
- toolCallId: pending.tool_call.id,
4832
- toolName: pending.tool_call.name,
4833
- result: null,
4834
- error: error.message
4835
- });
4836
- }
4837
- }
4838
- const response = await this.runsClient.submitToolResults(
4839
- runId,
4840
- {
4841
- node_id: nodeId,
4842
- step: waiting.step,
4843
- request_id: waiting.request_id,
4844
- results: results.map((r) => ({
4845
- tool_call: {
4846
- id: r.toolCallId,
4847
- name: r.toolName
4848
- },
4849
- output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
4850
- }))
4851
- },
4852
- { customerId: this.customerId }
4853
- );
4854
- await this.onSubmitted?.(runId, response.accepted, response.status);
4855
- return {
4856
- accepted: response.accepted,
4857
- status: response.status,
4858
- results
4859
- };
4860
- }
4861
- /**
4862
- * Processes a stream of run events, automatically handling node_waiting events.
4863
- *
4864
- * This is the main entry point for running a workflow with client-side tools.
4865
- * It yields all events through (including node_waiting after handling).
4866
- *
4867
- * @param runId - The run ID to process
4868
- * @param events - AsyncIterable of run events (from RunsClient.events())
4869
- * @yields All run events, with node_waiting events handled automatically
4870
- *
4871
- * @example
4872
- * ```typescript
4873
- * const run = await client.runs.create(workflowSpec);
4874
- * const eventStream = client.runs.events(run.run_id);
4875
- *
4876
- * for await (const event of runner.processEvents(run.run_id, eventStream)) {
4877
- * switch (event.type) {
4878
- * case "node_started":
4879
- * console.log(`Node ${event.node_id} started`);
4880
- * break;
4881
- * case "node_succeeded":
4882
- * console.log(`Node ${event.node_id} succeeded`);
4883
- * break;
4884
- * case "run_succeeded":
4885
- * console.log("Run completed!");
4886
- * break;
4887
- * }
4888
- * }
4889
- * ```
4890
- */
4891
- async *processEvents(runId, events) {
4892
- for await (const event of events) {
4893
- if (event.type === "node_waiting") {
4894
- const waitingEvent = event;
4895
- try {
4896
- await this.handleNodeWaiting(
4897
- runId,
4898
- waitingEvent.node_id,
4899
- waitingEvent.waiting
4900
- );
4901
- } catch (err) {
4902
- const error = err instanceof Error ? err : new Error(String(err));
4903
- await this.onError?.(error);
4904
- throw error;
4905
- }
4906
- }
4907
- yield event;
4908
- }
4909
- }
4910
- /**
4911
- * Checks if a run event is a node_waiting event.
4912
- * Utility for filtering events when not using processEvents().
4913
- */
4914
- static isNodeWaiting(event) {
4915
- return event.type === "node_waiting";
4916
- }
4917
- /**
4918
- * Checks if a run status is terminal (succeeded, failed, or canceled).
4919
- * Utility for determining when to stop polling.
4920
- */
4921
- static isTerminalStatus(status) {
4922
- return status === "succeeded" || status === "failed" || status === "canceled";
4923
- }
4924
- };
4925
- function createToolRunner(options) {
4926
- return new ToolRunner(options);
4927
- }
4928
-
4929
6959
  // src/generated/index.ts
4930
6960
  var generated_exports = {};
4931
6961
 
@@ -4936,7 +6966,7 @@ __export(workflow_exports, {
4936
6966
  LLMNodeBuilder: () => LLMNodeBuilder,
4937
6967
  LLM_TEXT_OUTPUT: () => LLM_TEXT_OUTPUT,
4938
6968
  LLM_USER_MESSAGE_TEXT: () => LLM_USER_MESSAGE_TEXT,
4939
- NodeTypesLite: () => NodeTypesLite,
6969
+ NodeTypesIntent: () => NodeTypesIntent,
4940
6970
  WorkflowIntentBuilder: () => WorkflowIntentBuilder,
4941
6971
  parseNodeId: () => parseNodeId,
4942
6972
  parseOutputName: () => parseOutputName,
@@ -4945,17 +6975,17 @@ __export(workflow_exports, {
4945
6975
  workflowIntent: () => workflowIntent
4946
6976
  });
4947
6977
  var KindIntent = WorkflowKinds.WorkflowIntent;
4948
- var NodeTypesLite = {
4949
- LLM: WorkflowNodeTypesLite.LLM,
4950
- JoinAll: WorkflowNodeTypesLite.JoinAll,
4951
- JoinAny: WorkflowNodeTypesLite.JoinAny,
4952
- JoinCollect: WorkflowNodeTypesLite.JoinCollect,
4953
- TransformJSON: WorkflowNodeTypesLite.TransformJSON,
4954
- MapFanout: WorkflowNodeTypesLite.MapFanout
6978
+ var NodeTypesIntent = {
6979
+ LLM: WorkflowNodeTypesIntent.LLM,
6980
+ JoinAll: WorkflowNodeTypesIntent.JoinAll,
6981
+ JoinAny: WorkflowNodeTypesIntent.JoinAny,
6982
+ JoinCollect: WorkflowNodeTypesIntent.JoinCollect,
6983
+ TransformJSON: WorkflowNodeTypesIntent.TransformJSON,
6984
+ MapFanout: WorkflowNodeTypesIntent.MapFanout
4955
6985
  };
4956
6986
 
4957
6987
  // src/index.ts
4958
- var ModelRelay = class _ModelRelay {
6988
+ var _ModelRelay = class _ModelRelay {
4959
6989
  static fromSecretKey(secretKey, options = {}) {
4960
6990
  return new _ModelRelay({ ...options, key: parseSecretKey(secretKey) });
4961
6991
  }
@@ -5004,22 +7034,188 @@ var ModelRelay = class _ModelRelay {
5004
7034
  });
5005
7035
  this.images = new ImagesClient(this.http, auth);
5006
7036
  this.sessions = new SessionsClient(this, this.http, auth);
7037
+ this.stateHandles = new StateHandlesClient(this.http, auth);
7038
+ this.messages = new MessagesClient(this.http, auth);
5007
7039
  this.tiers = new TiersClient(this.http, { apiKey, accessToken });
7040
+ this.plugins = new PluginsClient({
7041
+ responses: this.responses,
7042
+ http: this.http,
7043
+ auth,
7044
+ runs: this.runs
7045
+ });
5008
7046
  }
5009
7047
  forCustomer(customerId) {
5010
7048
  return new CustomerScopedModelRelay(this.responses, customerId, this.baseUrl);
5011
7049
  }
7050
+ /**
7051
+ * Simple chat completion with system and user prompt.
7052
+ *
7053
+ * Returns the full Response object for access to usage, model, etc.
7054
+ *
7055
+ * @example
7056
+ * ```typescript
7057
+ * const response = await mr.chat("claude-sonnet-4-5", "Hello!");
7058
+ * console.log(response.output);
7059
+ * console.log(response.usage);
7060
+ * ```
7061
+ *
7062
+ * @example With system prompt
7063
+ * ```typescript
7064
+ * const response = await mr.chat("claude-sonnet-4-5", "Explain quantum computing", {
7065
+ * system: "You are a physics professor",
7066
+ * });
7067
+ * ```
7068
+ */
7069
+ async chat(model, prompt, options = {}) {
7070
+ const { system, customerId, ...reqOptions } = options;
7071
+ let builder = this.responses.new().model(asModelId(model));
7072
+ if (system) {
7073
+ builder = builder.system(system);
7074
+ }
7075
+ builder = builder.user(prompt);
7076
+ if (customerId) {
7077
+ builder = builder.customerId(customerId);
7078
+ }
7079
+ return this.responses.create(builder.build(), reqOptions);
7080
+ }
7081
+ /**
7082
+ * Simple prompt that returns just the text response.
7083
+ *
7084
+ * The most ergonomic way to get a quick answer.
7085
+ *
7086
+ * @example
7087
+ * ```typescript
7088
+ * const answer = await mr.ask("claude-sonnet-4-5", "What is 2 + 2?");
7089
+ * console.log(answer); // "4"
7090
+ * ```
7091
+ *
7092
+ * @example With system prompt
7093
+ * ```typescript
7094
+ * const haiku = await mr.ask("claude-sonnet-4-5", "Write about the ocean", {
7095
+ * system: "You are a poet who only writes haikus",
7096
+ * });
7097
+ * ```
7098
+ */
7099
+ async ask(model, prompt, options = {}) {
7100
+ const response = await this.chat(model, prompt, options);
7101
+ return extractAssistantText(response.output);
7102
+ }
7103
+ /**
7104
+ * Run an agentic tool loop to completion.
7105
+ *
7106
+ * Runs API calls in a loop until the model stops calling tools
7107
+ * or maxTurns is reached.
7108
+ *
7109
+ * @example
7110
+ * ```typescript
7111
+ * import { z } from "zod";
7112
+ *
7113
+ * const tools = mr.tools()
7114
+ * .add("read_file", "Read a file", z.object({ path: z.string() }), async (args) => {
7115
+ * return fs.readFile(args.path, "utf-8");
7116
+ * })
7117
+ * .add("write_file", "Write a file", z.object({ path: z.string(), content: z.string() }), async (args) => {
7118
+ * await fs.writeFile(args.path, args.content);
7119
+ * return "File written successfully";
7120
+ * });
7121
+ *
7122
+ * const result = await mr.agent("claude-sonnet-4-5", {
7123
+ * tools,
7124
+ * prompt: "Read config.json and add a version field",
7125
+ * });
7126
+ *
7127
+ * console.log(result.output); // Final text response
7128
+ * console.log(result.usage); // Total tokens used
7129
+ * ```
7130
+ *
7131
+ * @example With system prompt and maxTurns
7132
+ * ```typescript
7133
+ * const result = await mr.agent("claude-sonnet-4-5", {
7134
+ * tools,
7135
+ * prompt: "Refactor the auth module",
7136
+ * system: "You are a senior TypeScript developer",
7137
+ * maxTurns: 50, // or ModelRelay.NO_TURN_LIMIT for unlimited
7138
+ * });
7139
+ * ```
7140
+ */
7141
+ async agent(model, options) {
7142
+ const { definitions, registry } = options.tools.build();
7143
+ const maxTurns = options.maxTurns ?? _ModelRelay.DEFAULT_MAX_TURNS;
7144
+ const modelId = asModelId(model);
7145
+ const input = [];
7146
+ if (options.system) {
7147
+ input.push(createSystemMessage(options.system));
7148
+ }
7149
+ input.push(createUserMessage(options.prompt));
7150
+ const outcome = await runToolLoop({
7151
+ client: this.responses,
7152
+ input,
7153
+ tools: definitions,
7154
+ registry,
7155
+ maxTurns,
7156
+ buildRequest: (builder) => builder.model(modelId)
7157
+ });
7158
+ if (outcome.status !== "complete") {
7159
+ throw new ConfigError("agent tool loop requires a tool registry");
7160
+ }
7161
+ return {
7162
+ output: outcome.output,
7163
+ usage: outcome.usage,
7164
+ response: outcome.response
7165
+ };
7166
+ }
7167
+ /**
7168
+ * Creates a fluent tool builder for defining tools with Zod schemas.
7169
+ *
7170
+ * @example
7171
+ * ```typescript
7172
+ * import { z } from "zod";
7173
+ *
7174
+ * const tools = mr.tools()
7175
+ * .add("get_weather", "Get current weather", z.object({ location: z.string() }), async (args) => {
7176
+ * return { temp: 72, unit: "fahrenheit" };
7177
+ * })
7178
+ * .add("read_file", "Read a file", z.object({ path: z.string() }), async (args) => {
7179
+ * return fs.readFile(args.path, "utf-8");
7180
+ * });
7181
+ *
7182
+ * // Use with agent (pass ToolBuilder directly)
7183
+ * const result = await mr.agent("claude-sonnet-4-5", {
7184
+ * tools,
7185
+ * prompt: "What's the weather in Paris?",
7186
+ * });
7187
+ *
7188
+ * // Or get tool definitions for manual use
7189
+ * const toolDefs = tools.definitions();
7190
+ * ```
7191
+ */
7192
+ tools() {
7193
+ return new ToolBuilder();
7194
+ }
5012
7195
  };
7196
+ // =========================================================================
7197
+ // Convenience Methods (Simple Case Simple)
7198
+ // =========================================================================
7199
+ /** Default maximum turns for agent loops. */
7200
+ _ModelRelay.DEFAULT_MAX_TURNS = 100;
7201
+ /**
7202
+ * Use this for maxTurns to disable the turn limit.
7203
+ * Use with caution as this can lead to infinite loops and runaway API costs.
7204
+ */
7205
+ _ModelRelay.NO_TURN_LIMIT = Number.MAX_SAFE_INTEGER;
7206
+ var ModelRelay = _ModelRelay;
5013
7207
  function resolveBaseUrl(override) {
5014
7208
  const base = override || DEFAULT_BASE_URL;
5015
7209
  return base.replace(/\/+$/, "");
5016
7210
  }
5017
7211
  export {
5018
7212
  APIError,
7213
+ AgentMaxTurnsError,
5019
7214
  AuthClient,
5020
7215
  BillingProviders,
5021
7216
  ConfigError,
5022
7217
  ContentPartTypes,
7218
+ ContextManager,
5023
7219
  CustomerResponsesClient,
5024
7220
  CustomerScopedModelRelay,
5025
7221
  CustomerTokenProvider,
@@ -5028,6 +7224,7 @@ export {
5028
7224
  DEFAULT_CONNECT_TIMEOUT_MS,
5029
7225
  DEFAULT_REQUEST_TIMEOUT_MS,
5030
7226
  ErrorCodes,
7227
+ FileConversationStore,
5031
7228
  ImagesClient,
5032
7229
  InputItemTypes,
5033
7230
  JoinOutput,
@@ -5048,19 +7245,30 @@ export {
5048
7245
  LLM_TEXT_OUTPUT,
5049
7246
  LLM_USER_MESSAGE_TEXT,
5050
7247
  LocalSession,
5051
- MemorySessionStore,
7248
+ MAX_STATE_HANDLE_TTL_SECONDS,
7249
+ MemoryConversationStore,
5052
7250
  MessageRoles,
7251
+ MessagesClient,
5053
7252
  ModelRelay,
5054
7253
  ModelRelayError,
7254
+ OrchestrationModes,
5055
7255
  OutputFormatTypes,
5056
7256
  OutputItemTypes,
5057
7257
  PathEscapeError,
7258
+ PluginConverter,
7259
+ PluginLoader,
7260
+ PluginOrchestrationError,
7261
+ PluginOrchestrationErrorCodes,
7262
+ PluginRunner,
7263
+ PluginToolNames,
7264
+ PluginsClient,
5058
7265
  ResponsesClient,
5059
7266
  ResponsesStream,
5060
7267
  RunsClient,
5061
7268
  RunsEventStream,
5062
7269
  SDK_VERSION,
5063
7270
  SessionsClient,
7271
+ SqliteConversationStore,
5064
7272
  StopReasons,
5065
7273
  StreamProtocolError,
5066
7274
  StreamTimeoutError,
@@ -5071,18 +7279,19 @@ export {
5071
7279
  TiersClient,
5072
7280
  ToolArgsError,
5073
7281
  ToolArgumentError,
7282
+ ToolBuilder,
5074
7283
  ToolCallAccumulator,
5075
7284
  ToolChoiceTypes,
5076
7285
  ToolRegistry,
5077
7286
  ToolRunner,
5078
7287
  ToolTypes,
5079
7288
  TransportError,
7289
+ USER_ASK_TOOL_NAME,
5080
7290
  WORKFLOWS_COMPILE_PATH,
5081
- WebToolIntents,
5082
7291
  WorkflowIntentBuilder,
5083
7292
  WorkflowKinds,
5084
- WorkflowNodeTypesLite as WorkflowNodeTypes,
5085
- WorkflowNodeTypesLite,
7293
+ WorkflowNodeTypesIntent as WorkflowNodeTypes,
7294
+ WorkflowNodeTypesIntent,
5086
7295
  WorkflowValidationError,
5087
7296
  WorkflowsClient,
5088
7297
  asModelId,
@@ -5096,19 +7305,22 @@ export {
5096
7305
  createAccessTokenAuth,
5097
7306
  createApiKeyAuth,
5098
7307
  createAssistantMessage,
7308
+ createFileConversationStore,
5099
7309
  createFunctionCall,
5100
7310
  createFunctionTool,
5101
- createFunctionToolFromSchema,
5102
7311
  createLocalSession,
5103
- createMemorySessionStore,
7312
+ createMemoryConversationStore,
5104
7313
  createMockFetchQueue,
7314
+ createModelContextResolver,
5105
7315
  createRetryMessages,
7316
+ createSqliteConversationStore,
5106
7317
  createSystemMessage,
5107
7318
  createToolCall,
5108
7319
  createToolRunner,
7320
+ createTypedTool,
5109
7321
  createUsage,
7322
+ createUserAskTool,
5110
7323
  createUserMessage,
5111
- createWebTool,
5112
7324
  defaultRetryHandler,
5113
7325
  defaultTierModelId,
5114
7326
  executeWithRetry,
@@ -5116,10 +7328,18 @@ export {
5116
7328
  formatToolErrorForModel,
5117
7329
  generateSessionId,
5118
7330
  generated_exports as generated,
7331
+ getAllToolCalls,
7332
+ getAssistantText,
5119
7333
  getRetryableErrors,
7334
+ getToolArgs,
7335
+ getToolArgsRaw,
7336
+ getToolName,
7337
+ getTypedToolCall,
7338
+ getTypedToolCalls,
5120
7339
  hasRetryableErrors,
5121
7340
  hasToolCalls,
5122
7341
  isSecretKey,
7342
+ isUserAskToolCall,
5123
7343
  llm,
5124
7344
  mergeMetrics,
5125
7345
  mergeTrace,
@@ -5135,15 +7355,20 @@ export {
5135
7355
  parsePlanHash,
5136
7356
  parseRunId,
5137
7357
  parseSecretKey,
5138
- parseToolArgs,
5139
- parseToolArgsRaw,
7358
+ parseTypedToolCall,
7359
+ parseUserAskArgs,
7360
+ prepareInputWithContext,
5140
7361
  respondToToolCall,
7362
+ runToolLoop,
7363
+ serializeUserAskResult,
5141
7364
  stopReasonToString,
5142
7365
  toolChoiceAuto,
5143
7366
  toolChoiceNone,
5144
7367
  toolChoiceRequired,
5145
7368
  toolResultMessage,
5146
- tryParseToolArgs,
7369
+ truncateInputByTokens,
7370
+ userAskResultChoice,
7371
+ userAskResultFreeform,
5147
7372
  validateWithZod,
5148
7373
  workflow_exports as workflow,
5149
7374
  workflowIntent,