@matatbread/matbot-plugin-api 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "node",
5
5
  "browser"
6
6
  ],
7
- "version": "0.2.9",
7
+ "version": "0.3.0",
8
8
  "engines": {
9
9
  "node": ">=24.0.0"
10
10
  },
@@ -50,13 +50,13 @@ export function invokeTool<K extends string, const P>(
50
50
  };
51
51
 
52
52
  // The registry is name-keyed and untyped (`ToolExecutor.execute` yields `ToolEvent<unknown>`); the
53
- // `ToolResults` mapping is the call-site contract, asserted here so callers read a typed result.
53
+ // `ToolContracts` mapping is the call-site contract, asserted here so callers read a typed result.
54
54
  return tool.executor.execute(params, ctx) as AsyncIterable<ToolEvent<ToolResultFor<K, P>>>;
55
55
  }
56
56
 
57
57
  /**
58
58
  * Drain a tool event stream (e.g. {@link invokeTool}'s return) to its raw `result` *value*, typed:
59
- * paired with `invokeTool(machine, name, …)` it returns whatever `ToolResults[name]` declares (or
59
+ * paired with `invokeTool(machine, name, …)` it returns whatever `ToolContracts[name]` declares (or
60
60
  * `unknown` for an unregistered tool). This is the structured counterpart to {@link toolText} — use it
61
61
  * for any tool that returns data; use `toolText` when you want the result collapsed to a string.
62
62
  * Stops and throws on the first `error` event, or if the tool finished without yielding a `result`.
package/src/plugin.ts CHANGED
@@ -2,6 +2,7 @@ import type {
2
2
  FileStore, Vault, Message, ModelParameters,
3
3
  ProviderAdapter, ProviderConfig, ProviderRegistry, Tool, ToolRegistry, FrontendInfo,
4
4
  Store, Session, SystemContextRegistry, KnowledgeIndex, PromptFn, SessionRunner, Usage,
5
+ TypeScriptStripper, ToolTypeIndex,
5
6
  } from './types.js';
6
7
  import type { HookRegistry } from './hooks.js';
7
8
 
@@ -92,6 +93,10 @@ export interface PluginSelf {
92
93
  */
93
94
  export interface MatbotServices {
94
95
  readonly StorageBackend?: StorageBackend | undefined;
96
+ /** Optional, node-only tool-type index (a `.d.ts` of what tool calls resolve to), used by code
97
+ * generators/composers so the model isn't guessing return shapes. Absent where no TS program can run
98
+ * (the browser); consumers must degrade. Provided by the `tool-types` plugin. See {@link ToolTypeIndex}. */
99
+ readonly ToolTypeIndex?: ToolTypeIndex | undefined;
95
100
  /** The live vault — also the `register('Vault', impl)` swap key. Capture-safe behind a proxy, so a
96
101
  * reference held across a swap keeps resolving to the live backend. Always present (boot default). */
97
102
  readonly Vault: Vault;
@@ -119,6 +124,11 @@ export interface MatbotRuntime {
119
124
  /** The calling plugin's own settings store. Scoped to the plugin — it cannot reach another's. */
120
125
  settings(): PluginSettings;
121
126
 
127
+ /** Host-provided, per-platform TypeScript type-stripper. Always present (every execution environment
128
+ * supplies one); not registerable/swappable. Use it to erase types from source you compile at runtime
129
+ * rather than importing a platform-specific stripper. See {@link TypeScriptStripper}. */
130
+ readonly TypeScriptStripper: TypeScriptStripper;
131
+
122
132
  /**
123
133
  * Hot-load a plugin by specifier into the running process. Returns the loaded plugin.
124
134
  * `prompt`, when supplied, resolves tool-name collisions interactively during the new
package/src/types.ts CHANGED
@@ -441,6 +441,57 @@ export interface KnowledgeIndex {
441
441
  entries?(): Iterable<KnowledgeEntry>;
442
442
  }
443
443
 
444
+ // ── TypeScript ──────────────────────────────────────────────────────────────
445
+
446
+ /**
447
+ * Erases TypeScript types from a source string, returning runnable JavaScript. Type-stripping is
448
+ * foundational — the harness ships raw `.ts` and any code compiled at runtime must have its types
449
+ * removed before it can execute — so every execution environment provides one, and it lives as a fixed
450
+ * runtime capability (on {@link MatbotRuntime}) the host supplies per platform, not a swappable service:
451
+ * node uses its built-in `stripTypeScriptTypes`, the browser bundle uses sucrase. A consumer that
452
+ * compiles source at runtime (e.g. `tool_function`) calls this instead of importing a platform stripper.
453
+ *
454
+ * `strip` may be async: a platform whose stripper loads lazily (the browser fetches sucrase on first use)
455
+ * returns a promise; a synchronous stripper (node) returns the string directly — callers `await` either.
456
+ * It is type-erasure, not full transpilation: node's stripper is erasable-only (enums/namespaces throw),
457
+ * sucrase is more permissive, so authors should keep to erasable TypeScript to stay portable.
458
+ */
459
+ export interface TypeScriptStripper {
460
+ strip(source: string): string | Promise<string>;
461
+ }
462
+
463
+ /**
464
+ * Optional, node-only developer-experience service: the live `.d.ts` of the types the loaded tools
465
+ * expose — what `toolResult(invokeTool(…, name, …))`, or a `function-tools` `await tool.name(…)`, resolves
466
+ * to — derived by compiling each loaded plugin's `declare module '@matatbread/matbot-plugin-api'`
467
+ * augmentations. The runtime registry can't supply this (result types are erased); only a TypeScript
468
+ * program reading the source can. Consumers that generate or compose tool-calling code use it so the model
469
+ * isn't guessing return shapes. Absent where no TypeScript program can run (the browser today) — a consumer
470
+ * must degrade (guess-and-run) when `services.ToolTypeIndex` is undefined.
471
+ *
472
+ * The result is rebuilt lazily and cached, invalidated when the tool set changes. Tools the source scan
473
+ * can't reach (a `function-tools` function) contribute their types by declaring a `toolContract` string on
474
+ * their registered {@link Tool} — identical in shape to a `ToolContracts` arm; the index splices it off the
475
+ * live registry, so no separate registration step is needed.
476
+ */
477
+ export interface ToolTypeIndex {
478
+ /** Self-contained type declarations as a `.d.ts` string: the source-derived `ToolContracts` augmentations
479
+ * merged with the arms of every other live tool, plus `declare const tool: ToolProxy` — the overloaded
480
+ * proxy a generator writes `await tool.x(params)` against. */
481
+ dts(): Promise<string>;
482
+ /** Type-check a TypeScript `snippet` against exactly the {@link dts} above — the `tool` proxy ({@link
483
+ * ToolProxy}: each multi-action tool an overload set, so `await tool.x(params)` narrows its result by the
484
+ * params) is in scope. Returns human-readable diagnostics scoped to the snippet ([] means clean). A
485
+ * composer uses it to catch bad tool-call code before running/registering it. */
486
+ check(snippet: string): Promise<string[]>;
487
+ /** Per live tool (that has a contract): the `params`/`result` wire text, flattened from the one contract —
488
+ * a source tool's `ToolContracts` arms (via the source scan) or a source-less tool's `toolContract` string.
489
+ * The single contract is thus also the source of the wire description; the host folds this into the
490
+ * outgoing tool descriptions at the turn's dispatch edge. A tool with only a loose `inputSchema` (no
491
+ * contract) is absent. */
492
+ wireContracts(): Promise<Record<string, { params: string; result: string }>>;
493
+ }
494
+
444
495
  // ── Tools ─────────────────────────────────────────────────────────────────────
445
496
 
446
497
  export type ToolEvent<Result = unknown> =
@@ -462,7 +513,7 @@ export type ToolEvent<Result = unknown> =
462
513
  * concrete result type back from {@link invokeTool} + `toolResult` instead of `unknown`:
463
514
  *
464
515
  * declare module '@matatbread/matbot-plugin-api' {
465
- * interface ToolResults { find_fact: string[] | null }
516
+ * interface ToolContracts { find_fact: string[] | null }
466
517
  * }
467
518
  *
468
519
  * The tool name is the discriminator: `invokeTool(machine, 'find_fact', …)` is typed
@@ -472,60 +523,85 @@ export type ToolEvent<Result = unknown> =
472
523
  * executor already produces. Keep the entry in sync with what the executor actually yields.
473
524
  *
474
525
  * **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
526
+ * shapes depending on its params. Register such a tool as a union of {@link ToolContract} *arms*, each
476
527
  * pairing a result with the discriminating params *pattern* that selects it — `invokeTool` matches the
477
528
  * call's literal params against the patterns and narrows the result to the matching arm:
478
529
  *
479
530
  * declare module '@matatbread/matbot-plugin-api' {
480
- * interface ToolResults {
531
+ * interface ToolContracts {
481
532
  * session_action:
482
- * | ToolResult<Session, { action: 'get' }>
483
- * | ToolResult<{ id: string; title: string }, { action: 'rename' }>;
533
+ * | ToolContract<Session, { action: 'get' }>
534
+ * | ToolContract<{ id: string; title: string }, { action: 'rename' }>;
484
535
  * }
485
536
  * }
486
537
  * // invokeTool(machine, 'session_action', { action: 'get', sessionId }) → result is Session
487
538
  *
488
539
  * 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:
540
+ * presence registers `ToolContract<…, { interval: string }>`). It is a *pattern*, not the full input:
490
541
  * key only on the discriminant so a call carrying just that field still matches. When no arm's pattern
491
542
  * matches (a non-literal discriminant, or an absence-discriminant the positive patterns can't express),
492
543
  * the result falls back to the union of every arm — always sound, just less narrow.
493
544
  */
494
- export interface ToolResults {}
545
+ export interface ToolContracts {}
495
546
 
496
547
  /**
497
- * One overload arm of a multi-action tool registered in {@link ToolResults}: the `Result` it yields
548
+ * One overload arm of a multi-action tool registered in {@link ToolContracts}: the `Result` it yields
498
549
  * for a call whose params match the discriminating pattern `Args`. `Args` defaults to `unknown` (an
499
550
  * arm that matches any params). Purely type-level — the `__` fields are phantom and never constructed.
500
551
  */
501
- export interface ToolResult<Result, Args = unknown> {
552
+ export interface ToolContract<Result, Args = unknown> {
502
553
  readonly __result: Result;
503
554
  readonly __args: Args;
504
555
  }
505
556
 
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;
557
+ type ToolResultArmed<E> = E extends ToolContract<unknown, unknown> ? true : false;
558
+ type ToolResultUnion<E> = E extends ToolContract<infer R, unknown> ? R : never;
559
+ type ToolResultMatched<E, P> = E extends ToolContract<infer R, infer A> ? (P extends A ? R : never) : never;
509
560
 
510
561
  /** The full set of `result` values a tool named `K` may yield — the union over all its arms (for an
511
562
  * arm-based entry), the entry itself (for a plain entry), or `unknown` when `K` is unregistered. This
512
563
  * is what an executor binds against (`ToolExecutor<ToolResultOf<'my_tool'>>`): it must cover every arm. */
513
564
  export type ToolResultOf<K extends string> =
514
- K extends keyof ToolResults
515
- ? ToolResultArmed<ToolResults[K]> extends true ? ToolResultUnion<ToolResults[K]> : ToolResults[K]
565
+ K extends keyof ToolContracts
566
+ ? ToolResultArmed<ToolContracts[K]> extends true ? ToolResultUnion<ToolContracts[K]> : ToolContracts[K]
516
567
  : unknown;
517
568
 
518
569
  /** The `result` a call to tool `K` with params `P` yields: for an arm-based entry, the arm whose
519
570
  * pattern `P` matches (or the full union when none match — always sound); otherwise {@link ToolResultOf}. */
520
571
  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]
572
+ K extends keyof ToolContracts
573
+ ? ToolResultArmed<ToolContracts[K]> extends true
574
+ ? ([ToolResultMatched<ToolContracts[K], P>] extends [never]
575
+ ? ToolResultUnion<ToolContracts[K]>
576
+ : ToolResultMatched<ToolContracts[K], P>)
577
+ : ToolContracts[K]
527
578
  : unknown;
528
579
 
580
+ // Distribute a union to its intersection: `A | B` → `A & B`. An intersection of function types is an
581
+ // overload set, so this turns a union of per-arm call signatures into an overloaded function.
582
+ type UnionToIntersection<U> =
583
+ (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
584
+
585
+ // One `ToolContracts` arm → its call signature. A fully-untyped arm (`ToolContract<unknown, unknown>`, the
586
+ // fallback for a foreign/MCP tool carrying only a JSON-Schema `inputSchema`) keeps a loose, optional-param
587
+ // signature so it stays callable; a typed arm requires its declared params.
588
+ type ArmCallSig<E> =
589
+ E extends ToolContract<infer R, infer P>
590
+ ? (unknown extends P ? (params?: unknown) => Promise<R> : (params: P) => Promise<R>)
591
+ : never;
592
+
593
+ /**
594
+ * The type of the injected `tool` proxy that `function-tools` and compiled skills call
595
+ * (`await tool.name(params)`). Each key is derived from its {@link ToolContracts} entry: a multi-arm
596
+ * (multi-action) entry becomes an **overload set** — one `(params) => Promise<result>` signature per arm —
597
+ * so `await tool.name(params)` narrows its result by the params passed, exactly the contract a code
598
+ * generator needs to write a correct call first time. A single-arm entry is one signature. This is what the
599
+ * generated `dts()` declares `tool` as; it is *derived* — never hand-authored — from the same `ToolContracts`
600
+ * augmentation that types authors' `invokeTool`/`toolResult` calls, so the human editor and the LLM see one
601
+ * source of truth.
602
+ */
603
+ export type ToolProxy = { [K in keyof ToolContracts]: UnionToIntersection<ArmCallSig<ToolContracts[K]>> };
604
+
529
605
  /**
530
606
  * Ask the user a question and resolve with their answer. The host supplies the
531
607
  * implementation — readline in the CLI, an SSE round-trip in the web frontend. This is
@@ -577,7 +653,7 @@ export interface ToolContext {
577
653
 
578
654
  /**
579
655
  * 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
656
+ * at the source, so the executor's yields and the tool's {@link ToolContracts} registry entry can't
581
657
  * silently drift. The `unknown` default keeps untyped executors compiling untouched, and covariance
582
658
  * means a narrower `ToolExecutor<X>` still satisfies `ToolExecutor` (i.e. `ToolExecutor<unknown>`),
583
659
  * so the heterogeneous registry boundary (`Tool[]`) accepts any executor. Bind `R` to the registry
@@ -591,6 +667,14 @@ export interface Tool<R = unknown> {
591
667
  name: string;
592
668
  description: string;
593
669
  inputSchema: JSONSchema;
670
+ /** The call contract as TypeScript source — a {@link ToolContract}`<Result, Params>` (or a union of arms
671
+ * for a multi-action tool), *identical in shape* to what a `ToolContracts` augmentation declares. Only a
672
+ * tool with **no scannable source** carries this — a `function-tools` function (whose signature supplies
673
+ * both halves), and any similarly runtime-defined tool: the host splices it verbatim into the generated
674
+ * dts's `ToolContracts`, and derives the wire `params`/`result` text from it. A tool WITH source declares
675
+ * its contract as a `ToolContracts` augmentation instead and omits this. Foreign tools (e.g. MCP proxies)
676
+ * that have neither fall back to the loose `inputSchema`. */
677
+ toolContract?: string;
594
678
  executor: ToolExecutor<R>;
595
679
  pluginName?: string;
596
680
  }