Polyweave

@polyweave/tool-adapter (1.0.5)

Published 2026-07-17 15:32:34 +00:00 by Dvorak

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_RESULT frame's content.result (falling back to content.content then the raw frame)
  • Calls back with an Error on the first ERROR frame (content.message)
  • Calls back with result (or error) on END frame or source completion

Error conditions:

  • Timeout after options.timeoutMs — calls back with Error('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:

  1. Sync function (args) => result — detected when fn.length <= 1. Wrapped in try/catch; calls back with result or error.
  2. Async function (args, callback) => void — detected when fn.length > 1. Called directly; must invoke callback itself.
  3. Duplex adapter object { adapter: duplex } — wrapped via wrapToolAdapter with the same name and toolTimeoutMs.
  4. Duplex adapter (raw object with .source and .sink) — wrapped via wrapToolAdapter.

Behavior

  1. On TOOL_CALL frame: looks up tool by name. If not found, writes TOOL_RESULT with error 'Tool "<name>" not found'.
  2. Executes the tool. On completion, writes TOOL_RESULT with the result (or error message).
  3. Increments and decrements a pendingCalls counter. When pendingCalls reaches 0, injects a TEXT frame with 'Tool results above.' (plus the last tool error if any) to prompt the LLM to continue.
  4. Buffers END frames while pendingCalls > 0; releases the buffered END when 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_RESULT with error: 'Tool "<name>" not found'; sets _lastToolError
  • Tool throws synchronously → TOOL_RESULT with error.message; sets _lastToolError
  • Tool calls callback with error → TOOL_RESULT with error: err.message; sets _lastToolError
  • Tool result has .error or .success === false_lastToolError is set to the error value
  • Duplex adapter tool timeout → wrapToolAdapter produces an Error via callback
  • onToolCall and onToolResult hook 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 *

Keywords

polyweave tool-adapter tool-bridge push-stream
Details
npm
2026-07-17 15:32:34 +00:00
272
John Dvorak
SEE LICENSE IN LICENSE
latest
5.7 KiB
Assets (1)
Versions (5) View all
1.0.5 2026-07-17
1.0.6 2026-07-11
1.0.3 2026-07-11
1.0.2 2026-07-10
1.0.1 2026-07-10