@matatbread/matbot-plugin-api 0.4.14 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "node",
5
5
  "browser"
6
6
  ],
7
- "version": "0.4.14",
7
+ "version": "0.4.15",
8
8
  "engines": {
9
9
  "node": ">=24.0.0"
10
10
  },
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  export type * from './types.js';
12
- export { CONFIRM_YES, CONFIRM_NO, optionValue, optionLabel, isTruncatedToolResult, renderToolCheck, renderToolCheckOmitted } from './types.js';
12
+ export { CONFIRM_YES, CONFIRM_NO, optionValue, optionLabel, isTruncatedToolResult, renderToolCheck, renderToolCheckOmitted, FUNCTION_TIMEOUT } from './types.js';
13
13
 
14
14
  export * from './base64.js';
15
15
  export * from './bytes.js';
@@ -68,7 +68,8 @@ export type InvokeToolOptions =
68
68
  * calling tool's `ctx` to forward them all (see {@link InvokeToolOptions}); everything else (vault,
69
69
  * plugin (un)loading, workdir/configPath/files) is filled from `machine`.
70
70
  *
71
- * Throws synchronously if no tool is registered under `name`. When no `prompt` is supplied the tool
71
+ * Throws synchronously if no tool is registered under `name`, or if `opts.signal` has already aborted.
72
+ * When no `prompt` is supplied the tool
72
73
  * runs non-interactively: any attempt to prompt rejects, which a tool surfaces as a normal error
73
74
  * event. Pair with {@link toolText} to collapse the stream to its result string.
74
75
  */
@@ -80,6 +81,9 @@ export function invokeTool<K extends string, const P>(
80
81
  ): AsyncIterable<ToolEvent<ToolResultFor<K, P>>> {
81
82
  const tool = machine.tools.resolve(name);
82
83
  if (tool === null) throw new Error(`Tool "${name}" is not registered`);
84
+ // A call whose turn was cancelled never starts. Otherwise a composed body looping over `await tool.x()`
85
+ // goes on calling tools after the abort, each callee deciding for itself whether to honour the signal.
86
+ if (opts.signal.aborted) throw new Error(`Tool "${name}" was not started: the call was cancelled.`);
83
87
 
84
88
  const prompt = opts.prompt ?? rejectingPrompt;
85
89
  const ctx: ToolContext = {
package/src/plugin.ts CHANGED
@@ -2,7 +2,7 @@ import type {
2
2
  FileStore, MediaStore, Principal, Vault, Message, ModelParameters,
3
3
  ProviderAdapter, ProviderConfig, ProviderRegistry, Tool, ToolRegistry, FrontendInfo,
4
4
  Store, Session, SystemContextRegistry, KnowledgeIndex, PromptFn, SessionRunner, Usage, HookRegistrar,
5
- TypeScriptStripper, ToolTypeIndex, ToolPresenter, SteeringPolicy, UserContent, PermissionGate,
5
+ TypeScriptStripper, FunctionRunner, ToolTypeIndex, ToolPresenter, SteeringPolicy, UserContent, PermissionGate,
6
6
  } from './types.js';
7
7
  import type { Notifications, Notifier } from './notify.js';
8
8
 
@@ -191,6 +191,10 @@ export interface MatbotServices {
191
191
  * `file-ref` degrades to the converters' `[Attached file: x]` note. A text-only deployment is
192
192
  * unaffected either way. See {@link MediaStore}. */
193
193
  readonly MediaStore?: MediaStore | undefined;
194
+ /** Optional bound on how long model-authored code may run without awaiting. The node host registers
195
+ * one; absent ⇒ such code runs unbounded, as it must where synchronous code cannot be interrupted.
196
+ * See {@link FunctionRunner}. */
197
+ readonly FunctionRunner?: FunctionRunner | undefined;
194
198
  }
195
199
 
196
200
  /** The assembled machine: registry services wired to the fixed runtime — what `setup()` receives. */
@@ -17,6 +17,26 @@ export interface TypeScriptStripper {
17
17
  strip(source: string): string | Promise<string>;
18
18
  }
19
19
 
20
+ /**
21
+ * Optional, host-supplied: compiles already-stripped JavaScript into an async function whose SYNCHRONOUS
22
+ * execution is bounded. Model-authored code (a `tool_function` body) runs on the one event loop every
23
+ * session, frontend and schedule shares, so a loop that never awaits freezes all of them — and nothing
24
+ * in-process can stop it, an abort signal included, since stopping needs the loop to yield. Only the
25
+ * stretches between awaits are bounded: a body waiting on a slow tool call is not doing work.
26
+ *
27
+ * Absent ⇒ a consumer compiles with `new AsyncFunction` and runs unbounded, which is all a platform with
28
+ * no way to interrupt synchronous code (the browser) can do. An embedder may register its own.
29
+ */
30
+ export interface FunctionRunner {
31
+ /** Compile `body` as the body of an async function taking `params`. Throws on a syntax error. A call
32
+ * stopped at the limit rejects with an error whose `code` is {@link FUNCTION_TIMEOUT}. */
33
+ compile(params: readonly string[], body: string): (...args: unknown[]) => Promise<unknown>;
34
+ }
35
+
36
+ /** The `code` on the error a {@link FunctionRunner} rejects with when it stops a run at its limit, so a
37
+ * consumer can tell a stopped runaway from an ordinary failure without matching message text. */
38
+ export const FUNCTION_TIMEOUT = 'FUNCTION_TIMEOUT';
39
+
20
40
  /**
21
41
  * One finding from {@link ToolTypeIndex.check} — the record, not a rendering of it.
22
42
  *