-
Notifications
You must be signed in to change notification settings - Fork 41
OpenCode as First-Class Model Provider #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
OpenCode as First-Class Model Provider #171
Conversation
Implementiert saubere Provider-Separation für Agent-Definitions: - getAgentDefinition() entfernt model-Feld bei provider=opencode - loadSwarmConfig() lädt .opencode/swarm-config.json - Kein Modell-Knowledge in Swarm, OpenCode injiziert zur Laufzeit Tests: Unit Tests für Adapter (16/0 passed)
Erweitert swarm setup um Provider-Auswahl: - Provider Selection vor Modell-Selection - checkOpenCodeAuth() prüft ob OpenCode konfiguriert ist - Speichert .opencode/swarm-config.json mit provider-Feld - Klar erklärende Fehlermeldung wenn OpenCode fehlt Tests: Integration Tests für Setup-Flow
Ergänzt package.json exports und build scripts: - dist/provider/adapter.js wird exportiert - build script kompiliert Adapter - test:provider Script für isolierte Tests - swarm-queue zu EXTERNALS hinzugefügt Tests: Build Verification
README: - OpenCode Provider Abschnitt mit Einrichtungs-Anleitung - Modellwechsel via OpenCode erklärt - Config-Struktur erklärt - Fehlertypen erklärt CHANGELOG: - Release-Notes für minor version - Breaking Changes dokumentiert - Migrations-Anleitung - Architektur-Änderungen erklärt
Korrigiert Provider Selection: - Entfernt else-Zweig, der Provider Selection übersprungen hat - Provider Selection wird jetzt IMMER ausgeführt - Fixes Bug: "Update agent models" zeigte Modelle ohne Provider-Auswahl
|
Someone is attempting to deploy a commit to the joelhooks' projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis pull request introduces OpenCode as a first-class model provider for the swarm plugin. It adds a provider adapter module, updates the setup flow to prompt users for provider selection (OpenCode or Manual), persists configuration to .opencode/swarm-config.json, and modifies agent definition generation to conditionally strip model fields when using the OpenCode provider. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/CLI
participant Setup as Setup Flow (swarm.ts)
participant Auth as OpenCode Auth
participant Config as Config File<br/>(.opencode/swarm-config.json)
participant Adapter as Provider Adapter
participant Output as Agent Definitions
User->>Setup: Initiate swarm setup
Setup->>Setup: Prompt provider selection
User->>Setup: Select "opencode" or "manual"
alt Provider is OpenCode
Setup->>Auth: Check OpenCode authentication
Auth-->>Setup: Auth valid/invalid
Setup->>Config: Save provider choice
else Provider is Manual
Setup->>Config: Save provider choice
end
Setup->>Config: Load swarm-config.json
Config-->>Setup: ProviderConfig
Setup->>Adapter: Generate agent definitions<br/>(planner, worker, researcher)
Adapter->>Adapter: Check provider type
alt Provider is OpenCode
Adapter->>Adapter: Strip model field from agent
else Provider is Manual
Adapter->>Adapter: Keep agent unchanged
end
Adapter-->>Setup: Agent definition (with/without model)
Setup->>Output: Write agent files
Output-->>User: Setup complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@packages/opencode-swarm-plugin/bin/swarm.ts`:
- Around line 2566-2588: The provider prompt (the p.select call) blocks in
non-interactive runs; modify the setup flow to skip prompting when
nonInteractive is true by defaulting provider = "opencode" (or another
configured default) instead of calling p.select, and only call p.select when
nonInteractive is false/interactive; keep the existing p.isCancel handling for
the interactive branch so that p.log.step, p.select, p.isCancel logic remains
unchanged when prompting.
- Around line 2614-2619: The code currently writes swarm-config.json to a global
configDir (swarmConfigPath built with join(configDir,...)) but
loadSwarmConfig(projectPath) expects .opencode/swarm-config.json in the project;
change the write path to the project's .opencode directory (e.g., build a path
using projectPath and join(projectPath, ".opencode", "swarm-config.json") or
similar), ensure the .opencode directory exists before writing, and keep the
same payload (swarmConfigContent) and success log (p.log.success) so that
provider selection is stored per-project and will be picked up by
loadSwarmConfig(projectPath).
- Around line 70-101: Add unit tests for the checkOpenCodeAuth function and
provider selection persistence: write tests that (1) mock child_process.spawn to
simulate a successful opencode --version (proc emits exit 0) and assert
checkOpenCodeAuth() returns true; (2) mock spawn to simulate an error/exit
non-zero and assert it returns false; (3) simulate a hanging process where the
5s timeout kills the proc (ensure proc.kill is called and checkOpenCodeAuth()
resolves false); and (4) test provider selection/persistence by exercising the
code path that writes swarm-config.json (mock fs or use a temp dir) to confirm
the selected provider is saved and reloaded correctly. Target the
checkOpenCodeAuth function and the config write/read logic that updates
swarm-config.json, using spawn and fs mocks to avoid real CLI/filesystem side
effects.
In `@packages/opencode-swarm-plugin/README.md`:
- Around line 92-158: Move the entire "## OpenCode as Model Provider" section
(the level-2 heading and its following content) so it appears after the
Quickstart finish line "Done. You're swarming." to restore the contiguous
numbered quickstart flow; locate the block beginning with "## OpenCode as Model
Provider" and cut/paste it to immediately follow the "Done. You're swarming."
paragraph so that "### 3. Run Your First Swarm" and its code block remain
together.
In `@packages/opencode-swarm-plugin/src/provider/adapter.test.ts`:
- Around line 1-13: The test touches the filesystem so rename the test file from
adapter.test.ts to adapter.integration.test.ts and update any references/imports
if present; specifically ensure the file exporting tests that call
loadSwarmConfig and getAgentDefinition is renamed so the test runner treats it
as an integration test (tests invoking loadSwarmConfig, tmpdir usage,
mkdirSync/writeFileSync/readFileSync, etc. remain unchanged), and verify your
test script (test:provider) still picks up the new filename.
- Around line 141-163: Add a new unit test that ensures lines containing
"model:" in the content body are preserved by
getAgentDefinition/getAgentDefinition's helper removeModelField: create an agent
string that has no YAML frontmatter but includes a body line like "This line
mentions model: something" and assert the returned result still contains that
"model:" substring; this will catch the scoping bug in removeModelField in
adapter.ts where it may incorrectly strip "model:" outside frontmatter.
In `@packages/opencode-swarm-plugin/src/provider/adapter.ts`:
- Around line 58-63: The removeModelField function currently removes any line
starting with "model:" across the whole agentDefinition; update it to only
remove such lines inside the YAML frontmatter block between the first leading
'---' and the next '---'. Specifically, in removeModelField(agentDefinition:
string) detect if frontmatter exists (find the first '---' and the closing
'---'); if not, return agentDefinition unchanged; otherwise split into three
parts (pre-frontmatter, frontmatter, post-frontmatter), filter lines in the
frontmatter only by removing lines whose trimmed start is "model:", then
reassemble and return the concatenated string preserving the original
pre/front/post sections and delimiters. Ensure function handles edge cases like
missing closing delimiter and empty frontmatter.
🧹 Nitpick comments (3)
packages/opencode-swarm-plugin/CHANGELOG.md (1)
5-7: Fix heading style to satisfy markdownlint (MD003).Line 6 triggers “setext vs atx” heading style. Use consistent ATX headings or remove the setext-style separator usage around the changeset header to comply with MD003.
packages/opencode-swarm-plugin/src/provider/adapter.ts (2)
14-21: JSDoc and inline comments are in German.The interface doc (
Konfiguration,Nur provider Name), function docs (Aktualisiert,Entfernt,Lädt), and inline comments (Behält,Keine Config) are in German while the rest of the codebase and the file's own header (lines 1-9) are in English. Consider making the language consistent.
83-91: Type assertion bypasses runtime validation.
JSON.parse(...)is cast directly toProviderConfigon line 84. If the file contains{"provider": 123}or{"provider": "opencode", "extra": ...}, the cast silently succeeds. The manual string check on line 89 partially covers this, but won't catch non-stringprovidervalues that happen to pass!==checks (e.g., the booleanfalse).Given zod 4.1.8 is already a dependency, a one-liner schema would make this airtight, but the file intentionally avoids external deps. At minimum, add a
typeof config.provider === "string"guard.Proposed minimal guard
// Validiere provider-Feld + if (typeof config.provider !== "string") { + return null; + } if (config.provider !== "opencode" && config.provider !== "manual") { return null; }
| /** | ||
| * Prüft ob OpenCode installiert und erreichbar ist | ||
| * | ||
| * Gekapselter CLI Check - kein SDK Call in Agenten, nur minimaler | ||
| * Check im setup. Testbar über Mock von spawn. | ||
| * | ||
| * @returns true wenn opencode CLI erreichbar ist | ||
| */ | ||
| async function checkOpenCodeAuth(): Promise<boolean> { | ||
| try { | ||
| const { spawn } = await import("child_process"); | ||
|
|
||
| // Minimaler CLI Check - prüft ob opencode --version funktioniert | ||
| const proc = spawn("opencode", ["--version"], { | ||
| stdio: "pipe", | ||
| shell: true | ||
| }); | ||
|
|
||
| const timeout = setTimeout(() => proc.kill(), 5000); // 5s Timeout | ||
|
|
||
| const exitCode = await new Promise<number>((resolve) => { | ||
| proc.on("exit", resolve); | ||
| proc.on("error", () => resolve(1)); | ||
| }); | ||
|
|
||
| clearTimeout(timeout); | ||
|
|
||
| return exitCode === 0; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for test files in the opencode-swarm-plugin package
find packages/opencode-swarm-plugin -type f \( -name "*.test.ts" -o -name "*.spec.ts" -o -name "__tests__" \) | head -20Repository: joelhooks/swarm-tools
Length of output: 1302
🏁 Script executed:
#!/bin/bash
# Look for tests covering checkOpenCodeAuth, provider config, and swarm-config persistence
rg -n "checkOpenCodeAuth|swarm-config|provider.*select|provider.*persist" packages/opencode-swarm-plugin --type ts | grep -i "test\|spec" | head -30Repository: joelhooks/swarm-tools
Length of output: 952
🏁 Script executed:
#!/bin/bash
# Check for test directory structure and test file names
find packages/opencode-swarm-plugin -type f -name "*.test.ts" -o -name "*.spec.ts" -o -path "*/__tests__/*" | catRepository: joelhooks/swarm-tools
Length of output: 6792
🏁 Script executed:
#!/bin/bash
# Look for any Jest/test configuration and list all test files
ls -la packages/opencode-swarm-plugin/
find packages/opencode-swarm-plugin -type f \( -name "jest.config.*" -o -name "vitest.config.*" \) -o -name "*.test.ts"Repository: joelhooks/swarm-tools
Length of output: 8595
🏁 Script executed:
#!/bin/bash
# Search for references to checkOpenCodeAuth throughout the codebase
rg -n "checkOpenCodeAuth" packages/opencode-swarm-plugin --type tsRepository: joelhooks/swarm-tools
Length of output: 262
🏁 Script executed:
#!/bin/bash
# Search specifically for checkOpenCodeAuth in swarm.test.ts
rg -n "checkOpenCodeAuth" packages/opencode-swarm-plugin/bin/swarm.test.ts -A 5 -B 2Repository: joelhooks/swarm-tools
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Check the size and structure of swarm.test.ts
wc -l packages/opencode-swarm-plugin/bin/swarm.test.ts
head -50 packages/opencode-swarm-plugin/bin/swarm.test.tsRepository: joelhooks/swarm-tools
Length of output: 1396
🏁 Script executed:
#!/bin/bash
# Search the entire codebase for any tests that mention checkOpenCodeAuth or test the auth flow
rg -rn "checkOpenCodeAuth" packages/opencode-swarm-plugin/bin --type tsRepository: joelhooks/swarm-tools
Length of output: 222
🏁 Script executed:
#!/bin/bash
# Look for OpenCode setup/auth related tests in swarm.test.ts
rg -n "OpenCode|auth|setup" packages/opencode-swarm-plugin/bin/swarm.test.ts | head -20Repository: joelhooks/swarm-tools
Length of output: 162
🏁 Script executed:
#!/bin/bash
# Check what functions are exported or tested in swarm.test.ts
rg -n "describe|test|it\(" packages/opencode-swarm-plugin/bin/swarm.test.ts | head -30Repository: joelhooks/swarm-tools
Length of output: 1681
🏁 Script executed:
#!/bin/bash
# Let's look at the swarm.ts file around line 2594 where checkOpenCodeAuth is called
sed -n '2580,2610p' packages/opencode-swarm-plugin/bin/swarm.tsRepository: joelhooks/swarm-tools
Length of output: 975
🏁 Script executed:
#!/bin/bash
# Look for any setup/provider persistence code
rg -n "provider.*persist|swarm-config|selectProvider" packages/opencode-swarm-plugin/bin/swarm.ts | head -20Repository: joelhooks/swarm-tools
Length of output: 133
🏁 Script executed:
#!/bin/bash
# Check if there are any tests that import or reference the setup flow that uses checkOpenCodeAuth
rg -n "setup|provider" packages/opencode-swarm-plugin/bin/swarm.test.ts | head -20Repository: joelhooks/swarm-tools
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Let's verify the setup flow by looking at the complete context around provider selection
sed -n '2570,2650p' packages/opencode-swarm-plugin/bin/swarm.tsRepository: joelhooks/swarm-tools
Length of output: 2895
🏁 Script executed:
#!/bin/bash
# Check if provider persistence is tested anywhere
rg -rn "swarm-config" packages/opencode-swarm-plugin/bin --type tsRepository: joelhooks/swarm-tools
Length of output: 161
🏁 Script executed:
#!/bin/bash
# Check the structure of swarm.test.ts to see if setup-related tests exist
tail -100 packages/opencode-swarm-plugin/bin/swarm.test.tsRepository: joelhooks/swarm-tools
Length of output: 3139
🏁 Script executed:
#!/bin/bash
# Search for any test files that might test the setup flow or checkOpenCodeAuth
find packages/opencode-swarm-plugin -name "*setup*.test.ts" -o -name "*auth*.test.ts"Repository: joelhooks/swarm-tools
Length of output: 131
🏁 Script executed:
#!/bin/bash
# Check for any mocking of spawn or child_process in tests
rg -rn "spawn|child_process" packages/opencode-swarm-plugin/bin --type ts | grep -i "test\|mock"Repository: joelhooks/swarm-tools
Length of output: 1808
🏁 Script executed:
#!/bin/bash
# Check the swarm-setup-consolidate.test.ts file to see what it tests
cat packages/opencode-swarm-plugin/bin/swarm-setup-consolidate.test.tsRepository: joelhooks/swarm-tools
Length of output: 2952
Add tests for checkOpenCodeAuth() function and provider configuration persistence.
The new OpenCode auth validation and provider selection code lacks test coverage. Per TDD requirements, tests must cover: (1) success case (OpenCode available), (2) failure case (OpenCode unavailable or auth error), (3) timeout scenario (5-second kill), and (4) provider selection/persistence to swarm-config.json.
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/bin/swarm.ts` around lines 70 - 101, Add unit
tests for the checkOpenCodeAuth function and provider selection persistence:
write tests that (1) mock child_process.spawn to simulate a successful opencode
--version (proc emits exit 0) and assert checkOpenCodeAuth() returns true; (2)
mock spawn to simulate an error/exit non-zero and assert it returns false; (3)
simulate a hanging process where the 5s timeout kills the proc (ensure proc.kill
is called and checkOpenCodeAuth() resolves false); and (4) test provider
selection/persistence by exercising the code path that writes swarm-config.json
(mock fs or use a temp dir) to confirm the selected provider is saved and
reloaded correctly. Target the checkOpenCodeAuth function and the config
write/read logic that updates swarm-config.json, using spawn and fs mocks to
avoid real CLI/filesystem side effects.
| p.log.step("Selecting model provider..."); | ||
|
|
||
| const provider = await p.select({ | ||
| message: "Select model provider for swarm agents:", | ||
| options: [ | ||
| { | ||
| value: "opencode", | ||
| label: "OpenCode (recommended)", | ||
| hint: "Swarm uses whatever model you select in OpenCode", | ||
| }, | ||
| { | ||
| value: "manual", | ||
| label: "Manual (legacy)", | ||
| hint: "Specify fixed models for each agent type", | ||
| }, | ||
| ], | ||
| initialValue: "opencode", | ||
| }); | ||
|
|
||
| if (p.isCancel(provider)) { | ||
| p.cancel("Setup cancelled"); | ||
| process.exit(0); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-interactive setup still blocks on the provider prompt.
setup(..., nonInteractive=true) still prompts, which will hang in --yes/CI flows. Default a provider when non-interactive and only prompt in interactive mode.
💡 Suggested fix
- const provider = await p.select({
+ const provider = nonInteractive
+ ? "manual"
+ : await p.select({
message: "Select model provider for swarm agents:",
options: [
{
value: "opencode",
@@
],
initialValue: "opencode",
- });
+ });
- if (p.isCancel(provider)) {
+ if (!nonInteractive && p.isCancel(provider)) {
p.cancel("Setup cancelled");
process.exit(0);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| p.log.step("Selecting model provider..."); | |
| const provider = await p.select({ | |
| message: "Select model provider for swarm agents:", | |
| options: [ | |
| { | |
| value: "opencode", | |
| label: "OpenCode (recommended)", | |
| hint: "Swarm uses whatever model you select in OpenCode", | |
| }, | |
| { | |
| value: "manual", | |
| label: "Manual (legacy)", | |
| hint: "Specify fixed models for each agent type", | |
| }, | |
| ], | |
| initialValue: "opencode", | |
| }); | |
| if (p.isCancel(provider)) { | |
| p.cancel("Setup cancelled"); | |
| process.exit(0); | |
| } | |
| p.log.step("Selecting model provider..."); | |
| const provider = nonInteractive | |
| ? "manual" | |
| : await p.select({ | |
| message: "Select model provider for swarm agents:", | |
| options: [ | |
| { | |
| value: "opencode", | |
| label: "OpenCode (recommended)", | |
| hint: "Swarm uses whatever model you select in OpenCode", | |
| }, | |
| { | |
| value: "manual", | |
| label: "Manual (legacy)", | |
| hint: "Specify fixed models for each agent type", | |
| }, | |
| ], | |
| initialValue: "opencode", | |
| }); | |
| if (!nonInteractive && p.isCancel(provider)) { | |
| p.cancel("Setup cancelled"); | |
| process.exit(0); | |
| } |
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/bin/swarm.ts` around lines 2566 - 2588, The
provider prompt (the p.select call) blocks in non-interactive runs; modify the
setup flow to skip prompting when nonInteractive is true by defaulting provider
= "opencode" (or another configured default) instead of calling p.select, and
only call p.select when nonInteractive is false/interactive; keep the existing
p.isCancel handling for the interactive branch so that p.log.step, p.select,
p.isCancel logic remains unchanged when prompting.
| // Speichere Provider-Auswahl in Config | ||
| const swarmConfigPath = join(configDir, "swarm-config.json"); | ||
| const swarmConfigContent = JSON.stringify({ provider }, null, 2); | ||
| writeFileSync(swarmConfigPath, swarmConfigContent, "utf-8"); | ||
| p.log.success(`Provider saved: ${green(provider)}`); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provider config is written to a global path, but agent generation reads project-local config.
loadSwarmConfig(projectPath) expects .opencode/swarm-config.json, yet setup writes to ~/.config/opencode/swarm-config.json. That mismatch means the provider choice won’t apply during agent generation and isn’t project-local. Write the config under the project .opencode/ directory (ensure it exists).
🛠️ Suggested fix
- const swarmConfigPath = join(configDir, "swarm-config.json");
+ const projectConfigDir = join(cwd, ".opencode");
+ mkdirSync(projectConfigDir, { recursive: true });
+ const swarmConfigPath = join(projectConfigDir, "swarm-config.json");
const swarmConfigContent = JSON.stringify({ provider }, null, 2);
writeFileSync(swarmConfigPath, swarmConfigContent, "utf-8");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Speichere Provider-Auswahl in Config | |
| const swarmConfigPath = join(configDir, "swarm-config.json"); | |
| const swarmConfigContent = JSON.stringify({ provider }, null, 2); | |
| writeFileSync(swarmConfigPath, swarmConfigContent, "utf-8"); | |
| p.log.success(`Provider saved: ${green(provider)}`); | |
| // Speichere Provider-Auswahl in Config | |
| const projectConfigDir = join(cwd, ".opencode"); | |
| mkdirSync(projectConfigDir, { recursive: true }); | |
| const swarmConfigPath = join(projectConfigDir, "swarm-config.json"); | |
| const swarmConfigContent = JSON.stringify({ provider }, null, 2); | |
| writeFileSync(swarmConfigPath, swarmConfigContent, "utf-8"); | |
| p.log.success(`Provider saved: ${green(provider)}`); |
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/bin/swarm.ts` around lines 2614 - 2619, The
code currently writes swarm-config.json to a global configDir (swarmConfigPath
built with join(configDir,...)) but loadSwarmConfig(projectPath) expects
.opencode/swarm-config.json in the project; change the write path to the
project's .opencode directory (e.g., build a path using projectPath and
join(projectPath, ".opencode", "swarm-config.json") or similar), ensure the
.opencode directory exists before writing, and keep the same payload
(swarmConfigContent) and success log (p.log.success) so that provider selection
is stored per-project and will be picked up by loadSwarmConfig(projectPath).
| ## OpenCode as Model Provider | ||
|
|
||
| Swarm can use OpenCode as a model provider - meaning Swarm automatically uses the currently selected model in OpenCode. | ||
|
|
||
| ### Why OpenCode Provider? | ||
|
|
||
| - **No model configuration:** You don't need to configure model names in Swarm | ||
| - **Easy model switching:** Change the model in OpenCode, it takes effect immediately for Swarm | ||
| - **Provider-agnostic:** Swarm doesn't store or know model names | ||
| - **Future-proof:** New models in OpenCode are automatically available | ||
|
|
||
| ### Setup | ||
|
|
||
| 1. Install and configure OpenCode: | ||
| ```bash | ||
| npm install -g opencode-ai | ||
| opencode auth | ||
| ``` | ||
|
|
||
| 2. Configure Swarm with OpenCode Provider: | ||
| ```bash | ||
| cd your-project | ||
| swarm setup | ||
| ``` | ||
|
|
||
| Select "OpenCode (recommended)" as Model Provider. | ||
|
|
||
| Swarm saves `.opencode/swarm-config.json` in your project. | ||
|
|
||
| ### Model Switching | ||
|
|
||
| When using OpenCode as provider, you don't change models in Swarm - you change them in OpenCode: | ||
|
|
||
| ```bash | ||
| # Select a new model in OpenCode | ||
| opencode auth | ||
|
|
||
| # Use Swarm immediately with the new model | ||
| /swarm "Implement new feature" | ||
| ``` | ||
|
|
||
| Swarm doesn't know which model you selected - OpenCode injects it automatically when executing agents. | ||
|
|
||
| ### Manual Behavior (Legacy) | ||
|
|
||
| If you select "Manual" as provider, Swarm behaves as before: | ||
|
|
||
| - Specify models during swarm setup | ||
| - These models are hardcoded | ||
| - Changes in OpenCode have no effect | ||
|
|
||
| ### Config Structure | ||
|
|
||
| The `.opencode/swarm-config.json` file contains only the provider name: | ||
|
|
||
| ```json | ||
| { | ||
| "provider": "opencode" | ||
| } | ||
| ``` | ||
|
|
||
| **Important:** | ||
|
|
||
| - No model names | ||
| - No defaults | ||
| - Only "opencode" or "manual" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New provider section breaks the Quickstart flow.
The ## OpenCode as Model Provider section (a level-2 heading) is inserted between the ### 3. Run Your First Swarm heading (line ~90) and its code block content (line 160). This disrupts the numbered quickstart steps. Consider moving this section after the quickstart block (after "Done. You're swarming." at line 171) so the 3-step getting-started flow remains contiguous.
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/README.md` around lines 92 - 158, Move the
entire "## OpenCode as Model Provider" section (the level-2 heading and its
following content) so it appears after the Quickstart finish line "Done. You're
swarming." to restore the contiguous numbered quickstart flow; locate the block
beginning with "## OpenCode as Model Provider" and cut/paste it to immediately
follow the "Done. You're swarming." paragraph so that "### 3. Run Your First
Swarm" and its code block remain together.
| /** | ||
| * Provider Adapter Tests | ||
| * | ||
| * Tests für Provider-Adapter mit Fokus auf: | ||
| * - getAgentDefinition() mit verschiedenen Providers | ||
| * - loadSwarmConfig() mit Fehlerfällen | ||
| * - Edge Cases (malformed JSON, kein Frontmatter, etc.) | ||
| */ | ||
| import { describe, test, expect, beforeEach, afterEach } from "bun:test"; | ||
| import { mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "fs"; | ||
| import { join } from "path"; | ||
| import { tmpdir } from "os"; | ||
| import { getAgentDefinition, loadSwarmConfig, type ProviderConfig } from "./adapter"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Test file uses filesystem — should be named .integration.test.ts.
The loadSwarmConfig tests create directories and files in tmpdir. As per coding guidelines, tests that touch the filesystem should use the .integration.test.ts tier naming (".test.ts for unit tests (<100ms, no deps), .integration.test.ts for integration tests (<5s, uses libSQL/filesystem)").
Rename to adapter.integration.test.ts. The test:provider script (bun test src/provider/) will still pick it up.
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/src/provider/adapter.test.ts` around lines 1 -
13, The test touches the filesystem so rename the test file from adapter.test.ts
to adapter.integration.test.ts and update any references/imports if present;
specifically ensure the file exporting tests that call loadSwarmConfig and
getAgentDefinition is renamed so the test runner treats it as an integration
test (tests invoking loadSwarmConfig, tmpdir usage,
mkdirSync/writeFileSync/readFileSync, etc. remain unchanged), and verify your
test script (test:provider) still picks up the new filename.
| test("handles agent with no frontmatter", () => { | ||
| const agent = "Just content, no frontmatter"; | ||
|
|
||
| const result = getAgentDefinition(agent, mockOpencodeConfig); | ||
|
|
||
| expect(result).toBe(agent); | ||
| }); | ||
|
|
||
| test("handles multiple model lines (should remove all)", () => { | ||
| const agent = `--- | ||
| name: test-agent | ||
| model: anthropic/claude-sonnet-4-5 | ||
| description: Test | ||
| model: anthropic/claude-haiku-4-5 | ||
| --- | ||
|
|
||
| Content | ||
| `; | ||
|
|
||
| const result = getAgentDefinition(agent, mockOpencodeConfig); | ||
|
|
||
| expect(result).not.toContain("model:"); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test: model: appearing in content body should be preserved.
The "no frontmatter" test (line 141) doesn't actually contain model: in the string. Add a test that verifies content-body lines containing model: are not removed — this would catch the removeModelField scoping bug flagged in adapter.ts.
Suggested test case
+ test("preserves model: in content body (outside frontmatter)", () => {
+ const agent = `---
+name: test-agent
+model: anthropic/claude-sonnet-4-5
+---
+
+The model: architecture uses transformers.
+`;
+
+ const result = getAgentDefinition(agent, mockOpencodeConfig);
+
+ expect(result).not.toContain("model: anthropic");
+ expect(result).toContain("The model: architecture uses transformers.");
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test("handles agent with no frontmatter", () => { | |
| const agent = "Just content, no frontmatter"; | |
| const result = getAgentDefinition(agent, mockOpencodeConfig); | |
| expect(result).toBe(agent); | |
| }); | |
| test("handles multiple model lines (should remove all)", () => { | |
| const agent = `--- | |
| name: test-agent | |
| model: anthropic/claude-sonnet-4-5 | |
| description: Test | |
| model: anthropic/claude-haiku-4-5 | |
| --- | |
| Content | |
| `; | |
| const result = getAgentDefinition(agent, mockOpencodeConfig); | |
| expect(result).not.toContain("model:"); | |
| }); | |
| test("handles agent with no frontmatter", () => { | |
| const agent = "Just content, no frontmatter"; | |
| const result = getAgentDefinition(agent, mockOpencodeConfig); | |
| expect(result).toBe(agent); | |
| }); | |
| test("handles multiple model lines (should remove all)", () => { | |
| const agent = `--- | |
| name: test-agent | |
| model: anthropic/claude-sonnet-4-5 | |
| description: Test | |
| model: anthropic/claude-haiku-4-5 | |
| --- | |
| Content | |
| `; | |
| const result = getAgentDefinition(agent, mockOpencodeConfig); | |
| expect(result).not.toContain("model:"); | |
| }); | |
| test("preserves model: in content body (outside frontmatter)", () => { | |
| const agent = `--- | |
| name: test-agent | |
| model: anthropic/claude-sonnet-4-5 | |
| --- | |
| The model: architecture uses transformers. | |
| `; | |
| const result = getAgentDefinition(agent, mockOpencodeConfig); | |
| expect(result).not.toContain("model: anthropic"); | |
| expect(result).toContain("The model: architecture uses transformers."); | |
| }); |
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/src/provider/adapter.test.ts` around lines 141
- 163, Add a new unit test that ensures lines containing "model:" in the content
body are preserved by getAgentDefinition/getAgentDefinition's helper
removeModelField: create an agent string that has no YAML frontmatter but
includes a body line like "This line mentions model: something" and assert the
returned result still contains that "model:" substring; this will catch the
scoping bug in removeModelField in adapter.ts where it may incorrectly strip
"model:" outside frontmatter.
| function removeModelField(agentDefinition: string): string { | ||
| return agentDefinition | ||
| .split('\n') | ||
| .filter(line => !line.trim().startsWith('model:')) | ||
| .join('\n'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removeModelField strips model: from the entire document, not just YAML frontmatter.
The filter operates on every line of the agent definition. If the content body (below the --- closing delimiter) contains a line like "The model: architecture is...", it will be incorrectly removed. The function should scope removal to within the frontmatter delimiters only.
🐛 Proposed fix: scope to frontmatter only
function removeModelField(agentDefinition: string): string {
- return agentDefinition
- .split('\n')
- .filter(line => !line.trim().startsWith('model:'))
- .join('\n');
+ const lines = agentDefinition.split('\n');
+ let inFrontmatter = false;
+ let frontmatterCount = 0;
+
+ return lines
+ .filter(line => {
+ if (line.trim() === '---') {
+ frontmatterCount++;
+ inFrontmatter = frontmatterCount === 1;
+ return true;
+ }
+ if (inFrontmatter && line.trim().startsWith('model:')) {
+ return false;
+ }
+ return true;
+ })
+ .join('\n');
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function removeModelField(agentDefinition: string): string { | |
| return agentDefinition | |
| .split('\n') | |
| .filter(line => !line.trim().startsWith('model:')) | |
| .join('\n'); | |
| } | |
| function removeModelField(agentDefinition: string): string { | |
| const lines = agentDefinition.split('\n'); | |
| let inFrontmatter = false; | |
| let frontmatterCount = 0; | |
| return lines | |
| .filter(line => { | |
| if (line.trim() === '---') { | |
| frontmatterCount++; | |
| inFrontmatter = frontmatterCount === 1; | |
| return true; | |
| } | |
| if (inFrontmatter && line.trim().startsWith('model:')) { | |
| return false; | |
| } | |
| return true; | |
| }) | |
| .join('\n'); | |
| } |
🤖 Prompt for AI Agents
In `@packages/opencode-swarm-plugin/src/provider/adapter.ts` around lines 58 - 63,
The removeModelField function currently removes any line starting with "model:"
across the whole agentDefinition; update it to only remove such lines inside the
YAML frontmatter block between the first leading '---' and the next '---'.
Specifically, in removeModelField(agentDefinition: string) detect if frontmatter
exists (find the first '---' and the closing '---'); if not, return
agentDefinition unchanged; otherwise split into three parts (pre-frontmatter,
frontmatter, post-frontmatter), filter lines in the frontmatter only by removing
lines whose trimmed start is "model:", then reassemble and return the
concatenated string preserving the original pre/front/post sections and
delimiters. Ensure function handles edge cases like missing closing delimiter
and empty frontmatter.
OpenCode as a First Class Model Provider Closes #150
Swarm can now use OpenCode as a native model provider. No manual model configuration is required anymore.
Overview
Swarm delegates all model related decisions to OpenCode. The currently active model in OpenCode is used automatically at runtime. Swarm itself has no knowledge of model names or versions.
What Is New
OpenCode Provider
Swarm automatically uses the active model configured in OpenCode.
Provider Selection During Setup
The setup flow allows choosing between OpenCode and Manual.
Project Local Configuration
The Swarm configuration is stored inside the project and can be versioned safely without global side effects.
Compatible With Existing Projects
If no provider configuration exists Swarm behaves exactly as before.
Breaking Changes
When the OpenCode provider is selected
All hard coded model fields are removed from agent definitions.
Model selection is handled exclusively by OpenCode.
Swarm no longer stores or resolves any model identifiers.
Migration Guide
Step 1
Install OpenCode globally and authenticate it using the OpenCode CLI.
Step 2
Navigate to your project directory and run the Swarm setup command.
Step 3
Select OpenCode as the provider. This option is recommended.
Step 4
You are done. Swarm now uses the model that is currently active in OpenCode.
Architecture Changes
Provider Adapter
A new provider adapter is introduced that defines a clear interface for loading swarm configuration and resolving agent definitions.
Setup Flow
Provider selection happens before any model related logic.
Configuration Schema
The configuration only contains the selected provider and no model information.
No Model Knowledge in Swarm
All model resolution happens at runtime through OpenCode.
Release
Version 0.62.2
Patch release
Summary by CodeRabbit
New Features
Documentation
Breaking Changes