Commit graph

53 commits

Author SHA1 Message Date
Bryan Helmkamp
120f0fa80b
Merge pull request #725 from fabro-sh/codex/fireworks-kimi-k3-fast
Add Fireworks Kimi K3 and Kimi K3 Fast support
2026-08-04 14:03:23 -04:00
Bryan Helmkamp
2b29dddb33
feat(models): add Fireworks Kimi K3 Fast 2026-08-04 13:55:48 -04:00
Bryan Helmkamp
69bb95c0cc
feat(models): add Qwen3.8 Max to OpenRouter 2026-08-03 16:01:19 -04:00
Bryan Helmkamp
cc590f6f97
Merge remote-tracking branch 'origin/main' into fix/modal-provider-catalog
Resolve the model catalog table conflict in docs/public/core-concepts/models.mdx
by keeping both changes: this branch's `kimi` -> `moonshot` provider rename for
the Kimi rows, and main's new DeepSeek V4 rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:32:19 -04:00
Release Repro
799fac1d3b
style(models): group DeepSeek price digits 2026-07-31 13:17:44 -04:00
Release Repro
40391ac3ac
fix(models): use exact DeepSeek context window 2026-07-31 13:17:08 -04:00
Release Repro
1016f995c4
chore(models): pin DeepSeek agent profile 2026-07-31 13:16:37 -04:00
Release Repro
76dae568f5
feat(reasoning): expose DeepSeek effort controls 2026-07-31 13:14:11 -04:00
Release Repro
f613821bfb
feat(llm): add direct DeepSeek provider 2026-07-31 13:10:08 -04:00
Release Repro
8864e0f0cf
feat(models): refresh DeepSeek V4 Flash metadata 2026-07-31 13:02:58 -04:00
Release Repro
0afd3a43b6
feat(models): add portable DeepSeek aliases to Fireworks 2026-07-31 12:59:40 -04:00
Release Repro
8bae35398f
fix(models): point DeepSeek aliases to V4 Flash 2026-07-31 12:59:00 -04:00
Bryan Helmkamp
fd55ff03ad
fix(model): prefer MOONSHOT_API_KEY
Keep KIMI_API_KEY as a legacy fallback for env and vault credentials.
2026-07-31 09:14:58 -04:00
Bryan Helmkamp
24f9ac6bd1
refactor(model): rename Kimi provider to Moonshot 2026-07-31 09:00:52 -04:00
Bryan Helmkamp
a2b64dea2a
fix(model): prioritize Modal over Kimi 2026-07-31 08:44:43 -04:00
Bryan Helmkamp
e6eb36f852
refactor: simplify model-keyed fallback internals
Consolidation pass over the fallback feature, no intended behavior
changes beyond noted validation and event-shape cleanups:

- Unify the two parallel notice types: FallbackPlanNotice is gone;
  ModelFallbackNotice now owns the runtime NoNearbyReasoningLevel case
  and the shared ChainEmpty wording. Notices emit through a new
  Emitter::notice_scoped with their own level, and each distinct notice
  is emitted once per run instead of on every LLM call.
- Move canonical_model_id onto Catalog so chain keys are written and
  read through one function; reject provider-qualified fallback keys,
  which could never match at dispatch and were silently dead config.
- Type FallbackTarget as ProviderId/ModelId, removing repeated
  ProviderId::new re-wrapping at every use site.
- Derive FallbackPlan's current route from a position index instead of
  storing current/requested_controls copies; advance() no longer has
  unreachable None branches.
- Bundle the agent invocation's live state (session, bridge, lease,
  forwarder, accounting) into LiveAgentInvocation; failover_agent_session
  drops from 21 parameters to 7 and the six copies of the
  abort/discard/classify teardown collapse into two methods.
- Share one route_request builder between one_shot and its failover
  loop; complete_one_shot_request takes the request by value instead of
  deep-cloning the message payload per call.
- Event::Failover carries FailoverProps directly; the props' original
  route and attempt fields are now required, and reasoning efforts are
  typed ReasoningEffort instead of strings.
- Reuse RunModelSettings/RunModelControls in fabro-api via
  with_replacement, add the missing controls property to the OpenAPI
  schema, regenerate the TS client, and add the type-identity/JSON
  parity test.
- Smaller cleanups: ReasoningEffort::closest_supported uses enum
  discriminants; ModelFallbackPolicy gains len(); resolve_model_fallbacks
  takes a provider slice; duplicate-target filtering lives only in the
  resolver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 12:51:36 -04:00
Bryan Helmkamp
ba82656656
feat: add model-keyed fallback policies 2026-07-30 12:02:41 -04:00
Bryan Helmkamp
8a597ca264
fix(workflow): report the real cause when a fallback cannot resolve
Addresses review feedback on the fallback notice work.

A provider-only fallback such as `openrouter` needs the primary model's
catalog entry to find the closest capability match. When the primary is
itself a passthrough selector there is no entry, so every provider-only
candidate was skipped with "provider `X` has no compatible model" even
when that provider had plenty. Adds a `PrimaryNotInCatalog` notice that
names the missing primary instead of blaming the provider.

Also from review:

- `code()` was a wildcard fallthrough, which docs/internal/events-strategy.md
  forbids for new variants. Now exhaustive.
- `NoConfiguredOffering` discarded the `providers` list that
  `NoEligibleOffering` hands it. The notice now names the providers that do
  offer the model.
- `ModelFallbackNotice::reference` was a rendered `String`; it is now the
  `ModelRef` it came from, which also drops the per-candidate double
  allocation the previous refactor introduced.
- `ResolvedStartLlm` unpacked and repacked `ResolvedFallbackChain`
  field-for-field; it now holds it directly.
- Added `FallbackTarget: Display` as `provider:model`, replacing two
  hand-written `"{}:{}"` format strings.
- `Catalog::select` still inlined the `require_provider` body.
- Emission moved to `ModelFallbackNotice::emit_all`, covered by a new test
  proving notices reach the event stream with the right level, code, and
  message. Nothing tested that hand-off before.

Documented in `resolve_fallback_chain` why an unknown provider stays a hard
error while an unconfigured one is skipped, and that an unqualified unknown
selector pins to the primary's provider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:37:51 -04:00
Bryan Helmkamp
48fd09aaab
refactor(workflow): simplify fallback chain resolution
Follow-up cleanup on the portable fallback chain work.

- Add `Catalog::require_provider` and `Catalog::provider_id`, replacing the
  `catalog_provider_id` free function in `start.rs` and two copies of the same
  `provider(..).ok_or_else(UnknownProvider)` block inside the catalog.
- Add `FallbackTarget::new` and use it for the six struct literals that each
  stringified a provider and model by hand.
- Extract per-candidate resolution into `resolve_fallback_candidate`, returning
  a `FallbackCandidate` that is either a target or the skip reason. This flattens
  `resolve_fallback_chain` from four levels of nesting to one loop and splits the
  qualified/unqualified model arms into separate match patterns.
- Drop the `seen` HashSet and its per-candidate key clones in favor of a
  `contains` check on the chain being built; fallback chains hold a handful of
  entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:23:25 -04:00
Bryan Helmkamp
fa85bc42a2
Merge pull request #643 from fabro-sh/feat/claude-5-profile
feat(agent): add Claude 5 profile
2026-07-28 18:25:21 -04:00
Bryan Helmkamp
b611d946a6
Merge pull request #673 from fabro-sh/feat/modal-llm-provider
Add Modal as an LLM provider
2026-07-28 18:14:43 -04:00
Bryan Helmkamp
977628000b
Merge pull request #668 from fabro-sh/feat/provider-qualified-fallbacks
Support provider-qualified model fallback selectors
2026-07-28 18:13:43 -04:00
Bryan Helmkamp
8e4251bb07
refactor(llm): simplify the Modal provider tests and fixtures
Reduce duplication and over-specification introduced with the Modal
provider, without changing shipped behavior.

- Extract enabled_provider_catalog and assert_deep_tool_round_trip in
  the fabro-llm integration tests. The Poolside, Fireworks, OpenRouter,
  and Modal deep round trips were four near-identical copies.
- Add ApiCredential::with_extra_headers for providers that authenticate
  with request headers instead of an API key.
- Replace the unreachable require_env guards in the Modal e2e test with
  the std::env::var form used by every sibling test, and register
  MODAL_TOKEN_ID and MODAL_TOKEN_SECRET in EnvVars.
- Collapse modal_requires_both_vault_proxy_tokens to a single case. The
  loop rebuilt the whole built-in catalog per iteration.
- Drop tautological and over-specified assertions: the api_key_url doc
  URL, the forced default/probe lookups on a single-model provider, and
  the get_on_provider loop that could not fail.
- Inline the single-use modal_env_catalog fixture and note why it
  overrides the shipped secrets templates.
- Sort the MODAL_* keys in .env.example, and record in modal.toml why
  api_id keeps the Hugging Face capitalization.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:06:07 -04:00
Bryan Helmkamp
e91343bbeb
refactor: address run title review findings 2026-07-28 17:14:03 -04:00
Bryan Helmkamp
8771c971d7
Add Modal LLM provider 2026-07-28 16:02:57 -04:00
Bryan Helmkamp
b2942519d5
Merge the duplicate selector catalog tests
catalog_from_settings_rejects_duplicate_provider_api_ids was a copy of
catalog_from_settings_rejects_duplicate_model_aliases with the alias
declaration swapped for an api_id. Fold them into one table-driven test
so the shared invariant is stated once: canonical IDs, aliases, and API
IDs occupy a single identifier namespace per provider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:48:22 -04:00
Bryan Helmkamp
8a41d4665f
Simplify model reference parsing and catalog indexing
Follow-up cleanup on the provider-qualified selector work.

- build_model_indexes now takes the paired (Model, CatalogModelSettings)
  slice it is built from, instead of a separate settings map. This drops
  a per-model map lookup with two cloned key components and removes the
  expect() panic path for an invariant the caller already guarantees.
- get_on_provider expresses the exact-then-legacy lookup as one closure
  applied twice, rather than a nested then/flatten chain.
- ModelRef::from_str selects the separator first and then checks both
  sides once, so the empty-side check is no longer duplicated across two
  branches and the slash split no longer allocates a Vec.
- Shorten the TooManySlashes message to the action the user should take.
- Merge the two near-identical fallback chain tests into one that runs
  both qualified selector forms through the same assertion.
- The fallbacks splice test now asserts through the existing Serialize
  impl instead of hand-rolling the ModelRefOrSplice rendering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:42:03 -04:00
Bryan Helmkamp
33b94d850e
Support provider-qualified fallback selectors 2026-07-28 11:48:20 -04:00
Bryan Helmkamp
0864375bcd
Skip providers with no small model when picking a small default
`small_default_for_provider` fell back to the provider's normal default
when no model was marked `small_default`. That turned "give me the small
utility model" into "give me the flagship" for any provider without one.

Run title generation asks for the small default, then gives it a 64-token
budget and a 10s timeout. On a server where kimi is the highest-priority
configured provider, that resolved to kimi-k3 — an always-reasoning model
that burned 161 reasoning tokens before emitting anything. The structured
output never completed, `generate_object` returned NoObjectGenerated, and
the caller silently kept the deterministic title.

Return `None` instead, and have `small_default_for_configured_ids` move on
to the next configured provider. The ordinary default is used only when no
configured provider marks a small model, and it now comes from the
configured set rather than the global catalog default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 17:06:17 -04:00
Bryan Helmkamp
4167fcd39b
feat(agent): add Claude 5 profile 2026-07-25 13:15:57 -04:00
Bryan Helmkamp
d931ae6105
fix(agent): simplify GPT-5.6 tool routing 2026-07-25 11:49:40 -04:00
Bryan Helmkamp
c7ad387d3e
feat(agent): add gpt56 profile for GPT-5.6 Sol, Terra, and Luna
Codex drives the GPT-5.6 models with a much narrower tool set than the
other OpenAI models: a shell, `apply_patch`, and `update_plan`. It has no
file-read, file-write, grep, glob, or fetch tool at all -- reading and
searching go through the shell, and every write goes through
`apply_patch`. Offering 5.6 fabro's extra tools advertises affordances its
instructions never mention, so this adds a profile that registers only
what Codex does.

The profile is selected per model via `agent_profile = "gpt56"` on the six
5.6 rows (three each on `openai` and `openrouter`), following the existing
Kimi-over-a-gateway pattern. Every other model on those providers keeps
its provider default, with no code branch and no version sniffing.

- `ToolVocabulary::Codex` renames `shell` to `shell_command`; a strum
  alias keeps `from_any_name` resolving it to `NativeTool::Shell`, so
  permissions, categories, and telemetry still key on the canonical name.
- `shell_command` gains `workdir`, passed to the `cwd` argument
  `execute_shell_command` already accepted, with Codex's "always set
  `workdir`, do not `cd`" guidance.
- `prompts/gpt56.md.j2` is adapted from Codex's 5.6 `base_instructions`,
  which are byte-identical across Sol, Terra, and Luna. A header comment
  records provenance and the departures fabro's harness forces.

This is an alignment-only pass: it matches Codex's tool contract while
keeping direct tool calls. Codex actually drives 5.6 in code mode, with a
single `exec` tool taking JavaScript and every other tool reached through
a `tools` object inside a V8 isolate. That is deliberately out of scope.

Luna's `multi_agent_version: v1` (vs v2 on Sol and Terra) is also out of
scope. It only changes the sub-agent tool set, which fabro registers from
the caller rather than the profile, and fabro's current set matches
neither version exactly.

Two server cancel-timing tests are adjusted. `gpt-5.6-sol` is the
`openai` provider's default model, so runs that name no model now build a
3-tool profile instead of an 8-tool one and reach their first stage
sooner. `full_http_lifecycle_cancel` asserted `status.kind == "blocked"`
at the instant of cancel, which the worker is free to change the moment it
is signaled; it now accepts either live state, matching the tolerance its
own comment already documents for `pending_control`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 10:42:32 -04:00
Bryan Helmkamp
4666f51d98
feat(model): add Claude Opus 5 to OpenRouter 2026-07-24 22:34:02 -04:00
Bryan Helmkamp
c914fbbbe0
Merge pull request #630 from fabro-sh/fix/compaction-reasoning-token-budget
fix(agent): budget compaction summaries for reasoning models
2026-07-24 22:26:12 -04:00
Bryan Helmkamp
eddee10b35
fix(agent): harden Kimi profile tool contracts 2026-07-24 22:05:48 -04:00
Release Repro
5d0617f547
fix(agent): harden compaction reasoning budgets
Model default reasoning explicitly at the provider-route level so always-reasoning endpoints without effort controls receive summary headroom. Cap all summary requests at model output limits and bound retained visible summaries to the original allowance. Reuse builtin catalog fixtures and named budget constants in tests, and document the new model setting.
2026-07-24 21:58:23 -04:00
Bryan Helmkamp
c08e5c5490
feat(agent): add a Kimi agent profile for Moonshot and gateway routes
Kimi models ran on the OpenAI profile, which exists to look like Codex. Give
them their own profile derived from Kimi Code's system prompt.

Routing is per model, not per provider, because Kimi models are served both
directly by Moonshot and through gateways. `kimi` sets agent_profile at the
provider level; the Kimi model rows on `openrouter` set it individually, so a
gateway route behaves like the direct one while other OpenRouter models keep
the provider's OpenAI profile.

The profile targets a measured failure. Across two observed K3 implementation
stages, 32 of 35 tool failures were the same thing: writes to files the model
had not read, rejected by the workspace read-before-write guard, or
`old_string` values reconstructed from memory rather than taken from a read.
Kimi Code drills this rule in its own tool descriptions, so the profile does
too -- `edit_file` and `write_file` carry Kimi-specific descriptions naming the
guard and the failure text the model will see, alongside a "Reading Before
Writing" section in the system prompt. Profiles own their tool registries, so
this re-describes the tools for Kimi only; every other profile is untouched and
the executors and JSON schemas are shared unchanged.

Tool names stay fabro's existing snake_case. Whether Kimi Code's PascalCase
vocabulary measurably helps is untested, and renaming would also mean updating
the name-keyed categories in tool_permissions.rs, where an unknown tool falls
back to Shell. That is a separate change to make on evidence.

The prompt is a subtractive port: capabilities fabro does not have -- plan
mode, background tasks, cron, subagent swarms, the cwd tree listing -- are
dropped rather than promised. The shell timeout default matches Kimi Code's 60s
and memory discovery reads AGENTS.md, which is the only instruction file Kimi
Code looks for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 19:50:58 -04:00
Release Repro
0b58d087ee
feat(model): add Claude Opus 5 2026-07-24 13:43:08 -04:00
Bryan Helmkamp
8394eb2723
Merge pull request #619 from fabro-sh/feat/fireworks-provider
feat(llm): add Fireworks AI as an opt-in provider
2026-07-24 09:46:03 -04:00
Bryan Helmkamp
ad9b3810c2
docs(fireworks): fix CLI examples flagged in review
- The catalog comment showed `fabro provider login fireworks`, but
  `--provider` is a required flag: `fabro provider login --provider fireworks`.
- The remote-server `fabro model test` example omitted `--provider fireworks`,
  which could resolve the slug against a different provider.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:37:18 -04:00
Bryan Helmkamp
3beaddc224
Merge remote-tracking branch 'origin/main' into feat/expose-model-controls
# Conflicts:
#	lib/foundation/fabro-api/src/lib.rs
2026-07-24 09:28:01 -04:00
Bryan Helmkamp
3970c9f545
Restore serde defaults on Model.controls for older-server compatibility
Copilot review flagged that dropping #[serde(default)] makes newer
clients hard-fail against servers that predate the controls field.
The late-added Model fields (default, small_default, configured) set
the precedent: required in the OpenAPI spec, defaulted on
deserialization. An empty controls list already means "unsupported",
so the degraded value is semantically correct.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:23:18 -04:00
Bryan Helmkamp
5d8befa6ac
Reuse canonical ModelControls in fabro-api and tighten serde contract
Add the missing with_replacement for ModelControls so progenitor reuses
fabro_model::ModelControls instead of generating a dead parallel DTO,
re-export it from fabro_api::types, and assert type identity in the
round-trip test.

Drop #[serde(default)] from Model.controls and
ModelControls.reasoning_effort: the OpenAPI spec marks both required,
matching the strict deserialization of the sibling features/costs
fields. Update CLI stub payloads to include the now-required field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:15:18 -04:00
Bryan Helmkamp
1b9275f4a8
refactor(llm): align Fireworks provider with catalog conventions
- Rewrite the Fireworks tool round-trip E2E test on the shared
  run_model_test deep-test pattern used by the OpenRouter and Poolside
  opt-in provider tests, instead of a fourth hand-rolled copy of the
  multiply-tool scaffold.
- Drop the "(via Fireworks)" display-name suffix from slugs that have no
  first-party provider (kimi-k2.6, deepseek-v4-*, minimax-m2.7),
  matching the OpenRouter convention; rename "Qwen 3.7 Plus" to
  "Qwen3.7 Plus" to match existing Qwen entries.
- Fix kimi-k2.6 vision flag to false, matching the OpenRouter entry for
  the same slug (the portability test asserts they are the same model).
- Assert small_default_for_provider and per-model family/vision/
  reasoning in the catalog tests, mirroring sibling provider tests.
- Add Troubleshooting and Further reading sections to the Fireworks
  docs page, matching the other opt-in provider pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:08:32 -04:00
Bryan Helmkamp
856101bbd7
Merge pull request #615 from fabro-sh/fix/preflight-ready-provider-routing
Fix preflight provider routing
2026-07-24 09:01:10 -04:00
Bryan Helmkamp
c06c60214a
refactor: simplify readiness-fallback plumbing
The fallback provider set was always catalog.all_provider_ids(), computed
at every call site and threaded through five layers alongside the catalog
itself. Fold it into Catalog::resolve_selection_with_catalog_fallback and
carry only a catalog_fallback flag through the transform/validate/
materialize entry points.

- materialize_run delegates to resolve_run_model again instead of
  re-inlining its provider normalization and selection
- run_preflight derives ready providers from llm_result instead of
  taking both, so callers cannot pass inconsistent pairs; the legacy
  tests now exercise the production ready-first routing path
- AppState::resolve_llm_client_with_ready_ids replaces three copies of
  resolve-then-extract-provider-ids, and ready_llm_provider_ids
  delegates to it
- the unreachable "model resolution failed" preflight check becomes an
  invariant error where the materialized run is produced
- validate_prepared_manifest_with_vars/_for_preflight share the
  ValidateInput construction

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:49:01 -04:00
Bryan Helmkamp
9708ca8177
feat(llm): add Fireworks AI as an opt-in provider
Adds a disabled-by-default `fireworks` provider to the built-in catalog,
served through the existing openai_compatible adapter/codec. The curated
roster covers Kimi K2.7 Code (default), Kimi K2.6, DeepSeek V4 Pro/Flash,
GLM 5.2, MiniMax M2.7, Qwen 3.7 Plus, and GPT-OSS 120B/20B (small
default + probe), with serverless pricing including cached-input rates.

All api_ids were verified live against /chat/completions (Fireworks'
GET /v1/models only returns a featured subset), and serverless responses
were confirmed to report prompt_tokens_details.cached_tokens, so cache
billing works through the existing codec path.

FIREWORKS_API_KEY is registered as an optional vault secret; provider
login, vault storage, and diagnostics probing are catalog-driven and
need no code changes. Includes catalog/install tests, two live e2e
tests, an integrations docs page, and a provider logo for the web UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:03:39 -04:00
Bryan Helmkamp
4bd9753217
Expose model reasoning effort controls 2026-07-24 07:44:40 -04:00
Bryan Helmkamp
1c1ea53093
fix: prefer ready providers during preflight 2026-07-24 07:28:51 -04:00
Bryan Helmkamp
05fa485637
feat: add portable GLM and DeepSeek aliases 2026-07-24 07:07:56 -04:00