UNPKG

@unified-llm/core

Version:

Unified LLM interface (in-memory).

451 lines 21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.threadRepository = exports.ThreadRepository = void 0; const drizzle_orm_1 = require("drizzle-orm"); const connection_1 = require("./connection"); const schema_1 = require("./schema"); const uuid_1 = require("uuid"); class ThreadRepository { constructor(dbPath) { this.dbPromise = (0, connection_1.getDatabase)(dbPath); // Promise<LibSQLDatabase | null> } /* ────────────── ヘルパ ────────────── */ async getDb() { return await this.dbPromise; } async isDisabled() { return (await this.getDb()) === null; } /* ========== スレッド管理 ========== */ /** 新規作成 */ async createThread(config = {}) { var _a, _b, _c, _d, _e, _f, _g; const now = new Date(); const newThread = { id: (_a = config.threadId) !== null && _a !== void 0 ? _a : `thread_${(0, uuid_1.v4)()}`, title: config.title, description: config.description, createdAt: now, updatedAt: now, createdBy: config.createdBy, isActive: true, tags: config.tags ? JSON.stringify(config.tags) : null, metadata: config.metadata ? JSON.stringify(config.metadata) : null, }; if (await this.isDisabled()) { return { ...newThread, title: (_b = newThread.title) !== null && _b !== void 0 ? _b : null, description: (_c = newThread.description) !== null && _c !== void 0 ? _c : null, createdBy: (_d = newThread.createdBy) !== null && _d !== void 0 ? _d : null, tags: (_e = newThread.tags) !== null && _e !== void 0 ? _e : null, metadata: (_f = newThread.metadata) !== null && _f !== void 0 ? _f : null, isActive: (_g = newThread.isActive) !== null && _g !== void 0 ? _g : null, }; } const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const [thread] = await db.insert(schema_1.threads).values(newThread).returning(); return thread; } /** 取得 */ async getThread(threadId) { if (await this.isDisabled()) return null; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const [thread] = await db .select() .from(schema_1.threads) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.threads.id, threadId), (0, drizzle_orm_1.eq)(schema_1.threads.isActive, true))); return thread !== null && thread !== void 0 ? thread : null; } /** 更新 */ async updateThread(threadId, updates) { if (await this.isDisabled()) return null; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const updateData = { ...updates, updatedAt: new Date() }; if (updates.tags) updateData.tags = JSON.stringify(updates.tags); if (updates.metadata) updateData.metadata = JSON.stringify(updates.metadata); const [updated] = await db .update(schema_1.threads) .set(updateData) .where((0, drizzle_orm_1.eq)(schema_1.threads.id, threadId)) .returning(); return updated !== null && updated !== void 0 ? updated : null; } /** 論理削除 */ async deleteThread(threadId) { var _a; if (await this.isDisabled()) return false; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const res = await db .update(schema_1.threads) .set({ isActive: false, updatedAt: new Date() }) .where((0, drizzle_orm_1.eq)(schema_1.threads.id, threadId)) .run(); const affected = typeof res.rowsAffected === 'bigint' ? Number(res.rowsAffected) : (_a = res.rowsAffected) !== null && _a !== void 0 ? _a : 0; return affected > 0; } /** スレッド一覧 */ async listThreads(options) { var _a, _b, _c, _d, _e, _f; if (await this.isDisabled()) return []; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 50; const offset = (_b = options === null || options === void 0 ? void 0 : options.offset) !== null && _b !== void 0 ? _b : 0; const conditions = [(0, drizzle_orm_1.eq)(schema_1.threads.isActive, true)]; if (options === null || options === void 0 ? void 0 : options.createdBy) conditions.push((0, drizzle_orm_1.eq)(schema_1.threads.createdBy, options.createdBy)); const base = await db .select({ id: schema_1.threads.id, title: schema_1.threads.title, description: schema_1.threads.description, updatedAt: schema_1.threads.updatedAt, tags: schema_1.threads.tags, messageCount: (0, drizzle_orm_1.sql) `count(distinct ${schema_1.messages.id})`, }) .from(schema_1.threads) .leftJoin(schema_1.messages, (0, drizzle_orm_1.eq)(schema_1.messages.threadId, schema_1.threads.id)) .where((0, drizzle_orm_1.and)(...conditions)) .groupBy(schema_1.threads.id) .orderBy((0, drizzle_orm_1.desc)(schema_1.threads.updatedAt)) .limit(limit) .offset(offset); const summaries = []; for (const row of base) { const participants = await this.getActiveParticipants(row.id); summaries.push({ id: row.id, title: (_c = row.title) !== null && _c !== void 0 ? _c : undefined, description: (_d = row.description) !== null && _d !== void 0 ? _d : undefined, messageCount: (_e = row.messageCount) !== null && _e !== void 0 ? _e : 0, participantCount: participants.length, lastActivity: row.updatedAt, activeParticipants: participants.map((p) => p.clientId), tags: row.tags ? JSON.parse(row.tags) : undefined, }); } if ((_f = options === null || options === void 0 ? void 0 : options.tags) === null || _f === void 0 ? void 0 : _f.length) { return summaries.filter((s) => { var _a; return (_a = s.tags) === null || _a === void 0 ? void 0 : _a.some((t) => { var _a, _b; return (_b = (_a = options.tags) === null || _a === void 0 ? void 0 : _a.includes(t)) !== null && _b !== void 0 ? _b : false; }); }); } return summaries; } /* ========== 参加者管理 ========== */ /** 参加 */ async joinThread(threadId, clientId, options = {}) { var _a, _b, _c, _d, _e; const now = new Date(); const newP = { id: `participant_${(0, uuid_1.v4)()}`, threadId, clientId, joinedAt: now, leftAt: null, role: (_a = options.role) !== null && _a !== void 0 ? _a : 'participant', nickname: options.nickname, metadata: options.metadata ? JSON.stringify(options.metadata) : null, }; if (await this.isDisabled()) { return { ...newP, nickname: (_b = newP.nickname) !== null && _b !== void 0 ? _b : null, metadata: (_c = newP.metadata) !== null && _c !== void 0 ? _c : null, role: (_d = newP.role) !== null && _d !== void 0 ? _d : null, leftAt: (_e = newP.leftAt) !== null && _e !== void 0 ? _e : null, }; } const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const existing = await db .select() .from(schema_1.threadParticipants) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.threadParticipants.threadId, threadId), (0, drizzle_orm_1.eq)(schema_1.threadParticipants.clientId, clientId), (0, drizzle_orm_1.isNull)(schema_1.threadParticipants.leftAt))); if (existing.length) { throw new Error(`LLMClient ${clientId} is already participating in thread ${threadId}`); } const [participant] = await db .insert(schema_1.threadParticipants) .values(newP) .returning(); await db .update(schema_1.threads) .set({ updatedAt: now }) .where((0, drizzle_orm_1.eq)(schema_1.threads.id, threadId)) .run(); return participant; } /** 離脱 */ async leaveThread(threadId, clientId) { var _a; if (await this.isDisabled()) return false; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const now = new Date(); const res = await db .update(schema_1.threadParticipants) .set({ leftAt: now }) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.threadParticipants.threadId, threadId), (0, drizzle_orm_1.eq)(schema_1.threadParticipants.clientId, clientId), (0, drizzle_orm_1.isNull)(schema_1.threadParticipants.leftAt))) .run(); const affected = typeof res.rowsAffected === 'bigint' ? Number(res.rowsAffected) : (_a = res.rowsAffected) !== null && _a !== void 0 ? _a : 0; if (affected > 0) { await db .update(schema_1.threads) .set({ updatedAt: now }) .where((0, drizzle_orm_1.eq)(schema_1.threads.id, threadId)) .run(); } return affected > 0; } /** アクティブ参加者 */ async getActiveParticipants(threadId) { if (await this.isDisabled()) return []; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const rows = await db .select({ participant: schema_1.threadParticipants, client: schema_1.llmClients, }) .from(schema_1.threadParticipants) .innerJoin(schema_1.llmClients, (0, drizzle_orm_1.eq)(schema_1.threadParticipants.clientId, schema_1.llmClients.id)) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.threadParticipants.threadId, threadId), (0, drizzle_orm_1.isNull)(schema_1.threadParticipants.leftAt))) .orderBy((0, drizzle_orm_1.asc)(schema_1.threadParticipants.joinedAt)); return rows.map((r) => ({ ...r.participant, client: r.client })); } /** ある時点以降の参加者 */ async getParticipantsSince(threadId, since) { if (await this.isDisabled()) return []; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const rows = await db .select({ participant: schema_1.threadParticipants, client: schema_1.llmClients, }) .from(schema_1.threadParticipants) .innerJoin(schema_1.llmClients, (0, drizzle_orm_1.eq)(schema_1.threadParticipants.clientId, schema_1.llmClients.id)) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.threadParticipants.threadId, threadId), (0, drizzle_orm_1.sql) `${schema_1.threadParticipants.joinedAt} >= ${since.getTime()}`, (0, drizzle_orm_1.isNull)(schema_1.threadParticipants.leftAt))) .orderBy((0, drizzle_orm_1.asc)(schema_1.threadParticipants.joinedAt)); return rows.map((r) => ({ ...r.participant, client: r.client })); } /* ========== メッセージ管理 ========== */ /** 追加 */ async addMessage(data) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; const newMsg = { id: `msg_${(0, uuid_1.v4)()}`, threadId: data.threadId, clientId: data.clientId, role: data.role, content: data.content, toolCalls: data.toolCalls, toolResults: data.toolResults, timestamp: new Date(), sequence: 1, parentMessageId: data.parentMessageId, isEdited: false, editedAt: null, tokens: data.tokens, cost: data.cost, metadata: data.metadata, }; if (await this.isDisabled()) { return { ...newMsg, parentMessageId: (_a = newMsg.parentMessageId) !== null && _a !== void 0 ? _a : null, metadata: (_b = newMsg.metadata) !== null && _b !== void 0 ? _b : null, toolCalls: (_c = newMsg.toolCalls) !== null && _c !== void 0 ? _c : null, toolResults: (_d = newMsg.toolResults) !== null && _d !== void 0 ? _d : null, tokens: (_e = newMsg.tokens) !== null && _e !== void 0 ? _e : null, cost: (_f = newMsg.cost) !== null && _f !== void 0 ? _f : null, sequence: (_g = newMsg.sequence) !== null && _g !== void 0 ? _g : null, threadId: (_h = newMsg.threadId) !== null && _h !== void 0 ? _h : null, clientId: (_j = newMsg.clientId) !== null && _j !== void 0 ? _j : null, editedAt: (_k = newMsg.editedAt) !== null && _k !== void 0 ? _k : null, isEdited: (_l = newMsg.isEdited) !== null && _l !== void 0 ? _l : null, }; } const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const [cnt] = await db .select({ count: (0, drizzle_orm_1.sql) `count(*)` }) .from(schema_1.messages) .where((0, drizzle_orm_1.eq)(schema_1.messages.threadId, data.threadId)); newMsg.sequence = ((_m = cnt.count) !== null && _m !== void 0 ? _m : 0) + 1; const [msg] = await db.insert(schema_1.messages).values(newMsg).returning(); await db .update(schema_1.threads) .set({ updatedAt: new Date() }) .where((0, drizzle_orm_1.eq)(schema_1.threads.id, data.threadId)) .run(); return msg; } /** スレッドのメッセージ取得 */ async getThreadMessages(threadId, options) { var _a, _b; if (await this.isDisabled()) return []; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const cond = [(0, drizzle_orm_1.eq)(schema_1.messages.threadId, threadId)]; if (options === null || options === void 0 ? void 0 : options.since) { cond.push((0, drizzle_orm_1.sql) `${schema_1.messages.timestamp} >= ${options.since.getTime()}`); } return await db .select() .from(schema_1.messages) .where((0, drizzle_orm_1.and)(...cond)) .orderBy((0, drizzle_orm_1.asc)(schema_1.messages.sequence)) .limit((_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 1000) .offset((_b = options === null || options === void 0 ? void 0 : options.offset) !== null && _b !== void 0 ? _b : 0); } /** 参加以降に見えるメッセージ */ async getVisibleMessages(threadId, clientId, options) { var _a; if (await this.isDisabled()) return []; const db = await this.getDb(); if (!db) throw new Error('Database connection is not available'); const [participant] = await db .select() .from(schema_1.threadParticipants) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.threadParticipants.threadId, threadId), (0, drizzle_orm_1.eq)(schema_1.threadParticipants.clientId, clientId))) .orderBy((0, drizzle_orm_1.asc)(schema_1.threadParticipants.joinedAt)) .limit(1); if (!participant) { throw new Error(`LLMClient ${clientId} is not a participant in thread ${threadId}`); } const sinceTs = participant.joinedAt.getTime(); let msgs; if (options === null || options === void 0 ? void 0 : options.includeContext) { const after = await db .select() .from(schema_1.messages) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.messages.threadId, threadId), (0, drizzle_orm_1.sql) `${schema_1.messages.timestamp} >= ${sinceTs}`)) .orderBy((0, drizzle_orm_1.asc)(schema_1.messages.sequence)); const before = await db .select() .from(schema_1.messages) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.messages.threadId, threadId), (0, drizzle_orm_1.sql) `${schema_1.messages.timestamp} < ${sinceTs}`)) .orderBy((0, drizzle_orm_1.desc)(schema_1.messages.sequence)) .limit((_a = options.contextLimit) !== null && _a !== void 0 ? _a : 20); msgs = [...before.reverse(), ...after]; } else { msgs = await db .select() .from(schema_1.messages) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.messages.threadId, threadId), (0, drizzle_orm_1.sql) `${schema_1.messages.timestamp} >= ${sinceTs}`)) .orderBy((0, drizzle_orm_1.asc)(schema_1.messages.sequence)); } return msgs; } /* ========== 統一変換 / 統計 ========== */ async getConversationThread(threadId) { var _a; if (await this.isDisabled()) return null; const thread = await this.getThread(threadId); if (!thread) return null; const msgs = await this.getThreadMessages(threadId); return { id: thread.id, title: (_a = thread.title) !== null && _a !== void 0 ? _a : undefined, messages: msgs.map(this.convertToUnifiedMessage), created_at: thread.createdAt, updated_at: thread.updatedAt, metadata: { description: thread.description, created_by: thread.createdBy, tags: thread.tags ? JSON.parse(thread.tags) : undefined, ...(thread.metadata ? JSON.parse(thread.metadata) : {}), }, }; } convertToUnifiedMessage(db) { var _a; return { id: db.id, role: db.role, content: db.content, created_at: db.timestamp, metadata: { client_id: db.clientId, tokens: db.tokens, cost: db.cost, tool_calls: db.toolCalls, tool_results: db.toolResults, sequence: db.sequence, parent_message_id: db.parentMessageId, is_edited: db.isEdited, edited_at: db.editedAt, ...((_a = db.metadata) !== null && _a !== void 0 ? _a : {}), }, }; } /** 統計 */ async getThreadStats(threadId) { if (await this.isDisabled()) { return { messageCount: 0, participantCount: 0, totalTokens: 0, totalCost: 0, participants: [], }; } const msgs = await this.getThreadMessages(threadId); const parts = await this.getActiveParticipants(threadId); const base = msgs.reduce((acc, m) => { var _a, _b; acc.messageCount++; acc.totalTokens += (_a = m.tokens) !== null && _a !== void 0 ? _a : 0; acc.totalCost += (_b = m.cost) !== null && _b !== void 0 ? _b : 0; return acc; }, { messageCount: 0, totalTokens: 0, totalCost: 0 }); const perParticipant = parts.map((p) => ({ clientId: p.clientId, messageCount: msgs.filter((m) => m.clientId === p.clientId).length, joinedAt: p.joinedAt, })); return { ...base, participantCount: parts.length, participants: perParticipant, }; } } exports.ThreadRepository = ThreadRepository; /* シングルトン */ exports.threadRepository = new ThreadRepository(); //# sourceMappingURL=thread-repository.js.map