[Feature] Add agent bridge and webui update#1364
Conversation
Add a reverse-proxy bridge that exposes an Anthropic /v1/messages endpoint and routes traffic to EvalScope's Model.generate_async, letting external agent CLIs (claude-code, mock) drive any DefaultDataAdapter-based benchmark while their LLM calls flow back through the bridge and into a structured Trajectory. * HTTP bridge with non-streaming + synthesized SSE Anthropic route * Per-loop ModelProxyServer keyed on id(loop) with auto-shutdown via AsyncioLoopRunner.register_close_callback (no fresh port per sample) * Runner protocol + MockAgentRunner / ClaudeCodeRunner; AgentEnvironment gains an env= kwarg honored by local + enclave backends * TaskConfig.external_agent typed as Optional[Any] and validated lazily so config import does not pull the runner stack * IDEALAB credentials read from EVALSCOPE_IDEALAB_TOKEN (via .env)
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive integration for external agents, featuring an HTTP reverse-proxy bridge that translates Anthropic and OpenAI protocols to EvalScope's internal model API. Key additions include the ClaudeCodeRunner for executing third-party CLIs in sandboxes, a unified AgentTrace format for both native and external runs, and a refactored registry system to support aliases. The web dashboard is also updated with new components for trajectory visualization. Feedback identifies a critical bug in the bridge server where asyncio.Lock is incorrectly shared across event loops, potentially causing runtime errors; a switch to threading.Lock is recommended. Other suggestions include implementing proper cleanup for temporary directories in the agent runner and using Pydantic validation aliases to prevent breaking changes caused by field renaming.
- Light theme reworked from cool-slate translation to warm-cream Console: surface ladder (#faf9f5/#f0ebe1/#fff/#f5f0e7), solid hex hairlines (#e6dfd8/#d6cdbe/#c1b6a3) replacing translucent violet, warm-ink shadow tints, warm-grey text ladder. Documented in DESIGN.md. - Score gradient gains per-theme CSS vars (--score-fg-s/-l/-bg-a-mul) so yellow mid-tones stay legible on cream without forking the brand HSL. scoreColor/scoreBg switch to modern CSS color function syntax. - Chart palette forks per-theme RGB to restore data-bearing punch on cream. - ScoreRing stroke bumped to 6px (mini)/8px (summary) per spec; ScoreChip switches to outline style to sidestep yellow-fill legibility on cream. - Bulk inline-style → Tailwind className across reports/single chat/ui; CSS-var refs preserved so theme switching is unaffected. - scoreColor import consolidated to @/utils/colorScale (drop Table re-export).
Adds the third bridge route POST /openai/v1/responses + the OpenAI Responses SSE event sequence (pre-resolve mode, mirroring inspect_ai) so codex v0.133+ (which dropped chat completions) can drive external-agent evaluations through evalscope. * translate_responses.py: full coverage of codex/OpenAI input items — message / function_call / function_call_output / reasoning / custom_tool_call(_output) / computer_call(_output) plus opaque placeholder rendering for built-in tool items (web_search_call, mcp_call, mcp_list_tools, file_search_call, code_interpreter_call, image_generation_call, local_shell_call). item_reference + unknown types log WARN and skip (forward-compat, no raise). * sse_responses.py: pre-resolve synthesizer for the full Responses event sequence (created → in_progress → output_item.added/done × N → completed), strictly monotonic sequence_number, 32-char function_call_arguments chunking matching inspect_ai. Upstream error path emits OpenAI SDK ResponseErrorEvent shape (flat type/code/message/param/sequence_number). * server.py: route registration + handlers. Shared _auth_check_openai and _prepare_sse_response helpers extracted; anthropic 401 shape preserved for claude-code compatibility. * trace_recorder.py: record_responses_turn reusing _record_turn via new messages_key parameter; _ingest_initial_user_message filters type=='message' to avoid mis-ingesting function_call_output as user prompts; _extract_responses_tool_results WARNs once when an output appears mid-input[] instead of at the tail. * CodexRunner: positional prompt (avoids ms_enclave stdin gap), -c model_providers.evalscope.* config injection, npm install fallback for codex CLI. Setup + run + answer extraction via --output-last-message. Tests: 23 new mock cases (event_sequence × 3 spy oracle, round_trip × 5, tool_calls_streaming × 2, multi_turn × 1, instructions_merge × 3, previous_response_id_ignored × 1, extended_item_types × 6, error_paths × 2) + opt-in real codex × qwen3-max e2e (test_codex_runner). All 47 mock tests + lint clean. SWE-bench Pro 1 sample × codex × qwen3-max verified end-to-end on enclave: 37 turns (35 exec_command + 1 apply_patch + final stop), 828K input / 9.5K output tokens, rc=0, bridge ERROR/WARN = 0.
Runners
- CodexRunner: hardcode sandbox=workspace-write + always-bypass-approvals;
drop output-last-message kwarg (constant). Remaining knobs: model_name,
extra_args, extra_config, home_override, auto_install, install_timeout_s,
node_setup_url, npm_package. Default kwargs={} now works end-to-end.
- ClaudeCodeRunner: rename install_node -> auto_install for API parity
with CodexRunner.
- Both: expose install_timeout_s (default 600s codex / 300s claude-code)
so slow CDNs / apt mirrors don't need a source edit. Class _INSTALL_TIMEOUT_S
kept as the default sentinel for callers that already passed it.
Trace recorder
- _ingest_initial_messages: capture the FULL initial setup on the first
responses-API turn (top-level instructions + every system/developer item
+ every user item, in document order), not just the first user message.
Fixes the SWE-bench Pro symptom where codex put the task in `instructions`
and the recorded transcript only contained <environment_context>.
- _extract_responses_tool_results: scan the whole input[] and dedup against
already-recorded ChatMessageTool entries, replacing the tail-only
shortcut. codex re-sends every (function_call, function_call_output)
pair interleaved through input[], not clumped at the tail, so the old
logic logged 100+ WARNs per multi-turn run and re-emitted prior tool
results as new events.
Tests
- Four new BridgeTraceRecorder cases for the responses path: instructions
-> ChatMessageSystem, multi-user ingest, no re-ingest on later turns,
empty-user-content edge case.
- Delete tests/agent/external/test_real_claude_code.py and test_streaming.py
(subsumed by qwen_via_claude_code + walking_skeleton). Rename
test_responses_instructions_merge.py -> test_responses_input_assembly.py
to match what it actually covers.
Docs
- Split docs/{en,zh}/user_guides/agent.md into a subdir with index.md
(overview) + native.md (AgentLoop / Strategy / Environment) + bridge.md
(external CLI bridge: claude-code, codex). Cross-link from sandbox /
parameters / visualization / READMEs / index.
Scripts
- Delete scripts/run_codex_qwen.py, run_swebenchpro_codex_qwen.py,
run_swebenchpro_qwen_cc.py. All three were dev-time one-shot triggers
with no in-tree references; their code paths are now fully covered by
tests/benchmark/test_agent_loop.py (TestSWEBenchProExternalClaudeCode,
TestSWEBenchProExternalCodex). pytest -v -s runs the same end-to-end.
Verified
- 51 mock tests + lint pass
- SWE-bench Pro x codex x qwen3-max (limit=3, 10min) clean exit
- ms-enclave cross-loop shutdown noise gone after upstream PR
(modelscope/ms-enclave#12)
There was a problem hiding this comment.
Pull request overview
Adds an external-agent bridge path alongside the native AgentLoop, updates agent configuration plumbing, expands bridge/test coverage, and refreshes the Web UI design system and documentation.
Changes:
- Added external agent runner/bridge abstractions, config union support, runner tests, and SWE-bench patch extraction hooks.
- Refactored native agent execution into a shared runner and renamed agent free-form options toward
kwargs. - Updated Web UI tokens/components, report/task pages, trace event typing, and agent documentation.
Reviewed changes
Copilot reviewed 145 out of 146 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
tests/agent/test_t2_environment.py |
Updates native agent tests for async generation and kwargs. |
tests/agent/test_agent_interfaces.py |
Updates config default assertion to kwargs. |
tests/agent/external/test_walking_skeleton.py |
Adds bridge smoke and auth tests. |
tests/agent/external/test_responses_previous_response_id_ignored.py |
Adds Responses warning-path test. |
tests/agent/external/test_responses_multi_turn.py |
Adds Responses multi-turn trace test. |
tests/agent/external/test_responses_input_assembly.py |
Adds Responses translator ordering tests. |
tests/agent/external/test_responses_error_paths.py |
Adds Responses upstream error tests. |
tests/agent/external/test_qwen_via_claude_code.py |
Adds opt-in real Claude Code/Qwen test. |
tests/agent/external/test_openai_chat_tool_calls_streaming.py |
Adds OpenAI chat SSE tool-call tests. |
tests/agent/external/test_openai_chat_multi_turn.py |
Adds OpenAI chat multi-turn trace test. |
tests/agent/external/test_helpers_patch.py |
Adds patch extraction tests. |
tests/agent/external/test_evaluator_pipeline.py |
Adds evaluator pipeline bridge test. |
tests/agent/external/test_codex_runner.py |
Adds opt-in real Codex/Qwen test. |
tests/agent/external/test_agent_loop_external.py |
Adds AgentLoopAdapter external dispatch tests. |
requirements/framework.txt |
Adds aiohttp dependency. |
README.md |
Documents bridge feature and agent guide links. |
README_zh.md |
Documents bridge feature and Chinese guide links. |
evalscope/web/src/utils/colorScale.ts |
Centralizes score color helpers. |
evalscope/web/src/pages/ReportViewerPage.tsx |
Updates theme tokens. |
evalscope/web/src/pages/ReportsPage.tsx |
Updates text-dim comments/tokens. |
evalscope/web/src/pages/PerfTaskPage.tsx |
Changes perf task running-state handling. |
evalscope/web/src/pages/EvalTaskPage.tsx |
Changes eval task running-state handling. |
evalscope/web/src/pages/BenchmarksPage.tsx |
Updates token usage. |
evalscope/web/src/i18n/translations.ts |
Adds run start/end labels. |
evalscope/web/src/hooks/useCopy.ts |
Adds clipboard hook. |
evalscope/web/src/contexts/ThemeContext.tsx |
Adds OS theme fallback. |
evalscope/web/src/contexts/ReportsContext.tsx |
Adds report cache limiting/useRef loading. |
evalscope/web/src/components/ui/Tabs.tsx |
Updates filled-state tokens. |
evalscope/web/src/components/ui/Table.tsx |
Removes local score helper and updates table styling. |
evalscope/web/src/components/ui/Select.tsx |
Adds design-system comments. |
evalscope/web/src/components/ui/SearchInput.tsx |
Adds design-system comments. |
evalscope/web/src/components/ui/ScoreChip.tsx |
Adds score chip atom. |
evalscope/web/src/components/ui/ScoreBadge.tsx |
Adds score badge atom. |
evalscope/web/src/components/ui/PathBar.tsx |
Adds path input atom. |
evalscope/web/src/components/ui/ModelGroupHeader.tsx |
Adds collapsible group header atom. |
evalscope/web/src/components/ui/KpiCard.tsx |
Adds KPI card atom. |
evalscope/web/src/components/ui/formStyles.ts |
Adds shared form classes. |
evalscope/web/src/components/ui/FormField.tsx |
Adds form field wrapper. |
evalscope/web/src/components/ui/Eyebrow.tsx |
Adds eyebrow typography atom. |
evalscope/web/src/components/ui/EvalRunCard.tsx |
Adds eval run card atom. |
evalscope/web/src/components/ui/Collapsible.tsx |
Adds collapsible atom. |
evalscope/web/src/components/ui/ChatBubble.tsx |
Adds role-based chat bubble atom. |
evalscope/web/src/components/ui/Card.tsx |
Uses Eyebrow. |
evalscope/web/src/components/ui/Button.tsx |
Updates primary button tokens. |
evalscope/web/src/components/ui/Breadcrumb.tsx |
Adds design-system comment. |
evalscope/web/src/components/ui/Badge.tsx |
Updates success token. |
evalscope/web/src/components/single/ChatView.tsx |
Refactors legacy message rendering and trace props. |
evalscope/web/src/components/single/chat/roleConfig.ts |
Maps chat roles to ChatBubble tokens. |
evalscope/web/src/components/reports/ReportSummaryStats.tsx |
Uses centralized score colors and new typography. |
evalscope/web/src/components/reports/ReportHeader.tsx |
Uses centralized score colors and token comments. |
evalscope/web/src/components/reports/ReportFilters.tsx |
Adds design-system comments. |
evalscope/web/src/components/reports/ReportCard.tsx |
Uses centralized score colors and updated tokens. |
evalscope/web/src/components/reports/OverviewTab.tsx |
Uses centralized score colors. |
evalscope/web/src/components/reports/DetailsTab.tsx |
Uses centralized score colors and ring styling. |
evalscope/web/src/components/reports/DatasetNav.tsx |
Updates typography token. |
evalscope/web/src/components/nav/TopNav.tsx |
Updates navigation tokens. |
evalscope/web/src/components/common/ScoreBadge.tsx |
Removes old score badge. |
evalscope/web/src/components/common/Pagination.tsx |
Updates theme tokens. |
evalscope/web/src/components/common/MarkdownRenderer.tsx |
Extracts lightbox and memoizes markdown components. |
evalscope/web/src/components/common/LoadingSpinner.tsx |
Updates text token. |
evalscope/web/src/components/common/ImageLightbox.tsx |
Adds shared image lightbox. |
evalscope/web/src/components/common/FilterBar.tsx |
Updates surface/accent tokens. |
evalscope/web/src/components/common/ErrorBoundary.tsx |
Converts inline styles to token classes. |
evalscope/web/src/components/common/EmptyState.tsx |
Reworks empty-state component API/design. |
evalscope/web/src/components/common/DataTable.tsx |
Updates table tokens. |
evalscope/web/src/components/charts/PlotlyChart.tsx |
Updates label/error styling. |
evalscope/web/src/api/types.ts |
Adds run start/end trace event types. |
evalscope/web/src/api/reports.ts |
Simplifies report API parameter construction. |
evalscope/web/README.md |
Documents Web design system atoms/tokens. |
evalscope/web/index.html |
Applies OS theme before first paint. |
evalscope/config.py |
Adds native/external agent config union validation. |
evalscope/benchmarks/swe_bench/swe_bench_agentic_adapter.py |
Adds external patch extraction hook. |
evalscope/benchmarks/swe_bench_pro/swe_bench_pro_agentic_adapter.py |
Adds external patch extraction hook. |
evalscope/arguments.py |
Adds CLI --agent-config. |
evalscope/api/benchmark/adapters/default_data_adapter.py |
Routes default adapter to native or external agent paths. |
evalscope/api/benchmark/adapters/agent_loop_adapter.py |
Adds external-agent dispatch for agentic adapters. |
evalscope/api/agent/types.py |
Splits base/native agent config and adds kwargs. |
evalscope/api/agent/trace.py |
Extends trace metadata and run events. |
evalscope/api/agent/loop.py |
Marks native framework and aggregates usage. |
evalscope/api/agent/environment.py |
Adds per-command env override to environment API. |
evalscope/api/agent/__init__.py |
Exports new agent config types. |
evalscope/agent/strategies/react.py |
Makes ReAct explicitly inherit AgentStrategy. |
evalscope/agent/runner.py |
Adds native agent runner orchestration. |
evalscope/agent/external/runners/mock.py |
Adds mock external runner. |
evalscope/agent/external/runners/base.py |
Adds external runner protocol models. |
evalscope/agent/external/runners/_assets/claude_code.Dockerfile |
Adds Claude Code runner image asset. |
evalscope/agent/external/runners/_assets/build_claude_code_image.sh |
Adds image build helper. |
evalscope/agent/external/runners/__init__.py |
Registers/exposes built-in runners. |
evalscope/agent/external/helpers/patch.py |
Adds tracked-file patch extraction. |
evalscope/agent/external/helpers/__init__.py |
Exports external helpers. |
evalscope/agent/external/config.py |
Adds external agent/bridge config. |
evalscope/agent/external/bridge/sse_openai.py |
Adds OpenAI chat SSE synthesis. |
evalscope/agent/external/bridge/sse_anthropic.py |
Adds Anthropic SSE synthesis. |
evalscope/agent/external/bridge/_sse_common.py |
Adds shared SSE chunking constants. |
evalscope/agent/external/bridge/__init__.py |
Exports bridge server/session. |
evalscope/agent/external/__init__.py |
Exports external-agent APIs. |
evalscope/agent/environments/local.py |
Supports command env overrides. |
evalscope/agent/environments/enclave.py |
Adds aliases, env injection, config merging, and duration handling. |
docs/zh/user_guides/sandbox.md |
Updates agent guide link. |
docs/zh/user_guides/agent/native.md |
Adds Chinese native agent guide. |
docs/zh/user_guides/agent/index.md |
Adds Chinese agent overview. |
docs/zh/user_guides/agent/bridge.md |
Adds Chinese bridge guide. |
docs/zh/index.md |
Updates Chinese docs toctree. |
docs/zh/get_started/visualization.md |
Updates Chinese trace docs link. |
docs/zh/get_started/parameters.md |
Updates Chinese agent parameter links. |
docs/en/user_guides/sandbox.md |
Updates agent guide link. |
docs/en/user_guides/agent/native.md |
Adds English native agent guide. |
docs/en/user_guides/agent/index.md |
Adds English agent overview. |
docs/en/user_guides/agent/bridge.md |
Adds English bridge guide. |
docs/en/index.md |
Updates English docs toctree. |
docs/en/get_started/visualization.md |
Updates English trace docs link. |
docs/en/get_started/parameters.md |
Updates English agent parameter links. |
* feat(external-agent): P0 bridge for third-party CLI agents
Add a reverse-proxy bridge that exposes an Anthropic /v1/messages
endpoint and routes traffic to EvalScope's Model.generate_async,
letting external agent CLIs (claude-code, mock) drive any
DefaultDataAdapter-based benchmark while their LLM calls flow back
through the bridge and into a structured Trajectory.
* HTTP bridge with non-streaming + synthesized SSE Anthropic route
* Per-loop ModelProxyServer keyed on id(loop) with auto-shutdown via
AsyncioLoopRunner.register_close_callback (no fresh port per sample)
* Runner protocol + MockAgentRunner / ClaudeCodeRunner; AgentEnvironment
gains an env= kwarg honored by local + enclave backends
* TaskConfig.external_agent typed as Optional[Any] and validated
lazily so config import does not pull the runner stack
* IDEALAB credentials read from EVALSCOPE_IDEALAB_TOKEN (via .env)
* rewise p0
* update p0
* update p0
* update registry
* update p1
* update web view
* simplify web view
* openai sse proxy bridge
* update webui refactor
* refactor(web): warm-cream light theme + per-theme score vars
- Light theme reworked from cool-slate translation to warm-cream Console:
surface ladder (#faf9f5/#f0ebe1/#fff/#f5f0e7), solid hex hairlines
(#e6dfd8/#d6cdbe/#c1b6a3) replacing translucent violet, warm-ink shadow
tints, warm-grey text ladder. Documented in DESIGN.md.
- Score gradient gains per-theme CSS vars (--score-fg-s/-l/-bg-a-mul) so
yellow mid-tones stay legible on cream without forking the brand HSL.
scoreColor/scoreBg switch to modern CSS color function syntax.
- Chart palette forks per-theme RGB to restore data-bearing punch on cream.
- ScoreRing stroke bumped to 6px (mini)/8px (summary) per spec; ScoreChip
switches to outline style to sidestep yellow-fill legibility on cream.
- Bulk inline-style → Tailwind className across reports/single chat/ui;
CSS-var refs preserved so theme switching is unaffected.
- scoreColor import consolidated to @/utils/colorScale (drop Table re-export).
* openai responses api bridge + codex runner
Adds the third bridge route POST /openai/v1/responses + the OpenAI
Responses SSE event sequence (pre-resolve mode, mirroring inspect_ai)
so codex v0.133+ (which dropped chat completions) can drive
external-agent evaluations through evalscope.
* translate_responses.py: full coverage of codex/OpenAI input items —
message / function_call / function_call_output / reasoning /
custom_tool_call(_output) / computer_call(_output) plus opaque
placeholder rendering for built-in tool items (web_search_call,
mcp_call, mcp_list_tools, file_search_call, code_interpreter_call,
image_generation_call, local_shell_call). item_reference + unknown
types log WARN and skip (forward-compat, no raise).
* sse_responses.py: pre-resolve synthesizer for the full Responses
event sequence (created → in_progress → output_item.added/done × N
→ completed), strictly monotonic sequence_number, 32-char
function_call_arguments chunking matching inspect_ai. Upstream error
path emits OpenAI SDK ResponseErrorEvent shape (flat
type/code/message/param/sequence_number).
* server.py: route registration + handlers. Shared _auth_check_openai
and _prepare_sse_response helpers extracted; anthropic 401 shape
preserved for claude-code compatibility.
* trace_recorder.py: record_responses_turn reusing _record_turn via
new messages_key parameter; _ingest_initial_user_message filters
type=='message' to avoid mis-ingesting function_call_output as user
prompts; _extract_responses_tool_results WARNs once when an output
appears mid-input[] instead of at the tail.
* CodexRunner: positional prompt (avoids ms_enclave stdin gap),
-c model_providers.evalscope.* config injection, npm install
fallback for codex CLI. Setup + run + answer extraction via
--output-last-message.
Tests: 23 new mock cases (event_sequence × 3 spy oracle, round_trip
× 5, tool_calls_streaming × 2, multi_turn × 1, instructions_merge × 3,
previous_response_id_ignored × 1, extended_item_types × 6, error_paths
× 2) + opt-in real codex × qwen3-max e2e (test_codex_runner). All 47
mock tests + lint clean. SWE-bench Pro 1 sample × codex × qwen3-max
verified end-to-end on enclave: 37 turns (35 exec_command +
1 apply_patch + final stop), 828K input / 9.5K output tokens, rc=0,
bridge ERROR/WARN = 0.
* update codex bridge
* external-agent: review cleanup, trace fixes, agent docs reshuffle
Runners
- CodexRunner: hardcode sandbox=workspace-write + always-bypass-approvals;
drop output-last-message kwarg (constant). Remaining knobs: model_name,
extra_args, extra_config, home_override, auto_install, install_timeout_s,
node_setup_url, npm_package. Default kwargs={} now works end-to-end.
- ClaudeCodeRunner: rename install_node -> auto_install for API parity
with CodexRunner.
- Both: expose install_timeout_s (default 600s codex / 300s claude-code)
so slow CDNs / apt mirrors don't need a source edit. Class _INSTALL_TIMEOUT_S
kept as the default sentinel for callers that already passed it.
Trace recorder
- _ingest_initial_messages: capture the FULL initial setup on the first
responses-API turn (top-level instructions + every system/developer item
+ every user item, in document order), not just the first user message.
Fixes the SWE-bench Pro symptom where codex put the task in `instructions`
and the recorded transcript only contained <environment_context>.
- _extract_responses_tool_results: scan the whole input[] and dedup against
already-recorded ChatMessageTool entries, replacing the tail-only
shortcut. codex re-sends every (function_call, function_call_output)
pair interleaved through input[], not clumped at the tail, so the old
logic logged 100+ WARNs per multi-turn run and re-emitted prior tool
results as new events.
Tests
- Four new BridgeTraceRecorder cases for the responses path: instructions
-> ChatMessageSystem, multi-user ingest, no re-ingest on later turns,
empty-user-content edge case.
- Delete tests/agent/external/test_real_claude_code.py and test_streaming.py
(subsumed by qwen_via_claude_code + walking_skeleton). Rename
test_responses_instructions_merge.py -> test_responses_input_assembly.py
to match what it actually covers.
Docs
- Split docs/{en,zh}/user_guides/agent.md into a subdir with index.md
(overview) + native.md (AgentLoop / Strategy / Environment) + bridge.md
(external CLI bridge: claude-code, codex). Cross-link from sandbox /
parameters / visualization / READMEs / index.
Scripts
- Delete scripts/run_codex_qwen.py, run_swebenchpro_codex_qwen.py,
run_swebenchpro_qwen_cc.py. All three were dev-time one-shot triggers
with no in-tree references; their code paths are now fully covered by
tests/benchmark/test_agent_loop.py (TestSWEBenchProExternalClaudeCode,
TestSWEBenchProExternalCodex). pytest -v -s runs the same end-to-end.
Verified
- 51 mock tests + lint pass
- SWE-bench Pro x codex x qwen3-max (limit=3, 10min) clean exit
- ms-enclave cross-loop shutdown noise gone after upstream PR
(modelscope/ms-enclave#12)
* update codex bridge
Summary
Adds agent bridge: a process-internal protocol-translation proxy that lets external CLI coding agents (claude-code, codex) drive any evalscope-configured model on any benchmark. Plus a web-view refactor folded into the same branch.
The bridge speaks three wire protocols so a single evalscope
Modelcan serve agents that expect very different APIs:POST /anthropic/v1/messagesPOST /openai/v1/chat/completionsPOST /openai/v1/responsesinput[])Each route translates the incoming request →
ChatMessage[]→model.generate_async→ reverse-translatesModelOutput→ synthesises the wire-shape SSE stream back. The bridge is a protocol translator, not a passthrough proxy — downstream LLMs (e.g. DashScope qwen3-max, which only speaks chat completions) still work transparently for clients that demand Anthropic or Responses on the wire.Why this design
Existing AgentLoop is great for evalscope-native evaluations, but production-grade coding agents (claude-code, codex, cursor-agent, …) are external CLI binaries with their own loop/strategy/tooling. Wrapping them as a
Modeladapter loses fidelity; the bridge instead lets the CLI run unchanged and intercepts at the HTTP layer, capturing the full trajectory in evalscope's existingAgentTraceschema so all downstream tooling (scoring, viewer, reports) works without modification.What's in the PR
evalscope/agent/external/bridge/— aiohttp server (server.py), three pairs oftranslate_*.py+sse_*.pymodules per wire protocol, shared_sse_common.py.trace_recorder.pyemitsAgentTrace/AgentTraceEventshapes identical to native AgentLoop so downstream serialisers see a single trace format.runners/—AgentRunnerABC (base.py) + concrete runners:claude_code.py→claude --printviaANTHROPIC_BASE_URLcodex.py→codex exec -c model_providers.evalscope.*(Responses-only since v0.133)mock.pyfor unit testsadapter.py—run_external_agentglue: starts bridge → spawns CLI → captures trace → producesTaskState.config.py/ExternalAgentConfig— declarative kwargs passed viaTaskConfig.agent_config = {'mode': 'external', 'framework': 'codex'|'claude-code', 'kwargs': {...}}.Bench / model integration
AgentLoopAdapter._on_external_agent_inferencedispatches to the runner registry byframeworkname.LocalAgentEnvironment+EnclaveAgentEnvironmentare reused unchanged (the bridge is environment-agnostic).kwargs={}works end-to-end.Web view refactor
evalscope/web/: warm-cream light theme + per-theme score CSS vars + several view components simplified (c73fdc4, 4fdd7d4, 94f61c0, 373e48b). Independent of the bridge — bundled here because it touches the report viewer that's used to inspect bridge-produced traces.Docs
docs/{en,zh}/user_guides/agent/split intoindex.md(overview) +native.md(AgentLoop) +bridge.md(external CLI bridge). Plus cross-links from sandbox / parameters / visualization / READMEs.Usage
For claude-code use
framework='claude-code'— same shape, dial any model behind the Anthropic Messages wire.Verification
tests/agent/external/): 51 mock cases covering all three wire protocols (request translation, SSE event ordering + sequence numbers, multi-turn, tool-use, instructions/system merging, malformed-JSON tool arguments, 401 auth path).tests/benchmark/test_agent_loop.py, opt-in viaDASHSCOPE_API_KEY):TestGSM8KExternalClaudeCode— claude-code × qwen3-max × GSM8KTestSWEBenchProExternalClaudeCode— claude-code × qwen3-max × SWE-bench ProTestSWEBenchProExternalCodex— codex × qwen3-max × SWE-bench Promake lint).Notes for reviewers
previous_response_id(Responses-side stateful sessions) is deliberately not supported; bridge logs WARN and processes the fullinput[]each turn. Stateless retry is more useful for evals than session continuity.parallel_tool_calls=TrueSSE bug (delta fan-out bytool_calls[i].indexbroken) is documented as a landmine incodex.py; codex v0.133+ avoids it by always speaking Responses. Other OpenAI clients that hit the chat path may need User-Agent detection if they trip it.@register_runner('<framework>'). Third-party runners drop intoevalscope/agent/external/runners/and register the same way new benchmarks do.Test plan
TestSWEBenchProExternalCodexreal-API run (limit=3) greenmake lintcleanpytest tests/cli/test_all.py::TestRun::test_ci_lite -v -s -p no:warningsfor non-regression smoke