@matatbread/matbot-plugin-api 0.2.5 → 0.2.6

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.2.5",
7
+ "version": "0.2.6",
8
8
  "engines": {
9
9
  "node": ">=24.0.0"
10
10
  },
@@ -1,5 +1,5 @@
1
1
  import type { MatbotMachine } from './plugin.js';
2
- import type { ToolContext, ToolEvent, ToolResultOf, PromptFn, FormField } from './types.js';
2
+ import type { ToolContext, ToolEvent, ToolResultFor, PromptFn, FormField } from './types.js';
3
3
 
4
4
  /**
5
5
  * Inputs to {@link invokeTool} that a one-shot caller can't derive from the machine. A tool
@@ -25,12 +25,12 @@ export type InvokeToolOptions =
25
25
  * runs non-interactively: any attempt to prompt rejects, which a tool surfaces as a normal error
26
26
  * event. Pair with {@link toolText} to collapse the stream to its result string.
27
27
  */
28
- export function invokeTool<K extends string>(
28
+ export function invokeTool<K extends string, const P>(
29
29
  machine: MatbotMachine,
30
30
  name: K,
31
- params: unknown,
31
+ params: P,
32
32
  opts: InvokeToolOptions,
33
- ): AsyncIterable<ToolEvent<ToolResultOf<K>>> {
33
+ ): AsyncIterable<ToolEvent<ToolResultFor<K, P>>> {
34
34
  const tool = machine.tools.resolve(name);
35
35
  if (tool === null) throw new Error(`Tool "${name}" is not registered`);
36
36
 
@@ -51,7 +51,7 @@ export function invokeTool<K extends string>(
51
51
 
52
52
  // The registry is name-keyed and untyped (`ToolExecutor.execute` yields `ToolEvent<unknown>`); the
53
53
  // `ToolResults` mapping is the call-site contract, asserted here so callers read a typed result.
54
- return tool.executor.execute(params, ctx) as AsyncIterable<ToolEvent<ToolResultOf<K>>>;
54
+ return tool.executor.execute(params, ctx) as AsyncIterable<ToolEvent<ToolResultFor<K, P>>>;
55
55
  }
56
56
 
57
57
  /**
package/src/plugin.ts CHANGED
@@ -491,7 +491,13 @@ export interface MatbotPluginSpec {
491
491
  */
492
492
  export interface MatbotPlugin extends MatbotPluginSpec {
493
493
  readonly name: string; // resolver.identify(specifier)
494
- readonly specifier: string; // how it was loaded
494
+ readonly specifier: string; // how it was loaded (config entry / pre-resolved)
495
+ /** The stable URL the loader actually imported, minus any reload cache-bust stamp — a `file:` URL
496
+ * for local/remote-cached plugins, the bare specifier when nothing was pre-resolved. Lets a consumer
497
+ * map a loaded plugin back to its on-disk source without re-running specifier resolution (e.g.
498
+ * skills_compiler builds a types program over the live plugin set). Set by the loader; absent only
499
+ * on hosts that construct MatbotPlugin by hand. */
500
+ readonly resolvedUrl?: string;
495
501
  readonly source?: PluginSource;
496
502
  // The package.json `matbotRuntime`, captured at load from the same value used for the pre-import
497
503
  // gate (the host reads it once; downstream just reads this). Absent ⇒ undeclared. Stored rather
package/src/types.ts CHANGED
@@ -431,6 +431,11 @@ export interface KnowledgeEntry {
431
431
 
432
432
  export interface KnowledgeIndex {
433
433
  index(entry: KnowledgeEntry): Promise<void>;
434
+ /** Remove the entry with this id, if present (idempotent — removing an absent id is a no-op). The
435
+ * id is the index's sole primary key (`index` replaces by it), so retraction is by id alone; the
436
+ * index never inspects an entry's opaque `source`. The party that indexed an entry owns retracting
437
+ * it — e.g. a skill manager retracts a hidden/deleted skill rather than the index policing tenants. */
438
+ remove(id: string): Promise<void>;
434
439
  search(terms: Array<{ term: string; context?: string }>, signal: AbortSignal): Promise<KnowledgeEntry[]>;
435
440
  /** Enumerate all indexed entries. When present, register('KnowledgeIndex', …) drains these into the incoming backend. */
436
441
  entries?(): Iterable<KnowledgeEntry>;
@@ -465,11 +470,61 @@ export type ToolEvent<Result = unknown> =
465
470
  * name with no registered entry resolves to `unknown` (the caller must narrow). This is a pure
466
471
  * type-level construct — no runtime validation; it pins, at the call site, the contract a tool's
467
472
  * executor already produces. Keep the entry in sync with what the executor actually yields.
473
+ *
474
+ * **Multi-action tools** are a weird form of overloaded function: the same tool returns different
475
+ * shapes depending on its params. Register such a tool as a union of {@link ToolResult} *arms*, each
476
+ * pairing a result with the discriminating params *pattern* that selects it — `invokeTool` matches the
477
+ * call's literal params against the patterns and narrows the result to the matching arm:
478
+ *
479
+ * declare module '@matatbread/matbot-plugin-api' {
480
+ * interface ToolResults {
481
+ * session_action:
482
+ * | ToolResult<Session, { action: 'get' }>
483
+ * | ToolResult<{ id: string; title: string }, { action: 'rename' }>;
484
+ * }
485
+ * }
486
+ * // invokeTool(machine, 'session_action', { action: 'get', sessionId }) → result is Session
487
+ *
488
+ * The pattern is *any* discriminating field(s), not just `action` (a tool keying on `interval`'s
489
+ * presence registers `ToolResult<…, { interval: string }>`). It is a *pattern*, not the full input:
490
+ * key only on the discriminant so a call carrying just that field still matches. When no arm's pattern
491
+ * matches (a non-literal discriminant, or an absence-discriminant the positive patterns can't express),
492
+ * the result falls back to the union of every arm — always sound, just less narrow.
468
493
  */
469
494
  export interface ToolResults {}
470
495
 
471
- /** The `result` value a tool named `K` yields, or `unknown` when `K` isn't registered in {@link ToolResults}. */
472
- export type ToolResultOf<K extends string> = K extends keyof ToolResults ? ToolResults[K] : unknown;
496
+ /**
497
+ * One overload arm of a multi-action tool registered in {@link ToolResults}: the `Result` it yields
498
+ * for a call whose params match the discriminating pattern `Args`. `Args` defaults to `unknown` (an
499
+ * arm that matches any params). Purely type-level — the `__` fields are phantom and never constructed.
500
+ */
501
+ export interface ToolResult<Result, Args = unknown> {
502
+ readonly __result: Result;
503
+ readonly __args: Args;
504
+ }
505
+
506
+ type ToolResultArmed<E> = E extends ToolResult<unknown, unknown> ? true : false;
507
+ type ToolResultUnion<E> = E extends ToolResult<infer R, unknown> ? R : never;
508
+ type ToolResultMatched<E, P> = E extends ToolResult<infer R, infer A> ? (P extends A ? R : never) : never;
509
+
510
+ /** The full set of `result` values a tool named `K` may yield — the union over all its arms (for an
511
+ * arm-based entry), the entry itself (for a plain entry), or `unknown` when `K` is unregistered. This
512
+ * is what an executor binds against (`ToolExecutor<ToolResultOf<'my_tool'>>`): it must cover every arm. */
513
+ export type ToolResultOf<K extends string> =
514
+ K extends keyof ToolResults
515
+ ? ToolResultArmed<ToolResults[K]> extends true ? ToolResultUnion<ToolResults[K]> : ToolResults[K]
516
+ : unknown;
517
+
518
+ /** The `result` a call to tool `K` with params `P` yields: for an arm-based entry, the arm whose
519
+ * pattern `P` matches (or the full union when none match — always sound); otherwise {@link ToolResultOf}. */
520
+ export type ToolResultFor<K extends string, P> =
521
+ K extends keyof ToolResults
522
+ ? ToolResultArmed<ToolResults[K]> extends true
523
+ ? ([ToolResultMatched<ToolResults[K], P>] extends [never]
524
+ ? ToolResultUnion<ToolResults[K]>
525
+ : ToolResultMatched<ToolResults[K], P>)
526
+ : ToolResults[K]
527
+ : unknown;
473
528
 
474
529
  /**
475
530
  * Ask the user a question and resolve with their answer. The host supplies the
@@ -520,15 +575,23 @@ export interface ToolContext {
520
575
  unloadPlugin(specifier: string): Promise<boolean>;
521
576
  }
522
577
 
523
- export interface ToolExecutor {
524
- execute(input: unknown, ctx: ToolContext): AsyncIterable<ToolEvent>;
578
+ /**
579
+ * A tool's runtime. `R` is the type of the `value` carried by its `result` event — declared once,
580
+ * at the source, so the executor's yields and the tool's {@link ToolResults} registry entry can't
581
+ * silently drift. The `unknown` default keeps untyped executors compiling untouched, and covariance
582
+ * means a narrower `ToolExecutor<X>` still satisfies `ToolExecutor` (i.e. `ToolExecutor<unknown>`),
583
+ * so the heterogeneous registry boundary (`Tool[]`) accepts any executor. Bind `R` to the registry
584
+ * entry with `ToolExecutor<ToolResultOf<'my_tool'>>` so the single augmentation is the source of truth.
585
+ */
586
+ export interface ToolExecutor<R = unknown> {
587
+ execute(input: unknown, ctx: ToolContext): AsyncIterable<ToolEvent<R>>;
525
588
  }
526
589
 
527
- export interface Tool {
590
+ export interface Tool<R = unknown> {
528
591
  name: string;
529
592
  description: string;
530
593
  inputSchema: JSONSchema;
531
- executor: ToolExecutor;
594
+ executor: ToolExecutor<R>;
532
595
  pluginName?: string;
533
596
  }
534
597