@openrouter/agent 0.7.2 → 0.9.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 (105) hide show
  1. package/README.md +728 -19
  2. package/esm/index.d.ts +24 -6
  3. package/esm/index.d.ts.map +1 -1
  4. package/esm/index.js +20 -5
  5. package/esm/index.js.map +1 -1
  6. package/esm/inner-loop/call-model.d.ts +8 -5
  7. package/esm/inner-loop/call-model.d.ts.map +1 -1
  8. package/esm/inner-loop/call-model.js +49 -40
  9. package/esm/inner-loop/call-model.js.map +1 -1
  10. package/esm/inner-loop/resume-tool-results.d.ts +128 -0
  11. package/esm/inner-loop/resume-tool-results.d.ts.map +1 -0
  12. package/esm/inner-loop/resume-tool-results.js +244 -0
  13. package/esm/inner-loop/resume-tool-results.js.map +1 -0
  14. package/esm/lib/agent-tool.d.ts +92 -0
  15. package/esm/lib/agent-tool.d.ts.map +1 -0
  16. package/esm/lib/agent-tool.js +211 -0
  17. package/esm/lib/agent-tool.js.map +1 -0
  18. package/esm/lib/async-params.d.ts +107 -6
  19. package/esm/lib/async-params.d.ts.map +1 -1
  20. package/esm/lib/async-params.js +8 -1
  21. package/esm/lib/async-params.js.map +1 -1
  22. package/esm/lib/async-tool-registry.d.ts +134 -0
  23. package/esm/lib/async-tool-registry.d.ts.map +1 -0
  24. package/esm/lib/async-tool-registry.js +352 -0
  25. package/esm/lib/async-tool-registry.js.map +1 -0
  26. package/esm/lib/async-tools.d.ts +23 -0
  27. package/esm/lib/async-tools.d.ts.map +1 -0
  28. package/esm/lib/async-tools.js +19 -0
  29. package/esm/lib/async-tools.js.map +1 -0
  30. package/esm/lib/conversation-state.d.ts +45 -1
  31. package/esm/lib/conversation-state.d.ts.map +1 -1
  32. package/esm/lib/conversation-state.js +119 -0
  33. package/esm/lib/conversation-state.js.map +1 -1
  34. package/esm/lib/doom-loop.d.ts +533 -0
  35. package/esm/lib/doom-loop.d.ts.map +1 -0
  36. package/esm/lib/doom-loop.js +1221 -0
  37. package/esm/lib/doom-loop.js.map +1 -0
  38. package/esm/lib/hooks-emit.d.ts +50 -0
  39. package/esm/lib/hooks-emit.d.ts.map +1 -0
  40. package/esm/lib/hooks-emit.js +307 -0
  41. package/esm/lib/hooks-emit.js.map +1 -0
  42. package/esm/lib/hooks-manager.d.ts +141 -0
  43. package/esm/lib/hooks-manager.d.ts.map +1 -0
  44. package/esm/lib/hooks-manager.js +282 -0
  45. package/esm/lib/hooks-manager.js.map +1 -0
  46. package/esm/lib/hooks-matchers.d.ts +12 -0
  47. package/esm/lib/hooks-matchers.d.ts.map +1 -0
  48. package/esm/lib/hooks-matchers.js +26 -0
  49. package/esm/lib/hooks-matchers.js.map +1 -0
  50. package/esm/lib/hooks-resolve.d.ts +17 -0
  51. package/esm/lib/hooks-resolve.d.ts.map +1 -0
  52. package/esm/lib/hooks-resolve.js +42 -0
  53. package/esm/lib/hooks-resolve.js.map +1 -0
  54. package/esm/lib/hooks-schemas.d.ts +221 -0
  55. package/esm/lib/hooks-schemas.d.ts.map +1 -0
  56. package/esm/lib/hooks-schemas.js +238 -0
  57. package/esm/lib/hooks-schemas.js.map +1 -0
  58. package/esm/lib/hooks-types.d.ts +178 -0
  59. package/esm/lib/hooks-types.d.ts.map +1 -0
  60. package/esm/lib/hooks-types.js +45 -0
  61. package/esm/lib/hooks-types.js.map +1 -0
  62. package/esm/lib/model-result.d.ts +848 -12
  63. package/esm/lib/model-result.d.ts.map +1 -1
  64. package/esm/lib/model-result.js +4055 -410
  65. package/esm/lib/model-result.js.map +1 -1
  66. package/esm/lib/reusable-stream.d.ts +15 -0
  67. package/esm/lib/reusable-stream.d.ts.map +1 -1
  68. package/esm/lib/reusable-stream.js +25 -0
  69. package/esm/lib/reusable-stream.js.map +1 -1
  70. package/esm/lib/stream-transformers.d.ts +20 -0
  71. package/esm/lib/stream-transformers.d.ts.map +1 -1
  72. package/esm/lib/stream-transformers.js +45 -0
  73. package/esm/lib/stream-transformers.js.map +1 -1
  74. package/esm/lib/tool-check.d.ts +76 -0
  75. package/esm/lib/tool-check.d.ts.map +1 -0
  76. package/esm/lib/tool-check.js +241 -0
  77. package/esm/lib/tool-check.js.map +1 -0
  78. package/esm/lib/tool-concurrency.d.ts +42 -0
  79. package/esm/lib/tool-concurrency.d.ts.map +1 -0
  80. package/esm/lib/tool-concurrency.js +80 -0
  81. package/esm/lib/tool-concurrency.js.map +1 -0
  82. package/esm/lib/tool-context.d.ts +45 -1
  83. package/esm/lib/tool-context.d.ts.map +1 -1
  84. package/esm/lib/tool-context.js +60 -1
  85. package/esm/lib/tool-context.js.map +1 -1
  86. package/esm/lib/tool-executor.d.ts +52 -9
  87. package/esm/lib/tool-executor.d.ts.map +1 -1
  88. package/esm/lib/tool-executor.js +263 -21
  89. package/esm/lib/tool-executor.js.map +1 -1
  90. package/esm/lib/tool-orchestrator.d.ts.map +1 -1
  91. package/esm/lib/tool-orchestrator.js +87 -47
  92. package/esm/lib/tool-orchestrator.js.map +1 -1
  93. package/esm/lib/tool-task.d.ts +157 -0
  94. package/esm/lib/tool-task.d.ts.map +1 -0
  95. package/esm/lib/tool-task.js +206 -0
  96. package/esm/lib/tool-task.js.map +1 -0
  97. package/esm/lib/tool-types.d.ts +609 -58
  98. package/esm/lib/tool-types.d.ts.map +1 -1
  99. package/esm/lib/tool-types.js +72 -10
  100. package/esm/lib/tool-types.js.map +1 -1
  101. package/esm/lib/tool.d.ts +217 -25
  102. package/esm/lib/tool.d.ts.map +1 -1
  103. package/esm/lib/tool.js +210 -1
  104. package/esm/lib/tool.js.map +1 -1
  105. package/package.json +35 -3
@@ -1,5 +1,8 @@
1
1
  import type * as models from '@openrouter/sdk/models';
2
2
  import type { OpenResponsesResult } from '@openrouter/sdk/models';
3
+ import type { DoomLoopOption } from './doom-loop.js';
4
+ import type { HooksManager } from './hooks-manager.js';
5
+ import type { InlineHookConfig } from './hooks-types.js';
3
6
  import type { Item } from './item-types.js';
4
7
  import type { ContextInput } from './tool-context.js';
5
8
  import type { ParsedToolCall, StateAccessor, StopWhen, Tool, ToolContextMapWithShared, TurnContext } from './tool-types.js';
@@ -37,13 +40,22 @@ type BaseCallModelInput<TTools extends readonly Tool[] = readonly Tool[], TShare
37
40
  /**
38
41
  * When the loop exits because `stopWhen` was met and the last response
39
42
  * still contained tool calls, execute those pending tool calls (so they
40
- * have matching outputs) and then make one more model request with no
41
- * tools so the model produces a final text response.
43
+ * have matching outputs) and then make one more model request with
44
+ * `toolChoice: 'none'` so the model produces a final text response.
45
+ * Tools stay in the request — only calling is forbidden — so the
46
+ * prompt-cache prefix is preserved.
42
47
  *
43
- * - `true` (or `''`) — re-prompt with the accumulated conversation and no
44
- * tools.
45
- * - non-empty string — additionally append that string as a final user
46
- * message (e.g. `"Please summarize what you've learned"`).
48
+ * **Default: on.** Omitting the option behaves like `true`.
49
+ *
50
+ * - `true` / omitted — re-prompt with the accumulated conversation and a
51
+ * default final-answer directive appended as a user message
52
+ * (`DEFAULT_FINAL_RESPONSE_DIRECTIVE`). Without the directive, models
53
+ * that emit tool-call syntax as text may leak an unparsed tool call
54
+ * into the final content.
55
+ * - non-empty string — append that string as the final user message
56
+ * instead of the default (e.g. `"Please summarize what you've learned"`).
57
+ * - `''` — no final user message; tool calls are still forbidden.
58
+ * - `false` — no final turn; the run ends on the halted tool-call turn.
47
59
  *
48
60
  * The full accumulated input array and the original `instructions` are
49
61
  * sent. Manual (non-executable) tool calls in the halted turn are paired
@@ -52,6 +64,95 @@ type BaseCallModelInput<TTools extends readonly Tool[] = readonly Tool[], TShare
52
64
  * (HITL pause, approval pause, interruption, or natural completion).
53
65
  */
54
66
  allowFinalResponse?: boolean | string;
67
+ /**
68
+ * When true, throw if the final response has an empty `output` array even
69
+ * after completed tool rounds (legacy behavior). Default false: empty
70
+ * finals after tool work are retried once, then accepted with empty text.
71
+ */
72
+ strictFinalResponse?: boolean;
73
+ /** Hook system for lifecycle events. Accepts inline config or a HooksManager instance. */
74
+ hooks?: InlineHookConfig | HooksManager;
75
+ /**
76
+ * Doom-loop detection: catch runs that repeat the same tool call with
77
+ * identical arguments (including repeated empty or unparseable calls) or
78
+ * emit the same text over and over, and respond with a graduated ladder
79
+ * (observe → steer → block → stop; defaults observe@2, block@3, stop@6
80
+ * consecutive repetitions). `true` enables recommended defaults; an
81
+ * object tunes thresholds and text detection. Off by default.
82
+ *
83
+ * Tools declare precise call identity via `loopKey`: a computed function
84
+ * (return the search query, `({ command, cwd }) => ({ command, cwd })`,
85
+ * ...), a field-name array (`['command', 'cwd']` — serializable, used by
86
+ * MCP tool caches), or `false` to exempt a tool (legitimate polling);
87
+ * without one the full arguments object is the identity. Detection is
88
+ * deterministic: the same transcript always produces the same verdicts.
89
+ */
90
+ doomLoop?: DoomLoopOption;
91
+ /**
92
+ * Cancel the whole run. Aborting stops the tool-execution loop at the
93
+ * next turn boundary AND aborts the in-flight API request/stream, so a
94
+ * stalled provider fails fast instead of hanging until an outer test or
95
+ * caller timeout kills the process. The run's promises reject with the
96
+ * signal's abort reason.
97
+ *
98
+ * Composes with per-request `RequestOptions.timeoutMs` (the third
99
+ * `callModel` argument): when both are set, each request is bounded by
100
+ * whichever fires first. (Passing a raw `signal` through `RequestOptions`
101
+ * instead would silently disable the SDK's `timeoutMs` wiring.)
102
+ *
103
+ * Aborting also fires every in-flight tool's `ctx.signal` and aborts
104
+ * background tasks, so cooperative tool bodies stop working.
105
+ */
106
+ signal?: AbortSignal;
107
+ /**
108
+ * Default per-tool execution deadline in milliseconds; a tool-level
109
+ * `timeoutMs` overrides it. When the deadline elapses the round stops
110
+ * waiting — the model receives `{ error, code: 'tool_timeout' }`
111
+ * immediately and the tool's `ctx.signal` aborts. Bounds the round's
112
+ * WAIT, not the tool body: a body that ignores its signal keeps running
113
+ * detached and its result is discarded.
114
+ */
115
+ toolTimeoutMs?: number;
116
+ /**
117
+ * Concurrency limits for tool execution. A bare number is shorthand for
118
+ * `{ round: n }`.
119
+ * - `round`: max simultaneous tool executions within one tool round.
120
+ * Unbounded by default (matching previous behavior).
121
+ * - `background`: max simultaneous detached background-tool executions
122
+ * (`tool.background` work that outlived its grace window). Default 16.
123
+ * When full, work queues FIFO; queue wait counts against `timeoutMs`.
124
+ * Execution order may change under a cap; OUTPUT order never does
125
+ * (results stay in call order for prompt-cache stability).
126
+ */
127
+ toolConcurrency?: number | {
128
+ round?: number;
129
+ background?: number;
130
+ };
131
+ /**
132
+ * Behavior for async tools (`tool.background` / `tool.deferred`).
133
+ *
134
+ * `onRunEnd` decides what happens when the run would finish while
135
+ * background tasks are still in flight:
136
+ * - `'drain'` (default): wait up to `drainTimeoutMs` (default 30s),
137
+ * inject settled results, and grant up to `maxDrainTurns` (default 2)
138
+ * extra no-tool turns so the final answer incorporates them;
139
+ * - `'detach'`: return immediately — tasks keep running, results are
140
+ * dropped (persisted as `orphaned` when a StateAccessor exists);
141
+ * - `'cancel'`: abort in-flight tasks and finish.
142
+ */
143
+ asyncTools?: {
144
+ onRunEnd?: 'drain' | 'detach' | 'cancel';
145
+ drainTimeoutMs?: number;
146
+ maxDrainTurns?: number;
147
+ /**
148
+ * Whether the single universal `task` tool is registered so the model
149
+ * can check/steer/result/cancel running tasks by taskId. Default true
150
+ * when any long-running tool is present.
151
+ */
152
+ checkins?: boolean;
153
+ /** Max characters for the `transcript` check view. Default 20_000. */
154
+ maxTranscriptChars?: number;
155
+ };
55
156
  };
56
157
  /**
57
158
  * Approval params when state is provided (allows approve/reject)
@@ -1 +1 @@
1
- {"version":3,"file":"async-params.d.ts","sourceRoot":"","sources":["../../src/lib/async-params.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"async-params.d.ts","sourceRoot":"","sources":["../../src/lib/async-params.ts"],"names":[],"mappings":"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"}
@@ -47,7 +47,14 @@ export async function resolveAsyncFunctions(input, context) {
47
47
  'sharedContextSchema', // Client-side schema for shared context validation
48
48
  'onTurnStart', // Client-side turn start callback
49
49
  'onTurnEnd', // Client-side turn end callback
50
- 'allowFinalResponse', // Client-side: triggers no-tools final turn when stopWhen breaks the loop
50
+ 'allowFinalResponse', // Client-side: tunes the default toolChoice:'none' final turn when stopWhen breaks the loop
51
+ 'strictFinalResponse', // Client-side: restore throw on empty final after tool rounds
52
+ 'hooks', // Client-side hook system
53
+ 'doomLoop', // Client-side doom-loop detection config
54
+ 'signal', // Client-side run cancellation
55
+ 'toolTimeoutMs', // Client-side per-tool deadline default
56
+ 'toolConcurrency', // Client-side tool concurrency limits
57
+ 'asyncTools', // Client-side async tool (background/deferred) behavior
51
58
  ]);
52
59
  // Iterate over all keys in the input
53
60
  for (const [key, value] of Object.entries(input)) {
@@ -1 +1 @@
1
- {"version":3,"file":"async-params.js","sourceRoot":"","sources":["../../src/lib/async-params.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,SAAS,mBAAmB,CAC1B,KAAc;IAEd,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAC3B,OAKC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAExC,OAAO,GAAoC,CAAC;AAC9C,CAAC;AAoHD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAA6B,EAC7B,OAAoB;IAEpB,kCAAkC;IAClC,MAAM,eAAe,GAKjB,EAAE,CAAC;IAEP,+DAA+D;IAC/D,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;QAC/B,UAAU,EAAE,oCAAoC;QAChD,OAAO,EAAE,+BAA+B;QACxC,iBAAiB,EAAE,sCAAsC;QACzD,kBAAkB,EAAE,iCAAiC;QACrD,iBAAiB,EAAE,kCAAkC;QACrD,SAAS,EAAE,yDAAyD;QACpE,qBAAqB,EAAE,mDAAmD;QAC1E,aAAa,EAAE,kCAAkC;QACjD,WAAW,EAAE,gCAAgC;QAC7C,oBAAoB,EAAE,0EAA0E;KACjG,CAAC,CAAC;IAEH,qCAAqC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,wFAAwF;QACxF,qGAAqG;QACrG,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,yDAAyD;gBACzD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBACrD,eAAe,CAAC,IAAI,CAAC;oBACnB,GAAG;oBACH,MAAM;iBACE,CAAC,CAAC;YACd,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oDAAoD;gBACpD,MAAM,IAAI,KAAK,CACb,+CAA+C,GAAG,MAChD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,4BAA4B;YAC5B,eAAe,CAAC,IAAI,CAAC;gBACnB,GAAG;gBACH,KAAK;aACG,CAAC,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,oBAAoB,CAAC,eAAe,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"async-params.js","sourceRoot":"","sources":["../../src/lib/async-params.ts"],"names":[],"mappings":"AAmBA;;;GAGG;AACH,SAAS,mBAAmB,CAC1B,KAAc;IAEd,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAC3B,OAKC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAExC,OAAO,GAAoC,CAAC;AAC9C,CAAC;AAwND;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAA6B,EAC7B,OAAoB;IAEpB,kCAAkC;IAClC,MAAM,eAAe,GAKjB,EAAE,CAAC;IAEP,+DAA+D;IAC/D,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;QAC/B,UAAU,EAAE,oCAAoC;QAChD,OAAO,EAAE,+BAA+B;QACxC,iBAAiB,EAAE,sCAAsC;QACzD,kBAAkB,EAAE,iCAAiC;QACrD,iBAAiB,EAAE,kCAAkC;QACrD,SAAS,EAAE,yDAAyD;QACpE,qBAAqB,EAAE,mDAAmD;QAC1E,aAAa,EAAE,kCAAkC;QACjD,WAAW,EAAE,gCAAgC;QAC7C,oBAAoB,EAAE,4FAA4F;QAClH,qBAAqB,EAAE,8DAA8D;QACrF,OAAO,EAAE,0BAA0B;QACnC,UAAU,EAAE,yCAAyC;QACrD,QAAQ,EAAE,+BAA+B;QACzC,eAAe,EAAE,wCAAwC;QACzD,iBAAiB,EAAE,sCAAsC;QACzD,YAAY,EAAE,wDAAwD;KACvE,CAAC,CAAC;IAEH,qCAAqC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,wFAAwF;QACxF,qGAAqG;QACrG,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,yDAAyD;gBACzD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBACrD,eAAe,CAAC,IAAI,CAAC;oBACnB,GAAG;oBACH,MAAM;iBACE,CAAC,CAAC;YACd,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oDAAoD;gBACpD,MAAM,IAAI,KAAK,CACb,+CAA+C,GAAG,MAChD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,4BAA4B;YAC5B,eAAe,CAAC,IAAI,CAAC;gBACnB,GAAG;gBACH,KAAK;aACG,CAAC,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,oBAAoB,CAAC,eAAe,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,134 @@
1
+ import type { TaskLogEntry } from './tool-task.js';
2
+ import { ToolTask } from './tool-task.js';
3
+ import type { PendingAsyncTool } from './tool-types.js';
4
+ /**
5
+ * Outcome of a settled async tool task, harvested by the engine at turn
6
+ * boundaries (`flushAsyncToolDeliveries`) or during end-of-run drain.
7
+ */
8
+ export interface SettledToolTask {
9
+ callId: string;
10
+ taskId: string;
11
+ name: string;
12
+ status: 'completed' | 'failed' | 'cancelled' | 'timed_out';
13
+ /** Present when status is 'completed'. */
14
+ result?: unknown;
15
+ /** Present otherwise. */
16
+ error?: string;
17
+ /** The call's arguments, when tracked — feeds PostToolUse at settle. */
18
+ input?: Record<string, unknown>;
19
+ /** Wall-clock ms from task start to settlement — feeds PostToolUse. */
20
+ durationMs: number;
21
+ }
22
+ /**
23
+ * Per-run registry of async tool tasks (background, deferred, agent).
24
+ *
25
+ * Owns the in-process half of async tool support:
26
+ * - background/agent tasks: tracks the in-flight work + ToolTask (logs,
27
+ * inbox, transcript source), queues settled outcomes for turn-boundary
28
+ * harvesting, supports drain / cancel / abort-all at run end;
29
+ * - deferred tasks: tracks identity only (the work lives outside the
30
+ * process); the durable copy is mirrored to
31
+ * `ConversationState.pendingAsyncTools` by the engine.
32
+ *
33
+ * The registry knows nothing about the tool loop or concurrency pools — the
34
+ * engine (`ModelResult`) owns semaphores and decides when to harvest and how
35
+ * to deliver. Settlement is first-writer-wins: a task settles exactly once
36
+ * (cancel racing completion is safe), and settled tasks drop their
37
+ * controller references so they cannot retain the run graph.
38
+ */
39
+ export declare class AsyncToolRegistry {
40
+ private readonly tasks;
41
+ /** Settled outcomes not yet harvested by the engine. */
42
+ private settledQueue;
43
+ private taskCounter;
44
+ /** Resolvers waiting on "any task settled" (drain support). */
45
+ private settleWaiters;
46
+ /** Generate a task id for background/agent tasks (deferred bring their own). */
47
+ generateTaskId(): string;
48
+ /**
49
+ * Register a task so it is visible to steering / cancel / snapshots.
50
+ * Used for background tasks DURING their grace window — the work is in
51
+ * flight but not yet committed to async delivery. `untrack` removes it
52
+ * again when the work settles in-window (the call behaved
53
+ * synchronously; no async machinery should remain visible).
54
+ */
55
+ register(task: ToolTask): void;
56
+ /**
57
+ * Remove a task registered via `register` whose work settled inside the
58
+ * grace window, along with any settlement queued for it in the meantime
59
+ * (e.g. a cancelTask racing the in-window completion) — the call is
60
+ * yielding a synchronous output, so a later envelope for the same callId
61
+ * would be a double delivery.
62
+ */
63
+ untrack(callId: string): void;
64
+ /**
65
+ * Track an already-started background/agent task. `work` is the tool's
66
+ * in-flight run promise (output-validated). When `timeoutMs` is set the
67
+ * task is raced against it, so a body that ignores its abort signal still
68
+ * settles as `timed_out` instead of hanging the drain. The deadline is
69
+ * anchored at `task.startedAt` (the work started before tracking — a
70
+ * grace window may already have elapsed), so `timeoutMs` bounds the
71
+ * task's TOTAL runtime, matching the documented contract.
72
+ */
73
+ trackBackground(task: ToolTask, work: Promise<unknown>, options?: {
74
+ timeoutMs?: number;
75
+ }): void;
76
+ /** Track a deferred task (identity only; work lives outside the process). */
77
+ trackDeferred(entry: {
78
+ callId: string;
79
+ taskId: string;
80
+ name: string;
81
+ expiresAt?: number;
82
+ pollAfterMs?: number;
83
+ }): ToolTask;
84
+ private settle;
85
+ /** Harvest (and clear) settled outcomes queued since the last call. */
86
+ takeSettled(): SettledToolTask[];
87
+ /** True when any background/agent task is still in flight. */
88
+ hasInFlight(): boolean;
89
+ /** True when any settled outcome awaits harvesting. */
90
+ hasUnharvestedSettled(): boolean;
91
+ /** True when any task (any mode) has been tracked this run. */
92
+ hasTasks(): boolean;
93
+ /** Find the live task for a task id. */
94
+ getTask(taskId: string): ToolTask | undefined;
95
+ /** Find the live task for a call id. */
96
+ findByCallId(callId: string): ToolTask | undefined;
97
+ /** All live tasks. */
98
+ listTasks(): ToolTask[];
99
+ /** Append a log entry to a task by call id (engine log sink). */
100
+ appendLog(callId: string, data: unknown, kind?: TaskLogEntry['kind']): void;
101
+ /** Mark every still-working background/agent task as orphaned (detach mode). */
102
+ markWorkingAsOrphaned(): PendingAsyncTool[];
103
+ /** Snapshot of every tracked task (for `getAsyncTasks()` / persistence). */
104
+ snapshot(): PendingAsyncTool[];
105
+ /**
106
+ * Send a steering message to a working task's run body. Deferred tasks
107
+ * are owned by an external system — steering them is the caller's job.
108
+ * @returns true when a working in-process task received the message.
109
+ */
110
+ sendToTask(taskId: string, message: unknown): boolean;
111
+ /**
112
+ * Cancel one task by task id. Settles it as 'cancelled' immediately
113
+ * (first-writer-wins makes the racing body's own settlement a no-op) and
114
+ * aborts the controller so cooperative bodies stop working.
115
+ *
116
+ * Deferred tasks: cancellation is LOCAL-ONLY — the conversation records
117
+ * the cancel, but the external system is not notified (mirror of the
118
+ * sendToTask contract; tell the external system yourself, or use the
119
+ * tool's `.cancel()` method which persists the terminal status for
120
+ * cross-process callers).
121
+ * @returns true when a working task was found and cancelled.
122
+ */
123
+ cancelTask(taskId: string, reason?: string): boolean;
124
+ /** Abort every in-flight background/agent task (run abort / onRunEnd: 'cancel'). */
125
+ abortAll(reason?: string): void;
126
+ /**
127
+ * Wait until every in-flight task settles or `timeoutMs` elapses,
128
+ * whichever comes first. Returns true when everything settled.
129
+ */
130
+ drain(timeoutMs: number): Promise<boolean>;
131
+ /** Resolve true on the next settle event, or false after `timeoutMs`. */
132
+ private waitForSettle;
133
+ }
134
+ //# sourceMappingURL=async-tool-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-tool-registry.d.ts","sourceRoot":"","sources":["../../src/lib/async-tool-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,352 @@
1
+ import { ToolTask } from './tool-task.js';
2
+ /**
3
+ * Per-run registry of async tool tasks (background, deferred, agent).
4
+ *
5
+ * Owns the in-process half of async tool support:
6
+ * - background/agent tasks: tracks the in-flight work + ToolTask (logs,
7
+ * inbox, transcript source), queues settled outcomes for turn-boundary
8
+ * harvesting, supports drain / cancel / abort-all at run end;
9
+ * - deferred tasks: tracks identity only (the work lives outside the
10
+ * process); the durable copy is mirrored to
11
+ * `ConversationState.pendingAsyncTools` by the engine.
12
+ *
13
+ * The registry knows nothing about the tool loop or concurrency pools — the
14
+ * engine (`ModelResult`) owns semaphores and decides when to harvest and how
15
+ * to deliver. Settlement is first-writer-wins: a task settles exactly once
16
+ * (cancel racing completion is safe), and settled tasks drop their
17
+ * controller references so they cannot retain the run graph.
18
+ */
19
+ export class AsyncToolRegistry {
20
+ constructor() {
21
+ this.tasks = new Map();
22
+ /** Settled outcomes not yet harvested by the engine. */
23
+ this.settledQueue = [];
24
+ this.taskCounter = 0;
25
+ /** Resolvers waiting on "any task settled" (drain support). */
26
+ this.settleWaiters = [];
27
+ }
28
+ /** Generate a task id for background/agent tasks (deferred bring their own). */
29
+ generateTaskId() {
30
+ this.taskCounter++;
31
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
32
+ return `task_${crypto.randomUUID()}`;
33
+ }
34
+ return `task_${Date.now()}_${this.taskCounter}`;
35
+ }
36
+ /**
37
+ * Register a task so it is visible to steering / cancel / snapshots.
38
+ * Used for background tasks DURING their grace window — the work is in
39
+ * flight but not yet committed to async delivery. `untrack` removes it
40
+ * again when the work settles in-window (the call behaved
41
+ * synchronously; no async machinery should remain visible).
42
+ */
43
+ register(task) {
44
+ this.tasks.set(task.callId, task);
45
+ }
46
+ /**
47
+ * Remove a task registered via `register` whose work settled inside the
48
+ * grace window, along with any settlement queued for it in the meantime
49
+ * (e.g. a cancelTask racing the in-window completion) — the call is
50
+ * yielding a synchronous output, so a later envelope for the same callId
51
+ * would be a double delivery.
52
+ */
53
+ untrack(callId) {
54
+ this.tasks.delete(callId);
55
+ this.settledQueue = this.settledQueue.filter((s) => s.callId !== callId);
56
+ }
57
+ /**
58
+ * Track an already-started background/agent task. `work` is the tool's
59
+ * in-flight run promise (output-validated). When `timeoutMs` is set the
60
+ * task is raced against it, so a body that ignores its abort signal still
61
+ * settles as `timed_out` instead of hanging the drain. The deadline is
62
+ * anchored at `task.startedAt` (the work started before tracking — a
63
+ * grace window may already have elapsed), so `timeoutMs` bounds the
64
+ * task's TOTAL runtime, matching the documented contract.
65
+ */
66
+ trackBackground(task, work, options) {
67
+ this.tasks.set(task.callId, task);
68
+ let timer;
69
+ const timeoutMs = options?.timeoutMs;
70
+ if (timeoutMs !== undefined && timeoutMs > 0) {
71
+ const remainingMs = Math.max(0, timeoutMs - (Date.now() - task.startedAt));
72
+ timer = setTimeout(() => {
73
+ const timeoutError = new Error(`Tool "${task.toolName}" task timed out after ${timeoutMs}ms`);
74
+ // Capture the controller BEFORE settling — settle() drops the
75
+ // reference as a leak guard, and the running body must still be
76
+ // told to stop (same pattern as cancelTask / abortAll).
77
+ const controller = task.controller;
78
+ this.settle(task.callId, {
79
+ status: 'timed_out',
80
+ error: timeoutError.message,
81
+ });
82
+ controller?.abort(timeoutError);
83
+ }, remainingMs);
84
+ if (typeof timer === 'object' && 'unref' in timer && typeof timer.unref === 'function') {
85
+ timer.unref();
86
+ }
87
+ }
88
+ work
89
+ .then((result) => {
90
+ this.settle(task.callId, {
91
+ status: 'completed',
92
+ result,
93
+ });
94
+ }, (error) => {
95
+ const cancelled = task.controller?.signal.aborted ?? false;
96
+ this.settle(task.callId, {
97
+ status: cancelled ? 'cancelled' : 'failed',
98
+ error: error instanceof Error ? error.message : String(error),
99
+ });
100
+ })
101
+ .finally(() => {
102
+ if (timer !== undefined) {
103
+ clearTimeout(timer);
104
+ }
105
+ });
106
+ }
107
+ /** Track a deferred task (identity only; work lives outside the process). */
108
+ trackDeferred(entry) {
109
+ const task = new ToolTask({
110
+ taskId: entry.taskId,
111
+ callId: entry.callId,
112
+ toolName: entry.name,
113
+ mode: 'defer',
114
+ ...(entry.expiresAt !== undefined && {
115
+ expiresAt: entry.expiresAt,
116
+ }),
117
+ ...(entry.pollAfterMs !== undefined && {
118
+ pollAfterMs: entry.pollAfterMs,
119
+ }),
120
+ });
121
+ this.tasks.set(entry.callId, task);
122
+ return task;
123
+ }
124
+ settle(callId, outcome) {
125
+ const task = this.tasks.get(callId);
126
+ if (!task || task.status !== 'working') {
127
+ return; // first writer wins
128
+ }
129
+ task.status =
130
+ outcome.status === 'completed'
131
+ ? 'completed'
132
+ : outcome.status === 'cancelled'
133
+ ? 'cancelled'
134
+ : 'failed';
135
+ task.settledAt = Date.now();
136
+ if (outcome.status === 'completed') {
137
+ task.result = outcome.result;
138
+ }
139
+ else {
140
+ task.error = outcome.error;
141
+ task.appendLog(outcome.error, 'system');
142
+ }
143
+ // Leak guard: a settled task must not retain the run graph.
144
+ task.controller = undefined;
145
+ this.settledQueue.push({
146
+ callId,
147
+ taskId: task.taskId,
148
+ name: task.toolName,
149
+ durationMs: (task.settledAt ?? Date.now()) - task.startedAt,
150
+ ...(task.input !== undefined && {
151
+ input: task.input,
152
+ }),
153
+ ...outcome,
154
+ });
155
+ const waiters = this.settleWaiters;
156
+ this.settleWaiters = [];
157
+ for (const waiter of waiters) {
158
+ waiter();
159
+ }
160
+ }
161
+ /** Harvest (and clear) settled outcomes queued since the last call. */
162
+ takeSettled() {
163
+ const settled = this.settledQueue;
164
+ this.settledQueue = [];
165
+ return settled;
166
+ }
167
+ /** True when any background/agent task is still in flight. */
168
+ hasInFlight() {
169
+ for (const task of this.tasks.values()) {
170
+ if (task.mode !== 'defer' && task.status === 'working') {
171
+ return true;
172
+ }
173
+ }
174
+ return false;
175
+ }
176
+ /** True when any settled outcome awaits harvesting. */
177
+ hasUnharvestedSettled() {
178
+ return this.settledQueue.length > 0;
179
+ }
180
+ /** True when any task (any mode) has been tracked this run. */
181
+ hasTasks() {
182
+ return this.tasks.size > 0;
183
+ }
184
+ /** Find the live task for a task id. */
185
+ getTask(taskId) {
186
+ for (const task of this.tasks.values()) {
187
+ if (task.taskId === taskId) {
188
+ return task;
189
+ }
190
+ }
191
+ return undefined;
192
+ }
193
+ /** Find the live task for a call id. */
194
+ findByCallId(callId) {
195
+ return this.tasks.get(callId);
196
+ }
197
+ /** All live tasks. */
198
+ listTasks() {
199
+ return Array.from(this.tasks.values());
200
+ }
201
+ /** Append a log entry to a task by call id (engine log sink). */
202
+ appendLog(callId, data, kind) {
203
+ this.tasks.get(callId)?.appendLog(data, kind);
204
+ }
205
+ /** Mark every still-working background/agent task as orphaned (detach mode). */
206
+ markWorkingAsOrphaned() {
207
+ const orphaned = [];
208
+ for (const task of this.tasks.values()) {
209
+ if (task.mode !== 'defer' && task.status === 'working') {
210
+ task.orphaned = true;
211
+ orphaned.push({
212
+ callId: task.callId,
213
+ taskId: task.taskId,
214
+ name: task.toolName,
215
+ mode: task.mode,
216
+ status: task.status,
217
+ startedAt: task.startedAt,
218
+ orphaned: true,
219
+ ...(task.lastLog !== undefined && {
220
+ lastLog: {
221
+ at: task.lastLog.at,
222
+ text: renderLastLog(task.lastLog.data),
223
+ },
224
+ }),
225
+ });
226
+ }
227
+ }
228
+ return orphaned;
229
+ }
230
+ /** Snapshot of every tracked task (for `getAsyncTasks()` / persistence). */
231
+ snapshot() {
232
+ return Array.from(this.tasks.values(), (t) => ({
233
+ callId: t.callId,
234
+ taskId: t.taskId,
235
+ name: t.toolName,
236
+ mode: t.mode,
237
+ status: t.status,
238
+ startedAt: t.startedAt,
239
+ ...(t.expiresAt !== undefined && {
240
+ expiresAt: t.expiresAt,
241
+ }),
242
+ ...(t.pollAfterMs !== undefined && {
243
+ pollAfterMs: t.pollAfterMs,
244
+ }),
245
+ ...(t.orphaned === true && {
246
+ orphaned: true,
247
+ }),
248
+ ...(t.lastLog !== undefined && {
249
+ lastLog: {
250
+ at: t.lastLog.at,
251
+ text: renderLastLog(t.lastLog.data),
252
+ },
253
+ }),
254
+ }));
255
+ }
256
+ /**
257
+ * Send a steering message to a working task's run body. Deferred tasks
258
+ * are owned by an external system — steering them is the caller's job.
259
+ * @returns true when a working in-process task received the message.
260
+ */
261
+ sendToTask(taskId, message) {
262
+ const task = this.getTask(taskId);
263
+ if (!task || task.status !== 'working') {
264
+ return false;
265
+ }
266
+ if (task.mode === 'defer') {
267
+ throw new Error(`Task "${taskId}" is deferred — its work runs in an external system; deliver steering messages there instead.`);
268
+ }
269
+ task.send(message);
270
+ return true;
271
+ }
272
+ /**
273
+ * Cancel one task by task id. Settles it as 'cancelled' immediately
274
+ * (first-writer-wins makes the racing body's own settlement a no-op) and
275
+ * aborts the controller so cooperative bodies stop working.
276
+ *
277
+ * Deferred tasks: cancellation is LOCAL-ONLY — the conversation records
278
+ * the cancel, but the external system is not notified (mirror of the
279
+ * sendToTask contract; tell the external system yourself, or use the
280
+ * tool's `.cancel()` method which persists the terminal status for
281
+ * cross-process callers).
282
+ * @returns true when a working task was found and cancelled.
283
+ */
284
+ cancelTask(taskId, reason) {
285
+ const task = this.getTask(taskId);
286
+ if (!task || task.status !== 'working') {
287
+ return false;
288
+ }
289
+ const controller = task.controller;
290
+ this.settle(task.callId, {
291
+ status: 'cancelled',
292
+ error: reason ?? `Task ${taskId} cancelled`,
293
+ });
294
+ controller?.abort(new Error(reason ?? `Task ${taskId} cancelled`));
295
+ return true;
296
+ }
297
+ /** Abort every in-flight background/agent task (run abort / onRunEnd: 'cancel'). */
298
+ abortAll(reason) {
299
+ for (const task of this.tasks.values()) {
300
+ if (task.mode !== 'defer' && task.status === 'working') {
301
+ const controller = task.controller;
302
+ this.settle(task.callId, {
303
+ status: 'cancelled',
304
+ error: reason ?? 'Run aborted',
305
+ });
306
+ controller?.abort(new Error(reason ?? 'Run aborted'));
307
+ }
308
+ }
309
+ }
310
+ /**
311
+ * Wait until every in-flight task settles or `timeoutMs` elapses,
312
+ * whichever comes first. Returns true when everything settled.
313
+ */
314
+ async drain(timeoutMs) {
315
+ const deadline = Date.now() + timeoutMs;
316
+ while (this.hasInFlight()) {
317
+ const remaining = deadline - Date.now();
318
+ if (remaining <= 0) {
319
+ return false;
320
+ }
321
+ const settled = await this.waitForSettle(remaining);
322
+ if (!settled) {
323
+ return !this.hasInFlight();
324
+ }
325
+ }
326
+ return true;
327
+ }
328
+ /** Resolve true on the next settle event, or false after `timeoutMs`. */
329
+ waitForSettle(timeoutMs) {
330
+ return new Promise((resolve) => {
331
+ const onSettle = () => {
332
+ clearTimeout(timer);
333
+ resolve(true);
334
+ };
335
+ const timer = setTimeout(() => {
336
+ this.settleWaiters = this.settleWaiters.filter((w) => w !== onSettle);
337
+ resolve(false);
338
+ }, timeoutMs);
339
+ // Don't keep the event loop alive purely for the drain timer.
340
+ if (typeof timer === 'object' && 'unref' in timer && typeof timer.unref === 'function') {
341
+ timer.unref();
342
+ }
343
+ this.settleWaiters.push(onSettle);
344
+ });
345
+ }
346
+ }
347
+ /** Render a log entry's data to the small persisted `lastLog.text` form. */
348
+ function renderLastLog(data) {
349
+ const text = typeof data === 'string' ? data : (JSON.stringify(data) ?? String(data));
350
+ return text.length > 200 ? `${text.slice(0, 200)}…` : text;
351
+ }
352
+ //# sourceMappingURL=async-tool-registry.js.map