mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
Merge pull request #695 from fabro-sh/fix/modal-provider-catalog
Prioritize Modal and rename Kimi provider to Moonshot
This commit is contained in:
commit
33e5dc8d3f
47 changed files with 301 additions and 180 deletions
|
|
@ -5,7 +5,7 @@ DEEPSEEK_API_KEY=
|
|||
FIREWORKS_API_KEY=
|
||||
GEMINI_API_KEY=
|
||||
INCEPTION_API_KEY=
|
||||
KIMI_API_KEY=
|
||||
MOONSHOT_API_KEY=
|
||||
MINIMAX_API_KEY=
|
||||
MODAL_KIMI_K3_BASE_URL=
|
||||
MODAL_TOKEN_ID=
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M121.683 75.25L149.997 124L91.4816 224.75C90.3128 226.757 88.155 228 85.8174 228H32.9664C31.7976 228 30.6778 227.691 29.697 227.131C28.7161 226.57 27.8906 225.758 27.3021 224.75L0.876625 179.25C-0.292208 177.243 -0.292208 174.765 0.876625 172.75L57.512 75.25C58.0923 74.2425 58.9259 73.43 59.9068 72.8694C60.8876 72.3088 62.0074 72 63.1762 72H116.027C118.365 72 120.523 73.2431 121.692 75.25H121.683ZM299.125 172.75L242.49 75.25C241.91 74.2425 241.076 73.43 240.095 72.8694C239.114 72.3088 237.995 72 236.826 72H183.975C181.637 72 179.479 73.2431 178.311 75.25L149.997 124L208.512 224.75C209.681 226.757 211.839 228 214.177 228H267.027C268.196 228 269.316 227.691 270.297 227.131C271.278 226.57 272.103 225.758 272.692 224.75L299.117 179.25C300.286 177.243 300.286 174.765 299.117 172.75H299.125Z" fill="#62DE61"/>
|
||||
<path d="M121.683 75.25L149.997 124L91.4816 224.75C90.3128 226.757 88.155 228 85.8174 228H32.9664C31.7976 228 30.6778 227.691 29.697 227.131C28.7161 226.57 27.8906 225.758 27.3021 224.75L0.876625 179.25C-0.292208 177.243 -0.292208 174.765 0.876625 172.75L57.512 75.25C58.0923 74.2425 58.9259 73.43 59.9068 72.8694C60.8876 72.3088 62.0074 72 63.1762 72H116.027C118.365 72 120.523 73.2431 121.692 75.25H121.683ZM299.125 172.75L242.49 75.25C241.91 74.2425 241.076 73.43 240.095 72.8694C239.114 72.3088 237.995 72 236.826 72H183.975C181.637 72 179.479 73.2431 178.311 75.25L149.997 124L208.512 224.75C209.681 226.757 211.839 228 214.177 228H267.027C268.196 228 269.316 227.691 270.297 227.131C271.278 226.57 272.103 225.758 272.692 224.75L299.117 179.25C300.286 177.243 300.286 174.765 299.117 172.75H299.125Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 922 B |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
|
@ -1,6 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="currentColor">
|
||||
<path d="M3 248.945C18 248.945 76 236 106 219C136 202 136 202 198 158C276.497 102.293 332 120.945 423 120.945" stroke-width="90"/>
|
||||
<path d="M511 121.5L357.25 210.268L357.25 32.7324L511 121.5Z"/>
|
||||
<path d="M0 249C15 249 73 261.945 103 278.945C133 295.945 133 295.945 195 339.945C273.497 395.652 329 377 420 377" stroke-width="90"/>
|
||||
<path d="M508 376.445L354.25 287.678L354.25 465.213L508 376.445Z"/>
|
||||
<svg width="24" height="24" viewBox="0 0 401.4 293.7" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M303.9475,17.19926c42.79734,0,77.48933,34.69327,77.48933,77.48933s-34.69199,77.48933-77.48933,77.48933l76.86166,76.86244c9.76367,9.76313,2.84903,26.45667-10.95697,26.45667h-220.88335c-71.32686,0-129.14889-57.82202-129.14889-129.14889S77.64197,17.19926,148.96884,17.19926h154.97866ZM148.96884,68.85881c-42.79607,0-77.48933,34.69327-77.48933,77.48933s34.69327,77.48933,77.48933,77.48933,77.48933-34.69327,77.48933-77.48933-34.69327-77.48933-77.48933-77.48933Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 533 B After Width: | Height: | Size: 587 B |
|
|
@ -383,7 +383,7 @@ fabro secret set GEMINI_API_KEY AI...
|
|||
| `ANTHROPIC_API_KEY` | Anthropic (Claude) |
|
||||
| `OPENAI_API_KEY` | OpenAI (GPT) |
|
||||
| `GEMINI_API_KEY` | Google (Gemini) |
|
||||
| `KIMI_API_KEY` | Kimi |
|
||||
| `MOONSHOT_API_KEY` or `KIMI_API_KEY` | Moonshot AI; `MOONSHOT_API_KEY` takes precedence |
|
||||
| `ZAI_API_KEY` | Zai (GLM) |
|
||||
| `MINIMAX_API_KEY` | Minimax |
|
||||
| `INCEPTION_API_KEY` | Inception (Mercury) |
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Previously, you could only see the final output of a run. Now you can trace exac
|
|||
</Accordion>
|
||||
|
||||
<Accordion title="Improvements">
|
||||
- Kimi, Z.AI, and Minimax LLM providers added to model catalog
|
||||
- Moonshot AI, Z.AI, and Minimax LLM providers added to model catalog
|
||||
- Runs use time-sortable ULIDs instead of UUIDs, making it easier to find recent runs in logs and the API
|
||||
- Asymmetric JWT authentication between fabro-web and the API server, with GitHub identity in the token
|
||||
</Accordion>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Schema upgrades now create a consistent `fabro.sqlite3.pre-migration.bak` snapsh
|
|||
</Accordion>
|
||||
|
||||
<Accordion title="Improvements">
|
||||
- Added Kimi K3 through the direct Kimi provider and OpenRouter
|
||||
- Added Kimi K3 through the direct Moonshot AI provider and OpenRouter
|
||||
- Added GLM 5.2 through Z.AI and OpenRouter
|
||||
- Left-to-right run graphs now zoom to 400% and preserve separate viewport positions for left-to-right and top-to-bottom layouts
|
||||
</Accordion>
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ Fabro performs this selection once when creating a run and persists the chosen p
|
|||
| `gemini-3.5-flash` | gemini | `gemini-35-flash` | 1M | $1.50 / $9.00 | 150 tok/s |
|
||||
| `gemini-3-flash-preview` | gemini | `gemini-flash` | 1M | $0.50 / $3.00 | 150 tok/s |
|
||||
| `gemini-3.1-flash-lite` | gemini | `gemini-flash-lite`, `gemini-3.1-flash-lite-preview` | 1M | $0.25 / $1.50 | 200 tok/s |
|
||||
| `kimi-k2.5` | kimi | | 262K | $0.60 / $3.00 | 50 tok/s |
|
||||
| `kimi-k3` | kimi | `kimi` | 1M | $3.00 / $15.00 | n/a |
|
||||
| `kimi-k2.5` | moonshot | | 262K | $0.60 / $3.00 | 50 tok/s |
|
||||
| `kimi-k3` | moonshot | `kimi` | 1M | $3.00 / $15.00 | n/a |
|
||||
| `deepseek-v4-flash` | deepseek | `deepseek`, `deepseek-v4`, `deepseek-flash` | 1,048,576 | $0.14 / $0.28 | n/a |
|
||||
| `deepseek-v4-pro` | deepseek | | 1,048,576 | $0.435 / $0.87 | n/a |
|
||||
| `laguna-s-2.1` | poolside | `laguna`, `laguna-s` | 1M | $0.10 / $0.20 | n/a |
|
||||
|
|
@ -229,7 +229,7 @@ When no model or provider is specified, Fabro chooses the default offering on th
|
|||
| `anthropic` | `claude-sonnet-4-6` |
|
||||
| `openai` | `gpt-5.6-sol` |
|
||||
| `gemini` | `gemini-3.5-flash` |
|
||||
| `kimi` | `kimi-k3` |
|
||||
| `moonshot` | `kimi-k3` |
|
||||
| `poolside` | `laguna-s-2.1` |
|
||||
| `zai` | `glm-5.2` |
|
||||
| `minimax` | `minimax-m2.5` |
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ For a qualified fallback, the selector may be that provider's canonical model ID
|
|||
```toml title="run.toml"
|
||||
[run.model.fallbacks]
|
||||
"kimi-k3" = [
|
||||
"kimi:kimi-k3",
|
||||
"moonshot:kimi-k3",
|
||||
"openrouter:kimi-k3",
|
||||
"claude-opus",
|
||||
]
|
||||
|
|
@ -169,7 +169,7 @@ For example, a server that selects Modal as the primary `kimi-k3` offering can d
|
|||
|
||||
```toml title="settings.toml"
|
||||
[run.model.fallbacks]
|
||||
"kimi-k3" = ["kimi:kimi-k3", "openrouter:kimi-k3", "claude-opus"]
|
||||
"kimi-k3" = ["moonshot:kimi-k3", "openrouter:kimi-k3", "claude-opus"]
|
||||
"glm-5.2" = ["gpt-sol"]
|
||||
"gpt-sol" = ["claude-opus"]
|
||||
"claude-opus" = ["gpt-sol"]
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ For env-backed usage, `EnvCredentialSource` checks for API key environment varia
|
|||
| `ANTHROPIC_API_KEY` | Anthropic |
|
||||
| `OPENAI_API_KEY` | OpenAI |
|
||||
| `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini |
|
||||
| `KIMI_API_KEY` | Kimi |
|
||||
| `MOONSHOT_API_KEY` or `KIMI_API_KEY` | Moonshot AI; `MOONSHOT_API_KEY` takes precedence |
|
||||
| `ZAI_API_KEY` | ZAI |
|
||||
| `MINIMAX_API_KEY` | Minimax |
|
||||
| `INCEPTION_API_KEY` | Inception |
|
||||
|
|
|
|||
|
|
@ -3515,7 +3515,7 @@ root = "{}"
|
|||
assert!(ids.contains(&ProviderId::anthropic()));
|
||||
assert!(ids.contains(&ProviderId::openai()));
|
||||
assert!(ids.contains(&ProviderId::gemini()));
|
||||
assert!(ids.contains(&ProviderId::new("kimi")));
|
||||
assert!(ids.contains(&ProviderId::new("moonshot")));
|
||||
assert!(ids.contains(&ProviderId::new("zai")));
|
||||
assert!(ids.contains(&ProviderId::new("minimax")));
|
||||
assert!(ids.contains(&ProviderId::new("inception")));
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ mod tests {
|
|||
.body(
|
||||
serde_json::json!({
|
||||
"model_id": "kimi-k2.5",
|
||||
"provider": "kimi",
|
||||
"provider": "moonshot",
|
||||
"status": "skip"
|
||||
})
|
||||
.to_string(),
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ mod tests {
|
|||
ProviderId::anthropic(),
|
||||
ProviderId::openai(),
|
||||
ProviderId::gemini(),
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
ProviderId::new("zai"),
|
||||
ProviderId::new("minimax"),
|
||||
ProviderId::new("inception"),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ fn remove_provider_env(cmd: &mut Command) -> &mut Command {
|
|||
.env_remove("OPENAI_API_KEY")
|
||||
.env_remove("GEMINI_API_KEY")
|
||||
.env_remove("GOOGLE_API_KEY")
|
||||
.env_remove("MOONSHOT_API_KEY")
|
||||
.env_remove("KIMI_API_KEY")
|
||||
.env_remove("ZAI_API_KEY")
|
||||
.env_remove("MINIMAX_API_KEY")
|
||||
|
|
|
|||
|
|
@ -1542,7 +1542,7 @@ enabled = true
|
|||
let mut checks = Vec::new();
|
||||
let configured = std::collections::BTreeMap::from([(
|
||||
"kimi-k3".to_string(),
|
||||
model_refs(&["kimi:kimi-k3", "openrouter:kimi-k3"]),
|
||||
model_refs(&["moonshot:kimi-k3", "openrouter:kimi-k3"]),
|
||||
)]);
|
||||
|
||||
assert!(run_model_fallback_check(
|
||||
|
|
@ -1554,13 +1554,12 @@ enabled = true
|
|||
|
||||
let check = checks.last().expect("fallback check should be present");
|
||||
assert_eq!(check.status, CheckStatus::Warning);
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.iter()
|
||||
.any(|detail| detail.warn
|
||||
&& detail.text.contains("provider `kimi` is not configured"))
|
||||
);
|
||||
assert!(check.details.iter().any(|detail| {
|
||||
detail.warn
|
||||
&& detail
|
||||
.text
|
||||
.contains("provider `moonshot` is not configured")
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1603,15 +1602,15 @@ enabled = true
|
|||
})
|
||||
}
|
||||
|
||||
fn ready_kimi_and_openrouter_state(
|
||||
fn ready_moonshot_and_openrouter_state(
|
||||
server: &httpmock::MockServer,
|
||||
) -> Arc<crate::server::AppState> {
|
||||
let kimi_url = server.url("/kimi/v1");
|
||||
let moonshot_url = server.url("/moonshot/v1");
|
||||
let openrouter_url = server.url("/openrouter/v1");
|
||||
let llm_catalog_settings: LlmCatalogSettings = toml::from_str(&format!(
|
||||
r#"
|
||||
[providers.kimi]
|
||||
base_url = "{kimi_url}"
|
||||
[providers.moonshot]
|
||||
base_url = "{moonshot_url}"
|
||||
|
||||
[providers.openrouter]
|
||||
base_url = "{openrouter_url}"
|
||||
|
|
@ -1623,7 +1622,7 @@ enabled = true
|
|||
crate::test_support::TestAppStateBuilder::new()
|
||||
.llm_catalog_settings(llm_catalog_settings)
|
||||
.vault_entries([
|
||||
(EnvVars::KIMI_API_KEY, "test-kimi-key"),
|
||||
(EnvVars::KIMI_API_KEY, "test-moonshot-key"),
|
||||
(EnvVars::OPENROUTER_API_KEY, "test-openrouter-key"),
|
||||
])
|
||||
.build()
|
||||
|
|
@ -1640,7 +1639,7 @@ enabled = true
|
|||
.unwrap_or_default();
|
||||
ready_providers.sort();
|
||||
assert_eq!(ready_providers, vec![
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
ProviderId::new("openrouter")
|
||||
]);
|
||||
|
||||
|
|
@ -2663,7 +2662,7 @@ digraph Demo {
|
|||
.json_body(openai_compatible_completion("anthropic/claude-fable-5"));
|
||||
})
|
||||
.await;
|
||||
let state = ready_kimi_and_openrouter_state(&server);
|
||||
let state = ready_moonshot_and_openrouter_state(&server);
|
||||
|
||||
let (response, _ok) = preflight_for_model(&state, "claude-fable").await;
|
||||
|
||||
|
|
@ -2687,18 +2686,18 @@ digraph Demo {
|
|||
#[tokio::test]
|
||||
async fn preflight_uses_ready_providers_for_unknown_unqualified_model() {
|
||||
let server = httpmock::MockServer::start_async().await;
|
||||
let kimi_probe = server
|
||||
let moonshot_probe = server
|
||||
.mock_async(|when, then| {
|
||||
when.method(httpmock::Method::POST)
|
||||
.path("/kimi/v1/chat/completions")
|
||||
.header("authorization", "Bearer test-kimi-key")
|
||||
.path("/moonshot/v1/chat/completions")
|
||||
.header("authorization", "Bearer test-moonshot-key")
|
||||
.json_body_includes(r#"{"model":"provider-private-preview"}"#);
|
||||
then.status(200)
|
||||
.header("content-type", "application/json")
|
||||
.json_body(openai_compatible_completion("provider-private-preview"));
|
||||
})
|
||||
.await;
|
||||
let state = ready_kimi_and_openrouter_state(&server);
|
||||
let state = ready_moonshot_and_openrouter_state(&server);
|
||||
|
||||
let (response, _ok) = preflight_for_model(&state, "provider-private-preview").await;
|
||||
|
||||
|
|
@ -2717,10 +2716,10 @@ digraph Demo {
|
|||
.iter()
|
||||
.map(|detail| detail.text.as_str())
|
||||
.find(|detail| detail.starts_with("Provider: ")),
|
||||
Some("Provider: kimi")
|
||||
Some("Provider: moonshot")
|
||||
);
|
||||
assert_eq!(llm_check.status, types::PreflightCheckResultStatus::Pass);
|
||||
kimi_probe.assert_async().await;
|
||||
moonshot_probe.assert_async().await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -7446,7 +7446,7 @@ async fn list_models_exposes_reasoning_effort_controls() {
|
|||
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri(api("/models?provider=kimi"))
|
||||
.uri(api("/models?provider=moonshot"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -16443,7 +16443,7 @@ async fn create_completion_unsupported_reasoning_efforts_return_bad_request() {
|
|||
then.status(500);
|
||||
});
|
||||
let state = TestAppStateBuilder::new()
|
||||
.provider_base_url("kimi", upstream.url("/v1"))
|
||||
.provider_base_url("moonshot", upstream.url("/v1"))
|
||||
.vault_entries([(EnvVars::KIMI_API_KEY, "test-kimi-api-key")])
|
||||
.build();
|
||||
let app = crate::test_support::build_test_router(state);
|
||||
|
|
@ -16456,7 +16456,7 @@ async fn create_completion_unsupported_reasoning_efforts_return_bad_request() {
|
|||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
serde_json::json!({
|
||||
"provider": "kimi",
|
||||
"provider": "moonshot",
|
||||
"model": "kimi-k3",
|
||||
"reasoning_effort": effort,
|
||||
"stream": stream,
|
||||
|
|
@ -16515,7 +16515,7 @@ async fn create_completion_returns_disjoint_usage_buckets() {
|
|||
}));
|
||||
});
|
||||
let state = TestAppStateBuilder::new()
|
||||
.provider_base_url("kimi", upstream.base_url())
|
||||
.provider_base_url("moonshot", upstream.base_url())
|
||||
.vault_entries([(EnvVars::KIMI_API_KEY, "test-kimi-api-key")])
|
||||
.build();
|
||||
let app = crate::test_support::build_test_router(state);
|
||||
|
|
@ -16526,7 +16526,7 @@ async fn create_completion_returns_disjoint_usage_buckets() {
|
|||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"provider": "kimi",
|
||||
"provider": "moonshot",
|
||||
"model": "kimi-k3",
|
||||
"stream": false,
|
||||
"messages": [{
|
||||
|
|
@ -16655,7 +16655,7 @@ async fn create_completion_structured_output_forwards_reasoning_effort() {
|
|||
}));
|
||||
});
|
||||
let state = TestAppStateBuilder::new()
|
||||
.provider_base_url("kimi", upstream.base_url())
|
||||
.provider_base_url("moonshot", upstream.base_url())
|
||||
.vault_entries([(EnvVars::KIMI_API_KEY, "test-kimi-api-key")])
|
||||
.build();
|
||||
let app = crate::test_support::build_test_router(state);
|
||||
|
|
@ -16666,7 +16666,7 @@ async fn create_completion_structured_output_forwards_reasoning_effort() {
|
|||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
serde_json::json!({
|
||||
"provider": "kimi",
|
||||
"provider": "moonshot",
|
||||
"model": "kimi-k3",
|
||||
"reasoning_effort": "high",
|
||||
"stream": false,
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ async fn mock_anthropic_install_validation(server: &MockServer) -> httpmock::Moc
|
|||
.await
|
||||
}
|
||||
|
||||
async fn mock_kimi_install_validation(server: &MockServer) -> httpmock::Mock<'_> {
|
||||
async fn mock_moonshot_install_validation(server: &MockServer) -> httpmock::Mock<'_> {
|
||||
server
|
||||
.mock_async(|when, then| {
|
||||
when.method("POST")
|
||||
|
|
@ -1355,11 +1355,13 @@ async fn token_install_finish_invokes_shutdown_callback_after_accepting() {
|
|||
#[tokio::test]
|
||||
async fn install_llm_accepts_catalog_openai_compatible_provider() {
|
||||
let llm_mock = MockServer::start_async().await;
|
||||
mock_kimi_install_validation(&llm_mock).await;
|
||||
mock_moonshot_install_validation(&llm_mock).await;
|
||||
|
||||
let app = build_install_router(
|
||||
InstallAppState::for_test("test-install-token")
|
||||
.with_provider_base_url(ProviderId::new("kimi"), format!("{}/v1", llm_mock.url(""))),
|
||||
InstallAppState::for_test("test-install-token").with_provider_base_url(
|
||||
ProviderId::new("moonshot"),
|
||||
format!("{}/v1", llm_mock.url("")),
|
||||
),
|
||||
);
|
||||
|
||||
let test_response = app
|
||||
|
|
@ -1371,7 +1373,7 @@ async fn install_llm_accepts_catalog_openai_compatible_provider() {
|
|||
.header("authorization", "Bearer test-install-token")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
r#"{"provider":"kimi","api_key":"kimi-test-key"}"#,
|
||||
r#"{"provider":"moonshot","api_key":"kimi-test-key"}"#,
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
|
|
@ -1388,7 +1390,7 @@ async fn install_llm_accepts_catalog_openai_compatible_provider() {
|
|||
.header("authorization", "Bearer test-install-token")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
r#"{"providers":[{"provider":"kimi","api_key":"kimi-test-key"}]}"#,
|
||||
r#"{"providers":[{"provider":"moonshot","api_key":"kimi-test-key"}]}"#,
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -443,12 +443,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn summary_budget_for_always_reasoning_route_without_effort_adds_headroom() {
|
||||
let kimi = ProviderId::new("kimi");
|
||||
let model = catalog_model(&kimi, "kimi-k2.5");
|
||||
let moonshot = ProviderId::new("moonshot");
|
||||
let model = catalog_model(&moonshot, "kimi-k2.5");
|
||||
assert!(model.supports_reasoning());
|
||||
assert!(!model.supports_reasoning_effort());
|
||||
assert_eq!(
|
||||
builtin_summary_max_tokens(&kimi, "kimi-k2.5"),
|
||||
builtin_summary_max_tokens(&moonshot, "kimi-k2.5"),
|
||||
SUMMARY_MAX_TOKENS + REASONING_HEADROOM_TOKENS
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ impl KimiProfile {
|
|||
Self {
|
||||
base: BaseProfile {
|
||||
profile_kind: AgentProfileKind::Kimi,
|
||||
provider_id: ProviderId::new("kimi"),
|
||||
provider_id: ProviderId::new("moonshot"),
|
||||
model: model.into(),
|
||||
catalog: None,
|
||||
registry,
|
||||
|
|
@ -178,8 +178,8 @@ mod tests {
|
|||
#[test]
|
||||
fn kimi_models_select_the_kimi_profile_on_every_provider() {
|
||||
for (catalog, provider, model) in [
|
||||
(catalog(), "kimi", "kimi-k3"),
|
||||
(catalog(), "kimi", "kimi-k2.5"),
|
||||
(catalog(), "moonshot", "kimi-k3"),
|
||||
(catalog(), "moonshot", "kimi-k2.5"),
|
||||
(catalog_with_openrouter(), "openrouter", "kimi-k3"),
|
||||
(catalog_with_openrouter(), "openrouter", "kimi-k2.6"),
|
||||
] {
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ mod tests {
|
|||
let options = native_tool_options(AgentProfileKind::OpenAi, has_web_search);
|
||||
let deps = ProfileDeps::standalone(options);
|
||||
OpenAiProfile::with_native_tools("kimi-k2.5", &deps).with_route(
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
Arc::new(Catalog::from_builtin().unwrap()),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,19 +229,19 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn kimi_provider_prompt_uses_catalog_display_name() {
|
||||
fn moonshot_provider_prompt_uses_catalog_display_name() {
|
||||
let profile =
|
||||
OpenAiProfile::new("kimi-k2.5").with_route(ProviderId::new("kimi"), test_catalog());
|
||||
OpenAiProfile::new("kimi-k2.5").with_route(ProviderId::new("moonshot"), test_catalog());
|
||||
let env = MockSandbox::linux();
|
||||
let prompt = profile.build_system_prompt(&env, &EnvContext::default(), &[], None, &[]);
|
||||
assert!(prompt.contains("powered by Kimi"));
|
||||
assert!(prompt.contains("powered by Moonshot AI"));
|
||||
assert!(!prompt.contains("powered by OpenAI"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_compatible_profile_uses_json_schema_edit_tool() {
|
||||
let profile =
|
||||
OpenAiProfile::new("kimi-k2.5").with_route(ProviderId::new("kimi"), test_catalog());
|
||||
OpenAiProfile::new("kimi-k2.5").with_route(ProviderId::new("moonshot"), test_catalog());
|
||||
|
||||
let names = profile.tool_registry().names();
|
||||
assert!(names.contains(&"edit_file".to_string()));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
source: lib/components/fabro-agent/src/profiles/mod.rs
|
||||
expression: system_prompt(&openai_edit_file_profile(true))
|
||||
---
|
||||
You are a coding agent powered by Kimi, running in a terminal-based agentic coding assistant. You are expected to be precise, safe, and helpful.
|
||||
You are a coding agent powered by Moonshot AI, running in a terminal-based agentic coding assistant. You are expected to be precise, safe, and helpful.
|
||||
|
||||
You can receive user prompts and context such as files in the workspace, communicate with the user by streaming thinking and responses, and emit function calls to run terminal commands and edit files.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
source: lib/components/fabro-agent/src/profiles/mod.rs
|
||||
expression: system_prompt(&openai_edit_file_profile(false))
|
||||
---
|
||||
You are a coding agent powered by Kimi, running in a terminal-based agentic coding assistant. You are expected to be precise, safe, and helpful.
|
||||
You are a coding agent powered by Moonshot AI, running in a terminal-based agentic coding assistant. You are expected to be precise, safe, and helpful.
|
||||
|
||||
You can receive user prompts and context such as files in the workspace, communicate with the user by streaming thinking and responses, and emit function calls to run terminal commands and edit files.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ struct OpenAiTwinOptions {
|
|||
|
||||
fn summarizer_model_id(provider: &Provider) -> ModelHandle {
|
||||
match provider.as_str() {
|
||||
ProviderId::OPENAI | "kimi" | "zai" | "minimax" | "inception" => ModelHandle::ByName {
|
||||
ProviderId::OPENAI | "moonshot" | "zai" | "minimax" | "inception" => ModelHandle::ByName {
|
||||
provider: ProviderId::openai(),
|
||||
model: "gpt-5.4-mini".to_string(),
|
||||
},
|
||||
|
|
@ -279,7 +279,7 @@ macro_rules! provider_tests {
|
|||
);
|
||||
provider_test!(
|
||||
$scenario,
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
"kimi-k2.5",
|
||||
kimi,
|
||||
keys = ["KIMI_API_KEY"]
|
||||
|
|
@ -413,7 +413,7 @@ provider_test!(
|
|||
);
|
||||
provider_test!(
|
||||
web_fetch,
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
"kimi-k2.5",
|
||||
kimi,
|
||||
keys = ["KIMI_API_KEY", "OPENAI_API_KEY"]
|
||||
|
|
@ -461,7 +461,7 @@ web_search_provider_test!(
|
|||
keys = ["GEMINI_API_KEY", "BRAVE_SEARCH_API_KEY"]
|
||||
);
|
||||
web_search_provider_test!(
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
"kimi-k2.5",
|
||||
kimi,
|
||||
keys = ["KIMI_API_KEY", "BRAVE_SEARCH_API_KEY"]
|
||||
|
|
@ -518,7 +518,7 @@ macro_rules! non_openai_provider_tests {
|
|||
);
|
||||
provider_test!(
|
||||
$scenario,
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
"kimi-k2.5",
|
||||
kimi,
|
||||
keys = ["KIMI_API_KEY"]
|
||||
|
|
@ -800,7 +800,7 @@ reasoning_effort_tests!(
|
|||
keys = ["GEMINI_API_KEY"]
|
||||
);
|
||||
reasoning_effort_tests!(
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
"kimi-k2.5",
|
||||
kimi_reasoning_effort,
|
||||
keys = ["KIMI_API_KEY"]
|
||||
|
|
@ -882,7 +882,7 @@ loop_detection_tests!(
|
|||
keys = ["GEMINI_API_KEY"]
|
||||
);
|
||||
loop_detection_tests!(
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
"kimi-k2.5",
|
||||
kimi_loop_detection,
|
||||
keys = ["KIMI_API_KEY"]
|
||||
|
|
|
|||
|
|
@ -427,10 +427,10 @@ mod tests {
|
|||
fn openai_compatible_factory_uses_provider_id_for_name() {
|
||||
let config = AdapterConfig {
|
||||
base_url: Some("https://api.moonshot.ai/v1".to_string()),
|
||||
..AdapterConfig::new("kimi", ApiKeyHeader::Bearer("k".to_string()))
|
||||
..AdapterConfig::new("moonshot", ApiKeyHeader::Bearer("k".to_string()))
|
||||
};
|
||||
let adapter = factory_for(AdapterKind::OpenAiCompatible)(config).unwrap();
|
||||
assert_eq!(adapter.name(), "kimi");
|
||||
assert_eq!(adapter.name(), "moonshot");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -491,7 +491,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn openai_compatible_factory_errors_without_base_url() {
|
||||
let config = AdapterConfig::new("kimi", ApiKeyHeader::Bearer("k".to_string()));
|
||||
let config = AdapterConfig::new("moonshot", ApiKeyHeader::Bearer("k".to_string()));
|
||||
let Err(err) = factory_for(AdapterKind::OpenAiCompatible)(config) else {
|
||||
panic!("expected missing base_url error");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1529,13 +1529,16 @@ base_url = "{}/v1"
|
|||
let mut client = Client::new(HashMap::new(), None, vec![]);
|
||||
client.catalog = Some(Arc::clone(&catalog));
|
||||
client
|
||||
.register_provider(Arc::new(MockProvider::new("kimi", "should not dispatch")))
|
||||
.register_provider(Arc::new(MockProvider::new(
|
||||
"moonshot",
|
||||
"should not dispatch",
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut request = test_request();
|
||||
request.model = "kimi-k2.5".to_string();
|
||||
request.provider = Some("kimi".to_string());
|
||||
request.provider = Some("moonshot".to_string());
|
||||
request.reasoning_effort = Some(ReasoningEffort::High);
|
||||
|
||||
let err = client.complete(&request).await.unwrap_err();
|
||||
|
|
@ -1554,13 +1557,13 @@ base_url = "{}/v1"
|
|||
let mut client = Client::new(HashMap::new(), None, vec![]);
|
||||
client.catalog = Some(Arc::clone(&catalog));
|
||||
client
|
||||
.register_provider(Arc::new(MockProvider::new("kimi", "accepted")))
|
||||
.register_provider(Arc::new(MockProvider::new("moonshot", "accepted")))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut request = test_request();
|
||||
request.model = "kimi-k3".to_string();
|
||||
request.provider = Some("kimi".to_string());
|
||||
request.provider = Some("moonshot".to_string());
|
||||
request.reasoning_effort = Some(ReasoningEffort::High);
|
||||
|
||||
let response = client.complete(&request).await.unwrap();
|
||||
|
|
@ -1724,7 +1727,7 @@ base_url = "{}/v1"
|
|||
let catalog = catalog_with("");
|
||||
let client = Client::from_credentials(
|
||||
vec![ApiCredential {
|
||||
provider: ProviderId::new("kimi"),
|
||||
provider: ProviderId::new("moonshot"),
|
||||
auth_header: Some(ApiKeyHeader::Bearer("kimi-key".to_string())),
|
||||
extra_headers: HashMap::new(),
|
||||
base_url: None,
|
||||
|
|
@ -1737,8 +1740,8 @@ base_url = "{}/v1"
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(client.provider_names(), vec!["kimi"]);
|
||||
assert_eq!(client.default_provider(), Some("kimi"));
|
||||
assert_eq!(client.provider_names(), vec!["moonshot"]);
|
||||
assert_eq!(client.default_provider(), Some("moonshot"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ pub(crate) struct CodecCtx<'a> {
|
|||
/// the stream model fallback uses `request.model`).
|
||||
pub request: &'a Request,
|
||||
/// Identity stamped into `Response.provider`, and the `provider_options`
|
||||
/// namespace key for the openai_compatible codec (kimi/zai/…).
|
||||
/// namespace key for the openai_compatible codec (moonshot/zai/…).
|
||||
pub provider_name: &'a str,
|
||||
/// The model id to send on the wire — catalog `api_id`, resolved by the
|
||||
/// route (today `api_id == id` everywhere).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! The OpenAI Chat Completions (`/chat/completions`) codec.
|
||||
//!
|
||||
//! Serves every "OpenAI-compatible" route (kimi, zai, minimax, venice,
|
||||
//! Serves every "OpenAI-compatible" route (moonshot, zai, minimax, venice,
|
||||
//! inception, ollama, litellm, …). Pure translation: no HTTP, auth, or base
|
||||
//! URL — the adapter shell owns those. Count-tokens and error mapping use the
|
||||
//! `Codec` trait defaults (this dialect has no count route and uses the shared
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ fn apply_cache_breakpoints(messages: &mut [ChatMessage]) {
|
|||
/// Merge `provider_options.<provider_name>` fields into the serialized API
|
||||
/// request body.
|
||||
///
|
||||
/// The provider name is configurable (e.g. "groq", "together", "kimi"),
|
||||
/// The provider name is configurable (e.g. "groq", "together", "moonshot"),
|
||||
/// allowing each instance to have its own namespace in `provider_options`.
|
||||
pub(super) fn merge_provider_options(
|
||||
body: &mut serde_json::Value,
|
||||
|
|
@ -221,7 +221,7 @@ mod tests {
|
|||
let mut request = minimal_request();
|
||||
request.reasoning_effort = Some(ReasoningEffort::High);
|
||||
|
||||
let body = encode_body(&request, "kimi", false);
|
||||
let body = encode_body(&request, "moonshot", false);
|
||||
|
||||
assert_eq!(body["reasoning_effort"], "high");
|
||||
}
|
||||
|
|
@ -229,7 +229,7 @@ mod tests {
|
|||
#[test]
|
||||
fn encode_omits_sampling_params_for_models_that_reject_them() {
|
||||
let model = Catalog::builtin()
|
||||
.get_on_provider(&ProviderId::new("kimi"), "kimi-k3")
|
||||
.get_on_provider(&ProviderId::new("moonshot"), "kimi-k3")
|
||||
.unwrap();
|
||||
let mut request = minimal_request();
|
||||
request.model = model.id.to_string();
|
||||
|
|
@ -238,7 +238,7 @@ mod tests {
|
|||
let params = CodecParams::default();
|
||||
let ctx = CodecCtx {
|
||||
request: &request,
|
||||
provider_name: "kimi",
|
||||
provider_name: "moonshot",
|
||||
deployment_id: model.id.as_str(),
|
||||
model: Some(model),
|
||||
params: ¶ms,
|
||||
|
|
@ -262,7 +262,7 @@ mod tests {
|
|||
let deployment_id = request.model.clone();
|
||||
let ctx = CodecCtx {
|
||||
request: &request,
|
||||
provider_name: "kimi",
|
||||
provider_name: "moonshot",
|
||||
deployment_id: &deployment_id,
|
||||
model: None,
|
||||
params: ¶ms,
|
||||
|
|
|
|||
|
|
@ -169,7 +169,8 @@ impl Adapter {
|
|||
|
||||
/// Collect a streaming response into a single [`Response`].
|
||||
///
|
||||
/// Used by non-Anthropic providers (e.g. Kimi) that require `stream=true`.
|
||||
/// Used by non-Anthropic providers (e.g. Moonshot) that require
|
||||
/// `stream=true`.
|
||||
async fn complete_via_stream(&self, request: &Request) -> Result<Response, Error> {
|
||||
use futures::StreamExt;
|
||||
|
||||
|
|
@ -266,7 +267,7 @@ impl ProviderAdapter for Adapter {
|
|||
self.validate_request(request)?;
|
||||
|
||||
let route = self.route_config();
|
||||
// Non-Anthropic providers (e.g. Kimi) require stream=true even for
|
||||
// Non-Anthropic providers (e.g. Moonshot) require stream=true even for
|
||||
// blocking calls. Collect the stream into a single Response.
|
||||
if route.force_streaming {
|
||||
return self.complete_via_stream(request).await;
|
||||
|
|
@ -367,8 +368,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn adapter_with_name() {
|
||||
let adapter = Adapter::new("key").with_name("kimi");
|
||||
assert_eq!(adapter.name(), "kimi");
|
||||
let adapter = Adapter::new("key").with_name("moonshot");
|
||||
assert_eq!(adapter.name(), "moonshot");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ async fn openai_gpt_5_5_pro_complete() {
|
|||
async fn kimi_k3_reasoning_tool_round_trip() {
|
||||
let api_key = std::env::var(EnvVars::KIMI_API_KEY).expect("KIMI_API_KEY must be set");
|
||||
let adapter = OpenAiCompatibleAdapter::new(api_key, "https://api.moonshot.ai/v1")
|
||||
.with_name("kimi")
|
||||
.with_name("moonshot")
|
||||
.with_catalog(Arc::new(Catalog::from_builtin().unwrap()));
|
||||
let tool = ToolDefinition::function(
|
||||
"multiply",
|
||||
|
|
|
|||
|
|
@ -835,11 +835,11 @@ async fn stream_without_message_stop_emits_no_finish() {
|
|||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Custom-named route (the Kimi-over-anthropic shape)
|
||||
// Custom-named route (the Moonshot Kimi-over-anthropic shape)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Shared setup for a custom-named anthropic-dialect (Kimi) stream route; the
|
||||
/// request and event halves are pinned by separate tests.
|
||||
/// Shared setup for a custom-named Moonshot Kimi stream route using the
|
||||
/// Anthropic dialect; separate tests pin the request and event halves.
|
||||
async fn custom_named_stream_capture() -> (WireCapture, Vec<serde_json::Value>) {
|
||||
let sse = support::sse_transcript(&[
|
||||
(
|
||||
|
|
@ -865,15 +865,15 @@ async fn custom_named_stream_capture() -> (WireCapture, Vec<serde_json::Value>)
|
|||
("message_stop", r#"{"type":"message_stop"}"#),
|
||||
]);
|
||||
stream_capture(
|
||||
adapter().with_name("kimi"),
|
||||
adapter().with_name("moonshot"),
|
||||
&base_request("kimi-test"),
|
||||
&sse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// A custom-named (Kimi) route authenticates with a bearer token and sends no
|
||||
/// `anthropic-version` header. This pins that route shape on the wire.
|
||||
/// A custom-named Moonshot route authenticates with a bearer token and sends
|
||||
/// no `anthropic-version` header. This pins that route shape on the wire.
|
||||
#[tokio::test]
|
||||
async fn custom_named_stream_route() {
|
||||
let (capture, _) = custom_named_stream_capture().await;
|
||||
|
|
@ -903,7 +903,7 @@ async fn custom_named_stream_error_identity() {
|
|||
),
|
||||
]);
|
||||
let (_capture, events) = stream_capture(
|
||||
adapter().with_name("kimi"),
|
||||
adapter().with_name("moonshot"),
|
||||
&base_request("kimi-test"),
|
||||
&sse,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ async fn encode_kimi_k3_uses_catalog_reasoning_and_sampling_controls() {
|
|||
..base_request(MODEL)
|
||||
};
|
||||
let capture = encode_capture_with(&request, move |adapter| {
|
||||
adapter.with_name("kimi").with_catalog(catalog)
|
||||
adapter.with_name("moonshot").with_catalog(catalog)
|
||||
})
|
||||
.await;
|
||||
|
||||
|
|
@ -398,9 +398,9 @@ async fn encode_openrouter_claude_auto_cache_opt_out() {
|
|||
async fn encode_provider_options_keyed_by_adapter_name() {
|
||||
let request = corpus_provider_options(
|
||||
MODEL,
|
||||
serde_json::json!({"kimi": {"repetition_penalty": 1.2}}),
|
||||
serde_json::json!({"moonshot": {"repetition_penalty": 1.2}}),
|
||||
);
|
||||
let capture = encode_capture_with(&request, |adapter| adapter.with_name("kimi")).await;
|
||||
let capture = encode_capture_with(&request, |adapter| adapter.with_name("moonshot")).await;
|
||||
fabro_test::fabro_json_snapshot!(capture.body);
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +411,7 @@ async fn encode_provider_options_other_namespace_ignored() {
|
|||
MODEL,
|
||||
serde_json::json!({"openai": {"repetition_penalty": 1.2}}),
|
||||
);
|
||||
let capture = encode_capture_with(&request, |adapter| adapter.with_name("kimi")).await;
|
||||
let capture = encode_capture_with(&request, |adapter| adapter.with_name("moonshot")).await;
|
||||
fabro_test::fabro_json_snapshot!(capture.body);
|
||||
}
|
||||
|
||||
|
|
@ -904,11 +904,11 @@ async fn stream_without_done_or_content_synthesizes_nothing() {
|
|||
async fn custom_named_complete_identity() {
|
||||
let server = MockServer::start();
|
||||
let (mock, _slot) = mount_capture(&server, "/chat/completions", minimal_body());
|
||||
let adapter = adapter(&server).with_name("kimi");
|
||||
let adapter = adapter(&server).with_name("moonshot");
|
||||
let response = adapter
|
||||
.complete(&base_request(MODEL))
|
||||
.await
|
||||
.expect("complete should succeed");
|
||||
mock.assert();
|
||||
assert_eq!(response.provider, "kimi");
|
||||
assert_eq!(response.provider, "moonshot");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ expression: rendered
|
|||
"type": "stream_start"
|
||||
},
|
||||
{
|
||||
"stream_item_error": "Server error from kimi: Overloaded",
|
||||
"stream_item_error": "Server error from moonshot: Overloaded",
|
||||
"retryable": true,
|
||||
"failover_eligible": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ expression: rendered
|
|||
"response": {
|
||||
"id": "msg_kimi",
|
||||
"model": "kimi-test",
|
||||
"provider": "kimi",
|
||||
"provider": "moonshot",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
|
|
|
|||
|
|
@ -3348,7 +3348,7 @@ enabled = true
|
|||
let policy = ModelFallbackPolicy::new(std::collections::BTreeMap::from([(
|
||||
"kimi-k3".to_string(),
|
||||
vec![
|
||||
FallbackTarget::new("kimi", "kimi-k3"),
|
||||
FallbackTarget::new("moonshot", "kimi-k3"),
|
||||
FallbackTarget::new("openrouter", "kimi-k3"),
|
||||
FallbackTarget::new("anthropic", "claude-opus-5"),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ enabled = true
|
|||
let eligible = [ProviderId::new("openrouter")];
|
||||
let configured = BTreeMap::from([(
|
||||
"kimi-k3".to_string(),
|
||||
references(&["kimi:kimi-k3", "openrouter:kimi-k3"]),
|
||||
references(&["moonshot:kimi-k3", "openrouter:kimi-k3"]),
|
||||
)]);
|
||||
|
||||
let resolved = resolve_model_fallbacks(&catalog, &eligible, &configured).unwrap();
|
||||
|
|
@ -491,7 +491,7 @@ enabled = true
|
|||
requested_model,
|
||||
provider,
|
||||
..
|
||||
}] if requested_model == "kimi-k3" && provider == &ProviderId::new("kimi")
|
||||
}] if requested_model == "kimi-k3" && provider == &ProviderId::new("moonshot")
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -512,13 +512,13 @@ enabled = true
|
|||
};
|
||||
let eligible = [
|
||||
ProviderId::new("modal"),
|
||||
ProviderId::new("kimi"),
|
||||
ProviderId::new("moonshot"),
|
||||
ProviderId::new("openrouter"),
|
||||
];
|
||||
let configured = BTreeMap::from([
|
||||
(
|
||||
"kimi-k3".to_string(),
|
||||
references(&["kimi:kimi-k3", "openrouter:kimi-k3", "claude-opus"]),
|
||||
references(&["moonshot:kimi-k3", "openrouter:kimi-k3", "claude-opus"]),
|
||||
),
|
||||
("glm-5.2".to_string(), references(&["gpt-sol"])),
|
||||
("gpt-sol".to_string(), references(&["claude-opus"])),
|
||||
|
|
@ -541,7 +541,7 @@ enabled = true
|
|||
.expect("requested model should have a chain")
|
||||
};
|
||||
assert_eq!(chain("kimi-k3"), [
|
||||
FallbackTarget::new("kimi", "kimi-k3"),
|
||||
FallbackTarget::new("moonshot", "kimi-k3"),
|
||||
FallbackTarget::new("openrouter", "kimi-k3"),
|
||||
FallbackTarget::new("openrouter", "claude-opus-5"),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -630,6 +630,60 @@ base_url = "https://example--kimi-k3.modal.run/v1"
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_moonshot_api_request_prefers_moonshot_env_key() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let vault = Vault::load(dir.path().join("secrets.json")).unwrap();
|
||||
let resolver = test_resolver(
|
||||
vault,
|
||||
Arc::new(|name| match name {
|
||||
EnvVars::MOONSHOT_API_KEY => Some("moonshot-key".to_string()),
|
||||
EnvVars::KIMI_API_KEY => Some("kimi-key".to_string()),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
let resolved = resolver
|
||||
.resolve(
|
||||
ProviderId::new("moonshot"),
|
||||
CredentialUsage::ApiRequest,
|
||||
&default_catalog(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let ResolvedCredential::Api(api) = resolved;
|
||||
assert_eq!(
|
||||
api.auth_header,
|
||||
Some(ApiKeyHeader::Bearer("moonshot-key".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_moonshot_api_request_falls_back_to_kimi_env_key() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let vault = Vault::load(dir.path().join("secrets.json")).unwrap();
|
||||
let resolver = test_resolver(
|
||||
vault,
|
||||
Arc::new(|name| (name == EnvVars::KIMI_API_KEY).then(|| "kimi-key".to_string())),
|
||||
);
|
||||
|
||||
let resolved = resolver
|
||||
.resolve(
|
||||
ProviderId::new("moonshot"),
|
||||
CredentialUsage::ApiRequest,
|
||||
&default_catalog(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let ResolvedCredential::Api(api) = resolved;
|
||||
assert_eq!(
|
||||
api.auth_header,
|
||||
Some(ApiKeyHeader::Bearer("kimi-key".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_openai_api_request_falls_back_to_codex_oauth_credential() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
|
|
@ -3,16 +3,23 @@
|
|||
//! Holds the trusted, mergeable LLM provider/model catalog data:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [llm.providers.kimi]
|
||||
//! display_name = "Kimi"
|
||||
//! [llm.providers.moonshot]
|
||||
//! display_name = "Moonshot AI"
|
||||
//! adapter = "openai_compatible"
|
||||
//! base_url = "https://api.moonshot.ai/v1"
|
||||
//! auth = { credentials = ["env:KIMI_API_KEY", "vault:KIMI_API_KEY"] }
|
||||
//! priority = 60
|
||||
//! enabled = true
|
||||
//! aliases = ["moonshot"]
|
||||
//! aliases = ["moonshot-ai"]
|
||||
//!
|
||||
//! [llm.providers.kimi.models."kimi-k2.5"]
|
||||
//! [llm.providers.moonshot.auth]
|
||||
//! credentials = [
|
||||
//! "env:MOONSHOT_API_KEY",
|
||||
//! "env:KIMI_API_KEY",
|
||||
//! "vault:MOONSHOT_API_KEY",
|
||||
//! "vault:KIMI_API_KEY",
|
||||
//! ]
|
||||
//!
|
||||
//! [llm.providers.moonshot.models."kimi-k2.5"]
|
||||
//! ...
|
||||
//! ```
|
||||
//!
|
||||
|
|
@ -489,33 +496,46 @@ agent_profile = "gemini"
|
|||
#[test]
|
||||
fn parses_minimal_provider_entry() {
|
||||
let toml = r#"
|
||||
[providers.kimi]
|
||||
display_name = "Kimi"
|
||||
[providers.moonshot]
|
||||
display_name = "Moonshot AI"
|
||||
adapter = "openai_compatible"
|
||||
agent_profile = "openai"
|
||||
base_url = "https://api.moonshot.ai/v1"
|
||||
priority = 60
|
||||
enabled = true
|
||||
aliases = ["moonshot"]
|
||||
aliases = ["moonshot-ai"]
|
||||
|
||||
[providers.kimi.auth]
|
||||
credentials = ["env:KIMI_API_KEY", "vault:KIMI_API_KEY"]
|
||||
[providers.moonshot.auth]
|
||||
credentials = [
|
||||
"env:MOONSHOT_API_KEY",
|
||||
"env:KIMI_API_KEY",
|
||||
"vault:MOONSHOT_API_KEY",
|
||||
"vault:KIMI_API_KEY",
|
||||
]
|
||||
"#;
|
||||
let layer: LlmLayer = toml::from_str(toml).unwrap();
|
||||
let kimi = layer.providers.get("kimi").unwrap();
|
||||
assert_eq!(kimi.display_name.as_deref(), Some("Kimi"));
|
||||
assert_eq!(kimi.adapter.as_deref(), Some("openai_compatible"));
|
||||
assert_eq!(kimi.agent_profile, Some(AgentProfileKind::OpenAi));
|
||||
let auth = kimi.auth.as_ref().expect("expected api_key auth");
|
||||
let moonshot = layer.providers.get("moonshot").unwrap();
|
||||
assert_eq!(moonshot.display_name.as_deref(), Some("Moonshot AI"));
|
||||
assert_eq!(moonshot.adapter.as_deref(), Some("openai_compatible"));
|
||||
assert_eq!(moonshot.agent_profile, Some(AgentProfileKind::OpenAi));
|
||||
let auth = moonshot.auth.as_ref().expect("expected api_key auth");
|
||||
assert_eq!(auth.header, ApiKeyHeaderPolicy::Bearer);
|
||||
assert_eq!(auth.credentials, vec![
|
||||
CredentialRef::Env("MOONSHOT_API_KEY".to_string()),
|
||||
CredentialRef::Env("KIMI_API_KEY".to_string()),
|
||||
CredentialRef::Vault("MOONSHOT_API_KEY".to_string()),
|
||||
CredentialRef::Vault("KIMI_API_KEY".to_string()),
|
||||
]);
|
||||
assert_eq!(kimi.base_url.as_deref(), Some("https://api.moonshot.ai/v1"));
|
||||
assert_eq!(kimi.priority, Some(60));
|
||||
assert_eq!(kimi.enabled, Some(true));
|
||||
assert_eq!(kimi.aliases.as_deref(), Some(&["moonshot".to_string()][..]));
|
||||
assert_eq!(
|
||||
moonshot.base_url.as_deref(),
|
||||
Some("https://api.moonshot.ai/v1")
|
||||
);
|
||||
assert_eq!(moonshot.priority, Some(60));
|
||||
assert_eq!(moonshot.enabled, Some(true));
|
||||
assert_eq!(
|
||||
moonshot.aliases.as_deref(),
|
||||
Some(&["moonshot-ai".to_string()][..])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -584,7 +604,7 @@ x-portkey-api-key = "sk-portkey-literal"
|
|||
fn parses_full_model_entry() {
|
||||
let toml = r#"
|
||||
[models."kimi-k2.5"]
|
||||
provider = "kimi"
|
||||
provider = "moonshot"
|
||||
api_id = "kimi-k2.5"
|
||||
display_name = "Kimi K2.5"
|
||||
family = "kimi"
|
||||
|
|
@ -611,7 +631,7 @@ cache_input_cost_per_mtok = 0.15
|
|||
"#;
|
||||
let layer: LlmLayer = toml::from_str(toml).unwrap();
|
||||
let m = layer.models.get("kimi-k2.5").unwrap();
|
||||
assert_eq!(m.provider.as_deref(), Some("kimi"));
|
||||
assert_eq!(m.provider.as_deref(), Some("moonshot"));
|
||||
assert_eq!(m.api_id.as_deref(), Some("kimi-k2.5"));
|
||||
assert_eq!(m.display_name.as_deref(), Some("Kimi K2.5"));
|
||||
assert_eq!(m.family.as_deref(), Some("kimi"));
|
||||
|
|
@ -714,7 +734,7 @@ cache_input_cost_per_mtok = 9.0
|
|||
#[test]
|
||||
fn rejects_unknown_provider_field() {
|
||||
let toml = r#"
|
||||
[providers.kimi]
|
||||
[providers.moonshot]
|
||||
adapter = "openai_compatible"
|
||||
unknown_field = true
|
||||
"#;
|
||||
|
|
@ -725,7 +745,7 @@ unknown_field = true
|
|||
#[test]
|
||||
fn rejects_removed_provider_base_url_env_field() {
|
||||
let toml = r#"
|
||||
[providers.kimi]
|
||||
[providers.moonshot]
|
||||
adapter = "openai_compatible"
|
||||
base_url_env = "KIMI_BASE_URL"
|
||||
"#;
|
||||
|
|
@ -902,7 +922,7 @@ mystery = 1
|
|||
fn merge_map_field_merges_per_provider_id() {
|
||||
let mut high_map: std::collections::HashMap<String, ProviderSettings> =
|
||||
std::collections::HashMap::new();
|
||||
high_map.insert("kimi".to_string(), ProviderSettings {
|
||||
high_map.insert("moonshot".to_string(), ProviderSettings {
|
||||
base_url: Some("https://override".to_string()),
|
||||
..ProviderSettings::default()
|
||||
});
|
||||
|
|
@ -910,7 +930,7 @@ mystery = 1
|
|||
|
||||
let mut low_map: std::collections::HashMap<String, ProviderSettings> =
|
||||
std::collections::HashMap::new();
|
||||
low_map.insert("kimi".to_string(), ProviderSettings {
|
||||
low_map.insert("moonshot".to_string(), ProviderSettings {
|
||||
adapter: Some("openai_compatible".to_string()),
|
||||
base_url: Some("https://defaults".to_string()),
|
||||
..ProviderSettings::default()
|
||||
|
|
@ -918,9 +938,9 @@ mystery = 1
|
|||
let low: MergeMap<ProviderSettings> = MergeMap::from(low_map);
|
||||
|
||||
let merged = high.combine(low);
|
||||
let kimi = merged.get("kimi").unwrap();
|
||||
assert_eq!(kimi.adapter.as_deref(), Some("openai_compatible"));
|
||||
assert_eq!(kimi.base_url.as_deref(), Some("https://override"));
|
||||
let moonshot = merged.get("moonshot").unwrap();
|
||||
assert_eq!(moonshot.adapter.as_deref(), Some("openai_compatible"));
|
||||
assert_eq!(moonshot.base_url.as_deref(), Some("https://override"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -292,15 +292,15 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn accepts_new_llm_providers_subtree() {
|
||||
let parsed = "[llm.providers.kimi]\nadapter = \"openai_compatible\"\n"
|
||||
let parsed = "[llm.providers.moonshot]\nadapter = \"openai_compatible\"\n"
|
||||
.parse::<SettingsLayer>()
|
||||
.unwrap();
|
||||
assert!(parsed.llm.unwrap().providers.contains_key("kimi"));
|
||||
assert!(parsed.llm.unwrap().providers.contains_key("moonshot"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_new_llm_models_subtree() {
|
||||
let parsed = "[llm.providers.kimi.models.\"foo\"]\n"
|
||||
let parsed = "[llm.providers.moonshot.models.\"foo\"]\n"
|
||||
.parse::<SettingsLayer>()
|
||||
.unwrap();
|
||||
assert!(
|
||||
|
|
@ -308,7 +308,7 @@ mod tests {
|
|||
.llm
|
||||
.unwrap()
|
||||
.providers
|
||||
.get("kimi")
|
||||
.get("moonshot")
|
||||
.unwrap()
|
||||
.models
|
||||
.contains_key("foo")
|
||||
|
|
|
|||
|
|
@ -1414,9 +1414,10 @@ output_cost_per_mtok = 2.0
|
|||
|
||||
#[test]
|
||||
fn old_provider_tagged_pricing_policy_is_rejected() {
|
||||
let error =
|
||||
serde_json::from_value::<ModelPricingPolicy>(openai_pricing_json("provider", "kimi"))
|
||||
.unwrap_err();
|
||||
let error = serde_json::from_value::<ModelPricingPolicy>(openai_pricing_json(
|
||||
"provider", "moonshot",
|
||||
))
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains("algorithm"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3743,7 +3743,7 @@ enabled = true
|
|||
assert_eq!(provider.codec, CodecKind::OpenAiCompatible);
|
||||
assert_eq!(provider.agent_profile, AgentProfileKind::Kimi);
|
||||
assert_eq!(provider.billing_policy, BillingPolicy::OpenAi);
|
||||
assert_eq!(provider.priority, 30);
|
||||
assert_eq!(provider.priority, 75);
|
||||
assert!(provider.auth.is_none());
|
||||
assert_eq!(
|
||||
provider.extra_headers,
|
||||
|
|
@ -3858,8 +3858,28 @@ enabled = true
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_kimi_k3_selection_prefers_direct_kimi_then_modal_over_openrouter() {
|
||||
let kimi = ProviderId::new("kimi");
|
||||
fn builtin_moonshot_provider_prefers_moonshot_api_key() {
|
||||
let moonshot = ProviderId::new("moonshot");
|
||||
let catalog = Catalog::builtin();
|
||||
let provider = catalog
|
||||
.provider(&moonshot)
|
||||
.expect("Moonshot provider should be present");
|
||||
|
||||
assert_eq!(provider.auth.as_ref().unwrap().credentials, vec![
|
||||
CredentialRef::Env("MOONSHOT_API_KEY".to_string()),
|
||||
CredentialRef::Env("KIMI_API_KEY".to_string()),
|
||||
CredentialRef::Vault("MOONSHOT_API_KEY".to_string()),
|
||||
CredentialRef::Vault("KIMI_API_KEY".to_string()),
|
||||
]);
|
||||
assert_eq!(
|
||||
catalog.provider_vault_secret_name(&moonshot),
|
||||
Some("MOONSHOT_API_KEY")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_kimi_k3_selection_prefers_modal_then_moonshot_over_openrouter() {
|
||||
let moonshot = ProviderId::new("moonshot");
|
||||
let modal = ProviderId::new("modal");
|
||||
let openrouter = ProviderId::new("openrouter");
|
||||
let catalog = Catalog::from_builtin_with_overrides(&minimal_settings(
|
||||
|
|
@ -3877,10 +3897,19 @@ enabled = true
|
|||
.select(
|
||||
"kimi-k3",
|
||||
None,
|
||||
&HashSet::from([kimi.clone(), modal.clone(), openrouter.clone()]),
|
||||
&HashSet::from([moonshot.clone(), modal.clone(), openrouter.clone()]),
|
||||
)
|
||||
.expect("direct Kimi should win portable Kimi K3 selection");
|
||||
assert_eq!(selected.provider, kimi);
|
||||
.expect("Modal should win portable Kimi K3 selection");
|
||||
assert_eq!(selected.provider, modal);
|
||||
|
||||
let selected = catalog
|
||||
.select(
|
||||
"kimi-k3",
|
||||
None,
|
||||
&HashSet::from([moonshot.clone(), openrouter.clone()]),
|
||||
)
|
||||
.expect("Moonshot should win when Modal is unavailable");
|
||||
assert_eq!(selected.provider, moonshot);
|
||||
|
||||
let selected = catalog
|
||||
.select("kimi-k3", None, &HashSet::from([modal.clone(), openrouter]))
|
||||
|
|
@ -4405,7 +4434,7 @@ enabled = true
|
|||
);
|
||||
assert_eq!(
|
||||
catalog
|
||||
.provider(&ProviderId::new("kimi"))
|
||||
.provider(&ProviderId::new("moonshot"))
|
||||
.unwrap()
|
||||
.base_url
|
||||
.as_deref(),
|
||||
|
|
@ -4464,7 +4493,7 @@ enabled = true
|
|||
("anthropic", CodecKind::AnthropicMessages),
|
||||
("openai", CodecKind::OpenAiResponses),
|
||||
("gemini", CodecKind::GeminiGenerate),
|
||||
("kimi", CodecKind::OpenAiCompatible),
|
||||
("moonshot", CodecKind::OpenAiCompatible),
|
||||
] {
|
||||
let provider_id = ProviderId::new(provider);
|
||||
assert_eq!(catalog.provider(&provider_id).unwrap().codec, expected);
|
||||
|
|
@ -6920,7 +6949,7 @@ sampling_params = false
|
|||
insta::assert_debug_snapshot!(m, @r#"
|
||||
Model {
|
||||
id: "kimi-k2.5",
|
||||
provider: kimi,
|
||||
provider: moonshot,
|
||||
family: "kimi-k2",
|
||||
display_name: "Kimi K2.5",
|
||||
limits: ModelLimits {
|
||||
|
|
@ -6976,7 +7005,7 @@ sampling_params = false
|
|||
insta::assert_debug_snapshot!(m, @r#"
|
||||
Model {
|
||||
id: "kimi-k3",
|
||||
provider: kimi,
|
||||
provider: moonshot,
|
||||
family: "kimi-k3",
|
||||
display_name: "Kimi K3",
|
||||
limits: ModelLimits {
|
||||
|
|
@ -7327,11 +7356,11 @@ context_window = 1050000
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn closest_model_haiku_to_kimi() {
|
||||
fn closest_model_haiku_to_moonshot() {
|
||||
let haiku = Catalog::builtin().get("claude-haiku-4-5").unwrap();
|
||||
assert!(
|
||||
Catalog::builtin()
|
||||
.closest(&ProviderId::new("kimi"), haiku)
|
||||
.closest(&ProviderId::new("moonshot"), haiku)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ display_name = "Modal"
|
|||
adapter = "openai_compatible"
|
||||
agent_profile = "kimi"
|
||||
api_key_url = "https://modal.com/docs/guide/endpoints#proxy-tokens"
|
||||
priority = 30
|
||||
priority = 75
|
||||
enabled = false
|
||||
|
||||
[providers.modal.extra_headers]
|
||||
|
|
|
|||
|
|
@ -1,26 +1,31 @@
|
|||
[providers.kimi]
|
||||
display_name = "Kimi"
|
||||
[providers.moonshot]
|
||||
display_name = "Moonshot AI"
|
||||
adapter = "openai_compatible"
|
||||
agent_profile = "kimi"
|
||||
api_key_url = "https://platform.kimi.ai/console/api-keys"
|
||||
base_url = "https://api.moonshot.ai/v1"
|
||||
priority = 70
|
||||
|
||||
[providers.kimi.auth]
|
||||
credentials = ["env:KIMI_API_KEY", "vault:KIMI_API_KEY"]
|
||||
[providers.moonshot.auth]
|
||||
credentials = [
|
||||
"env:MOONSHOT_API_KEY",
|
||||
"env:KIMI_API_KEY",
|
||||
"vault:MOONSHOT_API_KEY",
|
||||
"vault:KIMI_API_KEY",
|
||||
]
|
||||
|
||||
[providers.kimi.models."kimi-k2.5"]
|
||||
[providers.moonshot.models."kimi-k2.5"]
|
||||
display_name = "Kimi K2.5"
|
||||
family = "kimi-k2"
|
||||
training = "2025-10-01"
|
||||
knowledge_cutoff = "October 2025"
|
||||
estimated_output_tps = 50
|
||||
|
||||
[providers.kimi.models."kimi-k2.5".limits]
|
||||
[providers.moonshot.models."kimi-k2.5".limits]
|
||||
context_window = 262144
|
||||
max_output = 32768
|
||||
|
||||
[providers.kimi.models."kimi-k2.5".features]
|
||||
[providers.moonshot.models."kimi-k2.5".features]
|
||||
tools = true
|
||||
vision = true
|
||||
reasoning = true
|
||||
|
|
@ -28,24 +33,24 @@ reasoning_by_default = true
|
|||
prompt_cache = true
|
||||
sampling_params = false
|
||||
|
||||
[providers.kimi.models."kimi-k2.5".costs]
|
||||
[providers.moonshot.models."kimi-k2.5".costs]
|
||||
input_cost_per_mtok = 0.6
|
||||
output_cost_per_mtok = 3.0
|
||||
cache_input_cost_per_mtok = 0.1
|
||||
|
||||
[providers.kimi.models."kimi-k3"]
|
||||
[providers.moonshot.models."kimi-k3"]
|
||||
display_name = "Kimi K3"
|
||||
family = "kimi-k3"
|
||||
default = true
|
||||
aliases = ["kimi"]
|
||||
|
||||
[providers.kimi.models."kimi-k3".limits]
|
||||
[providers.moonshot.models."kimi-k3".limits]
|
||||
context_window = 1048576
|
||||
# K3 accepts explicit completion budgets up to 1048576, but Fabro also uses
|
||||
# max_output as the default request budget. Match Kimi's 131072-token default.
|
||||
max_output = 131072
|
||||
|
||||
[providers.kimi.models."kimi-k3".features]
|
||||
[providers.moonshot.models."kimi-k3".features]
|
||||
tools = true
|
||||
vision = true
|
||||
reasoning = true
|
||||
|
|
@ -53,10 +58,10 @@ reasoning_effort = "always_adaptive"
|
|||
prompt_cache = true
|
||||
sampling_params = false
|
||||
|
||||
[providers.kimi.models."kimi-k3".controls]
|
||||
[providers.moonshot.models."kimi-k3".controls]
|
||||
reasoning_effort = ["low", "high", "max"]
|
||||
|
||||
[providers.kimi.models."kimi-k3".costs]
|
||||
[providers.moonshot.models."kimi-k3".costs]
|
||||
input_cost_per_mtok = 3.0
|
||||
output_cost_per_mtok = 15.0
|
||||
cache_input_cost_per_mtok = 0.3
|
||||
|
|
@ -174,9 +174,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn provider_id_is_transparent_string_in_json() {
|
||||
let id = ProviderId::new("kimi");
|
||||
let id = ProviderId::new("moonshot");
|
||||
let json = serde_json::to_string(&id).unwrap();
|
||||
assert_eq!(json, "\"kimi\"");
|
||||
assert_eq!(json, "\"moonshot\"");
|
||||
let back: ProviderId = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(back, id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ impl EnvVars {
|
|||
pub const INCEPTION_API_KEY: &'static str = "INCEPTION_API_KEY";
|
||||
pub const KIMI_API_KEY: &'static str = "KIMI_API_KEY";
|
||||
pub const MINIMAX_API_KEY: &'static str = "MINIMAX_API_KEY";
|
||||
pub const MOONSHOT_API_KEY: &'static str = "MOONSHOT_API_KEY";
|
||||
pub const MODAL_TOKEN_ID: &'static str = "MODAL_TOKEN_ID";
|
||||
pub const MODAL_TOKEN_SECRET: &'static str = "MODAL_TOKEN_SECRET";
|
||||
pub const OPENAI_API_KEY: &'static str = "OPENAI_API_KEY";
|
||||
|
|
@ -208,6 +209,7 @@ mod tests {
|
|||
EnvVars::INCEPTION_API_KEY,
|
||||
EnvVars::KIMI_API_KEY,
|
||||
EnvVars::MINIMAX_API_KEY,
|
||||
EnvVars::MOONSHOT_API_KEY,
|
||||
EnvVars::OPENAI_API_KEY,
|
||||
EnvVars::OPENAI_BASE_URL,
|
||||
EnvVars::OPENAI_ORGANIZATION,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const OPTIONAL_VAULT_SECRETS: &[&str] = &[
|
|||
EnvVars::INCEPTION_API_KEY,
|
||||
EnvVars::KIMI_API_KEY,
|
||||
EnvVars::MINIMAX_API_KEY,
|
||||
EnvVars::MOONSHOT_API_KEY,
|
||||
EnvVars::OPENAI_API_KEY,
|
||||
EnvVars::OPENROUTER_API_KEY,
|
||||
EnvVars::POOLSIDE_API_KEY,
|
||||
|
|
@ -99,6 +100,7 @@ mod tests {
|
|||
EnvVars::INCEPTION_API_KEY,
|
||||
EnvVars::KIMI_API_KEY,
|
||||
EnvVars::MINIMAX_API_KEY,
|
||||
EnvVars::MOONSHOT_API_KEY,
|
||||
EnvVars::OPENAI_API_KEY,
|
||||
EnvVars::OPENROUTER_API_KEY,
|
||||
EnvVars::POOLSIDE_API_KEY,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue