Skip to content

Commit 856d2a2

Browse files
committed
docs: guides for headless runs and the fake server, reference entries
1 parent 90e9cde commit 856d2a2

6 files changed

Lines changed: 151 additions & 3 deletions

File tree

‎docs/guides/headless.md‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Running agents headless
2+
3+
The common embedded use of OpenCode is: start the server, wait until the agents you configured have loaded, run
4+
one prompt per unit of work, and read the result. Two helpers cover that on both clients.
5+
6+
## Wait until the server is ready
7+
8+
Agents load and MCP servers connect in the background after `opencode serve` starts listening, so the first
9+
requests can arrive before your agent exists. `wait_until_ready` polls until everything you name is there:
10+
11+
```python
12+
client.wait_until_ready(agents=["reviewer"], mcp_servers=["github"], timeout=60)
13+
```
14+
15+
It treats connection errors and `503` answers as "still booting". After `timeout` seconds it raises
16+
`TimeoutError` naming what is still missing.
17+
18+
## Run a prompt to completion
19+
20+
```python
21+
from opencode.types import ModelRef
22+
23+
result = client.session.run(
24+
"Review the staged changes",
25+
agent="reviewer",
26+
model=ModelRef(provider_id="anthropic", id="claude-sonnet-5-5"),
27+
timeout=600,
28+
)
29+
print(result.outcome) # "succeeded", "failed" or "interrupted"
30+
print(result.text) # text of the last assistant message
31+
print(result.cost, result.tokens.input, result.tokens.output)
32+
```
33+
34+
`run` creates a session with the agent and model, sends the prompt, blocks on `session.wait` until the session is
35+
idle, then returns a [`opencode.lib.RunResult`](../reference/testing.md#opencode.lib.RunResult):
36+
37+
- `messages`: the messages this run added, oldest first, starting with the prompt
38+
- `text`, `outcome`, `error`: the final answer and how the run ended
39+
- `cost`, `tokens`: usage of this run; `session.cost` and `session.tokens` are the session totals
40+
41+
Set `model` together with `agent` when creating a session: otherwise the session can run on a different model
42+
than the agent's.
43+
44+
Pass `session_id=` to continue a session. `agent` and `model` then switch the session before the prompt, and
45+
`messages` holds only the new messages.
46+
47+
### Interrupts
48+
49+
If waiting ends early, through `KeyboardInterrupt`, cancelling the task (async), the `timeout`, or a connection
50+
error, `run` calls `session.interrupt` before re-raising, so the agent does not keep running on the server.
51+
52+
### Files
53+
54+
`files` takes `PromptInputFileAttachment`s. Images and other inline content can be `data:` URLs:
55+
56+
```python
57+
import base64
58+
from opencode.types import PromptInputFileAttachment
59+
60+
png = base64.b64encode(open("screenshot.png", "rb").read()).decode()
61+
client.session.run(
62+
"What is wrong in this screenshot?",
63+
files=[PromptInputFileAttachment(uri=f"data:image/png;base64,{png}", name="screenshot.png")],
64+
)
65+
```
66+
67+
## Async
68+
69+
The same helpers exist on `AsyncOpencode`:
70+
71+
```python
72+
async with AsyncOpencode(url, password=password) as client:
73+
await client.wait_until_ready(agents=["build"])
74+
result = await client.session.run("Summarize README.md", agent="build")
75+
```

‎docs/guides/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ entry; the common ones also have a guide. CI fails if a group on the client is m
3030
| `client.reference` | References | - | [`ReferenceResource`](../reference/resources.md#opencode.resources.reference.ReferenceResource) |
3131
| `client.rpc` | Plugin RPC | [Plugin RPC](plugin-rpc.md) | [`RpcResource`](../reference/resources.md#opencode.resources.rpc.RpcResource) |
3232
| `client.server` | Server info and health | - | [`ServerResource`](../reference/resources.md#opencode.resources.server.ServerResource) |
33-
| `client.session` | Sessions, prompts and their sub-resources | [Guide](sessions.md) | [`SessionResource`](../reference/resources.md#opencode.resources.session.SessionResource) |
33+
| `client.session` | Sessions, prompts and their sub-resources; `run` for one-call prompts | [Guide](sessions.md), [headless](headless.md) | [`SessionResource`](../reference/resources.md#opencode.resources.session.SessionResource) |
3434
| `client.shell` | Shells | - | [`ShellResource`](../reference/resources.md#opencode.resources.shell.ShellResource) |
3535
| `client.skill` | Skills | - | [`SkillResource`](../reference/resources.md#opencode.resources.skill.SkillResource) |
3636
| `client.vcs` | Version control status and branches | - | [`VcsResource`](../reference/resources.md#opencode.resources.vcs.VcsResource) |

‎docs/guides/testing.md‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Testing with the fake server
2+
3+
`opencode.testing.FakeServer` lets you test code that uses the SDK without starting `opencode serve` or calling
4+
a model. It is an `httpx` transport (an `httpx.MockTransport`) that keeps sessions, messages and events in
5+
memory and answers with payloads built from the generated models.
6+
7+
```python
8+
from opencode import Opencode
9+
from opencode.testing import FakeServer
10+
11+
12+
def test_review() -> None:
13+
fake = FakeServer(reply=lambda prompt: "LGTM" if "diff" in prompt else "?")
14+
client = Opencode.fake(fake) # same as Opencode(transport=fake)
15+
16+
result = client.session.run("Review this diff", agent="build")
17+
18+
assert result.text == "LGTM"
19+
assert fake.prompts == ["Review this diff"]
20+
```
21+
22+
`AsyncOpencode.fake(fake)` works the same way.
23+
24+
## What it implements
25+
26+
Server info, agents, MCP servers, the event stream, and sessions: create, get, list, remove, switch agent or
27+
model, prompt (with `resume=False` queueing the prompt without running it), wait, interrupt and messages, with
28+
cursor pagination. Each prompt gets an assistant reply, an idle marker, cost and token usage. Other routes
29+
answer `404` with a message naming the route.
30+
31+
## Simulating a slow boot
32+
33+
```python
34+
fake = FakeServer(agents=["build"])
35+
fake.add_agent("reviewer", after_polls=3) # listed from the 4th GET /api/agent
36+
fake.set_mcp_status("github", "connected", after_polls=2)
37+
Opencode.fake(fake).wait_until_ready(agents=["reviewer"], mcp_servers=["github"])
38+
```
39+
40+
## Injecting failures
41+
42+
```python
43+
fake.fail(
44+
"POST",
45+
r"/api/session/.+/prompt",
46+
status=409,
47+
body={"_tag": "SessionBusyError", "sessionID": "ses_1", "message": "busy"},
48+
)
49+
fake.fail("GET", "/api/info", exception=httpx.ConnectError("refused"), times=2)
50+
```
51+
52+
`path` is a regular expression matched against the whole path. `times=None` fails every matching request.
53+
54+
## Inspecting state
55+
56+
`fake.requests` records every `httpx.Request`; `fake.sessions`, `fake.messages`, `fake.inbox` and `fake.events`
57+
hold the server state and can be changed directly.

‎docs/reference/clients.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
::: opencode.Opencode
44
options:
5-
members: [with_options, close]
5+
members: [with_options, close, wait_until_ready, fake]
66
show_bases: false
77

88
::: opencode.AsyncOpencode
99
options:
10-
members: [with_options, close]
10+
members: [with_options, close, wait_until_ready, fake]
1111
show_bases: false
1212

1313
## Module constants

‎docs/reference/testing.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Helpers and testing
2+
3+
## `session.run` result
4+
5+
::: opencode.lib.RunResult
6+
7+
## Fake server
8+
9+
::: opencode.testing.FakeServer
10+
options:
11+
members: [add_agent, set_mcp_status, fail, prompts]
12+
13+
::: opencode.testing.DEFAULT_MODEL

‎mkdocs.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ nav:
135135
- Guides:
136136
- guides/index.md
137137
- Sessions and prompts: guides/sessions.md
138+
- Running agents headless: guides/headless.md
139+
- Testing with the fake server: guides/testing.md
138140
- Async: guides/async.md
139141
- Models and unions: guides/models.md
140142
- Pagination: guides/pagination.md
@@ -155,6 +157,7 @@ nav:
155157
- Shared events and RPC: reference/events.md
156158
- Terminals: reference/terminals.md
157159
- Exceptions: reference/exceptions.md
160+
- Helpers and testing: reference/testing.md
158161
- Types: reference/types/index.md
159162
- Project:
160163
- How it's built: project/how-its-built.md

0 commit comments

Comments
 (0)