@feniix/bridgekit 0.14.0 → 0.15.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/README.md +15 -2
- package/dist/src/adapters/pi.d.ts +3 -1
- package/dist/src/adapters/pi.js +3 -0
- package/dist/src/core/define-tool.d.ts +15 -0
- package/dist/src/index.d.ts +1 -1
- package/examples/README.md +12 -3
- package/llms.txt +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,8 @@ import {
|
|
|
83
83
|
executePortableTool,
|
|
84
84
|
isDomainFailure,
|
|
85
85
|
isValidationFailure,
|
|
86
|
+
type PiToolCallRenderer,
|
|
87
|
+
type PiToolResultRenderer,
|
|
86
88
|
type PortableTool,
|
|
87
89
|
type PortableToolBuiltInHost,
|
|
88
90
|
type PortableToolContext,
|
|
@@ -167,13 +169,19 @@ they are not designed to be called on the pi adapter's wire object.
|
|
|
167
169
|
|
|
168
170
|
#### Per-host metadata via `hostExtras`
|
|
169
171
|
|
|
170
|
-
`PortableTool.hostExtras` is an optional namespace for host-specific fields that should travel with the tool definition rather than a parallel sidecar map. The pi adapter reads `hostExtras.pi`; the MCP adapter reads `hostExtras.mcp`; each adapter ignores keys it does not recognise. Tools that omit `hostExtras` see no behavior change.
|
|
172
|
+
`PortableTool.hostExtras` is an optional namespace for host-specific fields that should travel with the tool definition rather than a parallel sidecar map. The pi adapter reads `hostExtras.pi`; the MCP adapter reads `hostExtras.mcp`; each adapter ignores keys it does not recognise. Tools that omit `hostExtras` see no behavior change. Pi TUI renderers use the exported `PiToolCallRenderer` / `PiToolResultRenderer` aliases; they are opaque pass-through callbacks, so bridgekit does not import pi-tui types or validate their return values.
|
|
171
173
|
|
|
172
174
|
Custom host adapters can reuse the core execution helpers, but the built-in `ctx.host` union is intentionally closed (`"pi" | "mcp" | "test"`). Custom adapters should cast at their boundary and carry custom dispatch state on adapter-owned fields; use module augmentation of `PortableToolHostExtras` for type-safe host-specific metadata.
|
|
173
175
|
|
|
174
176
|
```ts
|
|
175
177
|
import { Type } from "typebox";
|
|
176
|
-
import { definePortableTool } from "@feniix/bridgekit";
|
|
178
|
+
import { definePortableTool, type PiToolCallRenderer, type PiToolResultRenderer } from "@feniix/bridgekit";
|
|
179
|
+
|
|
180
|
+
const renderSummaryCall: PiToolCallRenderer = (args) => ({ type: "text", text: `summarise ${args.text}` });
|
|
181
|
+
const renderSummaryResult: PiToolResultRenderer = (result, options) => ({
|
|
182
|
+
type: "text",
|
|
183
|
+
text: options.expanded ? result.content?.[0]?.text : "summary ready",
|
|
184
|
+
});
|
|
177
185
|
|
|
178
186
|
export const generateSummaryTool = definePortableTool({
|
|
179
187
|
name: "generate_summary",
|
|
@@ -190,6 +198,9 @@ export const generateSummaryTool = definePortableTool({
|
|
|
190
198
|
pendingMessage: "Summarising...",
|
|
191
199
|
promptSnippet: "Use this tool when the user asks for a short summary.",
|
|
192
200
|
promptGuidelines: ["Prefer < 80 chars.", "Strip markdown."],
|
|
201
|
+
// Optional pi TUI renderers, forwarded by identity to pi.registerTool.
|
|
202
|
+
renderCall: renderSummaryCall,
|
|
203
|
+
renderResult: renderSummaryResult,
|
|
193
204
|
},
|
|
194
205
|
mcp: {
|
|
195
206
|
annotations: { readOnlyHint: true },
|
|
@@ -198,6 +209,8 @@ export const generateSummaryTool = definePortableTool({
|
|
|
198
209
|
});
|
|
199
210
|
```
|
|
200
211
|
|
|
212
|
+
`renderCall` is invoked by pi before execution to render the call line. `renderResult` is invoked by pi after execution to render the result; its `options.expanded` flag tracks the user's Ctrl+O collapsed/expanded toggle. BridgeKit forwards both functions by identity to `pi.registerTool`.
|
|
213
|
+
|
|
201
214
|
See `docs/rfc-host-extras.md` for the design rationale (which fields are in scope, why a top-level field beats a sidecar map, the closure rule for future additions). `PortableToolHostExtras` is module-augmentable for custom host adapters; declare your namespace via `declare module "@feniix/bridgekit"`.
|
|
202
215
|
|
|
203
216
|
### MCP adapter
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TSchema } from "typebox";
|
|
2
|
-
import type { PortableTool, PortableToolErrorDetails, PortableToolResult } from "../core/define-tool.js";
|
|
2
|
+
import type { PiToolCallRenderer, PiToolResultRenderer, PortableTool, PortableToolErrorDetails, PortableToolResult } from "../core/define-tool.js";
|
|
3
3
|
type PiContent = {
|
|
4
4
|
type: "text";
|
|
5
5
|
text: string;
|
|
@@ -21,6 +21,8 @@ type PiToolDefinition = {
|
|
|
21
21
|
execute(toolCallId: string, params: unknown, signal?: AbortSignal, onUpdate?: (update: PiToolUpdate) => void, ctx?: unknown): Promise<PiToolResult>;
|
|
22
22
|
promptSnippet?: string;
|
|
23
23
|
promptGuidelines?: string[];
|
|
24
|
+
renderCall?: PiToolCallRenderer;
|
|
25
|
+
renderResult?: PiToolResultRenderer;
|
|
24
26
|
};
|
|
25
27
|
export type PiToolRegistration = {
|
|
26
28
|
registerTool(tool: PiToolDefinition): unknown;
|
package/dist/src/adapters/pi.js
CHANGED
|
@@ -65,6 +65,9 @@ export function registerPiTools(pi, tools, options = {}) {
|
|
|
65
65
|
// pi-coding-agent's registerTool input is `string[]` (mutable). The boundary
|
|
66
66
|
// copy satisfies both sides without weakening either declared type.
|
|
67
67
|
...(piExtras?.promptGuidelines !== undefined && { promptGuidelines: [...piExtras.promptGuidelines] }),
|
|
68
|
+
// TUI renderers forwarded verbatim from hostExtras.pi.
|
|
69
|
+
...(piExtras?.renderCall !== undefined && { renderCall: piExtras.renderCall }),
|
|
70
|
+
...(piExtras?.renderResult !== undefined && { renderResult: piExtras.renderResult }),
|
|
68
71
|
async execute(_toolCallId, params, signal, onUpdate, _ctx) {
|
|
69
72
|
let result;
|
|
70
73
|
try {
|
|
@@ -42,6 +42,19 @@ export interface PortableToolContext {
|
|
|
42
42
|
signal?: AbortSignal;
|
|
43
43
|
progress?: (update: PortableToolResult) => void;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Optional TUI renderer for the tool call line (before execution).
|
|
47
|
+
* Receives `(args, theme, context)` and returns a pi-tui Component.
|
|
48
|
+
* BridgeKit passes this through verbatim — no validation or wrapping.
|
|
49
|
+
*/
|
|
50
|
+
export type PiToolCallRenderer = (args: any, theme: any, context: any) => any;
|
|
51
|
+
/**
|
|
52
|
+
* Optional TUI renderer for the tool result (after execution).
|
|
53
|
+
* Receives `(result, options, theme, context)` and returns a pi-tui Component.
|
|
54
|
+
* `options.expanded` is toggled by the user via Ctrl+O in pi's TUI.
|
|
55
|
+
* BridgeKit passes this through verbatim — no validation or wrapping.
|
|
56
|
+
*/
|
|
57
|
+
export type PiToolResultRenderer = (result: any, options: any, theme: any, context: any) => any;
|
|
45
58
|
/**
|
|
46
59
|
* Pi-specific entries on `PortableTool.hostExtras`. Read only by the pi
|
|
47
60
|
* adapter; the MCP adapter ignores this namespace.
|
|
@@ -83,6 +96,8 @@ export interface PiHostExtras {
|
|
|
83
96
|
* once attached to a tool definition.
|
|
84
97
|
*/
|
|
85
98
|
promptGuidelines?: readonly string[];
|
|
99
|
+
renderCall?: PiToolCallRenderer;
|
|
100
|
+
renderResult?: PiToolResultRenderer;
|
|
86
101
|
}
|
|
87
102
|
/**
|
|
88
103
|
* MCP-specific entries on `PortableTool.hostExtras`. Read only by the MCP
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { definePortableTool, type McpHostExtras, type PiHostExtras, type PortableTool, type PortableToolBuiltInHost, type PortableToolContext, type PortableToolErrorDetails, type PortableToolHostExtras, type PortableToolResult, type PortableValidationError, } from "./core/define-tool.js";
|
|
1
|
+
export { definePortableTool, type McpHostExtras, type PiHostExtras, type PiToolCallRenderer, type PiToolResultRenderer, type PortableTool, type PortableToolBuiltInHost, type PortableToolContext, type PortableToolErrorDetails, type PortableToolHostExtras, type PortableToolResult, type PortableValidationError, } from "./core/define-tool.js";
|
|
2
2
|
export { executePortableTool, validatePortableToolArgs } from "./core/execute-tool.js";
|
|
3
3
|
export { isDomainFailure, isValidationFailure, type PortableDomainFailure, type PortableValidationFailure, } from "./core/result-guards.js";
|
package/examples/README.md
CHANGED
|
@@ -275,11 +275,17 @@ MCP behavior:
|
|
|
275
275
|
|
|
276
276
|
## 4. Per-host metadata via `hostExtras`
|
|
277
277
|
|
|
278
|
-
When a tool needs host-specific metadata — pi's `pendingMessage` for a "Processing..." signal, MCP's annotations as advisory hints to clients — the canonical place is `PortableTool.hostExtras`. Each host has its own namespace; adapters read the keys they recognise and ignore the rest. Tools that omit `hostExtras` see no behavior change.
|
|
278
|
+
When a tool needs host-specific metadata — pi's `pendingMessage` for a "Processing..." signal, pi TUI renderers for custom collapsed/expanded display, or MCP's annotations as advisory hints to clients — the canonical place is `PortableTool.hostExtras`. Each host has its own namespace; adapters read the keys they recognise and ignore the rest. Tools that omit `hostExtras` see no behavior change.
|
|
279
279
|
|
|
280
280
|
```ts
|
|
281
281
|
import { Type } from "typebox";
|
|
282
|
-
import { definePortableTool } from "@feniix/bridgekit";
|
|
282
|
+
import { definePortableTool, type PiToolCallRenderer, type PiToolResultRenderer } from "@feniix/bridgekit";
|
|
283
|
+
|
|
284
|
+
const renderSummariseCall: PiToolCallRenderer = (args) => ({ type: "text", text: `summarise ${args.text}` });
|
|
285
|
+
const renderSummariseResult: PiToolResultRenderer = (result, options) => ({
|
|
286
|
+
type: "text",
|
|
287
|
+
text: options.expanded ? result.content?.[0]?.text : "summary ready",
|
|
288
|
+
});
|
|
283
289
|
|
|
284
290
|
export const summariseTool = definePortableTool({
|
|
285
291
|
name: "summarise",
|
|
@@ -296,6 +302,9 @@ export const summariseTool = definePortableTool({
|
|
|
296
302
|
pendingMessage: "Summarising...",
|
|
297
303
|
promptSnippet: "Use this tool when the user asks for a short summary.",
|
|
298
304
|
promptGuidelines: ["Prefer < 80 chars.", "Strip markdown."],
|
|
305
|
+
// Opaque pi TUI callbacks. BridgeKit forwards these by identity.
|
|
306
|
+
renderCall: renderSummariseCall,
|
|
307
|
+
renderResult: renderSummariseResult,
|
|
299
308
|
},
|
|
300
309
|
mcp: {
|
|
301
310
|
// Advisory hints clients may surface. Do not affect validation or
|
|
@@ -308,7 +317,7 @@ export const summariseTool = definePortableTool({
|
|
|
308
317
|
|
|
309
318
|
Best practices for `hostExtras`:
|
|
310
319
|
|
|
311
|
-
- Keep tool *behavior* host-neutral. `hostExtras` carries
|
|
320
|
+
- Keep portable tool *behavior* host-neutral. `hostExtras` carries host presentation metadata; pi renderers should stay presentation-only and are forwarded verbatim, not invoked by BridgeKit.
|
|
312
321
|
- Set only the fields the adapter recognises; unrecognised keys are silently ignored, but they add noise to the definition.
|
|
313
322
|
- If you ship an adapter for a host outside `"pi" | "mcp"`, cast `ctx.host` at the adapter boundary (the host union is fixed since 0.10.0). For type-safe per-host metadata in your adapter's namespace, extend `PortableToolHostExtras` via `declare module "@feniix/bridgekit"` so consumers of your adapter get type safety on the new namespace.
|
|
314
323
|
|
package/llms.txt
CHANGED
|
@@ -79,7 +79,7 @@ export function createTools() {
|
|
|
79
79
|
- Validation errors and domain errors share the `{ field, message }` shape *inside* `validationErrors[]`. The array itself only appears on `kind: "validation"` — narrow with `isValidationFailure` (or check the discriminator) before iterating. `field` is derived from TypeBox's structured error data: required-property errors read `params.requiredProperties` (one entry per missing prop), additional-property errors read `params.additionalProperties` (one entry per offending key), `const`/`enum` errors carry the allowed value(s) in the message (`must equal "create"` / `must equal one of "create", "update"`), other errors take the last meaningful segment of the offending JSON pointer (`text`, not `/text`). Locale-independent and prop-names-with-commas-safe. Duplicate `(field, message)` pairs from union mismatches are deduplicated. As of 0.8.0; `path` no longer exists. As of 0.8.2: **discriminated unions** (`Type.Union([Type.Object({tag:Literal("a"),…}),Type.Object({tag:Literal("b"),…})])`) surface the *active branch's* missing-required hints when exactly one branch's discriminator is satisfied — instead of the bare `anyOf` summary. Recognized discriminator shapes: `Literal` (`const`), `enum`, and `Union of Literals` (`anyOf` of consts). Works inside `Type.Array(...)` per-element. When no branch matches (invalid discriminator), falls back to the 0.8.1 behavior (drop sibling `required`, keep `const`/`enum`/`anyOf` summary). Caveats: array-element fields can be numeric indices (`field: "0"`, path context lost); root-level schema failures use the sentinel `field: "(root)"` rather than empty string; for nested discriminated unions `field` is the leaf segment, not the full path.
|
|
80
80
|
- The result guards take a `PortableToolResult` — the value `executePortableTool` returns at the seam between BridgeKit and a host. They are not designed to be called on the pi adapter's wire object (`{ content, details, isError }`), which exposes `details` instead of `structuredContent`.
|
|
81
81
|
- `registerPiTools(pi, tools, { errorHandling: "throw" })` opts into the pre-0.7 behavior where `isError` results throw `PortableToolExecutionError`. Selecting this value emits a `DeprecationWarning` (code `BRIDGEKIT_PI_THROW_DEPRECATED`) once per process; only the `"throw"` value is deprecated, not the option itself. Scheduled for removal in 1.0; do not adopt it for new code.
|
|
82
|
-
- `PortableTool.hostExtras` (0.9+) carries optional per-host metadata in namespaced sub-objects (`pi`, `mcp`, plus module-augmentable custom hosts). Each field is optional and zero-cost when absent — a tool without `hostExtras` produces byte-identical pi registration and MCP `tools/list` payloads to 0.8.x. `hostExtras.pi.pendingMessage` fires `onUpdate({ content, details: { status: "pending" } })` exactly once before TypeBox validation; `hostExtras.pi.promptSnippet` / `promptGuidelines` pass through
|
|
82
|
+
- `PortableTool.hostExtras` (0.9+) carries optional per-host metadata in namespaced sub-objects (`pi`, `mcp`, plus module-augmentable custom hosts). Each field is optional and zero-cost when absent — a tool without `hostExtras` produces byte-identical pi registration and MCP `tools/list` payloads to 0.8.x. `hostExtras.pi.pendingMessage` fires `onUpdate({ content, details: { status: "pending" } })` exactly once before TypeBox validation; `hostExtras.pi.promptSnippet` / `promptGuidelines` pass through to pi's `registerTool`; `hostExtras.pi.renderCall` / `renderResult` are opaque TUI callbacks typed by exported `PiToolCallRenderer` / `PiToolResultRenderer` aliases and forwarded by identity; `hostExtras.mcp.annotations` (`title` / `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`) attaches MCP advisory hints. Tool definitions stay host-neutral — host-specific metadata lives in its namespace, not on the tool's top-level fields.
|
|
83
83
|
|
|
84
84
|
### Result-guard usage
|
|
85
85
|
|