conductor-oss / conductor-oss/javascript-sdk
`inputData.method` is stripped before it is read, so `result.toolCalls` names tools after the task type
- Dominant language
- TypeScript
- Stars
- 58
- Forks
- 20
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 7
Description
## Bug Description
`result.toolCalls` is unreliable in three ways after `run()`:
- Tool names for HTTP, MCP and human tools come back as `"http"`, `"call_mcp_tool"` and
`"human"` instead of the tool's name.
- An agent invoked as a tool never appears at all.
- Nothing is detected for a non-OpenAI provider, because selection keys on the provider's
tool-call ID format.
`result.events` is not affected. Events are populated correctly from the stream's own
accumulator (`src/agents/stream.ts:413`).
## Root Cause
All in `_extractToolCalls` (`src/agents/runtime.ts:2049`), called at `:262`, `:406`, `:1660`
and `:1740`.
**`method` is deleted before the line that reads it.** `INTERNAL_KEYS` includes `"method"`
(`:1970`) and is stripped from the copied `inputData` at `:2069-2071`. Five lines later, `:2074`
does:
```ts
const toolName = String(inputData.method ?? taskType).toLowerCase();
```
`inputData.method` is always `undefined` at that point, so the name is unconditionally
`taskType.toLowerCase()`. The comment at `:2073` ("Use the tool name from inputData.method (set
by compiler) if available") describes behaviour the strip above makes impossible, and the
`delete inputData.method` at `:2075` is redundant.
For a worker tool this happens to work, since an executed SIMPLE task's `taskType` is the task's
own name. For every other kind it yields the system task type. It also case-folds, so a tool
named `getWeather` is reported as `getweather`.
**`SUB_WORKFLOW` is on the skip list.** `SYSTEM_TASK_TYPES` (`:1957-1967`) includes it at
`:1966`, and `:2064` skips on that set. `agent_tool` compiles to `SUB_WORKFLOW`, so an agent
used as a tool is dropped.
**Selection keys on the provider's tool-call ID.** `:2062` requires `ref.startsWith("call_")`. A
real reference name is `call_PMnNIdOPvm9EQ8e6tn2kbxPY_0__1`: provider ID, fork index, loop
iteration. `call_` is OpenAI's format; Anthropic gives `toolu_…`; a blank ID becomes a UUID.
Adding `toolu_` is not a fix, since the next provider picks its own format.
## Steps to Reproduce
Register an agent with one HTTP tool and one worker tool named in camelCase, then:
```ts
const runtime = new AgentRuntime(config);
const result = await runtime.run(agent, "use both tools");
result.toolCalls;
// HTTP tool reported as { name: "http", ... }
// worker tool "getWeather" reported as { name: "getweather", ... }
```
Add another agent as a tool and it is absent from `result.toolCalls` entirely.
## Expected Behavior
Read the tool name before stripping internal keys, or resolve it from a field that survives:
`inputData._agent_tool_name` is set by the server's tool dispatch and is not read anywhere
today. Drop the case folding, since the server returns the name verbatim.
Select tool tasks by task type rather than by reference-name prefix, and do not skip
`SUB_WORKFLOW` unconditionally, since it is both an orchestration construct and a tool kind.
## Additional Notes
Verified against `origin/main` @ `816e5f5`.
**Corrections, from verifying this against a live Conductor 5.5.0 server.** The reproduction
holds: `main` returns `["getweather", "http"]` with the agent tool absent. Three claims above
do not.
1. `run()` sees neither field. `GET /agent/execution/{id}`, which `_fetchExecution` reads,
returns tasks carrying only `taskType`, `referenceTaskName`, `status` and `outputData`:
`inputData` is `null` and there is no `taskDefName`. No declared name reaches the SDK on
that path, so HTTP and MCP tools cannot be named from it. A workflow execution does carry
`inputData`, and the same extraction over one returns the real names.
2. The marker is not on every dispatched tool. For an agent tool the sub-workflow task mapper
rebuilds `inputData`, so it arrives nested under `workflowInput`. An agent that discovers
tools at runtime compiles through `enrichToolsScriptDynamic`, which never writes it.
3. `result.events` being populated correctly is not something this issue established. A real
three-tool run returned a single `done` event with streaming enabled. Not chased here.
An earlier revision of this issue claimed `run()` discarded collected events. That was wrong:
`runtime.ts:254` builds the result via `agentStream.getResult()`, which supplies
`events: [...this.events]` from the stream's accumulator. The local array at `:248-251` is dead
code. The assertions in `src/agents/testing/assertions.ts` are not broken by this SDK.
Where those assertions do come back empty for HTTP, MCP, agent-as-tool and human tools, the
cause is server-side: `AgentEventListener.isToolTask` in `conductor-oss/conductor` emits no tool
event for those kinds. Filed there separately.
No assertion in `assertions.ts` reads `toolCalls` (only `eval.ts:90`, for its length), so this is
wrong data rather than broken tests.
Same family of defects filed against `python-sdk`, `java-sdk` and `csharp-sdk`.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/agents/runtime.ts at _extractToolCalls and its call sites, then inspect _fetchExecution to compare the fields available from run() and workflow executions. Verify which tool names can be recovered on each path and separate SDK extraction behavior from the server-side AgentEventListener behavior; done means the required scope and validation path are established without treating assertions.ts as toolCalls coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100