fix: update the logic to update thinking in cli tools page

This commit is contained in:
2026-07-20 10:14:02 +07:00
parent 5329a4bd67
commit cb0807fe74
5 changed files with 170 additions and 97 deletions
@@ -67,7 +67,20 @@ const getDuplicateModelClass = ({ total, occurrence }) => (
total > 1 ? DUPLICATE_MODEL_COLORS[(occurrence - 1) % DUPLICATE_MODEL_COLORS.length] : "border-border bg-bg-secondary"
);
function buildConfigs(toolId, { baseUrl, apiKey, models, claudeModels = {}, claudeThinking = {}, codexModel = "", codexThinking = "", opencodeModels = [], opencodeDefaultModel = "", coworkThinking = {}, copilotTokens = {}, copilotThinking = {}, connectedModels = [] }) {
const createCoworkModelSettings = (models, savedSettings = [], legacyThinking = {}) => (
models.map((model, index) => ({
thinking: savedSettings[index]?.thinking || legacyThinking[model] || "",
}))
);
const createCopilotModelSettings = (models, savedSettings = [], legacyThinking = {}, legacyTokens = {}) => (
models.map((model, index) => ({
thinking: savedSettings[index]?.thinking || legacyThinking[model] || "",
tokens: savedSettings[index]?.tokens || legacyTokens[model] || DEFAULT_MODEL_TOKEN_LIMITS,
}))
);
function buildConfigs(toolId, { baseUrl, apiKey, models, claudeModels = {}, claudeThinking = {}, codexModel = "", codexThinking = "", opencodeModels = [], opencodeDefaultModel = "", coworkModelSettings = [], copilotModelSettings = [], connectedModels = [] }) {
const endpoint = normalizeV1(baseUrl);
const selectedModels = models.length ? models : [DEFAULT_MODEL];
const model = selectedModels[0];
@@ -123,7 +136,7 @@ function buildConfigs(toolId, { baseUrl, apiKey, models, claudeModels = {}, clau
case "copilot":
return [{
filename: "chatLanguageModels.json",
content: toJson(Object.values(selectedModels.reduce((groups, id) => {
content: toJson(Object.values(selectedModels.reduce((groups, id, index) => {
const connectedModel = connectedModels.find((item) => item.fullModel === id);
const providerId = connectedModel?.providerAlias || id.split("/")[0] || "9router";
const providerName = connectedModel?.provider?.name || formatModelName(providerId);
@@ -136,8 +149,9 @@ function buildConfigs(toolId, { baseUrl, apiKey, models, claudeModels = {}, clau
apiKey: COPILOT_API_KEY_INPUT,
models: [],
};
const tokens = copilotTokens[id] || {};
const modelId = withThinkingLevel(id, copilotThinking[id]);
const modelSettings = copilotModelSettings[index] || {};
const tokens = modelSettings.tokens || {};
const modelId = withThinkingLevel(id, modelSettings.thinking);
const entry = {
id: modelId,
name: formatModelName(modelId),
@@ -146,7 +160,7 @@ function buildConfigs(toolId, { baseUrl, apiKey, models, claudeModels = {}, clau
vision: true,
streaming: true,
};
if (copilotThinking[id]) {
if (modelSettings.thinking) {
entry.thinking = true;
entry.reasoningEffortFormat = "chat-completions";
}
@@ -168,8 +182,8 @@ function buildConfigs(toolId, { baseUrl, apiKey, models, claudeModels = {}, clau
inferenceProvider: "gateway",
inferenceGatewayBaseUrl: endpoint,
inferenceGatewayApiKey: apiKey,
inferenceModels: selectedModels.map((name) => ({
name: withThinkingLevel(name, coworkThinking[name]),
inferenceModels: selectedModels.map((name, index) => ({
name: withThinkingLevel(name, coworkModelSettings[index]?.thinking),
})),
}),
}];
@@ -211,9 +225,17 @@ export default function ConfigGeneratorCard({
const [codexThinking, setCodexThinking] = useState(() => initialConfig?.codexThinking || "");
const [opencodeModels, setOpencodeModels] = useState(() => initialConfig?.opencodeModels || []);
const [opencodeDefaultModel, setOpencodeDefaultModel] = useState(() => initialConfig?.opencodeDefaultModel || "");
const [coworkThinking, setCoworkThinking] = useState(() => initialConfig?.coworkThinking || {});
const [copilotTokens, setCopilotTokens] = useState(() => initialConfig?.copilotTokens || {});
const [copilotThinking, setCopilotThinking] = useState(() => initialConfig?.copilotThinking || {});
const [coworkModelSettings, setCoworkModelSettings] = useState(() => createCoworkModelSettings(
initialConfig?.selectedModels || [],
initialConfig?.coworkModelSettings,
initialConfig?.coworkThinking,
));
const [copilotModelSettings, setCopilotModelSettings] = useState(() => createCopilotModelSettings(
initialConfig?.selectedModels || [],
initialConfig?.copilotModelSettings,
initialConfig?.copilotThinking,
initialConfig?.copilotTokens,
));
const connectedModels = availableModels;
const [customBaseUrl, setCustomBaseUrl] = useState(() => resolveInitialCliToolBaseUrl(initialConfig?.baseUrl, baseUrl));
const [modelModalOpen, setModelModalOpen] = useState(false);
@@ -227,8 +249,8 @@ export default function ConfigGeneratorCard({
? COPILOT_API_KEY_INPUT
: selectedApiKey.trim() || (cloudEnabled ? "<API_KEY_FROM_DASHBOARD>" : "sk_9router");
const configs = useMemo(
() => buildConfigs(toolId, { baseUrl: effectiveBaseUrl, apiKey, models: selectedModels, claudeModels, claudeThinking, codexModel, codexThinking, opencodeModels, opencodeDefaultModel, coworkThinking, copilotTokens, copilotThinking, connectedModels: connectedModels || [] }),
[toolId, effectiveBaseUrl, apiKey, selectedModels, claudeModels, claudeThinking, codexModel, codexThinking, opencodeModels, opencodeDefaultModel, coworkThinking, copilotTokens, copilotThinking, connectedModels]
() => buildConfigs(toolId, { baseUrl: effectiveBaseUrl, apiKey, models: selectedModels, claudeModels, claudeThinking, codexModel, codexThinking, opencodeModels, opencodeDefaultModel, coworkModelSettings, copilotModelSettings, connectedModels: connectedModels || [] }),
[toolId, effectiveBaseUrl, apiKey, selectedModels, claudeModels, claudeThinking, codexModel, codexThinking, opencodeModels, opencodeDefaultModel, coworkModelSettings, copilotModelSettings, connectedModels]
);
const buildPersistableConfig = () => {
@@ -242,8 +264,8 @@ export default function ConfigGeneratorCard({
if (toolId === "claude") Object.assign(config, { claudeModels, claudeThinking });
if (toolId === "codex") Object.assign(config, { codexModel, codexThinking });
if (toolId === "opencode") Object.assign(config, { opencodeModels, opencodeDefaultModel });
if (toolId === "cowork") Object.assign(config, { selectedModels, coworkThinking });
if (toolId === "copilot") Object.assign(config, { selectedModels, copilotThinking, copilotTokens });
if (toolId === "cowork") Object.assign(config, { selectedModels, coworkModelSettings });
if (toolId === "copilot") Object.assign(config, { selectedModels, copilotModelSettings });
return config;
};
@@ -266,7 +288,7 @@ export default function ConfigGeneratorCard({
}
setSaveStatus((current) => current === "saving" ? current : "dirty");
setSaveError("");
}, [effectiveBaseUrl, apiKeyMode, selectedApiKey, selectedModels, claudeModels, claudeThinking, codexModel, codexThinking, opencodeModels, opencodeDefaultModel, coworkThinking, copilotTokens, copilotThinking]);
}, [effectiveBaseUrl, apiKeyMode, selectedApiKey, selectedModels, claudeModels, claudeThinking, codexModel, codexThinking, opencodeModels, opencodeDefaultModel, coworkModelSettings, copilotModelSettings]);
const getThinkingLevelsForModel = (fullModel) => {
const connectedModel = connectedModels?.find((model) => model.fullModel === fullModel);
@@ -274,7 +296,7 @@ export default function ConfigGeneratorCard({
return getThinkingLevels(connectedModel.provider.id, connectedModel.model);
};
const loadCopilotTokenLimits = async (modelIds) => {
const loadCopilotTokenLimits = async (modelIds, modelsForSettings = selectedModels) => {
try {
const response = await fetch("/api/models/token-limits", {
method: "POST",
@@ -284,14 +306,16 @@ export default function ConfigGeneratorCard({
if (!response.ok) throw new Error("Failed to load models.dev token limits");
const { limits = {} } = await response.json();
setCopilotTokens((current) => Object.entries(limits).reduce((next, [model, limit]) => {
const currentLimit = current[model];
setCopilotModelSettings((current) => current.map((settings, index) => {
const model = modelsForSettings[index];
const limit = limits[model];
if (!limit) return settings;
const currentLimit = settings.tokens;
const hasUserOverride = currentLimit
&& (currentLimit.maxInputTokens !== DEFAULT_MODEL_TOKEN_LIMITS.maxInputTokens
|| currentLimit.maxOutputTokens !== DEFAULT_MODEL_TOKEN_LIMITS.maxOutputTokens);
next[model] = hasUserOverride ? currentLimit : limit;
return next;
}, { ...current }));
return { ...settings, tokens: hasUserOverride ? currentLimit : limit };
}));
} catch (error) {
console.log("Error loading models.dev token limits:", error);
}
@@ -299,42 +323,23 @@ export default function ConfigGeneratorCard({
const addModel = (selected) => {
if (!selected?.value) return;
setSelectedModels((current) => [...current, selected.value]);
if (toolId === "cowork") setCoworkThinking((current) => ({ ...current, [selected.value]: "" }));
const nextModels = [...selectedModels, selected.value];
setSelectedModels(nextModels);
if (toolId === "cowork") setCoworkModelSettings((current) => [...current, { thinking: "" }]);
if (toolId === "copilot") {
setCopilotThinking((current) => ({ ...current, [selected.value]: "" }));
setCopilotTokens((current) => ({ ...current, [selected.value]: DEFAULT_MODEL_TOKEN_LIMITS }));
loadCopilotTokenLimits([selected.value]);
setCopilotModelSettings((current) => [...current, { thinking: "", tokens: DEFAULT_MODEL_TOKEN_LIMITS }]);
loadCopilotTokenLimits([selected.value], nextModels);
}
};
const removeCoworkModel = (model, index) => {
const remainingModels = selectedModels.filter((_, currentIndex) => currentIndex !== index);
setSelectedModels(remainingModels);
if (!remainingModels.includes(model)) {
setCoworkThinking((current) => {
const remainingThinking = { ...current };
delete remainingThinking[model];
return remainingThinking;
});
}
const removeCoworkModel = (index) => {
setSelectedModels((current) => current.filter((_, currentIndex) => currentIndex !== index));
setCoworkModelSettings((current) => current.filter((_, currentIndex) => currentIndex !== index));
};
const removeCopilotModel = (model, index) => {
const remainingModels = selectedModels.filter((_, currentIndex) => currentIndex !== index);
setSelectedModels(remainingModels);
if (!remainingModels.includes(model)) {
setCopilotThinking((current) => {
const remaining = { ...current };
delete remaining[model];
return remaining;
});
setCopilotTokens((current) => {
const remaining = { ...current };
delete remaining[model];
return remaining;
});
}
const removeCopilotModel = (index) => {
setSelectedModels((current) => current.filter((_, currentIndex) => currentIndex !== index));
setCopilotModelSettings((current) => current.filter((_, currentIndex) => currentIndex !== index));
};
const selectClaudeModel = (selected) => {
@@ -530,8 +535,10 @@ export default function ConfigGeneratorCard({
<label className="flex items-center gap-2 text-xs font-medium text-text-muted">
Reasoning / thinking
<select
value={coworkThinking[model] || ""}
onChange={(event) => setCoworkThinking((current) => ({ ...current, [model]: event.target.value }))}
value={coworkModelSettings[index]?.thinking || ""}
onChange={(event) => setCoworkModelSettings((current) => current.map((settings, currentIndex) => (
currentIndex === index ? { ...settings, thinking: event.target.value } : settings
)))}
className="min-w-28 rounded-lg border border-border bg-bg-primary px-2 py-1.5 text-xs text-text-main outline-none focus:border-primary"
>
<option value="">Default</option>
@@ -539,7 +546,7 @@ export default function ConfigGeneratorCard({
</select>
</label>
)}
<Button type="button" variant="ghost" size="sm" onClick={() => removeCoworkModel(model, index)} aria-label={`Remove ${model} instance ${index + 1}`}>Remove</Button>
<Button type="button" variant="ghost" size="sm" onClick={() => removeCoworkModel(index)} aria-label={`Remove ${model} instance ${index + 1}`}>Remove</Button>
</div>
);
})}
@@ -560,7 +567,8 @@ export default function ConfigGeneratorCard({
<div className="flex flex-col gap-2">
{selectedModels.map((model, index) => {
const thinkingLevels = getThinkingLevelsForModel(model);
const tokens = copilotTokens[model] || DEFAULT_MODEL_TOKEN_LIMITS;
const modelSettings = copilotModelSettings[index] || {};
const tokens = modelSettings.tokens || DEFAULT_MODEL_TOKEN_LIMITS;
const inputOptions = getInputTokenOptions(tokens);
const outputOptions = getOutputTokenOptions(tokens);
const occurrence = getModelOccurrence(selectedModels, model, index);
@@ -568,14 +576,16 @@ export default function ConfigGeneratorCard({
<div key={`${model}-${index}`} className={`flex flex-col gap-2 rounded-lg border px-3 py-2 ${getDuplicateModelClass(occurrence)}`}>
<div className="flex items-center justify-between gap-2">
<span className="min-w-0 flex-1 break-all text-xs font-medium text-text-main">{model}{occurrence.total > 1 && <span className="ml-1 text-[10px] font-bold opacity-70">#{occurrence.occurrence}</span>}</span>
<Button type="button" variant="ghost" size="sm" onClick={() => removeCopilotModel(model, index)} aria-label={`Remove ${model} instance ${index + 1}`}>Remove</Button>
<Button type="button" variant="ghost" size="sm" onClick={() => removeCopilotModel(index)} aria-label={`Remove ${model} instance ${index + 1}`}>Remove</Button>
</div>
{thinkingLevels && (
<label className="flex items-center gap-2 text-xs font-medium text-text-muted">
Reasoning / thinking
<select
value={copilotThinking[model] || ""}
onChange={(event) => setCopilotThinking((current) => ({ ...current, [model]: event.target.value }))}
value={modelSettings.thinking || ""}
onChange={(event) => setCopilotModelSettings((current) => current.map((settings, currentIndex) => (
currentIndex === index ? { ...settings, thinking: event.target.value } : settings
)))}
className="min-w-28 rounded-lg border border-border bg-bg-primary px-2 py-1.5 text-xs text-text-main outline-none focus:border-primary"
>
<option value="">Default</option>
@@ -588,10 +598,11 @@ export default function ConfigGeneratorCard({
maxInputTokens
<select
value={tokens.maxInputTokens || ""}
onChange={(event) => setCopilotTokens((current) => ({
...current,
[model]: { ...current[model], maxInputTokens: event.target.value ? Number(event.target.value) : undefined },
}))}
onChange={(event) => setCopilotModelSettings((current) => current.map((settings, currentIndex) => (
currentIndex === index
? { ...settings, tokens: { ...settings.tokens, maxInputTokens: event.target.value ? Number(event.target.value) : undefined } }
: settings
)))}
className="min-w-20 rounded-lg border border-border bg-bg-primary px-2 py-1.5 text-xs text-text-main outline-none focus:border-primary"
>
{inputOptions.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
@@ -601,10 +612,11 @@ export default function ConfigGeneratorCard({
maxOutputTokens
<select
value={tokens.maxOutputTokens || ""}
onChange={(event) => setCopilotTokens((current) => ({
...current,
[model]: { ...current[model], maxOutputTokens: event.target.value ? Number(event.target.value) : undefined },
}))}
onChange={(event) => setCopilotModelSettings((current) => current.map((settings, currentIndex) => (
currentIndex === index
? { ...settings, tokens: { ...settings.tokens, maxOutputTokens: event.target.value ? Number(event.target.value) : undefined } }
: settings
)))}
className="min-w-20 rounded-lg border border-border bg-bg-primary px-2 py-1.5 text-xs text-text-main outline-none focus:border-primary"
>
{outputOptions.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
+8 -3
View File
@@ -188,9 +188,14 @@ function cleanCliToolConfigsSync(db, providerAliases, modelId) {
for (const field of ["opencodeModels", "selectedModels"]) {
if (!Array.isArray(config[field])) continue;
const removedModels = config[field].filter(deleted);
if (removedModels.length === 0) continue;
config[field] = config[field].filter((reference) => !deleted(reference));
const removedIndexes = config[field].flatMap((reference, index) => (deleted(reference) ? [index] : []));
if (removedIndexes.length === 0) continue;
const removedModels = removedIndexes.map((index) => config[field][index]);
config[field] = config[field].filter((_, index) => !removedIndexes.includes(index));
for (const settingsField of ["coworkModelSettings", "copilotModelSettings"]) {
if (!Array.isArray(config[settingsField])) continue;
config[settingsField] = config[settingsField].filter((_, index) => !removedIndexes.includes(index));
}
for (const mapField of ["coworkThinking", "copilotThinking", "copilotTokens"]) {
if (!config[mapField] || typeof config[mapField] !== "object") continue;
const nextMap = { ...config[mapField] };
+52 -24
View File
@@ -89,6 +89,22 @@ function normalizeThinkingMap(value, allowedKeys, field) {
return normalized;
}
function normalizeTokenLimits(value, field) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new CliToolConfigValidationError(`${field} must be an object`);
}
const normalized = {};
for (const tokenField of ["maxInputTokens", "maxOutputTokens"]) {
if (value[tokenField] === undefined || value[tokenField] === null || value[tokenField] === "") continue;
const number = Number(value[tokenField]);
if (!Number.isSafeInteger(number) || number <= 0 || number > MAX_TOKEN_LIMIT) {
throw new CliToolConfigValidationError(`${tokenField} must be a positive integer no greater than ${MAX_TOKEN_LIMIT}`);
}
normalized[tokenField] = number;
}
return normalized;
}
function normalizeApiKeyReference(input) {
const mode = input.apiKeyMode === undefined ? "managed" : input.apiKeyMode;
if (!['managed', 'custom'].includes(mode)) {
@@ -98,30 +114,43 @@ function normalizeApiKeyReference(input) {
return { apiKeyMode: mode, apiKeyId: mode === "managed" ? apiKeyId : null };
}
function normalizeTokenMap(value, selectedModels) {
if (value === undefined) return {};
if (!value || typeof value !== "object" || Array.isArray(value)) {
function normalizeCoworkModelSettings(value, selectedModels, legacyThinking) {
if (value !== undefined && !Array.isArray(value)) {
throw new CliToolConfigValidationError("coworkModelSettings must be an array");
}
const legacySettings = normalizeThinkingMap(legacyThinking, selectedModels, "coworkThinking");
return selectedModels.map((model, index) => {
const setting = value?.[index];
if (setting !== undefined && (!setting || typeof setting !== "object" || Array.isArray(setting))) {
throw new CliToolConfigValidationError(`coworkModelSettings.${index} must be an object`);
}
const thinking = normalizeThinking(setting?.thinking ?? legacySettings[model], `coworkModelSettings.${index}.thinking`);
return thinking ? { thinking } : {};
});
}
function normalizeCopilotModelSettings(value, selectedModels, legacyThinking, legacyTokens) {
if (value !== undefined && !Array.isArray(value)) {
throw new CliToolConfigValidationError("copilotModelSettings must be an array");
}
const legacyThinkingSettings = normalizeThinkingMap(legacyThinking, selectedModels, "copilotThinking");
const legacyTokenSettings = legacyTokens === undefined ? {} : legacyTokens;
if (!legacyTokenSettings || typeof legacyTokenSettings !== "object" || Array.isArray(legacyTokenSettings)) {
throw new CliToolConfigValidationError("copilotTokens must be an object");
}
const normalized = {};
for (const model of selectedModels) {
const limits = value[model];
if (limits === undefined) continue;
if (!limits || typeof limits !== "object" || Array.isArray(limits)) {
throw new CliToolConfigValidationError(`copilotTokens.${model} must be an object`);
return selectedModels.map((model, index) => {
const setting = value?.[index];
if (setting !== undefined && (!setting || typeof setting !== "object" || Array.isArray(setting))) {
throw new CliToolConfigValidationError(`copilotModelSettings.${index} must be an object`);
}
const next = {};
for (const field of ["maxInputTokens", "maxOutputTokens"]) {
if (limits[field] === undefined || limits[field] === null || limits[field] === "") continue;
const number = Number(limits[field]);
if (!Number.isSafeInteger(number) || number <= 0 || number > MAX_TOKEN_LIMIT) {
throw new CliToolConfigValidationError(`${field} must be a positive integer no greater than ${MAX_TOKEN_LIMIT}`);
}
next[field] = number;
}
if (Object.keys(next).length) normalized[model] = next;
}
return normalized;
const thinking = normalizeThinking(setting?.thinking ?? legacyThinkingSettings[model], `copilotModelSettings.${index}.thinking`);
const tokensSource = setting?.tokens ?? legacyTokenSettings[model];
const tokens = tokensSource === undefined ? {} : normalizeTokenLimits(tokensSource, `copilotModelSettings.${index}.tokens`);
return {
...(thinking ? { thinking } : {}),
...(Object.keys(tokens).length ? { tokens } : {}),
};
});
}
export function normalizeCliToolConfig(toolId, input) {
@@ -155,13 +184,12 @@ export function normalizeCliToolConfig(toolId, input) {
: (config.opencodeModels[0] || "");
} else if (toolId === "cowork") {
config.selectedModels = normalizeModels(input.selectedModels);
config.coworkThinking = normalizeThinkingMap(input.coworkThinking, config.selectedModels, "coworkThinking");
config.coworkModelSettings = normalizeCoworkModelSettings(input.coworkModelSettings, config.selectedModels, input.coworkThinking);
} else if (toolId === "cursor") {
config.selectedModels = normalizeModels(input.selectedModels);
} else if (toolId === "copilot") {
config.selectedModels = normalizeModels(input.selectedModels);
config.copilotThinking = normalizeThinkingMap(input.copilotThinking, config.selectedModels, "copilotThinking");
config.copilotTokens = normalizeTokenMap(input.copilotTokens, config.selectedModels);
config.copilotModelSettings = normalizeCopilotModelSettings(input.copilotModelSettings, config.selectedModels, input.copilotThinking, input.copilotTokens);
}
return config;
+31 -3
View File
@@ -54,7 +54,10 @@ describe("CLI tool configuration contract", () => {
apiKeyMode: "managed",
selectedModels: ["cc/a", "cc/a"],
coworkThinking: { "cc/a": "high", "stale/model": "low" },
})).toMatchObject({ selectedModels: ["cc/a", "cc/a"], coworkThinking: { "cc/a": "high" } });
})).toMatchObject({
selectedModels: ["cc/a", "cc/a"],
coworkModelSettings: [{ thinking: "high" }, { thinking: "high" }],
});
expect(normalizeCliToolConfig("cursor", {
apiKeyMode: "managed",
@@ -73,11 +76,36 @@ describe("CLI tool configuration contract", () => {
})).toEqual({
baseUrl: "https://router.example/v1",
selectedModels: ["cc/a", "cc/a"],
copilotThinking: { "cc/a": "high" },
copilotTokens: { "cc/a": { maxInputTokens: 100000, maxOutputTokens: 32000 } },
copilotModelSettings: [
{ thinking: "high", tokens: { maxInputTokens: 100000, maxOutputTokens: 32000 } },
{ thinking: "high", tokens: { maxInputTokens: 100000, maxOutputTokens: 32000 } },
],
});
});
it("keeps duplicate model settings independent by occurrence", () => {
expect(normalizeCliToolConfig("copilot", {
baseUrl: "https://router.example/v1",
selectedModels: ["cc/a", "cc/a"],
copilotModelSettings: [
{ thinking: "low", tokens: { maxInputTokens: 16000 } },
{ thinking: "high", tokens: { maxOutputTokens: 32000 } },
],
})).toMatchObject({
copilotModelSettings: [
{ thinking: "low", tokens: { maxInputTokens: 16000 } },
{ thinking: "high", tokens: { maxOutputTokens: 32000 } },
],
});
expect(normalizeCliToolConfig("cowork", {
baseUrl: "https://router.example",
apiKeyMode: "managed",
selectedModels: ["cc/a", "cc/a"],
coworkModelSettings: [{ thinking: "low" }, { thinking: "high" }],
})).toMatchObject({ coworkModelSettings: [{ thinking: "low" }, { thinking: "high" }] });
});
it("rejects plaintext secrets, invalid URLs, and invalid token limits", () => {
expect(() => normalizeCliToolConfig("claude", {
baseUrl: "https://router.example",
+1 -1
View File
@@ -161,7 +161,7 @@ describe("permanent model deletion", () => {
});
expect((await db.getCliToolConfig(cliUser.id, "cowork")).config).toMatchObject({
selectedModels: [`${providerPrefix}/gpt-keep`],
coworkThinking: { [`${providerPrefix}/gpt-keep`]: "low" },
coworkModelSettings: [{ thinking: "low" }],
});
const history = await db.getUsageHistory({});