@matatbread/matbot-plugin-api 0.2.9 → 0.3.1
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 +15 -0
- package/src/types.ts +131 -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
|
@@ -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, ToolPresenter,
|
|
5
6
|
} from './types.js';
|
|
6
7
|
import type { HookRegistry } from './hooks.js';
|
|
7
8
|
|
|
@@ -92,10 +93,19 @@ 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;
|
|
98
103
|
readonly KnowledgeIndex: KnowledgeIndex;
|
|
104
|
+
/** Optional tool-presentation policy: chooses which tools are advertised to the model per provider
|
|
105
|
+
* call (a large-library search/deferral plugin registers one). Absent ⇒ the runner advertises the
|
|
106
|
+
* whole turn snapshot. A plain registered service (not a swap-member); consumed as a member. See
|
|
107
|
+
* {@link ToolPresenter}. */
|
|
108
|
+
readonly ToolPresenter?: ToolPresenter | undefined;
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
/** The assembled machine: registry services wired to the fixed runtime — what `setup()` receives. */
|
|
@@ -119,6 +129,11 @@ export interface MatbotRuntime {
|
|
|
119
129
|
/** The calling plugin's own settings store. Scoped to the plugin — it cannot reach another's. */
|
|
120
130
|
settings(): PluginSettings;
|
|
121
131
|
|
|
132
|
+
/** Host-provided, per-platform TypeScript type-stripper. Always present (every execution environment
|
|
133
|
+
* supplies one); not registerable/swappable. Use it to erase types from source you compile at runtime
|
|
134
|
+
* rather than importing a platform-specific stripper. See {@link TypeScriptStripper}. */
|
|
135
|
+
readonly TypeScriptStripper: TypeScriptStripper;
|
|
136
|
+
|
|
122
137
|
/**
|
|
123
138
|
* Hot-load a plugin by specifier into the running process. Returns the loaded plugin.
|
|
124
139
|
* `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
|
|
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,32 @@ export interface ToolRegistry {
|
|
|
718
802
|
watch(signal?: AbortSignal): AsyncIterable<ToolRegistryEvent>;
|
|
719
803
|
}
|
|
720
804
|
|
|
805
|
+
/** What the runner tells a {@link ToolPresenter} about the call it's about to make. */
|
|
806
|
+
export interface PresentContext {
|
|
807
|
+
/** The session as of this provider call — the persisted user turn plus any assistant/tool turns
|
|
808
|
+
* taken so far this turn. A presenter ranks the tool set against this. */
|
|
809
|
+
session: Session;
|
|
810
|
+
/** The provider profile name this turn runs under, so a presenter can tailor to provider
|
|
811
|
+
* capabilities (e.g. Anthropic's in-context tool_reference expansion). */
|
|
812
|
+
provider: string;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Chooses which tools are advertised to the model for a single provider call — a read-only *view* over
|
|
817
|
+
* the turn's tool snapshot, NOT tool management. Presentation is orthogonal to the {@link ToolRegistry},
|
|
818
|
+
* which stays the single source of registration, resolution, and type-contracts (its fragile, load-bearing
|
|
819
|
+
* machinery is never a swap surface). The runner consults the presenter before *every* provider call, so
|
|
820
|
+
* an impl may present a subset (deferring a large library behind a search tool) and grow it mid-turn as the
|
|
821
|
+
* model discovers tools. Executor resolution is unaffected — a tool withheld here stays callable by name
|
|
822
|
+
* through the live registry — so presentation only ever changes what the model is *told about*.
|
|
823
|
+
*
|
|
824
|
+
* Optional {@link MatbotServices} member (offer-loosely): absent ⇒ the runner advertises the whole
|
|
825
|
+
* snapshot, byte-identical to today. `present` may return synchronously or async (a reranker).
|
|
826
|
+
*/
|
|
827
|
+
export interface ToolPresenter {
|
|
828
|
+
present(tools: readonly Tool[], ctx: PresentContext): readonly Tool[] | Promise<readonly Tool[]>;
|
|
829
|
+
}
|
|
830
|
+
|
|
721
831
|
/**
|
|
722
832
|
* The named provider profiles the runtime resolves by name. A `ReadonlyMap` for the many read-only
|
|
723
833
|
* consumers (`services.providers.get`/`has`/`keys`/…), plus a sanctioned write path so a plugin can
|