refactor: extract permission handler base class for code reuse

This commit is contained in:
weishu
2026-01-05 18:04:51 +08:00
parent e8252c601f
commit 908d2f694a
3 changed files with 243 additions and 241 deletions
+38 -117
View File
@@ -7,7 +7,11 @@
import { logger } from "@/ui/logger";
import { ApiSessionClient } from "@/api/apiSession";
import { AgentState } from "@/api/types";
import {
BasePermissionHandler,
type PendingPermissionRequest,
type PermissionCompletion
} from "@/modules/common/permission/BasePermissionHandler";
interface PermissionResponse {
id: string;
@@ -16,25 +20,15 @@ interface PermissionResponse {
reason?: string;
}
interface PendingRequest {
resolve: (value: PermissionResult) => void;
reject: (error: Error) => void;
toolName: string;
input: unknown;
}
interface PermissionResult {
decision: 'approved' | 'approved_for_session' | 'denied' | 'abort';
reason?: string;
}
export class CodexPermissionHandler {
private pendingRequests = new Map<string, PendingRequest>();
private session: ApiSessionClient;
export class CodexPermissionHandler extends BasePermissionHandler<PermissionResponse, PermissionResult> {
constructor(session: ApiSessionClient) {
this.session = session;
this.setupRpcHandler();
super(session);
}
/**
@@ -51,12 +45,7 @@ export class CodexPermissionHandler {
): Promise<PermissionResult> {
return new Promise<PermissionResult>((resolve, reject) => {
// Store the pending request
this.pendingRequests.set(toolCallId, {
resolve,
reject,
toolName,
input
});
this.addPendingRequest(toolCallId, toolName, input, { resolve, reject });
// Send push notification
// this.session.api.push().sendToAllDevices(
@@ -70,117 +59,49 @@ export class CodexPermissionHandler {
// }
// );
// Update agent state with pending request
this.session.updateAgentState((currentState) => ({
...currentState,
requests: {
...currentState.requests,
[toolCallId]: {
tool: toolName,
arguments: input,
createdAt: Date.now()
}
}
}));
logger.debug(`[Codex] Permission request sent for tool: ${toolName} (${toolCallId})`);
});
}
/**
* Setup RPC handler for permission responses
* Handle permission responses
*/
private setupRpcHandler(): void {
this.session.rpcHandlerManager.registerHandler<PermissionResponse, void>(
'permission',
async (response) => {
// console.log(`[Codex] Permission response received:`, response);
const pending = this.pendingRequests.get(response.id);
if (!pending) {
logger.debug('[Codex] Permission request not found or already resolved');
return;
}
// Remove from pending
this.pendingRequests.delete(response.id);
// Resolve the permission request
const reason = typeof response.reason === 'string' ? response.reason : undefined;
const result: PermissionResult = response.approved
? {
decision: response.decision === 'approved_for_session' ? 'approved_for_session' : 'approved',
reason
}
: {
decision: response.decision === 'denied' ? 'denied' : 'abort',
reason
};
pending.resolve(result);
// Move request to completed in agent state
this.session.updateAgentState((currentState) => {
const request = currentState.requests?.[response.id];
if (!request) return currentState;
// console.log(`[Codex] Permission ${response.approved ? 'approved' : 'denied'} for ${pending.toolName}`);
const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
let res = {
...currentState,
requests: remainingRequests,
completedRequests: {
...currentState.completedRequests,
[response.id]: {
...request,
completedAt: Date.now(),
status: response.approved ? 'approved' : 'denied',
decision: result.decision,
reason: result.reason
}
}
} satisfies AgentState;
// console.log(`[Codex] Updated agent state:`, res);
return res;
});
logger.debug(`[Codex] Permission ${response.approved ? 'approved' : 'denied'} for ${pending.toolName}`);
protected handlePermissionResponse(
response: PermissionResponse,
pending: PendingPermissionRequest<PermissionResult>
): PermissionCompletion {
const reason = typeof response.reason === 'string' ? response.reason : undefined;
const result: PermissionResult = response.approved
? {
decision: response.decision === 'approved_for_session' ? 'approved_for_session' : 'approved',
reason
}
);
: {
decision: response.decision === 'denied' ? 'denied' : 'abort',
reason
};
pending.resolve(result);
logger.debug(`[Codex] Permission ${response.approved ? 'approved' : 'denied'} for ${pending.toolName}`);
return {
status: response.approved ? 'approved' : 'denied',
decision: result.decision,
reason: result.reason
};
}
protected handleMissingPendingResponse(_response: PermissionResponse): void {
logger.debug('[Codex] Permission request not found or already resolved');
}
/**
* Reset state for new sessions
*/
reset(): void {
// Reject all pending requests
for (const [id, pending] of this.pendingRequests.entries()) {
pending.reject(new Error('Session reset'));
}
this.pendingRequests.clear();
// Clear requests in agent state
this.session.updateAgentState((currentState) => {
const pendingRequests = currentState.requests || {};
const completedRequests = { ...currentState.completedRequests };
// Move all pending to completed as canceled
for (const [id, request] of Object.entries(pendingRequests)) {
completedRequests[id] = {
...request,
completedAt: Date.now(),
status: 'canceled',
reason: 'Session reset'
};
}
return {
...currentState,
requests: {},
completedRequests
};
this.cancelPendingRequests({
completedReason: 'Session reset',
rejectMessage: 'Session reset'
});
logger.debug('[Codex] Permission handler reset');