feat(agent-tool-set)#31
Conversation
robert-j-y
left a comment
There was a problem hiding this comment.
needs a rebase onto the current turborepo-migration — PR #30 landed and widened Tool to ClientTool | ServerToolBase. After rebase, two sites break:
1. packages/agent/src/inner-loop/call-model.ts:102 — produces TS2339: Property 'function' does not exist on type 'Tool'. Property 'function' does not exist on type 'ServerToolBase'. at compile time, and TypeError: Cannot read properties of undefined (reading 'name') at runtime if any serverTool(...) is in the array. Change:
const filteredTools = activeSet ? tools?.filter((t) => activeSet.has(t.function.name)) : tools;to:
const filteredTools = activeSet
? tools?.filter((t) => isServerTool(t) || activeSet.has(t.function.name))
: tools;(import isServerTool from ../lib/tool-types.js)
2. packages/agent-tool-set/src/tool-set.ts buildToolsMap (lines 32–42) — same root cause. Compile error doesn't surface yet only because agent-tool-set resolves @openrouter/agent through the stale compiled .d.ts; runtime t.function.name still throws on any server tool passed to createToolSet. Change:
for (const t of tools) {
const name = t.function.name;
if (map.has(name)) throw new Error(`Duplicate tool name: "${name}"`);
map.set(name, t);
}to:
for (const t of tools) {
if (isServerTool(t)) continue;
const name = t.function.name;
if (map.has(name)) throw new Error(`Duplicate tool name: "${name}"`);
map.set(name, t);
}(import isServerTool from @openrouter/agent)
Server tools have no name to activate by, so skipping them keeps ToolSet client-tool-only while still allowing users to pass a mixed array through.
3. PR description: InferActiveTools and InferInactiveTools are listed under "Types:" but are not exported from packages/agent-tool-set/src/index.ts. Remove or add.
Adds a new workspace package with declarative activate/deactivate/ activateWhen/deactivateWhen for tools, with predicates that receive the SDK's ConversationState and typed shared context. Also adds an `activeTools?: readonly string[]` option to callModel so inferTools() output can be spread directly into a request. Port of ai-tool-set v1.0.0 (MIT (C) zirkelc).
Drops the record-mapping InferToolSet and its InferActiveTools / InferInactiveTools aliases (faithful-port artifacts without true partition narrowing). The streaming-events discriminated union takes the InferToolSet name.
…er and buildToolsMap Addresses review feedback on PR #31 after rebasing onto current main (PR #30 widened `Tool` to `ClientTool | ServerToolBase`). - call-model.ts: filter keeps server tools unconditionally; name matching only applies to client tools, preventing `t.function.name` access on `ServerToolBase`. - tool-set.ts: `buildToolsMap` skips server tools since they have no name to activate by; `createToolSet` remains client-tool-only while accepting mixed arrays.
mattapperson
left a comment
There was a problem hiding this comment.
Two concerns on the new ToolSet surface worth a look before merge.
Addresses review feedback on PR #31: - Server tools are no longer silently dropped. ToolSet now tracks the full ordered list separately from the client-tool name index, so `.tools` and `.inferTools()` return both client and server tools. Server tools are always active (no name to filter by) and never appear in the `activeTools` list returned by `inferTools()`. - `createToolSet` now exposes the `TShared` generic (`createToolSet<T, TShared>`), so predicates type `context` as the user's context shape instead of `Record<string, unknown>`.
mattapperson
left a comment
There was a problem hiding this comment.
reviewed, no issues found
Summary
@openrouter/agent-tool-set— a new workspace package for the SDK'stool()/callModelAPIs.activeTools?: readonly string[]option tocallModelsoinferTools()output can be spread directly into a request, with filtering applied before both API conversion and executor registration.{ state: ConversationState, context: TShared }.InferToolSet<T>is mapped to this SDK's streaming event shapes (ToolPreliminaryResultEvent/ToolResultEvent).Public API (
@openrouter/agent-tool-set)createToolSet({ tools, mutable? })— array input; duplicate names throw at construction.ServerToolBaseentries are accepted and skipped (client-tool-only activation)..toolsgetter (full, ordered)..activate/.deactivate— string or array of names..activateWhen/.deactivateWhen— name+predicate or{ name: predicate, ... }bulk form. Last-call-wins per tool..inferTools(input?)→{ tools: Tool[]; activeTools: string[] }..clone({ mutable? }).ActivationInput,ActivationPredicate,InferToolSet.Agent changes
BaseCallModelInputgainsactiveTools?: readonly string[];clientOnlyFieldsincludes it.callModelfilterstoolsbyactiveTools(Set membership) beforeconvertToolsToAPIFormatand before passing toModelResult, so filtered tools are neither advertised to the model nor callable by the executor. Server tools bypass the filter (they have no name to match against).Test plan
pnpm build— both packages compilepnpm typecheck— strict mode cleanpnpm test— 268 unit tests pass (249 agent + 19 agent-tool-set)packages/agent-tool-set/tests/unit/tool-set.test.ts— 19 cases covering construction, duplicate-name error, activate/deactivate (string + array), activateWhen/deactivateWhen (single + bulk), last-call-wins, immutable vs mutable, clone, input handling, and predicate typingpackages/agent/tests/unit/call-model-active-tools.test.ts— 3 cases verifying the outbound request body via a capturingHTTPClientpnpm lint— Biome cleanpnpm changeset status— both packages bumped minorRelease
.changeset/agent-tool-set.md— minor bump for both@openrouter/agent-tool-set(0.1.0 initial) and@openrouter/agent(newactiveToolsoption).