diff --git a/packages/cli/package.json b/packages/cli/package.json index 3cbb97a1..c506bb65 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -8,7 +8,7 @@ "gemini-code": "dist/index.js" }, "scripts": { - "build": "tsc --build && touch dist/.last_build", + "build": "../../scripts/build_package.sh", "clean": "rm -rf dist", "start": "node dist/gemini.js", "debug": "node --inspect-brk dist/gemini.js", diff --git a/packages/server/package.json b/packages/server/package.json index b1f862cf..1b0e31c8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -6,7 +6,7 @@ "main": "dist/index.js", "scripts": { "start": "node dist/src/index.js", - "build": "tsc --build", + "build": "../../scripts/build_package.sh", "clean": "rm -rf dist", "lint": "eslint . --ext .ts,.tsx", "format": "prettier --write .", diff --git a/packages/server/src/config/config.ts b/packages/server/src/config/config.ts index 06092b84..8a49380c 100644 --- a/packages/server/src/config/config.ts +++ b/packages/server/src/config/config.ts @@ -15,9 +15,11 @@ import { GrepTool } from '../tools/grep.js'; import { GlobTool } from '../tools/glob.js'; import { EditTool } from '../tools/edit.js'; import { TerminalTool } from '../tools/terminal.js'; +import { ShellTool } from '../tools/shell.js'; import { WriteFileTool } from '../tools/write-file.js'; import { WebFetchTool } from '../tools/web-fetch.js'; import { ReadManyFilesTool } from '../tools/read-many-files.js'; +import { BaseTool, ToolResult } from '../tools/tools.js'; const DEFAULT_PASSTHROUGH_COMMANDS = ['ls', 'git', 'npm']; @@ -132,17 +134,24 @@ function createToolRegistry(config: Config): ToolRegistry { const registry = new ToolRegistry(); const targetDir = config.getTargetDir(); - const tools = [ + const tools: Array> = [ new LSTool(targetDir), new ReadFileTool(targetDir), new GrepTool(targetDir), new GlobTool(targetDir), new EditTool(targetDir), - new TerminalTool(targetDir, config), new WriteFileTool(targetDir), new WebFetchTool(), // Note: WebFetchTool takes no arguments new ReadManyFilesTool(targetDir), ]; + + // use ShellTool (next-gen TerminalTool) if environment variable is set + if (process.env.SHELL_TOOL) { + tools.push(new ShellTool(targetDir, config)); + } else { + tools.push(new TerminalTool(targetDir, config)); + } + for (const tool of tools) { registry.registerTool(tool); } diff --git a/packages/server/src/tools/shell.json b/packages/server/src/tools/shell.json new file mode 100644 index 00000000..f1ba372e --- /dev/null +++ b/packages/server/src/tools/shell.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "properties": { + "command": { + "description": "The exact bash command or sequence of commands (using ';' or '&&') to execute. Must adhere to usage guidelines. Example: 'npm install && npm run build'", + "type": "string" + }, + "description": { + "description": "Optional: A brief, user-centric explanation of what the command does and why it's being run. Used for logging and confirmation prompts. Example: 'Install project dependencies'", + "type": "string" + }, + "runInBackground": { + "description": "If true, execute the command in the background using '&'. Defaults to false. Use for servers or long tasks.", + "type": "boolean" + } + }, + "required": ["command"] +} diff --git a/packages/server/src/tools/shell.md b/packages/server/src/tools/shell.md new file mode 100644 index 00000000..ce71e42c --- /dev/null +++ b/packages/server/src/tools/shell.md @@ -0,0 +1 @@ +This is a minimal shell tool. diff --git a/packages/server/src/tools/shell.ts b/packages/server/src/tools/shell.ts new file mode 100644 index 00000000..658293b0 --- /dev/null +++ b/packages/server/src/tools/shell.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import path from 'path'; +import fs from 'fs'; +import { Config } from '../config/config.js'; +import { BaseTool, ToolResult } from './tools.js'; + +export interface ShellToolParams { + command: string; + description?: string; +} + +export class ShellTool extends BaseTool { + static Name: string = 'execute_bash_command'; + private readonly rootDirectory: string; + private readonly config: Config; + + constructor(rootDirectory: string, config: Config) { + const toolDisplayName = 'Shell'; + const descriptionUrl = new URL('shell.md', import.meta.url); + const toolDescription = fs.readFileSync(descriptionUrl, 'utf-8'); + const schemaUrl = new URL('shell.json', import.meta.url); + const toolParameterSchema = JSON.parse(fs.readFileSync(schemaUrl, 'utf-8')); + super( + ShellTool.Name, + toolDisplayName, + toolDescription, + toolParameterSchema, + ); + this.config = config; + this.rootDirectory = path.resolve(rootDirectory); + } + + async execute(_params: ShellToolParams): Promise { + return { + llmContent: 'hello', + returnDisplay: 'hello', + }; + } +} diff --git a/scripts/build_package.sh b/scripts/build_package.sh new file mode 100755 index 00000000..ba51b8a5 --- /dev/null +++ b/scripts/build_package.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +if [[ $(pwd) != *"/packages/"* ]]; then + echo "must be invoked from a package directory" + exit 1 +fi + +# clean dist directory +rm -rf dist/* + +# build typescript files +tsc --build + +# copy .{md,json} files (replace -q with -i to see itemized changes) +rsync -am -q --include='*.md' --include='*.json' --include='*/' --exclude='*' ./src/ ./dist/src/ + +# touch dist/.last_build +touch dist/.last_build diff --git a/scripts/start_sandbox.sh b/scripts/start_sandbox.sh index 2146a0c8..b17f1a7e 100755 --- a/scripts/start_sandbox.sh +++ b/scripts/start_sandbox.sh @@ -53,10 +53,13 @@ while [ "$current_dir" != "/" ]; do current_dir=$(dirname "$current_dir") done -# if GEMINI_API_KEY is set, copy into container +# copy GEMINI_API_KEY if [ -n "${GEMINI_API_KEY:-}" ]; then run_args+=(--env GEMINI_API_KEY="$GEMINI_API_KEY"); fi -# pass TERM and COLORTERM to container to maintain terminal colors +# copy SHELL_TOOL to optionally enable shell tool +if [ -n "${SHELL_TOOL:-}" ]; then run_args+=(--env SHELL_TOOL="$SHELL_TOOL"); fi + +# copy TERM and COLORTERM to try to maintain terminal setup if [ -n "${TERM:-}" ]; then run_args+=(--env TERM="$TERM"); fi if [ -n "${COLORTERM:-}" ]; then run_args+=(--env COLORTERM="$COLORTERM"); fi