Files
tireless/crates/tireless-agent/src/opencode.rs
rob thijssen 4e42f87576
Some checks failed
deploy / build (push) Has been cancelled
deploy / deploy (push) Has been cancelled
feat(tireless): scaffold workspace, dashboard and staged design plan
Autonomous issue-to-PR driver for Claude Code and OpenCode, structured per
lair/architecture generic.md.

Workspace: entities/core/data/agent library crates plus api, worker and cli
binaries. Two pieces of real logic land with tests — lane routing (cc for
judgement, oc for specification) and the limit governor.

Constraints encoded as code rather than comments:
- agents are spawned as vendor binaries; tireless never calls a provider API
- ANTHROPIC_API_KEY is never set by tireless, only passed through
- assert_not_anthropic refuses to start an OpenCode lane pointed at Anthropic
- every run passes the governor; provider rate-limit signals win over our own
  accounting

Deployment assets target bob.hanzalova.internal:23296 (registered in
port-allocations.md), fronted by hanzalova at tireless.internal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHhHtohxcdk1PL3tfnYJdH
2026-08-02 12:46:42 +03:00

102 lines
3.8 KiB
Rust

//! The OpenCode lane.
//!
//! OpenCode is run the way vibe-kanban runs it: as a local server
//! (`opencode serve --hostname 127.0.0.1 --port 0`) driven over loopback HTTP
//! with a per-spawn basic-auth password. See
//! `crates/executors/src/executors/opencode.rs:92`.
//!
//! **This lane is never pointed at Anthropic.** OpenCode is a third-party
//! harness with its own provider clients; driving an Anthropic *subscription*
//! through it is the pattern Anthropic blocked in January 2026, and OpenCode was
//! named explicitly. Anthropic work goes through the Claude Code lane, which
//! uses the first-party binary. [`assert_not_anthropic`] enforces this at
//! startup so the rule survives a config edit.
use tireless_entities::Error;
/// Pinned for the same reason as the Claude Code package.
pub const OPENCODE_PACKAGE: &str = "opencode-ai@1.4.7";
pub struct OpencodeExecutor {
/// OpenCode provider id, e.g. `lair-helexa`.
pub provider: String,
/// Model id within that provider, e.g. `Qwen/Qwen3.6-27B`.
pub model: String,
/// OpenAI-compatible base URL. Defaults to helexa cortex on the office site,
/// which routes by model name across the neuron fleet.
pub base_url: String,
pub timeout_seconds: u64,
}
/// Provider identifiers that indicate an Anthropic backend.
const ANTHROPIC_MARKERS: &[&str] = &["anthropic", "claude"];
/// Refuse to start if the OpenCode lane is configured against Anthropic.
///
/// Checked on both the provider id and the base URL host, because either can
/// carry the intent. This is a terms-of-service constraint expressed as code:
/// the comment explaining it can rot, a failing startup assertion cannot.
pub fn assert_not_anthropic(provider: &str, base_url: &str) -> Result<(), Error> {
let provider_lc = provider.to_ascii_lowercase();
let url_lc = base_url.to_ascii_lowercase();
// Match provider ids on token boundaries so a self-hosted provider that
// merely *serves* a Claude-compatible surface is not caught by accident,
// while `anthropic`, `anthropic-oauth` and `claude-max` all are.
let provider_hits = ANTHROPIC_MARKERS.iter().any(|m| {
provider_lc
.split(|c: char| !c.is_ascii_alphanumeric())
.any(|part| part == *m)
});
// The URL check is host-based: `api.anthropic.com` is disqualifying wherever
// it appears, but a local gateway is not.
let url_hits = url_lc.contains("anthropic.com");
if provider_hits || url_hits {
return Err(Error::AnthropicViaOpencode(provider.to_string()));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_house_helexa_backend_is_accepted() {
assert!(assert_not_anthropic("lair-helexa", "http://hanzalova.internal:31313/v1").is_ok());
assert!(
assert_not_anthropic("lmstudio", "http://beast.hanzalova.internal:1234/v1").is_ok()
);
}
#[test]
fn anthropic_providers_are_refused() {
for provider in [
"anthropic",
"Anthropic",
"anthropic-oauth",
"claude",
"claude-max",
] {
assert!(
assert_not_anthropic(provider, "http://localhost/v1").is_err(),
"{provider} should be refused"
);
}
}
#[test]
fn the_anthropic_api_host_is_refused_whatever_the_provider_is_called() {
assert!(assert_not_anthropic("totally-fine", "https://api.anthropic.com/v1").is_err());
}
#[test]
fn a_local_gateway_serving_an_anthropic_compatible_surface_is_fine() {
// cortex presents both OpenAI and Anthropic compatible APIs, but it is
// local inference — no Anthropic subscription is involved.
assert!(assert_not_anthropic("lair-helexa", "http://hanzalova.internal:31313/v1").is_ok());
}
}