@matatbread/matbot-plugin-api 0.3.1 → 0.3.2
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 +53 -1
- package/src/types.ts +13 -1
package/package.json
CHANGED
package/src/invoke-tool.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MatbotMachine } from './plugin.js';
|
|
2
|
-
import type { ToolContext, ToolEvent, ToolResultFor, PromptFn, FormField } from './types.js';
|
|
2
|
+
import type { ToolContext, ToolEvent, ToolResultFor, ToolProxy, 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
|
|
@@ -98,3 +98,55 @@ const rejectingPrompt: PromptFn = (((p: string | FormField): Promise<string> =>
|
|
|
98
98
|
const label = typeof p === 'string' ? p : p.label;
|
|
99
99
|
return Promise.reject(new Error(`Non-interactive context: cannot prompt for "${label}"`));
|
|
100
100
|
}) as PromptFn);
|
|
101
|
+
|
|
102
|
+
/** Compact a value to one trace line. */
|
|
103
|
+
function compactTrace(v: unknown): string {
|
|
104
|
+
let s: string;
|
|
105
|
+
if (typeof v === 'string') s = v;
|
|
106
|
+
else { try { s = JSON.stringify(v) ?? String(v); } catch { s = String(v); } }
|
|
107
|
+
return s.length > 240 ? `${s.slice(0, 240)}…` : s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A factory for a context-overridden {@link ToolProxy}: `toolInContext({ provider }).some_tool(params)`
|
|
112
|
+
* runs `some_tool` with the given fields merged over the box's bound context (omitted fields inherited).
|
|
113
|
+
* Keyed on `ToolContracts`, so a hallucinated tool name is a **compile error**. This is the explicit
|
|
114
|
+
* escape hatch beside the default `tool` proxy — reach for it only when a call needs a different provider,
|
|
115
|
+
* signal, prompt or session.
|
|
116
|
+
*/
|
|
117
|
+
export type ToolBox = (call?: Partial<InvokeToolOptions>) => ToolProxy;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Build the typed calling surface both `function-tools` compositions and compiled skills use:
|
|
121
|
+
*
|
|
122
|
+
* const { tool, toolInContext } = makeToolBox(machine, callContext, { onEvent });
|
|
123
|
+
* await tool.some_tool(params); // bound context; a bad name is a compile error
|
|
124
|
+
* await toolInContext({ provider }).some_tool(...); // override one field for this call
|
|
125
|
+
*
|
|
126
|
+
* `tool` is a {@link ToolProxy} bound to `callContext`; `toolInContext(override)` returns a fresh proxy
|
|
127
|
+
* with `override` merged over it (never mutating `tool`). `onEvent`, when supplied, receives a `stdout`
|
|
128
|
+
* trace per call (→ name / ← result) so a run is observable; omit it for a silent surface. Built on
|
|
129
|
+
* {@link invokeTool}/{@link toolResult}, which remain the low-level, dynamic-name seam.
|
|
130
|
+
*/
|
|
131
|
+
export function makeToolBox(
|
|
132
|
+
machine: MatbotMachine,
|
|
133
|
+
defaultCall: InvokeToolOptions,
|
|
134
|
+
opts?: { onEvent?: (event: ToolEvent) => void },
|
|
135
|
+
): { tool: ToolProxy; toolInContext: ToolBox } {
|
|
136
|
+
const build = (call: InvokeToolOptions): ToolProxy =>
|
|
137
|
+
new Proxy({}, {
|
|
138
|
+
get(_target, prop) {
|
|
139
|
+
// A symbol key, or `then`, must not read as a callable tool — otherwise the proxy looks like a
|
|
140
|
+
// thenable and awaiting/returning it would try to invoke a tool named `then`.
|
|
141
|
+
if (typeof prop !== 'string' || prop === 'then') return undefined;
|
|
142
|
+
return async (params?: unknown): Promise<unknown> => {
|
|
143
|
+
opts?.onEvent?.({ type: 'stdout', chunk: `→ ${prop}(${compactTrace(params)})\n` });
|
|
144
|
+
const value = await toolResult(invokeTool(machine, prop, params ?? {}, call));
|
|
145
|
+
opts?.onEvent?.({ type: 'stdout', chunk: `← ${prop}: ${compactTrace(value)}\n` });
|
|
146
|
+
return value;
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
}) as ToolProxy;
|
|
150
|
+
const toolInContext: ToolBox = (override) => build(override === undefined ? defaultCall : { ...defaultCall, ...override });
|
|
151
|
+
return { tool: build(defaultCall), toolInContext };
|
|
152
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -590,6 +590,9 @@ type ArmCallSig<E> =
|
|
|
590
590
|
? (unknown extends P ? (params?: unknown) => Promise<R> : (params: P) => Promise<R>)
|
|
591
591
|
: never;
|
|
592
592
|
|
|
593
|
+
// The union of a tool's arm params — the parameter type of the trailing catch-all signature below.
|
|
594
|
+
type ArmArgsUnion<E> = E extends ToolContract<unknown, infer A> ? A : never;
|
|
595
|
+
|
|
593
596
|
/**
|
|
594
597
|
* The type of the injected `tool` proxy that `function-tools` and compiled skills call
|
|
595
598
|
* (`await tool.name(params)`). Each key is derived from its {@link ToolContracts} entry: a multi-arm
|
|
@@ -599,8 +602,17 @@ type ArmCallSig<E> =
|
|
|
599
602
|
* generated `dts()` declares `tool` as; it is *derived* — never hand-authored — from the same `ToolContracts`
|
|
600
603
|
* augmentation that types authors' `invokeTool`/`toolResult` calls, so the human editor and the LLM see one
|
|
601
604
|
* source of truth.
|
|
605
|
+
*
|
|
606
|
+
* The trailing catch-all signature (params union → result union) exists for soundness at the *meta-type*
|
|
607
|
+
* level, not for call sites: overload resolution is first-match, so a well-formed call still hits its
|
|
608
|
+
* specific arm and narrows precisely, and a malformed call matches nothing and still errors. What it
|
|
609
|
+
* changes: `ReturnType<typeof tool.x>` — which on a bare overload set resolves to an arbitrary (last) arm,
|
|
610
|
+
* a real-world codegen trap — now degrades to the sound full union; and a params value typed as a union of
|
|
611
|
+
* arms (dynamic multi-action dispatch) becomes callable, returning the result union.
|
|
602
612
|
*/
|
|
603
|
-
export type ToolProxy = { [K in keyof ToolContracts]:
|
|
613
|
+
export type ToolProxy = { [K in keyof ToolContracts]:
|
|
614
|
+
UnionToIntersection<ArmCallSig<ToolContracts[K]>>
|
|
615
|
+
& ((params: ArmArgsUnion<ToolContracts[K]>) => Promise<ToolResultUnion<ToolContracts[K]>>) };
|
|
604
616
|
|
|
605
617
|
/**
|
|
606
618
|
* Ask the user a question and resolve with their answer. The host supplies the
|