feat(llm): add Poolside Laguna models

Add Poolside as a built-in OpenAI-compatible provider and expose Laguna S 2.1 and XS 2.1 both directly and through OpenRouter. Include vault/env credential registration, secret redaction, live coverage, catalog tests, and user documentation.
This commit is contained in:
Bryan Helmkamp 2026-07-22 18:49:16 -04:00
parent 04f55666f9
commit 8f47275d84
No known key found for this signature in database
18 changed files with 500 additions and 2 deletions

View file

@ -6,6 +6,8 @@ INCEPTION_API_KEY=
KIMI_API_KEY=
MINIMAX_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY=
POOLSIDE_API_KEY=
ZAI_API_KEY=
SESSION_SECRET=

View file

@ -375,6 +375,8 @@ Standalone CLI/library usage can still opt into env-backed credential sources ex
| `ZAI_API_KEY` | Zai (GLM) |
| `MINIMAX_API_KEY` | Minimax |
| `INCEPTION_API_KEY` | Inception (Mercury) |
| `POOLSIDE_API_KEY` | Poolside (Laguna) |
| `OPENROUTER_API_KEY` | OpenRouter (when enabled) |
### Sandbox and tools

View file

@ -34,11 +34,13 @@ No single model is best at everything. Fabro lets you assign the right model to
| `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 | `kimi` | 262K | $0.60 / $3.00 | 50 tok/s |
| `laguna-s-2.1` | poolside | `laguna`, `laguna-s` | 1M | $0.10 / $0.20 | n/a |
| `laguna-xs-2.1` | poolside | `laguna-xs` | 262K | $0.10 / $0.20 | n/a |
| `glm-4.7` | zai | `glm`, `glm4` | 203K | $0.60 / $2.20 | 100 tok/s |
| `minimax-m2.5` | minimax | `minimax` | 197K | $0.30 / $1.20 | 45 tok/s |
| `mercury-2` | inception | `mercury` | 131K | $0.25 / $0.75 | 1000 tok/s |
Each provider requires its own API key. Server-backed workflows read provider credentials from the server vault (for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `GEMINI_API_KEY` set with `fabro secret set` or `fabro provider login`). Standalone SDK/CLI flows can opt into env-backed credential sources explicitly. See the [Quick Start](/getting-started/quick-start) for setup.
Each provider requires its own API key. Server-backed workflows read provider credentials from the server vault (for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, or `POOLSIDE_API_KEY` set with `fabro secret set` or `fabro provider login`). Standalone SDK/CLI flows can opt into env-backed credential sources explicitly. See the [Quick Start](/getting-started/quick-start) for setup.
Claude Fable 5 is available as an explicit model but is not the default Anthropic model. If Fable refuses a request, Fabro reports the refusal as a content-filter LLM error and applies the configured `run.model.fallbacks` chain when one is present.
@ -135,6 +137,10 @@ Provider `billing_policy` defaults from `adapter` and controls usage-cost estima
Provider fields in configuration, APIs, and model routing are provider ID strings. Built-in names like `anthropic`, `openai`, and `gemini` still work, but custom IDs like `proxy` work anywhere a provider ID is accepted.
</Note>
### Poolside
Fabro ships a built-in [Poolside](/integrations/poolside) provider for Laguna S 2.1 and Laguna XS 2.1 over Poolside's OpenAI-compatible API. Store a direct API key with `fabro provider login --provider poolside`. The same models are also available through the opt-in OpenRouter provider under vendor-namespaced IDs.
### OpenRouter
Fabro ships an [OpenRouter](/integrations/openrouter) provider definition with a curated model catalog, disabled by default. Enable it in settings and store an API key with `fabro provider login --provider openrouter`:
@ -175,6 +181,7 @@ When no model or provider is specified, Fabro checks configured provider credent
| `openai` | `gpt-5.6-sol` |
| `gemini` | `gemini-3.5-flash` |
| `kimi` | `kimi-k2.5` |
| `poolside` | `laguna-s-2.1` |
| `zai` | `glm-4.7` |
| `minimax` | `minimax-m2.5` |
| `inception` | `mercury` |

View file

@ -96,6 +96,7 @@
"integrations/daytona",
"integrations/litellm",
"integrations/bedrock",
"integrations/poolside",
"integrations/openrouter",
"integrations/slack",
"integrations/brave-search"

View file

@ -55,6 +55,7 @@ The built-in catalog curates frontier and open-weights models under vendor-names
| `google/gemini-3.1-pro-preview`, `google/gemini-3.5-flash` | |
| `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | |
| `moonshotai/kimi-k2.6`, `qwen/qwen3-coder`, `qwen/qwen3.6-flash` | |
| `poolside/laguna-s-2.1`, `poolside/laguna-xs-2.1` | Poolside Laguna coding models with native reasoning and tool use |
| `z-ai/glm-4.6`, `minimax/minimax-m2.7`, `xiaomi/mimo-v2.5-pro` | |
| `nvidia/nemotron-3-super-120b-a12b`, `mistralai/devstral-2512` | |

View file

@ -0,0 +1,139 @@
---
title: "Poolside"
description: "Run Laguna coding models through Poolside's direct API or OpenRouter"
---
[Poolside](https://poolside.ai/) trains the Laguna family of agentic coding models. Fabro includes Poolside's OpenAI-compatible API as a built-in provider, with Laguna S 2.1 and Laguna XS 2.1 available through both Poolside directly and the opt-in [OpenRouter integration](/integrations/openrouter).
## Prerequisites
- A Poolside developer API key from [platform.poolside.ai](https://platform.poolside.ai/)
- A running Fabro server
Poolside Platform currently offers free developer access for a limited time. The built-in catalog records Poolside's published paid endpoint prices so Fabro can produce durable estimated cost telemetry when the preview ends or an account uses paid access.
## Configure direct access
The direct `poolside` provider is enabled in the built-in catalog. Store its key in the target Fabro server vault:
```bash
fabro provider login --provider poolside
# For a non-default remote server:
fabro provider login --server https://your-fabro.example --provider poolside
# Or set the vault token directly:
fabro secret set POOLSIDE_API_KEY
fabro secret --server https://your-fabro.example set POOLSIDE_API_KEY
```
Standalone SDK usage outside a Fabro server can use an env-backed credential source explicitly:
```bash
export POOLSIDE_API_KEY=<api-key>
```
The provider sends OpenAI-compatible Chat Completions requests to `https://inference.poolside.ai/v1` using bearer authentication.
## Included models
| Fabro model ID | Poolside API model ID | Context | Max output | Role |
|---|---|---:|---:|---|
| `laguna-s-2.1` | `poolside/laguna-s-2.1` | 1,048,576 | 131,072 | Provider default; aliases `laguna`, `laguna-s` |
| `laguna-xs-2.1` | `poolside/laguna-xs-2.1` | 262,144 | 32,768 | Small default and connectivity probe; alias `laguna-xs` |
Both models support text input, tool calling, native reasoning, streaming, and automatic prompt-cache usage reporting. They do not support image input.
## Use direct Poolside models
```bash
fabro model list --provider poolside
fabro model test --model laguna-xs-2.1 --deep
fabro run workflow.fabro --model laguna-s-2.1
```
In workflow stylesheets:
```dot title="workflow.fabro"
digraph Example {
graph [
model_stylesheet="
* { model: laguna-s-2.1; }
.quick { model: laguna-xs-2.1; }
"
]
start [shape=Mdiamond, label="Start"]
work [label="Implement", prompt="Implement and verify the requested change."]
exit [shape=Msquare, label="Exit"]
start -> work -> exit
}
```
## Reasoning behavior
Laguna S 2.1 and XS 2.1 support two thinking modes: off and max. Max thinking is enabled by default. These releases do not expose low, medium, or high reasoning-effort levels, so Fabro intentionally does not advertise typed `reasoning_effort` controls for them.
Fabro preserves Poolside's `reasoning_content` between assistant and tool messages. This is important for interleaved thinking across multi-step tool calls.
For direct API or SDK requests, disable thinking through `provider_options.poolside`:
```json
{
"model": "laguna-s-2.1",
"provider_options": {
"poolside": {
"chat_template_kwargs": {
"enable_thinking": false
}
}
}
}
```
## Use Laguna through OpenRouter
Enable OpenRouter and configure its separate API key as described in the [OpenRouter integration](/integrations/openrouter):
```toml title="settings.toml"
[llm.providers.openrouter]
enabled = true
```
The OpenRouter routes use vendor-namespaced model IDs so they can coexist with direct Poolside routes:
```bash
fabro model test --model poolside/laguna-xs-2.1 --deep
fabro run workflow.fabro --model poolside/laguna-s-2.1
```
OpenRouter returns authoritative in-band cost telemetry. Its current paid rates per million input, output, and cache-read tokens are:
| Model | Input | Output | Cache read |
|---|---:|---:|---:|
| `poolside/laguna-s-2.1` | $0.10 | $0.20 | $0.01 |
| `poolside/laguna-xs-2.1` | $0.06 | $0.12 | $0.03 |
The XS rate reflects OpenRouter's current promotional discount and can change. Fabro prefers OpenRouter's authoritative `usage.cost` over catalog estimates.
Fabro does not include promotional `:free` OpenRouter variants in the built-in catalog because their availability and limits can change. Add one as a custom model if you explicitly want that route.
## Troubleshooting
**"No API key configured"** — Store `POOLSIDE_API_KEY` on the target server with `fabro provider login --provider poolside`. The server runtime resolves the key from its vault, not from process env.
**Unknown model** — Direct Poolside routes use `laguna-s-2.1` and `laguna-xs-2.1`. OpenRouter routes include the `poolside/` prefix.
**Reasoning effort rejected** — Laguna's hosted endpoints support thinking off or max, not low/medium/high effort. Omit `reasoning_effort`; use the provider option above only when you need to disable thinking.
## Further reading
<Columns cols={2}>
<Card title="Poolside API" icon="code" href="https://docs.poolside.ai/api/overview">
Official API endpoints, authentication, model listing, and tool-use examples.
</Card>
<Card title="Laguna models" icon="microchip" href="https://poolside.ai/models">
Current model capabilities, weights, context windows, and release information.
</Card>
</Columns>

View file

@ -373,6 +373,8 @@ For env-backed usage, `EnvCredentialSource` checks for API key environment varia
| `ZAI_API_KEY` | ZAI |
| `MINIMAX_API_KEY` | Minimax |
| `INCEPTION_API_KEY` | Inception |
| `POOLSIDE_API_KEY` | Poolside |
| `OPENROUTER_API_KEY` | OpenRouter, when enabled in settings |
The first provider registered becomes the default. Provider base URLs come from the model catalog. For vault-backed usage inside Fabro, use `fabro_auth::VaultCredentialSource` instead.

View file

@ -3510,6 +3510,7 @@ root = "{}"
assert!(ids.contains(&ProviderId::new("minimax")));
assert!(ids.contains(&ProviderId::new("inception")));
assert!(ids.contains(&ProviderId::new("venice")));
assert!(ids.contains(&ProviderId::new("poolside")));
assert!(!ids.contains(&ProviderId::new("ollama")));
assert!(!ids.contains(&ProviderId::new("litellm")));
}

View file

@ -311,6 +311,8 @@ mod tests {
("gpt-5.6-terra", "gpt-5.6-terra", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("kimi-k2.5", "kimi-k2.5", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("kimi-k3", "kimi-k3", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("laguna-s-2.1", "poolside/laguna-s-2.1", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("laguna-xs-2.1", "poolside/laguna-xs-2.1", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("mercury-2", "mercury-2", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("minimax-m2.5", "minimax-m2.5", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("venice-uncensored-1-2", "venice-uncensored-1-2", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),

View file

@ -142,7 +142,7 @@ fn build_deep_test_params(info: &Model, client: Arc<Client>) -> Option<GenerateP
.max_tool_rounds(5)
.max_tokens(1024);
if info.features.reasoning {
if info.supports_reasoning_effort() {
params = params.reasoning_effort(ReasoningEffort::High);
}
@ -240,6 +240,40 @@ mod tests {
);
}
#[test]
fn deep_test_omits_effort_for_reasoning_without_effort_controls() {
let info = test_model_with(ModelFeatures {
tools: true,
vision: false,
reasoning: true,
reasoning_effort: ReasoningEffortFeature::None,
prompt_cache: true,
sampling_params: true,
});
let params = build_deep_test_params(&info, empty_test_client())
.expect("tool-capable model should produce deep-test params");
assert_eq!(params.reasoning_effort, None);
}
#[test]
fn deep_test_uses_high_effort_when_supported() {
let info = test_model_with(ModelFeatures {
tools: true,
vision: false,
reasoning: true,
reasoning_effort: ReasoningEffortFeature::Levels,
prompt_cache: true,
sampling_params: true,
});
let params = build_deep_test_params(&info, empty_test_client())
.expect("tool-capable model should produce deep-test params");
assert_eq!(params.reasoning_effort, Some(ReasoningEffort::High));
}
#[test]
fn validate_deep_result_does_not_fail_only_for_missing_reasoning() {
let tool_results = vec![ToolResult::success("call_1", serde_json::json!(42))];

View file

@ -313,6 +313,31 @@ async fn bedrock_openai_frontier_complete() {
assert_eq!(response.provider, "bedrock-openai");
}
#[fabro_macros::e2e_test(live("POOLSIDE_API_KEY"))]
async fn poolside_laguna_xs_deep_tool_round_trip() {
let api_key = std::env::var(EnvVars::POOLSIDE_API_KEY).expect("POOLSIDE_API_KEY must be set");
let provider = ProviderId::new("poolside");
let catalog = Arc::new(Catalog::from_builtin().expect("built-in catalog should be valid"));
let credential = ApiCredential::from_api_key(provider, api_key, &catalog)
.expect("Poolside credential should resolve from the catalog");
let client = Arc::new(
Client::from_credentials(vec![credential], Arc::clone(&catalog))
.await
.expect("Poolside client should build from the catalog"),
);
let model = catalog
.get("laguna-xs-2.1")
.expect("direct Poolside Laguna XS should be present");
let outcome = run_model_test(model, ModelTestMode::Deep, client).await;
assert_eq!(
outcome.status,
ModelTestStatus::Ok,
"direct Poolside Laguna XS deep test failed: {:?}",
outcome.error_message
);
}
#[fabro_macros::e2e_test(live("OPENROUTER_API_KEY"))]
async fn openrouter_complete() {
let api_key =
@ -336,6 +361,29 @@ async fn openrouter_complete() {
assert_eq!(response.cost_source, Some(CostSource::Authoritative));
}
#[fabro_macros::e2e_test(live("OPENROUTER_API_KEY"))]
async fn openrouter_poolside_laguna_complete() {
let api_key =
std::env::var(EnvVars::OPENROUTER_API_KEY).expect("OPENROUTER_API_KEY must be set");
let adapter = OpenAiCompatibleAdapter::new(api_key, "https://openrouter.ai/api/v1")
.with_name("openrouter");
let request = make_request("poolside/laguna-xs-2.1");
let response = adapter.complete(&request).await.unwrap();
assert!(
!response.text().is_empty(),
"response text should not be empty"
);
assert!(response.usage.input_tokens > 0);
assert!(response.usage.output_tokens > 0);
assert_eq!(response.provider, "openrouter");
assert!(
response.cost_usd.is_some(),
"OpenRouter responses should carry an authoritative usage.cost",
);
assert_eq!(response.cost_source, Some(CostSource::Authoritative));
}
#[fabro_macros::e2e_test(live("OPENROUTER_API_KEY"))]
async fn openrouter_kimi_k3_deep_tool_round_trip() {
let api_key =

View file

@ -2005,6 +2005,85 @@ enabled = true
);
}
#[test]
fn builtin_poolside_provider_routes_current_laguna_models() {
let poolside = ProviderId::new("poolside");
let catalog = Catalog::builtin();
let provider = catalog
.provider(&poolside)
.expect("Poolside provider should be active");
assert_eq!(provider.adapter, AdapterKind::OpenAiCompatible);
assert_eq!(provider.codec, CodecKind::OpenAiCompatible);
assert_eq!(provider.billing_policy, BillingPolicy::OpenAi);
assert_eq!(
provider.base_url.as_deref(),
Some("https://inference.poolside.ai/v1")
);
assert_eq!(provider.priority, 65);
assert_eq!(provider.auth.as_ref().unwrap().credentials, vec![
CredentialRef::Env("POOLSIDE_API_KEY".to_string()),
CredentialRef::Vault("POOLSIDE_API_KEY".to_string()),
]);
assert_eq!(
catalog
.default_for_provider(&poolside)
.map(|model| model.id.as_str()),
Some("laguna-s-2.1")
);
assert_eq!(
catalog
.small_default_for_provider(&poolside)
.map(|model| model.id.as_str()),
Some("laguna-xs-2.1")
);
assert_eq!(
catalog
.probe_for_provider(&poolside)
.map(|model| model.id.as_str()),
Some("laguna-xs-2.1")
);
let s = catalog.get("laguna").expect("Laguna alias should resolve");
assert_eq!(s.id, "laguna-s-2.1");
assert_eq!(s.limits.context_window, 1_048_576);
assert_eq!(s.limits.max_output, Some(131_072));
assert!(s.features.tools);
assert!(s.features.reasoning);
assert!(s.features.prompt_cache);
assert!(s.features.sampling_params);
assert!(!s.features.vision);
assert!(!s.supports_reasoning_effort());
assert_eq!(s.costs.input_cost_per_mtok, Some(0.10));
assert_eq!(s.costs.output_cost_per_mtok, Some(0.20));
assert_eq!(s.costs.cache_input_cost_per_mtok, Some(0.01));
assert_eq!(
catalog.model_settings(&s.id).unwrap().api_id,
"poolside/laguna-s-2.1"
);
let xs = catalog
.get("laguna-xs")
.expect("Laguna XS alias should resolve");
assert_eq!(xs.id, "laguna-xs-2.1");
assert_eq!(xs.limits.context_window, 262_144);
assert_eq!(xs.limits.max_output, Some(32_768));
assert!(xs.features.tools);
assert!(xs.features.reasoning);
assert!(xs.features.prompt_cache);
assert!(xs.features.sampling_params);
assert!(!xs.features.vision);
assert!(!xs.supports_reasoning_effort());
assert_eq!(xs.costs.input_cost_per_mtok, Some(0.10));
assert_eq!(xs.costs.output_cost_per_mtok, Some(0.20));
assert_eq!(xs.costs.cache_input_cost_per_mtok, Some(0.05));
assert_eq!(
catalog.model_settings(&xs.id).unwrap().api_id,
"poolside/laguna-xs-2.1"
);
}
#[test]
fn builtin_openrouter_provider_is_opt_in() {
let openrouter = ProviderId::new("openrouter");
@ -2121,6 +2200,58 @@ enabled = true
]);
}
#[test]
fn builtin_openrouter_includes_poolside_laguna_when_enabled() {
let catalog = Catalog::from_builtin_with_overrides(&minimal_settings(
r"
[providers.openrouter]
enabled = true
",
))
.expect("enabled OpenRouter override should build from the built-in provider settings");
let expected = [
(
"poolside/laguna-s-2.1",
1_048_576,
131_072,
0.10,
0.20,
0.01,
),
("poolside/laguna-xs-2.1", 262_144, 32_768, 0.06, 0.12, 0.03),
];
for (id, context, max_output, input, output, cache_read) in expected {
let model = catalog
.get(id)
.unwrap_or_else(|| panic!("OpenRouter model '{id}' should be present"));
assert_eq!(model.provider, ProviderId::new("openrouter"), "{id}");
assert_eq!(model.family, "laguna-2", "{id}");
assert_eq!(model.limits.context_window, context, "{id}");
assert_eq!(model.limits.max_output, Some(max_output), "{id}");
assert!(model.features.tools, "{id}");
assert!(model.features.reasoning, "{id}");
assert!(model.features.prompt_cache, "{id}");
assert!(model.features.sampling_params, "{id}");
assert!(!model.features.vision, "{id}");
assert!(!model.supports_reasoning_effort(), "{id}");
assert_eq!(model.costs.input_cost_per_mtok, Some(input), "{id}");
assert_eq!(model.costs.output_cost_per_mtok, Some(output), "{id}");
assert_eq!(
model.costs.cache_input_cost_per_mtok,
Some(cache_read),
"{id}"
);
let settings = catalog
.model_settings(id)
.unwrap_or_else(|| panic!("OpenRouter settings for '{id}' should be present"));
assert_eq!(settings.api_id, id, "{id}");
assert!(settings.controls.reasoning_effort.is_empty(), "{id}");
}
}
#[test]
fn builtin_ollama_provider_is_opt_in() {
let ollama = ProviderId::new("ollama");

View file

@ -304,6 +304,52 @@ input_cost_per_mtok = 3.0
output_cost_per_mtok = 15.0
cache_input_cost_per_mtok = 0.3
[models."poolside/laguna-s-2.1"]
provider = "openrouter"
api_id = "poolside/laguna-s-2.1"
display_name = "Laguna S 2.1 (via OpenRouter)"
family = "laguna-2"
[models."poolside/laguna-s-2.1".limits]
context_window = 1048576
max_output = 131072
[models."poolside/laguna-s-2.1".features]
tools = true
vision = false
reasoning = true
prompt_cache = true
sampling_params = true
[models."poolside/laguna-s-2.1".costs]
input_cost_per_mtok = 0.10
output_cost_per_mtok = 0.20
cache_input_cost_per_mtok = 0.01
[models."poolside/laguna-xs-2.1"]
provider = "openrouter"
api_id = "poolside/laguna-xs-2.1"
display_name = "Laguna XS 2.1 (via OpenRouter)"
family = "laguna-2"
[models."poolside/laguna-xs-2.1".limits]
context_window = 262144
max_output = 32768
[models."poolside/laguna-xs-2.1".features]
tools = true
vision = false
reasoning = true
prompt_cache = true
sampling_params = true
# Current promotional rate. OpenRouter's authoritative in-band usage.cost
# supersedes this estimate on completed responses.
[models."poolside/laguna-xs-2.1".costs]
input_cost_per_mtok = 0.06
output_cost_per_mtok = 0.12
cache_input_cost_per_mtok = 0.03
[models."qwen/qwen3-coder"]
provider = "openrouter"
api_id = "qwen/qwen3-coder"

View file

@ -0,0 +1,63 @@
[providers.poolside]
display_name = "Poolside"
adapter = "openai_compatible"
api_key_url = "https://platform.poolside.ai"
base_url = "https://inference.poolside.ai/v1"
priority = 65
[providers.poolside.auth]
credentials = ["env:POOLSIDE_API_KEY", "vault:POOLSIDE_API_KEY"]
[models."laguna-s-2.1"]
provider = "poolside"
api_id = "poolside/laguna-s-2.1"
display_name = "Laguna S 2.1"
family = "laguna-2"
default = true
aliases = ["laguna", "laguna-s"]
[models."laguna-s-2.1".limits]
context_window = 1048576
max_output = 131072
[models."laguna-s-2.1".features]
tools = true
vision = false
reasoning = true
prompt_cache = true
sampling_params = true
# Poolside Platform is free for a limited preview period. Keep the published
# paid hosted rate as Fabro's durable estimate for paid access and post-preview
# usage.
[models."laguna-s-2.1".costs]
input_cost_per_mtok = 0.10
output_cost_per_mtok = 0.20
cache_input_cost_per_mtok = 0.01
[models."laguna-xs-2.1"]
provider = "poolside"
api_id = "poolside/laguna-xs-2.1"
display_name = "Laguna XS 2.1"
family = "laguna-2"
small_default = true
probe = true
aliases = ["laguna-xs"]
[models."laguna-xs-2.1".limits]
context_window = 262144
max_output = 32768
[models."laguna-xs-2.1".features]
tools = true
vision = false
reasoning = true
prompt_cache = true
sampling_params = true
# Poolside Platform is free for a limited preview period. These are Poolside's
# published paid endpoint rates.
[models."laguna-xs-2.1".costs]
input_cost_per_mtok = 0.10
output_cost_per_mtok = 0.20
cache_input_cost_per_mtok = 0.05

View file

@ -2567,6 +2567,13 @@ regex = '''\b(sk-or-v1-[0-9a-f]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)'''
entropy = 3
keywords = ["sk-or-v1-"]
[[rules]]
id = "poolside-api-key"
description = "Found a Poolside API Key, posing a risk of unauthorized model access and billing."
regex = '''\b(sky_[A-Za-z0-9]{8}\.[A-Za-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)'''
entropy = 3
keywords = ["sky_"]
[[rules]]
id = "openshift-user-token"
description = "Found an OpenShift user token, potentially compromising an OpenShift/Kubernetes cluster."

View file

@ -272,6 +272,14 @@ mod tests {
assert_eq!(regions.len(), 1, "expected 1 region, got {regions:?}");
}
#[test]
fn detects_poolside_api_key() {
let input = format!("sky_Ab12Cd34.{}", "Ef56Gh78".repeat(4));
let regions = find_gitleaks_regions(&input);
assert_eq!(regions.len(), 1, "expected 1 region, got {regions:?}");
assert_eq!(&input[regions[0].start..regions[0].end], input);
}
#[test]
fn detects_private_key_block() {
let input =

View file

@ -62,6 +62,7 @@ impl EnvVars {
pub const OPENAI_ORG_ID: &'static str = "OPENAI_ORG_ID";
pub const OPENAI_PROJECT_ID: &'static str = "OPENAI_PROJECT_ID";
pub const OPENROUTER_API_KEY: &'static str = "OPENROUTER_API_KEY";
pub const POOLSIDE_API_KEY: &'static str = "POOLSIDE_API_KEY";
pub const ZAI_API_KEY: &'static str = "ZAI_API_KEY";
// GitHub, OAuth, and Slack
@ -207,6 +208,7 @@ mod tests {
EnvVars::OPENAI_ORG_ID,
EnvVars::OPENAI_PROJECT_ID,
EnvVars::OPENROUTER_API_KEY,
EnvVars::POOLSIDE_API_KEY,
EnvVars::ZAI_API_KEY,
EnvVars::GH_TOKEN,
EnvVars::GITHUB_APP_CLIENT_SECRET,

View file

@ -31,6 +31,7 @@ const OPTIONAL_VAULT_SECRETS: &[&str] = &[
EnvVars::MINIMAX_API_KEY,
EnvVars::OPENAI_API_KEY,
EnvVars::OPENROUTER_API_KEY,
EnvVars::POOLSIDE_API_KEY,
EnvVars::ZAI_API_KEY,
EnvVars::DAYTONA_API_KEY,
];
@ -96,6 +97,7 @@ mod tests {
EnvVars::MINIMAX_API_KEY,
EnvVars::OPENAI_API_KEY,
EnvVars::OPENROUTER_API_KEY,
EnvVars::POOLSIDE_API_KEY,
EnvVars::ZAI_API_KEY,
] {
assert_eq!(