import type { ChatKitStore, ClientToolContinuationClaim, StoreContext } from './store'; import type { ThreadMetadata, ThreadRef, ThreadItem, Attachment, Page, StoreItemType } from './types'; import { type RunListOptions, type RunRecordData, type RunStepRecordData, type RunStore } from '../runs/store'; import { type TaskListOptions, type TaskRecordData, type TaskStore } from '../tasks/store'; import { type TaskAuthBindingRepository } from '../tasks/TaskAuthBinding'; import { type AiCredentialSecretDecoder } from '../../ai/service/AiCredentialSecret'; import { CredentialStatus } from '../../credential/schema/types'; import { type ServerGroupReconcilerService } from '../reconciler'; import { type ReaderAiConfig } from '../../document/ReaderAiConfig'; export interface PodChatKitStoreOptions { tokenEndpoint: string; serverGroupReconcilerService?: ServerGroupReconcilerService; /** * Reads a Pod credential secret (AI Connections envelope, `plaintext-v1` * payload, or a legacy bare `apiKey`). Defaults to the plaintext decoder; the * container injects a vault-backed one so wrapped Cloud secrets resolve too. */ credentialSecretDecoder?: AiCredentialSecretDecoder; /** Deployment whose credentials this runtime prefers when a Pod holds several. */ deployment?: string; } /** * Pod-based ChatKit Store implementation * * 数据模型映射: * - ChatKit thread = Thread (sioc:Thread) * - ChatKit thread item = Message (meeting:Message) * - Thread 通过 sioc:has_parent 归属到 Chat 或 Task command surface。 * - ChatKit/API 的 opaque 字段不进入 Pod 持久 metadata;内部存储路径从 parent/id 派生。 */ export declare class PodChatKitStore implements ChatKitStore, RunStore, TaskStore, TaskAuthBindingRepository { private readonly logger; private readonly tokenEndpoint; private readonly serverGroupReconcilerService?; private readonly credentialSecretDecoder; private readonly deployment?; /** 默认 Chat 容器 ID */ private static readonly DEFAULT_CHAT_ID; constructor(options: PodChatKitStoreOptions); /** Build a Pod-scoped context for trusted internal maintenance jobs. */ createTrustedContext(input: { webId: string; podUrl: string; fetch: typeof fetch; }): Promise; /** * 获取认证后的 drizzle 实例 (缓存到 context 中) */ private getDb; private getClientCredentialsAccessToken; private createAccessTokenFetch; /** * 从 context 获取 webId */ private getWebId; private derivePodBaseUrl; private normalizePodBaseUrl; private readPodUrlFromRuntimeSource; private readPodUrlFromDatabase; private readExplicitPodBaseUrl; private ensurePodBaseUrlCache; /** * 将 ThreadStatus 对象转为字符串 */ private statusToString; /** * 将字符串转为 ThreadStatus 对象 */ private stringToStatus; private readMetadataString; private isBaseRelativeChatResourceId; private buildChatResourceId; private buildMessageParentResourceId; private chatSurfaceIdFromResourceId; private chatSurfaceIdFromResource; private commandSurfaceFromResourceRef; private resolveTaskParentResource; private resolveExplicitThreadParentResource; private resolveThreadParent; private threadStoredMetadata; private mentionsFromUserMessageContent; /** * 确保 Chat 容器存在,如果不存在则创建 */ private ensureChat; /** * 将 ThreadRecord 转为 ThreadMetadata。 * Pod 中以 thread.parent 作为权威归属关系,metadata 只返回业务扩展字段。 */ private threadRecordToMetadata; private timestampToIso; private isoToTimestamp; private parseJsonObject; private jsonObjectOrNull; private getXpodMetadata; private withXpodMetadata; private withoutXpodMetadata; private withTaskAuthBindingMetadata; private parseTaskAuthBinding; private isBaseRelativeDataResourceId; private isResourceLikeId; private isThreadResourceId; private isMessageResourceId; private buildThreadResourceId; private generateThreadResourceId; private buildMessageResourceId; private generateMessageResourceId; private resolveDataResource; private baseRelativeIdFromResource; private baseRelativeIdFromPodPath; private runRecordToData; private runStepRecordToData; private taskRecordToData; /** * 将 MessageRecord 转为 ThreadItem * thread_id 返回 Message 所属的 Thread ID */ private messageRecordToItem; private isAbsoluteHttpResource; private parseThreadResource; private normalizeThreadCacheKey; private parseThreadResourceId; private resolveThreadRef; /** * 缓存 Thread -> surfaceId 映射 */ private cacheThreadSurfaceId; /** * 缓存完整的 Thread metadata */ private cacheThreadMetadata; /** * 从缓存获取 Thread metadata */ private getCachedThreadMetadata; private getCachedThreadMetadataList; private getCachedFetch; private getCachedPodBaseUrl; private parseSparqlBindingValue; private selectMessagesForThread; private datePathFromTimestamp; generateThreadId(_context: StoreContext): string; generateItemId(itemType: StoreItemType, thread: ThreadMetadata, _context: StoreContext): string; loadThread(thread: ThreadRef, context: StoreContext): Promise; saveThread(thread: ThreadMetadata, context: StoreContext): Promise; loadThreads(limit: number, after: string | undefined, order: string, context: StoreContext): Promise>; deleteThread(thread: ThreadRef, context: StoreContext): Promise; loadThreadItems(thread: ThreadRef, after: string | undefined, limit: number, order: string, context: StoreContext): Promise>; addThreadItem(thread: ThreadRef, item: ThreadItem, context: StoreContext): Promise; private reconcileGroupUserMessage; private recentlyCreatedIds; saveItem(thread: ThreadRef, item: ThreadItem, context: StoreContext): Promise; /** * 直接使用 SPARQL UPDATE PATCH 更新消息内容 * 避免 drizzle-solid UPDATE 的 bug */ private directPatchMessage; private directDeleteMessage; loadItem(thread: ThreadRef, itemId: string, context: StoreContext): Promise; deleteThreadItem(thread: ThreadRef, itemId: string, context: StoreContext): Promise; saveAttachment(attachment: Attachment, _context: StoreContext): Promise; loadAttachment(attachmentId: string, _context: StoreContext): Promise; deleteAttachment(attachmentId: string, _context: StoreContext): Promise; saveRun(run: RunRecordData, context: StoreContext): Promise; loadRun(id: string, context: StoreContext): Promise; listRuns(options: RunListOptions, context: StoreContext): Promise; appendRunStep(event: RunStepRecordData, context: StoreContext): Promise; loadRunSteps(runId: string, context: StoreContext): Promise; claimRun(input: { runId: string; leaseOwner: string; leaseExpiresAt: number; now: number; }, context: StoreContext): Promise; claimClientToolContinuation(input: { threadRef: ThreadRef; itemId: string; claimId: string; leaseExpiresAt: number; now: number; }, context: StoreContext): Promise; releaseClientToolContinuation(claim: ClientToolContinuationClaim, now: number, context: StoreContext): Promise; saveTask(task: TaskRecordData, context: StoreContext): Promise; loadTask(taskId: string, context: StoreContext): Promise; listTasks(options: TaskListOptions, context: StoreContext): Promise; saveTaskAuthCredential(input: { id: string; apiKey: string; displayName?: string; expiresAt?: number; }, context: StoreContext): Promise<{ id: string; service: string; status: string; apiKey?: string | null; label?: string | null; oauthExpiresAt?: string | Date | null; createdAt?: string | Date | null; }>; loadTaskAuthCredential(id: string, context: StoreContext): Promise<{ id: string; service: string; status: string; apiKey?: string | null; label?: string | null; oauthExpiresAt?: string | Date | null; createdAt?: string | Date | null; } | undefined>; /** * AI 配置结果 */ static readonly AiConfigResult: { new (): { providerId: string; baseUrl: string; proxyUrl?: string; apiKey: string; credentialId: string; }; }; /** * 从 Pod 获取 AI 配置(Provider + Credential) * 复用已缓存的 Session,避免重复登录 */ private extractProviderId; private pushAvailableModel; private resolvePodResource; private findProviderForCredential; private findConfiguredEmbeddingModel; /** * The embedding model this Pod may index with. * * AI Connections model selection is the allowlist: once a provider has active * embedding-typed selections, only those models are usable, and the first one * becomes the default when the Pod names none. A provider without an explicit * embedding selection keeps the configured model (or the provider default), so * existing Pods keep indexing. */ private resolveEmbeddingModel; private selectedEmbeddingModels; private sortAiCredentialCandidates; private isTruthyValue; private isDeploymentCredential; private timestampValue; private parseIntegerValue; private extractModelId; private queryAiConfigFromSettingsSparql; /** * AI Connections stores the secret in `encryptedSecret`; older rows carry a * bare `apiKey`. Resolve whichever the row has, so a key entered once is usable * by chat and by embedding/indexing alike. */ private resolveCredentialSecret; private credentialEnabledFromMetadata; private parseMetadataValue; getAiConfig(context: StoreContext): Promise<{ providerId: string; baseUrl: string; proxyUrl?: string; defaultModel?: string; embeddingModel?: string; apiKey: string; credentialId: string; } | undefined>; getReaderConfig(context: StoreContext, preferredProviderId?: string): Promise; listAvailableModels(context: StoreContext): Promise; /** * 更新凭据状态(如 429 限流) * 复用已缓存的 Session */ updateCredentialStatus(context: StoreContext, credentialId: string, status: CredentialStatus, options?: { rateLimitResetAt?: Date; incrementFailCount?: boolean; }): Promise; /** * 记录凭据使用成功(重置 failCount,更新 lastUsedAt) */ recordCredentialSuccess(context: StoreContext, credentialId: string): Promise; }