Repository navigation
feat(GTM-38): let the MozFest hosted agent run on a Fireworks model - #1359
Conversation
AGENT_MODEL now picks the provider from its id: accounts/fireworks/models/* runs on Fireworks through the AI SDK's OpenAI-compatible provider, built on createManagedProviderConnection('fireworks'), so the key, base URL and instrumented fetch are shared. Admission and the ledger use the Fireworks identity, so the prices seeded in 0033 apply. Startup refuses a Fireworks model without FIREWORKS_API_KEY. Anthropic behaviour is unchanged.
|
Semgrep Security ScanNo security issues found. |
PR Metrics
Updated Sun, 11 Oct 2026 19:57:57 GMT · run #3410 |
|
Preview environment destroyed 🧹 Stack |
darkbanjo
left a comment
There was a problem hiding this comment.
did a deep pass, comments are tagged blocking / convention / nit. the real end-to-end billing test is great. a few things outside the diff:
- convention: fireworks caches prompts by default at about a fifth of the input price, but the ledger zeroes cache tokens for non-anthropic providers (
usage-ledger.ts:79-82). the agent resends the whole conversation every turn, so from turn two most prompt tokens bill at ~5x real cost and the anonymous 10¢ window runs out well before real spend does. worth pricing cache reads before the event, or at least dropping "slightly" inmodels.md:94? - nit: a usage chunk with null token counts becomes 0, so the run lands as a zero-cost row with no
agent_usage_missinglog. andhistory.test.ts:107-112only covers the forgedproviderMetadatacase with ananthropickey, anopenaiCompatibleone would pin the boundary this provider now relies on. - follow-ups: a run cut at the 120s timeout is never billed (fireworks only reports usage in the last chunk), and with client system messages dropped and an empty
AGENT_SYSTEM_PROMPT, reply language falls to the model's default, which changes going from claude to glm.
Blocking: AGENT_MODEL no longer picks its provider by sniffing the id prefix. It resolves through the same managed-model data /v1/chat uses, and startup refuses an id it cannot place with an error naming the id and the supported shapes, so a typo or another account's model fails at boot instead of 503ing every request. The hosted agent takes no file parts for now: a request carrying one gets 400 AGENT_FILES_UNSUPPORTED before admission. Also: reasoning is not forwarded to visitors (sendReasoning false), Fireworks requests carry a per-user x-prompt-cache-isolation-key derived from a hash of the user id, a usage chunk with null counts logs agent_usage_missing instead of a silent zero-cost row, test fixtures tidied with a test-owned price row, and the docs and comments Jordan listed.
|
Thanks for the review, and for the cache-pricing catch. Everything tagged is addressed in 2e39e2a and answered on its thread. The items outside the diff:
One behaviour change worth knowing: with the table-based resolution, |
| }) | ||
| const filePartSchema = z.object({ type: z.literal('file') }) | ||
|
|
||
| const containsFilePart = (body: unknown): boolean => { |
There was a problem hiding this comment.
New helpers lack required comments
containsFilePart has no JSDoc comment. This violates the repository directive: “Add JSDoc comments to new utility functions.” The new createFilesUnsupportedResponse helper in backend/src/hosted-agent/routes.ts also needs one. Add short comments explaining both helpers before merging.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The hosted-agent wiring test used a fake AGENT_MODEL that the table-based resolution now refuses at startup. It takes the id from managedDirectRuntimes so it cannot drift, and inserts its price row with a conflict guard because a migration already seeds that model.
…a passes Merges origin/main (two doc tables conflicted with the #1359 and #1360 rows; both sides kept, line refs re-verified). With CAPTCHA_PROVIDER=altcha a POST without x-captcha-token spent a point from the deployment-wide anonymous sign-in budget before the 403, so a script behind a raised per-IP limit could drain the hourly budget without solving a challenge. The budget is now charged in the Better Auth before-hook after the captcha verification and session-fixation guard pass; the per-IP bucket still runs first, and with the none provider every sign-in is charged as before. Tests: tokenless requests leave the budget untouched, a valid solution charges it once and its replay does not, none charges every sign-in, and moving the charge back ahead of verification fails the altcha tests. Docs say the budget counts only sign-ins that passed the captcha.
Why
#1333 seeded Fireworks prices and taught the ledger the
fireworksprovider; #1346 gave the backend a shared provider connection factory. The hosted-agent endpoint still hardcoded Anthropic. MozFest runs on GLM 5.3 via Fireworks, so this is the last piece of the agent path.What
AGENT_MODELresolves to a provider through the same managed-model data/v1/chatuses, not by sniffing the id prefix. Startup refuses an id it cannot place (a typo, a model in another Fireworks account, a bare id) with an error naming the id and the two supported shapes, so a misconfiguration fails at boot instead of 503ing every request.claude-opus-5is no longer accepted asAGENT_MODELeven though its price row still exists. No new setting.createAgentModelbuilds Fireworks through@ai-sdk/openai-compatiblewithincludeUsage: true(so the stream's final chunk carries usage the ledger can bill) overcreateManagedProviderConnection('fireworks', ..., { source: 'agent' }), so the key, base URL, instrumented fetch and theagentsource label are all shared with/v1/chat.AGENT_ENABLED=truewith a Fireworks model and noFIREWORKS_API_KEY, via the factory's existing check, same path as the Anthropic refusal.sendReasoning: false), so visitors never see raw chain-of-thought from GLM or MiniMax, matching the Anthropic path. Fireworks requests carryx-prompt-cache-isolation-keyderived from a hash of the user id so one account's prompt cache cannot be probed from another.AGENT_FILES_UNSUPPORTEDbefore admission. feat(GTM-36): use the hosted agent in the web app for MozFest #1353 stops the client sending them.agent_usage_missinginstead of landing a silent zero-cost row.@ai-sdk/openai-compatible@^2.0.81, the currentai-v6line. It shares@ai-sdk/providerandprovider-utilswith the existingai@6stack; the only nested copy is tinfoil's pre-existing 3.x..env.exampleandconfiguration.md(Hosted agent block,AGENT_MODELandFIREWORKS_API_KEYrows),models.md(the "follow-up release" sentence now describes current behaviour, and the seed migration list includes 0035).Tests
Derivation (both seeded ids, Anthropic ids, lookalikes), startup refusal, a Fireworks request landing on
https://api.fireworks.ai/inference/v1/chat/completionswith the bearer key andstream_options.include_usage, admission seeing{ provider: 'fireworks', model }, and a ledger row against migrated PGlite costed from the 0033 GLM row (184,000 nano-USD for 100 in / 10 out). Six revert checks.bun test src/hosted-agent src/inference --randomize --rerun-each 5: 1740 pass, 0 failLive smoke against Fireworks was not run (no key in the dev env). Steps:
PORT=8011 AGENT_ENABLED=true AGENT_MODEL=accounts/fireworks/models/glm-5p3 bun run dev, one anonymous chat through/v1/agent/chat, checkinference_usagefor afireworksrow.Size
Against main: 16 files changed, 469 insertions(+), 119 deletions(-), including the lockfile. Over the 400-line guideline after the review round (table-based provider resolution, the file-part rejection and the new tests).