@roll-agent/runtime 0.10.0 → 0.12.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.
@@ -1 +1 @@
1
- import{createInterface as n}from"node:readline";function o(n){return"object"==typeof n&&null!==n&&"jsonrpc"in n&&"2.0"===n.jsonrpc}export function createStdioConnection(e,t){const r=[],s=[],c=n({input:e});return c.on("line",n=>{const e=n.trim();if(0===e.length)return;let t;try{t=JSON.parse(e)}catch{return}if(o(t))for(const n of r)n(t)}),c.on("close",()=>{for(const n of s)n()}),{send(n){t.write(`${JSON.stringify(n)}\n`)},onMessage(n){r.push(n)},onClose(n){s.push(n)},close(){c.close()}}}
1
+ import{createInterface as e}from"node:readline";import{once as r}from"node:events";export const STDIO_MAX_FRAME_BYTES=4194304;function n(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function o(e){return"string"==typeof e||"number"==typeof e&&Number.isFinite(e)}function t(e){return n(e)&&o(e.id)?e.id:null}function i(e){return!(!n(e)||"2.0"!==e.jsonrpc)&&("method"in e?"string"==typeof e.method&&0!==e.method.length&&(!("id"in e)||o(e.id)):"result"in e?o(e.id)&&!("error"in e):(null===e.id||o(e.id))&&n(e.error)&&"number"==typeof e.error.code&&Number.isInteger(e.error.code)&&"string"==typeof e.error.message)}export function createStdioConnection(n,o,s={}){const c=[],u=[],d=e({input:n}),l=s.maxFrameBytes??4194304;let f=Promise.resolve();const a=e=>{const n=`${JSON.stringify(e)}\n`;f=f.then(async()=>{o.write(n)||await r(o,"drain")}).catch(()=>{d.close()})};return d.on("line",e=>{const r=e.trim();if(0===r.length)return;if(Buffer.byteLength(e,"utf8")>l)return void a({jsonrpc:"2.0",id:null,error:{code:-32600,message:`Invalid Request: frame exceeds ${String(l)} byte limit`}});let n;try{n=JSON.parse(r)}catch{return void a({jsonrpc:"2.0",id:null,error:{code:-32700,message:"Parse error"}})}if(i(n))for(const e of c)e(n);else a({jsonrpc:"2.0",id:t(n),error:{code:-32600,message:"Invalid Request"}})}),d.on("close",()=>{for(const e of u)e()}),{send(e){a(e)},onMessage(e){c.push(e)},onClose(e){u.push(e)},close(){d.close()}}}
@@ -0,0 +1,110 @@
1
+ import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type InitializeParams, type InitializeResult, type RuntimeEventEnvelope, type RuntimeInstanceId, type RuntimeMethodParams, type RuntimeMethodResult, type RuntimeProtocolErrorData, type ThreadId, type TurnId } from "@roll-agent/protocol";
2
+ import type { AgentSession } from "../engine/agent-session.ts";
3
+ import { ThreadStore } from "../store/thread-store.ts";
4
+ type RollErrorCode = RuntimeProtocolErrorData["rollCode"];
5
+ export type RuntimeServiceSession = Pick<AgentSession, "id" | "send" | "approve" | "reject" | "cancel" | "close" | "getCapabilityManifest" | "getCapabilityTurnContext">;
6
+ export interface RuntimeServiceEngine {
7
+ createSession(input?: {
8
+ readonly title?: string;
9
+ }): Promise<RuntimeServiceSession>;
10
+ resumeSession(threadId: string): Promise<RuntimeServiceSession>;
11
+ }
12
+ export interface RuntimeServiceOptions {
13
+ readonly serverName?: string;
14
+ readonly serverVersion?: string;
15
+ readonly runtimeVersion?: string;
16
+ readonly maxFrameBytes?: number;
17
+ readonly idempotencyCacheEntries?: number;
18
+ /**
19
+ * Optional host-controlled projection. Raw model reasoning is never sent to public clients.
20
+ * Supplying this enables the `reasoning-summary` capability.
21
+ */
22
+ readonly reasoningSummaryProjector?: (delta: string) => string | undefined;
23
+ }
24
+ export interface RuntimeApprovalIdentity {
25
+ readonly threadId: ThreadId;
26
+ readonly turnId: TurnId;
27
+ readonly approvalId: ReturnType<typeof approvalIdSchema.parse>;
28
+ }
29
+ export type RuntimeApprovalDecision = {
30
+ readonly decision: "approve";
31
+ } | {
32
+ readonly decision: "reject";
33
+ readonly reason?: string;
34
+ };
35
+ export declare class RuntimeServiceError extends Error {
36
+ readonly rollCode: RollErrorCode;
37
+ readonly retryable: boolean;
38
+ readonly details: unknown;
39
+ constructor(rollCode: RollErrorCode, message: string, options?: {
40
+ readonly retryable?: boolean;
41
+ readonly details?: unknown;
42
+ });
43
+ }
44
+ type MutationRuntimeMethod = typeof RUNTIME_METHODS.threadCreate | typeof RUNTIME_METHODS.threadRename | typeof RUNTIME_METHODS.threadDelete | typeof RUNTIME_METHODS.threadDetach | typeof RUNTIME_METHODS.turnStart | typeof RUNTIME_METHODS.turnCancel | typeof RUNTIME_METHODS.approvalRespond;
45
+ export declare class MutationRequestCache {
46
+ private readonly maxEntries;
47
+ private readonly inFlight;
48
+ private readonly settled;
49
+ constructor(maxEntries: number);
50
+ run<TMethod extends MutationRuntimeMethod>(requestId: string, method: TMethod, params: RuntimeMethodParams<TMethod>, action: () => RuntimeMethodResult<TMethod> | Promise<RuntimeMethodResult<TMethod>>): Promise<RuntimeMethodResult<TMethod>>;
51
+ clear(): void;
52
+ private assertMatchingRequest;
53
+ private settle;
54
+ }
55
+ export declare class RuntimeService {
56
+ readonly runtimeInstanceId: RuntimeInstanceId;
57
+ private readonly engine;
58
+ private readonly store;
59
+ private readonly serverName;
60
+ private readonly serverVersion;
61
+ private readonly runtimeVersion;
62
+ private readonly maxFrameBytes;
63
+ private readonly idempotencyCacheEntries;
64
+ private readonly reasoningSummaryProjector;
65
+ private readonly sessions;
66
+ private readonly activeTurns;
67
+ private readonly activeTurnOwners;
68
+ private readonly settledTurnOwners;
69
+ private readonly pendingApprovals;
70
+ private readonly listeners;
71
+ private readonly mutationRequests;
72
+ private sequence;
73
+ private closing;
74
+ constructor(engine: RuntimeServiceEngine, store: ThreadStore, options?: RuntimeServiceOptions);
75
+ initialize(params: InitializeParams): InitializeResult;
76
+ onEvent(listener: (event: RuntimeEventEnvelope) => void): () => void;
77
+ listThreads(params: RuntimeMethodParams<"thread.list">): RuntimeMethodResult<"thread.list">;
78
+ createThread(params: RuntimeMethodParams<"thread.create">): Promise<RuntimeMethodResult<"thread.create">>;
79
+ openThread(params: RuntimeMethodParams<"thread.open">): Promise<RuntimeMethodResult<"thread.open">>;
80
+ snapshotThread(params: RuntimeMethodParams<"thread.snapshot">): RuntimeMethodResult<"thread.snapshot">;
81
+ renameThread(params: RuntimeMethodParams<"thread.rename">): Promise<RuntimeMethodResult<"thread.rename">>;
82
+ deleteThread(params: RuntimeMethodParams<"thread.delete">): Promise<RuntimeMethodResult<"thread.delete">>;
83
+ detachThread(params: RuntimeMethodParams<"thread.detach">): Promise<RuntimeMethodResult<"thread.detach">>;
84
+ threadCapabilities(params: RuntimeMethodParams<"thread.capabilities">): Promise<RuntimeMethodResult<"thread.capabilities">>;
85
+ startTurn(params: RuntimeMethodParams<"turn.start">): Promise<RuntimeMethodResult<"turn.start">>;
86
+ cancelTurn(params: RuntimeMethodParams<"turn.cancel">): Promise<RuntimeMethodResult<"turn.cancel">>;
87
+ respondApproval(params: RuntimeMethodParams<"approval.respond">): Promise<RuntimeMethodResult<"approval.respond">>;
88
+ resolvePendingApproval(identity: RuntimeApprovalIdentity, decision: RuntimeApprovalDecision): RuntimeMethodResult<"approval.respond">;
89
+ private cancelPendingApproval;
90
+ failPendingApprovalInteraction(identity: RuntimeApprovalIdentity, reason: string, resolution?: Extract<ApprovalResolution, {
91
+ readonly status: "cancelled" | "expired";
92
+ }>): Promise<boolean>;
93
+ getOperation(params: RuntimeMethodParams<"operation.get">): RuntimeMethodResult<"operation.get">;
94
+ close(): Promise<void>;
95
+ private assertOpen;
96
+ private requireThread;
97
+ private requireSession;
98
+ private emit;
99
+ private driveTurn;
100
+ private findTurnOwner;
101
+ private settleTurnOwner;
102
+ private projectSessionEvent;
103
+ private projectToolOutcome;
104
+ private findPendingApproval;
105
+ private requirePendingApproval;
106
+ private emitApprovalResolved;
107
+ private cancelPendingApprovalsForTurn;
108
+ private emitTurnTerminal;
109
+ }
110
+ export {};
@@ -0,0 +1 @@
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import{RUNTIME_ERROR_CODES as r,RUNTIME_FEATURES as s,RUNTIME_METHODS as n,RUNTIME_PROTOCOL_VERSION as i,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as a,approvalIdSchema as o,operationIdSchema as d,parseRuntimeMethodResult as u,runtimeInstanceIdSchema as l,runtimeProtocolVersionSchema as c,streamIdSchema as h,threadIdSchema as p}from"@roll-agent/protocol";import{createSafeCapabilitySnapshot as m}from"../engine/capability-manifest.js";import{isSensitiveFieldName as v,redactSecretText as I,toRedactedToolExecutionRecordSummary as g}from"../tool-bridge/tool-execution-record.js";import{ThreadStore as f}from"../store/thread-store.js";const y=4194304,T=500,w=1e4,q=16e3,A=64,S=128,x=16,O=new Set(["_meta","authorization","cookie","password","providerOptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function R(t){const s=JSON.stringify(t);if(void 0===s)throw new RuntimeServiceError(r.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(s).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=R(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>u(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>u(t,e));const o=Promise.resolve().then(s),d={method:t,fingerprint:n,response:o};return this.inFlight.set(e,d),o.then(()=>{this.settle(e,d)},()=>{this.settle(e,d)}),o}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,s,n){if(n.method!==t||n.fingerprint!==s)throw new RuntimeServiceError(r.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function E(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function N(e,t,r=0){if(void 0!==t&&(O.has(t.toLowerCase())||v(t)))return"[redacted]";if(r>=x)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=I(e);return t.length<=q?t:`${t.slice(0,q)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,A).map(e=>N(e,void 0,r+1));return e.length>A&&t.push(`[${String(e.length-A)} items omitted]`),t}if(E(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,S))void 0!==n&&(t[s]=N(n,s,r+1));return Object.keys(e).length>S&&(t._omitted=`${String(Object.keys(e).length-S)} keys omitted`),t}return void 0===e?null:String(e)}function C(e){return e instanceof Error?e.message:String(e)}function b(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>E(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function P(e){return p.parse(e)}function F(e,t){return{id:P(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function j(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=I(b(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:t.length>0?[{type:"text",text:t}]:[]}}function M(e){const t=g(e);return{id:d.parse(t.id),sequence:e.sequence,toolCallId:t.toolCallId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,outcome:{kind:t.outcome.kind,..."reason"in t.outcome&&void 0!==t.outcome.reason?{reason:t.outcome.reason}:{}},display:N(t.display,void 0)}}function V(e,t){return{id:o.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:N(t.input,void 0),...void 0!==t.reason?{reason:I(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;listeners=new Set;mutationRequests;sequence=0;closing=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??y,this.idempotencyCacheEntries=s.idempotencyCacheEntries??w,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.runtimeInstanceId=l.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=c.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(r.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:a}});return{protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:s.filter(e=>"reasoning-summary"!==e||void 0!==this.reasoningSummaryProjector),limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:T,eventReplay:!1,idempotencyCacheEntries:this.idempotencyCacheEntries}}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(r.invalidParams,"thread.list cursor 超出安全整数范围");const s=this.store.listThreads(),n=s.slice(t,t+e.limit).map(e=>F(this.store,e)),i=t+n.length;return{items:n,nextCursor:i<s.length?String(i):null}}async createThread(e){return this.mutationRequests.run(e.requestId,n.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.sessions.set(t.id,t),{thread:F(this.store,this.requireThread(t.id))}})}async openThread(e){return await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:F(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=j(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(M),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId)}}async renameThread(e){return this.mutationRequests.run(e.requestId,n.threadRename,e,()=>(this.assertOpen(),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:F(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,n.threadDelete,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(r.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.sessions.get(e.threadId);return void 0!==t&&(await t.close(),this.sessions.delete(e.threadId)),this.store.deleteThread(e.threadId),{deleted:!0}})}async detachThread(e){return this.mutationRequests.run(e.requestId,n.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))return{detached:!1};const t=this.sessions.get(e.threadId);return void 0===t?{detached:!1}:(await t.close(),this.sessions.delete(e.threadId),{detached:!0})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:N(m(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,n.turnStart,e,async()=>{this.assertOpen();const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(r.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(r.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const s=await this.requireSession(e.threadId),n=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==n){if(n===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(r.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(r.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const i={threadId:e.threadId,turnId:e.turnId,session:s,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0};return this.activeTurns.set(e.threadId,i),this.activeTurnOwners.set(e.turnId,e.threadId),this.emit(e.threadId,e.turnId,{type:"turn.started"}),this.driveTurn(i,e.input.text).catch(()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,n.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(r.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let s=!1;try{this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{s=t.session.cancel()}return{cancelling:s}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,n.approvalRespond,e,()=>this.resolvePendingApproval(e,"approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}))}resolvePendingApproval(e,t){this.assertOpen();const s=this.requirePendingApproval(e),n="approve"===t.decision?s.session.approve(e.approvalId):s.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!n)throw this.emitApprovalResolved(s,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(r.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(s,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}cancelPendingApproval(e,t){const r=this.findPendingApproval(e);return void 0!==r&&(this.pendingApprovals.delete(e.approvalId),this.emitApprovalResolved(r,t),!0)}async failPendingApprovalInteraction(e,t,r={status:"cancelled",reason:t}){const s=this.findPendingApproval(e);if(void 0===s)return!1;const n=this.activeTurns.get(e.threadId);void 0!==n&&n.turnId===e.turnId&&(n.interactionFailure=t),this.pendingApprovals.delete(e.approvalId);try{this.emitApprovalResolved(s,r)}finally{s.session.cancel()}return!0}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:M(t)}}async close(){if(this.closing)return;this.closing=!0;for(const e of this.activeTurns.values())e.session.cancel();const e=[...this.sessions.values()];this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),await Promise.allSettled(e.map(e=>e.close())),this.mutationRequests.clear()}assertOpen(){if(this.closing)throw new RuntimeServiceError(r.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(r.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertOpen(),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t;const r=await this.engine.resumeSession(e);return this.sessions.set(e,r),r}emit(e,t,r){const s={protocolVersion:i,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:(new Date).toISOString(),threadId:e,...void 0!==t?{turnId:t}:{},event:r};this.sequence+=1;for(const e of this.listeners)try{e(s)}catch{}}async driveTurn(e,t){const r={streamId:void 0,text:"",terminalEvent:void 0};let s;try{for await(const s of e.session.send(t))this.projectSessionEvent(e,r,s);s=r.terminalEvent??{type:"turn.completed"}}catch(e){s=r.terminalEvent??{type:"turn.failed",stage:"execute",message:I(C(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}this.emitTurnTerminal(e,s)}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{const s=h.parse(t());return r.streamId=s,r.text="",void this.emit(e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??h.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{const n=r.streamId??h.parse(t());return this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:I(s.text||r.text)}),void(r.terminalEvent??={type:"turn.completed"})}case"reasoning-delta":{const t=this.reasoningSummaryProjector?.(s.delta);return void(void 0!==t&&t.length>0&&this.emit(e.threadId,e.turnId,{type:"reasoning.summary.delta",reasoningId:s.reasoningId,delta:I(t)}))}case"tool-call":return void this.emit(e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:N(s.input,void 0)});case"tool-output-delta":return void this.emit(e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:I(s.delta)});case"tool-result":return void this.emit(e.threadId,e.turnId,{type:"tool.completed",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:d.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:N(s.display,void 0)});case"confirmation-required":{const t=V(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t}),void this.emit(e.threadId,e.turnId,{type:"approval.required",approval:t})}case"turn-cancelled":r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:I(s.message)}:{type:"turn.failed",stage:"execute",message:I(e.interactionFailure)};break;case"error":r.terminalEvent??={type:"turn.failed",stage:s.stage,message:I(s.message)}}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:I(e.reason)}:{}}}findPendingApproval(e){const t=this.pendingApprovals.get(e.approvalId);if(void 0!==t&&t.threadId===e.threadId&&t.approval.turnId===e.turnId)return t}requirePendingApproval(e){const t=this.findPendingApproval(e);if(void 0===t)throw new RuntimeServiceError(r.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){this.emit(e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:t})}cancelPendingApprovalsForTurn(e,t){const r=[...this.pendingApprovals.values()].filter(t=>t.approval.turnId===e);let s;for(const e of r)try{this.cancelPendingApproval({threadId:e.threadId,turnId:e.approval.turnId,approvalId:e.approval.id},t)}catch(e){s??=e}if(void 0!==s)throw s}emitTurnTerminal(e,t){this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.threadId,e.turnId,t)}}
@@ -38,6 +38,18 @@ export interface ListTranscriptMessagesOptions {
38
38
  readonly throughSequence?: number;
39
39
  readonly limit?: number;
40
40
  }
41
+ export interface ListRecentEntriesOptions {
42
+ readonly beforeSequence?: number;
43
+ readonly limit?: number;
44
+ }
45
+ export interface RecentTranscriptMessagePage {
46
+ readonly entries: readonly ArchivedTranscriptMessage[];
47
+ readonly nextBeforeSequence: number | undefined;
48
+ }
49
+ export interface RecentToolExecutionPage {
50
+ readonly entries: readonly SequencedToolExecutionRecord[];
51
+ readonly nextBeforeSequence: number | undefined;
52
+ }
41
53
  export interface CompactionEvidenceWatermarks {
42
54
  /** Highest append-only transcript sequence that was visible while building the draft. */
43
55
  readonly transcriptMessagesThroughSequence: number;
@@ -101,6 +113,7 @@ export declare class ThreadStore {
101
113
  getThread(id: string): ThreadRecord | undefined;
102
114
  listThreads(): ThreadRecord[];
103
115
  countMessages(threadId: string): number;
116
+ countTranscriptMessages(threadId: string): number;
104
117
  updateTitle(threadId: string, title: string): void;
105
118
  deleteThread(threadId: string): void;
106
119
  appendMessages(threadId: string, messages: readonly ModelMessage[]): void;
@@ -110,9 +123,11 @@ export declare class ThreadStore {
110
123
  appendToolExecution(threadId: string, record: ToolExecutionRecord): number;
111
124
  private enforceToolExecutionRetentionInTransaction;
112
125
  listToolExecutions(threadId: string, options?: ListToolExecutionsOptions): SequencedToolExecutionRecord[];
126
+ listRecentToolExecutions(threadId: string, options?: ListRecentEntriesOptions): RecentToolExecutionPage;
113
127
  getToolExecution(threadId: string, executionId: string): SequencedToolExecutionRecord | undefined;
114
128
  getTranscriptCompleteness(threadId: string): TranscriptCompleteness;
115
129
  listTranscriptMessages(threadId: string, options?: ListTranscriptMessagesOptions): ArchivedTranscriptMessage[];
130
+ listRecentTranscriptMessages(threadId: string, options?: ListRecentEntriesOptions): RecentTranscriptMessagePage;
116
131
  getLatestCheckpoint(threadId: string): CompactionCheckpoint | undefined;
117
132
  getLatestSummaryCheckpoint(threadId: string): CompactionCheckpoint | undefined;
118
133
  getCheckpoint(threadId: string, checkpointId: string): CompactionCheckpoint | undefined;
@@ -123,5 +138,6 @@ export declare class ThreadStore {
123
138
  private maxToolExecutionSequence;
124
139
  private withCurrentTranscriptCompleteness;
125
140
  private validateTranscriptRange;
141
+ private validateRecentPage;
126
142
  close(): void;
127
143
  }
@@ -1 +1 @@
1
- import{chmodSync as e,existsSync as t,mkdirSync as n}from"node:fs";import{homedir as r}from"node:os";import{resolve as s}from"node:path";import{randomUUID as o}from"node:crypto";import{DatabaseSync as a}from"node:sqlite";import{modelMessageSchema as i}from"ai";import{parsePersistedToolExecutionRecord as c,prepareToolExecutionRecordForPersistence as d,redactSecretText as E}from"../tool-bridge/tool-execution-record.js";import{COMPACTION_CHECKPOINT_VERSION as h,COMPACTION_TRANSCRIPT_COMPLETENESS as T,TRANSCRIPT_MESSAGE_PROVENANCES as p,UnsupportedCompactionCheckpointVersionError as u,archivedTranscriptMessageSchema as _,createCompactionCheckpoint as l,createCompactionCheckpointDraft as m,parseCompactionCheckpoint as g}from"../engine/compaction-checkpoint.js";import{legacyCompactionTranscriptFragmentsSchema as S}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as N}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as R,repairActiveToolProtocol as O}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as I}from"../engine/explicit-skill-context.js";const L=4,C=100,x=500,A=50,f=500,b=100,q=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution"];export function expandTilde(e){return e.startsWith("~/")?s(r(),e.slice(2)):e}export function defaultThreadsDir(){return s(r(),".roll-agent","threads")}function M(e){return e.prepare("PRAGMA foreign_key_list(messages)").all().some(e=>"threads"===e.table&&"thread_id"===e.from&&"id"===e.to&&"CASCADE"===e.on_delete.toUpperCase())}function w(e,t){return"valid"===e.status?e:"fallback"===e.status?{status:"fallback",reason:e.reason,...void 0!==t?{lastValidCheckpointId:t.id}:{}}:{status:"skipped",...void 0!==t?{lastValidCheckpointId:t.id}:{}}}function v(e){if(Number.isInteger(e.schema_version)&&e.schema_version>h)throw new u(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=g(JSON.parse(e.checkpoint_json));if(t.version!==e.schema_version)throw new Error(`Compaction checkpoint schema version mismatch: row v${String(e.schema_version)}, payload v${String(t.version)}`);return t}function k(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at}}export class ThreadStore{db;constructor(r=defaultThreadsDir()){const o=expandTilde(r);t(o)||n(o,{recursive:!0,mode:448}),"win32"!==process.platform&&e(o,448);const i=s(o,"threads.db");if(this.db=new a(i),"win32"!==process.platform)try{e(i,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(q)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>4)throw new Error(`ThreadStore schema v${String(e.user_version)} 高于当前支持的 v${String(4)}`);this.db.exec("BEGIN IMMEDIATE");try{this.db.exec("CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );"),M(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),e.user_version<3&&this.db.exec("INSERT OR IGNORE INTO thread_transcript_state (thread_id, completeness)\n SELECT id, 'legacy_snapshot' FROM threads;\n INSERT OR IGNORE INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n SELECT thread_id, idx, role, content_json, 'legacy_snapshot', created_at\n FROM messages;"),e.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),this.enforceAllToolExecutionRetentionInTransaction((new Date).toISOString()),this.db.exec(`PRAGMA user_version = ${String(4)};`),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}migrateToolExecutionPersistenceInTransaction(){const e=this.db.prepare("SELECT rowid AS storage_rowid, record_json\n FROM tool_executions\n WHERE rowid > ?\n ORDER BY rowid ASC\n LIMIT 100"),t=this.db.prepare("UPDATE tool_executions\n SET tool_call_id = ?, agent_name = ?, tool_name = ?, record_json = ?, created_at = ?\n WHERE rowid = ?");let n=0;for(;;){const r=e.all(n);if(0===r.length)break;for(const e of r){const r=c(JSON.parse(e.record_json));t.run(r.toolCallId,r.agentName,r.toolName,JSON.stringify(r),r.createdAt,e.storage_rowid),n=e.storage_rowid}}}enforceAllToolExecutionRetentionInTransaction(e){const t=this.db.prepare("SELECT DISTINCT thread_id AS id FROM tool_executions").all();for(const{id:n}of t)this.enforceToolExecutionRetentionInTransaction(n,e)}migrateExplicitSkillCheckpointPersistenceInTransaction(){const e=[{table:"messages",column:"content_json"},{table:"transcript_messages",column:"message_json"}];for(const t of e){const e=this.db.prepare(`SELECT rowid AS storage_rowid, ${t.column} AS message_json\n FROM ${t.table}\n WHERE ${t.column} LIKE '%"modelUserContent"%'`).all(),n=this.db.prepare(`UPDATE ${t.table} SET ${t.column} = ? WHERE rowid = ?`);for(const t of e){const e=i.parse(JSON.parse(t.message_json)),r=I(e);n.run(JSON.stringify(r),t.storage_rowid)}}}createThread(e={}){const t=e.id??o(),n=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,T[0]),this.db.prepare("INSERT INTO thread_tool_execution_state (thread_id, next_sequence) VALUES (?, 0)").run(t),this.db.exec("COMMIT"),t}catch(e){throw this.db.exec("ROLLBACK"),e}}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?k(t):void 0}listThreads(){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(k)}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendMessages(e,t){if(0===t.length)return;if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>I(i.parse(e))),r=(new Date).toISOString(),s=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)"),o=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)");this.db.exec("BEGIN IMMEDIATE");try{const t=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e),a=this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e);let i=t.maxIdx+1,c=a.maxSequence+1;for(const t of n){const n=JSON.stringify(t);s.run(e,i,t.role,n,r),o.run(e,c,t.role,n,p[0],r),i+=1,c+=1}this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(r,e),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>i.parse(e)),r=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.replaceMessagesInTransaction(e,n,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>i.parse(JSON.parse(e.content_json)))}replaceMessagesInTransaction(e,t,n){const r=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,s)=>{const o=I(t);r.run(e,s,o.role,JSON.stringify(o),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=d(t),r=JSON.stringify(n),s=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const o=t.next_sequence;return this.db.prepare("INSERT INTO tool_executions\n (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,o,n.id,n.toolCallId,n.agentName,n.toolName,r,n.createdAt),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(o+1,e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),o}catch(e){throw this.db.exec("ROLLBACK"),e}}enforceToolExecutionRetentionInTransaction(e,t){const n=new Date(Date.parse(t)-TOOL_EXECUTION_RETENTION_POLICY.maxAgeMs).toISOString();let r=!1;const s=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND created_at < ?").run(e,n);r=Number(s.changes)>0;const o=this.db.prepare("SELECT sequence\n FROM (\n SELECT sequence,\n ROW_NUMBER() OVER (ORDER BY sequence DESC) AS retained_rank,\n SUM(length(CAST(record_json AS BLOB))) OVER (\n ORDER BY sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM tool_executions\n WHERE thread_id = ?\n )\n WHERE retained_rank > ? OR retained_bytes > ?").all(e,TOOL_EXECUTION_RETENTION_POLICY.maxRecordsPerThread,TOOL_EXECUTION_RETENTION_POLICY.maxBytesPerThread);if(o.length>0){const t=this.db.prepare("DELETE FROM tool_executions WHERE thread_id = ? AND sequence = ?");for(const n of o)t.run(e,n.sequence);r=!0}r&&this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness)\n VALUES (?, 'legacy_snapshot')\n ON CONFLICT(thread_id) DO UPDATE SET completeness = 'legacy_snapshot'").run(e)}listToolExecutions(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??C;if(!Number.isInteger(n)||n<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(s)||s<1||s>x)throw new Error(`limit 必须是 1-${String(x)} 之间的整数`);if(!Number.isInteger(r)||r<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(r<=n)return[];return this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n AND (? IS NULL OR tool_call_id = ?)\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,t.toolCallId??null,t.toolCallId??null,s).map(e=>({...c(JSON.parse(e.record_json)),sequence:e.sequence}))}getToolExecution(e,t){const n=this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t);if(n)return{...c(JSON.parse(n.record_json)),sequence:n.sequence}}getTranscriptCompleteness(e){const t=this.db.prepare("SELECT completeness FROM thread_transcript_state WHERE thread_id = ?").get(e);return t?.completeness===T[0]?T[0]:T[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??A;if(this.validateTranscriptRange(n,r,s,f),r<=n)return[];return this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,s).map(e=>_.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)}))}getLatestCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{return this.withCurrentTranscriptCompleteness(e,v(n))}catch(e){if(e instanceof u)throw e}}getLatestSummaryCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{const t=v(n);if("valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof u)throw e}}getCheckpoint(e,t){const n=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ? AND id = ?").get(e,t);if(void 0!==n)try{return this.withCurrentTranscriptCompleteness(e,v(n))}catch(e){if(e instanceof u)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:O(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>i.parse(e)),r=t.expectedActiveMessages.map(e=>i.parse(e)),s=O(n);if(s.status!==R.valid){const e=[...s.removedToolCallIds,...s.removedToolResultIds].filter((e,t,n)=>n.indexOf(e)===t).sort((e,t)=>e.localeCompare(t));throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const o=m(t.draft),a=S.parse(t.legacySnapshotTranscriptFragments??[]),c=S.parse(a.map(e=>E(e))).map(e=>i.parse(N(e))),d=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=O(this.getMessages(e)).messages;if(JSON.stringify(s)!==JSON.stringify(r))throw new Error("Compaction active projection changed after the draft snapshot was captured");const a=this.getLatestCheckpoint(e);if(a?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const i=this.getLatestSummaryCheckpoint(e),E=w(o.summary,i),h=this.getTranscriptCompleteness(e),u=m({...o,summary:E,...h===T[1]&&"valid"===o.toolState.integrityStatus?{toolState:{...o.toolState,integrityStatus:"sanitized"}}:{}}),_=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration\n FROM compaction_checkpoints\n WHERE thread_id = ?").get(e),g=this.maxTranscriptMessageSequence(e),S=this.maxToolExecutionSequence(e),N=t.evidenceWatermarks.transcriptMessagesThroughSequence,R=t.evidenceWatermarks.toolExecutionsThroughSequence,I=a?.transcript.messages.throughSequence??-1,L=a?.transcript.toolExecutions.throughSequence??-1,C=2===a?.version?a.semanticEvidence.messagesThroughSequence:-1,x=2===a?.version?a.semanticEvidence.toolExecutionsThroughSequence:-1;if(c.length>0&&1!==a?.version)throw new Error("Legacy snapshot transcript archive requires a V1 parent checkpoint");if(!Number.isInteger(N)||N<I||N>g)throw new Error(`Compaction transcript message watermark ${String(N)} is outside the available range ${String(I)}-${String(g)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.messagesThroughSequence)||t.semanticEvidenceWatermarks.messagesThroughSequence<C||t.semanticEvidenceWatermarks.messagesThroughSequence>N)throw new Error(`Compaction semantic message watermark ${String(t.semanticEvidenceWatermarks.messagesThroughSequence)} is outside the available range ${String(C)}-${String(N)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence<x||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence>R)throw new Error(`Compaction semantic Tool watermark ${String(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)} is outside the available range ${String(x)}-${String(R)}`);if(!Number.isInteger(R)||R<L||R>S)throw new Error(`Compaction tool execution watermark ${String(R)} is outside the available range ${String(L)}-${String(S)}`);let A=N;if(c.length>0){if(N!==g)throw new Error("Legacy snapshot transcript archive requires the latest message watermark");const t=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)");let n=g+1;for(const r of c)t.run(e,n,r.role,JSON.stringify(r),p[1],d),n+=1;A=n-1}const f={messages:{fromSequenceExclusive:I,throughSequence:A},toolExecutions:{fromSequenceExclusive:L,throughSequence:R},completeness:h},b=l({draft:u,semanticState:t.semanticState,semanticEvidence:t.semanticEvidenceWatermarks,generation:_.maxGeneration+1,transcript:f,...void 0!==a?{previousCheckpointId:a.id}:{},createdAt:d});return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version,\n message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence,\n checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(b.id,e,b.generation,b.version,b.transcript.messages.fromSequenceExclusive,b.transcript.messages.throughSequence,b.transcript.toolExecutions.fromSequenceExclusive,b.transcript.toolExecutions.throughSequence,JSON.stringify(b),b.createdAt),this.replaceMessagesInTransaction(e,n,d),this.db.exec("COMMIT"),b}catch(e){throw this.db.exec("ROLLBACK"),e}}readCheckpointTranscript(e,t){if("message"!==t.kind&&"tool_execution"!==t.kind)throw new Error("kind 必须是 message 或 tool_execution");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??A,s="message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,o=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(o,s.throughSequence,r,b);const a="message"===t.kind?this.listTranscriptMessages(e,{afterSequence:o,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:o,throughSequence:s.throughSequence,limit:Math.min(r+1,x)}).map(e=>({kind:"tool_execution",...e})),i=a.length>r,c=i?a.slice(0,r):a,d=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...i&&void 0!==d?{nextAfterSequence:d}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}maxTranscriptMessageSequence(e){return this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e).maxSequence}maxToolExecutionSequence(e){const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);return(t?.next_sequence??0)-1}withCurrentTranscriptCompleteness(e,t){const n=T[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:T[0],s=r===n&&"valid"===t.toolState.integrityStatus?{...t.toolState,integrityStatus:"sanitized"}:t.toolState;return t.transcript.completeness===r&&t.toolState===s?t:{...t,toolState:s,transcript:{...t.transcript,completeness:r}}}validateTranscriptRange(e,t,n,r){if(!Number.isInteger(e)||e<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(t)||t<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(n)||n<1||n>r)throw new Error(`limit 必须是 1-${String(r)} 之间的整数`)}close(){this.db.close()}}
1
+ import{chmodSync as e,existsSync as t,mkdirSync as n}from"node:fs";import{homedir as r}from"node:os";import{resolve as s}from"node:path";import{randomUUID as a}from"node:crypto";import{DatabaseSync as o}from"node:sqlite";import{modelMessageSchema as i}from"ai";import{parsePersistedToolExecutionRecord as c,prepareToolExecutionRecordForPersistence as E,redactSecretText as d}from"../tool-bridge/tool-execution-record.js";import{COMPACTION_CHECKPOINT_VERSION as h,COMPACTION_TRANSCRIPT_COMPLETENESS as T,TRANSCRIPT_MESSAGE_PROVENANCES as u,UnsupportedCompactionCheckpointVersionError as p,archivedTranscriptMessageSchema as _,createCompactionCheckpoint as l,createCompactionCheckpointDraft as m,parseCompactionCheckpoint as g}from"../engine/compaction-checkpoint.js";import{legacyCompactionTranscriptFragmentsSchema as S}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as N}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as R,repairActiveToolProtocol as O}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as I}from"../engine/explicit-skill-context.js";const L=4,C=100,A=500,x=50,f=500,q=100,b=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution"];export function expandTilde(e){return e.startsWith("~/")?s(r(),e.slice(2)):e}export function defaultThreadsDir(){return s(r(),".roll-agent","threads")}function M(e){return e.prepare("PRAGMA foreign_key_list(messages)").all().some(e=>"threads"===e.table&&"thread_id"===e.from&&"id"===e.to&&"CASCADE"===e.on_delete.toUpperCase())}function v(e,t){return"valid"===e.status?e:"fallback"===e.status?{status:"fallback",reason:e.reason,...void 0!==t?{lastValidCheckpointId:t.id}:{}}:{status:"skipped",...void 0!==t?{lastValidCheckpointId:t.id}:{}}}function w(e){if(Number.isInteger(e.schema_version)&&e.schema_version>h)throw new p(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=g(JSON.parse(e.checkpoint_json));if(t.version!==e.schema_version)throw new Error(`Compaction checkpoint schema version mismatch: row v${String(e.schema_version)}, payload v${String(t.version)}`);return t}function D(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at}}export class ThreadStore{db;constructor(r=defaultThreadsDir()){const a=expandTilde(r);t(a)||n(a,{recursive:!0,mode:448}),"win32"!==process.platform&&e(a,448);const i=s(a,"threads.db");if(this.db=new o(i),"win32"!==process.platform)try{e(i,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(b)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>4)throw new Error(`ThreadStore schema v${String(e.user_version)} 高于当前支持的 v${String(4)}`);this.db.exec("BEGIN IMMEDIATE");try{this.db.exec("CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );"),M(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),e.user_version<3&&this.db.exec("INSERT OR IGNORE INTO thread_transcript_state (thread_id, completeness)\n SELECT id, 'legacy_snapshot' FROM threads;\n INSERT OR IGNORE INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n SELECT thread_id, idx, role, content_json, 'legacy_snapshot', created_at\n FROM messages;"),e.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),this.enforceAllToolExecutionRetentionInTransaction((new Date).toISOString()),this.db.exec(`PRAGMA user_version = ${String(4)};`),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}migrateToolExecutionPersistenceInTransaction(){const e=this.db.prepare("SELECT rowid AS storage_rowid, record_json\n FROM tool_executions\n WHERE rowid > ?\n ORDER BY rowid ASC\n LIMIT 100"),t=this.db.prepare("UPDATE tool_executions\n SET tool_call_id = ?, agent_name = ?, tool_name = ?, record_json = ?, created_at = ?\n WHERE rowid = ?");let n=0;for(;;){const r=e.all(n);if(0===r.length)break;for(const e of r){const r=c(JSON.parse(e.record_json));t.run(r.toolCallId,r.agentName,r.toolName,JSON.stringify(r),r.createdAt,e.storage_rowid),n=e.storage_rowid}}}enforceAllToolExecutionRetentionInTransaction(e){const t=this.db.prepare("SELECT DISTINCT thread_id AS id FROM tool_executions").all();for(const{id:n}of t)this.enforceToolExecutionRetentionInTransaction(n,e)}migrateExplicitSkillCheckpointPersistenceInTransaction(){const e=[{table:"messages",column:"content_json"},{table:"transcript_messages",column:"message_json"}];for(const t of e){const e=this.db.prepare(`SELECT rowid AS storage_rowid, ${t.column} AS message_json\n FROM ${t.table}\n WHERE ${t.column} LIKE '%"modelUserContent"%'`).all(),n=this.db.prepare(`UPDATE ${t.table} SET ${t.column} = ? WHERE rowid = ?`);for(const t of e){const e=i.parse(JSON.parse(t.message_json)),r=I(e);n.run(JSON.stringify(r),t.storage_rowid)}}}createThread(e={}){const t=e.id??a(),n=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,T[0]),this.db.prepare("INSERT INTO thread_tool_execution_state (thread_id, next_sequence) VALUES (?, 0)").run(t),this.db.exec("COMMIT"),t}catch(e){throw this.db.exec("ROLLBACK"),e}}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?D(t):void 0}listThreads(){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(D)}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}countTranscriptMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM transcript_messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendMessages(e,t){if(0===t.length)return;if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>I(i.parse(e))),r=(new Date).toISOString(),s=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)"),a=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)");this.db.exec("BEGIN IMMEDIATE");try{const t=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e),o=this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e);let i=t.maxIdx+1,c=o.maxSequence+1;for(const t of n){const n=JSON.stringify(t);s.run(e,i,t.role,n,r),a.run(e,c,t.role,n,u[0],r),i+=1,c+=1}this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(r,e),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>i.parse(e)),r=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.replaceMessagesInTransaction(e,n,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>i.parse(JSON.parse(e.content_json)))}replaceMessagesInTransaction(e,t,n){const r=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,s)=>{const a=I(t);r.run(e,s,a.role,JSON.stringify(a),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=E(t),r=JSON.stringify(n),s=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const a=t.next_sequence;return this.db.prepare("INSERT INTO tool_executions\n (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,a,n.id,n.toolCallId,n.agentName,n.toolName,r,n.createdAt),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(a+1,e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),a}catch(e){throw this.db.exec("ROLLBACK"),e}}enforceToolExecutionRetentionInTransaction(e,t){const n=new Date(Date.parse(t)-TOOL_EXECUTION_RETENTION_POLICY.maxAgeMs).toISOString();let r=!1;const s=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND created_at < ?").run(e,n);r=Number(s.changes)>0;const a=this.db.prepare("SELECT sequence\n FROM (\n SELECT sequence,\n ROW_NUMBER() OVER (ORDER BY sequence DESC) AS retained_rank,\n SUM(length(CAST(record_json AS BLOB))) OVER (\n ORDER BY sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM tool_executions\n WHERE thread_id = ?\n )\n WHERE retained_rank > ? OR retained_bytes > ?").all(e,TOOL_EXECUTION_RETENTION_POLICY.maxRecordsPerThread,TOOL_EXECUTION_RETENTION_POLICY.maxBytesPerThread);if(a.length>0){const t=this.db.prepare("DELETE FROM tool_executions WHERE thread_id = ? AND sequence = ?");for(const n of a)t.run(e,n.sequence);r=!0}r&&this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness)\n VALUES (?, 'legacy_snapshot')\n ON CONFLICT(thread_id) DO UPDATE SET completeness = 'legacy_snapshot'").run(e)}listToolExecutions(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??C;if(!Number.isInteger(n)||n<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(s)||s<1||s>A)throw new Error(`limit 必须是 1-${String(A)} 之间的整数`);if(!Number.isInteger(r)||r<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(r<=n)return[];return this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n AND (? IS NULL OR tool_call_id = ?)\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,t.toolCallId??null,t.toolCallId??null,s).map(e=>({...c(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??C;this.validateRecentPage(n,r,A);const s=this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),a=s.length>r,o=s.slice(0,r).map(e=>({...c(JSON.parse(e.record_json)),sequence:e.sequence})).reverse();return{entries:o,nextBeforeSequence:a?o[0]?.sequence:void 0}}getToolExecution(e,t){const n=this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t);if(n)return{...c(JSON.parse(n.record_json)),sequence:n.sequence}}getTranscriptCompleteness(e){const t=this.db.prepare("SELECT completeness FROM thread_transcript_state WHERE thread_id = ?").get(e);return t?.completeness===T[0]?T[0]:T[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??x;if(this.validateTranscriptRange(n,r,s,f),r<=n)return[];return this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,s).map(e=>_.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)}))}listRecentTranscriptMessages(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??x;this.validateRecentPage(n,r,f);const s=this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),a=s.length>r,o=s.slice(0,r).map(e=>_.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})).reverse();return{entries:o,nextBeforeSequence:a?o[0]?.sequence:void 0}}getLatestCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{return this.withCurrentTranscriptCompleteness(e,w(n))}catch(e){if(e instanceof p)throw e}}getLatestSummaryCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{const t=w(n);if("valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof p)throw e}}getCheckpoint(e,t){const n=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ? AND id = ?").get(e,t);if(void 0!==n)try{return this.withCurrentTranscriptCompleteness(e,w(n))}catch(e){if(e instanceof p)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:O(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>i.parse(e)),r=t.expectedActiveMessages.map(e=>i.parse(e)),s=O(n);if(s.status!==R.valid){const e=[...s.removedToolCallIds,...s.removedToolResultIds].filter((e,t,n)=>n.indexOf(e)===t).sort((e,t)=>e.localeCompare(t));throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const a=m(t.draft),o=S.parse(t.legacySnapshotTranscriptFragments??[]),c=S.parse(o.map(e=>d(e))).map(e=>i.parse(N(e))),E=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=O(this.getMessages(e)).messages;if(JSON.stringify(s)!==JSON.stringify(r))throw new Error("Compaction active projection changed after the draft snapshot was captured");const o=this.getLatestCheckpoint(e);if(o?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const i=this.getLatestSummaryCheckpoint(e),d=v(a.summary,i),h=this.getTranscriptCompleteness(e),p=m({...a,summary:d,...h===T[1]&&"valid"===a.toolState.integrityStatus?{toolState:{...a.toolState,integrityStatus:"sanitized"}}:{}}),_=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration\n FROM compaction_checkpoints\n WHERE thread_id = ?").get(e),g=this.maxTranscriptMessageSequence(e),S=this.maxToolExecutionSequence(e),N=t.evidenceWatermarks.transcriptMessagesThroughSequence,R=t.evidenceWatermarks.toolExecutionsThroughSequence,I=o?.transcript.messages.throughSequence??-1,L=o?.transcript.toolExecutions.throughSequence??-1,C=2===o?.version?o.semanticEvidence.messagesThroughSequence:-1,A=2===o?.version?o.semanticEvidence.toolExecutionsThroughSequence:-1;if(c.length>0&&1!==o?.version)throw new Error("Legacy snapshot transcript archive requires a V1 parent checkpoint");if(!Number.isInteger(N)||N<I||N>g)throw new Error(`Compaction transcript message watermark ${String(N)} is outside the available range ${String(I)}-${String(g)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.messagesThroughSequence)||t.semanticEvidenceWatermarks.messagesThroughSequence<C||t.semanticEvidenceWatermarks.messagesThroughSequence>N)throw new Error(`Compaction semantic message watermark ${String(t.semanticEvidenceWatermarks.messagesThroughSequence)} is outside the available range ${String(C)}-${String(N)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence<A||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence>R)throw new Error(`Compaction semantic Tool watermark ${String(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)} is outside the available range ${String(A)}-${String(R)}`);if(!Number.isInteger(R)||R<L||R>S)throw new Error(`Compaction tool execution watermark ${String(R)} is outside the available range ${String(L)}-${String(S)}`);let x=N;if(c.length>0){if(N!==g)throw new Error("Legacy snapshot transcript archive requires the latest message watermark");const t=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)");let n=g+1;for(const r of c)t.run(e,n,r.role,JSON.stringify(r),u[1],E),n+=1;x=n-1}const f={messages:{fromSequenceExclusive:I,throughSequence:x},toolExecutions:{fromSequenceExclusive:L,throughSequence:R},completeness:h},q=l({draft:p,semanticState:t.semanticState,semanticEvidence:t.semanticEvidenceWatermarks,generation:_.maxGeneration+1,transcript:f,...void 0!==o?{previousCheckpointId:o.id}:{},createdAt:E});return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version,\n message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence,\n checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(q.id,e,q.generation,q.version,q.transcript.messages.fromSequenceExclusive,q.transcript.messages.throughSequence,q.transcript.toolExecutions.fromSequenceExclusive,q.transcript.toolExecutions.throughSequence,JSON.stringify(q),q.createdAt),this.replaceMessagesInTransaction(e,n,E),this.db.exec("COMMIT"),q}catch(e){throw this.db.exec("ROLLBACK"),e}}readCheckpointTranscript(e,t){if("message"!==t.kind&&"tool_execution"!==t.kind)throw new Error("kind 必须是 message 或 tool_execution");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??x,s="message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,a=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(a,s.throughSequence,r,q);const o="message"===t.kind?this.listTranscriptMessages(e,{afterSequence:a,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:a,throughSequence:s.throughSequence,limit:Math.min(r+1,A)}).map(e=>({kind:"tool_execution",...e})),i=o.length>r,c=i?o.slice(0,r):o,E=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...i&&void 0!==E?{nextAfterSequence:E}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}maxTranscriptMessageSequence(e){return this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e).maxSequence}maxToolExecutionSequence(e){const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);return(t?.next_sequence??0)-1}withCurrentTranscriptCompleteness(e,t){const n=T[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:T[0],s=r===n&&"valid"===t.toolState.integrityStatus?{...t.toolState,integrityStatus:"sanitized"}:t.toolState;return t.transcript.completeness===r&&t.toolState===s?t:{...t,toolState:s,transcript:{...t.transcript,completeness:r}}}validateTranscriptRange(e,t,n,r){if(!Number.isInteger(e)||e<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(t)||t<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(n)||n<1||n>r)throw new Error(`limit 必须是 1-${String(r)} 之间的整数`)}validateRecentPage(e,t,n){if(!Number.isSafeInteger(e)||e<0||e>Number.MAX_SAFE_INTEGER)throw new Error("beforeSequence 必须是非负安全整数");if(!Number.isInteger(t)||t<1||t>n)throw new Error(`limit 必须是 1-${String(n)} 之间的整数`)}close(){this.db.close()}}
@@ -7,12 +7,27 @@ export declare const TOOL_OUTCOME_KINDS: {
7
7
  readonly cancelled: "cancelled";
8
8
  readonly toolFailed: "tool_failed";
9
9
  };
10
+ export declare const TOOL_CANCELLATION_EXECUTION_STATES: {
11
+ readonly notExecuted: "not_executed";
12
+ readonly outcomeUnknown: "outcome_unknown";
13
+ };
10
14
  export type ToolOutcomeKind = (typeof TOOL_OUTCOME_KINDS)[keyof typeof TOOL_OUTCOME_KINDS];
15
+ export type ToolCancellationExecutionState = (typeof TOOL_CANCELLATION_EXECUTION_STATES)[keyof typeof TOOL_CANCELLATION_EXECUTION_STATES];
11
16
  export type ToolOutcome = {
12
17
  readonly kind: typeof TOOL_OUTCOME_KINDS.success;
13
18
  } | {
14
- readonly kind: typeof TOOL_OUTCOME_KINDS.userRejected | typeof TOOL_OUTCOME_KINDS.policyDenied | typeof TOOL_OUTCOME_KINDS.invalidInput | typeof TOOL_OUTCOME_KINDS.cancelled | typeof TOOL_OUTCOME_KINDS.toolFailed;
19
+ readonly kind: typeof TOOL_OUTCOME_KINDS.userRejected | typeof TOOL_OUTCOME_KINDS.policyDenied | typeof TOOL_OUTCOME_KINDS.invalidInput | typeof TOOL_OUTCOME_KINDS.toolFailed;
20
+ readonly reason?: string;
21
+ } | {
22
+ readonly kind: typeof TOOL_OUTCOME_KINDS.cancelled;
15
23
  readonly reason?: string;
24
+ /**
25
+ * Durable execution fact recorded when a Turn is interrupted.
26
+ *
27
+ * Missing means execution state was not captured, including legacy records and
28
+ * cancellations outside interrupted-Turn persistence.
29
+ */
30
+ readonly executionState?: ToolCancellationExecutionState;
16
31
  };
17
32
  export type ToolModelContentPart = {
18
33
  type: "text";
@@ -71,6 +86,7 @@ export declare function createToolResult(outcome: ToolOutcome, display: unknown,
71
86
  export declare function successfulToolResult(display: unknown, options?: Omit<ToolResultOptions, "reason">): NormalizedToolResult;
72
87
  export declare function failedToolResult(kind: Exclude<ToolOutcomeKind, typeof TOOL_OUTCOME_KINDS.success>, display: unknown, options?: ToolResultOptions): NormalizedToolResult;
73
88
  export declare function normalizeToolResult(result: unknown): NormalizedToolResult;
89
+ export declare function isToolCancellationExecutionState(value: unknown): value is ToolCancellationExecutionState;
74
90
  export declare function isNormalizedToolResult(value: unknown): value is NormalizedToolResult;
75
91
  export declare function readToolOutcome(value: unknown): ToolOutcome;
76
92
  export declare function readIsError(value: unknown): boolean;
@@ -1 +1 @@
1
- import{extractTextContent as t,isToolErrorResult as e}from"@roll-agent/core/cli/utils/tool-results";export const TOOL_OUTCOME_KINDS={success:"success",userRejected:"user_rejected",policyDenied:"policy_denied",invalidInput:"invalid_input",cancelled:"cancelled",toolFailed:"tool_failed"};const n=16e3,r=64,o=6e4,i=128,u="\n\n[工具模型内容已截断;完整结果仍保留在 raw 视图]";function s(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function l(t){if(null===t||"string"==typeof t||"boolean"==typeof t||"number"==typeof t&&Number.isFinite(t))return t;if(Array.isArray(t))return t.map(l);if(s(t)){const e={};for(const[n,r]of Object.entries(t))e[n]=l(r);return e}return void 0===t?null:String(t)}function c(t){if(!t)return!1;const e=t.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(t=>t.length>0);return 1===e.length&&["data","image","audio"].includes(e[0]??"")||e.some(t=>["base64","blob","bytes"].includes(t))||e.includes("data")&&e.some(t=>["image","audio","binary"].includes(t))}function a(t){return`[binary content omitted: ${String(t.length)} chars]`}function p(t,e,n){return t.length<=e?t:e<=n.length?n.slice(0,e):`${t.slice(0,e-n.length)}${n}`}function d(t,e,n=0){if(n>16)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):p(t,o,u);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,i).map(t=>d(t,void 0,n+1));return t.length>i&&e.push(`[${String(t.length-i)} items omitted]`),e}if(s(t)){const e={},r=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,o]of r.slice(0,i))e[t]=d(o,t,n+1);return r.length>i&&(e["[omitted]"]=`${String(r.length-i)} fields`),e}return void 0===t?null:String(t)}function f(t,e,o=0){if(o>12)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):t.length<=n?t:g(t);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,r).map(t=>f(t,void 0,o+1));return t.length>r&&e.push(`[${String(t.length-r)} items omitted]`),e}if(s(t)){const e={},n=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,i]of n.slice(0,r))e[t]=f(i,t,o+1);return n.length>r&&(e["[omitted]"]=`${String(n.length-r)} fields`),e}return void 0===t?null:String(t)}function y(t){return"string"==typeof t?t:JSON.stringify(l(t),null,2)}function g(t){return t.length<=n?t:`${t.slice(0,n)}\n\n[工具展示内容已截断]`}function m(t,e=!1){const n=d(t),r=y(n);if(r.length<=o)return e?{type:"error-json",value:n}:{type:"json",value:n};const i=p(r,o,u);return e?{type:"error-text",value:i}:{type:"text",value:i}}function O(t,e=!1){const n=p(t,o,u);return e?{type:"error-text",value:n}:{type:"text",value:n}}function T(t){const e=[];let n=o;for(const r of t.slice(0,i)){if(n<=0)break;if("text"===r.type){const t=p(r.text,n,u);e.push({type:"text",text:t}),n-=t.length;continue}const t=r.data.data.length;if(t<=n){e.push(r),n-=t;continue}const o=p(`[${r.mediaType} file omitted from model projection: ${String(t)} chars]`,n,u);e.push({type:"text",text:o}),n-=o.length}if(t.length>i&&n>0){const r=p(`[${String(t.length-i)} model content parts omitted]`,n,u);e.push({type:"text",text:r})}return e}function h(t,e){return e.kind===TOOL_OUTCOME_KINDS.userRejected?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:e.kind===TOOL_OUTCOME_KINDS.success?"string"==typeof t?O(t):m(t):m({code:e.kind,message:"string"==typeof t?t:y(t),...void 0!==e.reason?{reason:e.reason}:{}},!0)}export function createToolResult(t,e,n={}){const r=t.kind!==TOOL_OUTCOME_KINDS.success;return{raw:Object.prototype.hasOwnProperty.call(n,"raw")?n.raw:e,model:n.model??h(e,t),display:e,outcome:t,output:e,isError:r}}export function successfulToolResult(t,e={}){return createToolResult({kind:TOOL_OUTCOME_KINDS.success},t,e)}export function failedToolResult(t,e,n={}){return createToolResult({kind:t,...void 0!==n.reason?{reason:n.reason}:{}},e,n)}function x(t){return s(t)?t.content:void 0}function _(t){return s(t)?t.structuredContent:void 0}function v(t){return s(t)&&"toolResult"in t?{value:t.toolResult,wrapped:!0}:{value:t,wrapped:!1}}function b(t){if("string"==typeof t.data&&"string"==typeof t.mimeType)return{type:"file",data:{type:"data",data:t.data},mediaType:t.mimeType}}function S(t){const e=s(t.resource)?t.resource:void 0;if(!e)return;const n="string"==typeof e.uri?e.uri:"embedded resource";return"string"==typeof e.text?{type:"text",text:`[${n}]\n${e.text}`}:"string"==typeof e.blob?{type:"file",data:{type:"data",data:e.blob},mediaType:"string"==typeof e.mimeType?e.mimeType:"application/octet-stream",filename:n}:void 0}function $(t){if("string"!=typeof t.uri)return;const e="string"==typeof t.name?t.name:"resource",n="string"==typeof t.description?` — ${t.description}`:"";return{type:"text",text:`[${e}] ${t.uri}${n}`}}function N(t){if(s(t)&&"string"==typeof t.type)return"text"===t.type&&"string"==typeof t.text?{type:"text",text:t.text}:"image"===t.type||"audio"===t.type?b(t):"resource"===t.type?S(t):"resource_link"===t.type?$(t):void 0}function k(t,e,n,r){if(n)return h(e,{kind:TOOL_OUTCOME_KINDS.toolFailed});const o=x(t),i=Array.isArray(o)?o.flatMap(t=>{const e=N(t);return void 0===e?[]:[e]}):[],u=_(t);return void 0!==u&&i.length>0&&i.push({type:"text",text:`[structuredContent]\n${y(d(u))}`}),i.length>0?{type:"content",value:T(i)}:void 0!==u?m(u):r?"string"==typeof t?O(t):m(t):h(e,{kind:TOOL_OUTCOME_KINDS.success})}function M(e,o,i){const u=t(x(e));if(u.length>0)return g(u.join("\n"));const l=_(e);if(void 0!==l){const t=f(l),e=y(t);return e.length<=n?t:g(e)}const c=x(e);if(Array.isArray(c)&&c.length>0){const t=c.slice(0,r).flatMap(t=>{if(!s(t)||"string"!=typeof t.type)return[];const e="string"==typeof t.mimeType?` ${t.mimeType}`:"";return[`[${t.type}${e}]`]});if(t.length>0){const e=c.length-Math.min(c.length,r);return`${t.join("\n")}${e>0?`\n[另有 ${String(e)} 项未展示]`:""}`}}if(i){const t=f(e),r=y(t);return r.length<=n?t:g(r)}return o?"工具执行失败(无可展示的文本结果)":"工具执行完成(无可展示的文本结果)"}export function normalizeToolResult(t){const n=v(t),r=e(n.value),o=M(n.value,r,n.wrapped);return createToolResult(r?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success},o,{raw:t,model:k(n.value,o,r,n.wrapped)})}function C(t){return!(!s(t)||"string"!=typeof t.kind)&&Object.values(TOOL_OUTCOME_KINDS).some(e=>e===t.kind)}export function isNormalizedToolResult(t){return s(t)&&"raw"in t&&"model"in t&&"display"in t&&C(t.outcome)}export function readToolOutcome(t){return isNormalizedToolResult(t)?t.outcome:s(t)&&"execution-denied"===t.type?{kind:TOOL_OUTCOME_KINDS.userRejected,..."string"==typeof t.reason?{reason:t.reason}:{}}:s(t)&&"string"==typeof t.type&&t.type.startsWith("error")||E(t)?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success}}function E(t){return s(t)&&!0===t.isError}export function readIsError(t){return readToolOutcome(t).kind!==TOOL_OUTCOME_KINDS.success}export function readDisplayOutput(t){return isNormalizedToolResult(t)?t.display:s(t)&&"output"in t?t.output:t}export function toolResultToModelOutput(t){return t.model}
1
+ import{extractTextContent as t,isToolErrorResult as e}from"@roll-agent/core/cli/utils/tool-results";export const TOOL_OUTCOME_KINDS={success:"success",userRejected:"user_rejected",policyDenied:"policy_denied",invalidInput:"invalid_input",cancelled:"cancelled",toolFailed:"tool_failed"};export const TOOL_CANCELLATION_EXECUTION_STATES={notExecuted:"not_executed",outcomeUnknown:"outcome_unknown"};const n=16e3,o=64,r=6e4,i=128,u="\n\n[工具模型内容已截断;完整结果仍保留在 raw 视图]";function s(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function l(t){if(null===t||"string"==typeof t||"boolean"==typeof t||"number"==typeof t&&Number.isFinite(t))return t;if(Array.isArray(t))return t.map(l);if(s(t)){const e={};for(const[n,o]of Object.entries(t))e[n]=l(o);return e}return void 0===t?null:String(t)}function c(t){if(!t)return!1;const e=t.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(t=>t.length>0);return 1===e.length&&["data","image","audio"].includes(e[0]??"")||e.some(t=>["base64","blob","bytes"].includes(t))||e.includes("data")&&e.some(t=>["image","audio","binary"].includes(t))}function a(t){return`[binary content omitted: ${String(t.length)} chars]`}function p(t,e,n){return t.length<=e?t:e<=n.length?n.slice(0,e):`${t.slice(0,e-n.length)}${n}`}function d(t,e,n=0){if(n>16)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):p(t,r,u);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,i).map(t=>d(t,void 0,n+1));return t.length>i&&e.push(`[${String(t.length-i)} items omitted]`),e}if(s(t)){const e={},o=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,r]of o.slice(0,i))e[t]=d(r,t,n+1);return o.length>i&&(e["[omitted]"]=`${String(o.length-i)} fields`),e}return void 0===t?null:String(t)}function f(t,e,r=0){if(r>12)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):t.length<=n?t:O(t);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,o).map(t=>f(t,void 0,r+1));return t.length>o&&e.push(`[${String(t.length-o)} items omitted]`),e}if(s(t)){const e={},n=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,i]of n.slice(0,o))e[t]=f(i,t,r+1);return n.length>o&&(e["[omitted]"]=`${String(n.length-o)} fields`),e}return void 0===t?null:String(t)}function y(t){return"string"==typeof t?t:JSON.stringify(l(t),null,2)}function O(t){return t.length<=n?t:`${t.slice(0,n)}\n\n[工具展示内容已截断]`}function g(t,e=!1){const n=d(t),o=y(n);if(o.length<=r)return e?{type:"error-json",value:n}:{type:"json",value:n};const i=p(o,r,u);return e?{type:"error-text",value:i}:{type:"text",value:i}}function m(t,e=!1){const n=p(t,r,u);return e?{type:"error-text",value:n}:{type:"text",value:n}}function T(t){const e=[];let n=r;for(const o of t.slice(0,i)){if(n<=0)break;if("text"===o.type){const t=p(o.text,n,u);e.push({type:"text",text:t}),n-=t.length;continue}const t=o.data.data.length;if(t<=n){e.push(o),n-=t;continue}const r=p(`[${o.mediaType} file omitted from model projection: ${String(t)} chars]`,n,u);e.push({type:"text",text:r}),n-=r.length}if(t.length>i&&n>0){const o=p(`[${String(t.length-i)} model content parts omitted]`,n,u);e.push({type:"text",text:o})}return e}function x(t,e){return e.kind===TOOL_OUTCOME_KINDS.userRejected?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:e.kind===TOOL_OUTCOME_KINDS.success?"string"==typeof t?m(t):g(t):g({code:e.kind,message:"string"==typeof t?t:y(t),...void 0!==e.reason?{reason:e.reason}:{}},!0)}export function createToolResult(t,e,n={}){const o=t.kind!==TOOL_OUTCOME_KINDS.success;return{raw:Object.prototype.hasOwnProperty.call(n,"raw")?n.raw:e,model:n.model??x(e,t),display:e,outcome:t,output:e,isError:o}}export function successfulToolResult(t,e={}){return createToolResult({kind:TOOL_OUTCOME_KINDS.success},t,e)}export function failedToolResult(t,e,n={}){return createToolResult({kind:t,...void 0!==n.reason?{reason:n.reason}:{}},e,n)}function _(t){return s(t)?t.content:void 0}function h(t){return s(t)?t.structuredContent:void 0}function v(t){return s(t)&&"toolResult"in t?{value:t.toolResult,wrapped:!0}:{value:t,wrapped:!1}}function S(t){if("string"==typeof t.data&&"string"==typeof t.mimeType)return{type:"file",data:{type:"data",data:t.data},mediaType:t.mimeType}}function b(t){const e=s(t.resource)?t.resource:void 0;if(!e)return;const n="string"==typeof e.uri?e.uri:"embedded resource";return"string"==typeof e.text?{type:"text",text:`[${n}]\n${e.text}`}:"string"==typeof e.blob?{type:"file",data:{type:"data",data:e.blob},mediaType:"string"==typeof e.mimeType?e.mimeType:"application/octet-stream",filename:n}:void 0}function E(t){if("string"!=typeof t.uri)return;const e="string"==typeof t.name?t.name:"resource",n="string"==typeof t.description?` — ${t.description}`:"";return{type:"text",text:`[${e}] ${t.uri}${n}`}}function N(t){if(s(t)&&"string"==typeof t.type)return"text"===t.type&&"string"==typeof t.text?{type:"text",text:t.text}:"image"===t.type||"audio"===t.type?S(t):"resource"===t.type?b(t):"resource_link"===t.type?E(t):void 0}function C(t,e,n,o){if(n)return x(e,{kind:TOOL_OUTCOME_KINDS.toolFailed});const r=_(t),i=Array.isArray(r)?r.flatMap(t=>{const e=N(t);return void 0===e?[]:[e]}):[],u=h(t);return void 0!==u&&i.length>0&&i.push({type:"text",text:`[structuredContent]\n${y(d(u))}`}),i.length>0?{type:"content",value:T(i)}:void 0!==u?g(u):o?"string"==typeof t?m(t):g(t):x(e,{kind:TOOL_OUTCOME_KINDS.success})}function $(e,r,i){const u=t(_(e));if(u.length>0)return O(u.join("\n"));const l=h(e);if(void 0!==l){const t=f(l),e=y(t);return e.length<=n?t:O(e)}const c=_(e);if(Array.isArray(c)&&c.length>0){const t=c.slice(0,o).flatMap(t=>{if(!s(t)||"string"!=typeof t.type)return[];const e="string"==typeof t.mimeType?` ${t.mimeType}`:"";return[`[${t.type}${e}]`]});if(t.length>0){const e=c.length-Math.min(c.length,o);return`${t.join("\n")}${e>0?`\n[另有 ${String(e)} 项未展示]`:""}`}}if(i){const t=f(e),o=y(t);return o.length<=n?t:O(o)}return r?"工具执行失败(无可展示的文本结果)":"工具执行完成(无可展示的文本结果)"}export function normalizeToolResult(t){const n=v(t),o=e(n.value),r=$(n.value,o,n.wrapped);return createToolResult(o?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success},r,{raw:t,model:C(n.value,r,o,n.wrapped)})}export function isToolCancellationExecutionState(t){return Object.values(TOOL_CANCELLATION_EXECUTION_STATES).some(e=>e===t)}function L(t){return!(!s(t)||"string"!=typeof t.kind)&&(!!Object.values(TOOL_OUTCOME_KINDS).some(e=>e===t.kind)&&((void 0===t.reason||"string"==typeof t.reason)&&(t.kind!==TOOL_OUTCOME_KINDS.cancelled?void 0===t.executionState:void 0===t.executionState||isToolCancellationExecutionState(t.executionState))))}export function isNormalizedToolResult(t){return s(t)&&"raw"in t&&"model"in t&&"display"in t&&L(t.outcome)}export function readToolOutcome(t){return isNormalizedToolResult(t)?t.outcome:s(t)&&"execution-denied"===t.type?{kind:TOOL_OUTCOME_KINDS.userRejected,..."string"==typeof t.reason?{reason:t.reason}:{}}:s(t)&&"string"==typeof t.type&&t.type.startsWith("error")||k(t)?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success}}function k(t){return s(t)&&!0===t.isError}export function readIsError(t){return readToolOutcome(t).kind!==TOOL_OUTCOME_KINDS.success}export function readDisplayOutput(t){return isNormalizedToolResult(t)?t.display:s(t)&&"output"in t?t.output:t}export function toolResultToModelOutput(t){return t.model}
@@ -1 +1 @@
1
- import{createHash as e,randomUUID as t}from"node:crypto";import{TOOL_OUTCOME_KINDS as n}from"./normalize-result.js";export const TOOL_EXECUTION_RECORD_VERSION=1;export const TOOL_EXECUTION_VALUE_ENVELOPE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_LIMITS={inputBytes:32768,rawBytes:65536,modelBytes:65536,displayBytes:32768,outcomeBytes:8192,identifierBytes:4096,recordBytes:262144};export const TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS={sizeLimit:"size_limit",recordLimit:"record_limit"};export const TOOL_EXECUTION_VALUE_ENCODINGS={json:"json",diagnostic:"diagnostic",redacted:"redacted"};export const TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS={error:"error",undefined:"undefined",bigint:"bigint",cycle:"cycle",nonFiniteNumber:"non_finite_number",unsupported:"unsupported",unreadable:"unreadable"};const o=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu,r=Object.freeze({version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.redacted}),i="[redacted]",a=/(?:密码|口令|密钥|秘钥|令牌|凭证|授权(?:信息)?)$/u,s=new Set(["accesskey","accesstoken","apikey","authkey","authsecret","authtoken","bearertoken","clientkey","clientsecret","connectionstring","privatekey","refreshtoken","secretkey","sessiontoken"]),c=/(?<![\p{L}\p{N}_.-])(?:(['"`])([\p{L}_][\p{L}\p{N}_.-]*)\1|([\p{L}_][\p{L}\p{N}_.-]*))(\s*[:=:=]\s*)/gu,u=/data:[^,\s]+;base64,[a-z0-9+/_=-]+(?:(?:[ \t]*\r?\n[ \t]*|[ \t]*\\(?:r\\n|n)[ \t]*)[a-z0-9+/_=-]+)*/giu,d=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----[\s\S]*?(?:-----END(?: [A-Z0-9]+)* PRIVATE KEY-----|$)/giu,l=/^[a-f0-9]{64}$/u;function p(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function E(e,t=0){return!(t>64)&&(!!(null===e||"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e))||(Array.isArray(e)?e.every(e=>E(e,t+1)):p(e)&&Object.values(e).every(e=>E(e,t+1))))}function f(e){return p(e)&&Object.values(TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS).includes(e.kind)&&"string"==typeof e.path&&"string"==typeof e.message&&(void 0===e.detail||"string"==typeof e.detail)&&(void 0===e.referencePath||"string"==typeof e.referencePath)}function O(e){return!(!p(e)||1!==e.version||!E(e.value))&&(e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json||e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic&&Array.isArray(e.diagnostics)&&e.diagnostics.every(f))}function y(e){return!!p(e)&&(e.kind===n.success||Object.values(n).includes(e.kind)&&(void 0===e.reason||"string"==typeof e.reason))}function I(e){return!!p(e)&&("text"===e.type?"string"==typeof e.text:"file"===e.type&&p(e.data)&&"data"===e.data.type&&"string"==typeof e.data.data&&"string"==typeof e.mediaType&&(void 0===e.filename||"string"==typeof e.filename))}function N(e){return!(!p(e)||"string"!=typeof e.type)&&("text"===e.type||"error-text"===e.type?"string"==typeof e.value:"json"===e.type||"error-json"===e.type?E(e.value):"execution-denied"===e.type?void 0===e.reason||"string"==typeof e.reason:"content"===e.type&&Array.isArray(e.value)&&e.value.every(I))}function _(e,t){return`${e}[${JSON.stringify(t)}]`}function m(e,t){return`${e}[${String(t)}]`}function T(e,t){return`${e}[${String(t)}]`}function g(e){try{return String(e)}catch{return"[value could not be stringified]"}}function S(e){return{$rollDiagnostic:{kind:e.kind,path:e.path,message:e.message,...void 0!==e.detail?{detail:e.detail}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}}function v(e,t){return e.diagnostics.push(t),S(t)}function L(e,t){let n;try{n=e.stack}catch{n=void 0}let o="Error",r="Error value could not be inspected";try{o=e.name,r=e.message}catch{}return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.error,path:t,message:`${o}: ${r}`,...void 0!==n?{detail:n}:{}}}function C(e,t,n){return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unsupported,path:e,message:t,...void 0!==n?{detail:n}:{}}}function b(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function x(e,t,n){const o=[];for(let r=0;r<e.length;r+=1){const i=m(t,r);if(!Object.prototype.hasOwnProperty.call(e,r)){o.push(v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:i,message:"Sparse array slots are not JSON values"}));continue}let a;try{a=e[r]}catch(e){o.push(v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:i,message:"Array item could not be read",detail:g(e)}));continue}o.push(U(a,i,n))}for(const o of Reflect.ownKeys(e)){if("length"===o||"string"==typeof o&&/^(0|[1-9][0-9]*)$/u.test(o))continue;v(n,C("symbol"==typeof o?T(t,o):_(t,o),"Non-index array properties are not representable in JSON"))}return o}function h(e,t,n){const o={};for(const r of Reflect.ownKeys(e)){const i="symbol"==typeof r?T(t,r):_(t,r);if("symbol"==typeof r){v(n,C(i,"Symbol-keyed properties are not representable in JSON"));continue}const a=Object.getOwnPropertyDescriptor(e,r);void 0!==a?a.enumerable?b(o,r,"value"in a?U(a.value,i,n):v(n,C(i,"Accessor properties are not read during persistence"))):v(n,C(i,"Non-enumerable properties are not representable in JSON")):v(n,C(i,"Property descriptor disappeared while encoding"))}return o}function A(e,t,n){const o=n.ancestors.get(e);if(void 0!==o)return v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.cycle,path:t,message:"Cyclic references are not representable in JSON",referencePath:o});n.ancestors.set(e,t);try{if(e instanceof Error)return v(n,L(e,t));if(Array.isArray(e))return x(e,t,n);const o=Object.getPrototypeOf(e);return o!==Object.prototype&&null!==o?v(n,C(t,"Only plain objects and arrays are losslessly representable in JSON",g(e))):h(e,t,n)}catch(e){return v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:t,message:"Object could not be inspected for persistence",detail:g(e)})}finally{n.ancestors.delete(e)}}function U(e,t,n){return null===e||"string"==typeof e||"boolean"==typeof e?e:"number"==typeof e?Number.isFinite(e)?e:v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.nonFiniteNumber,path:t,message:"Non-finite numbers are not JSON values",detail:g(e)}):void 0===e?v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:t,message:"undefined is not a JSON value"}):"bigint"==typeof e?v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.bigint,path:t,message:"BigInt is not a JSON value",detail:e.toString()}):"symbol"==typeof e?v(n,C(t,"Symbols are not JSON values",g(e))):"function"==typeof e?v(n,C(t,"Functions are not JSON values",e.name||"anonymous")):A(e,t,n)}function R(e){return encodeToolExecutionValue(e).value}function k(e){return"text"===e.type?{type:"text",text:e.text}:{type:"file",data:{type:"data",data:e.data.data},mediaType:e.mediaType,...void 0!==e.filename?{filename:e.filename}:{}}}function D(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:e.value}:"json"===e.type||"error-json"===e.type?{type:e.type,value:R(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:{type:"content",value:e.value.map(k)}}function X(e){return"success"===e.kind?{kind:"success"}:{kind:e.kind,...void 0!==e.reason?{reason:e.reason}:{}}}function V(e){return e.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(e=>e.length>0)}export function isSensitiveFieldName(e){if("_meta"===e)return!0;const t=e.trim();if(a.test(t))return!0;const n=t.toLowerCase().replace(/[^a-z0-9]+/gu,"");if(s.has(n))return!0;const o=V(e);if(o.some(e=>["authorization","cookie","credential","credentials","passphrase","passwd","password","secret"].includes(e)))return!0;if(o.includes("token"))return!o.some(e=>["budget","count","limit","maximum","minimum","total","usage"].includes(e));const r=o.some(e=>["dsn","uri","url"].includes(e)),i=o.some(e=>["connection","database","datasource","db","jdbc","mongo","mongodb","mysql","postgres","postgresql","redis"].includes(e));return!!(r&&i||o.includes("connection")&&o.includes("string"))||o.includes("key")&&o.some(e=>["access","api","auth","client","private","secret"].includes(e))}function P(e){if(void 0===e)return!1;const t=V(e);return 1===t.length&&["audio","data","image"].includes(t[0]??"")||t.some(e=>["base64","blob","bytes"].includes(e))||t.includes("data")&&t.some(e=>["audio","binary","image"].includes(e))}function $(e){return`[binary content omitted: ${String(e.length)} chars]`}function j(e,t,n){let o=!1;for(let r=t+1;r<e.length;r+=1){const t=e[r];if(o)o=!1;else if("\\"!==t){if(t===n)return r+1}else o=!0}return e.length}function B(e,t){const n=e[t];if('"'===n||"'"===n||"`"===n)return j(e,t,n);if("|"===n||">"===n)return e.length;const o=/[\r\n]/u.exec(e.slice(t));return null===o?e.length:t+o.index}function w(e,t){let n=0,o="";for(const r of e.matchAll(c)){if(r.index<n)continue;const i=r[2]??r[3];if(void 0===i||!isSensitiveFieldName(i))continue;const a=r.index+r[0].length,s=B(e,a);o+=`${e.slice(n,a)}${t}`,n=Math.max(a,s)}return`${o}${e.slice(n)}`}export function redactSecretText(e,t=i){return w(e,t).replace(d,t).replace(/\bbearer\s+[a-z0-9._~+/-]+=*/giu,`Bearer ${t}`).replace(/\b(?:github_pat_[a-z0-9_]{20,}|gh[pousr]_[a-z0-9]{20,})\b/giu,t).replace(/\bxox[a-z]-[a-z0-9-]{12,}\b/giu,t).replace(/\bAKIA[0-9A-Z]{16}\b/gu,t).replace(/\b(?:pk|rk|sk)-[a-z0-9_-]{8,}\b/giu,t).replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]+\.[a-z0-9_-]+\b/giu,t)}function z(e,t){return P(t)||/^data:[^,\s]+;base64,/iu.test(e)?$(e):redactSecretText(e).replace(u,e=>$(e))}function G(e,t,n=0){if(n>64)return"[nested value omitted]";if("string"==typeof e)return z(e,t);if(null===e||"boolean"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map(e=>G(e,void 0,n+1));const o={};for(const[t,r]of Object.entries(e))o[t]=void 0===r||isSensitiveFieldName(t)?i:G(r,t,n+1);return o}function M(e){return{kind:e.kind,path:e.path,message:z(e.message),...void 0!==e.detail?{detail:z(e.detail)}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}function K(e){return e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json?{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:G(e.value)}:{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:G(e.value),diagnostics:e.diagnostics.map(M)}}function J(e){return"text"===e.type?{type:"text",text:z(e.text)}:{type:"file",data:{type:"data",data:$(e.data.data)},mediaType:z(e.mediaType),...void 0!==e.filename?{filename:z(e.filename)}:{}}}function F(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:z(e.value)}:"json"===e.type||"error-json"===e.type?{type:e.type,value:G(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:z(e.reason)}:{}}:{type:"content",value:e.value.map(J)}}function Z(e){return e.kind===n.success?{kind:n.success}:{kind:e.kind,...void 0!==e.reason?{reason:z(e.reason)}:{}}}function q(e){return JSON.stringify(e)}function Y(e){return Buffer.byteLength(e,"utf8")}function W(t){return e("sha256").update(t,"utf8").digest("hex")}function H(e,t){return{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:{$rollEvidence:{omitted:!0,reason:t,redactedSha256:e}}}}function Q(e,t,n,o){return{originalByteLength:Y(e),storedByteLength:Y(n),redactedSha256:W(t),redactionApplied:!0,truncated:void 0!==o,...void 0!==o?{omissionReason:o}:{}}}function ee(e,t,n){const o=q(e),r=K(e),i=q(r),a=n??(Y(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0);if(void 0===a)return{value:r,metadata:Q(o,i,i)};const s=H(W(i),a);return{value:s,metadata:Q(o,i,q(s),a)}}function te(e,t,n){const o=z(e);if(Y(o)<=t)return{value:o,truncated:!1};const r=`…[${n} omitted; sha256:${W(o)}]`,i=Y(r);if(i>=t)return{value:r.slice(0,t),truncated:!0};let a=o;for(;a.length>0&&Y(a)+i>t;)a=a.slice(0,Math.floor(.9*a.length));return{value:`${a}${r}`,truncated:!0}}function ne(e,t,n){const o=q(e),r=F(e),i=q(r),a=n??(Y(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),s=void 0===a?r:{type:"text",value:`[durable model evidence omitted; sha256:${W(i)}]`};return{value:s,metadata:Q(o,i,q(s),a)}}function oe(e,t,o){const r=q(e),i=Z(e),a=q(i),s=o??(Y(a)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),c=void 0===s||i.kind===n.success?i:{kind:i.kind,reason:`[durable outcome detail omitted; sha256:${W(a)}]`};return{value:c,metadata:Q(r,a,q(c),s)}}function re(e){return p(e)&&Number.isInteger(e.originalByteLength)&&"number"==typeof e.originalByteLength&&e.originalByteLength>=0&&Number.isInteger(e.storedByteLength)&&"number"==typeof e.storedByteLength&&e.storedByteLength>=0&&"string"==typeof e.redactedSha256&&l.test(e.redactedSha256)&&!0===e.redactionApplied&&"boolean"==typeof e.truncated&&(void 0===e.omissionReason||Object.values(TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS).includes(e.omissionReason))}function ie(e){return p(e)&&1===e.version&&p(e.fields)&&re(e.fields.input)&&re(e.fields.raw)&&re(e.fields.model)&&re(e.fields.display)&&re(e.fields.outcome)}export function isPersistedToolExecutionRecord(e){return isToolExecutionRecord(e)&&p(e)&&ie(e.persistence)}function ae(e,t){const n=ee(e.input,TOOL_EXECUTION_PERSISTENCE_LIMITS.inputBytes,t),o=ee(e.raw,TOOL_EXECUTION_PERSISTENCE_LIMITS.rawBytes,t),r=ne(e.model,TOOL_EXECUTION_PERSISTENCE_LIMITS.modelBytes,t),i=ee(e.display,TOOL_EXECUTION_PERSISTENCE_LIMITS.displayBytes,t),a=oe(e.outcome,TOOL_EXECUTION_PERSISTENCE_LIMITS.outcomeBytes,t);return{version:e.version,id:e.id,toolCallId:te(e.toolCallId,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolCallId").value,agentName:te(e.agentName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"agentName").value,toolName:te(e.toolName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolName").value,createdAt:te(e.createdAt,256,"createdAt").value,input:n.value,raw:o.value,model:r.value,display:i.value,outcome:a.value,persistence:{version:1,fields:{input:n.metadata,raw:o.metadata,model:r.metadata,display:i.metadata,outcome:a.metadata}}}}export function prepareToolExecutionRecordForPersistence(e){const t=ae(e);return Y(q(t))<=TOOL_EXECUTION_PERSISTENCE_LIMITS.recordBytes?t:ae(e,TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.recordLimit)}export function parsePersistedToolExecutionRecord(e){return isPersistedToolExecutionRecord(e)?e:prepareToolExecutionRecordForPersistence(parseToolExecutionRecord(e))}export function isToolExecutionRecordId(e){return"string"==typeof e&&o.test(e)}export function parseToolExecutionRecordId(e){if(!isToolExecutionRecordId(e))throw new Error(`Invalid ToolExecutionRecord UUID: ${e}`);return e}export function createToolExecutionRecordId(){return parseToolExecutionRecordId(t())}export function isToolExecutionRecord(e){return p(e)&&1===e.version&&isToolExecutionRecordId(e.id)&&"string"==typeof e.toolCallId&&"string"==typeof e.agentName&&"string"==typeof e.toolName&&"string"==typeof e.createdAt&&O(e.input)&&O(e.raw)&&N(e.model)&&O(e.display)&&y(e.outcome)}export function parseToolExecutionRecord(e){if(!isToolExecutionRecord(e))throw new Error("Invalid persisted ToolExecutionRecord");return e}export function encodeToolExecutionValue(e){const t={ancestors:new WeakMap,diagnostics:[]},n=U(e,"$",t);return 0===t.diagnostics.length?{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:n}:{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:n,diagnostics:t.diagnostics}}export function createToolExecutionRecord(e){return{version:1,id:void 0!==e.id?parseToolExecutionRecordId(e.id):createToolExecutionRecordId(),toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt??(new Date).toISOString(),input:encodeToolExecutionValue(e.input),raw:encodeToolExecutionValue(e.result.raw),model:D(e.result.model),display:encodeToolExecutionValue(e.result.display),outcome:X(e.result.outcome)}}export function toRedactedToolExecutionRecordSummary(e){return{version:e.version,id:e.id,toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt,input:r,raw:r,model:F(e.model),display:K(e.display),outcome:Z(e.outcome),...isPersistedToolExecutionRecord(e)?{persistence:e.persistence}:{}}}
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import{TOOL_OUTCOME_KINDS as n,isToolCancellationExecutionState as o}from"./normalize-result.js";export const TOOL_EXECUTION_RECORD_VERSION=1;export const TOOL_EXECUTION_VALUE_ENVELOPE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_LIMITS={inputBytes:32768,rawBytes:65536,modelBytes:65536,displayBytes:32768,outcomeBytes:8192,identifierBytes:4096,recordBytes:262144};export const TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS={sizeLimit:"size_limit",recordLimit:"record_limit"};export const TOOL_EXECUTION_VALUE_ENCODINGS={json:"json",diagnostic:"diagnostic",redacted:"redacted"};export const TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS={error:"error",undefined:"undefined",bigint:"bigint",cycle:"cycle",nonFiniteNumber:"non_finite_number",unsupported:"unsupported",unreadable:"unreadable"};const r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu,i=Object.freeze({version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.redacted}),a="[redacted]",s=/(?:密码|口令|密钥|秘钥|令牌|凭证|授权(?:信息)?)$/u,c=new Set(["accesskey","accesstoken","apikey","authkey","authsecret","authtoken","bearertoken","clientkey","clientsecret","connectionstring","privatekey","refreshtoken","secretkey","sessiontoken"]),u=/(?<![\p{L}\p{N}_.-])(?:(['"`])([\p{L}_][\p{L}\p{N}_.-]*)\1|([\p{L}_][\p{L}\p{N}_.-]*))(\s*[:=:=]\s*)/gu,d=/data:[^,\s]+;base64,[a-z0-9+/_=-]+(?:(?:[ \t]*\r?\n[ \t]*|[ \t]*\\(?:r\\n|n)[ \t]*)[a-z0-9+/_=-]+)*/giu,l=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----[\s\S]*?(?:-----END(?: [A-Z0-9]+)* PRIVATE KEY-----|$)/giu,p=/^[a-f0-9]{64}$/u;function E(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function f(e,t=0){return!(t>64)&&(!!(null===e||"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e))||(Array.isArray(e)?e.every(e=>f(e,t+1)):E(e)&&Object.values(e).every(e=>f(e,t+1))))}function O(e){return E(e)&&Object.values(TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS).includes(e.kind)&&"string"==typeof e.path&&"string"==typeof e.message&&(void 0===e.detail||"string"==typeof e.detail)&&(void 0===e.referencePath||"string"==typeof e.referencePath)}function y(e){return!(!E(e)||1!==e.version||!f(e.value))&&(e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json||e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic&&Array.isArray(e.diagnostics)&&e.diagnostics.every(O))}function I(e){return!!E(e)&&(e.kind===n.success?void 0===e.reason&&void 0===e.executionState:!(!Object.values(n).includes(e.kind)||void 0!==e.reason&&"string"!=typeof e.reason)&&(e.kind!==n.cancelled?void 0===e.executionState:void 0===e.executionState||o(e.executionState)))}function N(e){return!!E(e)&&("text"===e.type?"string"==typeof e.text:"file"===e.type&&E(e.data)&&"data"===e.data.type&&"string"==typeof e.data.data&&"string"==typeof e.mediaType&&(void 0===e.filename||"string"==typeof e.filename))}function _(e){return!(!E(e)||"string"!=typeof e.type)&&("text"===e.type||"error-text"===e.type?"string"==typeof e.value:"json"===e.type||"error-json"===e.type?f(e.value):"execution-denied"===e.type?void 0===e.reason||"string"==typeof e.reason:"content"===e.type&&Array.isArray(e.value)&&e.value.every(N))}function m(e,t){return`${e}[${JSON.stringify(t)}]`}function T(e,t){return`${e}[${String(t)}]`}function g(e,t){return`${e}[${String(t)}]`}function S(e){try{return String(e)}catch{return"[value could not be stringified]"}}function v(e){return{$rollDiagnostic:{kind:e.kind,path:e.path,message:e.message,...void 0!==e.detail?{detail:e.detail}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}}function x(e,t){return e.diagnostics.push(t),v(t)}function L(e,t){let n;try{n=e.stack}catch{n=void 0}let o="Error",r="Error value could not be inspected";try{o=e.name,r=e.message}catch{}return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.error,path:t,message:`${o}: ${r}`,...void 0!==n?{detail:n}:{}}}function C(e,t,n){return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unsupported,path:e,message:t,...void 0!==n?{detail:n}:{}}}function b(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function h(e,t,n){const o=[];for(let r=0;r<e.length;r+=1){const i=T(t,r);if(!Object.prototype.hasOwnProperty.call(e,r)){o.push(x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:i,message:"Sparse array slots are not JSON values"}));continue}let a;try{a=e[r]}catch(e){o.push(x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:i,message:"Array item could not be read",detail:S(e)}));continue}o.push(R(a,i,n))}for(const o of Reflect.ownKeys(e)){if("length"===o||"string"==typeof o&&/^(0|[1-9][0-9]*)$/u.test(o))continue;x(n,C("symbol"==typeof o?g(t,o):m(t,o),"Non-index array properties are not representable in JSON"))}return o}function A(e,t,n){const o={};for(const r of Reflect.ownKeys(e)){const i="symbol"==typeof r?g(t,r):m(t,r);if("symbol"==typeof r){x(n,C(i,"Symbol-keyed properties are not representable in JSON"));continue}const a=Object.getOwnPropertyDescriptor(e,r);void 0!==a?a.enumerable?b(o,r,"value"in a?R(a.value,i,n):x(n,C(i,"Accessor properties are not read during persistence"))):x(n,C(i,"Non-enumerable properties are not representable in JSON")):x(n,C(i,"Property descriptor disappeared while encoding"))}return o}function U(e,t,n){const o=n.ancestors.get(e);if(void 0!==o)return x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.cycle,path:t,message:"Cyclic references are not representable in JSON",referencePath:o});n.ancestors.set(e,t);try{if(e instanceof Error)return x(n,L(e,t));if(Array.isArray(e))return h(e,t,n);const o=Object.getPrototypeOf(e);return o!==Object.prototype&&null!==o?x(n,C(t,"Only plain objects and arrays are losslessly representable in JSON",S(e))):A(e,t,n)}catch(e){return x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:t,message:"Object could not be inspected for persistence",detail:S(e)})}finally{n.ancestors.delete(e)}}function R(e,t,n){return null===e||"string"==typeof e||"boolean"==typeof e?e:"number"==typeof e?Number.isFinite(e)?e:x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.nonFiniteNumber,path:t,message:"Non-finite numbers are not JSON values",detail:S(e)}):void 0===e?x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:t,message:"undefined is not a JSON value"}):"bigint"==typeof e?x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.bigint,path:t,message:"BigInt is not a JSON value",detail:e.toString()}):"symbol"==typeof e?x(n,C(t,"Symbols are not JSON values",S(e))):"function"==typeof e?x(n,C(t,"Functions are not JSON values",e.name||"anonymous")):U(e,t,n)}function k(e){return encodeToolExecutionValue(e).value}function D(e){return"text"===e.type?{type:"text",text:e.text}:{type:"file",data:{type:"data",data:e.data.data},mediaType:e.mediaType,...void 0!==e.filename?{filename:e.filename}:{}}}function X(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:e.value}:"json"===e.type||"error-json"===e.type?{type:e.type,value:k(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:{type:"content",value:e.value.map(D)}}function V(e){return"success"===e.kind?{kind:"success"}:{kind:e.kind,...void 0!==e.reason?{reason:e.reason}:{},...e.kind===n.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function P(e){return e.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(e=>e.length>0)}export function isSensitiveFieldName(e){if("_meta"===e)return!0;const t=e.trim();if(s.test(t))return!0;const n=t.toLowerCase().replace(/[^a-z0-9]+/gu,"");if(c.has(n))return!0;const o=P(e);if(o.some(e=>["authorization","cookie","credential","credentials","passphrase","passwd","password","secret"].includes(e)))return!0;if(o.includes("token"))return!o.some(e=>["budget","count","limit","maximum","minimum","total","usage"].includes(e));const r=o.some(e=>["dsn","uri","url"].includes(e)),i=o.some(e=>["connection","database","datasource","db","jdbc","mongo","mongodb","mysql","postgres","postgresql","redis"].includes(e));return!!(r&&i||o.includes("connection")&&o.includes("string"))||o.includes("key")&&o.some(e=>["access","api","auth","client","private","secret"].includes(e))}function $(e){if(void 0===e)return!1;const t=P(e);return 1===t.length&&["audio","data","image"].includes(t[0]??"")||t.some(e=>["base64","blob","bytes"].includes(e))||t.includes("data")&&t.some(e=>["audio","binary","image"].includes(e))}function j(e){return`[binary content omitted: ${String(e.length)} chars]`}function B(e,t,n){let o=!1;for(let r=t+1;r<e.length;r+=1){const t=e[r];if(o)o=!1;else if("\\"!==t){if(t===n)return r+1}else o=!0}return e.length}function w(e,t){const n=e[t];if('"'===n||"'"===n||"`"===n)return B(e,t,n);if("|"===n||">"===n)return e.length;const o=/[\r\n]/u.exec(e.slice(t));return null===o?e.length:t+o.index}function z(e,t){let n=0,o="";for(const r of e.matchAll(u)){if(r.index<n)continue;const i=r[2]??r[3];if(void 0===i||!isSensitiveFieldName(i))continue;const a=r.index+r[0].length,s=w(e,a);o+=`${e.slice(n,a)}${t}`,n=Math.max(a,s)}return`${o}${e.slice(n)}`}export function redactSecretText(e,t=a){return z(e,t).replace(l,t).replace(/\bbearer\s+[a-z0-9._~+/-]+=*/giu,`Bearer ${t}`).replace(/\b(?:github_pat_[a-z0-9_]{20,}|gh[pousr]_[a-z0-9]{20,})\b/giu,t).replace(/\bxox[a-z]-[a-z0-9-]{12,}\b/giu,t).replace(/\bAKIA[0-9A-Z]{16}\b/gu,t).replace(/\b(?:pk|rk|sk)-[a-z0-9_-]{8,}\b/giu,t).replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]+\.[a-z0-9_-]+\b/giu,t)}function G(e,t){return $(t)||/^data:[^,\s]+;base64,/iu.test(e)?j(e):redactSecretText(e).replace(d,e=>j(e))}function M(e,t,n=0){if(n>64)return"[nested value omitted]";if("string"==typeof e)return G(e,t);if(null===e||"boolean"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map(e=>M(e,void 0,n+1));const o={};for(const[t,r]of Object.entries(e))o[t]=void 0===r||isSensitiveFieldName(t)?a:M(r,t,n+1);return o}function K(e){return{kind:e.kind,path:e.path,message:G(e.message),...void 0!==e.detail?{detail:G(e.detail)}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}function J(e){return e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json?{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:M(e.value)}:{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:M(e.value),diagnostics:e.diagnostics.map(K)}}function F(e){return"text"===e.type?{type:"text",text:G(e.text)}:{type:"file",data:{type:"data",data:j(e.data.data)},mediaType:G(e.mediaType),...void 0!==e.filename?{filename:G(e.filename)}:{}}}function Z(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:G(e.value)}:"json"===e.type||"error-json"===e.type?{type:e.type,value:M(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:G(e.reason)}:{}}:{type:"content",value:e.value.map(F)}}function q(e){return e.kind===n.success?{kind:n.success}:{kind:e.kind,...void 0!==e.reason?{reason:G(e.reason)}:{},...e.kind===n.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function Y(e){return JSON.stringify(e)}function W(e){return Buffer.byteLength(e,"utf8")}function H(t){return e("sha256").update(t,"utf8").digest("hex")}function Q(e,t){return{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:{$rollEvidence:{omitted:!0,reason:t,redactedSha256:e}}}}function ee(e,t,n,o){return{originalByteLength:W(e),storedByteLength:W(n),redactedSha256:H(t),redactionApplied:!0,truncated:void 0!==o,...void 0!==o?{omissionReason:o}:{}}}function te(e,t,n){const o=Y(e),r=J(e),i=Y(r),a=n??(W(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0);if(void 0===a)return{value:r,metadata:ee(o,i,i)};const s=Q(H(i),a);return{value:s,metadata:ee(o,i,Y(s),a)}}function ne(e,t,n){const o=G(e);if(W(o)<=t)return{value:o,truncated:!1};const r=`…[${n} omitted; sha256:${H(o)}]`,i=W(r);if(i>=t)return{value:r.slice(0,t),truncated:!0};let a=o;for(;a.length>0&&W(a)+i>t;)a=a.slice(0,Math.floor(.9*a.length));return{value:`${a}${r}`,truncated:!0}}function oe(e,t,n){const o=Y(e),r=Z(e),i=Y(r),a=n??(W(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),s=void 0===a?r:{type:"text",value:`[durable model evidence omitted; sha256:${H(i)}]`};return{value:s,metadata:ee(o,i,Y(s),a)}}function re(e,t,o){const r=Y(e),i=q(e),a=Y(i),s=o??(W(a)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),c=void 0===s||i.kind===n.success?i:{kind:i.kind,reason:`[durable outcome detail omitted; sha256:${H(a)}]`,...i.kind===n.cancelled&&void 0!==i.executionState?{executionState:i.executionState}:{}};return{value:c,metadata:ee(r,a,Y(c),s)}}function ie(e){return E(e)&&Number.isInteger(e.originalByteLength)&&"number"==typeof e.originalByteLength&&e.originalByteLength>=0&&Number.isInteger(e.storedByteLength)&&"number"==typeof e.storedByteLength&&e.storedByteLength>=0&&"string"==typeof e.redactedSha256&&p.test(e.redactedSha256)&&!0===e.redactionApplied&&"boolean"==typeof e.truncated&&(void 0===e.omissionReason||Object.values(TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS).includes(e.omissionReason))}function ae(e){return E(e)&&1===e.version&&E(e.fields)&&ie(e.fields.input)&&ie(e.fields.raw)&&ie(e.fields.model)&&ie(e.fields.display)&&ie(e.fields.outcome)}export function isPersistedToolExecutionRecord(e){return isToolExecutionRecord(e)&&E(e)&&ae(e.persistence)}function se(e,t){const n=te(e.input,TOOL_EXECUTION_PERSISTENCE_LIMITS.inputBytes,t),o=te(e.raw,TOOL_EXECUTION_PERSISTENCE_LIMITS.rawBytes,t),r=oe(e.model,TOOL_EXECUTION_PERSISTENCE_LIMITS.modelBytes,t),i=te(e.display,TOOL_EXECUTION_PERSISTENCE_LIMITS.displayBytes,t),a=re(e.outcome,TOOL_EXECUTION_PERSISTENCE_LIMITS.outcomeBytes,t);return{version:e.version,id:e.id,toolCallId:ne(e.toolCallId,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolCallId").value,agentName:ne(e.agentName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"agentName").value,toolName:ne(e.toolName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolName").value,createdAt:ne(e.createdAt,256,"createdAt").value,input:n.value,raw:o.value,model:r.value,display:i.value,outcome:a.value,persistence:{version:1,fields:{input:n.metadata,raw:o.metadata,model:r.metadata,display:i.metadata,outcome:a.metadata}}}}export function prepareToolExecutionRecordForPersistence(e){const t=se(e);return W(Y(t))<=TOOL_EXECUTION_PERSISTENCE_LIMITS.recordBytes?t:se(e,TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.recordLimit)}export function parsePersistedToolExecutionRecord(e){return isPersistedToolExecutionRecord(e)?e:prepareToolExecutionRecordForPersistence(parseToolExecutionRecord(e))}export function isToolExecutionRecordId(e){return"string"==typeof e&&r.test(e)}export function parseToolExecutionRecordId(e){if(!isToolExecutionRecordId(e))throw new Error(`Invalid ToolExecutionRecord UUID: ${e}`);return e}export function createToolExecutionRecordId(){return parseToolExecutionRecordId(t())}export function isToolExecutionRecord(e){return E(e)&&1===e.version&&isToolExecutionRecordId(e.id)&&"string"==typeof e.toolCallId&&"string"==typeof e.agentName&&"string"==typeof e.toolName&&"string"==typeof e.createdAt&&y(e.input)&&y(e.raw)&&_(e.model)&&y(e.display)&&I(e.outcome)}export function parseToolExecutionRecord(e){if(!isToolExecutionRecord(e))throw new Error("Invalid persisted ToolExecutionRecord");return e}export function encodeToolExecutionValue(e){const t={ancestors:new WeakMap,diagnostics:[]},n=R(e,"$",t);return 0===t.diagnostics.length?{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:n}:{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:n,diagnostics:t.diagnostics}}export function createToolExecutionRecord(e){return{version:1,id:void 0!==e.id?parseToolExecutionRecordId(e.id):createToolExecutionRecordId(),toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt??(new Date).toISOString(),input:encodeToolExecutionValue(e.input),raw:encodeToolExecutionValue(e.result.raw),model:X(e.result.model),display:encodeToolExecutionValue(e.result.display),outcome:V(e.result.outcome)}}export function toRedactedToolExecutionRecordSummary(e){return{version:e.version,id:e.id,toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt,input:i,raw:i,model:Z(e.model),display:J(e.display),outcome:q(e.outcome),...isPersistedToolExecutionRecord(e)?{persistence:e.persistence}:{}}}
@@ -12,12 +12,12 @@ export declare const turnCancellationMetadataV1Schema: z.ZodReadonly<z.ZodObject
12
12
  reason: z.ZodEnum<["user", "timeout", "runtime"]>;
13
13
  }, "strip", z.ZodTypeAny, {
14
14
  version: 1;
15
- kind: "turn-cancellation";
16
15
  reason: "user" | "timeout" | "runtime";
16
+ kind: "turn-cancellation";
17
17
  }, {
18
18
  version: 1;
19
- kind: "turn-cancellation";
20
19
  reason: "user" | "timeout" | "runtime";
20
+ kind: "turn-cancellation";
21
21
  }>>;
22
22
  export declare function createTurnCancellationMessage(content: string, reason: SessionCancellationReason): AssistantModelMessage;
23
23
  export declare function readTurnCancellationReason(message: ModelMessage): SessionCancellationReason | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,11 +29,12 @@
29
29
  "@ai-sdk/provider": "^4.0.1",
30
30
  "@modelcontextprotocol/sdk": "^1.12.0",
31
31
  "ai": "^7.0.9",
32
- "zod": "^3.25.76"
32
+ "zod": "^3.25.76",
33
+ "@roll-agent/protocol": "0.2.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^22.0.0",
36
- "@roll-agent/core": "0.23.0"
37
+ "@roll-agent/core": "0.25.0"
37
38
  },
38
39
  "scripts": {
39
40
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",