File size: 2,531 Bytes
7421850
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {
  type AgentLoopContext,
  ShellExecutionService,
  ShellTool,
  type Config as CoreConfig,
} from '@google/gemini-cli-core';
import type {
  AgentShell,
  AgentShellResult,
  AgentShellOptions,
} from './types.js';

/**
 * SDK implementation of {@link AgentShell} that executes commands via the
 * core ShellExecutionService, subject to the agent's security policies.
 *
 * Commands that require interactive confirmation will be rejected since
 * no interactive session is available in headless SDK mode.
 *
 * @remarks In this implementation, stderr is combined into stdout by the
 * underlying ShellExecutionService. As a result, the stderr field of the
 * returned {@link AgentShellResult} will be empty, and both output and
 * stdout will contain the combined output.
 */
export class SdkAgentShell implements AgentShell {
  constructor(private readonly config: CoreConfig) {}

  async exec(
    command: string,
    options?: AgentShellOptions,
  ): Promise<AgentShellResult> {
    const cwd = options?.cwd || this.config.getWorkingDir();
    const abortController = new AbortController();

    // Use ShellTool to check policy
    const loopContext: AgentLoopContext = this.config;
    const shellTool = new ShellTool(this.config, loopContext.messageBus);
    try {
      const invocation = shellTool.build({
        command,
        dir_path: cwd,
      });

      const confirmation = await invocation.shouldConfirmExecute(
        abortController.signal,
      );
      if (confirmation) {
        throw new Error(
          'Command execution requires confirmation but no interactive session is available.',
        );
      }
    } catch (error) {
      return {
        output: '',
        stdout: '',
        stderr: '',
        exitCode: 1,
        error: error instanceof Error ? error : new Error(String(error)),
      };
    }

    const handle = await ShellExecutionService.execute(
      command,
      cwd,
      () => {}, // No-op output event handler for now
      abortController.signal,
      false, // shouldUseNodePty: false for headless execution
      this.config.getShellExecutionConfig(),
    );

    const result = await handle.result;

    return {
      output: result.output,
      stdout: result.output, // ShellExecutionService combines stdout/stderr usually
      stderr: '', // ShellExecutionService currently combines, so stderr is empty or mixed
      exitCode: result.exitCode,
    };
  }
}