@polyweave/tool-adapter (1.0.5)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/tool-adapter@1.0.5"@polyweave/tool-adapter": "1.0.5"About this package
@polyweave/tool-adapter
Bridges duplex-based tool adapters (Polyweave source/sink streams) to the (args, callback) convention used by @polyweave/tools routers. Also wraps LLM adapter duplexes to intercept TOOL_CALL frames and route them to registered tool handlers.
Synopsis
import { wrapToolAdapter, createToolAdapterWrapper } from '@polyweave/tool-adapter';
import { SomeToolDuplex } from '@polyweave/some-tool';
// Wrap a duplex tool adapter as a callback-based tool
const wrapped = wrapToolAdapter(SomeToolDuplex, { name: 'myTool', timeoutMs: 15000 });
wrapped({ query: 'hello' }, (err, result) => {
console.log(err, result);
});
// Wrap an LLM adapter to intercept TOOL_CALL frames
const wrapper = createToolAdapterWrapper(llmDuplex, {
tools: {
calculator: (args, cb) => cb(null, { result: args.a + args.b }),
fileWriter: { adapter: fileToolDuplex } // duplex adapter form
}
});
wrapper.source.pipe(someSink);
Install
npm install @polyweave/tool-adapter
Why
Polyweave tool adapters speak in push-stream frames (source/sink duplexes), but tool routers expect callback conventions. This package bridges the two, enabling callbacks that pipe frames through the duplex, collect TOOL_RESULT frames, and settle on END or ERROR. The bidirectional wrapper (createToolAdapterWrapper) intercepts TOOL_CALL frames on an LLM response path and routes them to registered tools — supporting both sync functions and full adapter duplexes — then injects TEXT prompts to continue the conversation.
API
wrapToolAdapter(toolAdapter, options)
Wraps a tool adapter duplex as an (args, callback) function. Pipes frames through the adapter, extracts the TOOL_RESULT frame content, and calls back on END or ERROR.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
toolAdapter |
object |
Yes | — | Duplex with .source (readable) and .sink (writable) |
options.name |
string |
No | 'unknown' |
Tool name for timeout error messages |
options.timeoutMs |
number |
No | 30000 |
Timeout before the callback is invoked with an error |
Returns (args, callback) — a function accepting tool arguments and a Node-style (err, result) callback.
args is written as a TOOL_CALL frame to the adapter's sink. The wrapper creates a frame sink (createFrameSink) on the adapter's source and:
- Accumulates all frames
- Calls back with the first
TOOL_RESULTframe'scontent.result(falling back tocontent.contentthen the raw frame) - Calls back with an
Erroron the firstERRORframe (content.message) - Calls back with result (or error) on
ENDframe or source completion
Error conditions:
- Timeout after
options.timeoutMs— calls back withError('Tool <name> timed out after <ms>ms') - Only the first settlement is delivered; subsequent frames are ignored
createToolAdapterWrapper(innerDuplex, options)
Wraps an inner duplex and intercepts TOOL_CALL frames on the response path, routing them to registered tool callbacks. Returns an augmented duplex that can be used as a drop-in replacement for the original.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
innerDuplex |
object |
Yes | — | Duplex to wrap (typically an LLM adapter) |
options.name |
string |
No | null |
Name passed to the bidirectional wrapper |
options.tools |
object |
No | {} |
Map of tool name → handler function or adapter object |
options.toolTimeoutMs |
number |
No | 30000 |
Per-tool timeout (alias: toolTimeout) |
options.onToolCall |
function |
No | null |
(name, args) hook called synchronously on each TOOL_CALL |
options.onToolResult |
function |
No | null |
(name, args, result, err) hook called on each tool result |
options.mirrorContinue |
boolean |
No | true |
When true, mirrors the "continue" TEXT frame to wrapper.source |
Returns a duplex (createBidirectionalWrapper result) with additional methods:
| Method | Description |
|---|---|
.getToolTrace() |
Returns a copy of [{ name, args, result, error }] for all tool calls made |
.clearTrace() |
Resets the tool trace |
.abort() |
Prevents further continue-text injection (sets internal abort flag) |
._onToolResult |
Assignable property (name, args, result, err) — dynamic hook, same interface as onToolResult |
Tool registration forms
Each value in options.tools can be one of:
- Sync function
(args) => result— detected whenfn.length <= 1. Wrapped in try/catch; calls back with result or error. - Async function
(args, callback) => void— detected whenfn.length > 1. Called directly; must invoke callback itself. - Duplex adapter object
{ adapter: duplex }— wrapped viawrapToolAdapterwith the samenameandtoolTimeoutMs. - Duplex adapter (raw object with
.sourceand.sink) — wrapped viawrapToolAdapter.
Behavior
- On
TOOL_CALLframe: looks up tool byname. If not found, writesTOOL_RESULTwith error'Tool "<name>" not found'. - Executes the tool. On completion, writes
TOOL_RESULTwith the result (or error message). - Increments and decrements a
pendingCallscounter. WhenpendingCallsreaches 0, injects aTEXTframe with'Tool results above.'(plus the last tool error if any) to prompt the LLM to continue. - Buffers
ENDframes whilependingCalls > 0; releases the bufferedENDwhen all tools complete.
Frame protocol (wrapping layer):
| Direction | Frame Type | Action |
|---|---|---|
| Response path (LLM → consumer) | TOOL_CALL |
Intercepted — executes tool, writes TOOL_RESULT to inner sink, returns null |
| Response path | END |
Buffered if tools pending; forwarded otherwise |
| Response path | All others | Passed through unchanged |
| Request path | All | Passed through unchanged (onRequest is identity) |
Error conditions:
- Unknown tool →
TOOL_RESULTwitherror: 'Tool "<name>" not found'; sets_lastToolError - Tool throws synchronously →
TOOL_RESULTwitherror.message; sets_lastToolError - Tool calls callback with error →
TOOL_RESULTwitherror: err.message; sets_lastToolError - Tool result has
.erroror.success === false→_lastToolErroris set to the error value - Duplex adapter tool timeout →
wrapToolAdapterproduces anErrorvia callback onToolCallandonToolResulthook exceptions are silently caught
Tests
npm test
Runs via node --test test/*.test.js. Covers: wrapToolAdapter result extraction and error handling with both TOOL_RESULT and ERROR frames, timeout settlement, createToolAdapterWrapper tool interception (sync, async, adapter forms), unknown tool errors, pending-call-buffered END release, and trace/abort/clearTrace helpers.
Requirements
- Node.js >= 22.0.0
- ESM only
@polyweave/core(peer)@polyweave/tools(dependency)
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |