Skip to content

feat(agent-core-v2): add visual model assignment for image inspection - #2754

Open
zikzak-ai wants to merge 1 commit into
MoonshotAI:mainfrom
zikzak-ai:feat/visual-model-assignment
Open

feat(agent-core-v2): add visual model assignment for image inspection#2754
zikzak-ai wants to merge 1 commit into
MoonshotAI:mainfrom
zikzak-ai:feat/visual-model-assignment

Conversation

@zikzak-ai

Copy link
Copy Markdown

Related Issue

Closes #2750

Problem

Many coding models are text-only — they cannot inspect images. Web and mobile development regularly requires visual inspection (screenshots, UI states, layout renders, image diffs of a rendered page). Today there is no way for a user to pin a specific, vision-capable model for these visual tasks; they fall back to automatic / current-model selection, which is often a text-only model. See the linked issue for the full context.

What changed

Mirrors the existing experimental [secondary_model] slot as a new [visual_model] configuration section, so a user can pin a vision-capable companion model for image / screenshot / video inspection. The design copies the secondary-model shape verbatim — config section + env overrides + experiment flag + derived-entry overlay + resolver family — and wires the resolver into the media-tools registrar.

Config + overlay + flag

  • [visual_model] config section registered in packages/agent-core-v2/src/app/kosongConfig/configSection.ts (parallel to SECONDARY_MODEL_SECTION). Accepts model, default_effort, and every [models."<alias>".overrides] field as a patch.
  • KIMI_VISUAL_MODEL / KIMI_VISUAL_EFFORT env overrides (priority over config.toml).
  • packages/agent-core-v2/src/app/kosongConfig/visualModelOverlay.ts synthesizes the derived __visual__ registry entry when the recipe carries patch fields (base copy, patch merged into overrides with patch winning conflicts, aliases dropped). The derived entry lives only in memory and is stripped from config.toml writes — including a default_model pointer at the derived id. Mirror of secondaryModelOverlay.
  • packages/agent-core-v2/src/session/visual/flag.ts registers the visual-model experimental flag (KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL), default off, surface core.

Resolver family

packages/agent-core-v2/src/session/visual/configSection.ts exports the visual-model resolver family, mirroring session/subagent/configSection.ts:

  • resolveVisualModel(config, flags) — returns the configured recipe when the experiment is on, undefined otherwise.
  • resolveVisualBinding(config, flags, own, requested?) — resolves which model a visual task binds to. Unset = caller's model (no behavior change). Explicit 'primary' forces the caller's model even when a visual model is configured.
  • buildVisualModelDescriptions(...) — the "Available models for visual inspection" block, with capability suffixes, mirroring buildSubagentModelDescriptions.
  • visualDisplayModel(config, boundAlias) — resolves the derived __visual__ id back to the recipe's base alias for display, flag-independent so an already-persisted derived binding still resolves after the experiment is switched off.
  • stripVisualModelParameter(...) — strips the no-op model parameter from a visual-task tool's advertised schema while the experiment is off.
  • wrapVisualModelError(...) — wraps a missing-alias failure with a hint pointing at [visual_model].model / KIMI_VISUAL_MODEL.

Wiring

packages/agent-core-v2/src/agent/media/mediaToolsRegistrar.ts now injects IConfigService + IFlagService and consults resolveVisualModel on every refresh. When the caller's model is text-only (!image_in && !video_in) but a vision-capable visual model is configured and the experiment is on, the registrar registers ReadMediaFile against the visual model's capabilities and video uploader so the LLM keeps the tool available for visual inspection. When the visual model is unset, the registrar's behavior is identical to before (caller's capabilities gate, caller's requester binds). The state key now includes the visual model alias + capability signature so a visual-model change re-runs registration.

The actual end-to-end routing of image content to the visual model (e.g. via a subagent spawn for the inspection) is a follow-up enhancement that this PR enables — the configuration slot, resolver, and registrar gate are the foundation.

Index exports

packages/agent-core-v2/src/index.ts re-exports the new symbols (VISUAL_MODEL_SECTION, VISUAL_MODEL_ENV, VISUAL_MODEL_EFFORT_ENV, VisualModelConfigSchema, visualModelEnvBindings, VISUAL_DERIVED_MODEL_ID, visualModelOverlay, visualModelPatch, VISUAL_MODEL_FLAG_ID, VISUAL_MODEL_FLAG_ENV, visualModelFlag, resolveVisualModel, resolveVisualBinding, visualDisplayModel, buildVisualModelDescriptions, stripVisualModelParameter, wrapVisualModelError, VisualModelChoice) and side-effect-imports the overlay + flag + resolver so the registrations run on first import.

Docs + manifest

  • Bilingual VitePress docs: new ## visual_model section in docs/en/configuration/config-files.md and docs/zh/configuration/config-files.md, mirroring the ## secondary_model section (table, patch semantics, env overrides, experiment flag, advisory validation note).
  • packages/agent-core-v2/docs/config-manifest.toml regenerated via pnpm --filter @moonshot-ai/agent-core-v2 gen:config-manifest — now lists visualModel and the visualModelOverlay.

Tests

  • packages/agent-core-v2/test/app/kosongConfig/visualModelOverlay.test.ts (9 tests, mirror of secondaryModelOverlay.test.ts): apply path (no-op, pointer-only, patch synthesis, missing pointer, never-derive-from-derived, no collision with __secondary__) and strip path (removes derived entry, leaves other domains, rolls back defaultModel pointer).
  • packages/agent-core-v2/test/session/visual/configSection.test.ts (19 tests): resolveVisualModel (disabled flag, unset, set), resolveVisualBinding (unset fallback, disabled-flag fallback, pointer-only binding, derived-entry binding + display, explicit primary override), visualDisplayModel (passthrough, derived-id resolution, fallback after recipe removal), stripVisualModelParameter (no-op, removes model + required, no mutation), wrapVisualModelError (caller-own passthrough, non-CONFIG_INVALID passthrough, mismatched-details passthrough, missing-alias wrap with [visual_model] hint, derived-id wrap).
  • packages/agent-core-v2/test/agent/media/tools/read-media.test.ts updated for the two new AgentMediaToolsRegistrar constructor deps (StubConfigService + stubFlag(false)).

Changeset

.changeset/visual-model-assignment.md@moonshot-ai/kimi-code: minor (user-visible config + behavior change in the CLI).

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (feat: allow assigning a dedicated model for visual tasks (visual model id) #2750).
  • I have added tests that prove my feature works (28 new tests across overlay + resolver).
  • Ran gen-changesets skill (changeset added for @moonshot-ai/kimi-code).
  • Ran gen-docs skill (bilingual VitePress docs updated; manifest regenerated).
  • pnpm lint — 0 errors (only pre-existing warnings in unrelated packages/minidb).
  • pnpm --filter @moonshot-ai/agent-core-v2 typecheck — clean.
  • pnpm --filter @moonshot-ai/agent-core-v2 exec vitest run for the touched tests — all pass (visual overlay + visual resolver + read-media + secondary-model + secondary-model-warning).

Mirror the experimental `[secondary_model]` slot as a new `[visual_model]`
configuration section so users can pin a vision-capable model for image /
screenshot / video inspection tasks even when their main coding model is
text-only.

  - `[visual_model]` config section + `KIMI_VISUAL_MODEL` /
    `KIMI_VISUAL_EFFORT` env overrides, registered alongside the other
    kosong config sections (parallel to `SECONDARY_MODEL_SECTION`).
  - `visualModelOverlay` synthesizes the derived `__visual__` registry
    entry when the recipe carries patch fields (mirror of
    `secondaryModelOverlay`); the derived entry lives only in memory and
    is stripped from `config.toml` writes.
  - `visual-model` experimental flag (`KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL`)
    gates the resolver; off by default.
  - `resolveVisualModel` / `resolveVisualBinding` /
    `buildVisualModelDescriptions` / `visualDisplayModel` /
    `stripVisualModelParameter` / `wrapVisualModelError` mirror the
    subagent resolver family.
  - `AgentMediaToolsRegistrar` now consults `resolveVisualModel`: when the
    caller's model is text-only but a vision-capable visual model is
    configured, the media tools register against the visual model's
    capabilities and requester so `ReadMediaFile` stays available. When
    unset, behavior is unchanged.
  - Regenerates `docs/config-manifest.toml` (now lists `visualModel` and
    the `visualModelOverlay`).
  - Bilingual VitePress docs (`docs/{en,zh}/configuration/config-files.md`)
    describe the new section, env vars, experiment flag, and the
    unchanged-when-unset contract.
  - Vitest coverage: `visualModelOverlay.test.ts` (apply / strip / no
    collision with `__secondary__`) and `session/visual/configSection.test.ts`
    (resolution + unset-fallback + display model + schema strip + error
    wrapping). The existing `read-media.test.ts` registrar construction
    site is updated for the two new constructor deps.

Closes MoonshotAI#2750
@changeset-bot

changeset-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 605bb69

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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

Copy link
Copy Markdown

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: 605bb69005

ℹ️ 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 on lines +121 to +122
const useVisual = !callerHasMedia && visualHasMedia;
const capabilities = useVisual ? visualCapabilities! : callerCapabilities;

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 Do not expose media tools without a visual request path

When the experiment is enabled and the caller model has no media capability but [visual_model] points to a vision model, this branch registers ReadMediaFile using the visual model's capabilities. The tool execution still writes image/video ContentParts into the normal tool-result history, and llmRequesterService.resolveRequest sends the following request with the caller profile's requester, so the advertised text-only-main-model scenario sends media payloads to the text-only model instead of to the visual model. Keep the tool gated on the caller model until the media read is summarized through the visual model, or route the follow-up request to that visual binding.

Useful? React with 👍 / 👎.


import { VISUAL_MODEL_FLAG_ID } from './flag';

export type VisualModelChoice = AgentModelPreference;

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 Type visual choices as primary or visual

VisualModelChoice aliases AgentModelPreference, whose values are primary | secondary, but this resolver and the exported schema/documentation use visual. A TypeScript caller cannot pass the intended "visual" value to resolveVisualBinding without a cast, while "secondary" is accepted and is treated as the visual path because the branch only checks for "primary". Define this type from VISUAL_MODEL_CHOICE_SCHEMA or a primary | visual literal union so the public contract matches runtime behavior.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: allow assigning a dedicated model for visual tasks (visual model id)

1 participant