Skip to content

Conversation

@Samuel-Mencke
Copy link

@Samuel-Mencke Samuel-Mencke commented Feb 6, 2026

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

  1. OpenCode Provider
    Swarm automatically uses the active model configured in OpenCode.

  2. Provider Selection During Setup
    The setup flow allows choosing between OpenCode and Manual.

  3. Project Local Configuration
    The Swarm configuration is stored inside the project and can be versioned safely without global side effects.

  4. Compatible With Existing Projects
    If no provider configuration exists Swarm behaves exactly as before.

Breaking Changes

  1. When the OpenCode provider is selected
    All hard coded model fields are removed from agent definitions.

  2. Model selection is handled exclusively by OpenCode.

  3. 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

  1. Provider Adapter
    A new provider adapter is introduced that defines a clear interface for loading swarm configuration and resolving agent definitions.

  2. Setup Flow
    Provider selection happens before any model related logic.

  3. Configuration Schema
    The configuration only contains the selected provider and no model information.

  4. 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

    • OpenCode is now available as a first-class model provider for Swarm
    • New provider selection during setup: choose between OpenCode or Manual mode
    • Dynamic model switching via OpenCode configuration at .opencode/swarm-config.json
    • Project-local configuration support for provider settings
  • Documentation

    • Updated setup instructions for OpenCode provider integration
  • Breaking Changes

    • Model field handling has changed when using OpenCode provider
    • Exclusive model selection now routes through OpenCode provider

Samuel Mencke added 7 commits February 6, 2026 17:16
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
@vercel
Copy link

vercel bot commented Feb 6, 2026

Someone is attempting to deploy a commit to the joelhooks' projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Documentation
CHANGELOG.md, README.md
Added version 0.63.0 entry documenting OpenCode provider feature, setup steps, configuration structure, and migration guidance. Expanded README with new "OpenCode as Model Provider" section covering rationale, setup, and model switching behavior.
Setup Flow
bin/swarm.ts
Integrated provider selection prompt in CLI setup, OpenCode authentication validation, swarm-config persistence, and provider-aware agent definition generation. Added helper for auth checking and cleanup of legacy agent files.
Configuration & Packaging
package.json, scripts/build.ts
Bumped version to 0.63.0, added export for "./provider/adapter" module, added test:provider script, and extended build config to include provider adapter output and swarm-queue as external dependency.
Provider Adapter
src/provider/adapter.ts, src/provider/adapter.test.ts
Introduced new provider adapter module with getAgentDefinition() (strips model field for OpenCode provider) and loadSwarmConfig() (loads .opencode/swarm-config.json). Comprehensive test coverage for config loading, YAML model removal, edge cases, and validation logic.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A tale of providers dancing through the code,
OpenCode and Manual, down the swarm-config road,
Model fields stripped clean with a whisker's delight,
Agents spring to life, oh what a sight!
Setup flows anew, from auth to adapters so bright,
The swarm takes its shape, perfectly right!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately reflects the main objective of the pull request: integrating OpenCode as a first-class model provider for Swarm.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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 to ProviderConfig on 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-string provider values that happen to pass !== checks (e.g., the boolean false).

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;
     }

Comment on lines +70 to +101
/**
* 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;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: 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 -30

Repository: 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__/*" | cat

Repository: 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 ts

Repository: 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 2

Repository: 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.ts

Repository: 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 ts

Repository: 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 -20

Repository: 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 -30

Repository: 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.ts

Repository: 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 -20

Repository: 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 -20

Repository: 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.ts

Repository: 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 ts

Repository: 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.ts

Repository: 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.ts

Repository: 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.

Comment on lines +2566 to +2588
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +2614 to +2619
// 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)}`);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
// 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).

Comment on lines +92 to +158
## 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"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +1 to +13
/**
* 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";
Copy link

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.

Comment on lines +141 to +163
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:");
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +58 to +63
function removeModelField(agentDefinition: string): string {
return agentDefinition
.split('\n')
.filter(line => !line.trim().startsWith('model:'))
.join('\n');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

@Samuel-Mencke Samuel-Mencke changed the title feat: OpenCode as First-Class Model Provider OpenCode as First-Class Model Provider Feb 7, 2026
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.

OpenCode as a First Class Model Provider for Swarm #150

1 participant