refactor(models): centralize model metadata and provider-aware resolution#57468
refactor(models): centralize model metadata and provider-aware resolution#57468paul-tian wants to merge 4 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR centralizes model metadata handling into a new Key changes:
Confidence Score: 5/5Safe to merge; the refactoring is well-tested and all remaining findings are P2 style suggestions. Both bug fixes are correct and well-motivated. The new model-metadata module is clearly typed and thoroughly tested. No P0 or P1 issues found; the two P2 comments address a silent drop of partial cost configs and a minor double-call inefficiency in mergeModelMetadata, neither of which affects correctness. src/agents/model-metadata.ts — normalizeModelCost silent drop and mergeModelMetadata double-call; otherwise no files require special attention.
|
| Filename | Overview |
|---|---|
| src/agents/model-metadata.ts | New centralized model metadata module; well-structured with good normalization helpers. Minor: normalizeModelCost silently drops partial configs and mergeModelMetadata calls normalizePositiveInteger twice per field. |
| src/agents/context.ts | Key bug fix: resolveContextTokensForModel now passes params.provider instead of ref.provider to resolveConfiguredProviderContextWindow, preventing wrong-provider config lookups for slash-containing model IDs. Also adds provider-scoped cache keys for discovery entries. |
| src/commands/status.summary.ts | Fixes stale session contextTokens being used as contextTokensOverride; now uses cfg.agents?.defaults?.contextTokens instead of entry?.contextTokens. |
| src/agents/model-catalog.ts | ModelCatalogEntry is now aliased to ModelMetadata, reducing duplication. New applyConfiguredCatalogMetadata overlays config-defined traits onto discovered models after plugin merge. |
| src/auto-reply/reply/followup-runner.ts | Bug fix: providerUsed is now resolved from runResult.meta?.agentMeta?.provider ?? fallbackProvider ?? queued.run.provider instead of just fallbackProvider. |
| src/gateway/session-reset-service.ts | contextTokens is now set to undefined on session reset so context window is recalculated from model metadata rather than inheriting stale persisted values. |
| src/commands/status.summary.runtime.ts | Local resolveContextTokensForModel (which silently ignored allowAsyncLoad) and local parsing helpers removed in favor of shared implementations from context.ts and model-selection.ts. |
Comments Outside Diff (1)
-
src/agents/model-metadata.ts, line 1069-1075 (link)normalizePositiveIntegercalled twice per field inmergeModelMetadatanormalizePositiveInteger(overlay.contextWindow)andnormalizePositiveInteger(overlay.maxTokens)are each invoked twice — once in the condition and once in the spread value — making two redundant calls per field on every merge. Consider caching the result:const contextWindow = normalizePositiveInteger(overlay.contextWindow); const maxTokens = normalizePositiveInteger(overlay.maxTokens); return { ...base, ...(contextWindow === undefined ? {} : { contextWindow }), ...(maxTokens === undefined ? {} : { maxTokens }), ... };Prompt To Fix With AI
This is a comment left during a code review. Path: src/agents/model-metadata.ts Line: 1069-1075 Comment: **`normalizePositiveInteger` called twice per field in `mergeModelMetadata`** `normalizePositiveInteger(overlay.contextWindow)` and `normalizePositiveInteger(overlay.maxTokens)` are each invoked twice — once in the condition and once in the spread value — making two redundant calls per field on every merge. Consider caching the result: ``` const contextWindow = normalizePositiveInteger(overlay.contextWindow); const maxTokens = normalizePositiveInteger(overlay.maxTokens); return { ...base, ...(contextWindow === undefined ? {} : { contextWindow }), ...(maxTokens === undefined ? {} : { maxTokens }), ... }; ``` How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/model-metadata.ts
Line: 50-68
Comment:
**Silent drop for partial cost configs**
`normalizeModelCost` silently returns `undefined` if any of the four required fields (`input`, `output`, `cacheRead`, `cacheWrite`) is absent, with no log warning. Since this is new functionality that users can configure in `models.providers.*.models[].cost`, a user who defines only `{ input: 1, output: 2 }` (without cache pricing) will have their cost entry silently ignored.
Consider emitting a subsystem log warning when a cost object is provided but rejected due to missing fields, so misconfiguration is easier to diagnose.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/agents/model-metadata.ts
Line: 1069-1075
Comment:
**`normalizePositiveInteger` called twice per field in `mergeModelMetadata`**
`normalizePositiveInteger(overlay.contextWindow)` and `normalizePositiveInteger(overlay.maxTokens)` are each invoked twice — once in the condition and once in the spread value — making two redundant calls per field on every merge. Consider caching the result:
```
const contextWindow = normalizePositiveInteger(overlay.contextWindow);
const maxTokens = normalizePositiveInteger(overlay.maxTokens);
return {
...base,
...(contextWindow === undefined ? {} : { contextWindow }),
...(maxTokens === undefined ? {} : { maxTokens }),
...
};
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "refactor(models): unify configured model..." | Re-trigger Greptile
| function normalizeModelCost(cost: unknown): ModelDefinitionConfig["cost"] | undefined { | ||
| if (!isRecord(cost)) { | ||
| return undefined; | ||
| } | ||
| const input = typeof cost.input === "number" ? cost.input : undefined; | ||
| const output = typeof cost.output === "number" ? cost.output : undefined; | ||
| const cacheRead = typeof cost.cacheRead === "number" ? cost.cacheRead : undefined; | ||
| const cacheWrite = typeof cost.cacheWrite === "number" ? cost.cacheWrite : undefined; | ||
| if ( | ||
| input === undefined || | ||
| output === undefined || | ||
| cacheRead === undefined || | ||
| cacheWrite === undefined | ||
| ) { | ||
| return undefined; | ||
| } | ||
| return { input, output, cacheRead, cacheWrite }; | ||
| } | ||
|
|
There was a problem hiding this comment.
Silent drop for partial cost configs
normalizeModelCost silently returns undefined if any of the four required fields (input, output, cacheRead, cacheWrite) is absent, with no log warning. Since this is new functionality that users can configure in models.providers.*.models[].cost, a user who defines only { input: 1, output: 2 } (without cache pricing) will have their cost entry silently ignored.
Consider emitting a subsystem log warning when a cost object is provided but rejected due to missing fields, so misconfiguration is easier to diagnose.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-metadata.ts
Line: 50-68
Comment:
**Silent drop for partial cost configs**
`normalizeModelCost` silently returns `undefined` if any of the four required fields (`input`, `output`, `cacheRead`, `cacheWrite`) is absent, with no log warning. Since this is new functionality that users can configure in `models.providers.*.models[].cost`, a user who defines only `{ input: 1, output: 2 }` (without cache pricing) will have their cost entry silently ignored.
Consider emitting a subsystem log warning when a cost object is provided but rejected due to missing fields, so misconfiguration is easier to diagnose.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4558e59a27
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (scopedKey) { | ||
| // Explicit config wins for provider-scoped lookups. | ||
| params.cache.set(scopedKey, model.contextWindow); | ||
| } |
There was a problem hiding this comment.
Avoid clobbering raw discovery keys with configured scoped keys
Writing configured windows to scopedKey can overwrite unrelated raw discovery entries when IDs share the same provider/model shape across providers (for example, OpenRouter model google/gemini-2.5-pro vs configured Google model gemini-2.5-pro). In that case applyConfiguredContextWindows stores google/gemini-2.5-pro from config, and later resolveContextTokensForModel({ provider: "openrouter", model: "google/gemini-2.5-pro" }) reads the wrong value from the bare lookup path, misreporting context limits used for status and runtime token accounting.
Useful? React with 👍 / 👎.
| if (!metadata) { | ||
| continue; | ||
| } | ||
| configuredByKey.set(buildModelMetadataLookupKey(metadata.provider, metadata.id), metadata); |
There was a problem hiding this comment.
Keep exact provider precedence in catalog metadata overlays
The overlay map is keyed only by normalized provider+model, so alias providers like bedrock and amazon-bedrock collapse to the same key and whichever is iterated last overwrites the other. That makes catalog traits (contextWindow, maxTokens, reasoning, input, etc.) nondeterministic relative to the configured provider key, and can disagree with findConfiguredModelMetadata (which already prefers exact provider matches).
Useful? React with 👍 / 👎.
# Conflicts: # docs/gateway/configuration-reference.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 860d2b2ad9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| model: resolvedModel.model, | ||
| modelProvider: resolvedModel.provider, | ||
| contextTokens: resetEntry?.contextTokens, | ||
| contextTokens: undefined, |
There was a problem hiding this comment.
Preserve configured context cap when resetting sessions
Setting contextTokens to undefined on reset drops the only persisted cap that currently carries agents.defaults.contextTokens into gateway session rows. In src/gateway/session-utils.ts, row hydration falls back to resolveContextTokensForModel(...) without a contextTokensOverride, so a reset session can immediately report the model window (or generic fallback) instead of the configured cap until another run repopulates the field; this skews remaining/percent calculations for users who rely on default context caps.
Useful? React with 👍 / 👎.
|
Thanks for the model metadata pass. Closing this because it is a stale dirty XL refactor across docs, gateway, commands, agents, and OpenAI extension code; please resubmit a narrow owner-backed slice. |
Summary
Context carried into this branch
contextTokensfrom overriding current resolved model metadata in session/status displaysTesting
npx --yes pnpm exec vitest run src/agents/model-metadata.test.ts src/agents/model-catalog.test.ts src/agents/context.test.ts src/commands/status.summary.test.ts src/plugins/provider-catalog.test.ts extensions/openai/openai-codex-provider.test.ts extensions/openai/openai-provider.test.tsnpx --yes pnpm exec oxfmt --check src/agents/model-metadata.ts src/agents/model-metadata.test.ts src/agents/model-catalog.ts src/agents/model-catalog.test.ts src/agents/model-selection.ts src/agents/context.ts src/agents/context.test.ts src/agents/context-window-guard.ts src/commands/status.summary.runtime.ts src/plugins/provider-catalog.ts extensions/openai/openai-codex-provider.tsnpx --yes pnpm exec oxlint src/agents/model-metadata.ts src/agents/model-metadata.test.ts src/agents/model-catalog.ts src/agents/model-catalog.test.ts src/agents/model-selection.ts src/agents/context.ts src/agents/context.test.ts src/agents/context-window-guard.ts src/commands/status.summary.runtime.ts src/plugins/provider-catalog.ts extensions/openai/openai-codex-provider.ts