/ remote / remotePermissionBridge.ts
remotePermissionBridge.ts
 1  import { randomUUID } from 'crypto'
 2  import type { SDKControlPermissionRequest } from '../entrypoints/sdk/controlTypes.js'
 3  import type { Tool } from '../Tool.js'
 4  import type { AssistantMessage } from '../types/message.js'
 5  import { jsonStringify } from '../utils/slowOperations.js'
 6  
 7  /**
 8   * Create a synthetic AssistantMessage for remote permission requests.
 9   * The ToolUseConfirm type requires an AssistantMessage, but in remote mode
10   * we don't have a real one — the tool use runs on the CCR container.
11   */
12  export function createSyntheticAssistantMessage(
13    request: SDKControlPermissionRequest,
14    requestId: string,
15  ): AssistantMessage {
16    return {
17      type: 'assistant',
18      uuid: randomUUID(),
19      message: {
20        id: `remote-${requestId}`,
21        type: 'message',
22        role: 'assistant',
23        content: [
24          {
25            type: 'tool_use',
26            id: request.tool_use_id,
27            name: request.tool_name,
28            input: request.input,
29          },
30        ],
31        model: '',
32        stop_reason: null,
33        stop_sequence: null,
34        container: null,
35        context_management: null,
36        usage: {
37          input_tokens: 0,
38          output_tokens: 0,
39          cache_creation_input_tokens: 0,
40          cache_read_input_tokens: 0,
41        },
42      } as AssistantMessage['message'],
43      requestId: undefined,
44      timestamp: new Date().toISOString(),
45    }
46  }
47  
48  /**
49   * Create a minimal Tool stub for tools that aren't loaded locally.
50   * This happens when the remote CCR has tools (e.g., MCP tools) that the
51   * local CLI doesn't know about. The stub routes to FallbackPermissionRequest.
52   */
53  export function createToolStub(toolName: string): Tool {
54    return {
55      name: toolName,
56      inputSchema: {} as Tool['inputSchema'],
57      isEnabled: () => true,
58      userFacingName: () => toolName,
59      renderToolUseMessage: (input: Record<string, unknown>) => {
60        const entries = Object.entries(input)
61        if (entries.length === 0) return ''
62        return entries
63          .slice(0, 3)
64          .map(([key, value]) => {
65            const valueStr =
66              typeof value === 'string' ? value : jsonStringify(value)
67            return `${key}: ${valueStr}`
68          })
69          .join(', ')
70      },
71      call: async () => ({ data: '' }),
72      description: async () => '',
73      prompt: () => '',
74      isReadOnly: () => false,
75      isMcp: false,
76      needsPermissions: () => true,
77    } as unknown as Tool
78  }