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

78 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 执行记录(架构设计 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);
}
}