Files
gitmost/apps/server/src/database/migrations/20260618T120000-ai-embedding-credentials.ts
vvzvlad a7f244053b feat(ai): separate base URL and API key for chat vs embedding model
Per-workspace AI provider config previously shared a single base URL and
a single API key between the chat model and the embedding model. Add
dedicated, optional embedding endpoint/token that fall back to the chat
values when empty, preserving backward compatibility.

- db: new migration adds nullable `embedding_api_key_enc` to
  `ai_provider_credentials`; chat key stays in `api_key_enc`
- repo: add `upsertEmbeddingKey` / `clearEmbeddingKey` (on-conflict
  touches only its own column, so chat/embedding keys never overwrite)
- ai-settings.service: store non-secret `embeddingBaseUrl`; resolve()
  applies fallback (embeddingBaseUrl || baseUrl; embedding key || chat
  key); getMasked() exposes raw `embeddingBaseUrl` + `hasEmbeddingApiKey`,
  never the key; update() handles the embedding key write-only
- ai.service: getEmbeddingModel() builds openai/gemini/ollama with the
  embedding-specific URL/key; chat path unchanged
- client: new "Embedding base URL" and "Embedding API key" fields with
  fallback hints and a clear-key action

Requires running the DB migration on deploy.
2026-06-18 01:33:45 +03:00

19 lines
651 B
TypeScript

import { type Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
// Encrypted, embedding-specific provider key. Separate from `api_key_enc`
// (the chat key) so the chat model and the embedding model can use different
// tokens. When NULL, the embedding model falls back to `api_key_enc`.
await db.schema
.alterTable('ai_provider_credentials')
.addColumn('embedding_api_key_enc', 'text', (col) => col)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable('ai_provider_credentials')
.dropColumn('embedding_api_key_enc')
.execute();
}