Commit graph

3107 commits

Author SHA1 Message Date
Bryan Helmkamp
f6bf30de3e Disable empty doc-tests and add terse test output alias
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:57:26 -05:00
Bryan Helmkamp
4915574f96 Preserve provider-specific content parts across agent turns
The agent's Turn::Assistant decomposed responses into text, tool_calls,
and reasoning fields, discarding ContentPart::Other items. This lost
OpenAI reasoning items needed for Responses API round-tripping.

Add provider_parts field to Turn::Assistant to carry opaque content
parts through the history, and emit them first in convert_to_messages
so they precede function_call items as the API requires.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:54:35 -05:00
Bryan Helmkamp
b7883d6bef Add agent CLI with tool approval callback
Introduce `ToolApprovalFn` callback in `SessionConfig` to gate tool
execution by permission level. Create `agent-cli` crate as a thin CLI
binary wrapping `Session` with provider/model resolution, permission
model (read-only/read-write/full), interactive approval prompts,
real-time event rendering, debug middleware, and SIGINT handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:50:40 -05:00
Bryan Helmkamp
17a42d19f2 Preserve OpenAI reasoning items for Responses API round-trip
The Responses API requires that reasoning items (type: "reasoning",
id: "rs_xxx") are included alongside their associated function_call
items when replaying conversation history. Without them:
"function_call was provided without its required 'reasoning' item"

Store reasoning output items as ContentPart::Other and replay them
as raw input items in translate_input. Handles both non-streaming
(parse_output) and streaming (handle_output_item_done) paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:30:39 -05:00
Bryan Helmkamp
6e15d602d0 Fix OpenAI Responses API function call ID prefix mismatch
The Responses API returns two distinct IDs on function_call items:
- `id` (item-level, starts with `fc_`)
- `call_id` (call-level, starts with `call_`)

When sending function calls back as input, the `id` field must start
with `fc_`. Previously we used `call_id` for both fields, causing:
"Invalid 'input[1].id': Expected an ID that begins with 'fc'."

Now we preserve the item-level `id` in provider_metadata and use it
for the `id` field, while `call_id` continues to link tool results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:27:24 -05:00
Bryan Helmkamp
e992b57ced Add regression tests for Anthropic beta headers and Gemini thought_signature
Covers two recent runtime failures that lacked test coverage:
- Anthropic: assert deprecated beta header values are not sent
- Gemini: test function call parsing/translation with and without thoughtSignature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:22:14 -05:00
Bryan Helmkamp
94326f5438 docs/specs 2026-02-23 10:17:40 -05:00
Bryan Helmkamp
3b1411a3ac Preserve Gemini thought_signature on function calls
Gemini 3 models include a thoughtSignature field on function call
parts that must be returned in subsequent conversation turns. Add
provider_metadata to ToolCall to carry this through, and extract/emit
it in both the non-streaming and streaming Gemini code paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:14:19 -05:00
Bryan Helmkamp
7dddc49121 Remove deprecated anthropic-beta header values
The `extended-thinking-2025-04-14` and `max-tokens-3-5-sonnet-2025-04-14`
beta headers are no longer accepted by the Anthropic API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:05:43 -05:00
Bryan Helmkamp
d3e396753c Remove stale review docs, add spec DoD pipeline definitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:47:50 -05:00
Bryan Helmkamp
4bb2de5a49 Rename unified-llm crate to llm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:22:14 -05:00
Bryan Helmkamp
023cc54fed Merge unified-llm-cli crate into unified-llm
Move the ullm binary from a separate crate into unified-llm as
src/bin/ullm.rs, consolidating CLI dependencies into the library crate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:18:41 -05:00
Bryan Helmkamp
30cf3a6787 Rename coding-agent-loop crate to agent
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:17:48 -05:00
Bryan Helmkamp
5932b500e7 Implement GET /pipelines/{id}/graph endpoint for SVG rendering
Stores DOT source in ManagedPipeline and pipes it through `dot -Tsvg`
on request. Returns image/svg+xml on success, 502 if graphviz is
unavailable, 404 if pipeline not found. Resolves spec gap #1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:13:36 -05:00
Bryan Helmkamp
55b6000374 Fix OpenAI streaming returning no output for reasoning models
The SSE stream terminated prematurely when a network chunk contained
only unhandled event types (e.g. response.in_progress, reasoning
output_item.added). dispatch_sse_messages returned an empty vec, which
the unfold interpreted as stream-end. Now continues reading when
dispatch produces no StreamEvents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:12:40 -05:00
Bryan Helmkamp
433a7c6247 Implement attractor CLI binary with run and validate subcommands
Add a [[bin]] target to the attractor crate with two subcommands:
- `attractor validate <pipeline.dot>` -- parse and validate only
- `attractor run <pipeline.dot>` -- full pipeline execution with LLM backend

The CLI supports --dry-run, --auto-approve, --resume, --model, --provider,
and two-level verbosity (-v one-line summaries, -vv full event details).

Extracts a shared `default_registry()` function in handler/mod.rs so both
the CLI and server can build a fully-wired HandlerRegistry without
duplicating handler registration boilerplate.

The AgentBackend in cli/backend.rs implements CodergenBackend by creating
a coding-agent-loop Session per node invocation, giving LLM nodes access
to file and shell tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:11:42 -05:00
Bryan Helmkamp
54d0c887dc Add end-to-end test for subgraph class derivation, update spec gap analysis
Closes the subgraph class derivation test gap by adding a parse() pipeline
test that verifies DOT subgraph labels produce correct CSS-like classes on
contained nodes. Updates gap analysis to remove resolved items, add spec
contradictions section, and narrow remaining gaps to SVG rendering and
retry predicate customization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:39:42 -05:00
Bryan Helmkamp
a917a8d615 Implement 6 spec gaps: preamble synthesis, thread_id plumbing, cancellation, recording replay, retry presets, inform()
- Gap 1: Real preamble synthesis per fidelity mode (truncate, compact,
  summary:low/medium/high) in new preamble.rs module, replacing placeholder
- Gap 2: Pass thread_id to CodergenBackend.run() so backends can reuse
  LLM sessions across nodes sharing the same thread
- Gap 7: Engine cancellation via AtomicBool token checked between nodes,
  wired to server cancel endpoint, new Cancelled error variant
- Gap 8: RecordingInterviewer serialization (to_json/from_json, file I/O)
  and new ReplayInterviewer for replaying recorded Q&A sessions
- Gap 12: Preset retry policies selectable from DOT via retry_policy attr
  (none, standard, aggressive, linear, patient)
- Gap 15: Engine calls Interviewer.inform() at pipeline start, stage
  start, and stage complete lifecycle points

37 new tests (450 unit + 49 integration, all passing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:23:27 -05:00
Bryan Helmkamp
08d76b93de Add 8 integration tests: HTTP lifecycle, SSE events, sub-pipeline, manager loop, graph merge, real LLM
- Change server registry_factory to accept Arc<dyn Interviewer> so WaitHumanHandler
  shares the same WebInterviewer as the REST API endpoints
- Add full HTTP lifecycle tests: approve-and-complete flow + cancel flow
- Add SSE event stream content parsing test with frame-level verification
- Add sub-pipeline E2E test through the engine with context propagation
- Add manager loop E2E test with SimulatingChildObserver
- Add graph merge E2E test verifying module prefixing and execution ordering
- Add 3 real LLM tests (#[ignore]) using claude-haiku via AnthropicAdapter
- Add dotenvy and http-body-util dev dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:07:26 -04:00
Bryan Helmkamp
a6d5579d70 Merge branch 'worktree-agent-a0f72fec'
# Conflicts:
#	crates/coding-agent-loop/src/provider_profile.rs
#	crates/coding-agent-loop/src/test_support.rs
2026-02-22 12:50:15 -04:00
Bryan Helmkamp
a66e8c4657 Merge branch 'worktree-agent-a224811b'
# Conflicts:
#	crates/coding-agent-loop/src/subagent.rs
2026-02-22 12:49:29 -04:00
Bryan Helmkamp
fc5f9b096b Merge branch 'worktree-agent-a3e10c40' 2026-02-22 12:49:03 -04:00
Bryan Helmkamp
858f5622d5 Merge branch 'worktree-agent-afb2df8a' 2026-02-22 12:49:00 -04:00
Bryan Helmkamp
c9f28631b6 Consolidate test mocks and profiles in coding-agent-loop
Eliminate ~300 lines of duplicated test infrastructure:

- Add MockExecutionEnvironment::linux() constructor, replacing 4
  identical linux_env() helpers across profile test modules
- Extend MockExecutionEnvironment with written_files, captured_timeout,
  and apply_read_offset_limit fields to replace 4 specialized mocks
  (ReadFileEnv, WriteFileEnv, EditFileEnv, ShellCapturingEnv) in tools.rs
- Add MutableMockExecutionEnvironment for apply_patch tests that need
  writes visible to subsequent reads, replacing MockFileEnv in openai.rs
- Merge ParallelTestProfile into TestProfile with configurable
  parallel_tool_calls and context_window fields
- Replace ProviderTestProfile with TestProfile in provider_profile.rs
  tests, updating TestProfile::build_system_prompt to include user
  instructions like real profiles
- Extract shared CapturingLlmProvider into test_support.rs, replacing
  two inline capturing provider structs in session.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:41:04 -04:00
Bryan Helmkamp
b2b98be522 Simplify coding-agent-loop: extract required_str helper, simplify spawn, restrict test-only accessors
- Proposal 7.1: Introduce required_str() helper in tools.rs to replace ~15
  repetitions of the args.get("param").and_then(|v| v.as_str()).ok_or_else(...)
  pattern across tools.rs and subagent.rs. Provides consistent error messages.

- Proposal 4.2: Simplify SubAgentManager::spawn success path by using ? on
  process_input() result directly, eliminating the redundant success variable
  (always true on the Ok path) and the if-let-Err-return pattern.

- Proposal 8.2: Mark SubAgent::depth() and SubAgentManager::get() as
  #[cfg(test)] since they are only used in tests. Remove SubAgent::id()
  entirely as it was unused even in tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:37:08 -04:00
Bryan Helmkamp
e57257bd55 Simplify coding-agent-loop: BaseProfile, &str returns, EnvContext renames, typed errors
- 2.2: Introduce BaseProfile struct with shared id/model/registry fields;
  AnthropicProfile, GeminiProfile, OpenAiProfile now delegate to it
- 5.4: ProviderProfile::id() and ::model() return &str instead of owned String,
  eliminating unnecessary heap allocations on every call
- 6.1: Rename EnvContext fields: date -> current_date, model_name -> model
  for consistency with trait method names
- 8.1: Mark build_env_context_block (no-context variant) as #[cfg(test)]
  since it is only used in one test
- 9.1: Convert SubAgentManager methods (spawn, send_input, wait, close) from
  Result<T, String> to Result<T, AgentError>; tool executors convert at boundary
  via .map_err(|e| e.to_string())

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:36:50 -04:00
Bryan Helmkamp
23cb9d8e58 Simplify coding-agent-loop: remove redundant constructors, use let-else, add doc comments
- Remove History::new() (redundant with #[derive(Default)]), replace all callers with History::default()
- Replace match with let-else in extract_signatures_from_assistant for clearer happy path
- Add doc comments to Turn::System and Turn::Steering explaining their LLM role mapping
- Verified: Arc import in openai.rs is used in production code, mutex .expect() messages are consistent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:33:53 -04:00
Bryan Helmkamp
69473dbc20 Simplify session.rs: followup loop, schema check, and cached system prompt
- Replace loop/match/break with clearer let-else pattern for followup
  queue processing in process_input (proposal 4.1)
- Simplify validate_tool_args empty schema check by separating null
  and empty-object checks into distinct if-blocks (proposal 4.3)
- Cache system prompt once per input cycle in run_single_input and pass
  it to build_request and check_context_usage/estimate_token_count,
  avoiding redundant string construction on every tool round (5.2, 5.3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:32:17 -04:00
Bryan Helmkamp
c15b532bcf Implement three missing spec features: SubPipelineHandler, GraphMergeTransform, HTTP Server
Add SubPipelineHandler (handler/sub_pipeline.rs) that inline-executes a parsed
sub-graph within the same engine, reading DOT source from node attributes and
propagating context diffs back to the parent pipeline.

Add GraphMergeTransform (transform.rs) that merges nodes and edges from secondary
graphs into a primary graph with namespace-prefixed IDs to avoid collisions.

Add WebInterviewer (interviewer/web.rs) backed by oneshot channels for async
question/answer flow, and HTTP server (server.rs) with 8 axum endpoints behind
a "server" feature flag for pipeline management and human-in-the-loop via web.

Fix tempfile dev-dependency usage in server production code by using std::env::temp_dir.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:07:44 -04:00
Bryan Helmkamp
a2f9e87b7c Close spec compliance gaps: retry context, thread resolution, fan-in logging
Fix 3 confirmed gaps from spec compliance review (85 items, 91.8% aligned):

- Write internal.retry_count.<node_id> to PipelineContext after retries
  so handlers and conditions can access retry counts (spec 5.1)
- Add graph-level default_thread (step 3) to 5-step thread ID resolution,
  pass graph param to resolve_thread_id (spec 5.4)
- Write prompt.md/response.md in fan_in LLM evaluation path (spec 5.6)

Also includes pre-existing improvements: checkpoint stores node_outcomes
and next_node_id for correct resume, engine timeout enforcement,
auto_status support, fidelity degradation on resume, preamble injection,
is_retryable error classification, stylesheet specificity correction,
full stylesheet parse validation, direction_valid lint rule, pre-hook
returns Skipped not Fail, fan-in score-based sorting and all-fail
detection, manager_loop child autostart and steer cooldown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:49:21 -04:00
Bryan Helmkamp
2bdec7e8e6 Add README.md for each crate with usage examples
Create comprehensive READMEs for unified-llm, unified-llm-cli, and
coding-agent-loop crates, and expand the attractor README from a
one-liner into full documentation covering key concepts, API usage,
and code examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:12:21 -04:00
Bryan Helmkamp
34836200e5 Implement attractor crate: DOT-based pipeline runner with full spec compliance
Adds the attractor crate implementing all 11 sections of the attractor spec:
- DOT parser (lexer, grammar, semantic analysis) for strict DOT subset
- Pipeline execution engine with edge selection, goal gates, retry logic,
  failure routing, checkpoint save/resume, and loop_restart
- 9 node handlers: start, exit, codergen, wait_human, conditional, parallel
  (concurrent with join/error policies), fan_in (with LLM eval), tool, manager_loop
- State management: PipelineContext, Outcome, Artifact store, fidelity resolution
- Human-in-the-loop: Interviewer trait with auto_approve, callback, queue,
  recording, and console implementations, plus timeout enforcement
- Validation: 14 built-in lint rules with custom rule registration API
- Model stylesheet with universal/shape/class/ID selectors and specificity
- Transforms: variable expansion, stylesheet application, preamble; plus
  PipelineBuilder with register_transform and prepare_pipeline
- Condition expression language with =, !=, bare-key truthiness, && combinator
- Event system with all 16 event types emitted by engine and handlers
- Tool call hooks (pre/post) for CodergenHandler
- Run directory with manifest.json and per-node status.json

370 tests (354 unit + 16 integration) covering all spec sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:17:32 -04:00
Bryan Helmkamp
db32f00cd0 Simplify coding-agent-loop: deduplicate mocks, narrow traits, type events
- Extract shared test infrastructure (MockExecutionEnvironment, TestProfile,
  MockLlmProvider) replacing 11 duplicate mock implementations across tests
- Deduplicate tool execution logic between sequential and parallel paths
- Narrow ProviderProfile trait from 14 to 7 required methods via
  ProfileCapabilities struct and default implementations
- Replace stringly-typed HashMap event data with typed EventData enum
- Extract shared assemble_system_prompt helper and register_subagent_tools
  default method, eliminating copy-paste across all 3 profiles
- Replace fragile shell-based glob with glob crate, fix rg detection
- Add delete_file to ExecutionEnvironment, wire git context into env block
- Remove dead code (AgentError::Io, count_turns, trivial derived-trait tests)
- Use match-based lookups in truncation instead of per-call HashMap allocation

Net reduction: -1,401 lines across 20 files. All 180 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 16:34:29 -04:00
Bryan Helmkamp
1ffc954a0f Add knowledge_cutoff to ProviderProfile, fix subagent default max_turns
- Add knowledge_cutoff() method to ProviderProfile trait so session can
  populate EnvContext from the profile instead of leaving it empty
- Set subagent default max_turns to 50 per spec (was using session
  factory default which could be 0/unlimited)
- Add corrected spec compliance review after manual verification found
  the initial 5-agent review was largely false positives

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 16:02:20 -04:00
Bryan Helmkamp
8ff36fa900 Close spec compliance gaps in coding-agent-loop
Fix all gaps identified by spec review against docs/specs/coding-agent-loop-spec.md:

- Replace placeholder system prompts with substantial provider-aligned prompts
  for OpenAI (codex-rs style), Anthropic (Claude Code style), and Gemini
  (gemini-cli style) covering identity, tool usage, and coding best practices
- Fix ExecutionEnvironment trait: add offset/limit to read_file, depth to
  list_directory, path to glob, remove separate args from exec_command
- LocalEnv: use /bin/bash -c, spawn process groups with setsid, SIGTERM to
  -pid, use ripgrep with grep fallback, add env var safelist
- Add environment context block with <environment> XML tags including git
  branch, date, model, and knowledge cutoff fields
- Capture git context snapshot (branch, status, recent commits) on init
- Add user_instructions to SessionConfig, appended as final prompt layer
- Emit AssistantTextStart before LLM calls, SESSION_END on abort path
- Fix truncation messages to match spec wording exactly
- Implement provider_options() for all profiles (reasoning, beta headers,
  safety settings)
- Add Gemini-specific tools: read_many_files, list_dir, web_search, web_fetch
- Wire spawn_agent max_turns parameter
- Fall back to working_dir for project doc discovery outside git repos

All 188 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 15:22:10 -04:00
Bryan Helmkamp
99db2753ca Add user instructions, env context, expanded profiles to coding-agent-loop
Close spec compliance gaps:
- Add user_instructions to SessionConfig and ProviderProfile trait (spec Section 6.1 layer 5)
- Populate EnvContext with git branch, date, model name during Session::initialize()
- Expand all three profile system prompts with identity, tool guidance, coding practices
- Implement provider_options: Anthropic beta headers, OpenAI reasoning effort, Gemini safety settings
- Add register_subagent_tools() to all profiles for spawn_agent/send_input/wait/close_agent
- Add list_dir tool to Gemini profile (spec Section 3.6)
- Add chrono dependency for date formatting

20 new tests (170 → 190), all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:51:13 -04:00
Bryan Helmkamp
010d09a03a Close remaining spec compliance gaps in coding-agent-loop
- Abort now transitions to CLOSED state and returns Err(Aborted)
- Closed sessions no longer emit SessionStart before rejecting input
- Add set_reasoning_effort() for mid-session reasoning effort changes
- Fix spawn_agent truncation limit from 30k to spec-required 20k
- Add JSON Schema validation of tool arguments before execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:05:16 -04:00
Bryan Helmkamp
82572678c0 Fix model alias resolution and invalid gpt-5.2-mini model ID
resolve_model() was passing raw alias strings (e.g. "gpt5") directly to
APIs instead of resolving them to actual model IDs (e.g. "gpt-5.2").
Also rename gpt-5.2-mini to gpt-5-mini, which is the correct OpenAI
model ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:01:30 -04:00
Bryan Helmkamp
a8c576deba Close spec compliance gaps in coding-agent-loop
Replace all stub tools in profiles with real make_*_tool() factories,
wire up project docs discovery in session, add missing events
(SessionStart, SteeringInjected, Error), enrich environment context
block, fix per-tool truncation modes, improve loop detection to check
all groups, add SIGTERM-before-SIGKILL on timeout, and update subagent
with SubAgentResult struct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:54:50 -04:00
Bryan Helmkamp
890a6e9ccb Simplify unified-llm-cli: remove boilerplate, deduplicate, modernize idioms
- Remove unused tokio-stream dependency
- Replace manual Runtime::new().block_on() with #[tokio::main]
- Extract print_usage helper to deduplicate token display format
- Flatten Models match (single-variant enum destructure)
- Extract PromptArgs struct to reduce run_prompt parameter count
- Defer trim().to_string() allocation in read_stdin_prompt
- Fix #[allow(deprecated)] comment accuracy
- Replace ref q with &query (Rust 2018+ idiom)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:48:27 -04:00
Bryan Helmkamp
6a53634b05 Implement coding-agent-loop crate: tools, profiles, subagents, parallel execution
Add the core infrastructure for a programmable agentic coding loop:

- Core tool executors (read_file, write_file, edit_file, shell, grep, glob)
- Project doc discovery (AGENTS.md, provider-specific files, 32KB budget)
- Provider profiles: Anthropic (200K ctx), OpenAI (128K ctx, v4a apply_patch),
  Gemini (1M ctx) with provider-specific system prompts
- Subagent system with spawn/wait/close, depth limiting
- Parallel tool execution via futures::join_all when provider supports it
- Context window awareness with 80% threshold warning events

163 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:57:49 -04:00
Bryan Helmkamp
255a93c2e8 Add streaming middleware support and re-export set_default_client
Extend the Middleware trait with process_stream_event() for event-level
observation/transformation of streaming responses. Add
wrap_stream_with_middleware() helper. Re-export set_default_client at the
crate root per spec Section 2.5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:57:00 -04:00
Bryan Helmkamp
fa542605d7 Add ullm CLI for unified-llm library
New crate `unified-llm-cli` with binary `ullm` providing:
- `prompt` command: generate text via streaming/non-streaming, with
  system prompts, options (-o temperature=0.5), stdin piping, and
  token usage display
- `models list` command: browse catalog models with --provider and
  --query filters
- Auto-detects provider from catalog for correct API routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:48:19 -04:00
Bryan Helmkamp
978a477d1e Fix 8 spec compliance gaps in unified-llm
- Enforce stream_read timeout (30s default) in all 4 providers' streaming code
- Add with_timeout() builder method to all adapter constructors
- Fix ResponseFormatType::JsonObject to serialize as "json" per spec
- Add STEP_FINISH to StreamEventType enum in spec doc
- Add UnsupportedToolChoice error and enforce in all adapters via validate_tool_choice()
- Fix error classification to check status code before message content
- Add stop_sequences support to OpenAI Responses API adapter
- Handle Gemini thought parts (thought: true) in both complete and streaming paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:41:41 -04:00
Bryan Helmkamp
8a19b7be2a Fix 8 spec compliance gaps in unified-llm
- OpenAI adapter: include is_error flag on function_call_output items
- Anthropic adapter: extract retry_after from headers in streaming error path
- Error retryability: unknown errors now default to retryable per spec
- stream_object: add ObjectStreamResult wrapper with object() accessor
- TimeoutConfig: add From<f64> for total-only timeout shorthand
- Message::tool_result: accept serde_json::Value to preserve structured content
- GenerateParams: expose repair_tool_call field wired to execute_all_tools_with_repair
- Both generate() and stream() tool loops use repair-aware execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:15:00 -04:00
Bryan Helmkamp
88e71c64ad Implement spec gaps: reasoning tokens, tool validation, context injection, extensibility
- Anthropic adapter estimates reasoning_tokens from thinking block text lengths
- Add tool call validation against JSON schema + repair_tool_call callback
- Call adapter.initialize() on provider registration
- Extract model catalog to catalog.json data file loaded via include_str!
- Add ContentPart::Other variant for unknown/extensible content kinds
- Change StreamEvent::Error field from String to SdkError
- Add ToolContext (tool_call_id, messages, abort_signal) to execute handlers
- Gemini adapter uses gRPC status codes from error bodies for classification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:59:28 -04:00
Bryan Helmkamp
5a76551847 Implement spec gaps: audio/document content parts, streaming tool loop improvements
- Add Audio/Document content part handling across all providers:
  Anthropic supports documents natively, Gemini supports both audio
  and documents, OpenAI and OpenAI-compatible produce text fallbacks
- Add stop_when support to streaming tool loops (was only in generate())
- Add retry on initial stream connection (matching generate() behavior)
- Add total and per_step timeout support to streaming tool loops

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:51:26 -04:00
Bryan Helmkamp
a4978812e7 Implement spec gaps: StepFinish stream event, OpenAI org/project headers, default_headers
Add StepFinish stream event variant emitted between tool execution steps
during streaming, matching spec section 5.9. Add OPENAI_ORG_ID and
OPENAI_PROJECT_ID env var support with corresponding HTTP headers. Add
default_headers builder method to all four provider adapters for custom
header injection in programmatic setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:41:16 -04:00
Bryan Helmkamp
2f89a440c0 Implement spec gaps: Anthropic structured output, metadata, Gemini rate limits
- Anthropic: Add response_format support via tool-based extraction (JsonSchema
  injects synthetic tool, JsonObject appends system prompt instruction)
- Anthropic: Pass Request.metadata through to Messages API
- Anthropic: Refactor complete()/stream() to share build_api_request()
- Gemini: Parse rate limit headers from HTTP responses in complete() and stream()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:31:26 -04:00
Bryan Helmkamp
5f2385b87b Implement spec gaps: abort signal, StreamResult, provider_options, max_tool_rounds fix
- Add abort signal support using CancellationToken for cooperative cancellation
  of generate() and stream() calls
- Fix max_tool_rounds=0 to skip tool execution entirely (was executing first round)
- Add StreamResult wrapper with response(), text_stream(), partial_response()
  and multi-step tool loop support in high-level stream()
- Add OpenAI metadata and provider_options.openai pass-through to Responses API
- Add Gemini provider_options.gemini pass-through (safety settings, cached content)
- Add OpenAI-compatible provider_options.<name> pass-through using adapter name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:23:22 -04:00