Fix OpenCode backend invocation and diagnostics
This commit is contained in:
+1
-1
@@ -211,7 +211,7 @@ models:
|
||||
# opencode:
|
||||
# enabled: false
|
||||
# # path: /usr/local/bin/opencode
|
||||
# # args: ["-p", "{prompt}"]
|
||||
# # args: ["run", "--format", "default", "{prompt}"]
|
||||
# # timeout_ms: 120000
|
||||
# codex:
|
||||
# enabled: false
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# OpenCode CLI subagent runbook
|
||||
|
||||
This runbook defines how Flynn uses the **OpenCode** CLI (`opencode`) as a *subagent*.
|
||||
|
||||
Principles (same as other CLI subagents):
|
||||
- Treat output as **untrusted input**: digest + sanity-check; verify with local tools/tests when possible.
|
||||
- Prefer **plain text** output for easy ingestion; switch to structured output only when it provides clear benefit.
|
||||
- Never claim something is true just because the model said so—verify if we can.
|
||||
|
||||
---
|
||||
|
||||
## 0) Quick facts
|
||||
|
||||
- Binary: `opencode`
|
||||
- Default model (verified present in `opencode models`): **`opencode/glm-5-free`**
|
||||
- Preferred mode: **non-interactive** (`opencode run ...`)
|
||||
|
||||
---
|
||||
|
||||
## 1) Default invocation
|
||||
|
||||
### 1.1 Plain text (default)
|
||||
Use this for most tasks (summaries, parsing, transformation suggestions, drafting):
|
||||
|
||||
```bash
|
||||
opencode run -m opencode/glm-5-free --format default "${PROMPT}"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Keep prompts short and explicit.
|
||||
- If the prompt contains quotes/newlines, wrap with a heredoc substitution:
|
||||
|
||||
```bash
|
||||
opencode run -m opencode/glm-5-free --format default "$(cat <<'PROMPT'
|
||||
...prompt text...
|
||||
PROMPT
|
||||
)"
|
||||
```
|
||||
|
||||
### 1.2 JSON output (selective)
|
||||
Only use JSON mode when we need robust post-processing or auditing.
|
||||
|
||||
```bash
|
||||
opencode run -m opencode/glm-5-free --format json "${PROMPT}"
|
||||
```
|
||||
|
||||
Caveat:
|
||||
- `--format json` typically yields structured output that may be easier to parse, but it can be more verbose and may require filtering.
|
||||
|
||||
---
|
||||
|
||||
## 2) Model selection
|
||||
|
||||
Default to `opencode/glm-5-free` unless the task clearly benefits from switching.
|
||||
|
||||
When to switch models:
|
||||
- **Higher quality / long docs / tricky reasoning**: use a stronger model available in `opencode models` (example: `anthropic/claude-3-7-sonnet-latest`, `openai/gpt-5.3-codex`, `google/gemini-2.5-pro`).
|
||||
- **Need a specific provider** (e.g. Copilot org policy): use `github-copilot/...` models.
|
||||
|
||||
Rule:
|
||||
- If model choice matters, be explicit in the call.
|
||||
|
||||
---
|
||||
|
||||
## 3) Attach files
|
||||
|
||||
OpenCode supports attaching files. Use this for document parsing tasks when the CLI supports the file type.
|
||||
|
||||
```bash
|
||||
opencode run -m opencode/glm-5-free --format default -f /path/to/file.pdf "Extract a table of key dates and parties. Return JSON."
|
||||
```
|
||||
|
||||
Guidance:
|
||||
- Prefer attaching the file over copy/pasting huge content.
|
||||
- For PDFs/images, we may still prefer local tooling (Poppler, OCR) first, then provide extracted text.
|
||||
|
||||
---
|
||||
|
||||
## 4) Task recipes
|
||||
|
||||
### 4.1 Document search & retrieval help
|
||||
Use OpenCode to:
|
||||
- expand queries
|
||||
- generate keywords/entities
|
||||
- propose relevance criteria
|
||||
|
||||
Prompt pattern:
|
||||
- Provide: goal, corpus description, constraints, examples of good/bad hits.
|
||||
|
||||
### 4.2 Document parsing
|
||||
Use OpenCode to:
|
||||
- outline structure
|
||||
- extract entities / timelines
|
||||
- convert semi-structured text to JSON
|
||||
|
||||
Prompt pattern:
|
||||
- Specify the target schema exactly.
|
||||
- Ask it to quote source spans/anchors when possible.
|
||||
|
||||
### 4.3 PDF manipulation (planning)
|
||||
Use OpenCode to decide *what* to do; use local tools to do it.
|
||||
- Example: “split pages 3–7”, “OCR then extract”, “remove password”, “linearize”, etc.
|
||||
|
||||
---
|
||||
|
||||
## 5) How Flynn digests OpenCode output
|
||||
|
||||
When I run OpenCode:
|
||||
1. I’ll extract the actionable result (answer/plan/patch schema).
|
||||
2. I’ll sanity-check for contradictions and missing constraints.
|
||||
3. I’ll verify locally when possible (grep/tests/PDF tool output).
|
||||
4. I’ll present you the final integrated conclusion.
|
||||
|
||||
Raw OpenCode output is included only when:
|
||||
- you ask for it
|
||||
- it’s needed for auditing/debugging
|
||||
- it contains a critical snippet (e.g., exact JSON) that must be preserved
|
||||
|
||||
---
|
||||
|
||||
## 6) Troubleshooting
|
||||
|
||||
- If startup fails with:
|
||||
- `default agent "<name>" is a subagent`
|
||||
- then OpenCode config points `default_agent` to a non-primary agent.
|
||||
- Fix by setting `default_agent` to a primary agent in `~/.config/opencode/opencode.json`, or remove that key.
|
||||
- For Flynn external backend wiring, prefer:
|
||||
```bash
|
||||
opencode run --format default "{prompt}"
|
||||
```
|
||||
instead of `opencode -p "{prompt}"`.
|
||||
- If `opencode` behaves strangely in a repo directory, try running from a neutral cwd (e.g. `$HOME`) or ensure paths passed to `-f` are absolute.
|
||||
- If a provider/model isn’t found, run:
|
||||
```bash
|
||||
opencode models
|
||||
```
|
||||
and pick an available entry.
|
||||
@@ -59,6 +59,25 @@ describe('ExternalCliBackend', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('uses inferred run args for opencode backend', async () => {
|
||||
mockExecFile.mockImplementation((_cmd, _args, _opts, callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(null, 'ok', '');
|
||||
}
|
||||
return {} as never;
|
||||
});
|
||||
|
||||
const backend = new OpenCodeBackend('/usr/bin/opencode', []);
|
||||
await backend.process({ prompt: 'hello', history: [] });
|
||||
|
||||
expect(mockExecFile).toHaveBeenCalledWith(
|
||||
'/usr/bin/opencode',
|
||||
['run', '--format', 'default', 'USER: hello'],
|
||||
expect.any(Object),
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports {prompt} substitution in configured args', async () => {
|
||||
mockExecFile.mockImplementation((_cmd, _args, _opts, callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
@@ -98,6 +117,19 @@ describe('ExternalCliBackend', () => {
|
||||
.rejects.toThrow('returned no output');
|
||||
});
|
||||
|
||||
it('includes stdout details when backend process fails', async () => {
|
||||
mockExecFile.mockImplementation((_cmd, _args, _opts, callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(new Error('Command failed: opencode'), 'default agent "code-planner" is a subagent', '');
|
||||
}
|
||||
return {} as never;
|
||||
});
|
||||
|
||||
const backend = new OpenCodeBackend('/usr/bin/opencode', []);
|
||||
await expect(backend.process({ prompt: 'hello', history: [] }))
|
||||
.rejects.toThrow('default agent "code-planner" is a subagent');
|
||||
});
|
||||
|
||||
it('constructs default commands for opencode and gemini backends', () => {
|
||||
const opencode = new OpenCodeBackend();
|
||||
const gemini = new GeminiBackend();
|
||||
|
||||
@@ -37,6 +37,9 @@ function inferArgs(name: ExternalBackendName, prompt: string): string[] {
|
||||
if (name === 'claude_code') {
|
||||
return ['--print', prompt];
|
||||
}
|
||||
if (name === 'opencode') {
|
||||
return ['run', '--format', 'default', prompt];
|
||||
}
|
||||
return ['-p', prompt];
|
||||
}
|
||||
|
||||
@@ -99,7 +102,11 @@ function execFileAsync(command: string, args: string[], timeoutMs: number): Prom
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(command, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(new Error(`${error.message}${stderr ? `\n${stderr}` : ''}`));
|
||||
const details = [stderr, stdout]
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry.length > 0)
|
||||
.join('\n');
|
||||
reject(new Error(details ? `${error.message}\n${details}` : error.message));
|
||||
return;
|
||||
}
|
||||
resolve(stdout || '');
|
||||
|
||||
Reference in New Issue
Block a user