fix: opencode config
This commit is contained in:
@@ -9,6 +9,8 @@ AGENTCI_PLAN_MODEL=openai/gpt-5.6-sol
|
|||||||
AGENTCI_PLAN_VARIANT=
|
AGENTCI_PLAN_VARIANT=
|
||||||
AGENTCI_IMPLEMENT_MODEL=openai/gpt-5.6-sol
|
AGENTCI_IMPLEMENT_MODEL=openai/gpt-5.6-sol
|
||||||
AGENTCI_IMPLEMENT_VARIANT=
|
AGENTCI_IMPLEMENT_VARIANT=
|
||||||
|
AGENTCI_EXPLORE_MODEL=openai/gpt-5.6-luna
|
||||||
|
AGENTCI_EXPLORE_VARIANT=low
|
||||||
AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna
|
AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna
|
||||||
AGENTCI_RESEARCH_VARIANT=high
|
AGENTCI_RESEARCH_VARIANT=high
|
||||||
# Optional; Context7 works without a key at lower rate limits.
|
# Optional; Context7 works without a key at lower rate limits.
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ RUN chmod 0755 \
|
|||||||
&& OPENCODE_CONFIG=/etc/opencode/opencode.json \
|
&& OPENCODE_CONFIG=/etc/opencode/opencode.json \
|
||||||
OPENCODE_DISABLE_PROJECT_CONFIG=1 \
|
OPENCODE_DISABLE_PROJECT_CONFIG=1 \
|
||||||
OPENCODE_PURE=1 \
|
OPENCODE_PURE=1 \
|
||||||
|
AGENTCI_EXPLORE_MODEL=openai/gpt-5.6-luna \
|
||||||
|
AGENTCI_EXPLORE_VARIANT=low \
|
||||||
AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna \
|
AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna \
|
||||||
AGENTCI_RESEARCH_VARIANT=high \
|
AGENTCI_RESEARCH_VARIANT=high \
|
||||||
CONTEXT7_API_KEY= \
|
CONTEXT7_API_KEY= \
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ has a connected provider. The worker leaves jobs queued while the runtime is una
|
|||||||
Models use OpenCode's `provider/model` format. Planning, implementation, and research can use
|
Models use OpenCode's `provider/model` format. Planning, implementation, and research can use
|
||||||
different providers. Optional `AGENTCI_PLAN_VARIANT` and `AGENTCI_IMPLEMENT_VARIANT` values are
|
different providers. Optional `AGENTCI_PLAN_VARIANT` and `AGENTCI_IMPLEMENT_VARIANT` values are
|
||||||
passed directly to OpenCode for providers that support variants. `AGENTCI_RESEARCH_VARIANT`
|
passed directly to OpenCode for providers that support variants. `AGENTCI_RESEARCH_VARIANT`
|
||||||
configures the research subagent and defaults to `high`.
|
configures the research subagent and defaults to `high`. `AGENTCI_EXPLORE_MODEL` and
|
||||||
|
`AGENTCI_EXPLORE_VARIANT` configure OpenCode's explore agent and default to
|
||||||
|
`openai/gpt-5.6-luna` with `low`.
|
||||||
|
|
||||||
`AGENTCI_OPENCODE_VERSION` controls the npm version or range installed into the image and defaults
|
`AGENTCI_OPENCODE_VERSION` controls the npm version or range installed into the image and defaults
|
||||||
to `^1`. The build verifies that the resolved version is still OpenCode 1.x and prints it. Compose
|
to `^1`. The build verifies that the resolved version is still OpenCode 1.x and prints it. Compose
|
||||||
@@ -75,10 +77,10 @@ are root-owned and read-only, and external skill discovery is disabled, so an ag
|
|||||||
instructions for later repositories. CodeGraph, Context7, and `gh_grep` are configured as MCP
|
instructions for later repositories. CodeGraph, Context7, and `gh_grep` are configured as MCP
|
||||||
servers. Set `AGENTCI_CONTEXT7_API_KEY` to raise Context7 rate limits.
|
servers. Set `AGENTCI_CONTEXT7_API_KEY` to raise Context7 rate limits.
|
||||||
|
|
||||||
The `research` subagent has the same unrestricted permissions as every other agent. Its prompt
|
The `research` subagent can only use Exa web search, Context7, and the `gh_grep` public-code search
|
||||||
asks it to focus on external evidence, but this is guidance rather than an isolation boundary.
|
MCP. All filesystem, shell, editing, task, and other tools are denied for that agent. Exa is enabled
|
||||||
Agent CI initializes or refreshes CodeGraph before every turn and locally excludes `.codegraph/`
|
with `OPENCODE_ENABLE_EXA=1`. Agent CI initializes or refreshes CodeGraph before every parent turn
|
||||||
from Git.
|
and locally excludes `.codegraph/` from Git.
|
||||||
|
|
||||||
### Security boundary
|
### Security boundary
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ services:
|
|||||||
AGENTCI_PLAN_VARIANT: ${AGENTCI_PLAN_VARIANT:-}
|
AGENTCI_PLAN_VARIANT: ${AGENTCI_PLAN_VARIANT:-}
|
||||||
AGENTCI_IMPLEMENT_MODEL: ${AGENTCI_IMPLEMENT_MODEL:-openai/gpt-5.6-sol}
|
AGENTCI_IMPLEMENT_MODEL: ${AGENTCI_IMPLEMENT_MODEL:-openai/gpt-5.6-sol}
|
||||||
AGENTCI_IMPLEMENT_VARIANT: ${AGENTCI_IMPLEMENT_VARIANT:-}
|
AGENTCI_IMPLEMENT_VARIANT: ${AGENTCI_IMPLEMENT_VARIANT:-}
|
||||||
|
AGENTCI_EXPLORE_MODEL: ${AGENTCI_EXPLORE_MODEL:-openai/gpt-5.6-luna}
|
||||||
|
AGENTCI_EXPLORE_VARIANT: ${AGENTCI_EXPLORE_VARIANT:-low}
|
||||||
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna}
|
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna}
|
||||||
AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}
|
AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}
|
||||||
AGENTCI_PLAN_REVIEW_ROUNDS: ${AGENTCI_PLAN_REVIEW_ROUNDS:-4}
|
AGENTCI_PLAN_REVIEW_ROUNDS: ${AGENTCI_PLAN_REVIEW_ROUNDS:-4}
|
||||||
@@ -63,11 +65,14 @@ services:
|
|||||||
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "1"
|
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "1"
|
||||||
OPENCODE_DISABLE_CLAUDE_CODE: "1"
|
OPENCODE_DISABLE_CLAUDE_CODE: "1"
|
||||||
OPENCODE_DISABLE_AUTOUPDATE: "1"
|
OPENCODE_DISABLE_AUTOUPDATE: "1"
|
||||||
|
OPENCODE_ENABLE_EXA: "1"
|
||||||
OPENCODE_PURE: "1"
|
OPENCODE_PURE: "1"
|
||||||
OPENCODE_SERVER_USERNAME: ${OPENCODE_SERVER_USERNAME:-opencode}
|
OPENCODE_SERVER_USERNAME: ${OPENCODE_SERVER_USERNAME:-opencode}
|
||||||
OPENCODE_SERVER_PASSWORD_FILE: /run/secrets/opencode_server_password
|
OPENCODE_SERVER_PASSWORD_FILE: /run/secrets/opencode_server_password
|
||||||
AGENTCI_GITEA_URL: ${AGENTCI_GITEA_URL:-http://gitea:3000}
|
AGENTCI_GITEA_URL: ${AGENTCI_GITEA_URL:-http://gitea:3000}
|
||||||
AGENTCI_TEA_CONFIG_HOME: /run/agentci
|
AGENTCI_TEA_CONFIG_HOME: /run/agentci
|
||||||
|
AGENTCI_EXPLORE_MODEL: ${AGENTCI_EXPLORE_MODEL:-openai/gpt-5.6-luna}
|
||||||
|
AGENTCI_EXPLORE_VARIANT: ${AGENTCI_EXPLORE_VARIANT:-low}
|
||||||
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna}
|
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna}
|
||||||
AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}
|
AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}
|
||||||
CONTEXT7_API_KEY: ${AGENTCI_CONTEXT7_API_KEY:-}
|
CONTEXT7_API_KEY: ${AGENTCI_CONTEXT7_API_KEY:-}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"permission": "allow"
|
"permission": "allow"
|
||||||
},
|
},
|
||||||
"explore": {
|
"explore": {
|
||||||
|
"model": "{env:AGENTCI_EXPLORE_MODEL}",
|
||||||
|
"variant": "{env:AGENTCI_EXPLORE_VARIANT}",
|
||||||
"permission": "allow"
|
"permission": "allow"
|
||||||
},
|
},
|
||||||
"research": {
|
"research": {
|
||||||
@@ -23,7 +25,12 @@
|
|||||||
"mode": "subagent",
|
"mode": "subagent",
|
||||||
"model": "{env:AGENTCI_RESEARCH_MODEL}",
|
"model": "{env:AGENTCI_RESEARCH_MODEL}",
|
||||||
"variant": "{env:AGENTCI_RESEARCH_VARIANT}",
|
"variant": "{env:AGENTCI_RESEARCH_VARIANT}",
|
||||||
"permission": "allow",
|
"permission": {
|
||||||
|
"*": "deny",
|
||||||
|
"websearch": "allow",
|
||||||
|
"context7_*": "allow",
|
||||||
|
"gh_grep_*": "allow"
|
||||||
|
},
|
||||||
"prompt": "Research external, current, or unfamiliar technical facts for the parent agent. Use Context7 for library documentation, gh_grep for public-code examples, and web search for primary sources or broader verification. Prefer authoritative sources, report links, distinguish facts from inference, and return a concise evidence-focused summary. Do not include secrets or proprietary source in external queries."
|
"prompt": "Research external, current, or unfamiliar technical facts for the parent agent. Use Context7 for library documentation, gh_grep for public-code examples, and web search for primary sources or broader verification. Prefer authoritative sources, report links, distinguish facts from inference, and return a concise evidence-focused summary. Do not include secrets or proprietary source in external queries."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class Settings(BaseSettings):
|
|||||||
plan_variant: str | None = None
|
plan_variant: str | None = None
|
||||||
implement_model: str = "openai/gpt-5.6-sol"
|
implement_model: str = "openai/gpt-5.6-sol"
|
||||||
implement_variant: str | None = None
|
implement_variant: str | None = None
|
||||||
|
explore_model: str = "openai/gpt-5.6-luna"
|
||||||
|
explore_variant: str = "low"
|
||||||
research_model: str = "openai/gpt-5.6-luna"
|
research_model: str = "openai/gpt-5.6-luna"
|
||||||
research_variant: str = "high"
|
research_variant: str = "high"
|
||||||
plan_review_rounds: int = Field(default=4, ge=1, le=20)
|
plan_review_rounds: int = Field(default=4, ge=1, le=20)
|
||||||
@@ -53,7 +55,7 @@ class Settings(BaseSettings):
|
|||||||
def strip_url(cls, value: str) -> str:
|
def strip_url(cls, value: str) -> str:
|
||||||
return value.rstrip("/")
|
return value.rstrip("/")
|
||||||
|
|
||||||
@field_validator("plan_model", "implement_model", "research_model")
|
@field_validator("plan_model", "implement_model", "explore_model", "research_model")
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_opencode_model(cls, value: str) -> str:
|
def validate_opencode_model(cls, value: str) -> str:
|
||||||
provider, separator, model = value.partition("/")
|
provider, separator, model = value.partition("/")
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ async def build_container(settings: Settings) -> Container:
|
|||||||
required_models=(
|
required_models=(
|
||||||
(settings.plan_model, settings.plan_variant),
|
(settings.plan_model, settings.plan_variant),
|
||||||
(settings.implement_model, settings.implement_variant),
|
(settings.implement_model, settings.implement_variant),
|
||||||
|
(settings.explore_model, settings.explore_variant),
|
||||||
(settings.research_model, settings.research_variant),
|
(settings.research_model, settings.research_variant),
|
||||||
),
|
),
|
||||||
timeout_seconds=settings.turn_timeout_seconds,
|
timeout_seconds=settings.turn_timeout_seconds,
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ def test_defaults_research_variant_to_high() -> None:
|
|||||||
assert settings.research_variant == "high"
|
assert settings.research_variant == "high"
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_explore_agent_to_luna_low() -> None:
|
||||||
|
settings = Settings(_env_file=None) # type: ignore[call-arg]
|
||||||
|
assert settings.explore_model == "openai/gpt-5.6-luna"
|
||||||
|
assert settings.explore_variant == "low"
|
||||||
|
|
||||||
|
|
||||||
def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) -> None:
|
def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) -> None:
|
||||||
monkeypatch.setenv("AGENTCI_INSTALL_SCRIPTS", "python,dotnet")
|
monkeypatch.setenv("AGENTCI_INSTALL_SCRIPTS", "python,dotnet")
|
||||||
|
|
||||||
@@ -40,7 +46,9 @@ def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) ->
|
|||||||
assert settings.install_scripts == ["python", "dotnet"]
|
assert settings.install_scripts == ["python", "dotnet"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("field", ["plan_model", "implement_model", "research_model"])
|
@pytest.mark.parametrize(
|
||||||
|
"field", ["plan_model", "implement_model", "explore_model", "research_model"]
|
||||||
|
)
|
||||||
def test_requires_provider_qualified_opencode_models(field: str) -> None:
|
def test_requires_provider_qualified_opencode_models(field: str) -> None:
|
||||||
with pytest.raises(ValidationError, match="provider/model"):
|
with pytest.raises(ValidationError, match="provider/model"):
|
||||||
Settings(_env_file=None, **{field: "model-only"}) # type: ignore[call-arg]
|
Settings(_env_file=None, **{field: "model-only"}) # type: ignore[call-arg]
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def test_config_grants_all_agents_unrestricted_permissions() -> None:
|
def test_config_grants_parent_agents_access_and_restricts_research() -> None:
|
||||||
root = Path(__file__).parents[1]
|
root = Path(__file__).parents[1]
|
||||||
config = json.loads((root / "opencode" / "opencode.json").read_text())
|
config = json.loads((root / "opencode" / "opencode.json").read_text())
|
||||||
|
|
||||||
assert config["permission"] == "allow"
|
assert config["permission"] == "allow"
|
||||||
assert all(
|
assert all(
|
||||||
config["agent"][name]["permission"] == "allow"
|
config["agent"][name]["permission"] == "allow"
|
||||||
for name in ("build", "plan", "general", "explore", "research")
|
for name in ("build", "plan", "general", "explore")
|
||||||
)
|
)
|
||||||
|
assert config["agent"]["research"]["permission"] == {
|
||||||
|
"*": "deny",
|
||||||
|
"websearch": "allow",
|
||||||
|
"context7_*": "allow",
|
||||||
|
"gh_grep_*": "allow",
|
||||||
|
}
|
||||||
assert config["mcp"]["codegraph"]["command"] == ["codegraph", "serve", "--mcp"]
|
assert config["mcp"]["codegraph"]["command"] == ["codegraph", "serve", "--mcp"]
|
||||||
assert config["mcp"]["context7"]["url"] == "https://mcp.context7.com/mcp"
|
assert config["mcp"]["context7"]["url"] == "https://mcp.context7.com/mcp"
|
||||||
|
assert config["agent"]["explore"]["model"] == "{env:AGENTCI_EXPLORE_MODEL}"
|
||||||
|
assert config["agent"]["explore"]["variant"] == "{env:AGENTCI_EXPLORE_VARIANT}"
|
||||||
assert config["agent"]["research"]["variant"] == "{env:AGENTCI_RESEARCH_VARIANT}"
|
assert config["agent"]["research"]["variant"] == "{env:AGENTCI_RESEARCH_VARIANT}"
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +36,8 @@ def test_compose_removes_codex_sandbox_exceptions() -> None:
|
|||||||
assert "HOME: /etc/opencode/home" in compose
|
assert "HOME: /etc/opencode/home" in compose
|
||||||
assert "OPENCODE_DISABLE_EXTERNAL_SKILLS" in compose
|
assert "OPENCODE_DISABLE_EXTERNAL_SKILLS" in compose
|
||||||
assert "OPENCODE_DISABLE_DEFAULT_PLUGINS" not in compose
|
assert "OPENCODE_DISABLE_DEFAULT_PLUGINS" not in compose
|
||||||
|
assert 'OPENCODE_ENABLE_EXA: "1"' in compose
|
||||||
|
assert "AGENTCI_EXPLORE_VARIANT: ${AGENTCI_EXPLORE_VARIANT:-low}" in compose
|
||||||
assert "AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}" in compose
|
assert "AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}" in compose
|
||||||
assert compose.count("/run/agentci:mode=1777") == 2
|
assert compose.count("/run/agentci:mode=1777") == 2
|
||||||
assert "AGENTCI_OPENCODE_VERSION: ${AGENTCI_OPENCODE_VERSION:-^1}" in compose
|
assert "AGENTCI_OPENCODE_VERSION: ${AGENTCI_OPENCODE_VERSION:-^1}" in compose
|
||||||
|
|||||||
Reference in New Issue
Block a user