koishi-plugin-chatluna-studio 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/index.js +45313 -0
  4. package/dist/style.css +1 -0
  5. package/lib/chatluna/error.d.ts +15 -0
  6. package/lib/chatluna/runtime.d.ts +25 -0
  7. package/lib/chatluna/session-tracker.d.ts +98 -0
  8. package/lib/chatluna/usage.d.ts +46 -0
  9. package/lib/console-contract.d.ts +61 -0
  10. package/lib/console.d.ts +39 -0
  11. package/lib/evidence-kind.d.ts +61 -0
  12. package/lib/evidence-preview-text.d.ts +13 -0
  13. package/lib/index.d.ts +42 -0
  14. package/lib/index.js +4579 -0
  15. package/lib/model-evidence/index.d.ts +15 -0
  16. package/lib/model-evidence/metrics.d.ts +11 -0
  17. package/lib/model-evidence/request.d.ts +7 -0
  18. package/lib/model-evidence/response.d.ts +7 -0
  19. package/lib/model-evidence/shared.d.ts +37 -0
  20. package/lib/model-evidence/transport.d.ts +13 -0
  21. package/lib/model-evidence/types.d.ts +104 -0
  22. package/lib/model-evidence/usage.d.ts +10 -0
  23. package/lib/model-request-collector.d.ts +32 -0
  24. package/lib/model-request-trajectory.d.ts +21 -0
  25. package/lib/model-request-variables.d.ts +16 -0
  26. package/lib/model-request.d.ts +158 -0
  27. package/lib/persistence.d.ts +85 -0
  28. package/lib/presets/evidence-match.d.ts +9 -0
  29. package/lib/presets/index.d.ts +6 -0
  30. package/lib/presets/repository.d.ts +86 -0
  31. package/lib/presets/runtime-correlation.d.ts +16 -0
  32. package/lib/presets/service.d.ts +69 -0
  33. package/lib/presets/source-document.d.ts +2 -0
  34. package/lib/presets/types.d.ts +63 -0
  35. package/lib/record-store.d.ts +85 -0
  36. package/lib/types.d.ts +270 -0
  37. package/package.json +110 -0
@@ -0,0 +1,15 @@
1
+ import type { ModelEvidenceProjection, ModelEvidenceSlice } from './types';
2
+ export * from './types';
3
+ export { countMessageCharacters, countToolCallCharacters, countToolDefinitionCharacters, } from './metrics';
4
+ /**
5
+ * 模型证据投影的唯一对外入口。也是唯一从请求体读取协议结构的地方。
6
+ *
7
+ * 输入只有一条模型请求记录的证据切片(请求体、响应原文、响应 transport 格式)。
8
+ * provider、URL、生命周期状态、HTTP 状态、空间归属、耗时、最终用量、模型名称
9
+ * 与原始请求体的字段数都不参与协议识别,它们继续由模型请求记录与现有读取 module 拥有:
10
+ * 模型名称是采集那一刻观察到的记录事实,字段数是原始 JSON 事实,两者都不是协议事实。
11
+ *
12
+ * 协议识别完全由内部 shape-driven adapter 完成;调用方不注册也不选择具体 adapter。
13
+ * 投影每次处理当前可用证据,不做全局缓存,也不深拷贝原始子树。
14
+ */
15
+ export declare function projectModelEvidence(slice: ModelEvidenceSlice): ModelEvidenceProjection;
@@ -0,0 +1,11 @@
1
+ import type { ModelEvidenceMessage, ModelEvidenceToolCall, ModelEvidenceToolDefinition } from './types';
2
+ /**
3
+ * 证据字符数度量。
4
+ *
5
+ * 请求组成图与分析卡片头部必须使用同一个公式,否则同一条证据会在两个视图里显示不同字符数。
6
+ * 每项只统计它自己能证明的内容:消息不含工具调用参数,工具调用只算参数,工具定义算整段声明。
7
+ */
8
+ export declare function countMessageCharacters(message: ModelEvidenceMessage): number;
9
+ export declare function countToolCallCharacters(call: ModelEvidenceToolCall): number;
10
+ export declare function countToolDefinitionCharacters(definition: ModelEvidenceToolDefinition): number;
11
+ export declare function serializedLength(value: unknown): number;
@@ -0,0 +1,7 @@
1
+ import type { ModelEvidenceDiagnostic, ModelEvidenceMessage, ModelEvidenceToolDefinition } from './types';
2
+ export interface RequestEvidence {
3
+ messages: ModelEvidenceMessage[];
4
+ toolDefinitions: ModelEvidenceToolDefinition[];
5
+ diagnostics: ModelEvidenceDiagnostic[];
6
+ }
7
+ export declare function projectRequestEvidence(body: unknown): RequestEvidence;
@@ -0,0 +1,7 @@
1
+ import type { ModelEvidenceDiagnostic, ModelEvidenceResponseEvent, ModelEvidenceTransport } from './types';
2
+ export interface ResponseEvidence {
3
+ transport: ModelEvidenceTransport;
4
+ events: ModelEvidenceResponseEvent[];
5
+ diagnostics: ModelEvidenceDiagnostic[];
6
+ }
7
+ export declare function projectResponseEvidence(raw: string | undefined, format: 'json' | 'text' | 'sse' | undefined): ResponseEvidence;
@@ -0,0 +1,37 @@
1
+ import type { ModelEvidenceContentPart, ModelEvidenceRegion, ModelEvidenceSource } from './types';
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ export declare function stringValue(value: unknown): string | undefined;
4
+ export declare function stringifyValue(value: unknown): string;
5
+ export declare function source(region: ModelEvidenceRegion, path: readonly string[], value: unknown): ModelEvidenceSource;
6
+ /**
7
+ * 证据身份由区域、语义种类和原始结构路径派生。
8
+ *
9
+ * 不使用角色内序号:同一份原始证据在轨迹、组成图和分析视图里必须得到同一个 id,
10
+ * 而角色内序号会随折叠条件、会话聚合范围和分组顺序变化。
11
+ * 语义种类参与身份,让同一个父节点上的消息与工具调用不会互相覆盖。
12
+ */
13
+ export declare function evidenceId(region: ModelEvidenceRegion, kind: string, path: readonly string[]): string;
14
+ export declare function semanticText(parts: readonly ModelEvidenceContentPart[]): string;
15
+ /** 通用(OpenAI / Anthropic / AI SDK)内容分片归一化,保留原始顺序与多模态种类。 */
16
+ export declare function normalizeContentParts(value: unknown): ModelEvidenceContentPart[];
17
+ /** Gemini part 归一化:inlineData / fileData 与 OpenAI 的多模态字段完全不同。 */
18
+ export declare function normalizeGeminiPart(value: unknown): ModelEvidenceContentPart[];
19
+ /** AI SDK 工具输出是 typed 值(`{type:'json'|'text', value}`),不能直接 stringify 整个包装。 */
20
+ export declare function normalizeToolOutput(value: unknown): string;
21
+ /**
22
+ * 读取显式调用标识。
23
+ *
24
+ * 只认专门表示“配对到哪次调用”的字段。裸 `id` 不在其中:消息、Responses item 和 AI SDK part
25
+ * 都带自己的条目 id,把条目 id 当调用标识会凭空造出工具调用与工具结果的配对关系,
26
+ * 而领域约定要求缺少显式调用标识时不做任何推断配对。
27
+ */
28
+ export declare function readCallId(value: Record<string, unknown>): string | undefined;
29
+ /**
30
+ * 读取工具调用/工具结果对象自身的调用标识。
31
+ *
32
+ * 仅当传入记录本身就是调用或结果对象时才可用:OpenAI `tool_calls[i]`、Anthropic `tool_use`、
33
+ * Gemini `functionCall` / `functionResponse` 的 `id` 就是调用标识本身。
34
+ * 传入消息或 item 包装对象会退化成按条目 id 推断配对,是错误用法。
35
+ */
36
+ export declare function readCallObjectId(value: Record<string, unknown>): string | undefined;
37
+ export declare function readToolName(value: Record<string, unknown>): string | undefined;
@@ -0,0 +1,13 @@
1
+ import type { ModelEvidenceDiagnostic, ModelEvidenceTransport } from './types';
2
+ /** 一个待语义解释的响应 payload。`streaming` 说明它来自 SSE 分片而不是完整响应体。 */
3
+ export interface ResponsePayload {
4
+ path: readonly string[];
5
+ value: unknown;
6
+ streaming: boolean;
7
+ }
8
+ export interface ResponseTransportEvidence {
9
+ transport: ModelEvidenceTransport;
10
+ payloads: ResponsePayload[];
11
+ diagnostics: ModelEvidenceDiagnostic[];
12
+ }
13
+ export declare function parseResponseTransport(raw: string | undefined, format: 'json' | 'text' | 'sse' | undefined): ResponseTransportEvidence;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * 模型证据投影的对外契约。
3
+ *
4
+ * 这里只描述“从原始模型证据能证明什么”,不描述任何展示目标、DOM、排序或交互状态。
5
+ * 调用方(模型请求轨迹、模型请求对话视图)只依赖本文件的类型和 `projectModelEvidence` 一个入口。
6
+ */
7
+ /** 证据所属区域。请求体与响应原文是两份独立的权威事实,不建立跨区域总顺序。 */
8
+ export type ModelEvidenceRegion = 'request' | 'response';
9
+ /**
10
+ * 一条规范项的原始来源。
11
+ *
12
+ * `path` 是原始结构路径(对象键或数组下标),`value` 是原始子树的只读引用;
13
+ * 投影不深拷贝原始请求和响应,避免大响应被重复放大。
14
+ */
15
+ export interface ModelEvidenceSource {
16
+ region: ModelEvidenceRegion;
17
+ path: readonly string[];
18
+ value: unknown;
19
+ }
20
+ export type ModelEvidenceContentPartKind = 'text' | 'image' | 'file' | 'audio' | 'video' | 'other';
21
+ export interface ModelEvidenceContentPart {
22
+ kind: ModelEvidenceContentPartKind;
23
+ value: string;
24
+ mimeType?: string;
25
+ }
26
+ export type ModelEvidenceMessageRole = 'system' | 'user' | 'assistant' | 'tool';
27
+ /** 请求体中的工具调用证据。只有存在显式调用标识时才带 `callId`。 */
28
+ export interface ModelEvidenceToolCall {
29
+ evidenceId: string;
30
+ name: string;
31
+ callId?: string;
32
+ arguments?: string;
33
+ sources: readonly ModelEvidenceSource[];
34
+ }
35
+ export interface ModelEvidenceMessage {
36
+ evidenceId: string;
37
+ role: ModelEvidenceMessageRole;
38
+ /** 语义正文:文本与无法归类的分片,按原始顺序拼接。 */
39
+ text: string;
40
+ contentParts: readonly ModelEvidenceContentPart[];
41
+ reasoning?: string;
42
+ toolCalls: readonly ModelEvidenceToolCall[];
43
+ /** 工具结果消息的显式调用标识。缺失时不得按名称或位置推断配对。 */
44
+ toolCallId?: string;
45
+ toolName?: string;
46
+ sources: readonly ModelEvidenceSource[];
47
+ }
48
+ export interface ModelEvidenceToolDefinition {
49
+ evidenceId: string;
50
+ name: string;
51
+ description: string;
52
+ parameters?: unknown;
53
+ propertyCount: number;
54
+ requiredFields: readonly string[];
55
+ sources: readonly ModelEvidenceSource[];
56
+ }
57
+ export type ModelEvidenceResponseEventKind = 'content' | 'reasoning' | 'tool-call' | 'tool-result' | 'finish-reason' | 'usage' | 'unknown';
58
+ export interface ModelEvidenceUsageCandidate {
59
+ inputTokens?: number;
60
+ outputTokens?: number;
61
+ reasoningTokens?: number;
62
+ cachedTokens?: number;
63
+ totalTokens?: number;
64
+ }
65
+ export interface ModelEvidenceResponseEvent {
66
+ evidenceId: string;
67
+ kind: ModelEvidenceResponseEventKind;
68
+ /** content / reasoning / finish-reason 的文本事实。 */
69
+ text?: string;
70
+ name?: string;
71
+ callId?: string;
72
+ arguments?: string;
73
+ /** 响应体中的原始用量对象,仅作为候选证据。 */
74
+ usage?: Record<string, unknown>;
75
+ normalizedUsage?: ModelEvidenceUsageCandidate;
76
+ sources: readonly ModelEvidenceSource[];
77
+ }
78
+ export type ModelEvidenceDiagnosticCode = 'request-body-unsupported' | 'request-container-ambiguous' | 'response-body-malformed' | 'response-payload-unsupported' | 'response-payload-ambiguous';
79
+ export type ModelEvidenceDiagnosticSeverity = 'info' | 'warning' | 'error';
80
+ /** 结构化诊断只表达代码、严重程度和来源,不包含任何页面文案。 */
81
+ export interface ModelEvidenceDiagnostic {
82
+ code: ModelEvidenceDiagnosticCode;
83
+ severity: ModelEvidenceDiagnosticSeverity;
84
+ source: ModelEvidenceSource;
85
+ }
86
+ export type ModelEvidenceTransportKind = 'json' | 'sse' | 'text' | 'empty';
87
+ /** 响应 transport 解析结果。原始 JSON / SSE / TEXT 查看能力直接消费 `value`。 */
88
+ export interface ModelEvidenceTransport {
89
+ kind: ModelEvidenceTransportKind;
90
+ value?: unknown;
91
+ }
92
+ /** 投影入口接收的证据切片:只有请求体、响应原文和响应 transport 格式。 */
93
+ export interface ModelEvidenceSlice {
94
+ requestBody?: unknown;
95
+ responseBodyRaw?: string;
96
+ responseBodyFormat?: 'json' | 'text' | 'sse';
97
+ }
98
+ export interface ModelEvidenceProjection {
99
+ requestMessages: readonly ModelEvidenceMessage[];
100
+ toolDefinitions: readonly ModelEvidenceToolDefinition[];
101
+ responseEvents: readonly ModelEvidenceResponseEvent[];
102
+ responseTransport: ModelEvidenceTransport;
103
+ diagnostics: readonly ModelEvidenceDiagnostic[];
104
+ }
@@ -0,0 +1,10 @@
1
+ import type { ModelEvidenceUsageCandidate } from './types';
2
+ /**
3
+ * 响应体用量候选归一化。
4
+ *
5
+ * 这是协议知识(`prompt_tokens` / `input_tokens` / `promptTokenCount` 各家命名不同),
6
+ * 因此归属共享投影;最终采用哪个用量仍由调用方按 ADR-0059 的优先级决定。
7
+ */
8
+ export declare function normalizeUsageCandidate(usage: Record<string, unknown> | undefined): ModelEvidenceUsageCandidate | undefined;
9
+ /** 流式响应会分批上报用量;后到的残缺片段不得把已知数值覆盖成更小值。 */
10
+ export declare function mergeUsage(current: Record<string, unknown> | undefined, incoming: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,32 @@
1
+ import type { ChatLunaTurnResolution } from './chatluna/session-tracker';
2
+ import { type StudioModelRequestStore } from './model-request';
3
+ import type { StudioModelRequestRecord, StudioPresetRuntimeSnapshot } from './types';
4
+ export interface ChatLunaPluginLike {
5
+ prototype: {
6
+ fetch: (...args: any[]) => Promise<any>;
7
+ };
8
+ }
9
+ /** 预设运行时快照的查询目标:一次归属成功的模型请求所属的(机器人,会话)。 */
10
+ export interface ModelRequestPresetSnapshotTarget {
11
+ botId: string;
12
+ conversationId: string;
13
+ }
14
+ export declare function isKnownChatModelRequestUrl(url: string): boolean;
15
+ export declare function sanitizeModelRequestUrl(raw: string): string;
16
+ export declare function resolveChatLunaPluginClass(baseDir?: string): ChatLunaPluginLike | undefined;
17
+ export interface InstallModelRequestCollectorOptions {
18
+ plugin?: ChatLunaPluginLike;
19
+ baseDir?: string;
20
+ /** 全部模型请求都写进同一个记录库;归属与否只是记录上的一个字段。 */
21
+ store: StudioModelRequestStore;
22
+ /**
23
+ * 这次请求属于哪一轮对话,以及要不要记下来。
24
+ *
25
+ * 判不出归属时记成未归属;确定由虚拟机器人触发时整条记录都不写。
26
+ */
27
+ resolveTurn: () => ChatLunaTurnResolution;
28
+ getActivePresetSnapshots?: (target: ModelRequestPresetSnapshotTarget) => StudioPresetRuntimeSnapshot[];
29
+ onRecordAppended?: (record: StudioModelRequestRecord) => void;
30
+ }
31
+ export declare function inferModelResponseBodyFormat(contentType: string | undefined, raw: string): 'json' | 'text' | 'sse';
32
+ export declare function installModelRequestCollector(options: InstallModelRequestCollectorOptions): () => void;
@@ -0,0 +1,21 @@
1
+ import type { StudioModelRequestDetail, StudioModelRequestRecord, StudioModelRequestStatus, StudioModelRequestTrajectory } from './types';
2
+ import { type StudioModelRequestStore } from './model-request';
3
+ interface BuildStudioModelRequestTrajectoryOptions {
4
+ record: StudioModelRequestDetail;
5
+ mode: 'request' | 'conversation';
6
+ /** 会话模式下同会话的原始记录,按 sequence 正序;缺省表示无法聚合,只呈现当前请求。 */
7
+ conversationRecords?: readonly StudioModelRequestRecord[];
8
+ }
9
+ /**
10
+ * 读取会话模式需要的同会话记录。记录库的读取是异步的,而轨迹投影本身是纯函数;
11
+ * 这个薄封装是唯一需要 Store 的地方,投影因此保持可单测、可复用。
12
+ */
13
+ export declare function readStudioModelRequestConversationRecords(record: Pick<StudioModelRequestDetail, 'entities'>, store: StudioModelRequestStore | undefined): Promise<StudioModelRequestRecord[] | undefined>;
14
+ export declare function buildStudioModelRequestTrajectoryFromStore(options: {
15
+ record: StudioModelRequestDetail;
16
+ mode: 'request' | 'conversation';
17
+ store?: StudioModelRequestStore;
18
+ }): Promise<StudioModelRequestTrajectory>;
19
+ export declare function buildStudioModelRequestTrajectory(options: BuildStudioModelRequestTrajectoryOptions): StudioModelRequestTrajectory;
20
+ export declare function statusLabel(status: StudioModelRequestStatus): string;
21
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { ModelEvidenceProjection } from './model-evidence';
2
+ import type { StudioModelRequestVariable, StudioPresetRuntimeSnapshot } from './types';
3
+ /**
4
+ * 从运行时预设快照与一份已算好的模型证据投影派生模型请求变量。
5
+ *
6
+ * 本函数只读投影的请求消息,不解释请求体、响应原文或响应 transport 格式,
7
+ * 因此不接收模型请求记录,也不自己决定是否运行投影——那由调用方(模型请求记录的读取投影)决定。
8
+ */
9
+ export declare function deriveModelRequestVariables(snapshots: readonly StudioPresetRuntimeSnapshot[], evidence: ModelEvidenceProjection): StudioModelRequestVariable[];
10
+ /**
11
+ * 模型请求变量未被唯一证明时的状态说明文案。
12
+ *
13
+ * 轨迹行预览、分析导航项的搜索文本与右侧变量卡片正文共用这一份文案;
14
+ * 任何一侧自己再写一份,搜索这些文案时就会出现一侧命中、另一侧置灰。
15
+ */
16
+ export declare function modelRequestVariableStatusLabel(status: StudioModelRequestVariable['status']): string;
@@ -0,0 +1,158 @@
1
+ import { InMemoryRecordRows, type StudioRecordScopeSummary } from './record-store';
2
+ import type { GetStudioModelRequestRecordsInput, StudioChatLunaRequestError, StudioModelRequestAttribution, StudioModelRequestCapacity, StudioModelRequestDetail, StudioModelRequestEntities, StudioModelRequestError, StudioModelRequestFacets, StudioModelRequestListItem, StudioModelRequestRecord, StudioModelRequestRecordsPage, StudioModelRequestStatus, StudioModelResponseBodyFormat, StudioPresetRuntimeSnapshot, StudioModelResponseBodyStatus } from './types';
3
+ export declare const DEFAULT_MODEL_REQUEST_RECORD_LIMIT = 500;
4
+ export declare const DEFAULT_MODEL_REQUEST_RECORD_MAX_BYTES: number;
5
+ export declare const DEFAULT_MODEL_REQUEST_PAGE_SIZE = 50;
6
+ export declare const MAX_MODEL_REQUEST_PAGE_SIZE = 200;
7
+ /** 筛选下拉聚合扫描的记录条数上限;只读记录头,不触碰请求体与响应原文。 */
8
+ export declare const MODEL_REQUEST_FACET_SCAN_LIMIT = 500;
9
+ export interface StudioModelRequestPersistenceQuery {
10
+ attribution?: StudioModelRequestAttribution;
11
+ botId?: string;
12
+ conversationId?: string;
13
+ interactionId?: string;
14
+ model?: string;
15
+ errorsOnly?: boolean;
16
+ order: 'asc' | 'desc';
17
+ beforeSequence?: number;
18
+ beforeCreatedAt?: string;
19
+ beforeId?: string;
20
+ /** 调用方会多取一条用于判断 hasMore。 */
21
+ limit: number;
22
+ }
23
+ export type StudioModelRequestScopeSummary = StudioRecordScopeSummary;
24
+ /**
25
+ * 列表读取真正需要的记录切片。
26
+ *
27
+ * 请求体与响应原文是记录里唯一没有上限的两个字段,而列表投影从来不读它们;把这个切片
28
+ * 提升成一个具名类型,读取路径就能在适配器一侧把它们留在存储里,而不是先取出整条记录再丢掉。
29
+ */
30
+ export type StudioModelRequestRecordHeader = Omit<StudioModelRequestRecord, 'requestBody' | 'responseBodyRaw'>;
31
+ /** 从完整记录取出列表读取需要的切片。适配器与内存行库共用同一份口径。 */
32
+ export declare function toModelRequestRecordHeader(record: StudioModelRequestRecord): StudioModelRequestRecordHeader;
33
+ /**
34
+ * 模型请求记录按行持久化:追加是单行 insert,补充响应体或错误是单行 update,容量回收是
35
+ * 按序号区间 delete,读取由适配器完成过滤、排序与分页。
36
+ */
37
+ export interface StudioModelRequestPersistence {
38
+ /** 读取作用域摘要,不加载任何记录正文。 */
39
+ summarize(): Promise<StudioModelRequestScopeSummary>;
40
+ append(record: StudioModelRequestRecord, bytes: number, nextSequence: number): Promise<StudioModelRequestScopeSummary>;
41
+ find(recordId: string): Promise<StudioModelRequestRecord | undefined>;
42
+ /** 覆盖单行,按 sequence 定位;不触碰其他行。 */
43
+ replace(record: StudioModelRequestRecord, bytes: number): Promise<StudioModelRequestScopeSummary>;
44
+ query(query: StudioModelRequestPersistenceQuery): Promise<StudioModelRequestRecord[]>;
45
+ /** 与 query 同一套过滤、排序与分页,但不取出请求体与响应原文。 */
46
+ queryHeaders(query: StudioModelRequestPersistenceQuery): Promise<StudioModelRequestRecordHeader[]>;
47
+ /** 从最旧开始按序号区间删除,直到同时满足条数与字节上限。 */
48
+ reclaim(limits: {
49
+ maxRecords: number;
50
+ maxBytes: number;
51
+ }): Promise<StudioModelRequestScopeSummary>;
52
+ /** 清空作用域记录,但保留 nextSequence 高水位。 */
53
+ clear(nextSequence: number): Promise<StudioModelRequestScopeSummary>;
54
+ }
55
+ /** 内存适配器与数据库适配器必须给出等价结果;内存侧直接复用这个谓词。 */
56
+ export declare function matchesModelRequestQuery(record: StudioModelRequestRecord, query: StudioModelRequestPersistenceQuery): boolean;
57
+ /** 进程内模型请求行库;行库机制来自共享实现,这里只注入模型请求的过滤谓词与列表切片。 */
58
+ export declare class InMemoryModelRequestRecords extends InMemoryRecordRows<StudioModelRequestRecord, StudioModelRequestPersistenceQuery> implements StudioModelRequestPersistence {
59
+ constructor();
60
+ queryHeaders(query: StudioModelRequestPersistenceQuery): Promise<StudioModelRequestRecordHeader[]>;
61
+ }
62
+ export interface AppendModelRequestRecordInput {
63
+ status: StudioModelRequestStatus;
64
+ durationMs: number;
65
+ method?: string;
66
+ url?: string;
67
+ provider?: string;
68
+ model?: string;
69
+ headers?: Record<string, string>;
70
+ attribution: 'attributed' | 'unattributed';
71
+ entities: StudioModelRequestEntities;
72
+ requestBody?: unknown;
73
+ requestBodyAvailable: boolean;
74
+ responseBodyStatus?: StudioModelResponseBodyStatus;
75
+ responseBodyFormat?: StudioModelResponseBodyFormat;
76
+ responseStatus?: number;
77
+ responseBodyRaw?: string;
78
+ responseBodyError?: string;
79
+ interactionId?: string;
80
+ chatlunaRequestId?: string;
81
+ presetSnapshots?: StudioPresetRuntimeSnapshot[];
82
+ error?: StudioModelRequestError;
83
+ chatlunaError?: StudioChatLunaRequestError;
84
+ }
85
+ export interface UpdateModelRequestRecordInput {
86
+ status?: StudioModelRequestStatus;
87
+ durationMs?: number;
88
+ headers?: Record<string, string>;
89
+ responseBodyStatus?: StudioModelResponseBodyStatus;
90
+ responseBodyFormat?: StudioModelResponseBodyFormat;
91
+ responseStatus?: number;
92
+ responseBodyRaw?: string;
93
+ responseBodyError?: string;
94
+ chatlunaRequestId?: string;
95
+ error?: StudioModelRequestError;
96
+ chatlunaError?: StudioChatLunaRequestError;
97
+ }
98
+ export interface StudioModelRequestStoreOptions {
99
+ maxRecords?: number;
100
+ maxBytes?: number;
101
+ persistence?: StudioModelRequestPersistence;
102
+ }
103
+ export declare function estimateModelRequestRecordBytes(record: StudioModelRequestRecord): number;
104
+ export declare function createModelRequestError(error: unknown, traceId?: string): StudioModelRequestError;
105
+ /**
106
+ * 列表投影。入参允许带请求体与响应原文(轨迹从完整记录派生同会话列表项),但它们既不参与
107
+ * 投影也不参与拷贝:列表路径深拷贝整条记录再丢掉这两个字段,是列表读取里最大的一次无用功。
108
+ */
109
+ export declare function presentModelRequestListItem(record: StudioModelRequestRecordHeader & Partial<Pick<StudioModelRequestRecord, 'requestBody' | 'responseBodyRaw'>>): StudioModelRequestListItem;
110
+ export declare function presentModelRequestDetail(record: StudioModelRequestRecord): StudioModelRequestDetail;
111
+ export declare class StudioModelRequestStore {
112
+ private nextSequence;
113
+ private summary;
114
+ private readonly maxRecords;
115
+ private readonly maxBytes;
116
+ private persistence;
117
+ private readonly ready;
118
+ private readonly writes;
119
+ /** 已分配序号但尚未落盘的记录;恢复完成时按持久化高水位重新编号。 */
120
+ private staged;
121
+ private readonly pendingUpdates;
122
+ constructor(options?: StudioModelRequestStoreOptions);
123
+ waitForReady(): Promise<void>;
124
+ waitForPersistence(): Promise<void>;
125
+ /**
126
+ * 等到已入队的写入全部提交。读取只需要这一层:pendingUpdates 里是尚未产生写入的
127
+ * 外部 Promise(例如仍在流式传输的响应体),读取等待它们会与写入方互相死锁。
128
+ */
129
+ private settle;
130
+ /** 登记会在稍后触发 update 的外部 Promise(响应体旁路采集),让收尾等待能覆盖它。 */
131
+ trackUpdate(task: Promise<void>): void;
132
+ getCapacity(): Promise<StudioModelRequestCapacity>;
133
+ append(input: AppendModelRequestRecordInput): StudioModelRequestRecord;
134
+ update(recordId: string, input: UpdateModelRequestRecordInput): Promise<StudioModelRequestRecord | undefined>;
135
+ getRecords(input?: GetStudioModelRequestRecordsInput): Promise<StudioModelRequestRecordsPage>;
136
+ getRecord(recordId: string): Promise<StudioModelRequestDetail | undefined>;
137
+ /**
138
+ * 「这条模型请求记录在不在」由本记录库判定,取不到就抛。
139
+ *
140
+ * 与 `getRecord` 并存而不是替代它:本成员服务于「调用方就是要这条记录」,`getRecord` 服务于
141
+ * 跨记录域遍历——遍历必须能区分「这个记录域里没有」与「这个记录域的持久化坏了」,用异常表达
142
+ * 未命中会让一次真实故障被当成「这里没有」静默跳过。
143
+ */
144
+ requireRecord(recordId: string): Promise<StudioModelRequestDetail>;
145
+ getRawRecords(input?: GetStudioModelRequestRecordsInput): Promise<StudioModelRequestRecord[]>;
146
+ clear(): Promise<number>;
147
+ /**
148
+ * 筛选下拉的可选值。
149
+ *
150
+ * 只扫最近一段记录头而不是全表:这是一份用于「点开筛选看看有哪些会话」的辅助清单,
151
+ * 而记录库的保留上限本来就会淘汰更早的记录,为它引入一条新的存储查询不划算。
152
+ */
153
+ getFacets(limit?: number): Promise<StudioModelRequestFacets>;
154
+ private toFilter;
155
+ private readCapacity;
156
+ private reclaimOverflow;
157
+ }
158
+ export declare function resolveModelRequestOrder(input: Pick<GetStudioModelRequestRecordsInput, 'order'>): 'asc' | 'desc';
@@ -0,0 +1,85 @@
1
+ import type { Context } from 'koishi';
2
+ import type { StudioModelRequestAttribution, StudioModelRequestRecord, StudioModelRequestStatus } from './types';
3
+ import { type StudioModelRequestPersistence, type StudioModelRequestPersistenceQuery, type StudioModelRequestRecordHeader, type StudioModelRequestScopeSummary } from './model-request';
4
+ /** 记录里唯一没有上限的两个字段;列表读取从不投影它们。 */
5
+ export interface StudioModelRequestRecordBodies {
6
+ requestBody?: unknown;
7
+ responseBodyRaw?: string;
8
+ }
9
+ /**
10
+ * 记录库状态行:只保存 nextSequence 高水位,永远只有一行。
11
+ *
12
+ * 记录条数与字节数由记录行的 bytes 列现算,避免计数漂移;序号必须独立保存,否则清空记录后
13
+ * max(sequence)+1 会回退并复用旧序号,而列表游标正是按序号翻页的。
14
+ */
15
+ export interface StudioModelRequestStateRow {
16
+ id: string;
17
+ nextSequence: number;
18
+ updatedAt: Date;
19
+ }
20
+ /**
21
+ * 一条模型请求记录一行。
22
+ *
23
+ * 记录被拆成两列:`header` 是列表读取需要的全部字段,`bodies` 只装请求体与响应原文。
24
+ * 这两个字段是记录里唯一没有上限的部分,单列存放时列表读取会连它们一起取出并反序列化;
25
+ * 拆列之后列表查询只投影 `header`,翻页与自动刷新不再按请求体体积付代价。
26
+ */
27
+ export interface StudioModelRequestRecordRow {
28
+ sequence: number;
29
+ id: string;
30
+ createdAt: string;
31
+ attribution: StudioModelRequestAttribution;
32
+ botId: string;
33
+ conversationId: string;
34
+ interactionId: string;
35
+ model: string;
36
+ status: StudioModelRequestStatus;
37
+ bytes: number;
38
+ header: StudioModelRequestRecordHeader;
39
+ bodies: StudioModelRequestRecordBodies;
40
+ }
41
+ declare module '@koishijs/core' {
42
+ interface Tables {
43
+ 'chatluna-studio.model-request': StudioModelRequestRecordRow;
44
+ 'chatluna-studio.model-request-state': StudioModelRequestStateRow;
45
+ }
46
+ }
47
+ type StudioRecordRowQuery = Record<string, unknown>;
48
+ interface StudioRecordRowCursor {
49
+ limit?: number;
50
+ offset?: number;
51
+ fields?: string[];
52
+ sort?: Record<string, 'asc' | 'desc'>;
53
+ }
54
+ export interface StudioModelRequestDatabase {
55
+ get(table: 'chatluna-studio.model-request', query: StudioRecordRowQuery, cursor?: StudioRecordRowCursor): Promise<StudioModelRequestRecordRow[]>;
56
+ get(table: 'chatluna-studio.model-request-state', query: {
57
+ id: string;
58
+ }): Promise<StudioModelRequestStateRow[]>;
59
+ upsert(table: 'chatluna-studio.model-request', rows: StudioModelRequestRecordRow[]): Promise<unknown>;
60
+ upsert(table: 'chatluna-studio.model-request-state', rows: StudioModelRequestStateRow[]): Promise<unknown>;
61
+ remove(table: 'chatluna-studio.model-request', query: StudioRecordRowQuery): Promise<unknown>;
62
+ }
63
+ export declare function registerStudioModelRequestModel(ctx: Context): void;
64
+ export declare class KoishiDatabaseModelRequestPersistence implements StudioModelRequestPersistence {
65
+ private getDatabase;
66
+ private readyTimeoutMs;
67
+ private index;
68
+ constructor(getDatabase: () => StudioModelRequestDatabase | undefined, readyTimeoutMs?: number);
69
+ summarize(): Promise<StudioModelRequestScopeSummary>;
70
+ append(record: StudioModelRequestRecord, bytes: number, nextSequence: number): Promise<import("./record-store").StudioRecordScopeSummary>;
71
+ find(recordId: string): Promise<StudioModelRequestRecord | undefined>;
72
+ replace(record: StudioModelRequestRecord, bytes: number): Promise<import("./record-store").StudioRecordScopeSummary>;
73
+ query(query: StudioModelRequestPersistenceQuery): Promise<StudioModelRequestRecord[]>;
74
+ queryHeaders(query: StudioModelRequestPersistenceQuery): Promise<StudioModelRequestRecordHeader[]>;
75
+ reclaim(limits: {
76
+ maxRecords: number;
77
+ maxBytes: number;
78
+ }): Promise<import("./record-store").StudioRecordScopeSummary>;
79
+ clear(nextSequence: number): Promise<import("./record-store").StudioRecordScopeSummary>;
80
+ private toRow;
81
+ private toRowQuery;
82
+ private saveState;
83
+ private requireDatabase;
84
+ }
85
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { ModelEvidenceProjection } from '../model-evidence';
2
+ import type { PresetEvidenceMatchResult, PresetRuntimeSnapshot, PresetSourceDocument, PresetTemplateExpression } from './types';
3
+ export interface MatchPresetExpressionEvidenceInput {
4
+ document: PresetSourceDocument;
5
+ expression: PresetTemplateExpression;
6
+ snapshot: PresetRuntimeSnapshot;
7
+ evidence: ModelEvidenceProjection;
8
+ }
9
+ export declare function matchPresetExpressionEvidence(input: MatchPresetExpressionEvidenceInput): PresetEvidenceMatchResult;
@@ -0,0 +1,6 @@
1
+ export * from './types';
2
+ export * from './source-document';
3
+ export * from './repository';
4
+ export * from './runtime-correlation';
5
+ export * from './evidence-match';
6
+ export * from './service';
@@ -0,0 +1,86 @@
1
+ import type { PresetDocumentKind, PresetSourceDocument } from './types';
2
+ export type PresetRepositoryErrorCode = 'already-exists' | 'confirmation-required' | 'invalid-file-name' | 'not-found' | 'revision-conflict' | 'unsafe-file' | 'unsafe-root';
3
+ export declare class PresetRepositoryError extends Error {
4
+ readonly code: PresetRepositoryErrorCode;
5
+ readonly cause?: unknown | undefined;
6
+ readonly name = "PresetRepositoryError";
7
+ constructor(code: PresetRepositoryErrorCode, message: string, cause?: unknown | undefined);
8
+ }
9
+ export interface PresetRepositoryRoots {
10
+ coreRoot: string;
11
+ characterRoot: string;
12
+ }
13
+ export interface PresetFile {
14
+ kind: PresetDocumentKind;
15
+ fileName: string;
16
+ revision: string;
17
+ size: number;
18
+ modifiedAt: string;
19
+ source: string;
20
+ document: PresetSourceDocument;
21
+ }
22
+ export interface CreatePresetInput {
23
+ kind: PresetDocumentKind;
24
+ fileName: string;
25
+ source: string;
26
+ }
27
+ export interface SavePresetInput extends CreatePresetInput {
28
+ expectedRevision: string;
29
+ }
30
+ export interface RenamePresetInput {
31
+ kind: PresetDocumentKind;
32
+ fileName: string;
33
+ newFileName: string;
34
+ expectedRevision: string;
35
+ confirmed: boolean;
36
+ }
37
+ export interface DeletePresetInput {
38
+ kind: PresetDocumentKind;
39
+ fileName: string;
40
+ expectedRevision: string;
41
+ confirmed: boolean;
42
+ }
43
+ export interface PresetRepository {
44
+ readAll(kind: PresetDocumentKind): Promise<PresetFile[]>;
45
+ read(kind: PresetDocumentKind, fileName: string): Promise<PresetFile>;
46
+ create(input: CreatePresetInput): Promise<PresetFile>;
47
+ save(input: SavePresetInput): Promise<PresetFile>;
48
+ rename(input: RenamePresetInput): Promise<PresetFile>;
49
+ delete(input: DeletePresetInput): Promise<void>;
50
+ }
51
+ export interface FileSystemPresetRepositoryOptions {
52
+ beforeMutationCommit?: (context: {
53
+ operation: 'create' | 'save' | 'rename' | 'delete';
54
+ sourcePath?: string;
55
+ destinationPath?: string;
56
+ }) => Promise<void> | void;
57
+ }
58
+ export declare class FileSystemPresetRepository implements PresetRepository {
59
+ private readonly options;
60
+ private readonly rootPaths;
61
+ private roots?;
62
+ constructor(roots: PresetRepositoryRoots, options?: FileSystemPresetRepositoryOptions);
63
+ readAll(kind: PresetDocumentKind): Promise<PresetFile[]>;
64
+ read(kind: PresetDocumentKind, fileName: string): Promise<PresetFile>;
65
+ create(input: CreatePresetInput): Promise<PresetFile>;
66
+ save(input: SavePresetInput): Promise<PresetFile>;
67
+ rename(input: RenamePresetInput): Promise<PresetFile>;
68
+ delete(input: DeletePresetInput): Promise<void>;
69
+ private initializeRoots;
70
+ private initializeRoot;
71
+ private requireRoot;
72
+ private revalidateRoot;
73
+ private filePath;
74
+ private readPresetFile;
75
+ private readRegularFileSnapshot;
76
+ private readRegularFileAfterRevision;
77
+ private revalidateSnapshot;
78
+ private assertSameSnapshot;
79
+ private assertDestinationMissing;
80
+ private atomicWrite;
81
+ private beforeMutationCommit;
82
+ private toPresetFile;
83
+ private requireConfirmation;
84
+ private revisionConflict;
85
+ private fileReplaced;
86
+ }
@@ -0,0 +1,16 @@
1
+ import type { Context } from 'koishi';
2
+ import type { PresetRuntimeSessionTarget, PresetRuntimeSnapshot } from './types';
3
+ export declare class PresetRuntimeSnapshotTracker {
4
+ private ctx;
5
+ private readonly active;
6
+ private readonly coreTurnKeys;
7
+ private readonly characterSessionTurnKeys;
8
+ private readonly disposers;
9
+ private nextCoreTurn;
10
+ private nextCharacterTurn;
11
+ constructor(ctx: Context);
12
+ getActiveSnapshots(target: PresetRuntimeSessionTarget): PresetRuntimeSnapshot[];
13
+ dispose(): void;
14
+ private resolveSession;
15
+ private finishCore;
16
+ }