Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ npx skills add iii-hq/iii --all
| [`provider-anthropic`](provider-anthropic/) | Rust | Anthropic Messages API provider behind `llm-router` — `provider::anthropic::stream` with prompt caching, thinking, and live model discovery. |
| [`provider-claude-code`](provider-claude-code/) | Rust | Claude Code (Pro/Max subscription) Messages API provider behind `llm-router` — `provider::claude-code::stream` using OAuth credentials from the auth-credentials vault or `~/.claude/.credentials.json`, namespaced `claude-code/*` catalog. Local/personal dev only (ToS caveat). |
| [`provider-llamacpp`](provider-llamacpp/) | Rust | llama.cpp server (`llama-server`) Chat Completions provider behind `llm-router` — `provider::llamacpp::stream` with optional (no-`--api-key`) auth, real json_schema-constrained output, and live model discovery via `/v1/models` + `/props`. |
| [`provider-openai`](provider-openai/) | Rust | OpenAI Chat Completions provider behind `llm-router` — `provider::open
| [`provider-openai`](provider-openai/) | Rust | OpenAI Chat Completions provider behind `llm-router` — `provider::openai::stream` with reasoning support and live chat-model discovery, plus `provider::openai::embed` for batch embeddings (OpenAI-compatible endpoints included). |
| [`provider-opencode-go`](provider-opencode-go/) | Rust | OpenCode Go Chat Completions provider behind `llm-router` — `provider::opencode_go::stream`, live models.dev-enriched catalog via `refresh_models` |
ai::stream` with reasoning support and live chat-model discovery, plus `provider::openai::embed` for batch embeddings (OpenAI-compatible endpoints included). |
| [`provider-xai`](provider-xai/) | Rust | xAI (Grok) Chat Completions provider behind `llm-router` — `provider::xai::stream` with grok reasoning support and live model discovery against `api.x.ai`. |
| [`provider-zai`](provider-zai/) | Rust | Z.AI (GLM) Chat Completions provider behind `llm-router` — `provider::zai::stream` with GLM thinking/effort support and a curated catalog against `api.z.ai` (no upstream model listing). |
| [`shell`](shell/) | Rust | Unix shell + filesystem worker — `shell::exec` with denylist/timeout/output caps and background jobs; `fs::ls`/`stat`/`mkdir`/`rm`/`chmod`/`mv`/`grep`/`sed`/`read`/`write` with host jail, denylist, and size caps. |
Expand Down
7 changes: 4 additions & 3 deletions provider-opencode-go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ configuration entry (`providers.opencode_go.api_key`, default endpoint
when the model's curated effort list accepts the level (e.g. `grok-4.5`
accepts `low`/`medium`/`high`, `deepseek-v4-flash` accepts `high`/`max`);
models that reason without published effort levels, and unknown ids, stream
without the field. Thinking content is not streamed — the OpenCode Go Chat
Completions wire carries no reasoning deltas.
without the field. When the upstream emits `reasoning_content` deltas they
are relayed as thinking blocks; models that never emit them stream text
only.
- **Structured output:** a `response_format` with a schema maps to strict
`json_schema` mode; without one, `json_object` mode (the caller must
mention "JSON" in the prompt per OpenAI-compatible API rules).
Expand All @@ -74,7 +75,7 @@ provider, and a local stub upstream — no external API calls anywhere.
## Running

The binary takes the standard worker CLI flags: `--url` (engine WebSocket,
default `ws://127.0.0.1:49134`, falls back to the `III_WS_URL` environment
default `ws://127.0.0.1:49134`, falls back to the `III_URL` environment
variable), `--manifest` (print the registry manifest and exit), and
`--config` (accepted but ignored with a warning — provider config comes
from the `llm-router` configuration entry).
1 change: 1 addition & 0 deletions provider-opencode-go/iii-permissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ rules:
# Direct provider calls bypass the router's accounting, budgets, and retry
# policy — never agent-callable. The router invokes these worker-to-worker.
- '!provider::opencode_go::stream'
- '!provider::opencode_go::abort'
- '!provider::opencode_go::refresh_models'
- '!provider::opencode_go::on_router_ready'
28 changes: 27 additions & 1 deletion provider-opencode-go/src/curated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use llm_router::types::model::{Model, ReasoningEffort};
/// be omitted rather than guessed.
pub(crate) struct ModelMeta {
pub(crate) context_window: u64,
pub(crate) max_output: u64,
pub(crate) reasoning: bool,
pub(crate) reasoning_efforts: &'static [&'static str],
pub(crate) tool_call: bool,
Expand All @@ -31,167 +32,191 @@ pub(crate) struct ModelMeta {
pub(crate) fn meta(id: &str) -> Option<&'static ModelMeta> {
match id {
"grok-4.5" => Some(&ModelMeta {
max_output: 500000,
context_window: 500_000,
reasoning: true,
reasoning_efforts: &["low", "medium", "high"],
tool_call: true,
structured_output: true,
}),
"glm-5.2" => Some(&ModelMeta {
max_output: 131072,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &["high", "max"],
tool_call: true,
structured_output: true,
}),
"glm-5.1" => Some(&ModelMeta {
max_output: 32768,
context_window: 202_752,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"glm-5" => Some(&ModelMeta {
max_output: 32768,
context_window: 202_752,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"gpt-5.6-luna" => Some(&ModelMeta {
max_output: 128000,
context_window: 1_050_000,
reasoning: true,
reasoning_efforts: &["none", "low", "medium", "high", "xhigh", "max"],
tool_call: true,
structured_output: true,
}),
"kimi-k3" => Some(&ModelMeta {
max_output: 131072,
context_window: 1_048_576,
reasoning: true,
reasoning_efforts: &["max"],
tool_call: true,
structured_output: true,
}),
"kimi-k2.7-code" => Some(&ModelMeta {
max_output: 262144,
context_window: 262_144,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: true,
}),
"kimi-k2.6" => Some(&ModelMeta {
max_output: 65536,
context_window: 262_144,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"kimi-k2.5" => Some(&ModelMeta {
max_output: 65536,
context_window: 262_144,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"minimax-m3" => Some(&ModelMeta {
max_output: 131072,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"minimax-m2.7" => Some(&ModelMeta {
max_output: 131072,
context_window: 204_800,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"minimax-m2.5" => Some(&ModelMeta {
max_output: 65536,
context_window: 204_800,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"qwen3.7-max" => Some(&ModelMeta {
max_output: 65536,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"qwen3.7-plus" => Some(&ModelMeta {
max_output: 65536,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"qwen3.8-max" => Some(&ModelMeta {
max_output: 131072,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: true,
}),
"qwen3.5-plus" => Some(&ModelMeta {
max_output: 65536,
context_window: 262_144,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"qwen3.6-plus" => Some(&ModelMeta {
max_output: 65536,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"deepseek-v4-pro" => Some(&ModelMeta {
max_output: 384000,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &["high", "max"],
tool_call: true,
structured_output: true,
}),
"deepseek-v4-flash" => Some(&ModelMeta {
max_output: 384000,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &["high", "max"],
tool_call: true,
structured_output: true,
}),
"mimo-v2-omni" => Some(&ModelMeta {
max_output: 128000,
context_window: 262_144,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"mimo-v2-pro" => Some(&ModelMeta {
max_output: 128000,
context_window: 1_048_576,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"mimo-v2.5" => Some(&ModelMeta {
max_output: 128000,
context_window: 1_000_000,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"mimo-v2.5-pro" => Some(&ModelMeta {
max_output: 128000,
context_window: 1_048_576,
reasoning: true,
reasoning_efforts: &[],
tool_call: true,
structured_output: false,
}),
"hy3" => Some(&ModelMeta {
max_output: 64000,
context_window: 256_000,
reasoning: true,
reasoning_efforts: &["none", "low", "high"],
Expand All @@ -201,6 +226,7 @@ pub(crate) fn meta(id: &str) -> Option<&'static ModelMeta> {
// Preview variant in the subscription catalog but not on models.dev —
// conservative defaults rather than guessing hy3-like metadata.
"hy3-preview" => Some(&ModelMeta {
max_output: 4096,
context_window: 128_000,
reasoning: false,
reasoning_efforts: &[],
Expand All @@ -221,7 +247,7 @@ pub fn enrich(id: &str) -> Model {
provider: PROVIDER_ID.into(),
display_name: Some(id.into()),
context_window: m.context_window,
max_output_tokens: 4096,
max_output_tokens: m.max_output,
input_limit: None,
supports_thinking: if m.reasoning { Some(true) } else { None },
supports_xhigh: if m.reasoning_efforts.contains(&"xhigh") {
Expand Down
43 changes: 32 additions & 11 deletions provider-opencode-go/src/reasoning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ fn supported_efforts(model: &str) -> &'static [&'static str] {
.unwrap_or(&[])
}

fn level_str(level: ThinkingLevel) -> &'static str {
fn level_efforts(level: ThinkingLevel) -> &'static [&'static str] {
match level {
ThinkingLevel::Minimal => "minimal",
ThinkingLevel::Low => "low",
ThinkingLevel::Medium => "medium",
ThinkingLevel::High => "high",
ThinkingLevel::Xhigh => "xhigh",
// Some catalogs publish "none" as their floor instead of "minimal"
// (e.g. gpt-5.6-luna); prefer the literal level, fall back to the
// closest accepted floor rather than omitting the param entirely.
ThinkingLevel::Minimal => &["minimal", "none"],
ThinkingLevel::Low => &["low"],
ThinkingLevel::Medium => &["medium"],
ThinkingLevel::High => &["high"],
ThinkingLevel::Xhigh => &["xhigh"],
}
}

Expand All @@ -45,11 +48,10 @@ pub fn reasoning_effort_for(level: Option<ThinkingLevel>, model: &str) -> Option
if ladder.is_empty() {
return None;
}
let want = level_str(level?);
if ladder.contains(&want) {
return Some(want);
}
None
level_efforts(level?)
.iter()
.find(|want| ladder.contains(want))
.copied()
}

#[cfg(test)]
Expand Down Expand Up @@ -171,6 +173,25 @@ mod tests {
);
}

#[test]
fn minimal_falls_back_to_none_when_not_published() {
// gpt-5.6-luna publishes "none" as its floor — minimal maps to it.
assert_eq!(
reasoning_effort_for(Some(ThinkingLevel::Minimal), "gpt-5.6-luna"),
Some("none")
);
// grok-4.5 publishes neither minimal nor none — omit the param.
assert_eq!(
reasoning_effort_for(Some(ThinkingLevel::Minimal), "grok-4.5"),
None
);
// hy3 publishes "none" in its ladder.
assert_eq!(
reasoning_effort_for(Some(ThinkingLevel::Minimal), "hy3"),
Some("none")
);
}

#[test]
fn absent_level_omits_the_param() {
assert_eq!(reasoning_effort_for(None, "deepseek-v4-flash"), None);
Expand Down
9 changes: 7 additions & 2 deletions provider-opencode-go/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,17 @@ pub async fn register_provider(iii: IIIClient) -> Result<(), Error> {
.metadata(json!({ "internal": true })),
);
}
let _ = iii.register_trigger(RegisterTriggerInput {
if let Err(e) = iii.register_trigger(RegisterTriggerInput {
trigger_type: "router::ready".into(),
function_id: surface::ON_ROUTER_READY_ID.into(),
config: json!({}),
metadata: None,
});
}) {
tracing::warn!(
error = %e,
"failed to bind the router::ready trigger; the provider will not re-declare on router restarts"
);
}

// Boot declare, off the boot path.
tokio::spawn(declare_and_refresh(iii, http));
Expand Down
6 changes: 4 additions & 2 deletions provider-opencode-go/src/router_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Provider-scoped shims over the shared router-protocol client
//! (`llm_router::provider_scaffold::router_client`): every call binds this
//! crate's `PROVIDER_ID` and carries the registration token.
//! (`llm_router::provider_scaffold::router_client`): the resolve, reconcile,
//! and models_get wrappers bind this crate's `PROVIDER_ID` and carry the
//! registration token. `register` forwards a declaration payload that already
//! carries both (see `register::declare_once`).
use crate::PROVIDER_ID;
use iii_sdk::errors::Error;
use iii_sdk::IIIClient;
Expand Down
Loading
Loading