Polyweave

@polyweave/tools (1.0.8)

Published 2026-07-11 15:34:14 +00:00 by Dvorak

Installation

@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/
npm install @polyweave/tools@1.0.8
"@polyweave/tools": "1.0.8"

About this package

@polyweave/tools

Composable tool-execution wrappers for Polyweave push-stream pipelines — schema-driven tool duplexes, sync/async/streaming routers, schema installers, re-entrant tool loops, and built-in workspace file/command tools.

Synopsis

import {
  createToolDuplex,
  createWriteFileToolDuplex,
  createReadFileToolDuplex,
  createRunCommandToolDuplex,
  createToolRouter,
  createAsyncToolRouter,
  createStreamingToolRouter,
  createToolInstaller,
  createToolLoopWrapper
} from '@polyweave/tools';

const tool = createToolDuplex({
  name: 'greet',
  description: 'Returns a greeting',
  capabilities: ['read'],
  schema: {
    type: 'object',
    properties: { name: { type: 'string' } },
    required: ['name']
  },
  handler: (args) => ({ greeting: `Hello, ${args.name}` })
});

const router = createToolRouter(llmDuplex, {
  greet: (args) => ({ greeting: `Hello, ${args.name}` })
});

Install

npm install @polyweave/tools

Why

LLM tool calling requires schema-driven dispatch with DESCRIBE protocol support, sync and async execution, streaming argument accumulation for partial TOOL_CALL arguments, schema installer injection for provider registration, and re-entrant tool loops that feed results back to the LLM as follow-up messages. This package provides all six as composable primitives built on @polyweave/core bidirectional wrappers and frame duplexes.

API

createToolDuplex(config)

Creates a frame-duplex tool adapter with schema introspection. Responds to DESCRIBE frames with the tool's schema and handles TOOL_CALL frames by executing the handler.

Parameter Type Required Default Description
config.name string No 'unnamed' Tool name (for routing and error messages)
config.description string No 'Tool: <name>' Human-readable description
config.capabilities string[] No ['read'] Capability tags (mirrored in DESCRIPTION)
config.transactional boolean No false Whether the tool is transactional
config.reversible boolean No true Whether the tool supports undo
config.schema object No { type: 'object', properties: {}, required: [] } JSON Schema for arguments
config.handler function No (args, cb) => cb(null, {}) Tool implementation
config.timeoutMs number No 30000 Timeout in ms (0 to disable)
config.async boolean No auto-detected Explicit sync/async flag; overrides handler.length detection

Returns a duplex { source, sink, _toolName, _capabilities } created by createFrameDuplex.

Handler detection: if config.async is explicitly set, that value is used. Otherwise, if handler.length > 1 the handler is treated as async (callback-based); handler.length <= 1 means sync (return value).

Frame protocol:

Input Frame Action
DESCRIBE Responds with DESCRIPTION frame (schema, capabilities, metadata), then END
TICK, CANCEL, ABORT Ignored
TOOL_CALL (matching name) Executes handler, responds with TOOL_RESULT then END
TOOL_CALL (different name) Ignored

TOOL_RESULT on success: content.result is the handler return value (or {} if undefined). TOOL_RESULT on error: content.result = { success: false, error: err.message }.

Error conditions:

  • Handler throws synchronously → TOOL_RESULT with error: err.message
  • Timeout (timeoutMs > 0) → TOOL_RESULT with error: 'Tool <name> timed out after <ms>ms'
  • Async handler calls callback with error → TOOL_RESULT with error: err.message
  • The toolCallId and streamId are propagated from the triggering TOOL_CALL frame

createWriteFileToolDuplex(workspaceDir, allowedExtensions)

Built-in duplex tool for writing files to a workspace directory.

Parameter Type Required Default Description
workspaceDir string Yes Absolute path to the workspace root
allowedExtensions string[] No If provided, restricts allowed file extensions (e.g. ['.js', '.ts', '.md'])

Tool name: write_file. Schema requires path (string) and content (string). Paths are relative to workspaceDir; .. sequences are stripped.

Returns { success: boolean, path: string, bytes: number } or { success: false, error: string }.

Error conditions: empty path, disallowed extension, or filesystem errors from mkdirSync/writeFileSync.

createReadFileToolDuplex(workspaceDir, allowedExtensions)

Built-in duplex tool for reading files from a workspace directory.

Parameter Type Required Default Description
workspaceDir string Yes Absolute path to the workspace root
allowedExtensions string[] No If provided, restricts allowed file extensions

Tool name: read_file. Schema requires path (string). An empty or blank path lists directory files.

Returns:

  • For directory listing: { success: true, path: <workspaceDir>, isDirectory: true, files: string[], hint: string }
  • For file read: { success: true, path: string, content: string }
  • On error: { success: false, error: string, path: string }

Error conditions: disallowed extension or readFileSync/readdirSync filesystem errors.

createRunCommandToolDuplex(workspaceDir, timeoutMs)

Built-in duplex tool for executing shell commands in the workspace directory.

Parameter Type Required Default Description
workspaceDir string Yes Working directory for the command
timeoutMs number No 30000 Command timeout in ms

Tool name: run_command. Schema requires command (string). Uses execSync with maxBuffer: 1MB, stdio: ['ignore', 'pipe', 'pipe'].

Returns (always success: true — errors are captured):

  • On success: { success: true, command: string, stdout: string, exitCode: 0 }
  • On failure: { success: true, command: string, stdout: string, stderr: string, exitCode: number }
  • On empty command: { success: false, error: string }

createToolRouter(innerDuplex, tools, options)

Synchronous tool router. Intercepts TOOL_CALL frames on the response path, executes the matching tool synchronously, and writes TOOL_RESULT back.

Parameter Type Required Default Description
innerDuplex object Yes Duplex to wrap (LLM adapter response path)
tools object No {} Map of name → (args) => result
options.name string No null Wrapper name
options.silent boolean No false Suppress console output
options.debug boolean No false Enable debug logging

Tools must be synchronous functions (args) => result. Unknown tools produce a TOOL_RESULT error frame. Tool exceptions are caught and reported as error frames. All intercepted TOOL_CALL frames return null (preventing pass-through).

Frame protocol:

Direction Frame Type Action
Response TOOL_CALL Executes tool sync → writes TOOL_RESULT to inner sink → returns null
All other All types Returned unchanged

createAsyncToolRouter(innerDuplex, tools)

Async tool router. Same as createToolRouter but tools are callback-based: (args, callback) => void.

Parameter Type Required Default Description
innerDuplex object Yes Duplex to wrap
tools object No {} Map of name → (args, callback) => void

Tools must call callback(err, result). Exceptions on the sync invocation of the tool are caught; exceptions within the async callback body are not caught by the router.

createStreamingToolRouter(innerDuplex, tools, options)

Streaming-aware router. Supports atomic TOOL_CALL frames (same as sync router) plus streaming argument accumulation: TEXT frames with metadata.toolArgStream are buffered until a META frame with metadata.toolComplete signals completion.

Parameter Type Required Default Description
innerDuplex object Yes Duplex to wrap
tools object No {} Map of name → (args) => result
options.name string No null Wrapper name
options.durability object No {} { wrapperId, wrapperType, enabled } for ObservableState

Returns a joined duplex { sink, source } that merges the bidirectional wrapper and ObservableState.

Frame protocol:

  • Atomic TOOL_CALL → executed immediately (same as createToolRouter)
  • Streaming TOOL_CALL (metadata.streaming) → registered as pending
  • TEXT with metadata.toolArgStream and metadata.toolCallId → chunk buffered into pendingToolCalls[].argumentChunks
  • META with metadata.toolComplete → chunks joined with '', JSON.parsed, tool executed, pending entry removed
  • META with durability.restoreState → internal, consumed by ObservableState

Error conditions:

  • Unknown tool → TOOL_RESULT error frame, pending entry removed
  • JSON.parse failure on accumulated arguments → not caught (will throw)
  • Tool exception → TOOL_RESULT error frame, pending entry removed

createToolInstaller(innerDuplex, tooling)

Installs tool schema, system prompt, and tool_choice into the first request's PARAM frames (one-shot injection).

Parameter Type Required Default Description
innerDuplex object Yes Duplex to wrap
tooling.tools object[] No [] Array of tool schemas to inject as PARAM { name: 'tools' }
tooling.handlers object No {} Map of name → (args, callback) => void for async tool execution
tooling.systemPrompt string No null System prompt prepended to messages
tooling.toolChoice any No null Tool choice parameter injected as PARAM { name: 'tool_choice' }

On the first onRequest call, injects PARAM frames for tools, tool_choice, and (if first frame is a PARAM with name: 'messages') prepends systemPrompt as a system message. Subsequent requests pass through.

On the response path, intercepts TOOL_CALL frames and routes to tooling.handlers[name] (async callback-based).

createToolLoopWrapper(innerDuplex, options)

Re-entrant tool loop. Intercepts PARAM { name: 'messages' } to inject tool schemas and system prompt, then on each TOOL_CALL executes the handler and sends a follow-up message sequence to the inner duplex to keep the LLM iterating.

Parameter Type Required Default Description
innerDuplex object Yes Duplex to wrap
options.tools object[] No [] Tool schemas injected as PARAM { name: 'tools' }
options.handlers object No {} Map of name → (args, callback) => void
options.toolChoice any No null Injected as PARAM { name: 'tool_choice' }
options.systemPrompt string No null Prepended to messages
options.maxIterations number No 3 Max tool-call iterations before forcing final answer
options.finalSystemPrompt string No 'Answer directly using the tool result. Do not call tools.' System message appended when max iterations reached
options.onToolResult function No null ({ toolCall, result, streamId }) hook
options.name string No null Wrapper name

On first PARAM { name: 'messages' }, injects tools, toolChoice, and systemPrompt. On TOOL_CALL, executes the handler then sends a full follow-up: previous PARAM values (excluding messages/tools/tool_choice), the tools array, tool_choice, and the updated message list with assistant tool_calls and tool role messages. After maxIterations, appends finalSystemPrompt to force the LLM to produce a final answer.

Frame Protocol Summary

Module Intercepts Emits
createToolDuplex DESCRIBE, TOOL_CALL DESCRIPTION, TOOL_RESULT, END
createToolRouter TOOL_CALL (response) TOOL_RESULT (into inner sink)
createAsyncToolRouter TOOL_CALL (response) TOOL_RESULT (into inner sink)
createStreamingToolRouter TOOL_CALL, TEXT (tool arg stream), META (tool complete) TOOL_RESULT, META (state)
createToolInstaller First request (injects params), TOOL_CALL (response) PARAM, TOOL_RESULT
createToolLoopWrapper PARAM (messages), TOOL_CALL PARAM, TEXT, TOOL_RESULT

Tests

npm test

Runs via node --test test/installer.test.js test/toolDuplex.test.js test/toolLoop.test.js test/workspace-duplex-tools.test.js. Covers: DESCRIBE protocol, TOOL_CALL dispatch (sync and async), timeout enforcement, error handling, workspace file I/O tools, installer injection, tool loop follow-up messaging, iteration limits, and streaming argument accumulation.

Requirements

  • Node.js >= 22.0.0
  • ESM only
  • @polyweave/core (peer)

Dependencies

Development Dependencies

ID Version
@rigor/core *

Peer Dependencies

ID Version
@polyweave/core *

Keywords

polyweave tools function-calling
Details
npm
2026-07-11 15:34:14 +00:00
498
John Dvorak
SEE LICENSE IN LICENSE
latest
9.9 KiB
Assets (1)
tools-1.0.8.tgz 9.9 KiB
Versions (5) View all
1.0.8 2026-07-11
1.0.5 2026-07-11
1.0.4 2026-07-11
1.0.3 2026-07-10
1.0.2 2026-07-10