@matatbread/matbot-plugin-api 0.2.7 → 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 +1 -1
- package/src/invoke-tool.ts +2 -2
- package/src/plugin.ts +24 -3
- package/src/types.ts +126 -21
package/package.json
CHANGED
package/src/invoke-tool.ts
CHANGED
|
@@ -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
|
-
// `
|
|
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 `
|
|
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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
FileStore, Vault, Message, ModelParameters,
|
|
3
|
-
ProviderAdapter, ProviderConfig, Tool, ToolRegistry, FrontendInfo,
|
|
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
|
|
|
@@ -56,6 +57,13 @@ export type Runtime = 'node' | 'browser';
|
|
|
56
57
|
*/
|
|
57
58
|
export interface PluginResolver {
|
|
58
59
|
identify(specifier: string): Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* The plugin's package.json `version`, or `undefined` when it can't be read (no package.json, or a
|
|
62
|
+
* remote specifier whose version isn't baked). Read from the same nearest-named-package.json boundary
|
|
63
|
+
* as {@link identify}, so it lives here (host-specific: fs walk on node, baked manifest in the browser)
|
|
64
|
+
* rather than in the platform-neutral loader. Stamped onto {@link MatbotPlugin.version} at load.
|
|
65
|
+
*/
|
|
66
|
+
version?(specifier: string): Promise<string | undefined>;
|
|
59
67
|
/**
|
|
60
68
|
* The runtimes a plugin declares support for via its package.json `matbotRuntime`
|
|
61
69
|
* (e.g. `["node"]`, `["browser"]`, `["node","browser"]`), or `undefined` when the field is
|
|
@@ -85,6 +93,10 @@ export interface PluginSelf {
|
|
|
85
93
|
*/
|
|
86
94
|
export interface MatbotServices {
|
|
87
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;
|
|
88
100
|
/** The live vault — also the `register('Vault', impl)` swap key. Capture-safe behind a proxy, so a
|
|
89
101
|
* reference held across a swap keeps resolving to the live backend. Always present (boot default). */
|
|
90
102
|
readonly Vault: Vault;
|
|
@@ -97,7 +109,7 @@ export type MatbotMachine = MatbotServices & MatbotRuntime;
|
|
|
97
109
|
/**
|
|
98
110
|
* The fixed runtime — microcode, not a service: the agentic loop's primitives (`complete`,
|
|
99
111
|
* `createStore`), the registries you mutate but never swap wholesale (`hooks`, `tools`,
|
|
100
|
-
* `systemContext`), plugin lifecycle, and the registry API itself. Not augmentable, not registerable.
|
|
112
|
+
* `systemContext`, `providers`), plugin lifecycle, and the registry API itself. Not augmentable, not registerable.
|
|
101
113
|
*/
|
|
102
114
|
export interface MatbotRuntime {
|
|
103
115
|
complete(req: CompletionRequest): Promise<CompletionResponse>;
|
|
@@ -112,6 +124,11 @@ export interface MatbotRuntime {
|
|
|
112
124
|
/** The calling plugin's own settings store. Scoped to the plugin — it cannot reach another's. */
|
|
113
125
|
settings(): PluginSettings;
|
|
114
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
|
+
|
|
115
132
|
/**
|
|
116
133
|
* Hot-load a plugin by specifier into the running process. Returns the loaded plugin.
|
|
117
134
|
* `prompt`, when supplied, resolves tool-name collisions interactively during the new
|
|
@@ -198,7 +215,7 @@ export interface MatbotRuntime {
|
|
|
198
215
|
/** The calling plugin's own loader-established identity. Bound per-plugin inside setup(). */
|
|
199
216
|
readonly self?: PluginSelf;
|
|
200
217
|
|
|
201
|
-
readonly providers:
|
|
218
|
+
readonly providers: ProviderRegistry;
|
|
202
219
|
readonly sessions?: Store<Session>;
|
|
203
220
|
/** Per-session turn serialiser. Frontends submit and observe through this rather than calling
|
|
204
221
|
* runSession directly, so concurrent submits queue instead of clobbering the session. */
|
|
@@ -504,4 +521,8 @@ export interface MatbotPlugin extends MatbotPluginSpec {
|
|
|
504
521
|
// than re-derived so `list` can report it offline and for remote plugins, whose config-entry
|
|
505
522
|
// specifier (a github:/https URL) can't be resolved back to a package.json after the fact.
|
|
506
523
|
readonly matbotRuntime?: readonly Runtime[];
|
|
524
|
+
// The plugin's package.json `version`, read by the resolver at load (fs walk on node; baked manifest
|
|
525
|
+
// in the browser). Absent ⇒ couldn't be read (no package.json, or a remote whose version isn't baked).
|
|
526
|
+
// Loader-established provenance, like `name`/`matbotRuntime` — not the author's to declare.
|
|
527
|
+
readonly version?: string;
|
|
507
528
|
}
|
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
|
|
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
|
|
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
|
|
531
|
+
* interface ToolContracts {
|
|
481
532
|
* session_action:
|
|
482
|
-
* |
|
|
483
|
-
* |
|
|
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 `
|
|
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
|
|
545
|
+
export interface ToolContracts {}
|
|
495
546
|
|
|
496
547
|
/**
|
|
497
|
-
* One overload arm of a multi-action tool registered in {@link
|
|
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
|
|
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
|
|
507
|
-
type ToolResultUnion<E> = E extends
|
|
508
|
-
type ToolResultMatched<E, P> = E extends
|
|
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
|
|
515
|
-
? ToolResultArmed<
|
|
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
|
|
522
|
-
? ToolResultArmed<
|
|
523
|
-
? ([ToolResultMatched<
|
|
524
|
-
? ToolResultUnion<
|
|
525
|
-
: ToolResultMatched<
|
|
526
|
-
:
|
|
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
|
|
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
|
}
|
|
@@ -718,6 +802,27 @@ export interface ToolRegistry {
|
|
|
718
802
|
watch(signal?: AbortSignal): AsyncIterable<ToolRegistryEvent>;
|
|
719
803
|
}
|
|
720
804
|
|
|
805
|
+
/**
|
|
806
|
+
* The named provider profiles the runtime resolves by name. A `ReadonlyMap` for the many read-only
|
|
807
|
+
* consumers (`services.providers.get`/`has`/`keys`/…), plus a sanctioned write path so a plugin can
|
|
808
|
+
* *contribute* profiles live — a storage backend replaying provider definitions it captured in its own
|
|
809
|
+
* medium, or the built-in `provider` tool — exactly as a plugin contributes tools via {@link ToolRegistry}.
|
|
810
|
+
* Keyed by `config.name`; `register` upserts.
|
|
811
|
+
*/
|
|
812
|
+
export interface ProviderRegistry extends ReadonlyMap<string, ProviderConfig> {
|
|
813
|
+
register(config: ProviderConfig): void;
|
|
814
|
+
/** Explicit delete (the `provider` tool's `remove`) — removes the profile outright. */
|
|
815
|
+
remove(name: string): boolean;
|
|
816
|
+
/**
|
|
817
|
+
* Undo a contribution: restore the boot-baseline profile for `name` if one existed (a plugin-supplied
|
|
818
|
+
* profile that shadowed a configured one), otherwise delete it. A plugin that registers providers
|
|
819
|
+
* (e.g. a storage backend replaying them from its medium) calls this for each in its `teardown()`, so
|
|
820
|
+
* unloading it reverts the provider set to the host's boot condition — the multi-valued analogue of a
|
|
821
|
+
* swap-member reverting to its captured boot default.
|
|
822
|
+
*/
|
|
823
|
+
revert(name: string): void;
|
|
824
|
+
}
|
|
825
|
+
|
|
721
826
|
export type PluginRegistryEvent =
|
|
722
827
|
| { type: 'loaded'; name: string }
|
|
723
828
|
| { type: 'unloaded'; name: string };
|