feat: add tool-calling primitives (0.11.0)#3
Merged
marceloceccon merged 1 commit intomainfrom Apr 30, 2026
Merged
Conversation
Adds engine-orchestrated tool calling. Participants declare a `tools` list; the host plugs in a `ToolExecutor`; the engine drives the per-turn dispatch loop. 100% backward compatible with 0.10.x — every new field is optional, and absence of `toolExecutor` preserves byte-for-byte behaviour. Public surface (all optional): - Participant.tools?: ToolDefinition[] - ConsensusOptions.toolExecutor?: ToolExecutor - ConsensusOptions.maxToolIterations?: number (default 8, clamped [1,32]) - ModelCallRequest.tools?, ModelCallRequest.toolCallTurns? - ModelCallResponse.toolCalls? - New types: ToolDefinition, ToolCall, ToolCallTurn, ToolCallContext, ToolExecutionResult, ToolExecutor - New events: toolCallStart, toolCallComplete, toolError - ToolDefinitionSchema (zod) - MAX_TOOL_ITERATIONS_CAP constant Engine internals: new private #runParticipantTurn drives the loop; #dispatchToolCalls runs one iteration's calls and emits the events. Token usage is summed across iterations; the final response.content (after the loop terminates or hits the cap) becomes the turn's output. Tests: 130 → 142 (12 new — happy path, no-executor backward compat, exception capture, executor-returned errors, abort propagation, max- iteration cap with clamping, turn isolation, payload integrity, multi-call dispatch). README gets a new "Tool calling" section with the full integration recipe and ModelCaller responsibilities. CHANGELOG.md initialised.
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds engine-orchestrated tool calling so consumers can plug a
toolExecutorand let participants invoke tools mid-turn. 100% backward compatible with 0.10.x — every new field is optional, and absence oftoolExecutorpreserves 0.10 behaviour byte-for-byte.This is the upstream half of the work in
ai-consensus-mcpto add tool-calling support to the MCP wrapper.What changes
Surface additions (all optional):
Participant.tools?: ToolDefinition[]— per-participant tool inventoryConsensusOptions.toolExecutor?: ToolExecutor— host-supplied dispatcherConsensusOptions.maxToolIterations?: number— loop cap (default 8, clamped to[1, 32])ModelCallRequest.tools?,ModelCallRequest.toolCallTurns?— forwarded verbatim to the callerModelCallResponse.toolCalls?— caller may return tool-call requestsToolDefinition,ToolCall,ToolCallTurn,ToolCallContext,ToolExecutionResult,ToolExecutortoolCallStart,toolCallComplete(withok,durationMs,preview),toolErrorToolDefinitionSchemaMAX_TOOL_ITERATIONS_CAP(= 32)Engine internals:
#runParticipantTurndrives the per-turn tool loop#dispatchToolCallsruns one iteration's calls and emits the corresponding eventscontentbecomes the participant turn's outputDocumentation:
CHANGELOG.mdinitialised, with 0.10.0 retro-entry and the 0.11.0 entryWhy this shape
Participant.toolsenables this.ModelCallRequest.toolsandtoolCallTurnsare forwarded verbatim; the caller maps them to OpenAI'stools/tool_calls, Anthropic'stools/tool_use, etc. This keeps the library zero-coupled to provider-specific shapes.ParticipantResponseandConsensusResultare unchanged — tool history is observable via events but not persisted on the response. Stable JSON shape for existing consumers.Backward compatibility
toolExecutor, the engine ignores anytoolCallson the response (treatscontentas the final turn — same as 0.10).Test plan
toolCallsignored (0.10 backward compat preserved)toolsdeclared:toolsnot passed to caller{ error }, conversation continues,toolErrorevent fires{ error }: forwarded totoolCallComplete(ok: false) andtoolErroreventsmaxToolIterationsclamping to[1, 32](passing 0 yields 1 iteration)stopReason: \"aborted\"toolCallTurnsdoes not leak across participantstoolCallTurnspayload mirrors what the executor actually returnednpm run typecheckcleannpm run buildcleannpm run test142/142 passFollow-ups (not in this PR)
ParticipantResponse.toolHistory?: ToolCallTurn[]if we want non-event observers to see what happened. Deferred until there's a concrete consumer asking for it.ai-consensus-mcpPR depending on this lands separately (Phase 3 of the consumer-side plan).