Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions crates/agent-gui/test/providers/kbrain-attachment-read.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createTsModuleLoader } from "../helpers/load-ts-module.mjs";

const loader = createTsModuleLoader();
const {
customProviderFromKBrain,
kBrainSettingsUpdateForMissingInputModalities,
kBrainSettingsUpdateFromLegacyProviders,
} = loader.loadModule(
Expand Down Expand Up @@ -48,6 +49,32 @@ test("relay models without modalities are pushed to K-brain with catalog vision"
assert.equal(pushedModel(update, "relay", "unknown-relay-model").inputModalities, undefined);
});

test("anthropic-compatible user modality override reaches K-brain and survives reload", () => {
// #1045:目录未知的中转模型,用户在设置里选「文本与图片」后必须原样推给 K-brain
// (K-brain 只按 inputModalities/vision 判定收图),再读回时仍保留该覆盖。
const saved = relayProvider("relay", [
{ id: "unknown-relay-model", contextWindow: 8192, inputModalities: ["text", "image"] },
]);
const update = kBrainSettingsUpdateFromLegacyProviders([saved], [saved]);
const pushedProvider = update.providers.find((provider) => provider.id === "relay");
assert.equal(pushedProvider.api, "anthropic-messages");
assert.deepEqual(pushedModel(update, "relay", "unknown-relay-model").inputModalities, [
"text",
"image",
]);

const reloaded = customProviderFromKBrain({
id: "relay",
name: "relay",
api: "anthropic-messages",
baseUrl: "https://relay.invalid/claude/v1",
apiKeyConfigured: true,
models: [{ id: "unknown-relay-model", contextWindow: 8192, vision: true }],
});
assert.equal(reloaded.type, "claude_code");
assert.deepEqual(reloaded.models[0].inputModalities, ["text", "image"]);
});

test("explicit modalities win and are shared across providers for the same model id", () => {
const explicit = relayProvider("a", [{ id: "claude-opus-4-6", inputModalities: ["text"] }]);
const implicit = relayProvider("b", [{ id: "claude-opus-4-6" }]);
Expand Down
7 changes: 4 additions & 3 deletions crates/agent-gui/test/settings/input-modalities.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { createTsModuleLoader } from "../helpers/load-ts-module.mjs";
// inputModalities(模型输入模态用户覆盖)的反漂移锁:
// 1. normalizer 的过滤/补齐/规范顺序契约;
// 2. 设置加载往返不丢字段;
// 3. modelFactory 只在附件发送确实受 model.input 门控的分支(codex/gemini/
// deepseek)应用覆盖,anthropic 不适用(避免虚假能力声明)。
// 3. modelFactory 对所有供应商(含 Anthropic 兼容)应用覆盖:K-brain 按模型条目的
// inputModalities 判定能否收图,与 API 类型无关(#1045)。
const loader = createTsModuleLoader();
const { normalizeInputModalities, normalizeProviderModelConfig, normalizeProviderModelConfigs } =
loader.loadModule("src/lib/settings/index.ts");
Expand Down Expand Up @@ -106,7 +106,8 @@ test(
assert.equal(providerSupportsModelInputModalitiesOverride("xai"), true);
assert.equal(providerSupportsModelInputModalitiesOverride("gemini"), true);
assert.equal(providerSupportsModelInputModalitiesOverride("deepseek"), true);
assert.equal(providerSupportsModelInputModalitiesOverride("claude_code"), false);
// #1045:Anthropic 兼容供应商同样可以手动开启图片输入。
assert.equal(providerSupportsModelInputModalitiesOverride("claude_code"), true);
},
);

Expand Down
5 changes: 4 additions & 1 deletion crates/agent-ui/src/pages/settings/providerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ export function providerSupportsModelInputModalitiesOverride(providerId: Provide
// deepseek:Responses wire 已接受 input_image(官方《图像理解》指南),模型
// 能力默认按 id 推断(flash 家族吃图、Pro 纯文本),中转端点不吃图时用覆盖
// 改回 ["text"]。
providerId === "deepseek"
providerId === "deepseek" ||
// claude_code(Anthropic 兼容):K-brain 按模型条目的 inputModalities 判定能否
// 收图,与 API 类型无关;中转的 /v1/models 不返回模态时,靠覆盖开启图片输入(#1045)。
providerId === "claude_code"
);
}

Expand Down
Loading