CS-11249: realm-independent fallback models + host wire/picker integration#4949
Conversation
…ation Closes the silent-tools-off bug for the curated 6 models on the host side. sendActiveLLMEvent now fills missing toolsSupported/inputModalities from the bundled constant when SystemCard.modelConfigurations doesn't cover the chosen model; the AI assistant picker uses the same constant in its SystemCard-missing fallback path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds reasoningEffort to FallbackModelConfig (default 'medium' for the curated rows) and changes sendActiveLLMEvent to fall back to it via ?? when caller / SystemCard hasn't set one. Inlines the refresh procedure into matrix-constants.ts and drops the scratch plan + refresh docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 3h 35m 1s ⏱️ + 1h 43m 50s Results for commit add370b. ± Comparison against earlier commit 789ad81. For more details on these errors, see this check. Realm Server Test Results 1 files ± 0 1 suites ±0 8m 27s ⏱️ +50s Results for commit add370b. ± Comparison against earlier commit 789ad81. |
Move capability resolution out of sendActiveLLMEvent into a layered
resolveActiveLLMConfig on MatrixService so every wire write carries
defined toolsSupported + inputModalities — caller overrides, then
SystemCard, then DEFAULT_FALLBACK_MODELS, then prior valid event in the
same room, then a conservative floor. sendActiveLLMEvent becomes a dumb
wire writer with a required caps argument.
Trim FallbackModelConfig to { modelId, displayName, toolsSupported,
inputModalities }: reasoningEffort is a user choice (never auto-filled)
and supportsReasoning had no consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the two-step resolve-then-send dance at every call site by calling resolveActiveLLMConfig inside sendActiveLLMEvent and exposing the same callerOverrides arg on its signature. The resolver stays public for tests and any future query-only consumer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getRoomState returns the state event content directly, not a wrapper — the two sendActiveLLMEvent tests were dereferencing .content on the content and hitting undefined. The activateLLMTask edit was redundant once sendActiveLLMEvent's callerOverrides arg accepts the full caps shape; revert to main so the existing SystemCard-derived config flows through unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a realm-independent fallback model list and a layered resolver in the host Matrix service to ensure toolsSupported and inputModalities are always defined on app.boxel.active-llm wire events, even when SystemCard.modelConfigurations is unavailable. It also updates the host LLM picker fallback to use the curated fallback set while preserving previously-used (non-curated) models, and adds coverage across runtime-common, realm-server, and host integration tests.
Changes:
- Add
DEFAULT_FALLBACK_MODELS+DEFAULT_FALLBACK_MODEL_ID(curated capability surface) toruntime-common/matrix-constants.ts. - Add
matrix-service.resolveActiveLLMConfig()and ensuresendActiveLLMEvent()always writes resolved capability fields. - Update the host room picker fallback branch to use the curated fallback list (merged with
usedLLMs) and add tests validating resolver + picker behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/tests/fallback-models-test.ts | Adds shared tests that validate the curated fallback list shape and invariants. |
| packages/runtime-common/matrix-constants.ts | Introduces curated fallback models + default fallback model id for capability fill-in. |
| packages/realm-server/tests/index.ts | Registers the new fallback-models test in the realm-server test suite. |
| packages/realm-server/tests/fallback-models-test.ts | Runs the runtime-common shared fallback-model tests under realm-server. |
| packages/host/tests/integration/components/ai-assistant-panel/fallback-models-test.gts | Adds host integration tests for resolver layering, wire writes, and picker rendering. |
| packages/host/app/services/matrix-service.ts | Implements layered resolver and ensures active-llm state events always include capability fields. |
| packages/host/app/components/matrix/room.gts | Switches picker fallback list from legacy defaults to curated fallback list + used history. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // curated set changes. Derivation rules mirror | ||
| // `packages/host/app/commands/sync-openrouter-models.ts:67-141`: |
There was a problem hiding this comment.
Good catch — fixed in add370b. The comment now points at the computed fields on the OpenRouterModel card itself (packages/openrouter-realm/openrouter-model.gts), which is where toolsSupported = supportedParameters.includes('tools') and inputModalities = architecture.inputModalities actually live. Dropped the line range so the reference doesn't bit-rot.
| let priorEvents = (roomData?.events ?? []) | ||
| .filter((e) => e.type === APP_BOXEL_ACTIVE_LLM) | ||
| .map((e) => e as ActiveLLMEvent) | ||
| .filter( | ||
| (e) => | ||
| e.content.model === model && | ||
| e.content.toolsSupported !== undefined && | ||
| e.content.inputModalities !== undefined, | ||
| ) | ||
| .sort((a, b) => (b.origin_server_ts ?? 0) - (a.origin_server_ts ?? 0)); | ||
| let mostRecent = priorEvents[0]; |
There was a problem hiding this comment.
Fixed in add370b — Layer 4 is now a single for…of pass that tracks the newest matching event by origin_server_ts directly, no intermediate filtered array and no sort.
lukemelia
left a comment
There was a problem hiding this comment.
Copilot's recommendations are good. Otherwise, looks good to me.
- Comment in matrix-constants.ts now references the OpenRouterModel card's computed fields (the actual home of the toolsSupported / inputModalities derivation), not sync-openrouter-models.ts which only writes card JSON. - Layer 4 of resolveActiveLLMConfig walks roomData.events once tracking the newest matching APP_BOXEL_ACTIVE_LLM event by origin_server_ts, instead of filtering into an array and sorting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
DEFAULT_FALLBACK_MODELS(6 curated rows) +DEFAULT_FALLBACK_MODEL_IDtoruntime-common/matrix-constants.tsso capability fields are available even whenSystemCard.modelConfigurationsis missing — fixing the silent-tools-off failure mode for the curated set.matrix-service.resolveActiveLLMConfig(roomId, model, callerOverrides?)— layered resolver: caller overrides → SystemCard →DEFAULT_FALLBACK_MODELS→ most-recent valid priorapp.boxel.active-llmevent in this room → conservative floor ({ toolsSupported: false, inputModalities: ['text'] }).sendActiveLLMEventcalls it internally so every wire write carries definedtoolsSupported+inputModalities.FallbackModelConfigis intentionally minimal:{ modelId, displayName, toolsSupported, inputModalities }.reasoningEffortis a user choice (never auto-filled from the curated fallback);supportsReasoninghas no consumer in this project.room.gtsswaps the legacy 22-entryDEFAULT_LLM_LISTfor the curated 6; the existingusedLLMsmerge keeps non-curated history selectable.DEFAULT_LLM/_LIST/_ID_TO_NAME.Test plan
cd packages/realm-server && TEST_FILES=fallback-models-test pnpm test— shape unit tests pass locallycd packages/host && pnpm lint:js— cleancd packages/runtime-common && pnpm lint:js— cleanfallback-models-test.gts— 8 scenarios: resolver layers (curated caps fill-in, conservative floor for unknown model, caller override priority, rehydrate from prior valid event, skip prior broken event), wire writer (curated + override), picker renders the 6 displayNames🤖 Generated with Claude Code