///|
pub struct Model {
name : String
description : String?
model_name : String
model_type : Type
api_key : String
base_url : String
safe_zone_tokens : Int
supports_anthropic_prompt_caching : Bool
supports_apply_patch : Bool
// OAuth credentials for CodexOAuth provider
access_token : String?
account_id : String?
refresh_token : String?
id_token : String?
} derive(ToJson, Eq, Debug)
///|
pub(all) enum Provider {
OpenAI
Anthropic
CodexOAuth
Copilot
Qwen
Kimi
KimiCoding
} derive(Eq, Debug)
///|
pub(all) enum Type {
SaaS(Provider)
} derive(Eq, Debug)
///|
pub impl Show for Type with fn output(self : Type, logger : &Logger) -> Unit {
let text = match self {
SaaS(provider) =>
match provider {
OpenAI => "SaaS(OpenAI)"
Anthropic => "SaaS(Anthropic)"
CodexOAuth => "SaaS(CodexOAuth)"
Copilot => "SaaS(Copilot)"
Qwen => "SaaS(Qwen)"
Kimi => "SaaS(Kimi)"
KimiCoding => "SaaS(KimiCoding)"
}
}
logger.write_string(text)
}
///|
/// Create a new model configuration describing how to reach a provider
/// backed language model.
///
/// Parameters:
/// - api_key: API key used when issuing requests to the model service.
/// - base_url: Base URL of the provider endpoint.
/// - name: Identifier for this configuration, used to select models.
/// - safe_zone_tokens: Token buffer reserved for internal bookkeeping.
/// - model_name: Optional provider specific model identifier.
/// - model_type: Optional provider and deployment metadata.
/// - description: Optional human readable description.
/// - supports_anthropic_prompt_caching: Enable Anthropic prompt caching
/// support when true.
///
/// Returns a Model value ready to be passed to higher level APIs.
#as_free_fn
pub fn Model::new(
api_key~ : String,
base_url~ : String,
name~ : String,
safe_zone_tokens~ : Int,
model_name? : String = name,
model_type? : Type = SaaS(OpenAI),
description? : String,
supports_anthropic_prompt_caching? : Bool = false,
access_token? : String,
account_id? : String,
refresh_token? : String,
id_token? : String,
) -> Model {
// Auto-detect apply_patch support based on model name
let name_lower = model_name.to_lower()
let supports_apply_patch = name_lower.contains("gpt-5.1") ||
name_lower.contains("gpt-5.2") ||
name_lower.contains("gpt-5.3") ||
name_lower.contains("gpt-5.4") ||
name_lower.contains("gpt-5.5") ||
name_lower.contains("gpt-5.6")
{
api_key,
base_url,
name,
description,
safe_zone_tokens,
model_name,
model_type,
supports_anthropic_prompt_caching,
supports_apply_patch,
access_token,
account_id,
refresh_token,
id_token,
}
}
///|
/// Return the stable selector clients must use to choose this model.
///
/// `model_name` is the provider wire identifier. Built-in providers need a
/// namespace so that listing a model and selecting it again are inverse
/// operations.
pub fn Model::selector(self : Model) -> String {
let prefix = match self.model_type {
SaaS(CodexOAuth) => Some("codex")
SaaS(Copilot) => Some("copilot")
SaaS(Qwen) => Some("qwen")
SaaS(Kimi) => Some("kimi")
SaaS(KimiCoding) => Some("kimi-coding")
_ => None
}
match prefix {
Some(prefix) => {
let model_name = self.model_name.trim().to_owned()
if model_name.has_prefix("\{prefix}/") {
model_name
} else {
"\{prefix}/\{model_name}"
}
}
None => self.name
}
}
///|
// TODO(upstream): fmt allow blank line
pub(all) enum CommonModels {
/// https://openrouter.ai/qwen/qwen3-coder-plus
/// Starting at $1/M input tokens
/// Starting at $5/M output tokens
Qwen3CoderPlus
/// https://openrouter.ai/qwen/qwen3-coder-flash
/// Starting at $0.30/M input tokens
/// Starting at $1.50/M output tokens
Qwen3CoderFlash
/// https://openrouter.ai/x-ai/grok-4-fast
/// Starting at $0.20/M input tokens
/// Starting at $0.50/M output tokens
Grok4Fast
/// https://openrouter.ai/x-ai/grok-code-fast-1
/// $0.20/M input tokens
/// $1.50/M output tokens
GrokCodeFast1
/// https://openrouter.ai/anthropic/claude-haiku-4.5
/// $1/M input tokens
/// $5/M output tokens
ClaudeHaiku4_5
/// https://openrouter.ai/anthropic/claude-sonnet-4.5
/// Starting at $3/M input tokens
/// Starting at $15/M output tokens
ClaudeSonnet4_5
/// https://openrouter.ai/anthropic/claude-opus-4.5
/// $5/M input tokens
/// $25/M output tokens
ClaudeOpus4_5
/// https://openrouter.ai/openai/gpt-5-codex
/// $1.25/M input tokens
/// $10/M output tokens
Gpt5Codex
/// https://openrouter.ai/openai/gpt-5
/// $1.25/M input tokens
/// $10/M output tokens
Gpt5
/// https://openrouter.ai/openai/gpt-5-mini
/// $0.25/M input tokens
/// $2/M output tokens
Gpt5Mini
/// https://openrouter.ai/openai/gpt-5-nano
/// $0.05/M input tokens
/// $0.40/M output tokens
Gpt5Nano
/// https://openrouter.ai/moonshotai/kimi-k2-0905
/// $0.39/M input tokens
/// $1.90/M output tokens
KimiK2_0905
/// https://openrouter.ai/z-ai/glm-4.6
/// $0.40/M input tokens
/// $1.75/M output tokens
Glm4_6
/// https://openrouter.ai/minimax/minimax-m2
/// $0.15/M input tokens
/// $0.45/M output tokens
MinimaxM2
/// https://openrouter.ai/deepseek/deepseek-v3.2
/// $0.27/M input tokens
/// $0.40/M output tokens
DeepseekV3_2
}
///|
pub impl Show for CommonModels with fn output(
self : CommonModels,
logger : &Logger,
) -> Unit {
match self {
Qwen3CoderPlus => logger.write_string("qwen/qwen3-coder-plus")
Qwen3CoderFlash => logger.write_string("qwen/qwen3-coder-flash")
Grok4Fast => logger.write_string("x-ai/grok-4-fast")
GrokCodeFast1 => logger.write_string("x-ai/grok-code-fast-1")
ClaudeHaiku4_5 => logger.write_string("anthropic/claude-haiku-4.5")
ClaudeSonnet4_5 => logger.write_string("anthropic/claude-sonnet-4.5")
ClaudeOpus4_5 => logger.write_string("anthropic/claude-opus-4.5")
Gpt5Codex => logger.write_string("openai/gpt-5-codex")
Gpt5 => logger.write_string("openai/gpt-5")
Gpt5Mini => logger.write_string("openai/gpt-5-mini")
Gpt5Nano => logger.write_string("openai/gpt-5-nano")
KimiK2_0905 => logger.write_string("moonshotai/kimi-k2-0905")
Glm4_6 => logger.write_string("z-ai/glm-4.6")
MinimaxM2 => logger.write_string("minimax/minimax-m2")
DeepseekV3_2 => logger.write_string("deepseek/deepseek-v3.2")
}
}
///|
fn CommonModels::safe_zone_tokens(self : CommonModels) -> Int {
match self {
Qwen3CoderPlus => 128_000
Qwen3CoderFlash => 128_000
Grok4Fast => 2_000_000
GrokCodeFast1 => 256_000
ClaudeHaiku4_5 => 200_000
ClaudeSonnet4_5 =>
// Although sonnet supports 1 M context, but providers tends to charge
// at a higher rate for context > 200 K. So we keep the safe zone at 200 K
200_000
ClaudeOpus4_5 => 200_000
Gpt5Codex => 400_000
Gpt5 => 400_000
Gpt5Mini => 400_000
Gpt5Nano => 400_000
KimiK2_0905 => 262_144
Glm4_6 => 202_752
MinimaxM2 => 204_800
DeepseekV3_2 => 200_000
}
}
///|
/// Default to qwen3-coder-plus model on OpenRouter
/// for testing
pub fn open_router_model(
api_key~ : String,
name? : CommonModels = Qwen3CoderPlus,
) -> Model {
Model::new(
api_key~,
base_url="https://openrouter.ai/api/v1",
name=name.to_string(),
safe_zone_tokens=name.safe_zone_tokens(),
)
}
///|
pub(all) enum CodexModels {
Gpt5_6Sol
Gpt5_4
Gpt5_3Codex
}
///|
pub impl Show for CodexModels with fn output(
self : CodexModels,
logger : &Logger,
) -> Unit {
match self {
Gpt5_6Sol => logger.write_string("gpt-5.6-sol")
Gpt5_4 => logger.write_string("gpt-5.4")
Gpt5_3Codex => logger.write_string("gpt-5.3-codex")
}
}
///|
fn CodexModels::safe_zone_tokens(self : CodexModels) -> Int {
match self {
Gpt5_6Sol => 400_000
Gpt5_4 => 400_000
Gpt5_3Codex => 400_000
}
}
///|
pub fn codex_oauth_model(
access_token~ : String,
account_id~ : String,
refresh_token~ : String,
id_token? : String,
name? : CodexModels = Gpt5_6Sol,
) -> Model {
Model::new(
api_key="", // Not used for CodexOAuth
base_url="https://chatgpt.com/backend-api",
name=name.to_string(),
model_name=name.to_string(),
model_type=SaaS(CodexOAuth),
safe_zone_tokens=name.safe_zone_tokens(),
access_token~,
account_id~,
refresh_token~,
id_token?,
)
}
///|
pub(all) enum CopilotModels {
// OpenAI models
/// GPT-4.1 via GitHub Copilot
Gpt4_1
/// GPT-4o via GitHub Copilot
Gpt4o
/// GPT-5 via GitHub Copilot
Gpt5
/// GPT-5 mini via GitHub Copilot
Gpt5Mini
/// GPT-5-Codex via GitHub Copilot
Gpt5Codex
/// GPT-5.1 via GitHub Copilot
Gpt5_1
/// GPT-5.1 Codex via GitHub Copilot
Gpt5_1Codex
/// GPT-5.1 Codex Max via GitHub Copilot
Gpt5_1CodexMax
/// GPT-5.1 Codex Mini via GitHub Copilot
Gpt5_1CodexMini
/// GPT-5.2 via GitHub Copilot
Gpt5_2
/// o3 via GitHub Copilot
O3
/// o3-mini via GitHub Copilot
O3Mini
/// o4-mini via GitHub Copilot
O4Mini
// Anthropic models
/// Claude 3.5 Sonnet via GitHub Copilot
Claude3_5Sonnet
/// Claude 3.7 Sonnet via GitHub Copilot
Claude3_7Sonnet
/// Claude 3.7 Sonnet Thought via GitHub Copilot
Claude3_7SonnetThought
/// Claude Haiku 4.5 via GitHub Copilot
ClaudeHaiku4_5
/// Claude Opus 4 via GitHub Copilot
ClaudeOpus4
/// Claude Opus 4.5 via GitHub Copilot
ClaudeOpus4_5
/// Claude Opus 41 via GitHub Copilot
ClaudeOpus41
/// Claude Sonnet 4 via GitHub Copilot
ClaudeSonnet4
/// Claude Sonnet 4.5 via GitHub Copilot
ClaudeSonnet4_5
// Google models
/// Gemini 2.0 Flash via GitHub Copilot
Gemini2_0Flash
/// Gemini 2.5 Pro via GitHub Copilot
Gemini2_5Pro
/// Gemini 3 Flash Preview via GitHub Copilot
Gemini3FlashPreview
/// Gemini 3 Pro Preview via GitHub Copilot
Gemini3ProPreview
// xAI models
/// Grok Code Fast 1 via GitHub Copilot
GrokCodeFast1
}
///|
pub impl Show for CopilotModels with fn output(
self : CopilotModels,
logger : &Logger,
) -> Unit {
match self {
Gpt4_1 => logger.write_string("gpt-4.1")
Gpt4o => logger.write_string("gpt-4o")
Gpt5 => logger.write_string("gpt-5")
Gpt5Mini => logger.write_string("gpt-5-mini")
Gpt5Codex => logger.write_string("gpt-5-codex")
Gpt5_1 => logger.write_string("gpt-5.1")
Gpt5_1Codex => logger.write_string("gpt-5.1-codex")
Gpt5_1CodexMax => logger.write_string("gpt-5.1-codex-max")
Gpt5_1CodexMini => logger.write_string("gpt-5.1-codex-mini")
Gpt5_2 => logger.write_string("gpt-5.2")
O3 => logger.write_string("o3")
O3Mini => logger.write_string("o3-mini")
O4Mini => logger.write_string("o4-mini")
Claude3_5Sonnet => logger.write_string("claude-3.5-sonnet")
Claude3_7Sonnet => logger.write_string("claude-3.7-sonnet")
Claude3_7SonnetThought => logger.write_string("claude-3.7-sonnet-thought")
ClaudeHaiku4_5 => logger.write_string("claude-haiku-4.5")
ClaudeOpus4 => logger.write_string("claude-opus-4")
ClaudeOpus4_5 => logger.write_string("claude-opus-4.5")
ClaudeOpus41 => logger.write_string("claude-opus-41")
ClaudeSonnet4 => logger.write_string("claude-sonnet-4")
ClaudeSonnet4_5 => logger.write_string("claude-sonnet-4.5")
Gemini2_0Flash => logger.write_string("gemini-2.0-flash-001")
Gemini2_5Pro => logger.write_string("gemini-2.5-pro")
Gemini3FlashPreview => logger.write_string("gemini-3-flash-preview")
Gemini3ProPreview => logger.write_string("gemini-3-pro-preview")
GrokCodeFast1 => logger.write_string("grok-code-fast-1")
}
}
///|
fn CopilotModels::safe_zone_tokens(self : CopilotModels) -> Int {
match self {
Gpt4_1 => 1_000_000
Gpt4o => 128_000
Gpt5 => 400_000
Gpt5Mini => 400_000
Gpt5Codex => 400_000
Gpt5_1 => 400_000
Gpt5_1Codex => 400_000
Gpt5_1CodexMax => 400_000
Gpt5_1CodexMini => 400_000
Gpt5_2 => 400_000
O3 => 200_000
O3Mini => 200_000
O4Mini => 200_000
Claude3_5Sonnet => 200_000
Claude3_7Sonnet => 200_000
Claude3_7SonnetThought => 200_000
ClaudeHaiku4_5 => 200_000
ClaudeOpus4 => 200_000
ClaudeOpus4_5 => 200_000
ClaudeOpus41 => 200_000
ClaudeSonnet4 => 200_000
ClaudeSonnet4_5 => 200_000
Gemini2_0Flash => 1_000_000
Gemini2_5Pro => 1_000_000
Gemini3FlashPreview => 1_000_000
Gemini3ProPreview => 1_000_000
GrokCodeFast1 => 256_000
}
}
///|
pub fn copilot_model(
copilot_token~ : String,
github_token~ : String,
name? : CopilotModels = Gpt4_1,
) -> Model {
Model::new(
api_key=copilot_token,
base_url="https://api.githubcopilot.com",
name=name.to_string(),
model_name=name.to_string(),
model_type=SaaS(Copilot),
safe_zone_tokens=name.safe_zone_tokens(),
refresh_token=github_token, // Store github_token as refresh_token for refreshing
)
}
///|
pub(all) enum QwenModels {
Qwen3CoderPlus
Qwen3CoderFlash
Qwen3_235B_A22B
Qwen3_32B
QwenMax
QwenPlus
QwenTurbo
QwenLong
}
///|
pub impl Show for QwenModels with fn output(self : QwenModels, logger : &Logger) -> Unit {
match self {
Qwen3CoderPlus => logger.write_string("qwen3-coder-plus")
Qwen3CoderFlash => logger.write_string("qwen3-coder-flash")
Qwen3_235B_A22B => logger.write_string("qwen3-235b-a22b")
Qwen3_32B => logger.write_string("qwen3-32b")
QwenMax => logger.write_string("qwen-max")
QwenPlus => logger.write_string("qwen-plus")
QwenTurbo => logger.write_string("qwen-turbo")
QwenLong => logger.write_string("qwen-long")
}
}
///|
fn QwenModels::safe_zone_tokens(self : QwenModels) -> Int {
match self {
Qwen3CoderPlus => 128_000
Qwen3CoderFlash => 128_000
Qwen3_235B_A22B => 128_000
Qwen3_32B => 128_000
QwenMax => 32_000
QwenPlus => 128_000
QwenTurbo => 128_000
QwenLong => 1_000_000
}
}
///|
pub fn qwen_model(
api_key~ : String,
name? : QwenModels = Qwen3CoderPlus,
) -> Model {
Model::new(
api_key~,
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
name=name.to_string(),
model_name=name.to_string(),
model_type=SaaS(Qwen),
safe_zone_tokens=name.safe_zone_tokens(),
)
}
///|
pub(all) enum KimiModels {
KimiK2_0905
KimiK2P5
MoonshotV1_8k
MoonshotV1_32k
MoonshotV1_128k
}
///|
pub impl Show for KimiModels with fn output(self : KimiModels, logger : &Logger) -> Unit {
match self {
KimiK2_0905 => logger.write_string("kimi-k2-0905")
KimiK2P5 => logger.write_string("kimi-k2.5")
MoonshotV1_8k => logger.write_string("moonshot-v1-8k")
MoonshotV1_32k => logger.write_string("moonshot-v1-32k")
MoonshotV1_128k => logger.write_string("moonshot-v1-128k")
}
}
///|
fn KimiModels::safe_zone_tokens(self : KimiModels) -> Int {
match self {
KimiK2_0905 => 262_144
KimiK2P5 => 262_144
MoonshotV1_8k => 8_000
MoonshotV1_32k => 32_000
MoonshotV1_128k => 128_000
}
}
///|
pub fn kimi_model(api_key~ : String, name? : KimiModels = KimiK2P5) -> Model {
Model::new(
api_key~,
base_url="https://api.moonshot.cn/v1",
name=name.to_string(),
model_name=name.to_string(),
model_type=SaaS(Kimi),
safe_zone_tokens=name.safe_zone_tokens(),
)
}
///|
pub(all) enum KimiCodingModels {
K2P5
K2_0905
}
///|
pub impl Show for KimiCodingModels with fn output(
self : KimiCodingModels,
logger : &Logger,
) -> Unit {
match self {
K2P5 => logger.write_string("k2p5")
K2_0905 => logger.write_string("k2-0905")
}
}
///|
fn KimiCodingModels::safe_zone_tokens(self : KimiCodingModels) -> Int {
match self {
K2P5 => 262_144
K2_0905 => 262_144
}
}
///|
pub fn kimi_coding_model(
api_key~ : String,
name? : KimiCodingModels = K2P5,
) -> Model {
Model::new(
api_key~,
base_url="https://api.moonshot.cn/v1",
name=name.to_string(),
model_name=name.to_string(),
model_type=SaaS(KimiCoding),
safe_zone_tokens=name.safe_zone_tokens(),
)
}