- Added `vectordb` as a dependency in `package.json`. - Implemented `embedText` function in `src/main/ai/embeddings.ts` to handle text embeddings using GitHub Copilot OAuth token or OpenAI token. - Created `vectordb.ts` for managing LanceDB connection and embedding notes with upsert strategy. - Updated `index.ts` to initialize vector database and migrate existing notes on app ready. - Modified `router/index.ts` to fire-and-forget embedding calls on note creation and updates. - Enhanced `progress.txt` with detailed implementation notes and learnings regarding the integration.
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { getToken } from './token';
|
|
|
|
interface CopilotConfig {
|
|
copilot_tokens?: Record<string, string>;
|
|
}
|
|
|
|
/**
|
|
* Read the GitHub Copilot OAuth token from the CLI config file.
|
|
* Stored at ~/.copilot/config.json under copilot_tokens["{host}:{login}"].
|
|
* Returns the first available token, or null if unavailable.
|
|
*/
|
|
function readCopilotToken(): string | null {
|
|
try {
|
|
const raw = fs.readFileSync(
|
|
path.join(os.homedir(), '.copilot', 'config.json'),
|
|
'utf-8',
|
|
);
|
|
const cfg = JSON.parse(raw) as CopilotConfig;
|
|
const vals = Object.values(cfg.copilot_tokens ?? {});
|
|
return vals[0] ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Embed a single text string using the best available credentials.
|
|
*
|
|
* Priority:
|
|
* 1. GitHub Copilot CLI token → OpenAI-compatible embeddings endpoint at
|
|
* https://api.githubcopilot.com
|
|
* 2. Stored OpenAI token → standard OpenAI embeddings API
|
|
*
|
|
* Throws if no credentials are available or the API call fails.
|
|
* Callers must .catch() this and handle the error without rejecting
|
|
* the surrounding tRPC mutation.
|
|
*/
|
|
export async function embedText(text: string): Promise<number[]> {
|
|
const { OpenAIEmbeddings } = await import('@langchain/openai');
|
|
|
|
const copilotToken = readCopilotToken();
|
|
|
|
let embeddingsInstance;
|
|
if (copilotToken) {
|
|
embeddingsInstance = new OpenAIEmbeddings({
|
|
apiKey: copilotToken,
|
|
model: 'text-embedding-3-small',
|
|
configuration: { baseURL: 'https://api.githubcopilot.com' },
|
|
});
|
|
} else {
|
|
const openaiToken = await getToken('openai');
|
|
if (!openaiToken) {
|
|
throw new Error(
|
|
'[Embeddings] No credentials available. Authenticate with Copilot CLI or add an OpenAI token in Settings.',
|
|
);
|
|
}
|
|
embeddingsInstance = new OpenAIEmbeddings({
|
|
apiKey: openaiToken,
|
|
model: 'text-embedding-3-small',
|
|
});
|
|
}
|
|
|
|
// embedDocuments returns number[][] — cast explicitly to satisfy strict TS
|
|
const results = (await embeddingsInstance.embedDocuments([text])) as number[][];
|
|
const vector = results[0] as number[] | undefined;
|
|
if (!vector || vector.length === 0) {
|
|
throw new Error('[Embeddings] Empty vector returned from embedding API');
|
|
}
|
|
return vector;
|
|
}
|