Provide an explicit SDK API for built-in shell tool startup env/scripts · Issue #1911 · github/copilot-sdk · GitHub
Skip to content

Provide an explicit SDK API for built-in shell tool startup env/scripts #1911

Closed

Description

@anthonykim1

I am trying to make Python environment auto-activation work when VS Code Agent Host Copilot runs commands through the SDK-owned built-in shell tool.

Context: microsoft/vscode#323164

The VS Code Python environment extension writes shell-specific activation snippets into env vars such as:

VSCODE_PYTHON_BASH_ACTIVATE
VSCODE_PYTHON_ZSH_ACTIVATE
VSCODE_PYTHON_PWSH_ACTIVATE

VS Code terminal shell integration evaluates these during shell startup. The SDK built-in shell tool does not have an equivalent host-provided startup hook. It uses fresh shell process execution for commands, so startup material must be applied on every shell-tool spawn for child processes like python to inherit the activated environment.

Current generated RPC fields only cover presets/flags:

export interface SessionOpenOptions {
  shellInitProfile?: string;
  shellProcessFlags?: string[];
}

export interface SessionUpdateOptionsParams {
  shellInitProfile?: string;
  shellProcessFlags?: string[];
}

shellInitProfile: "non-interactive" plus BASH_ENV can help for bash, but it is bash-specific and indirect. There is no cross-shell SDK API for host-provided startup env/scripts for built-in shell tool child processes.

Ask

Please add an explicit per-session shell startup config for built-in shell tools, accepted on session create and update:

export interface ShellOptions {
  /** Existing shell init preset behavior. */
  initProfile?: "none" | "non-interactive";

  /** Existing per-shell process flags. */
  processFlags?: string[];

  /** Env applied to built-in shell tool child processes. `null` unsets/removes. */
  env?: Record<string, string | null>;

  /** Explicit host-provided startup scripts, sourced before shell-tool commands. */
  initScripts?: ShellInitScript[];
}

export interface ShellInitScript {
  shell: "bash" | "powershell";
  path: string;
}

export interface SessionConfigBase {
  shell?: ShellOptions;
}

export interface SessionUpdateOptionsParams {
  shell?: ShellOptions;
}

Example:

const session = await client.createSession({
  shell: {
    initProfile: "non-interactive",
    env: {
      VSCODE_PYTHON_BASH_ACTIVATE: "...",
      VSCODE_PYTHON_PWSH_ACTIVATE: "...",
    },
    initScripts: [
      { shell: "bash", path: "/path/to/generated-python-bash-init.sh" },
      { shell: "powershell", path: "C:\\path\\to\\generated-python-pwsh-init.ps1" },
    ],
  },
});

And later, if the selected interpreter changes:

await session.rpc.options.update({
  shell: {
    env: { VSCODE_PYTHON_BASH_ACTIVATE: "..." },
    initScripts: [{ shell: "bash", path: "/path/to/generated-python-bash-init.sh" }],
  },
});

Desired semantics:

  • Per-session, not CopilotClient-level.
  • Applied to every built-in shell tool command spawn.
  • Explicitly host-provided; not automatic user rc/profile sourcing.
  • Updatable mid-session, because VS Code's selected interpreter can change while a session is live.
  • Exported env should reach child processes (python). Shell functions/hooks only need to affect the shell process itself unless the shell explicitly exports them.

Happy to put up a PR.

/cc @MRayermannMSFT

Activity

  1. anthonykim1 commented on Jul 5, 2026

    @anthonykim1
    Author

    Thinking a bit more about the API shape here, I think the useful abstraction is probably a dedicated shell-tool startup config, separate from CopilotClientOptions.env.

    Today CopilotClientOptions.env configures the spawned SDK runtime process:

    // dist/types.d.ts
    export interface CopilotClientOptions {
      /**
       * Environment variables to pass to the runtime process.
       * If not set, inherits process.env.
       */
      env?: Record<string, string | undefined>;
    }

    That is useful, but it does not explicitly model startup material for built-in shell tool child processes.

    ShellOptions does not exist today ---> I am proposing it as a new shared shape.

    Current generated RPC shell-related fields are roughly:

    // dist/generated/rpc.d.ts
    export interface SessionOpenOptions {
      shellInitProfile?: string;
      shellProcessFlags?: string[];
    }
    
    export interface SessionUpdateOptionsParams {
      shellInitProfile?: string;
      shellProcessFlags?: string[];
    }

    One possible shared shape could be:

    export interface ShellOptions {
      /** Existing shell init preset behavior. */
      initProfile?: "none" | "non-interactive";
    
      /**
       * Environment variables applied to built-in shell tool child processes.
       * `null` means unset/remove this variable from the shell-tool env.
       */
      env?: Record<string, string | null>;
    
      /**
       * Explicit host-provided startup scripts for built-in shell tool child processes.
       * These are not user rc/profile files unless the host explicitly provides them.
       */
      initScripts?: ShellInitScript[];
    }
    
    export interface ShellInitScript {
      shell: "bash" | "powershell";
      path: string;
    }

    Depending on the intended ownership model, this could be exposed at runtime/client scope:

    // dist/types.d.ts
    export interface CopilotClientOptions {
      shell?: ShellOptions;
    }
    const client = new CopilotClient({
      shell: {
        initProfile: "non-interactive",
        env: {
          VSCODE_PYTHON_BASH_ACTIVATE: "...",
          VSCODE_PYTHON_PWSH_ACTIVATE: "...",
        },
        initScripts: [
          { shell: "bash", path: "/path/to/generated-python-bash-init.sh" },
          { shell: "powershell", path: "C:\\path\\to\\generated-python-pwsh-init.ps1" },
        ],
      },
    });

    Or, if the desired ownership is per SDK session, expose the same shape on session create/update:

    // dist/types.d.ts ergonomic SDK surface
    export interface SessionConfigBase {
      shell?: ShellOptions;
    }
    
    // dist/generated/rpc.d.ts generated RPC surfaces
    export interface SessionOpenOptions {
      shell?: ShellOptions;
    }
    
    export interface SessionUpdateOptionsParams {
      shell?: ShellOptions;
    }

    At session creation:

    const session = await client.createSession({
      shell: {
        initProfile: "non-interactive",
        env: {
          VSCODE_PYTHON_BASH_ACTIVATE: "...",
          VSCODE_PYTHON_PWSH_ACTIVATE: "...",
        },
        initScripts: [
          { shell: "bash", path: "/path/to/generated-python-bash-init.sh" },
          { shell: "powershell", path: "C:\\path\\to\\generated-python-pwsh-init.ps1" },
        ],
      },
    });

    And later, if the selected Python environment changes:

    await session.rpc.options.update({
      shell: {
        env: {
          VSCODE_PYTHON_BASH_ACTIVATE: "...",
        },
        initScripts: [
          { shell: "bash", path: "/path/to/generated-python-bash-init.sh" },
        ],
      },
    });

    The distinction I am trying to make is:

    • CopilotClientOptions.env configures the spawned SDK runtime process.
    • shell.env would configure built-in shell tool child processes.
    • shell.initScripts would be explicit host-provided startup scripts.
    • The SDK/runtime can decide whether shell is client/runtime-scoped, session-scoped, or both, but the API should make that scope explicit.
  2. changed the title [-]Provide ways for built-in shell tool to take session-scoped shell env/scripts[/-] [+]Provide an explicit SDK API for built-in shell tool startup env/scripts[/+] on Jul 5, 2026
  3. MRayermannMSFT commented on Jul 6, 2026

    @MRayermannMSFT
    Contributor
    • I wouldn't elevate shell init/tool related options up to CopilotClient unless they already exist there (my recollection is that they don't). Configuring tools feels like a per-session type of operation. I do not consider the existing env: option on CopilotClient, which can be used to transitively pass env vars to all shell tool sessions, to be a "shell init/tool related option".
    • Was shellProcessFlags not migrated to the proposed interface ShellOptions purposefully? I'd assume it'd be there assuming there isn't some good reason I missed/am not anticipating.
  4. MRayermannMSFT commented on Jul 6, 2026

    @MRayermannMSFT
    Contributor

    Overall, I have no qualms with the gist of the proposal, so long as my two bullets above are addressed.

  5. anthonykim1 commented on Jul 9, 2026

    @anthonykim1
    Author

    I wouldn't elevate shell init/tool related options up to CopilotClient unless they already exist there (my recollection is that they don't).

    Agreed, let's not elevate up to ClientOption. This actually suite us better for activation scripts since they're meant to be more workspace specific rather than a broader client scope.

    Was shellProcessFlags not migrated to the proposed interface ShellOptions purposefully? I'd assume it'd be there assuming there isn't some good reason I missed/am not anticipating.

    Good catch, we should have them for sure. Probably as processFlags?: string[] under ShellOptions
    Will start a PR soon based on the draft and above feedbacks, thanks!

  6. self-assigned this
    on Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions