@roll-agent/runtime 1.0.2 → 1.2.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.
- package/dist/bash/session/session-manager.d.ts +1 -0
- package/dist/bash/session/session-manager.js +1 -1
- package/dist/engine/agent-session.d.ts +9 -1
- package/dist/engine/agent-session.js +1 -1
- package/dist/engine/conversation-engine.d.ts +5 -0
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/host-contracts/index.d.ts +1 -0
- package/dist/host-contracts/index.js +1 -1
- package/dist/host-contracts/session-ownership.d.ts +12 -0
- package/dist/host-contracts/session-ownership.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/ownership/session-claims.d.ts +61 -0
- package/dist/ownership/session-claims.js +1 -0
- package/dist/server/runtime-protocol-adapter.d.ts +1 -0
- package/dist/server/runtime-protocol-adapter.js +1 -1
- package/dist/server/runtime-server.js +1 -1
- package/dist/service/attachment-store.js +1 -1
- package/dist/service/reasoning-display-buffer.d.ts +11 -0
- package/dist/service/reasoning-display-buffer.js +1 -0
- package/dist/service/runtime-service.d.ts +17 -1
- package/dist/service/runtime-service.js +1 -1
- package/dist/service/turn-reasoning-display.d.ts +20 -0
- package/dist/service/turn-reasoning-display.js +1 -0
- package/dist/store/thread-store.d.ts +51 -1
- package/dist/store/thread-store.js +1 -1
- package/dist/tool-bridge/file-tools/rg-exec.js +1 -1
- package/dist/types/events.d.ts +5 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{ConversationEngine,buildSessionBashSettings,buildSessionExecSettings}from"./engine/conversation-engine.js";export{runtimeSettingsSchema}from"./host-contracts/runtime-settings.js";export{buildScheduleToolset,SCHEDULE_CREATE_TOOL_ID}from"./tool-bridge/schedule-tool.js";export{scheduleCreateInputSchema}from"./tool-bridge/schedule-create-input.js";export{AgentSession}from"./engine/agent-session.js";export{WORKSPACE_INSTRUCTION_FILE_NAMES,WORKSPACE_INSTRUCTIONS_MAX_CHARS,WORKSPACE_INSTRUCTIONS_MODES,createWorkspaceInstructionsSource,findWorkspaceInstructionsPath,parseWorkspaceInstructionsSetting}from"./engine/workspace-instructions.js";export{resolveContextWindow,lookupContextWindow,resolveModelContextWindow,CONTEXT_WINDOW_SOURCES}from"./engine/context-window.js";export{createDefaultModelCatalog}from"./engine/model-catalog-default.js";export{ModelCatalog,defaultModelCatalogCachePath,lookupCatalogContextWindow,trimModelCatalog,MODEL_CATALOG_REFRESH_RESULTS}from"./engine/model-catalog.js";export{compactMessages,findTurnBoundaries,SUMMARY_PREFIX,SUMMARY_ACK}from"./engine/compactor.js";export{ROLL_RESOURCE_HINTS_META_KEY,buildAgentToolset}from"./tool-bridge/build-tools.js";export{COMMAND_CLASSIFICATIONS,unknownCommandClassifier}from"./types/command-classification.js";export{ruleBasedClassifier}from"./bash/classifier/index.js";export{buildPowerShellEncodedCommand,resolveShellProfile,SHELL_PROFILE_IDS,SHELL_TOOL_NAMES}from"./bash/profile.js";export{buildSessionExecToolset,EXEC_COMMAND_ID,EXEC_LIST_ID,EXEC_POLL_ID}from"./tool-bridge/session-exec-tool.js";export{ToolRegistry}from"./tool-bridge/naming.js";export{TOOL_RESOURCE_ACCESS_MODES,TOOL_RESOURCE_HINT_KINDS}from"./tool-bridge/tool-execution-coordinator.js";export{TOOL_CANCELLATION_EXECUTION_STATES,TOOL_OUTCOME_KINDS,normalizeToolResult,readToolOutcome}from"./tool-bridge/normalize-result.js";export{ThreadStore,defaultThreadsDir,expandTilde}from"./store/thread-store.js";export{stripCancelledTurnRecoveryMessages}from"./engine/cancelled-turn-recovery.js";export{THREAD_ORIGIN_KINDS,threadOriginSchema,scheduledThreadOriginSchema,threadDerivedFromSchema}from"./store/thread-origin.js";export{SCHEDULE_LEDGER_READ_STATUSES,ScheduleStore,readScheduleLedger,readScheduleOccupancy}from"./scheduler/schedule-store.js";export{SCHEDULER_LIMITS}from"./scheduler/limits.js";export{SCHEDULER_SCHEMA_VERSION}from"./scheduler/database-version.js";export{TRIGGER_KINDS,ScheduleTriggerError,computeNextRunAtMs,computeFirstRunAtMs,createScheduleTrigger,scheduleTimingInputSchema,createIntervalTrigger,describeTrigger,formatDuration,formatInterval,parseIntervalText,parseMaxRunText,parseTriggerJson,triggerSpecSchema}from"./scheduler/trigger.js";export{calendarScheduleSchema,calendarScheduleInputSchema,systemTimeZone}from"./scheduler/calendar.js";export{CANCEL_INVOCATION_OUTCOMES,COMPLETE_INVOCATION_OUTCOMES,EXECUTOR_LIVENESS,INVOCATION_FAILURE_OUTCOMES,INVOCATION_LIVE_STATUSES,INVOCATION_MODES,INVOCATION_STATUSES,INVOCATION_TERMINAL_STATUSES,INVOCATION_TREE_LIVENESS,TRACKED_LEADER_STATES,SCHEDULE_STATUSES,SCHEDULE_STORE_ERROR_CODES,ScheduleStoreError}from"./scheduler/types.js";export{DefaultToolPolicy}from"./policy/default-policy.js";export{ConfigurableToolPolicy}from"./policy/configurable-policy.js";export{UNATTENDED_CONFIRMATION_DENIED_REASON,UnattendedToolPolicy}from"./policy/unattended-policy.js";export{CAPABILITY_HOST_MODES,CAPABILITY_TURN_ORIGIN_KINDS}from"./engine/capability-manifest.js";export{ApprovalGate}from"./approval/approval-gate.js";export{RuntimeServer}from"./server/runtime-server.js";export{RuntimeClientRequestCancelledError,RuntimeClientRequestCoordinator,RuntimeClientRequestError,RuntimeClientRequestExpiredError,createRuntimeClientResponderId}from"./server/runtime-client-request-coordinator.js";export{RuntimeService,RuntimeServiceError}from"./service/runtime-service.js";export{AttachmentStore}from"./service/attachment-store.js";export{createStdioConnection}from"./server/transport/stdio.js";export{RpcMethod,EVENT_NOTIFICATION,isRequest}from"./server/protocol.js";export{createTurnCancellationMessage,readTurnCancellationReason,SESSION_CANCELLATION_REASONS,USER_CANCELLATION_ABORT_REASON}from"./types/cancellation.js";export{APPROVAL_DIFF_PREVIEW_KEY,fileChangeDiffSchema,fileChangeDisplaySchema,getApprovalDiffPreview,getFileChangeDisplay}from"@roll-agent/protocol";export{readScheduleHistory,readScheduleRun}from"./scheduler/schedule-store.js";
|
|
1
|
+
export{ConversationEngine,buildSessionBashSettings,buildSessionExecSettings}from"./engine/conversation-engine.js";export{runtimeSettingsSchema}from"./host-contracts/runtime-settings.js";export{buildScheduleToolset,SCHEDULE_CREATE_TOOL_ID}from"./tool-bridge/schedule-tool.js";export{scheduleCreateInputSchema}from"./tool-bridge/schedule-create-input.js";export{AgentSession}from"./engine/agent-session.js";export{WORKSPACE_INSTRUCTION_FILE_NAMES,WORKSPACE_INSTRUCTIONS_MAX_CHARS,WORKSPACE_INSTRUCTIONS_MODES,createWorkspaceInstructionsSource,findWorkspaceInstructionsPath,parseWorkspaceInstructionsSetting}from"./engine/workspace-instructions.js";export{resolveContextWindow,lookupContextWindow,resolveModelContextWindow,CONTEXT_WINDOW_SOURCES}from"./engine/context-window.js";export{createDefaultModelCatalog}from"./engine/model-catalog-default.js";export{ModelCatalog,defaultModelCatalogCachePath,lookupCatalogContextWindow,trimModelCatalog,MODEL_CATALOG_REFRESH_RESULTS}from"./engine/model-catalog.js";export{compactMessages,findTurnBoundaries,SUMMARY_PREFIX,SUMMARY_ACK}from"./engine/compactor.js";export{ROLL_RESOURCE_HINTS_META_KEY,buildAgentToolset}from"./tool-bridge/build-tools.js";export{COMMAND_CLASSIFICATIONS,unknownCommandClassifier}from"./types/command-classification.js";export{ruleBasedClassifier}from"./bash/classifier/index.js";export{buildPowerShellEncodedCommand,resolveShellProfile,SHELL_PROFILE_IDS,SHELL_TOOL_NAMES}from"./bash/profile.js";export{buildSessionExecToolset,EXEC_COMMAND_ID,EXEC_LIST_ID,EXEC_POLL_ID}from"./tool-bridge/session-exec-tool.js";export{ToolRegistry}from"./tool-bridge/naming.js";export{TOOL_RESOURCE_ACCESS_MODES,TOOL_RESOURCE_HINT_KINDS}from"./tool-bridge/tool-execution-coordinator.js";export{TOOL_CANCELLATION_EXECUTION_STATES,TOOL_OUTCOME_KINDS,normalizeToolResult,readToolOutcome}from"./tool-bridge/normalize-result.js";export{ThreadStore,defaultThreadsDir,expandTilde}from"./store/thread-store.js";export{stripCancelledTurnRecoveryMessages}from"./engine/cancelled-turn-recovery.js";export{THREAD_ORIGIN_KINDS,threadOriginSchema,scheduledThreadOriginSchema,threadDerivedFromSchema}from"./store/thread-origin.js";export{SCHEDULE_LEDGER_READ_STATUSES,ScheduleStore,readScheduleLedger,readScheduleOccupancy}from"./scheduler/schedule-store.js";export{SCHEDULER_LIMITS}from"./scheduler/limits.js";export{SCHEDULER_SCHEMA_VERSION}from"./scheduler/database-version.js";export{TRIGGER_KINDS,ScheduleTriggerError,computeNextRunAtMs,computeFirstRunAtMs,createScheduleTrigger,scheduleTimingInputSchema,createIntervalTrigger,describeTrigger,formatDuration,formatInterval,parseIntervalText,parseMaxRunText,parseTriggerJson,triggerSpecSchema}from"./scheduler/trigger.js";export{calendarScheduleSchema,calendarScheduleInputSchema,systemTimeZone}from"./scheduler/calendar.js";export{CANCEL_INVOCATION_OUTCOMES,COMPLETE_INVOCATION_OUTCOMES,EXECUTOR_LIVENESS,INVOCATION_FAILURE_OUTCOMES,INVOCATION_LIVE_STATUSES,INVOCATION_MODES,INVOCATION_STATUSES,INVOCATION_TERMINAL_STATUSES,INVOCATION_TREE_LIVENESS,TRACKED_LEADER_STATES,SCHEDULE_STATUSES,SCHEDULE_STORE_ERROR_CODES,ScheduleStoreError}from"./scheduler/types.js";export{DefaultToolPolicy}from"./policy/default-policy.js";export{ConfigurableToolPolicy}from"./policy/configurable-policy.js";export{UNATTENDED_CONFIRMATION_DENIED_REASON,UnattendedToolPolicy}from"./policy/unattended-policy.js";export{CAPABILITY_HOST_MODES,CAPABILITY_TURN_ORIGIN_KINDS}from"./engine/capability-manifest.js";export{ApprovalGate}from"./approval/approval-gate.js";export{RuntimeServer}from"./server/runtime-server.js";export{RuntimeClientRequestCancelledError,RuntimeClientRequestCoordinator,RuntimeClientRequestError,RuntimeClientRequestExpiredError,createRuntimeClientResponderId}from"./server/runtime-client-request-coordinator.js";export{RuntimeService,RuntimeServiceError}from"./service/runtime-service.js";export{AttachmentStore}from"./service/attachment-store.js";export{createStdioConnection}from"./server/transport/stdio.js";export{RpcMethod,EVENT_NOTIFICATION,isRequest}from"./server/protocol.js";export{createTurnCancellationMessage,readTurnCancellationReason,SESSION_CANCELLATION_REASONS,USER_CANCELLATION_ABORT_REASON}from"./types/cancellation.js";export{APPROVAL_DIFF_PREVIEW_KEY,fileChangeDiffSchema,fileChangeDisplaySchema,getApprovalDiffPreview,getFileChangeDisplay}from"@roll-agent/protocol";export{readScheduleHistory,readScheduleRun}from"./scheduler/schedule-store.js";export{SESSION_OCCUPANCY_CAPABILITY,SessionOccupiedError,SessionClaims,canonicalSessionDatabasePath}from"./ownership/session-claims.js";export{inspectThreadLibrary,migrateThreadLibrary,ThreadLibraryMigrationRequiredError,THREAD_STORE_SCHEMA_VERSION}from"./store/thread-store.js";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { SessionOwnershipHost } from "../host-contracts/session-ownership.ts";
|
|
3
|
+
export declare const SESSION_OCCUPANCY_CAPABILITY: {
|
|
4
|
+
readonly version: 1;
|
|
5
|
+
readonly scope: "canonical-library-and-thread";
|
|
6
|
+
readonly claims: "sqlite-sidecar-cas";
|
|
7
|
+
readonly idleOwnership: true;
|
|
8
|
+
readonly forceTakeover: false;
|
|
9
|
+
readonly legacyWriters: "unverified-sequential-only";
|
|
10
|
+
};
|
|
11
|
+
declare const claimSchema: z.ZodObject<{
|
|
12
|
+
thread_id: z.ZodString;
|
|
13
|
+
claim_id: z.ZodString;
|
|
14
|
+
identity: z.ZodString;
|
|
15
|
+
owner_json: z.ZodString;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
type Claim = z.infer<typeof claimSchema>;
|
|
18
|
+
export declare class SessionOccupiedError extends Error {
|
|
19
|
+
readonly details: {
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
readonly threadId: string;
|
|
22
|
+
readonly ownerKind?: string;
|
|
23
|
+
readonly label?: string;
|
|
24
|
+
readonly workspace?: string;
|
|
25
|
+
readonly action: string;
|
|
26
|
+
};
|
|
27
|
+
constructor(threadId: string, claim?: Claim, reason?: string);
|
|
28
|
+
}
|
|
29
|
+
/** Symlinks share the canonical SQLite journal. Hardlinks do not; reject before opening writable. */
|
|
30
|
+
export declare function canonicalSessionDatabasePath(path: string): string;
|
|
31
|
+
export interface SessionClaim {
|
|
32
|
+
readonly threadId: string;
|
|
33
|
+
readonly token: string;
|
|
34
|
+
assert(): void;
|
|
35
|
+
release(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Cooperative local-process fencing. The sidecar is never unlinked/replaced. A dead process
|
|
39
|
+
* cannot write; a live/unknown process is never reaped. This is not a distributed FS lock and
|
|
40
|
+
* does not constrain old binaries or external effects surviving their parent process.
|
|
41
|
+
*/
|
|
42
|
+
export declare class SessionClaims {
|
|
43
|
+
private readonly db;
|
|
44
|
+
private readonly held;
|
|
45
|
+
private closed;
|
|
46
|
+
readonly databasePath: string;
|
|
47
|
+
constructor(path: string, privateHost: SessionOwnershipHost);
|
|
48
|
+
private readonly host;
|
|
49
|
+
private read;
|
|
50
|
+
private checkOpen;
|
|
51
|
+
private reap;
|
|
52
|
+
acquire(threadId: string): SessionClaim;
|
|
53
|
+
private acquireKey;
|
|
54
|
+
/** No writer can enter while maintenance owns this gate; any held writer blocks it. */
|
|
55
|
+
maintenance(): SessionClaim;
|
|
56
|
+
private assert;
|
|
57
|
+
private handle;
|
|
58
|
+
/** Refuse to convert resource disposal into proof that unsettled sessions are safe. */
|
|
59
|
+
close(): void;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{chmodSync as i,existsSync as r,lstatSync as t,mkdirSync as s,realpathSync as a,statSync as o}from"node:fs";import{dirname as n,resolve as d}from"node:path";import{DatabaseSync as c}from"node:sqlite";import{z as h}from"zod";export const SESSION_OCCUPANCY_CAPABILITY={version:1,scope:"canonical-library-and-thread",claims:"sqlite-sidecar-cas",idleOwnership:!0,forceTakeover:!1,legacyWriters:"unverified-sequential-only"};const l=h.object({thread_id:h.string(),claim_id:h.string(),identity:h.string(),owner_json:h.string()}),p=h.object({kind:h.string(),label:h.string(),workspace:h.string().optional()});function m(e){if(void 0!==e)try{return p.parse(JSON.parse(e.owner_json))}catch{return}}const u="";export class SessionOccupiedError extends Error{details;constructor(e,i,r="session_occupied"){super("此会话正在另一个客户端中使用。请先退出该客户端的当前会话,再重新检测;两端请选择其一。"),this.name="SessionOccupiedError";const t=m(i);this.details={reason:r,threadId:e,action:"close_other_client_and_retry",...void 0!==t?{ownerKind:t.kind,label:t.label,...void 0===t.workspace?{}:{workspace:t.workspace}}:{}}}}export function canonicalSessionDatabasePath(e){if(!r(e)){if(t(e,{throwIfNoEntry:!1})?.isSymbolicLink())throw new SessionOccupiedError("",void 0,"ambiguous_library_alias");return d(a(n(e)),"threads.db")}const i=a(e);if(o(i).nlink>1)throw new SessionOccupiedError("",void 0,"ambiguous_library_alias");return i}export class SessionClaims{db;held=new Map;closed=!1;databasePath;constructor(e,r){if(this.host=r,!r.identity)throw new Error("Session ownership requires a verifiable host identity");s(n(e),{recursive:!0,mode:448}),this.databasePath=canonicalSessionDatabasePath(e);const t=`${this.databasePath}.occupancy.sqlite`;this.db=new c(t);try{"win32"!==process.platform&&i(t,384),this.db.exec("PRAGMA busy_timeout = 15000;\n CREATE TABLE IF NOT EXISTS claims (\n thread_id TEXT PRIMARY KEY, claim_id TEXT NOT NULL,\n identity TEXT NOT NULL, owner_json TEXT NOT NULL\n );")}catch(e){throw this.db.close(),e}}host;read(e){const i=this.db.prepare("SELECT * FROM claims WHERE thread_id = ?").get(e);return void 0===i?void 0:l.parse(i)}checkOpen(){if(this.closed)throw new Error("Session claims are closed");if(r(this.databasePath)&&canonicalSessionDatabasePath(this.databasePath)!==this.databasePath)throw new SessionOccupiedError("",void 0,"ambiguous_library_alias")}reap(e){let i="unknown";try{i=this.host.probe(e.identity)}catch{}return"dead"===i&&(this.db.prepare("DELETE FROM claims WHERE thread_id = ? AND claim_id = ?").run(e.thread_id,e.claim_id),!0)}acquire(e){if(!e)throw new Error("Thread ID must be nonempty");return this.acquireKey(e)}acquireKey(i){this.checkOpen();const r=this.held.get(i);if(void 0!==r)return this.assert(r.claim),r.references++,this.handle(r.claim);const t={thread_id:i,claim_id:e(),identity:this.host.identity,owner_json:JSON.stringify(this.host.owner)};for(let e=0;e<32;e++){let e;this.db.exec("BEGIN IMMEDIATE");try{e=this.read(u)??(i===u?this.db.prepare("SELECT * FROM claims LIMIT 1").all().map(e=>l.parse(e))[0]:this.read(i)),void 0===e&&this.db.prepare("INSERT INTO claims VALUES (?, ?, ?, ?)").run(i,t.claim_id,t.identity,t.owner_json),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}if(void 0===e)return this.held.set(i,{claim:t,references:1}),this.handle(t);if(!this.reap(e))throw new SessionOccupiedError(i,e)}throw new SessionOccupiedError(i)}maintenance(){return this.acquireKey(u)}assert(e){if(this.checkOpen(),this.read(e.thread_id)?.claim_id!==e.claim_id)throw new SessionOccupiedError(e.thread_id,void 0,"stale_session_claim")}handle(e){let i=!1;return{threadId:e.thread_id,token:e.claim_id,assert:()=>{if(i)throw new SessionOccupiedError(e.thread_id,void 0,"stale_session_claim");this.assert(e)},release:()=>{if(i)return;i=!0;const r=this.held.get(e.thread_id);r?.claim.claim_id===e.claim_id&&(r.references--,0===r.references&&(this.db.prepare("DELETE FROM claims WHERE thread_id = ? AND claim_id = ?").run(e.thread_id,e.claim_id),this.held.delete(e.thread_id)))}}}close(){if(!this.closed){if(0!==this.held.size)throw new Error("Cannot close ownership store with unsettled session claims");this.db.close(),this.closed=!0}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{RUNTIME_ERROR_CODES as t,RUNTIME_EVENT_NOTIFICATION as i,RUNTIME_METHODS as s,RUNTIME_PROTOCOL_VERSION as r,RUNTIME_SERVER_REQUEST_METHODS as a,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as n,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as o,RUNTIME_V13_RECOVERY_SNAPSHOT_METADATA_MAX_CHARS as c,clientCapabilitiesSetParamsSchema as h,getRuntimeProtocolCapabilities as l,getRuntimeProtocolRegistry as p,interactionIdSchema as d,isRuntimeMethodAvailable as u,normalizeUserInputResult as v,parseRuntimeMethodParamsForVersion as m,projectClientCapabilitiesSetResult as b,projectRuntimeEventEnvelopeForVersion as R,projectThreadSnapshotForVersion as f,projectOperationViewForVersion as y,runtimeMethodSchemas as I,runtimeDurableEventV16Schema as q,turnIdSchema as g}from"@roll-agent/protocol";import{RuntimeService as w,RuntimeServiceError as S}from"../service/runtime-service.js";import{RuntimeClientRequestCancelledError as V,RuntimeClientRequestCoordinator as C,RuntimeClientRequestExpiredError as P,createRuntimeClientResponderId as A,getRuntimeClientRequestCoordinatorInternal as U}from"./runtime-client-request-coordinator.js";const E="客户端未完成审批请求,Runtime 已终止当前 Turn",T="客户端未完成用户输入请求",z=new WeakSet,G=o,k=n;function B(e){return`user-input:${e}`}function F(e){switch(e.status){case"resolved":return"approve"===e.decision?"审批已批准":"审批已拒绝";case"cancelled":return e.reason;case"expired":return e.reason??"审批已到期"}}export class RuntimeProtocolAdapter{service;connection;clientRequests;responderId;unsubscribe;unsubscribeFatalError;unsubscribeUserInput;settlementTasks=new Set;detachResponder;initialized=!1;closing=!1;transportFailed=!1;controlsService=!1;protocolVersion;capabilityRevision;capabilityMethods;capabilityResult;replayGates=new Map;eventSequence=0;closePromise;constructor(e,i,s){if(z.has(e))throw new S(t.capabilityUnavailable,"当前 RuntimeService 已绑定 Runtime Protocol Adapter");z.add(e),this.controlsService=!0,this.service=e,this.connection=i,this.clientRequests=s,this.responderId=A();try{this.unsubscribe=e.onEvent(e=>this.handleServiceEvent(e))}catch(e){throw this.releaseServiceControl(),e}try{this.unsubscribeUserInput=e.onUserInputInteraction(e=>this.handleUserInputInteraction(e))}catch(e){throw this.unsubscribe(),this.releaseServiceControl(),e}try{this.unsubscribeFatalError=e.onFatalError(()=>this.closeBrokenTransport())}catch(e){throw this.unsubscribeUserInput(),this.unsubscribe(),this.releaseServiceControl(),e}}dispatchEventResume(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if("1.3"!==this.protocolVersion&&"1.4"!==this.protocolVersion&&"1.5"!==this.protocolVersion&&"1.6"!==this.protocolVersion)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);const i=I[s.runtimeEventsResume].params.parse(e.params);if(this.replayGates.has(i.threadId))throw new S(t.threadBusy,`Thread "${i.threadId}" 已有进行中的 Runtime Event replay`,{retryable:!0});const r={threadId:i.threadId,buffered:[],bufferedBytes:0};this.replayGates.set(i.threadId,r);try{const e=this.service.resumeEvents(i);for(const t of e.events){const e={protocolVersion:"1.6",runtimeInstanceId:this.service.runtimeInstanceId,sequence:this.eventSequence,timestamp:t.timestamp,threadId:i.threadId,...void 0===t.turnId?{}:{turnId:g.parse(t.turnId)},durability:"durable",eventId:t.eventId,cursor:t.cursor,event:q.parse(t.event)};if(!this.sendEvent(e))throw new Error("Runtime Event replay transport write failed")}return{result:{throughCursor:e.throughCursor,replayedCount:e.replayedCount},afterResponse:e=>{e?this.flushReplayGate(r):this.releaseReplayGate(r)}}}catch(e){throw this.releaseReplayGate(r),e}}handles(e){return u(r,e)}handleResponse(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequests||void 0===this.detachResponder)return!1;return U(this.clientRequests).handleResponseForAttachment(this.detachResponder,e)??this.clientRequests.handleResponse(this.responderId,e)}async dispatch(e){return e.method===s.runtimeEventsResume?this.dispatchEventResume(e):e.method===s.clientCapabilitiesSet?this.dispatchClientCapabilitiesSet(e):{result:await this.dispatchValue(e)}}dispatchClientCapabilitiesSet(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持 capability negotiation:${e.method}`);return this.setClientCapabilities(h.parse(e.params))}async dispatchValue(e){if(!this.handles(e.method))throw new S(t.capabilityUnavailable,`未知 Runtime Protocol 方法:${e.method}`);if(e.method===s.initialize){if(this.initialized)throw new S(t.capabilityUnavailable,"当前连接已完成 initialize,不能重新协商 Runtime Protocol");const i=I[s.initialize].params.parse(e.params),r=this.service.initialize(i);if(this.protocolVersion=r.protocolVersion,l(r.protocolVersion).serverRequests){const e=l(r.protocolVersion).serverRequestCapabilityNegotiation;if(this.detachResponder=this.clientRequests.attachResponder({id:this.responderId,scopeId:r.runtimeInstanceId,send:e=>this.connection.send(e),close:()=>this.connection.close()},{acceptedServerRequestMethods:e?[]:p(r.protocolVersion).serverRequestMethods,capabilitiesAcknowledged:!e}),e){if(!(U(this.clientRequests).beginCapabilityNegotiationForAttachment(this.detachResponder)??this.clientRequests.beginResponderCapabilityNegotiation(this.responderId)))throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 无法进入 capability negotiation")}}return this.initialized=!0,r}if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!u(this.protocolVersion,e.method))throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);if(e.method===s.approvalRespond&&void 0!==this.protocolVersion&&!l(this.protocolVersion).clientApprovalResponses)throw new S(t.capabilityUnavailable,"当前协商协议的审批只能通过 approval.request 响应完成");switch(e.method){case s.threadList:return this.service.listThreads(I[s.threadList].params.parse(e.params));case s.threadCreate:return this.service.createThread(I[s.threadCreate].params.parse(e.params));case s.threadOpen:return this.projectThreadSnapshot(await this.service.openThread(I[s.threadOpen].params.parse(e.params)));case s.threadSnapshot:{const t=m(this.protocolVersion,s.threadSnapshot,e.params),i=("1.3"===this.protocolVersion||"1.4"===this.protocolVersion||"1.5"===this.protocolVersion||"1.6"===this.protocolVersion)&&"recovery"in t&&!0===t.recovery;return this.projectThreadSnapshot(this.service.snapshotThread({threadId:t.threadId,...void 0===t.messageBeforeSequence?{}:{messageBeforeSequence:t.messageBeforeSequence},...void 0===t.operationBeforeSequence?{}:{operationBeforeSequence:t.operationBeforeSequence},limit:t.limit}),i)}case s.threadRename:return this.service.renameThread(I[s.threadRename].params.parse(e.params));case s.threadDelete:return this.service.deleteThread(I[s.threadDelete].params.parse(e.params));case s.threadDetach:return this.service.detachThread(I[s.threadDetach].params.parse(e.params));case s.threadCapabilities:return this.service.threadCapabilities(I[s.threadCapabilities].params.parse(e.params));case s.turnStart:return this.service.startTurn(m(this.protocolVersion,s.turnStart,e.params));case s.turnCancel:return this.service.cancelTurn(I[s.turnCancel].params.parse(e.params));case s.approvalRespond:return this.service.respondApproval(m(this.protocolVersion,s.approvalRespond,e.params));case s.approvalPreview:return this.service.getApprovalPreview(I[s.approvalPreview].params.parse(e.params));case s.operationGet:{const t=this.service.getOperation(I[s.operationGet].params.parse(e.params));return{operation:null===t.operation?null:y(this.protocolVersion,t.operation)}}case s.operationResultGet:return this.service.getOperationResult(I[s.operationResultGet].params.parse(e.params));case s.attachmentStage:return this.service.stageAttachment(I[s.attachmentStage].params.parse(e.params));case s.attachmentChunk:return this.service.appendAttachmentChunk(I[s.attachmentChunk].params.parse(e.params));case s.attachmentCommit:return this.service.commitAttachment(I[s.attachmentCommit].params.parse(e.params));case s.attachmentRelease:return this.service.releaseAttachment(I[s.attachmentRelease].params.parse(e.params));case s.runtimeEventsResume:throw new Error("runtime.events.resume requires the response-barrier dispatch path");case s.clientCapabilitiesSet:throw new Error("client.capabilities.set requires the response-barrier dispatch path")}}async close(){this.closePromise??=this.performClose(),await this.closePromise}disposeConstructionFailure(){this.closing=!0,this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput(),this.releaseServiceControl()}releaseServiceControl(){this.controlsService&&(z.delete(this.service),this.controlsService=!1)}async performClose(){for(this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0;this.settlementTasks.size>0;)await Promise.allSettled([...this.settlementTasks]);this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput()}handleServiceEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return;const t=this.replayGates.get(e.threadId);void 0===t?this.deliverLiveEvent(e):this.bufferReplayLiveEvent(t,e)||this.closeBrokenTransport()}deliverLiveEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return!1;const t=l(this.protocolVersion);return t.serverRequests&&"approval.resolved"===e.event.type&&this.clientRequests.cancel(e.event.approvalId,F(e.event.resolution)),t.serverRequests&&"approval.required"===e.event.type&&this.requestApproval(e.threadId,e.event.approval),this.sendEvent(e)}sendEvent(e){if(void 0===this.protocolVersion)return!1;if(!l(this.protocolVersion).approvalResolvedEvents&&"approval.resolved"===e.event.type)return!0;const t={...e,sequence:Math.max(this.eventSequence,e.sequence)};this.eventSequence=t.sequence+1;const s=R(this.protocolVersion,t);try{return this.connection.send({jsonrpc:"2.0",method:i,params:s}),!0}catch{return this.closeBrokenTransport(),!1}}bufferReplayLiveEvent(e,t){const i=Buffer.byteLength(JSON.stringify(t),"utf8");return e.buffered.length>=G||e.bufferedBytes+i>k?(this.releaseReplayGate(e),!1):(e.buffered.push(t),e.bufferedBytes+=i,!0)}flushReplayGate(e){if(this.replayGates.get(e.threadId)!==e)return;let t=0;for(;t<e.buffered.length;){const i=e.buffered[t];if(t+=1,void 0!==i&&!this.deliverLiveEvent(i))return void this.releaseReplayGate(e)}this.releaseReplayGate(e)}releaseReplayGate(e){this.replayGates.get(e.threadId)===e&&this.replayGates.delete(e.threadId),e.buffered.length=0,e.bufferedBytes=0}closeBrokenTransport(){if(!this.closing){this.transportFailed=!0,this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0,this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput();try{this.connection.close()}catch{}}}projectThreadSnapshot(e,i=!1){const s=this.protocolVersion;if(void 0===s)throw new S(t.initializeRequired,"投影 Thread Snapshot 前必须先完成 initialize");let r=[];if(!i&&l(s).serverRequestCapabilityNegotiation){if(void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder 已失效");const i=U(this.clientRequests).getPendingInteractionProjectionsForAttachment(this.detachResponder,e.thread.id)??this.clientRequests.getPendingInteractionProjections(this.responderId,e.thread.id);if(void 0===i)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder attachment 已失效");r=i}if(i){const t=e=>e?.slice(0,c),i=t(e.thread.title),r=t(e.thread.model);return f(s,{...e,thread:{...e.thread,...void 0===i?{}:{title:i},...void 0===r?{}:{model:r}},messages:{items:[],nextBeforeSequence:null},operations:{items:[],nextBeforeSequence:null},pendingApprovals:[],pendingInteractions:[],recoveryProjection:!0})}return f(s,{...e,pendingInteractions:r})}handleUserInputInteraction(e){"settled"!==e.type?!this.closing&&this.initialized&&void 0!==this.protocolVersion&&l(this.protocolVersion).serverRequestCapabilityNegotiation?this.requestUserInput(e):this.service.cancelPendingUserInput(e.requestId,T):this.clientRequests.cancel(B(e.requestId),e.reason)}requestUserInput(t){const i=this.protocolVersion;if(void 0===i||!l(i).serverRequestCapabilityNegotiation)return void this.service.cancelPendingUserInput(t.requestId,T);const s={interactionId:d.parse(e()),threadId:t.threadId,turnId:t.turnId,expiresAt:t.expiresAt,sensitivity:"normal",...t.form};let r;try{r=this.clientRequests.request(a.userInputRequest,s,{key:B(t.requestId),scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,protocolVersion:i,expiresAt:t.expiresAt})}catch{return void this.service.cancelPendingUserInput(t.requestId,T)}const n=r.result.then(e=>{try{const i=v(s,e);this.service.resolvePendingUserInput(t.requestId,i)}catch{this.service.cancelPendingUserInput(t.requestId,"客户端返回的用户输入不符合原始表单约束")}},e=>{const i=e instanceof P?"用户输入请求已超时":e instanceof V?e.reason:T;this.service.cancelPendingUserInput(t.requestId,i)});this.trackSettlement(n)}requestApproval(i,s){const r={threadId:i,turnId:s.turnId,approvalId:s.id};let n;try{const t=this.protocolVersion;if(void 0===t||"1.0"===t)throw new V("当前协议不支持 Server Request");const o=this.service.getPendingApprovalExpiresAt(r),c=l(t).serverRequestCapabilityNegotiation,h=c?(()=>{if(void 0===o)throw new V("Runtime Protocol 1.2 审批请求缺少绝对 expiresAt deadline");return o})():void 0,p=c?{interactionId:d.parse(e()),threadId:i,turnId:s.turnId,expiresAt:h,sensitivity:"normal",approval:s}:{threadId:i,approval:s};n=this.clientRequests.request(a.approvalRequest,p,{key:s.id,scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,approvalId:s.id,protocolVersion:t,...void 0===h?{}:{expiresAt:h}})}catch{const e=Promise.resolve().then(()=>this.service.failPendingApprovalInteraction(r,E)).then(()=>{});return void this.trackSettlement(e)}const o=n.result.then(async e=>{const i="approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}};try{this.service.resolvePendingApproval(r,i)}catch(e){e instanceof S&&e.rollCode===t.approvalNotFound||await this.service.failPendingApprovalInteraction(r,E)}},async e=>{if(this.closing&&!this.transportFailed)return;if(e instanceof P)return void await this.service.failPendingApprovalInteraction(r,"审批请求已到期",{status:"expired",reason:"审批请求已到期"});const t=e instanceof V?e.reason:E;await this.service.failPendingApprovalInteraction(r,t)});this.trackSettlement(o)}setClientCapabilities(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation||void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"client.capabilities.set 仅可用于支持 capability negotiation 的 Runtime Protocol");const i=[...e.serverRequestMethods].sort();if(void 0!==this.capabilityRevision){const s=e.revision===this.capabilityRevision,r=i.length===this.capabilityMethods?.length&&i.every((e,t)=>e===this.capabilityMethods?.[t]);if(s&&r&&void 0!==this.capabilityResult)return{result:this.capabilityResult};if(e.revision<=this.capabilityRevision)throw new S(t.capabilityRevisionConflict,`Capability revision ${String(e.revision)} 与当前 revision ${String(this.capabilityRevision)} 冲突`)}const s=b(e),r=`Runtime 客户端已在 capability revision ${String(e.revision)} 撤销处理能力`,n=U(this.clientRequests),o=n.setServerRequestMethodsForAttachment(this.detachResponder,s.acceptedServerRequestMethods,r)??n.setServerRequestMethodsForResponder(this.responderId,s.acceptedServerRequestMethods,r);if(!1===o)throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 已失效");return this.service.setUserInputAvailable(s.acceptedServerRequestMethods.includes(a.userInputRequest)),this.capabilityRevision=e.revision,this.capabilityMethods=i,this.capabilityResult=s,{result:s,afterResponse:e=>{e&&o()}}}trackSettlement(e){this.settlementTasks.add(e),e.finally(()=>this.settlementTasks.delete(e)).catch(()=>{})}}
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{RUNTIME_ERROR_CODES as t,RUNTIME_EVENT_NOTIFICATION as i,RUNTIME_METHODS as s,RUNTIME_PROTOCOL_VERSION as r,RUNTIME_SERVER_REQUEST_METHODS as n,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as a,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as o,RUNTIME_V13_RECOVERY_SNAPSHOT_METADATA_MAX_CHARS as c,clientCapabilitiesSetParamsSchema as h,getRuntimeProtocolCapabilities as l,getRuntimeProtocolRegistry as p,interactionIdSchema as d,isRuntimeMethodAvailable as u,normalizeUserInputResult as v,parseRuntimeMethodParamsForVersion as m,projectClientCapabilitiesSetResult as b,projectRuntimeEventEnvelopeForVersion as R,projectThreadSnapshotForVersion as f,projectOperationViewForVersion as y,runtimeMethodSchemas as I,runtimeDurableEventV16Schema as g,turnIdSchema as q}from"@roll-agent/protocol";import{RuntimeService as w,RuntimeServiceError as S}from"../service/runtime-service.js";import{RuntimeClientRequestCancelledError as V,RuntimeClientRequestCoordinator as C,RuntimeClientRequestExpiredError as P,createRuntimeClientResponderId as A,getRuntimeClientRequestCoordinatorInternal as T}from"./runtime-client-request-coordinator.js";const U="客户端未完成审批请求,Runtime 已终止当前 Turn",E="客户端未完成用户输入请求",x=new WeakSet,z=o,G=a;function k(e){return`user-input:${e}`}function B(e){switch(e.status){case"resolved":return"approve"===e.decision?"审批已批准":"审批已拒绝";case"cancelled":return e.reason;case"expired":return e.reason??"审批已到期"}}export class RuntimeProtocolAdapter{service;connection;clientRequests;responderId;unsubscribe;unsubscribeFatalError;unsubscribeUserInput;settlementTasks=new Set;detachResponder;initialized=!1;closing=!1;transportFailed=!1;controlsService=!1;protocolVersion;unsubscribeReasoningText;capabilityRevision;capabilityMethods;capabilityResult;replayGates=new Map;eventSequence=0;closePromise;constructor(e,i,s){if(x.has(e))throw new S(t.capabilityUnavailable,"当前 RuntimeService 已绑定 Runtime Protocol Adapter");x.add(e),this.controlsService=!0,this.service=e,this.connection=i,this.clientRequests=s,this.responderId=A();try{this.unsubscribe=e.onEvent(e=>this.handleServiceEvent(e))}catch(e){throw this.releaseServiceControl(),e}try{this.unsubscribeUserInput=e.onUserInputInteraction(e=>this.handleUserInputInteraction(e))}catch(e){throw this.unsubscribe(),this.releaseServiceControl(),e}try{this.unsubscribeFatalError=e.onFatalError(()=>this.closeBrokenTransport())}catch(e){throw this.unsubscribeUserInput(),this.unsubscribe(),this.releaseServiceControl(),e}}dispatchEventResume(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if("1.3"!==this.protocolVersion&&"1.4"!==this.protocolVersion&&"1.5"!==this.protocolVersion&&"1.6"!==this.protocolVersion)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);const i=I[s.runtimeEventsResume].params.parse(e.params);if(this.replayGates.has(i.threadId))throw new S(t.threadBusy,`Thread "${i.threadId}" 已有进行中的 Runtime Event replay`,{retryable:!0});const r={threadId:i.threadId,buffered:[],bufferedBytes:0};this.replayGates.set(i.threadId,r);try{const e=this.service.resumeEvents(i);for(const t of e.events){const e={protocolVersion:"1.6",runtimeInstanceId:this.service.runtimeInstanceId,sequence:this.eventSequence,timestamp:t.timestamp,threadId:i.threadId,...void 0===t.turnId?{}:{turnId:q.parse(t.turnId)},durability:"durable",eventId:t.eventId,cursor:t.cursor,event:g.parse(t.event)};if(!this.sendEvent(e))throw new Error("Runtime Event replay transport write failed")}return{result:{throughCursor:e.throughCursor,replayedCount:e.replayedCount},afterResponse:e=>{e?this.flushReplayGate(r):this.releaseReplayGate(r)}}}catch(e){throw this.releaseReplayGate(r),e}}handles(e){return u(r,e)}handleResponse(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequests||void 0===this.detachResponder)return!1;return T(this.clientRequests).handleResponseForAttachment(this.detachResponder,e)??this.clientRequests.handleResponse(this.responderId,e)}async dispatch(e){return e.method===s.runtimeEventsResume?this.dispatchEventResume(e):e.method===s.clientCapabilitiesSet?this.dispatchClientCapabilitiesSet(e):{result:await this.dispatchValue(e)}}dispatchClientCapabilitiesSet(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持 capability negotiation:${e.method}`);return this.setClientCapabilities(h.parse(e.params))}async dispatchValue(e){if(!this.handles(e.method))throw new S(t.capabilityUnavailable,`未知 Runtime Protocol 方法:${e.method}`);if(e.method===s.initialize){if(this.initialized)throw new S(t.capabilityUnavailable,"当前连接已完成 initialize,不能重新协商 Runtime Protocol");const i=I[s.initialize].params.parse(e.params),r=this.service.initialize(i);if(this.protocolVersion=r.protocolVersion,l(r.protocolVersion).serverRequests){const e=l(r.protocolVersion).serverRequestCapabilityNegotiation;if(this.detachResponder=this.clientRequests.attachResponder({id:this.responderId,scopeId:r.runtimeInstanceId,send:e=>this.connection.send(e),close:()=>this.connection.close()},{acceptedServerRequestMethods:e?[]:p(r.protocolVersion).serverRequestMethods,capabilitiesAcknowledged:!e}),e){if(!(T(this.clientRequests).beginCapabilityNegotiationForAttachment(this.detachResponder)??this.clientRequests.beginResponderCapabilityNegotiation(this.responderId)))throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 无法进入 capability negotiation")}}return this.initialized=!0,r}if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!u(this.protocolVersion,e.method))throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);if(e.method===s.approvalRespond&&void 0!==this.protocolVersion&&!l(this.protocolVersion).clientApprovalResponses)throw new S(t.capabilityUnavailable,"当前协商协议的审批只能通过 approval.request 响应完成");switch(e.method){case s.reasoningTextSubscribe:return I[s.reasoningTextSubscribe].params.parse(e.params),this.unsubscribeReasoningText??=this.service.subscribeReasoningText(e=>this.handleServiceEvent(e)),{subscribed:void 0!==this.unsubscribeReasoningText};case s.threadList:return this.service.listThreads(I[s.threadList].params.parse(e.params));case s.threadCreate:return this.service.createThread(I[s.threadCreate].params.parse(e.params));case s.threadOpen:return this.projectThreadSnapshot(await this.service.openThread(I[s.threadOpen].params.parse(e.params)));case s.threadSnapshot:{const t=m(this.protocolVersion,s.threadSnapshot,e.params),i=("1.3"===this.protocolVersion||"1.4"===this.protocolVersion||"1.5"===this.protocolVersion||"1.6"===this.protocolVersion)&&"recovery"in t&&!0===t.recovery;return this.projectThreadSnapshot(this.service.snapshotThread({threadId:t.threadId,...void 0===t.messageBeforeSequence?{}:{messageBeforeSequence:t.messageBeforeSequence},...void 0===t.operationBeforeSequence?{}:{operationBeforeSequence:t.operationBeforeSequence},limit:t.limit}),i)}case s.threadRename:return this.service.renameThread(I[s.threadRename].params.parse(e.params));case s.threadDelete:return this.service.deleteThread(I[s.threadDelete].params.parse(e.params));case s.threadDetach:return this.service.detachThread(I[s.threadDetach].params.parse(e.params));case s.threadCapabilities:return this.service.threadCapabilities(I[s.threadCapabilities].params.parse(e.params));case s.turnStart:return this.service.startTurn(m(this.protocolVersion,s.turnStart,e.params));case s.turnCancel:return this.service.cancelTurn(I[s.turnCancel].params.parse(e.params));case s.approvalRespond:return this.service.respondApproval(m(this.protocolVersion,s.approvalRespond,e.params));case s.approvalPreview:return this.service.getApprovalPreview(I[s.approvalPreview].params.parse(e.params));case s.operationGet:{const t=this.service.getOperation(I[s.operationGet].params.parse(e.params));return{operation:null===t.operation?null:y(this.protocolVersion,t.operation)}}case s.operationResultGet:return this.service.getOperationResult(I[s.operationResultGet].params.parse(e.params));case s.attachmentStage:return this.service.stageAttachment(I[s.attachmentStage].params.parse(e.params));case s.attachmentChunk:return this.service.appendAttachmentChunk(I[s.attachmentChunk].params.parse(e.params));case s.attachmentCommit:return this.service.commitAttachment(I[s.attachmentCommit].params.parse(e.params));case s.attachmentRelease:return this.service.releaseAttachment(I[s.attachmentRelease].params.parse(e.params));case s.runtimeEventsResume:throw new Error("runtime.events.resume requires the response-barrier dispatch path");case s.clientCapabilitiesSet:throw new Error("client.capabilities.set requires the response-barrier dispatch path")}}async close(){this.closePromise??=this.performClose(),await this.closePromise}disposeConstructionFailure(){this.closing=!0,this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput(),this.releaseServiceControl()}releaseServiceControl(){this.controlsService&&(x.delete(this.service),this.controlsService=!1)}async performClose(){for(this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0;this.settlementTasks.size>0;)await Promise.allSettled([...this.settlementTasks]);this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput()}handleServiceEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return;const t=this.replayGates.get(e.threadId);void 0===t?this.deliverLiveEvent(e):this.bufferReplayLiveEvent(t,e)||this.closeBrokenTransport()}deliverLiveEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return!1;const t=l(this.protocolVersion);return t.serverRequests&&"approval.resolved"===e.event.type&&this.clientRequests.cancel(e.event.approvalId,B(e.event.resolution)),t.serverRequests&&"approval.required"===e.event.type&&this.requestApproval(e.threadId,e.event.approval),this.sendEvent(e)}sendEvent(e){if(void 0===this.protocolVersion)return!1;if(!l(this.protocolVersion).approvalResolvedEvents&&"approval.resolved"===e.event.type)return!0;const t={...e,sequence:Math.max(this.eventSequence,e.sequence)};this.eventSequence=t.sequence+1;const s=R(this.protocolVersion,t);try{return this.connection.send({jsonrpc:"2.0",method:i,params:s}),!0}catch{return this.closeBrokenTransport(),!1}}bufferReplayLiveEvent(e,t){const i=Buffer.byteLength(JSON.stringify(t),"utf8");return e.buffered.length>=z||e.bufferedBytes+i>G?(this.releaseReplayGate(e),!1):(e.buffered.push(t),e.bufferedBytes+=i,!0)}flushReplayGate(e){if(this.replayGates.get(e.threadId)!==e)return;let t=0;for(;t<e.buffered.length;){const i=e.buffered[t];if(t+=1,void 0!==i&&!this.deliverLiveEvent(i))return void this.releaseReplayGate(e)}this.releaseReplayGate(e)}releaseReplayGate(e){this.replayGates.get(e.threadId)===e&&this.replayGates.delete(e.threadId),e.buffered.length=0,e.bufferedBytes=0}closeBrokenTransport(){if(!this.closing){this.transportFailed=!0,this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0,this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput();try{this.connection.close()}catch{}}}projectThreadSnapshot(e,i=!1){const s=this.protocolVersion;if(void 0===s)throw new S(t.initializeRequired,"投影 Thread Snapshot 前必须先完成 initialize");let r=[];if(!i&&l(s).serverRequestCapabilityNegotiation){if(void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder 已失效");const i=T(this.clientRequests).getPendingInteractionProjectionsForAttachment(this.detachResponder,e.thread.id)??this.clientRequests.getPendingInteractionProjections(this.responderId,e.thread.id);if(void 0===i)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder attachment 已失效");r=i}if(i){const t=e=>e?.slice(0,c),i=t(e.thread.title),r=t(e.thread.model);return f(s,{...e,thread:{...e.thread,...void 0===i?{}:{title:i},...void 0===r?{}:{model:r}},messages:{items:[],nextBeforeSequence:null},operations:{items:[],nextBeforeSequence:null},pendingApprovals:[],pendingInteractions:[],recoveryProjection:!0})}return f(s,{...e,pendingInteractions:r})}handleUserInputInteraction(e){"settled"!==e.type?!this.closing&&this.initialized&&void 0!==this.protocolVersion&&l(this.protocolVersion).serverRequestCapabilityNegotiation?this.requestUserInput(e):this.service.cancelPendingUserInput(e.requestId,E):this.clientRequests.cancel(k(e.requestId),e.reason)}requestUserInput(t){const i=this.protocolVersion;if(void 0===i||!l(i).serverRequestCapabilityNegotiation)return void this.service.cancelPendingUserInput(t.requestId,E);const s={interactionId:d.parse(e()),threadId:t.threadId,turnId:t.turnId,expiresAt:t.expiresAt,sensitivity:"normal",...t.form};let r;try{r=this.clientRequests.request(n.userInputRequest,s,{key:k(t.requestId),scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,protocolVersion:i,expiresAt:t.expiresAt})}catch{return void this.service.cancelPendingUserInput(t.requestId,E)}const a=r.result.then(e=>{try{const i=v(s,e);this.service.resolvePendingUserInput(t.requestId,i)}catch{this.service.cancelPendingUserInput(t.requestId,"客户端返回的用户输入不符合原始表单约束")}},e=>{const i=e instanceof P?"用户输入请求已超时":e instanceof V?e.reason:E;this.service.cancelPendingUserInput(t.requestId,i)});this.trackSettlement(a)}requestApproval(i,s){const r={threadId:i,turnId:s.turnId,approvalId:s.id};let a;try{const t=this.protocolVersion;if(void 0===t||"1.0"===t)throw new V("当前协议不支持 Server Request");const o=this.service.getPendingApprovalExpiresAt(r),c=l(t).serverRequestCapabilityNegotiation,h=c?(()=>{if(void 0===o)throw new V("Runtime Protocol 1.2 审批请求缺少绝对 expiresAt deadline");return o})():void 0,p=c?{interactionId:d.parse(e()),threadId:i,turnId:s.turnId,expiresAt:h,sensitivity:"normal",approval:s}:{threadId:i,approval:s};a=this.clientRequests.request(n.approvalRequest,p,{key:s.id,scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,approvalId:s.id,protocolVersion:t,...void 0===h?{}:{expiresAt:h}})}catch{const e=Promise.resolve().then(()=>this.service.failPendingApprovalInteraction(r,U)).then(()=>{});return void this.trackSettlement(e)}const o=a.result.then(async e=>{const i="approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}};try{this.service.resolvePendingApproval(r,i)}catch(e){e instanceof S&&e.rollCode===t.approvalNotFound||await this.service.failPendingApprovalInteraction(r,U)}},async e=>{if(this.closing&&!this.transportFailed)return;if(e instanceof P)return void await this.service.failPendingApprovalInteraction(r,"审批请求已到期",{status:"expired",reason:"审批请求已到期"});const t=e instanceof V?e.reason:U;await this.service.failPendingApprovalInteraction(r,t)});this.trackSettlement(o)}setClientCapabilities(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation||void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"client.capabilities.set 仅可用于支持 capability negotiation 的 Runtime Protocol");const i=[...e.serverRequestMethods].sort();if(void 0!==this.capabilityRevision){const s=e.revision===this.capabilityRevision,r=i.length===this.capabilityMethods?.length&&i.every((e,t)=>e===this.capabilityMethods?.[t]);if(s&&r&&void 0!==this.capabilityResult)return{result:this.capabilityResult};if(e.revision<=this.capabilityRevision)throw new S(t.capabilityRevisionConflict,`Capability revision ${String(e.revision)} 与当前 revision ${String(this.capabilityRevision)} 冲突`)}const s=b(e),r=`Runtime 客户端已在 capability revision ${String(e.revision)} 撤销处理能力`,a=T(this.clientRequests),o=a.setServerRequestMethodsForAttachment(this.detachResponder,s.acceptedServerRequestMethods,r)??a.setServerRequestMethodsForResponder(this.responderId,s.acceptedServerRequestMethods,r);if(!1===o)throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 已失效");return this.service.setUserInputAvailable(s.acceptedServerRequestMethods.includes(n.userInputRequest)),this.capabilityRevision=e.revision,this.capabilityMethods=i,this.capabilityResult=s,{result:s,afterResponse:e=>{e&&o()}}}trackSettlement(e){this.settlementTasks.add(e),e.finally(()=>this.settlementTasks.delete(e)).catch(()=>{})}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_ERROR_CODES as
|
|
1
|
+
import{SessionOccupiedError as e}from"../ownership/session-claims.js";import{RUNTIME_ERROR_CODES as s,jsonValueSchema as t}from"@roll-agent/protocol";import{createSafeCapabilitySnapshot as o}from"../engine/capability-manifest.js";import{isPersistedToolExecutionRecord as r}from"../tool-bridge/tool-execution-record.js";import{RuntimeServiceError as i}from"../service/runtime-service.js";import{RuntimeClientRequestCoordinator as n}from"./runtime-client-request-coordinator.js";import{RuntimeProtocolAdapter as a}from"./runtime-protocol-adapter.js";import{EVENT_NOTIFICATION as c,RpcMethod as d,abortParamsSchema as l,approveParamsSchema as u,capabilitiesParamsSchema as p,closeParamsSchema as h,compactParamsSchema as m,createParamsSchema as f,isRequest as v,messagesParamsSchema as w,rejectParamsSchema as g,resumeParamsSchema as y,sendParamsSchema as R,toolExecutionsParamsSchema as I}from"./protocol.js";function S(e){return e instanceof Error?e.message:String(e)}function C(e){return e.map(e=>{const{providerOptions:s,...t}=e;if("string"==typeof t.content)return t;const o=t.content.filter(e=>"reasoning"!==e.type).map(e=>{if(!("providerOptions"in e))return e;const{providerOptions:s,...t}=e;return t});return{...t,content:o}}).filter(e=>!("assistant"===e.role&&Array.isArray(e.content)&&0===e.content.length))}class MethodNotFoundError extends Error{}class RuntimeTransportWriteError extends Error{}function b(e){return e instanceof Error&&"ZodError"===e.name&&"issues"in e&&Array.isArray(e.issues)}function A(s,o){if(s instanceof i||s instanceof e){const o=t.safeParse(s.details);return{code:-32e3,message:s.message,data:{rollCode:s instanceof e?"THREAD_BUSY":s.rollCode,retryable:s instanceof e||s.retryable,...o.success?{details:o.data}:{}}}}return b(s)?{code:-32602,message:"Invalid params",data:{rollCode:"INVALID_PARAMS",retryable:!1}}:s instanceof MethodNotFoundError?{code:-32601,message:s.message}:{code:-32603,message:o?"Internal error":S(s),data:{rollCode:"INTERNAL_ERROR",retryable:!1}}}const E={unselected:"unselected",legacy:"legacy",runtime:"runtime"},q=new Set(Object.values(d));export class RuntimeServer{engine;connection;authorizeRawToolEvidence;sessions=new Map;runtimeService;runtimeClientRequests;protocolAdapter;connectionMode=E.unselected;abortPromise;constructor(e,s,t={}){this.engine=e,this.connection=s,this.authorizeRawToolEvidence=t.authorizeRawToolEvidence,this.runtimeService=t.runtimeService,this.runtimeClientRequests=void 0===t.runtimeService?void 0:t.runtimeClientRequests??new n,this.protocolAdapter=void 0===t.runtimeService||void 0===this.runtimeClientRequests?void 0:new a(t.runtimeService,s,this.runtimeClientRequests);try{this.connection.onMessage(e=>this.handleMessage(e))}catch(e){throw this.protocolAdapter?.disposeConstructionFailure(),e}}async abortAll(){this.abortPromise??=this.performAbortAll(),await this.abortPromise}async performAbortAll(){const e=[...this.sessions.values()];this.sessions.clear();const s=this.protocolAdapter?.close();this.runtimeClientRequests?.cancelAll("Runtime Server 已关闭");try{await Promise.all([...e.map(e=>e.close()),...void 0===s?[]:[s],...void 0===this.runtimeService?[]:[this.runtimeService.close()]])}finally{this.protocolAdapter?.releaseServiceControl()}}handleMessage(e){if(!0===this.protocolAdapter?.handleResponse(e))return;if(!v(e))return;const s=!0===this.protocolAdapter?.handles(e.method);try{this.selectConnectionMode(e.method,s)}catch(t){return void this.sendOrClose({jsonrpc:"2.0",id:e.id,error:A(t,s)})}this.dispatch(e).then(s=>{const t=this.sendOrClose({jsonrpc:"2.0",id:e.id,result:s.result});s.afterResponse?.(t)},t=>{t instanceof RuntimeTransportWriteError||this.sendOrClose({jsonrpc:"2.0",id:e.id,error:A(t,s)})})}sendOrClose(e){try{return this.connection.send(e),!0}catch{try{this.connection.close()}catch{}return!1}}sendNotificationOrThrow(e){if(!this.sendOrClose(e))throw new RuntimeTransportWriteError("Runtime transport write failed")}requireSession(e){const s=this.sessions.get(e);if(!s)throw new Error(`Session "${e}" 不存在`);return s}selectConnectionMode(e,t){if(t){if(this.connectionMode===E.legacy)throw new i(s.capabilityUnavailable,"当前连接已选择 legacy session.* 协议,不能切换到 Runtime Protocol");this.connectionMode=E.runtime}else if(q.has(e)){if(this.connectionMode===E.runtime)throw new i(s.capabilityUnavailable,"当前连接已选择 Runtime Protocol,不能调用 legacy session.* 方法");this.connectionMode=E.legacy}}async dispatch(e){return!0===this.protocolAdapter?.handles(e.method)?this.protocolAdapter.dispatch(e):{result:await this.dispatchLegacy(e)}}async dispatchLegacy(e){switch(e.method){case d.Create:{const s=f.parse(e.params),t=await this.engine.createSession(s.title?{title:s.title}:{});return this.sessions.set(t.id,t),{sessionId:t.id}}case d.Resume:{const s=y.parse(e.params),t=await this.engine.resumeSession(s.threadId);return this.sessions.set(t.id,t),{sessionId:t.id}}case d.Send:{const s=R.parse(e.params),t=this.requireSession(s.sessionId);for await(const e of t.send(s.input))"reasoning-delta"!==e.type&&this.sendNotificationOrThrow({jsonrpc:"2.0",method:c,params:{sessionId:s.sessionId,event:"confirmation-required"===e.type?{...e,fullDiff:void 0}:e}});return{status:"completed"}}case d.Approve:{const s=u.parse(e.params);return{resolved:this.requireSession(s.sessionId).approve(s.approvalId)}}case d.Reject:{const s=g.parse(e.params);return{resolved:this.requireSession(s.sessionId).reject(s.approvalId,s.reason)}}case d.Abort:{const s=l.parse(e.params);return{ok:!0,cancelled:this.requireSession(s.sessionId).cancel()}}case d.Close:{const s=h.parse(e.params),t=this.requireSession(s.sessionId);return await t.close(),this.sessions.delete(s.sessionId),{closed:!0}}case d.Messages:{const s=w.parse(e.params);return{messages:C(this.requireSession(s.sessionId).getMessages())}}case d.ToolExecutions:{const s=I.parse(e.params),t=this.requireSession(s.sessionId);if(s.includeRaw&&!await(this.authorizeRawToolEvidence?.({sessionId:s.sessionId,...void 0!==s.executionId?{executionId:s.executionId}:{}})))throw new Error("Raw Tool evidence access denied by RuntimeServer policy");if(void 0!==s.executionId){const e=t.getToolExecution(s.executionId,s.includeRaw);if(s.includeRaw&&void 0!==e&&!r(e))throw new Error("Raw Tool evidence is unavailable without a durable ledger projection");return{record:e}}const o=t.getToolExecutions({...void 0!==s.afterSequence?{afterSequence:s.afterSequence}:{},...void 0!==s.limit?{limit:s.limit}:{},...void 0!==s.toolCallId?{toolCallId:s.toolCallId}:{}},s.includeRaw);if(s.includeRaw&&o.some(e=>!r(e)))throw new Error("Raw Tool evidence is unavailable without a durable ledger projection");return{records:o}}case d.Capabilities:{const s=p.parse(e.params),t=this.requireSession(s.sessionId);return o(t.getCapabilityManifest(),t.getCapabilityTurnContext())}case d.Compact:{const s=m.parse(e.params),t=this.requireSession(s.sessionId);for await(const e of t.compact("manual"))this.sendNotificationOrThrow({jsonrpc:"2.0",method:c,params:{sessionId:s.sessionId,event:e}});return{status:"completed"}}default:throw new MethodNotFoundError(`Method not found: ${e.method}`)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{appendFileSync as a,lstatSync as s,mkdirSync as r,
|
|
1
|
+
import{createHash as e,randomUUID as t}from"node:crypto";import{appendFileSync as a,lstatSync as s,mkdirSync as r,readFileSync as i,rmSync as d,writeFileSync as n}from"node:fs";import{basename as c,extname as o,isAbsolute as m,join as h}from"node:path";import{attachmentIdSchema as u,attachmentMediaTypeSchema as l,RUNTIME_ERROR_CODES as g,RUNTIME_V14_MAX_ATTACHMENT_BYTES as p,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as f,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as y,SUPPORTED_ATTACHMENT_MEDIA_TYPES as k}from"@roll-agent/protocol";const x=6e5,I=18e5,S={"image/png":["png"],"image/jpeg":["jpg","jpeg"],"image/gif":["gif"],"image/webp":["webp"],"application/pdf":["pdf"],"text/plain":["txt","log","text"],"text/markdown":["md","markdown"]};function v(e){const t=c(e).replace(/[\\/:]+/gu,"").replace(/\p{Cc}+/gu,"").trim();return t.length>0?t:"attachment"}function T(t){return e("sha256").update(t).digest("hex")}function B(e,t){const a=S[t];if(void 0===a)return!1;const s=o(e).slice(1).toLowerCase();return a.includes(s)}export class AttachmentStore{dir;maxAttachmentBytes;maxStagedAttachments;stagedTtlMs;committedTtlMs;now;records=new Map;closed=!1;constructor(e){this.maxAttachmentBytes=e.maxAttachmentBytes??p,this.maxStagedAttachments=e.maxStagedAttachments??y,this.stagedTtlMs=e.stagedTtlMs??x,this.committedTtlMs=e.committedTtlMs??I,this.now=e.now??Date.now,this.dir=h(e.dir,t()),r(this.dir,{recursive:!0})}get limits(){return{maxAttachmentBytes:this.maxAttachmentBytes,maxAttachmentChunkBytes:f,maxStagedAttachments:this.maxStagedAttachments}}stage(e){if(this.sweepExpired(),e.bytes>this.maxAttachmentBytes)return{ok:!1,code:g.attachmentTooLarge,message:`附件超过单文件上限 ${String(this.maxAttachmentBytes)} 字节`};if(!(e.mediaType in S))return{ok:!1,code:g.attachmentTypeUnsupported,message:`不支持的附件类型: ${e.mediaType}(支持 ${k.join(", ")})`};if(!B(e.fileName,e.mediaType))return{ok:!1,code:g.attachmentTypeUnsupported,message:`文件扩展名与 mediaType "${e.mediaType}" 不一致`};if(this.records.size>=this.maxStagedAttachments)return{ok:!1,code:g.attachmentQuotaExceeded,message:`附件数已达上限 ${String(this.maxStagedAttachments)}:附件被 turn.start 引用后不会自动释放,请调用 attachment.release 释放不再需要的附件,或等待 TTL 回收`,retryable:!0};const a=u.parse(t()),s={attachmentId:a,threadId:e.threadId,fileName:e.fileName,displayName:v(e.fileName),mediaType:e.mediaType,declaredBytes:e.bytes,declaredSha256:e.sha256,source:e.source,createdAtMs:this.now(),state:"staged",receivedBytes:0,nextSequence:0,committedAtMs:void 0};if("chunks"===e.source)return n(this.filePath(a),Buffer.alloc(0)),this.records.set(a,s),{ok:!0,attachmentId:a,state:"staged"};const r=this.loadLocalFile(e.sourcePath??"",e.mediaType,e.bytes,e.sha256);return r.ok?(n(this.filePath(a),r.data),s.state="committed",s.receivedBytes=r.data.length,s.committedAtMs=this.now(),this.records.set(a,s),{ok:!0,attachmentId:a,state:"committed",descriptor:this.describe(s)}):r}appendChunk(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("staged"!==t.state||"chunks"!==t.source)return{ok:!1,code:g.invalidParams,message:"附件不处于 chunk 上传状态"};if(e.sequence!==t.nextSequence)return{ok:!1,code:g.invalidParams,message:`chunk sequence 不连续:期望 ${String(t.nextSequence)},收到 ${String(e.sequence)}`};const s=Buffer.from(e.dataBase64,"base64");return 0===s.length||s.length>f?{ok:!1,code:g.invalidParams,message:`chunk 大小必须在 1..${String(f)} 字节内`}:t.receivedBytes+s.length>t.declaredBytes?(this.remove(t.attachmentId),{ok:!1,code:g.attachmentTooLarge,message:"累计上传字节数超过申报大小,附件已回收"}):(a(this.filePath(t.attachmentId),s),t.receivedBytes+=s.length,t.nextSequence+=1,{ok:!0,receivedBytes:t.receivedBytes,nextSequence:t.nextSequence})}commit(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("committed"===t.state)return{ok:!0,descriptor:this.describe(t)};if(t.receivedBytes!==t.declaredBytes)return{ok:!1,code:g.attachmentUploadIncomplete,message:`上传未完成:已收 ${String(t.receivedBytes)} 字节,申报 ${String(t.declaredBytes)} 字节`};return T(i(this.filePath(t.attachmentId)))!==t.declaredSha256?(this.remove(t.attachmentId),{ok:!1,code:g.attachmentHashMismatch,message:"附件内容 sha256 与申报值不匹配,附件已回收"}):(t.state="committed",t.committedAtMs=this.now(),{ok:!0,descriptor:this.describe(t)})}release(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);return void 0===t?{ok:!0,released:!1}:(this.remove(t.attachmentId),{ok:!0,released:!0})}readCommitted(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("committed"!==t.state)return{ok:!1,code:g.attachmentNotCommitted,message:`附件 "${e.attachmentId}" 尚未 commit`};let a;try{a=i(this.filePath(t.attachmentId))}catch{return this.remove(t.attachmentId),{ok:!1,code:g.internalError,message:"附件数据文件缺失,附件已回收"}}return{ok:!0,descriptor:this.describe(t),dataBase64:a.toString("base64")}}releaseThread(e){for(const t of[...this.records.values()])t.threadId===e&&this.remove(t.attachmentId)}close(){this.closed||(this.closed=!0,this.records.clear(),d(this.dir,{recursive:!0,force:!0}))}describe(e){return{attachmentId:e.attachmentId,fileName:e.fileName,displayName:e.displayName,mediaType:l.parse(e.mediaType),bytes:e.declaredBytes,sha256:e.declaredSha256,source:e.source,createdAt:new Date(e.createdAtMs).toISOString()}}loadLocalFile(e,t,a,r){if(!m(e))return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 必须是绝对路径"};if(!B(e,t))return{ok:!1,code:g.attachmentTypeUnsupported,message:`sourcePath 扩展名与 mediaType "${t}" 不一致`};let d;try{d=s(e)}catch{return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 不存在或不可读"}}if(d.isSymbolicLink())return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 不允许是符号链接"};if(!d.isFile())return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 不是普通文件"};if(d.size>this.maxAttachmentBytes)return{ok:!1,code:g.attachmentTooLarge,message:`文件实际大小 ${String(d.size)} 超过单文件上限 ${String(this.maxAttachmentBytes)} 字节`};if(d.size!==a)return{ok:!1,code:g.invalidParams,message:`文件实际大小 ${String(d.size)} 与申报 ${String(a)} 不一致`};const n=i(e);return T(n)!==r?{ok:!1,code:g.attachmentHashMismatch,message:"文件内容 sha256 与申报值不匹配"}:{ok:!0,data:n}}findOwned(e,t){const a=this.records.get(t);if(void 0!==a&&a.threadId===e)return a}notFound(e){return{ok:!1,code:g.attachmentNotFound,message:`附件 "${e}" 不存在、已过期或不属于该 Thread`}}remove(e){this.records.delete(e),d(this.filePath(e),{force:!0})}sweepExpired(){const e=this.now();for(const t of[...this.records.values()]){const a="staged"===t.state?this.stagedTtlMs:this.committedTtlMs;e-(t.committedAtMs??t.createdAtMs)>a&&this.remove(t.attachmentId)}}filePath(e){return h(this.dir,`${e}.bin`)}}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Keep the context of earlier chunks so a credential split across deltas is redacted as one value. */
|
|
2
|
+
export declare class ReasoningDisplayBuffer {
|
|
3
|
+
private raw;
|
|
4
|
+
private sent;
|
|
5
|
+
private stopped;
|
|
6
|
+
private lastScanLength;
|
|
7
|
+
private lastScanAt;
|
|
8
|
+
append(delta: string): string;
|
|
9
|
+
finish(): string;
|
|
10
|
+
private advance;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{redactSecretText as t}from"../tool-bridge/tool-execution-record.js";const s=16384,e=128,n=32,i="\n…(后续内容已省略)";export class ReasoningDisplayBuffer{raw="";sent="";stopped=!1;lastScanLength=0;lastScanAt=0;append(i){if(this.stopped)return"";const a=s-this.raw.length;this.raw+=i.slice(0,a);const h=i.length>a;if(!h&&!/[\s。!?]/u.test(i))return"";const r=performance.now();if(!h&&this.raw.length>e&&this.raw.length-this.lastScanLength<e&&r-this.lastScanAt<n)return"";this.lastScanLength=this.raw.length,this.lastScanAt=r;const c=t(this.raw),o=/(?:-----BEGIN[ A-Z0-9-]*|\bbearer\s+)$/iu.exec(c);let l=o?.index??c.length;for(;l>this.sent.length&&!/[\s。!?]/u.test(c.charAt(l-1));)l--;return this.advance(c.slice(0,l),h)}finish(){return this.stopped?"":this.advance(t(this.raw))}advance(t,s=!1){const e=t.startsWith(this.sent),n=e?t.slice(this.sent.length):"";return s||!e?(this.stopped=!0,n+i):(this.sent=t,n)}}
|
|
@@ -2,9 +2,10 @@ import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type Initia
|
|
|
2
2
|
import type { AgentSession } from "../engine/agent-session.ts";
|
|
3
3
|
import { AttachmentStore } from "./attachment-store.ts";
|
|
4
4
|
import type { SessionUserInputRequestId } from "../interaction/user-input-interaction-manager.ts";
|
|
5
|
+
import type { ReasoningPresentation } from "../types/events.ts";
|
|
5
6
|
import { ThreadStore, type StoredRuntimeEvent } from "../store/thread-store.ts";
|
|
6
7
|
type RollErrorCode = RuntimeProtocolErrorDataV14["rollCode"];
|
|
7
|
-
export type RuntimeServiceSession = Pick<AgentSession, "id" | "send" | "approve" | "reject" | "cancel" | "close" | "getCapabilityManifest" | "getCapabilityTurnContext"> & Partial<Pick<AgentSession, "setUserInputAvailable" | "resolveUserInput" | "cancelUserInput">>;
|
|
8
|
+
export type RuntimeServiceSession = Pick<AgentSession, "id" | "send" | "approve" | "reject" | "cancel" | "close" | "getCapabilityManifest" | "getCapabilityTurnContext"> & Partial<Pick<AgentSession, "setUserInputAvailable" | "resolveUserInput" | "cancelUserInput" | "assertWritable" | "addCloseBarrier">>;
|
|
8
9
|
export interface RuntimeServiceEngine {
|
|
9
10
|
createSession(input?: {
|
|
10
11
|
readonly title?: string;
|
|
@@ -17,6 +18,8 @@ export interface RuntimeServiceOptions {
|
|
|
17
18
|
readonly runtimeVersion?: string;
|
|
18
19
|
readonly maxFrameBytes?: number;
|
|
19
20
|
readonly idempotencyCacheEntries?: number;
|
|
21
|
+
/** Accept only source-classified deltas of this kind. Text requires a separate local subscription. */
|
|
22
|
+
readonly providerReasoning?: ReasoningPresentation["kind"];
|
|
20
23
|
/**
|
|
21
24
|
* Optional host-controlled projection. Raw model reasoning is never sent to public clients.
|
|
22
25
|
* Supplying this enables the `reasoning-summary` capability.
|
|
@@ -71,11 +74,17 @@ export declare class MutationRequestCache {
|
|
|
71
74
|
private readonly settled;
|
|
72
75
|
constructor(maxEntries: number);
|
|
73
76
|
run<TMethod extends MutationRuntimeMethod>(requestId: string, method: TMethod, params: LatestRuntimeMethodParams<TMethod>, action: () => LatestRuntimeMethodResult<TMethod> | Promise<LatestRuntimeMethodResult<TMethod>>): Promise<LatestRuntimeMethodResult<TMethod>>;
|
|
77
|
+
settlePending(): Promise<void>;
|
|
74
78
|
clear(): void;
|
|
75
79
|
private assertMatchingRequest;
|
|
76
80
|
private settle;
|
|
77
81
|
}
|
|
78
82
|
export declare class RuntimeService {
|
|
83
|
+
private closePromise;
|
|
84
|
+
private readonly openingSessions;
|
|
85
|
+
private readonly closingThreads;
|
|
86
|
+
private readonly ownership;
|
|
87
|
+
private readonly turnJobs;
|
|
79
88
|
readonly runtimeInstanceId: RuntimeInstanceId;
|
|
80
89
|
private readonly engine;
|
|
81
90
|
private readonly store;
|
|
@@ -85,6 +94,8 @@ export declare class RuntimeService {
|
|
|
85
94
|
private readonly maxFrameBytes;
|
|
86
95
|
private readonly idempotencyCacheEntries;
|
|
87
96
|
private readonly reasoningSummaryProjector;
|
|
97
|
+
private readonly providerReasoning;
|
|
98
|
+
private reasoningTextSubscription;
|
|
88
99
|
private readonly attachmentStore;
|
|
89
100
|
private readonly sessions;
|
|
90
101
|
private readonly activeTurns;
|
|
@@ -101,6 +112,7 @@ export declare class RuntimeService {
|
|
|
101
112
|
private userInputAvailable;
|
|
102
113
|
constructor(engine: RuntimeServiceEngine, store: ThreadStore, options?: RuntimeServiceOptions);
|
|
103
114
|
initialize(params: InitializeParams): InitializeResult;
|
|
115
|
+
subscribeReasoningText(listener: (event: RuntimeEventEnvelopeV16) => void): (() => void) | undefined;
|
|
104
116
|
onEvent(listener: (event: RuntimeEventEnvelopeV16) => void): () => void;
|
|
105
117
|
onFatalError(listener: (error: unknown) => void): () => void;
|
|
106
118
|
onUserInputInteraction(listener: (event: RuntimeUserInputInteractionEvent) => void): () => void;
|
|
@@ -139,9 +151,13 @@ export declare class RuntimeService {
|
|
|
139
151
|
private assertOpen;
|
|
140
152
|
private requireThread;
|
|
141
153
|
private requireSession;
|
|
154
|
+
private assertThreadOpen;
|
|
155
|
+
private retainOwnership;
|
|
156
|
+
private finishThread;
|
|
142
157
|
private rememberSession;
|
|
143
158
|
private emit;
|
|
144
159
|
private notifyFatalError;
|
|
160
|
+
private createTurnReasoningDisplay;
|
|
145
161
|
private driveTurn;
|
|
146
162
|
private findTurnOwner;
|
|
147
163
|
private settleTurnOwner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,initializeResultSchema as s,APPROVAL_EXPLANATION_PREVIEW_KEY as n,RUNTIME_ERROR_CODES as i,RUNTIME_FEATURES_V13 as a,RUNTIME_METHODS as o,RUNTIME_PROTOCOL_VERSION as d,RUNTIME_V14_MAX_ATTACHMENT_BYTES as u,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as h,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as c,RUNTIME_V14_MAX_TURN_ATTACHMENTS as l,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as p,approvalExplanationSchema as m,approvalIdSchema as I,normalizeUserInputResultForForm as v,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV16Schema as y,runtimeEphemeralEventV13Schema as T,runtimeInstanceIdSchema as A,runtimeProtocolVersionSchema as w,streamIdSchema as q,threadIdSchema as S,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as E}from"../engine/capability-manifest.js";import{isSensitiveFieldName as O,redactSecretText as b,toRedactedToolExecutionRecordSummary as P}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as C,RuntimeEventCursorGapError as U,ThreadStore as N}from"../store/thread-store.js";const F=4194304,k=500,j=1e4,B=16e3,M=64,D=128,V=16,$=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 L(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(i.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).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=L(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>f(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=>f(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,r,s){if(s.method!==t||s.fingerprint!==r)throw new RuntimeServiceError(i.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){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function z(e,t,r=0){if(void 0!==t&&($.has(t.toLowerCase())||O(t)))return"[redacted]";if(r>=V)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=b(e);return t.length<=B?t:`${t.slice(0,B)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,M).map(e=>z(e,void 0,r+1));return e.length>M&&t.push(`[${String(e.length-M)} items omitted]`),t}if(_(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,D))void 0!==n&&(t[s]=z(n,s,r+1));return Object.keys(e).length>D&&(t._omitted=`${String(Object.keys(e).length-D)} keys omitted`),t}return void 0===e?null:String(e)}function W(e){return e instanceof Error?e.message:String(e)}function G(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>_(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function J(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function H(e){const t=e.content;return"user"===e.role&&Array.isArray(t)?t.flatMap(e=>_(e)?"file"===e.type&&"string"==typeof e.mediaType&&"string"==typeof e.data?[{type:"attachment",mediaType:e.mediaType,bytes:J(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:J(e.image)}]:[]:[]):[]}function K(e){return S.parse(e)}function Q(e,t){return{id:K(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 X(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=b(G(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...H(e.message)]}}function Y(e,t={status:"not_provided"}){const r=P(e);return{id:g.parse(r.id),sequence:e.sequence,toolCallId:r.toolCallId,agentName:r.agentName,toolName:r.toolName,createdAt:r.createdAt,outcome:{kind:r.outcome.kind,..."reason"in r.outcome&&void 0!==r.outcome.reason?{reason:r.outcome.reason}:{}},display:z(r.display,void 0),appOutput:t}}function Z(e,t){const s=z(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(b(t.explanation)),a=void 0===t.diff?void 0:z(t.diff,void 0),o=_(s)?{...s,...!0===i?.success?{[n]:i.data}:{},...void 0!==a?{[r]:a}:{},...void 0!==t.fullDiff?{fullDiffAvailable:!0}:{}}:s;return{id:I.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:b(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;attachmentStore;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;pendingUserInputs=new Map;listeners=new Set;fatalErrorListeners=new Set;userInputListeners=new Set;mutationRequests;sequence=0;closing=!1;userInputAvailable=!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??F,this.idempotencyCacheEntries=s.idempotencyCacheEntries??j,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=A.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=w.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(i.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:p}});const r=a.filter(e=>"reasoning-summary"!==e||void 0!==this.reasoningSummaryProjector),n={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:k,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t||"1.6"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===t||"1.6"===t?["app-output"]:[]],limits:{...n.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??u,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??h,maxTurnAttachments:l,maxStagedAttachments:e?.maxStagedAttachments??c}})}return"1.3"===t?{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!0}}:{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!1}}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}onFatalError(e){return this.fatalErrorListeners.add(e),()=>{this.fatalErrorListeners.delete(e)}}onUserInputInteraction(e){return this.userInputListeners.add(e),()=>{this.userInputListeners.delete(e)}}setUserInputAvailable(e){if(this.userInputAvailable!==e){this.userInputAvailable=e,e||this.cancelAllPendingUserInputs("当前客户端已撤销用户输入处理能力");for(const t of this.sessions.values())t.setUserInputAvailable?.(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(i.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>Q(this.store,e)),n=t+s.length;return{items:s,nextCursor:n<r.length?String(n):null}}async createThread(e){return this.mutationRequests.run(e.requestId,o.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:Q(this.store,this.requireThread(t.id))}})}async openThread(e){return this.assertOpen(),"scheduled"!==this.requireThread(e.threadId).origin.kind&&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:Q(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=X(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Y(t,this.store.getAppOutputDescriptor(e.threadId,t.id))),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),eventCursor:this.store.getRuntimeEventCursor(e.threadId),contextRecovery:null}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof C)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof U)throw new RuntimeServiceError(i.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,o.threadRename,e,()=>(this.assertOpen(),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:Q(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,o.threadDelete,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.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.attachmentStore?.releaseThread(e.threadId),this.store.deleteThread(e.threadId),{deleted:!0}})}async detachThread(e){return this.mutationRequests.run(e.requestId,o.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:z(E(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,o.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(i.invalidParams,"定时执行会话是只读记录;请基于该次运行创建新对话");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(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const r=await this.requireSession(e.threadId),s=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==s){if(s===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const n=this.resolveTurnAttachments(e),a={threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0};this.activeTurns.set(e.threadId,a),this.activeTurnOwners.set(e.turnId,e.threadId);try{this.emit(e.threadId,e.turnId,{type:"turn.started"})}catch(t){throw this.activeTurns.delete(e.threadId),this.activeTurnOwners.delete(e.turnId),t}return this.driveTurn(a,e.input.text,n).catch(()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,o.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(i.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingUserInputsForTurn(e.turnId,"Turn 已由客户端取消"),this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,o.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 r=this.requirePendingApproval(e),s="approve"===t.decision?r.session.approve(e.approvalId,t.scope):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!s)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}getPendingApprovalExpiresAt(e){return this.findPendingApproval(e)?.expiresAt}async stageAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentStage,e,()=>{this.assertOpen(),this.requireThread(e.threadId);const t=this.requireAttachmentStore().stage({threadId:e.threadId,fileName:e.fileName,mediaType:e.mediaType,bytes:e.bytes,sha256:e.sha256,source:e.source,sourcePath:e.sourcePath});return t.ok||this.throwAttachmentFailure(t),{attachmentId:t.attachmentId,state:t.state,...void 0!==t.descriptor?{descriptor:t.descriptor}:{}}})}async appendAttachmentChunk(e){return this.mutationRequests.run(e.requestId,o.attachmentChunk,e,()=>{this.assertOpen();const t=this.requireAttachmentStore().appendChunk({threadId:e.threadId,attachmentId:e.attachmentId,sequence:e.sequence,dataBase64:e.dataBase64});return t.ok||this.throwAttachmentFailure(t),{receivedBytes:t.receivedBytes,nextSequence:t.nextSequence}})}async commitAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentCommit,e,()=>{this.assertOpen();const t=this.requireAttachmentStore().commit({threadId:e.threadId,attachmentId:e.attachmentId});return t.ok||this.throwAttachmentFailure(t),{descriptor:t.descriptor}})}async releaseAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentRelease,e,()=>{this.assertOpen();return{released:this.requireAttachmentStore().release({threadId:e.threadId,attachmentId:e.attachmentId}).released}})}requireAttachmentStore(){if(void 0===this.attachmentStore)throw new RuntimeServiceError(i.capabilityUnavailable,"Runtime 未配置附件存储,attachments 能力不可用");return this.attachmentStore}throwAttachmentFailure(e){throw new RuntimeServiceError(e.code,e.message,{...void 0!==e.retryable?{retryable:e.retryable}:{}})}resolveTurnAttachments(e){const t=e.input.attachments;if(void 0===t||0===t.length)return;const r=this.requireAttachmentStore(),s=new Set,n=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(i.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),n.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return n}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}resolvePendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;let s;try{s=v(r.form,t)}catch{return this.cancelPendingUserInput(e,"用户输入不符合原始表单约束"),!1}this.pendingUserInputs.delete(e);const n=r.session.resolveUserInput?.(e,s)??!1;if(!n){const t="用户输入请求已失效";return r.session.cancelUserInput?.(e,t),this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),!1}return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:"submitted"===s.status?"用户已提交输入":"用户已取消输入"}),n}cancelPendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;this.pendingUserInputs.delete(e);const s=r.session.cancelUserInput?.(e,t)??!1;return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),s}getApprovalPreview(e){this.assertOpen();const t=this.requirePendingApproval(e);if(void 0===t.fullDiff||void 0!==t.expiresAt&&Date.parse(t.expiresAt)<=Date.now())throw new RuntimeServiceError(i.approvalNotFound,"完整预览不存在或审批已失效");const r=t.fullDiff.slice(e.offset,e.offset+e.maxChars),s=e.offset+r.length;return{text:r,totalChars:t.fullDiff.length,nextOffset:s<t.fullDiff.length?s:null}}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Y(t,this.store.getAppOutputDescriptor(e.threadId,t.id))}}getOperationResult(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);if(void 0===t)return{result:null};const r=this.store.getAppOutput(e.threadId,e.operationId)??{status:"not_provided"};return{result:{threadId:e.threadId,operationId:e.operationId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,output:r}}}async close(){if(this.closing)return;this.closing=!0,this.setUserInputAvailable(!1);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(),this.pendingUserInputs.clear(),this.userInputListeners.clear(),this.attachmentStore?.close(),await Promise.allSettled(e.map(e=>e.close())),this.mutationRequests.clear(),this.fatalErrorListeners.clear()}assertOpen(){if(this.closing)throw new RuntimeServiceError(i.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(i.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.rememberSession(r),r}rememberSession(e){e.setUserInputAvailable?.(this.userInputAvailable),this.sessions.set(e.id,e)}emit(e,t,r){const s=(new Date).toISOString(),n={protocolVersion:d,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:s,threadId:e,...void 0!==t?{turnId:t}:{}},i=y.safeParse(r),a=i.success?(()=>{let r;try{r=this.store.appendRuntimeEvent({threadId:e,...void 0===t?{}:{turnId:t},timestamp:s,event:i.data})}catch(e){throw this.notifyFatalError(e),e}return{...n,durability:"durable",eventId:r.eventId,cursor:r.cursor,event:i.data}})():{...n,durability:"ephemeral",event:T.parse(r)};this.sequence+=1;for(const e of this.listeners)try{e(a)}catch{}}notifyFatalError(e){for(const e of this.activeTurns.values())try{e.session.cancel()}catch{}for(const t of this.fatalErrorListeners)try{t(e)}catch{}}async driveTurn(e,t,r){const s={streamId:void 0,text:"",terminalEvent:void 0},n=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let i;try{for await(const t of e.session.send(n))this.projectSessionEvent(e,s,t);i=s.terminalEvent??{type:"turn.completed"}}catch(e){i=s.terminalEvent??{type:"turn.failed",stage:"execute",message:b(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}void 0!==s.streamId&&this.projectSessionEvent(e,s,{type:"message-finish",text:s.text}),this.emitTurnTerminal(e,i)}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":{if(void 0!==r.streamId)return;const s=q.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??q.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??q.parse(t());return void 0===r.streamId&&this.emit(e.threadId,e.turnId,{type:"message.started",streamId:n}),this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:b(r.text||s.text)}),r.streamId=void 0,void(r.text="")}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:b(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:z(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:b(s.delta)});case"tool-result":return void this.emit(e.threadId,e.turnId,{type:"tool.completed",appOutput:void 0===s.executionId?{status:"not_provided"}:this.store.getAppOutputDescriptor(e.threadId,s.executionId)??{status:"not_provided"},toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:g.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:z(s.display,void 0)});case"confirmation-required":{const t=Z(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt,fullDiff:void 0===s.fullDiff?void 0:b(s.fullDiff)}),void this.emit(e.threadId,e.turnId,{type:"approval.required",approval:t})}case"user-input-required":{if(!this.userInputAvailable||void 0===e.session.resolveUserInput||void 0===e.session.cancelUserInput)return void e.session.cancelUserInput?.(s.requestId,"当前客户端未协商用户输入处理能力");const t=x.parse(s.form),r=x.parse(t),n={requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,session:e.session,form:t,expiresAt:s.expiresAt};this.pendingUserInputs.set(s.requestId,n),e.status="waiting-for-user";return void(this.emitUserInputInteraction({type:"required",requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,form:r,expiresAt:s.expiresAt})||this.cancelPendingUserInput(s.requestId,"当前没有可处理用户输入请求的客户端"))}case"user-input-settled":{const e=this.pendingUserInputs.get(s.requestId);if(void 0===e)return;return this.pendingUserInputs.delete(s.requestId),this.restoreRunningStatus(e),void this.emitUserInputInteraction({type:"settled",requestId:e.requestId,threadId:e.threadId,turnId:e.turnId,reason:"submitted"===s.status?"用户输入请求已结算":"用户输入请求已取消"})}case"turn-cancelled":r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:b(s.message)}:{type:"turn.failed",stage:"execute",message:b(e.interactionFailure)};break;case"error":r.terminalEvent??={type:"turn.failed",stage:s.stage,message:b(s.message)};break;case"context-recovery":this.emit(e.threadId,e.turnId,{type:"context.recovery.changed",contextRecovery:s.state})}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:b(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(i.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:b(t.reason)}:t;this.emit(e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:r})}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}cancelPendingUserInputsForTurn(e,t){const r=[...this.pendingUserInputs.values()].filter(t=>t.turnId===e).map(e=>e.requestId);for(const e of r)this.cancelPendingUserInput(e,t)}cancelAllPendingUserInputs(e){for(const t of[...this.pendingUserInputs.keys()])this.cancelPendingUserInput(t,e)}restoreRunningStatus(e){const t=this.activeTurns.get(e.threadId);void 0!==t&&t.turnId===e.turnId&&"waiting-for-user"===t.status&&(t.status="running")}emitUserInputInteraction(e){let t=!1;for(const r of this.userInputListeners)try{r(e),t=!0}catch{}return t}emitTurnTerminal(e,t){this.cancelPendingUserInputsForTurn(e.turnId,`Turn 已终止:${t.type}`),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)}}
|
|
1
|
+
import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,initializeResultSchema as s,APPROVAL_EXPLANATION_PREVIEW_KEY as n,RUNTIME_ERROR_CODES as i,RUNTIME_FEATURES_V13 as a,RUNTIME_METHODS as o,RUNTIME_PROTOCOL_VERSION as d,RUNTIME_V14_MAX_ATTACHMENT_BYTES as h,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as u,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as c,RUNTIME_V14_MAX_TURN_ATTACHMENTS as l,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as p,approvalExplanationSchema as m,approvalIdSchema as I,normalizeUserInputResultForForm as v,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV16Schema as y,runtimeEphemeralEventV16Schema as T,runtimeInstanceIdSchema as w,runtimeProtocolVersionSchema as S,streamIdSchema as A,threadIdSchema as q,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as b}from"../engine/capability-manifest.js";import{TurnReasoningDisplay as O}from"./turn-reasoning-display.js";import{isSensitiveFieldName as E,redactSecretText as P,toRedactedToolExecutionRecordSummary as C}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as U,RuntimeEventCursorGapError as N,ThreadStore as F}from"../store/thread-store.js";const k=4194304,B=500,j=1e4,M=16e3,D=64,V=128,$=16,J=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 L(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(i.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).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=L(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>f(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=>f(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}async settlePending(){await Promise.allSettled([...this.inFlight.values()].map(e=>e.response))}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,r,s){if(s.method!==t||s.fingerprint!==r)throw new RuntimeServiceError(i.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){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function z(e,t,r=0){if(void 0!==t&&(J.has(t.toLowerCase())||E(t)))return"[redacted]";if(r>=$)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=P(e);return t.length<=M?t:`${t.slice(0,M)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,D).map(e=>z(e,void 0,r+1));return e.length>D&&t.push(`[${String(e.length-D)} items omitted]`),t}if(_(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,V))void 0!==n&&(t[s]=z(n,s,r+1));return Object.keys(e).length>V&&(t._omitted=`${String(Object.keys(e).length-V)} keys omitted`),t}return void 0===e?null:String(e)}function W(e){return e instanceof Error?e.message:String(e)}function G(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>_(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function H(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function K(e){const t=e.content;return"user"===e.role&&Array.isArray(t)?t.flatMap(e=>_(e)?"file"===e.type&&"string"==typeof e.mediaType&&"string"==typeof e.data?[{type:"attachment",mediaType:e.mediaType,bytes:H(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:H(e.image)}]:[]:[]):[]}function Q(e){return q.parse(e)}function X(e,t){return{id:Q(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 Y(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=P(G(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...K(e.message)]}}function Z(e,t={status:"not_provided"}){const r=C(e);return{id:g.parse(r.id),sequence:e.sequence,toolCallId:r.toolCallId,agentName:r.agentName,toolName:r.toolName,createdAt:r.createdAt,outcome:{kind:r.outcome.kind,..."reason"in r.outcome&&void 0!==r.outcome.reason?{reason:r.outcome.reason}:{}},display:z(r.display,void 0),appOutput:t}}function ee(e,t){const s=z(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(P(t.explanation)),a=void 0===t.diff?void 0:z(t.diff,void 0),o=_(s)?{...s,...!0===i?.success?{[n]:i.data}:{},...void 0!==a?{[r]:a}:{},...void 0!==t.fullDiff?{fullDiffAvailable:!0}:{}}:s;return{id:I.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:P(t.reason)}:{}}}export class RuntimeService{closePromise;openingSessions=new Map;closingThreads=new Set;ownership=new Map;turnJobs=new Map;runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;providerReasoning;reasoningTextSubscription;attachmentStore;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;pendingUserInputs=new Map;listeners=new Set;fatalErrorListeners=new Set;userInputListeners=new Set;mutationRequests;sequence=0;closing=!1;userInputAvailable=!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??k,this.idempotencyCacheEntries=s.idempotencyCacheEntries??j,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.providerReasoning=s.providerReasoning,this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=w.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=S.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(i.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:p}});const r=a.filter(e=>"reasoning-summary"!==e||"summary"===this.providerReasoning||void 0===this.providerReasoning&&void 0!==this.reasoningSummaryProjector),n={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:B,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t||"1.6"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===t||"1.6"===t?["app-output"]:[]],limits:{...n.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??h,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??u,maxTurnAttachments:l,maxStagedAttachments:e?.maxStagedAttachments??c}})}return"1.3"===t?{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!0}}:{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!1}}}subscribeReasoningText(e){if(this.assertOpen(),"text"!==this.providerReasoning||this.reasoningTextSubscription)return;const t={deliver:e};return this.reasoningTextSubscription=t,()=>{this.reasoningTextSubscription===t&&(this.reasoningTextSubscription=void 0)}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}onFatalError(e){return this.fatalErrorListeners.add(e),()=>{this.fatalErrorListeners.delete(e)}}onUserInputInteraction(e){return this.userInputListeners.add(e),()=>{this.userInputListeners.delete(e)}}setUserInputAvailable(e){if(this.userInputAvailable!==e){this.userInputAvailable=e,e||this.cancelAllPendingUserInputs("当前客户端已撤销用户输入处理能力");for(const t of this.sessions.values())t.setUserInputAvailable?.(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(i.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>X(this.store,e)),n=t+s.length;return{items:s,nextCursor:n<r.length?String(n):null}}async createThread(e){return this.mutationRequests.run(e.requestId,o.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:X(this.store,this.requireThread(t.id))}})}async openThread(e){return this.assertOpen(),"scheduled"!==this.requireThread(e.threadId).origin.kind&&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:X(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=Y(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))),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),eventCursor:this.store.getRuntimeEventCursor(e.threadId),contextRecovery:null}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof U)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof N)throw new RuntimeServiceError(i.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,o.threadRename,e,()=>(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:X(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,o.threadDelete,e,async()=>{if(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.store.retainThread(e.threadId);this.closingThreads.add(e.threadId);let r=!1;try{await Promise.allSettled([this.openingSessions.get(e.threadId)]);const s=this.sessions.get(e.threadId);return await(s?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),r=!0,t?.assert(),this.store.deleteThread(e.threadId),{deleted:!0}}finally{r&&(t?.release(),this.closingThreads.delete(e.threadId))}})}async detachThread(e){return this.mutationRequests.run(e.requestId,o.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.closingThreads.has(e.threadId))return{detached:!1};this.closingThreads.add(e.threadId),await Promise.allSettled([this.openingSessions.get(e.threadId)]);const t=this.sessions.get(e.threadId);return void 0!==t||this.ownership.has(e.threadId)?(await(t?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),this.closingThreads.delete(e.threadId),{detached:!0}):(this.closingThreads.delete(e.threadId),{detached:!1})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:z(b(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,o.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(i.invalidParams,"定时执行会话是只读记录;请基于该次运行创建新对话");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(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const r=await this.requireSession(e.threadId),s=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==s){if(s===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});this.assertThreadOpen(e.threadId);const n=this.resolveTurnAttachments(e),a={ownership:this.ownership.get(e.threadId),threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0},o=this.createTurnReasoningDisplay(a);this.activeTurns.set(e.threadId,a),this.activeTurnOwners.set(e.turnId,e.threadId);try{this.emit(a.ownership,e.threadId,e.turnId,{type:"turn.started"})}catch(t){throw this.activeTurns.delete(e.threadId),this.activeTurnOwners.delete(e.turnId),t}const d=this.driveTurn(a,e.input.text,n,o);return this.turnJobs.set(e.threadId,d),d.then(()=>{this.turnJobs.get(e.threadId)===d&&this.turnJobs.delete(e.threadId)},()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,o.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(i.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingUserInputsForTurn(e.turnId,"Turn 已由客户端取消"),this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,o.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 r=this.requirePendingApproval(e),s="approve"===t.decision?r.session.approve(e.approvalId,t.scope):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!s)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}getPendingApprovalExpiresAt(e){return this.findPendingApproval(e)?.expiresAt}async stageAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentStage,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId),this.requireThread(e.threadId);const t=this.requireAttachmentStore().stage({threadId:e.threadId,fileName:e.fileName,mediaType:e.mediaType,bytes:e.bytes,sha256:e.sha256,source:e.source,sourcePath:e.sourcePath});return t.ok||this.throwAttachmentFailure(t),{attachmentId:t.attachmentId,state:t.state,...void 0!==t.descriptor?{descriptor:t.descriptor}:{}}})}async appendAttachmentChunk(e){return this.mutationRequests.run(e.requestId,o.attachmentChunk,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().appendChunk({threadId:e.threadId,attachmentId:e.attachmentId,sequence:e.sequence,dataBase64:e.dataBase64});return t.ok||this.throwAttachmentFailure(t),{receivedBytes:t.receivedBytes,nextSequence:t.nextSequence}})}async commitAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentCommit,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().commit({threadId:e.threadId,attachmentId:e.attachmentId});return t.ok||this.throwAttachmentFailure(t),{descriptor:t.descriptor}})}async releaseAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentRelease,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);return{released:this.requireAttachmentStore().release({threadId:e.threadId,attachmentId:e.attachmentId}).released}})}requireAttachmentStore(){if(void 0===this.attachmentStore)throw new RuntimeServiceError(i.capabilityUnavailable,"Runtime 未配置附件存储,attachments 能力不可用");return this.attachmentStore}throwAttachmentFailure(e){throw new RuntimeServiceError(e.code,e.message,{...void 0!==e.retryable?{retryable:e.retryable}:{}})}resolveTurnAttachments(e){const t=e.input.attachments;if(void 0===t||0===t.length)return;const r=this.requireAttachmentStore(),s=new Set,n=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(i.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),n.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return n}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}resolvePendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;let s;try{s=v(r.form,t)}catch{return this.cancelPendingUserInput(e,"用户输入不符合原始表单约束"),!1}this.pendingUserInputs.delete(e);const n=r.session.resolveUserInput?.(e,s)??!1;if(!n){const t="用户输入请求已失效";return r.session.cancelUserInput?.(e,t),this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),!1}return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:"submitted"===s.status?"用户已提交输入":"用户已取消输入"}),n}cancelPendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;this.pendingUserInputs.delete(e);const s=r.session.cancelUserInput?.(e,t)??!1;return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),s}getApprovalPreview(e){this.assertOpen();const t=this.requirePendingApproval(e);if(void 0===t.fullDiff||void 0!==t.expiresAt&&Date.parse(t.expiresAt)<=Date.now())throw new RuntimeServiceError(i.approvalNotFound,"完整预览不存在或审批已失效");const r=t.fullDiff.slice(e.offset,e.offset+e.maxChars),s=e.offset+r.length;return{text:r,totalChars:t.fullDiff.length,nextOffset:s<t.fullDiff.length?s:null}}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))}}getOperationResult(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);if(void 0===t)return{result:null};const r=this.store.getAppOutput(e.threadId,e.operationId)??{status:"not_provided"};return{result:{threadId:e.threadId,operationId:e.operationId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,output:r}}}close(){if(void 0!==this.closePromise)return this.closePromise;this.closing=!0,this.setUserInputAvailable(!1);for(const e of this.activeTurns.values())e.session.cancel();return this.closePromise=(async()=>{await this.mutationRequests.settlePending(),await Promise.allSettled(this.openingSessions.values());const e=(await Promise.allSettled([...this.sessions.values()].map(e=>e.close()))).filter(e=>"rejected"===e.status);if(e.length>0)throw new AggregateError(e.map(e=>e.reason),"Runtime session cleanup failed");await Promise.all(this.turnJobs.values());for(const e of this.ownership.keys())this.finishThread(e);this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),this.pendingUserInputs.clear(),this.userInputListeners.clear(),this.reasoningTextSubscription=void 0,this.attachmentStore?.close(),this.mutationRequests.clear(),this.fatalErrorListeners.clear()})(),this.closePromise}assertOpen(){if(this.closing)throw new RuntimeServiceError(i.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(i.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertThreadOpen(e),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t.assertWritable?.(),this.ownership.get(e)?.assert(),t;const r=this.openingSessions.get(e);if(void 0!==r)return r;const s=this.engine.resumeSession(e).then(t=>(this.rememberSession(t),this.assertThreadOpen(e),t)).finally(()=>this.openingSessions.delete(e));return this.openingSessions.set(e,s),s}assertThreadOpen(e){if(this.assertOpen(),this.closingThreads.has(e))throw new RuntimeServiceError(i.threadBusy,"会话正在关闭,请等待清理完成",{retryable:!0})}retainOwnership(e){this.assertThreadOpen(e);const t=this.ownership.get(e);if(void 0!==t)return void t.assert();const r=this.store.retainThread(e);void 0!==r&&this.ownership.set(e,r)}finishThread(e){this.ownership.get(e)?.assert(),this.attachmentStore?.releaseThread(q.parse(e)),this.ownership.get(e)?.release(),this.ownership.delete(e),this.sessions.delete(e)}rememberSession(e){if(this.sessions.get(e.id)===e)return;const t=this.ownership.get(e.id)??this.store.retainThread(e.id);void 0!==t&&(t.assert(),this.ownership.set(e.id,t)),e.addCloseBarrier?.(async()=>{if(await this.turnJobs.get(e.id),t?.assert(),this.sessions.get(e.id)!==e)throw new RuntimeServiceError(i.threadBusy,"会话已关闭,不能释放后续会话的占用");this.finishThread(e.id)}),e.setUserInputAvailable?.(this.userInputAvailable),this.sessions.set(e.id,e)}emit(e,t,r,s){e?.assert();const n=(new Date).toISOString(),i={protocolVersion:d,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:n,threadId:t,...void 0!==r?{turnId:r}:{}},a=y.safeParse(s),o=a.success?(()=>{let e;try{e=this.store.appendRuntimeEvent({threadId:t,...void 0===r?{}:{turnId:r},timestamp:n,event:a.data})}catch(e){throw this.notifyFatalError(e),e}return{...i,durability:"durable",eventId:e.eventId,cursor:e.cursor,event:a.data}})():{...i,durability:"ephemeral",event:T.parse(s)};this.sequence+=1;const h="reasoning.text.delta"===s.type?this.reasoningTextSubscription?[this.reasoningTextSubscription.deliver]:[]:this.listeners;for(const e of h)try{e(o)}catch{}}notifyFatalError(e){for(const e of this.activeTurns.values())try{e.session.cancel()}catch{}for(const t of this.fatalErrorListeners)try{t(e)}catch{}}createTurnReasoningDisplay(e){const t=this.providerReasoning,r=this.reasoningSummaryProjector;if(void 0===t&&!r)return;const s=this.reasoningTextSubscription;if("text"===t&&!s)return;const n="text"===t?"reasoning.text.delta":"reasoning.summary.delta",i=()=>"text"!==t||s===this.reasoningTextSubscription;return new O(e=>{if(i())return void 0===t?r?.(e.delta):e.presentation===t?e.delta:void 0},(t,r)=>{i()&&this.emit(e.ownership,e.threadId,e.turnId,{type:n,reasoningId:t,delta:r})},void 0===t)}async driveTurn(e,t,r,s){const n={reasoning:s,streamId:void 0,text:"",terminalEvent:void 0},i=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let a;try{for await(const t of e.session.send(i))this.projectSessionEvent(e,n,t);a=n.terminalEvent??{type:"turn.completed"}}catch(e){n.reasoning?.reset(),a=n.terminalEvent??{type:"turn.failed",stage:"execute",message:P(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}void 0!==n.streamId&&this.projectSessionEvent(e,n,{type:"message-finish",text:n.text}),this.emitTurnTerminal(e,a)}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":{if(r.reasoning?.reset(),void 0!==r.streamId)return;const s=A.parse(t());return r.streamId=s,r.text="",void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??A.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{r.reasoning?.finish();const n=r.streamId??A.parse(t());return void 0===r.streamId&&this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n}),this.emit(e.ownership,e.threadId,e.turnId,{type:"message.completed",streamId:n,text:P(r.text||s.text)}),r.streamId=void 0,void(r.text="")}case"reasoning-start":return void r.reasoning?.start(s.reasoningId);case"reasoning-delta":return void r.reasoning?.append(s);case"reasoning-end":return void r.reasoning?.end(s.reasoningId);case"tool-call":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:z(s.input,void 0)});case"tool-output-delta":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:P(s.delta)});case"tool-result":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.completed",appOutput:void 0===s.executionId?{status:"not_provided"}:this.store.getAppOutputDescriptor(e.threadId,s.executionId)??{status:"not_provided"},toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:g.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:z(s.display,void 0)});case"confirmation-required":{const t=ee(e.turnId,s);return this.pendingApprovals.set(t.id,{ownership:e.ownership,threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt,fullDiff:void 0===s.fullDiff?void 0:P(s.fullDiff)}),void this.emit(e.ownership,e.threadId,e.turnId,{type:"approval.required",approval:t})}case"user-input-required":{if(!this.userInputAvailable||void 0===e.session.resolveUserInput||void 0===e.session.cancelUserInput)return void e.session.cancelUserInput?.(s.requestId,"当前客户端未协商用户输入处理能力");const t=x.parse(s.form),r=x.parse(t),n={requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,session:e.session,form:t,expiresAt:s.expiresAt};this.pendingUserInputs.set(s.requestId,n),e.status="waiting-for-user";return void(this.emitUserInputInteraction({type:"required",requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,form:r,expiresAt:s.expiresAt})||this.cancelPendingUserInput(s.requestId,"当前没有可处理用户输入请求的客户端"))}case"user-input-settled":{const e=this.pendingUserInputs.get(s.requestId);if(void 0===e)return;return this.pendingUserInputs.delete(s.requestId),this.restoreRunningStatus(e),void this.emitUserInputInteraction({type:"settled",requestId:e.requestId,threadId:e.threadId,turnId:e.turnId,reason:"submitted"===s.status?"用户输入请求已结算":"用户输入请求已取消"})}case"turn-cancelled":r.reasoning?.reset(),r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:P(s.message)}:{type:"turn.failed",stage:"execute",message:P(e.interactionFailure)};break;case"error":r.reasoning?.reset(),r.terminalEvent??={type:"turn.failed",stage:s.stage,message:P(s.message)};break;case"context-recovery":this.emit(e.ownership,e.threadId,e.turnId,{type:"context.recovery.changed",contextRecovery:s.state});break;case"model-step-start":case"model-recovery":r.reasoning?.reset();break;case"step-finish":r.reasoning?.finish()}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:P(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(i.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:P(t.reason)}:t;this.emit(e.ownership,e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:r})}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}cancelPendingUserInputsForTurn(e,t){const r=[...this.pendingUserInputs.values()].filter(t=>t.turnId===e).map(e=>e.requestId);for(const e of r)this.cancelPendingUserInput(e,t)}cancelAllPendingUserInputs(e){for(const t of[...this.pendingUserInputs.keys()])this.cancelPendingUserInput(t,e)}restoreRunningStatus(e){const t=this.activeTurns.get(e.threadId);void 0!==t&&t.turnId===e.turnId&&"waiting-for-user"===t.status&&(t.status="running")}emitUserInputInteraction(e){let t=!1;for(const r of this.userInputListeners)try{r(e),t=!0}catch{}return t}emitTurnTerminal(e,t){this.cancelPendingUserInputsForTurn(e.turnId,`Turn 已终止:${t.type}`),this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.ownership,e.threadId,e.turnId,t)}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SessionEvent } from "../types/events.ts";
|
|
2
|
+
type Delta = Extract<SessionEvent, {
|
|
3
|
+
type: "reasoning-delta";
|
|
4
|
+
}>;
|
|
5
|
+
/** Per-turn display state. Native block IDs can repeat between SDK steps or retries. */
|
|
6
|
+
export declare class TurnReasoningDisplay {
|
|
7
|
+
private readonly blocks;
|
|
8
|
+
private overflowed;
|
|
9
|
+
private nextId;
|
|
10
|
+
private readonly readDelta;
|
|
11
|
+
private readonly publish;
|
|
12
|
+
private readonly preserveNativeId;
|
|
13
|
+
constructor(readDelta: (event: Delta) => string | undefined, publish: (id: string, delta: string) => void, preserveNativeId?: boolean);
|
|
14
|
+
start(nativeId: string): void;
|
|
15
|
+
append(event: Delta): void;
|
|
16
|
+
end(nativeId: string): void;
|
|
17
|
+
finish(): void;
|
|
18
|
+
reset(): void;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ReasoningDisplayBuffer as e}from"./reasoning-display-buffer.js";const s=16;export class TurnReasoningDisplay{blocks=new Map;overflowed=!1;nextId=0;readDelta;publish;preserveNativeId;constructor(e,s,t=!1){this.readDelta=e,this.publish=s,this.preserveNativeId=t}start(e){this.blocks.delete(e)}append(t){if(this.overflowed)return;const i=this.readDelta(t);if(!i)return;let r=this.blocks.get(t.reasoningId);if(!r){if(this.blocks.size>=s)return this.blocks.clear(),void(this.overflowed=!0);r={id:this.preserveNativeId&&t.reasoningId?t.reasoningId:String(++this.nextId),buffer:new e},this.blocks.set(t.reasoningId,r)}const o=r.buffer.append(i);o&&this.publish(r.id,o)}end(e){const s=this.blocks.get(e);this.blocks.delete(e);const t=s?.buffer.finish();s&&t&&this.publish(s.id,t)}finish(){for(const e of this.blocks.keys())this.end(e);this.reset()}reset(){this.blocks.clear(),this.overflowed=!1}}
|