mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
* feat(chat): power legacy chat from the model-management "chat" backend Legacy chat, project chat, vault QA, quick command, and quick ask now select models from the model-management "chat" backend (backends.chat.enabledModels) instead of the legacy activeModels / defaultModelKey. A new ConfiguredModel→CustomModel bridge feeds the existing ChatModelManager (the stubbed v4 ChatModelFactory is untouched), and a new "Quick Chat" curation section under the Agents settings tab lets users choose which BYOK/Plus models appear in the chat picker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(skills): apply prettier formatting to builtinSkills.test.ts Collapse a multi-line expect() onto one line to satisfy the project's prettier config (surfaced when running npm run format). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(chat): stabilize chat backend model selections * fix(chat): address chat backend review regressions * fix(chat): show legacy chat models in a flat list Drop the per-provider _group on chat picker entries so the legacy chat model picker renders a single flat list (provider icons inline, no section headers), matching pre-migration behavior. Agent Mode keeps its per-backend headers via its own helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(chat): remove dead per-model parameter editing After the model-management chat migration, per-model parameter editing is unreachable: the chat selection is now a configuredModelId UUID, so the ChatSettingsPopover editor (gated on a legacy activeModels match) never renders, and TokenLimitWarning's activeModels lookup always misses and falls back to global settings. v4 agent mode no longer supports per-model parameter editing. - ChatSettingsPopover: drop the model-param plumbing (originalModel / bridgedModel / canEditModelParameters, local-model save/debounce, ModelParametersEditor); keep System Prompt + Disable Builtin controls. - TokenLimitWarning: always open the global Copilot settings tab. - Delete the now-orphaned ModelEditDialog and ModelParametersEditor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(chat): drop dead legacy model-key fallback in streaming session The streaming chat session's chain cache key fell back to name|provider when configuredModelId was absent. Both callers (Quick Ask, custom-command chat) now source their model from useResolvedChatBackendModel -> configuredModelToCustomModel, which always stamps configuredModelId, so the fallback was unreachable. Derive the key directly from configuredModelId; a model without one is treated as no usable model via the existing guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(chat): remove test-only getLegacyChatModelKey helper The singular getLegacyChatModelKey was exported but had no production callers — only the barrel re-export and one test assertion referenced it. Remove it (and its export + assertion); the load-bearing plural getLegacyChatModelKeys, which powers legacy-key resolution in isChatModelSelectionForEntry, is kept and now carries the rationale comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(model-management): dedupe catalog-id to provider map Drop the duplicate CATALOG_ID_TO_LEGACY_PROVIDER table in chatModelSelection and reuse the now-exported CATALOG_ID_TO_CHAT_PROVIDER from configuredModelToCustomModel (extended with anthropic/google for the legacy-key path). Single source of truth for catalog-id to ChatModelProviders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import type { ModelManagementApi } from "@/modelManagement/createModelManagement";
|
|
import type { ConfiguredModel, Provider } from "@/modelManagement/types/persisted";
|
|
import type { EnabledBackendEntry } from "@/modelManagement/types/runtime";
|
|
|
|
import { resolveChatBackendModel } from "./resolveChatBackendModel";
|
|
|
|
function provider(id: string, overrides: Partial<Provider> = {}): Provider {
|
|
return {
|
|
providerId: id,
|
|
providerType: "anthropic",
|
|
displayName: id,
|
|
origin: { kind: "byok" },
|
|
addedAt: 0,
|
|
apiKeyKeychainId: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function okEntry(configuredModelId: string, prov: Provider): EnabledBackendEntry {
|
|
const configuredModel: ConfiguredModel = {
|
|
configuredModelId,
|
|
providerId: prov.providerId,
|
|
info: { id: `${configuredModelId}-wire`, displayName: configuredModelId },
|
|
configuredAt: 0,
|
|
};
|
|
return { configuredModelId, state: "ok", configuredModel, provider: prov };
|
|
}
|
|
|
|
/** Minimal api stub exposing only what the resolver reads. */
|
|
function makeApi(
|
|
entries: readonly EnabledBackendEntry[],
|
|
keyByProvider: Record<string, string | null> = {}
|
|
): Pick<ModelManagementApi, "backendConfigRegistry" | "providerRegistry"> {
|
|
return {
|
|
backendConfigRegistry: {
|
|
resolveEnabled: (backend: string) => (backend === "chat" ? entries : []),
|
|
} as unknown as ModelManagementApi["backendConfigRegistry"],
|
|
providerRegistry: {
|
|
getApiKey: async (providerId: string) => keyByProvider[providerId] ?? null,
|
|
} as unknown as ModelManagementApi["providerRegistry"],
|
|
};
|
|
}
|
|
|
|
describe("resolveChatBackendModel", () => {
|
|
it("resolves the preferred model when it is enabled", async () => {
|
|
const p = provider("p1");
|
|
const api = makeApi([okEntry("a", p), okEntry("b", p)], { p1: "key" });
|
|
|
|
const result = await resolveChatBackendModel(api, "b");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.configuredModelId).toBe("b");
|
|
expect(result.customModel.apiKey).toBe("key");
|
|
}
|
|
});
|
|
|
|
it("falls back to the first enabled model when the preferred id is gone", async () => {
|
|
const p = provider("p1");
|
|
const api = makeApi([okEntry("a", p), okEntry("b", p)], { p1: "key" });
|
|
|
|
const result = await resolveChatBackendModel(api, "stale-uuid");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) expect(result.configuredModelId).toBe("a");
|
|
});
|
|
|
|
it("resolves a legacy name|provider selection", async () => {
|
|
const p = provider("p1");
|
|
const api = makeApi([okEntry("a", p)], { p1: "key" });
|
|
|
|
const result = await resolveChatBackendModel(api, "a-wire|anthropic");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) expect(result.configuredModelId).toBe("a");
|
|
});
|
|
|
|
it("falls back to the first enabled model when no preference is given", async () => {
|
|
const p = provider("p1");
|
|
const api = makeApi([okEntry("a", p)], { p1: "key" });
|
|
|
|
const result = await resolveChatBackendModel(api, undefined);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) expect(result.configuredModelId).toBe("a");
|
|
});
|
|
|
|
it("returns empty when nothing is enabled", async () => {
|
|
const api = makeApi([]);
|
|
const result = await resolveChatBackendModel(api, "anything");
|
|
expect(result).toEqual({ ok: false, reason: "empty" });
|
|
});
|
|
|
|
it("skips broken refs and resolves the first ok entry", async () => {
|
|
const p = provider("p1");
|
|
const broken: EnabledBackendEntry = { configuredModelId: "x", state: "broken" };
|
|
const api = makeApi([broken, okEntry("a", p)], { p1: "key" });
|
|
|
|
const result = await resolveChatBackendModel(api, "x");
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) expect(result.configuredModelId).toBe("a");
|
|
});
|
|
});
|