Commit Graph
4 Commits
Author SHA1 Message Date
krypticmouseandClaude Opus 4.7 00b85b71dd fix(agents): unwrap wrapped engines to derive opencode provider URL (+ clear guard)
The real `jarvis ask --agent opencode` path passes an InstrumentedEngine
(telemetry wrapper) whose underlying engine — and its `_host` — lives at
`_inner`. The base-URL derivation only checked the top object, so it returned
"", no provider was registered, and opencode 500'd on `openjarvis/<model>`.
Direct construction with a raw engine masked this; only the CLI path exposed
it.

- _derive_openai_base_url now unwraps up to 6 wrapper layers
  (`_inner`/`_engine`/`_wrapped`) to find `_host`/`base_url`.
- run() resolves the provider/model spec up front and, when it genuinely
  can't (no base URL + bare model name), returns a clear actionable error
  instead of letting opencode 500.

Tests: wrapper-unwrap derivation + unresolvable-provider guard. 20 passed,
ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 01:10:25 +00:00
krypticmouseandClaude Opus 4.7 6a011b4abb fix(agents): deterministic opencode permissions + no workspace pollution
Two hardening fixes for headless use, found while verifying real runs:

- Permissions: opencode's interactive default *asks* before some actions (and
  `plan` asks before bash), which would block forever with no TTY. The agent
  now writes an explicit permission policy: `build` allows edit+bash, `plan`
  denies them (read-only), overridable via a `permission` kwarg. Verified: a
  bash task completes without hanging and plan mode refuses to create files.
- Config no longer written into the user's workspace. We now write provider +
  permission config to a private temp file referenced via `OPENCODE_CONFIG`
  (confirmed honored by opencode), keeping the workspace clean while opencode
  still operates there as cwd.

Tests updated to cover `_build_config` (provider presence, per-mode
permission, custom override, no-pollution). 18 passed, ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 01:07:00 +00:00
krypticmouseandClaude Opus 4.7 20f90f3ca0 fix(agents): recover opencode tool-results from the full turn, not just the final message
A real-model E2E (Ollama qwen3:8b) exposed what unit tests with synthetic
parts missed: `POST /session/{id}/message` returns only the final assistant
message (text/reasoning), while ToolParts live in intermediate assistant
messages. The parser was reading the wrong message, so tool_results was always
empty even when opencode actually edited files.

- run(): after the prompt POST, GET /session/{id}/message and collect parts
  across the whole turn for tool extraction (content still comes from the
  final message). Verified against a live opencode session.
- _extract_tool_results: success now keys on opencode's state.status ==
  "completed" (states: completed | error | running | pending).
- Test now feeds the real full-turn message shape and asserts the write tool
  is recovered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 00:56:36 +00:00
krypticmouseandClaude Opus 4.7 4fa9163ea9 feat(agents): add OpenCodeAgent — run the opencode coding agent on a local engine
Adds an `OpenCodeAgent` (registry key `opencode`) that delegates coding tasks
to opencode (https://opencode.ai, MIT) while keeping inference local-first:
OpenJarvis's engine backs opencode via an OpenAI-compatible provider.

How it works:
- Derives an OpenAI-compatible base URL from the engine (e.g. Ollama/vLLM at
  `<host>/v1`) and writes an `opencode.json` registering it as an
  `@ai-sdk/openai-compatible` provider (`openjarvis/<model>`).
- Spawns a headless `opencode serve` (loopback, random port), waits for
  `/global/health`, then drives a session: `POST /session` →
  `POST /session/{id}/message` with `model={providerID,modelID}` + agent
  (`build`/`plan`) → parses message `parts` (text → content, tool → tool_results)
  into an `AgentResult`. `close()` disposes the server.
- opencode is an external binary (not bundled); `run()` returns a clear,
  actionable error when it's missing, mirroring ClaudeCodeAgent's degradation.

Verified end-to-end against the real opencode binary wired to a stub
OpenAI-compatible engine: opencode called the local endpoint and the agent
parsed the response (content/finish/model) correctly. Unit tests cover part
parsing, base-URL derivation, provider-config writing (incl. merge), binary
detection, graceful degradation, and run() parsing with a mocked client — 15
passed, ruff clean. Registered via the standard try/except import in
agents/__init__.py; documented in docs/user-guide/agents.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 00:41:22 +00:00