aps-agent/server/core/execution/store.ts

78 lines
2.7 KiB
TypeScript
Raw Normal View History

/**
* 执行记录(架构设计 4.2:执行模块)。
*
* 平台侧的权威记录:executionId、能力、参数摘要、状态、回执、幂等键。
* A3.7 落库前的内存实现;幂等索引与参数摘要的语义不随存储变化。
*/
import { createHash } from 'node:crypto';
import type { ExecutionReceipt } from '@aps/contracts';
export interface ExecutionRecord {
executionId: string;
capabilityId: string;
providerId: string;
sessionId: string;
projectId: string | null;
subjectUserId: string;
/** 参数摘要:只存摘要不存明文(架构设计 4.1「凭据不进日志」同理) */
paramsDigest: string;
idempotencyKey?: string;
status: ExecutionReceipt['status'];
receipt: ExecutionReceipt;
createdAt: number;
updatedAt: number;
}
export function digestParams(params: unknown): string {
const canonical = JSON.stringify(params ?? null, Object.keys((params ?? {}) as Record<string, unknown>).sort());
return `sha256:${createHash('sha256').update(canonical).digest('hex').slice(0, 16)}`;
}
export class ExecutionStore {
private readonly records = new Map<string, ExecutionRecord>();
private readonly byIdempotencyKey = new Map<string, string>();
create(input: Omit<ExecutionRecord, 'status' | 'receipt' | 'createdAt' | 'updatedAt'> & { receipt: ExecutionReceipt }): ExecutionRecord {
const now = Date.now();
const record: ExecutionRecord = {
...input,
status: input.receipt.status,
createdAt: now,
updatedAt: now,
};
this.records.set(record.executionId, record);
if (record.idempotencyKey) this.byIdempotencyKey.set(record.idempotencyKey, record.executionId);
return record;
}
complete(executionId: string, receipt: ExecutionReceipt): ExecutionRecord | undefined {
const record = this.records.get(executionId);
if (!record) return undefined;
record.receipt = receipt;
record.status = receipt.status;
record.updatedAt = Date.now();
return record;
}
get(executionId: string): ExecutionRecord | undefined {
return this.records.get(executionId);
}
findByIdempotencyKey(key: string): ExecutionRecord | undefined {
const executionId = this.byIdempotencyKey.get(key);
return executionId ? this.records.get(executionId) : undefined;
}
/** 某个会话最近的执行记录(上下文摘要里引用工具结果用) */
listBySession(sessionId: string, limit = 10): ExecutionRecord[] {
return [...this.records.values()]
.filter((record) => record.sessionId === sessionId)
.sort((a, b) => b.createdAt - a.createdAt)
.slice(0, limit);
}
list(limit = 50): ExecutionRecord[] {
return [...this.records.values()].sort((a, b) => b.createdAt - a.createdAt).slice(0, limit);
}
}