From 8f47275d84e50744389796178b17dc06eaa8fac0 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Wed, 22 Jul 2026 18:49:16 -0400 Subject: [PATCH] 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. --- .env.example | 2 + .../administration/server-configuration.mdx | 2 + docs/public/core-concepts/models.mdx | 9 +- docs/public/docs.json | 1 + docs/public/integrations/openrouter.mdx | 1 + docs/public/integrations/poolside.mdx | 139 ++++++++++++++++++ docs/public/reference/sdk.mdx | 2 + lib/crates/fabro-cli/src/commands/install.rs | 1 + lib/crates/fabro-llm/src/adapter_registry.rs | 2 + lib/crates/fabro-llm/src/model_test.rs | 36 ++++- lib/crates/fabro-llm/tests/integration.rs | 48 ++++++ lib/crates/fabro-model/src/catalog.rs | 131 +++++++++++++++++ .../src/catalog/providers/openrouter.toml | 46 ++++++ .../src/catalog/providers/poolside.toml | 63 ++++++++ lib/crates/fabro-redact/data/gitleaks.toml | 7 + lib/crates/fabro-redact/src/gitleaks.rs | 8 + lib/crates/fabro-static/src/env_vars.rs | 2 + .../fabro-static/src/secret_registry.rs | 2 + 18 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 docs/public/integrations/poolside.mdx create mode 100644 lib/crates/fabro-model/src/catalog/providers/poolside.toml diff --git a/.env.example b/.env.example index 98eb4ec51..621408f36 100644 --- a/.env.example +++ b/.env.example @@ -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= diff --git a/docs/public/administration/server-configuration.mdx b/docs/public/administration/server-configuration.mdx index 4a2f7d69e..7b74cbaee 100644 --- a/docs/public/administration/server-configuration.mdx +++ b/docs/public/administration/server-configuration.mdx @@ -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 diff --git a/docs/public/core-concepts/models.mdx b/docs/public/core-concepts/models.mdx index 604cac913..7bda6217b 100644 --- a/docs/public/core-concepts/models.mdx +++ b/docs/public/core-concepts/models.mdx @@ -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. +### 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` | diff --git a/docs/public/docs.json b/docs/public/docs.json index 5dc4a9f8d..9fccbc642 100644 --- a/docs/public/docs.json +++ b/docs/public/docs.json @@ -96,6 +96,7 @@ "integrations/daytona", "integrations/litellm", "integrations/bedrock", + "integrations/poolside", "integrations/openrouter", "integrations/slack", "integrations/brave-search" diff --git a/docs/public/integrations/openrouter.mdx b/docs/public/integrations/openrouter.mdx index 7e490955a..b1b6f2534 100644 --- a/docs/public/integrations/openrouter.mdx +++ b/docs/public/integrations/openrouter.mdx @@ -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` | | diff --git a/docs/public/integrations/poolside.mdx b/docs/public/integrations/poolside.mdx new file mode 100644 index 000000000..a1923dc83 --- /dev/null +++ b/docs/public/integrations/poolside.mdx @@ -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= +``` + +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 + + + + Official API endpoints, authentication, model listing, and tool-use examples. + + + Current model capabilities, weights, context windows, and release information. + + diff --git a/docs/public/reference/sdk.mdx b/docs/public/reference/sdk.mdx index 6208145d9..4332f3adc 100644 --- a/docs/public/reference/sdk.mdx +++ b/docs/public/reference/sdk.mdx @@ -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. diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index e12cf04c2..a0d67ec1f 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -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"))); } diff --git a/lib/crates/fabro-llm/src/adapter_registry.rs b/lib/crates/fabro-llm/src/adapter_registry.rs index 544897019..101d03c69 100644 --- a/lib/crates/fabro-llm/src/adapter_registry.rs +++ b/lib/crates/fabro-llm/src/adapter_registry.rs @@ -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), diff --git a/lib/crates/fabro-llm/src/model_test.rs b/lib/crates/fabro-llm/src/model_test.rs index 42209fc91..47bca353c 100644 --- a/lib/crates/fabro-llm/src/model_test.rs +++ b/lib/crates/fabro-llm/src/model_test.rs @@ -142,7 +142,7 @@ fn build_deep_test_params(info: &Model, client: Arc) -> Option 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 = diff --git a/lib/crates/fabro-model/src/catalog.rs b/lib/crates/fabro-model/src/catalog.rs index 811978968..ad7745dbb 100644 --- a/lib/crates/fabro-model/src/catalog.rs +++ b/lib/crates/fabro-model/src/catalog.rs @@ -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"); diff --git a/lib/crates/fabro-model/src/catalog/providers/openrouter.toml b/lib/crates/fabro-model/src/catalog/providers/openrouter.toml index 0c3393a22..061a0abb4 100644 --- a/lib/crates/fabro-model/src/catalog/providers/openrouter.toml +++ b/lib/crates/fabro-model/src/catalog/providers/openrouter.toml @@ -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" diff --git a/lib/crates/fabro-model/src/catalog/providers/poolside.toml b/lib/crates/fabro-model/src/catalog/providers/poolside.toml new file mode 100644 index 000000000..8fd1b6855 --- /dev/null +++ b/lib/crates/fabro-model/src/catalog/providers/poolside.toml @@ -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 diff --git a/lib/crates/fabro-redact/data/gitleaks.toml b/lib/crates/fabro-redact/data/gitleaks.toml index c61cdec2c..46b70d677 100644 --- a/lib/crates/fabro-redact/data/gitleaks.toml +++ b/lib/crates/fabro-redact/data/gitleaks.toml @@ -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." diff --git a/lib/crates/fabro-redact/src/gitleaks.rs b/lib/crates/fabro-redact/src/gitleaks.rs index 5d8115fd2..39c6fda88 100644 --- a/lib/crates/fabro-redact/src/gitleaks.rs +++ b/lib/crates/fabro-redact/src/gitleaks.rs @@ -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 = diff --git a/lib/crates/fabro-static/src/env_vars.rs b/lib/crates/fabro-static/src/env_vars.rs index f25b48f57..4ed7585ca 100644 --- a/lib/crates/fabro-static/src/env_vars.rs +++ b/lib/crates/fabro-static/src/env_vars.rs @@ -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, diff --git a/lib/crates/fabro-static/src/secret_registry.rs b/lib/crates/fabro-static/src/secret_registry.rs index 6166a1d42..ad5c20e3f 100644 --- a/lib/crates/fabro-static/src/secret_registry.rs +++ b/lib/crates/fabro-static/src/secret_registry.rs @@ -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!(