mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
boostrap new model managmeent
This commit is contained in:
parent
46863aeeb1
commit
d9388116bc
8 changed files with 947 additions and 1143 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -256,6 +256,7 @@ export default [
|
|||
{ type: "backend", pattern: "src/agentMode/backends/*", capture: ["name"] },
|
||||
{ type: "ui", pattern: "src/agentMode/ui" },
|
||||
{ type: "skills", pattern: "src/agentMode/skills" },
|
||||
{ type: "modelmgmt", pattern: "src/modelManagement" },
|
||||
{ type: "host", pattern: "src/**" },
|
||||
],
|
||||
},
|
||||
|
|
@ -295,7 +296,16 @@ export default [
|
|||
},
|
||||
},
|
||||
},
|
||||
{ from: { type: "host" }, allow: { to: { type: ["host", "barrel"] } } },
|
||||
// modelManagement: self-contained module. Host code may
|
||||
// freely reach into the module at the boundary layer; the
|
||||
// barrel-only entry rule (no deep imports of
|
||||
// `@/modelManagement/types/*`) is enforced by
|
||||
// `no-restricted-imports` patterns further down.
|
||||
{ from: { type: "modelmgmt" }, allow: { to: { type: "modelmgmt" } } },
|
||||
{
|
||||
from: { type: "host" },
|
||||
allow: { to: { type: ["host", "barrel", "modelmgmt"] } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -311,8 +321,19 @@ export default [
|
|||
},
|
||||
},
|
||||
|
||||
// Only acp/ may import `@agentclientprotocol/sdk`. Tests are exempted via
|
||||
// the test block; acp/ itself is exempted below.
|
||||
// Two path-based import fences, combined in one block (flat config
|
||||
// replaces — does not merge — rule values when the same rule key
|
||||
// appears across matching blocks, so both fences MUST live here):
|
||||
//
|
||||
// 1. `@agentclientprotocol/sdk` — confined to src/agentMode/acp/.
|
||||
// Other agent-mode layers depend on the session-domain types
|
||||
// in @/agentMode/session/types instead.
|
||||
// 2. `@/modelManagement/*` deep imports — host code must enter the
|
||||
// modelManagement module via its barrel (`@/modelManagement`).
|
||||
// This replaces a `modelmgmt-barrel` boundary element with the
|
||||
// lighter no-restricted-imports mechanism already used for (1).
|
||||
//
|
||||
// Module-internal files are exempted in the override blocks below.
|
||||
{
|
||||
files: ["src/**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
|
|
@ -326,6 +347,13 @@ export default [
|
|||
"ACP wire types are confined to src/agentMode/acp/. session/, sdk/, ui/, backends/, and skills/ should depend on the session-domain types in @/agentMode/session/types instead. See src/agentMode/AGENTS.md.",
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
{
|
||||
group: ["@/modelManagement/*"],
|
||||
message:
|
||||
"Import from @/modelManagement (the barrel) only. Deep imports of @/modelManagement/types/* are not allowed from outside the module. See src/modelManagement/AGENTS.md.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -336,6 +364,12 @@ export default [
|
|||
"no-restricted-imports": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/modelManagement/**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
"no-restricted-imports": "off",
|
||||
},
|
||||
},
|
||||
|
||||
// TypeScript-specific overrides (the @typescript-eslint plugin is registered
|
||||
// by obsidianmd's recommended config only for .ts/.tsx files).
|
||||
|
|
|
|||
21
src/modelManagement/AGENTS.md
Normal file
21
src/modelManagement/AGENTS.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Model Management — layer rules
|
||||
|
||||
The chat-model data model: providers, configured models, per-backend
|
||||
model selection, and the transient catalog types used during setup.
|
||||
|
||||
## Architecture in one paragraph
|
||||
|
||||
`models.dev` (the catalog) is consumed **only during setup**. When the
|
||||
user adds a provider via BYOK, when an agent is configured, a `Provider` row
|
||||
is created with an
|
||||
`origin` discriminator (`byok` / `agent` / `copilot-plus`). Each
|
||||
provider has zero or more `ConfiguredModel` rows under it, each
|
||||
embedding a `ModelInfo` snapshot copied from the catalog (or
|
||||
hand-typed for self-hosted endpoints). The plugin keeps working
|
||||
when the catalog is unreachable because every runtime-relevant field
|
||||
lives in `ConfiguredModel.info`. The four backends that curate model
|
||||
selection — `chat`, `opencode`, `claude-code`, `codex` — each persist a
|
||||
`BackendConfig` listing `configuredModelId`s they expose in their
|
||||
picker. The BYOK settings tab filters `providers` by
|
||||
`origin.kind === "byok"`; the chat-model factory dispatches purely on
|
||||
`Provider.providerType`.
|
||||
1
src/modelManagement/CLAUDE.md
Symbolic link
1
src/modelManagement/CLAUDE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENTS.md
|
||||
13
src/modelManagement/index.ts
Normal file
13
src/modelManagement/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Public surface of the model-management module. Host code must import
|
||||
// from this barrel — deep imports of `@/modelManagement/types/*` are
|
||||
// blocked by `no-restricted-imports` patterns in eslint.config.mjs.
|
||||
|
||||
export type { CatalogProvider, ModelInfo, ProviderType } from "./types/catalog";
|
||||
export type {
|
||||
AgentType,
|
||||
BackendConfig,
|
||||
BackendType,
|
||||
ConfiguredModel,
|
||||
Provider,
|
||||
ProviderOrigin,
|
||||
} from "./types/persisted";
|
||||
73
src/modelManagement/types/catalog.ts
Normal file
73
src/modelManagement/types/catalog.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Catalog types — transient, setup-time only.
|
||||
*
|
||||
* The catalog (e.g. `models.dev`) is consumed during the BYOK / agent /
|
||||
* Plus setup flows to scaffold defaults. Once a model is configured, its
|
||||
* metadata is snapshotted onto the persisted `ConfiguredModel.info` row
|
||||
* and the catalog is never consulted again. Nothing in this file is
|
||||
* persisted to disk.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Closed dispatch set the chat-model factory switches on.
|
||||
*
|
||||
* The user never types this — the BYOK / agent / Plus setup wizard
|
||||
* assigns it from the catalog (via the `npm` field on `models.dev`
|
||||
* entries) or from a built-in template (Ollama, LMStudio, Custom
|
||||
* OpenAI-compatible, Azure OpenAI, AWS Bedrock).
|
||||
*
|
||||
* "anthropic" → @langchain/anthropic
|
||||
* "openai-compatible" → @langchain/openai with custom baseUrl
|
||||
* (OpenAI, Mistral, Groq, OpenRouter, Together,
|
||||
* DeepSeek, Ollama, LMStudio, custom proxies)
|
||||
* "google" → @langchain/google-genai
|
||||
* "azure" → @langchain/openai (Azure path)
|
||||
* "bedrock" → @langchain/aws
|
||||
*/
|
||||
export type ProviderType = "anthropic" | "openai-compatible" | "google" | "azure" | "bedrock";
|
||||
|
||||
/**
|
||||
* Description of a single model. Used both as the catalog's per-model
|
||||
* record and embedded into `ConfiguredModel.info`. One shape — the
|
||||
* downstream code can iterate either catalog-side or persisted-side
|
||||
* models with the same fields.
|
||||
*
|
||||
* The catalog fetcher populates whatever `models.dev` exposes. Self-
|
||||
* hosted custom models (Ollama hand-typed) populate `id` + `displayName`
|
||||
* and leave the metadata fields empty.
|
||||
*/
|
||||
export interface ModelInfo {
|
||||
/** Wire-form id passed to the SDK ("claude-sonnet-4-5", "gpt-5", …). */
|
||||
id: string;
|
||||
displayName: string;
|
||||
modalities?: { input: string[]; output: string[] };
|
||||
limits?: { context: number; output: number; input?: number };
|
||||
reasoning?: boolean;
|
||||
toolCall?: boolean;
|
||||
cost?: { input: number; output: number; cacheRead?: number; cacheWrite?: number };
|
||||
releaseDate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A provider as listed by the catalog. Lives in memory during a setup
|
||||
* wizard pass; never persisted.
|
||||
*/
|
||||
export interface CatalogProvider {
|
||||
/** Catalog provider id ("anthropic", "openai", "opencode-zen", …). */
|
||||
id: string;
|
||||
/** From the catalog's `name` field. */
|
||||
displayName: string;
|
||||
/** From the catalog's `api` field. Pre-fills `Provider.baseUrl`. */
|
||||
defaultBaseUrl: string;
|
||||
/**
|
||||
* Derived from the catalog's `npm` field by the catalog fetcher.
|
||||
* "@ai-sdk/anthropic" → "anthropic"
|
||||
* "@ai-sdk/google" → "google"
|
||||
* "@ai-sdk/azure" → "azure"
|
||||
* "@ai-sdk/bedrock" → "bedrock"
|
||||
* anything else → "openai-compatible"
|
||||
*/
|
||||
providerType: ProviderType;
|
||||
/** Keyed by `ModelInfo.id` (the wire-form id). */
|
||||
models: Record<string, ModelInfo>;
|
||||
}
|
||||
133
src/modelManagement/types/persisted.ts
Normal file
133
src/modelManagement/types/persisted.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Persisted types — written to `CopilotSettings` (settings wiring lands
|
||||
* in a follow-up PR). Once a row exists, the catalog is no longer
|
||||
* consulted at runtime.
|
||||
*/
|
||||
|
||||
import type { ModelInfo, ProviderType } from "./catalog";
|
||||
|
||||
/**
|
||||
* The three agent backends. Each can own its own `Provider`(s) and
|
||||
* reports a runtime model inventory.
|
||||
*/
|
||||
export type AgentType = "opencode" | "claude-code" | "codex";
|
||||
|
||||
/**
|
||||
* The broader set used for per-backend model curation: the three agents
|
||||
* plus `"chat"` (Simple Chat). Used as the map key for `BackendConfig`
|
||||
* in `settings.backends: Record<BackendType, BackendConfig>`.
|
||||
*
|
||||
* "chat" → Simple Chat picker
|
||||
* "opencode" → OpenCode agent picker
|
||||
* "claude-code" → Claude Code agent picker
|
||||
* "codex" → Codex agent picker
|
||||
*
|
||||
* Everything else either has no curated selection (vault-qa / project /
|
||||
* quick-chat use their own per-feature model id; custom commands store
|
||||
* a model id directly) or is handled by another module.
|
||||
*/
|
||||
export type BackendType = AgentType | "chat";
|
||||
|
||||
/**
|
||||
* Where a `Provider` came from. Drives which settings tab shows it and
|
||||
* which code path created it. Downstream consumption is uniform — the
|
||||
* chat-model factory dispatches purely on `Provider.providerType`.
|
||||
*
|
||||
* "byok" → user added via the BYOK settings tab
|
||||
* "agent" → auto-created when an agent was set up; the agent
|
||||
* owns credentials and routing. Chat doesn't appear
|
||||
* here because chat doesn't own `Provider`s — its
|
||||
* models come from BYOK + Plus + provider-sharing
|
||||
* with backed agents.
|
||||
* "copilot-plus" → auto-created when the user signed into Plus
|
||||
*/
|
||||
export type ProviderOrigin =
|
||||
| { kind: "byok" }
|
||||
| { kind: "agent"; agentType: AgentType }
|
||||
| { kind: "copilot-plus" };
|
||||
|
||||
/**
|
||||
* A configured connection to a model provider.
|
||||
*
|
||||
* Multi-instance is supported within `byok`: two BYOK `Provider` rows
|
||||
* can share the same `providerType` (e.g. two Anthropic accounts) —
|
||||
* distinguished by `providerId` and `displayName`. Agent and Plus
|
||||
* origins typically have exactly one row each, but the data model
|
||||
* doesn't enforce singleton.
|
||||
*/
|
||||
export interface Provider {
|
||||
/** UUID; primary key. Also the keychain namespace (BYOK only). */
|
||||
providerId: string;
|
||||
/** Single dispatch field. See `ProviderType` in catalog.ts. */
|
||||
providerType: ProviderType;
|
||||
/** User-editable label (BYOK) or auto-assigned (agent / Plus). */
|
||||
displayName: string;
|
||||
/** Overrides what the wizard pre-filled from catalog / template. */
|
||||
baseUrl?: string;
|
||||
/**
|
||||
* Obsidian keychain entry id. `null` for providers that don't take an
|
||||
* API key (Ollama, LMStudio, some agent-owned providers).
|
||||
*/
|
||||
apiKeyKeychainId?: string | null;
|
||||
/**
|
||||
* Opaque per-`providerType` payload.
|
||||
* azure: { azureDeploymentName, azureApiVersion, azureInstanceName }
|
||||
* bedrock: { bedrockRegion }
|
||||
* openai: { openAIOrgId }
|
||||
* Kept because those adapters can't function without it.
|
||||
*/
|
||||
extras?: Record<string, unknown>;
|
||||
origin: ProviderOrigin;
|
||||
addedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model the plugin knows about. Self-sufficient at runtime — once
|
||||
* this row exists, the catalog is no longer consulted.
|
||||
*
|
||||
* "Configured" means "set up in the plugin, ready to use." Applies to
|
||||
* BYOK (user-added), agent-owned (auto-added at agent setup), and Plus
|
||||
* (auto-added at Plus sign-in) models alike — the difference is which
|
||||
* `Provider` this row belongs to and that provider's `origin`.
|
||||
*
|
||||
* "Configured" is distinct from "enrolled": a `ConfiguredModel` row
|
||||
* says the model exists on a provider; a backend separately enrolls
|
||||
* some subset of configured models for its picker via
|
||||
* `BackendConfig.enabledModels`. Auto-enrollment is the default UX, but
|
||||
* the two layers stay separate so per-backend pruning is expressible.
|
||||
*/
|
||||
export interface ConfiguredModel {
|
||||
/** UUID; primary key. */
|
||||
configuredModelId: string;
|
||||
/** FK to `Provider.providerId`. */
|
||||
providerId: string;
|
||||
/**
|
||||
* Embedded model description (wire-form id, display name, optional
|
||||
* metadata). The setup flow populates `info` from a `ModelInfo`
|
||||
* pulled from the catalog. Self-hosted custom models populate
|
||||
* `info.id` + `info.displayName` only.
|
||||
*
|
||||
* Uniqueness constraint: `(providerId, info.id)`.
|
||||
*/
|
||||
info: ModelInfo;
|
||||
configuredAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-backend model selection. Persisted.
|
||||
*
|
||||
* Backend identity is the map key in the future settings shape
|
||||
* (`settings.backends: Record<BackendType, BackendConfig>`), not a
|
||||
* field on this row.
|
||||
*
|
||||
* `enabledModels` and `defaultModel` reference `ConfiguredModel` rows
|
||||
* by `configuredModelId`. The picker shows every entry regardless of
|
||||
* any runtime ACP inventory; unreachable models surface at request
|
||||
* time, not as silent filters.
|
||||
*/
|
||||
export interface BackendConfig {
|
||||
/** Each entry is a `ConfiguredModel.configuredModelId`. */
|
||||
enabledModels: string[];
|
||||
/** A `ConfiguredModel.configuredModelId` or `null`. */
|
||||
defaultModel?: string | null;
|
||||
}
|
||||
Loading…
Reference in a new issue