Skip to content

feat: session.run, wait_until_ready, opencode.testing fake server, faster import - #12

Merged
shrijayan merged 3 commits into
mainfrom
feat/headless-helpers
Oct 22, 2026
Merged

shrijayan merged 3 commits into
mainfrom
feat/headless-helpers

Conversation

@shrijayan

Copy link
Copy Markdown
Owner

Adds high-level helpers for the headless use case in anomalyco/opencode#54300 (run an agent, wait, read the result), a fake server for users' tests, and a faster import opencode.

What's new

  • client.session.run(text, *, agent, model, files, session_id, timeout) (sync + async). It creates a session or reuses session_id (switching agent/model if given), prompts it, blocks on session.wait, and returns opencode.lib.RunResult with session, the new messages, text, outcome, error, and per-run cost/tokens. If the wait ends early (KeyboardInterrupt, async cancellation, timeout, or any error), it calls session.interrupt before re-raising. The async version shields that call from cancellation.
  • Codegen hook: HELPER_BASES in codegen/render.py lets a generated resource class inherit from a hand-written base in src/opencode/lib/. SessionResource(SessionHelpers) uses it.
  • client.wait_until_ready(*, agents=(), mcp_servers=(), timeout=60) (sync + async) polls server.info, agent.list and mcp.list. Connection errors and 503s count as still booting. On timeout it raises TimeoutError naming what is missing.
  • transport= on both client constructors (additive; can't be combined with http_client), plus Opencode.fake() / AsyncOpencode.fake().
  • opencode.testing.FakeServer is an httpx.MockTransport with in-memory sessions, prompts (including resume=False queueing), wait, interrupt, paged messages, agents, MCP status, server info and the SSE event stream. Payloads are built from the generated models. Test hooks: fail() for error/exception injection, add_agent/set_mcp_status(after_polls=...), requests, prompts.
  • Docs: README sections, docs/guides/headless.md, docs/guides/testing.md, docs/reference/testing.md.

Import time (separate commit perf: load opencode.types on first use)

Generated resources now name response types as strings, which type_adapter resolves on first use, and import models only under TYPE_CHECKING. opencode.types is loaded lazily. Wall clock for import opencode, median of 9 runs on macOS with Python 3.14:

import opencode
before ~400 ms
after ~265 ms

The model build (~130 ms) now happens on the first request. What remains at import is mostly httpx, pydantic and typing_extensions. A subprocess test guards that import opencode doesn't load opencode.types._generated.

Tests

  • tests/test_helpers.py (19 tests, all on the fake): run, reuse, paging, interrupt on 503, KeyboardInterrupt and async cancel, wait_until_ready, and fake CRUD/errors/events.
  • Live: wait_until_ready, plus run against a model of a nonexistent provider. That flow fails before any network call, so no model is called. Note: a default-model prompt on a sandboxed server uses OpenCode's hosted free model, which is why the test pins an unroutable model.

Don't merge without review: every merge to main publishes a release.

@shrijayan
shrijayan force-pushed the feat/headless-helpers branch from 9836522 to 9c594a7 Compare October 22, 2026 10:18
- codegen: HELPER_BASES lets a generated resource class inherit from a
  hand-written base in src/opencode/lib; SessionResource uses it for run()
- client.session.run (sync/async): create or reuse a session, prompt, wait
  until idle, return RunResult (messages, text, outcome, error, usage);
  interrupts the session on KeyboardInterrupt, cancellation or errors
- client.wait_until_ready: poll agents and MCP servers after boot
- transport= on both clients, Opencode.fake()
- opencode.testing.FakeServer: in-memory httpx transport for user tests
Generated resources now name response types as strings, resolved by
type_adapter on first use, and import models only under TYPE_CHECKING.
opencode.types is a lazy package attribute. import opencode no longer
builds ~640 pydantic models (median ~400ms -> ~265ms locally; the model
build moves to the first request, ~130ms).
@shrijayan
shrijayan force-pushed the feat/headless-helpers branch from 9c594a7 to 4eb17d6 Compare October 22, 2026 10:21
@shrijayan
shrijayan merged commit 856d2a2 into main Oct 22, 2026
16 checks passed
@shrijayan
shrijayan deleted the feat/headless-helpers branch October 22, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant