conductor-oss / conductor-oss/javascript-sdk

`inputData.method` is stripped before it is read, so `result.toolCalls` names tools after the task type

オープン
#172 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
TypeScript
スター
58
フォーク
20
平均マージ
1日 13時間
マージ済み PR(30日)
7

説明

## 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`.

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

src/agents/runtime.ts の _extractToolCalls とその呼び出し箇所から始め、次に _fetchExecution を調べて、run() と workflow 実行で利用できるフィールドを比較します。各パスでどのツール名を復元できるかを確認し、SDK の抽出動作とサーバー側の AgentEventListener の動作を分離します。必要なスコープと検証パスが確立され、assertions.ts を toolCalls のカバレッジとして扱っていなければ完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
api, backend
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
38/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。