Skip to content

refactor(models): centralize model metadata and provider-aware resolution#57468

Closed
paul-tian wants to merge 4 commits intoopenclaw:mainfrom
paul-tian:pr/models-management
Closed

refactor(models): centralize model metadata and provider-aware resolution#57468
paul-tian wants to merge 4 commits intoopenclaw:mainfrom
paul-tian:pr/models-management

Conversation

@paul-tian
Copy link
Copy Markdown

Summary

  • add a shared configured model metadata layer for durable provider/model traits like context window, max tokens, reasoning, and modality
  • route model catalog, model selection, context-window resolution, and status-summary runtime lookups through shared provider-aware metadata helpers
  • preserve explicit configured metadata while still allowing legitimate runtime/provider discovery to add or fill model catalog rows
  • document the merged model metadata flow for CLI, config, and status surfaces

Context carried into this branch

  • fix provider/model-scoped context window resolution across runtime accounting and status paths
  • stop stale persisted session contextTokens from overriding current resolved model metadata in session/status displays

Testing

  • 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.ts
  • npx --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.ts
  • npx --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

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime commands Command implementations agents Agent runtime and tooling extensions: openai size: XL labels Mar 30, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 30, 2026

Greptile Summary

This PR centralizes model metadata handling into a new src/agents/model-metadata.ts module and routes model catalog, context-window resolution, and status/session display through shared provider-aware helpers. It also ships two real bug fixes: (1) resolveContextTokensForModel now passes params.provider instead of the previously inferred ref.provider to config lookups, preventing wrong-provider context window resolution when a model ID contains a slash; (2) stale persisted session contextTokens no longer serve as the contextTokensOverride in status/session displays.

Key changes:

  • New model-metadata.ts module with ModelMetadata type and helpers — eliminates four separately maintained inline model-config parsing loops across model-catalog.ts, model-selection.ts, context-window-guard.ts, and context.ts.
  • applyConfiguredContextWindows now writes provider-scoped cache keys in addition to the legacy bare key; configured values win over discovery for scoped lookups.
  • session-reset-service.ts resets contextTokens: undefined so context window is recalculated from current model metadata after a reset.
  • followup-runner.ts correctly resolves providerUsed from agentMeta.provider rather than relying only on fallbackProvider.
  • normalizeModelCost silently drops a cost object if any of the four required fields is missing — a subsystem log warning would help diagnose partial cost configurations.
  • mergeModelMetadata calls normalizePositiveInteger twice per field — minor inefficiency.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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)

  1. src/agents/model-metadata.ts, line 1069-1075 (link)

    P2 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 }),
      ...
    };
    
    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

Comment on lines +50 to +68
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 };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/agents/context.ts
Comment on lines +127 to 130
if (scopedKey) {
// Explicit config wins for provider-scoped lookups.
params.cache.set(scopedKey, model.contextWindow);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@vincentkoc vincentkoc added triage: refactor-only Candidate: refactor/cleanup-only PR without maintainer context. triage: dirty-candidate Candidate: broad unrelated surfaces; may need splitting or cleanup. labels Apr 26, 2026
@vincentkoc vincentkoc self-assigned this Apr 29, 2026
@vincentkoc
Copy link
Copy Markdown
Member

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.

@vincentkoc vincentkoc closed this Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations docs Improvements or additions to documentation extensions: openai gateway Gateway runtime size: XL triage: dirty-candidate Candidate: broad unrelated surfaces; may need splitting or cleanup. triage: refactor-only Candidate: refactor/cleanup-only PR without maintainer context.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants