Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/claude-agents/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ ClaudeNotebookEdit: NativeTool

## Implementation Details

### 1. Prompt Construction (`build_prompt`)
### 1. Prompt Construction (`_build_prompt`)

The handler uses a flat `prompt` + optional `system_prompt` shape that the `claude_agent_sdk.query()` call accepts:

Expand All @@ -94,7 +94,7 @@ neither?

Note: the `messages` path collapses conversation history into a single flat string — roles are not individually structured. This is a limitation of the agent SDK's `query()` interface.

### 2. Tool Wiring (`build_tool_mcp`)
### 2. Tool Wiring (`_build_tool_mcp`)

Tools are delivered via an in-process MCP server, not as raw JSON schema defs. The pipeline:

Expand Down Expand Up @@ -238,4 +238,4 @@ Span names and attributes are described in [Implementation Details → Telemetry
- **MCP tool name prefix**: tools registered in the MCP server are accessible as `mcp__tool-mcp__<name>`. The `allowed_tools` list must use this prefix; omitting it will cause the agent to not invoke any tools.
- **Async generator teardown**: always use `break` inside `async for` (not `return`) and wrap the loop in `try/finally: await gen.aclose()`. See TESTING.md Appendix A.4.
- **`message.usage` shape**: the raw usage dict from the `claude_agent_sdk` is passed through directly to `parse_usage`. It accepts `input_tokens`/`output_tokens` which the SDK provides.
- **Native tools** (`ClaudeWebSearch`, etc.) are registered in `tool_handlers` as `NativeTool` sentinel instances (not callables). The handler's `partition_tools` function separates them from user-defined tools — native tools go into `native_tool_names` for the agent's built-in access, user tools go through the MCP server.
- **Native tools** (`ClaudeWebSearch`, etc.) are registered in `tool_handlers` as `NativeTool` sentinel instances (not callables). The handler's `_partition_tools` function separates them from user-defined tools — native tools go into `native_tool_names` for the agent's built-in access, user tools go through the MCP server.
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Public API of launchdarkly_ai_claude_agents. Generated by `make api-report`; do not edit.
build_prompt(config: AiConfigRep, user_input: str | None, variables: dict[str, Any], history: list[dict[str, Any]] | None = None) -> tuple[str, str | None]
build_tool_mcp(config_tools: dict[str, Any], handlers: dict[str, Any]) -> Any [async]
claude_agents(config_key: str, user_input: str, context: LDContext, *, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None, **kwargs: Any) -> Any
claude_graph(key: str, *, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None, **options: Any) -> Any
ClaudeBash = NativeTool('Bash')
Expand All @@ -15,5 +13,4 @@ ClaudeWebSearch = NativeTool('WebSearch')
ClaudeWrite = NativeTool('Write')
create_claude_agents_handler(*, capture_content: bool | None = None, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None) -> ProviderHandler
create_claude_agents_skills_handler(*, capture_content: bool | None = None, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None, skills_temp_dir: str | os.PathLike[str] | None = None) -> ProviderHandler
partition_tools(config_tools: dict[str, Any] | None, tool_handlers: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], list[str]]
to_claude_agents(def_promise: Any, opts: dict[str, Any] | None = None) -> Any
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@
)
from .graph import claude_graph
from .handler import (
build_prompt,
build_tool_mcp,
claude_agents,
create_claude_agents_handler,
partition_tools,
)
from .native_graph import to_claude_agents
from .skills import create_claude_agents_skills_handler
Expand All @@ -41,13 +38,10 @@
"ClaudeWebFetch",
"ClaudeWebSearch",
"ClaudeWrite",
"build_prompt",
"build_tool_mcp",
"claude_agents",
"claude_graph",
"create_claude_agents_handler",
"create_claude_agents_skills_handler",
"partition_tools",
"to_claude_agents",
]

Expand Down
20 changes: 10 additions & 10 deletions packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
# ---------------------------------------------------------------------------


async def build_tool_mcp(
async def _build_tool_mcp(
config_tools: dict[str, Any],
handlers: dict[str, Any],
) -> Any:
Expand Down Expand Up @@ -211,7 +211,7 @@ async def _execute(args: Any, _name: str = tool_name) -> dict[str, Any]:
return create_server(name=TOOL_MCP_NAME, version="1.0.0", tools=tool_objs)


def partition_tools(
def _partition_tools(
config_tools: dict[str, Any] | None,
tool_handlers: dict[str, Any],
) -> tuple[dict[str, Any], dict[str, Any], list[str]]:
Expand Down Expand Up @@ -242,7 +242,7 @@ def partition_tools(
def _native_tool_aliases(tool_handlers: dict[str, Any]) -> dict[str, str]:
"""AI Config key → provider tool name, for the config's native tools only.

``partition_tools``'s ``native_tool_map`` is keyed the other way round, by provider name, which
``_partition_tools``'s ``native_tool_map`` is keyed the other way round, by provider name, which
loses the AI Config key, and the key is what a config's declared schema is filed under.
:class:`~.spans.ToolCatalog` needs both ends to report one tool once, under the name the model
saw, with the schema the config gave it.
Expand Down Expand Up @@ -438,7 +438,7 @@ def cancel_open_spans() -> None:
# ---------------------------------------------------------------------------


def build_prompt(
def _build_prompt(
config: AiConfigRep,
user_input: str | None,
variables: dict[str, Any],
Expand Down Expand Up @@ -794,7 +794,7 @@ async def _call_impl(
# truthily, and this handler's own suite is built on mock spans in places.
open_root_span: Any = span

prompt, system_prompt = build_prompt(config, user_input, vs, history)
prompt, system_prompt = _build_prompt(config, user_input, vs, history)
query_prompt = build_query_prompt(config, user_input, vs, history, prompt)
if config.get("outputFormat"):
schema_instr = f"Respond with valid JSON matching this schema:\n{json.dumps(config['outputFormat'])}"
Expand All @@ -803,7 +803,7 @@ async def _call_impl(
)
opening = _opening_of(prompt, system_prompt)

native_tool_map, user_config_tools, native_tool_names = partition_tools(
native_tool_map, user_config_tools, native_tool_names = _partition_tools(
config.get("tools"), th
)
catalog = ToolCatalog(config.get("tools"), _native_tool_aliases(th))
Expand Down Expand Up @@ -831,7 +831,7 @@ async def _call_impl(
prepared = await _prepare(config) if _prepare is not None else None
extra_builtins = prepared.extra_builtins if prepared is not None else []
tool_mcp = (
await build_tool_mcp(user_config_tools, th)
await _build_tool_mcp(user_config_tools, th)
if user_config_tools
else None
)
Expand Down Expand Up @@ -1055,11 +1055,11 @@ async def _stream_gen(
span = start_root_span(config, variables)
parent = parent_context_of(span)

prompt, system_prompt = build_prompt(config, user_input, variables, history)
prompt, system_prompt = _build_prompt(config, user_input, variables, history)
query_prompt = build_query_prompt(config, user_input, variables, history, prompt)
opening = _opening_of(prompt, system_prompt)

native_tool_map, user_config_tools, native_tool_names = partition_tools(
native_tool_map, user_config_tools, native_tool_names = _partition_tools(
config.get("tools"), tool_handlers
)
catalog = ToolCatalog(config.get("tools"), _native_tool_aliases(tool_handlers))
Expand Down Expand Up @@ -1094,7 +1094,7 @@ async def _stream_gen(
prepared = await prepare(config) if prepare is not None else None
extra_builtins = prepared.extra_builtins if prepared is not None else []
tool_mcp = (
await build_tool_mcp(user_config_tools, tool_handlers)
await _build_tool_mcp(user_config_tools, tool_handlers)
if user_config_tools
else None
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@

from launchdarkly_ai_claude_agents.handler import (
_build_hooks,
_build_prompt,
_build_tool_mcp,
_host_query_options,
_options_parameters,
_partition_tools,
_raise_if_run_failed,
build_prompt,
build_query_prompt,
build_tool_mcp,
partition_tools,
)

TOOL_MCP_NAME = "tool-mcp"
Expand Down Expand Up @@ -129,16 +129,16 @@ async def _run_query(
track_data = make_track_data(node, graph_key, run_id)
wrapped = _wrap_native_tools(tool_handlers, ld_context, track_data)

prompt, system_prompt = build_prompt(node.config, input_text, variables)
prompt, system_prompt = _build_prompt(node.config, input_text, variables)
query_prompt = build_query_prompt(
node.config, input_text, variables, history, prompt
)
native_tool_map, user_config_tools, native_tool_names = partition_tools(
native_tool_map, user_config_tools, native_tool_names = _partition_tools(
node.config.get("tools"), wrapped
)

tool_mcp = (
await build_tool_mcp(user_config_tools, wrapped) if user_config_tools else None
await _build_tool_mcp(user_config_tools, wrapped) if user_config_tools else None
)
child_mcp = (
create_server(
Expand Down
Loading
Loading