Revert background agent commits (#4479)

This commit is contained in:
Tommaso Sciortino 2025-07-18 17:28:40 -07:00 committed by GitHub
parent 5b7b6fe608
commit 4dbd9f30b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 3 additions and 871 deletions

58
package-lock.json generated
View File

@ -916,10 +916,6 @@
"resolved": "packages/core",
"link": true
},
"node_modules/@google/gemini-cli-examples": {
"resolved": "packages/examples",
"link": true
},
"node_modules/@google/genai": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.9.0.tgz",
@ -5661,19 +5657,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-tsconfig": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
"integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@ -9207,16 +9190,6 @@
"node": ">=4"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/restore-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
@ -10517,26 +10490,6 @@
"dev": true,
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.20.3",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz",
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -11907,17 +11860,6 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"packages/examples": {
"name": "@google/gemini-cli-examples",
"version": "0.1.0",
"dependencies": {
"@modelcontextprotocol/sdk": "1.15.1",
"zod": "^3.23.8"
},
"devDependencies": {
"tsx": "^4.16.2"
}
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.0.1",

View File

@ -382,7 +382,6 @@ export async function loadCliConfig(
toolCallCommand: settings.toolCallCommand,
mcpServerCommand: settings.mcpServerCommand,
mcpServers,
backgroundAgents: settings.backgroundAgents,
userMemory: memoryContent,
geminiMdFileCount: fileCount,
approvalMode: argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT,

View File

@ -64,7 +64,6 @@ export interface Settings {
toolCallCommand?: string;
mcpServerCommand?: string;
mcpServers?: Record<string, MCPServerConfig>;
backgroundAgents?: Record<string, MCPServerConfig>;
allowMCPServers?: string[];
excludeMCPServers?: string[];
showMemoryUsage?: boolean;

View File

@ -96,7 +96,6 @@ describe('CommandService', () => {
mockConfig = {
getIdeMode: vi.fn(),
getCheckpointingEnabled: vi.fn(),
getBackgroundAgentManager: vi.fn(),
} as unknown as Mocked<Config>;
vi.mocked(ideCommand).mockReturnValue(null);
vi.mocked(restoreCommand).mockReturnValue(null);

View File

@ -7,7 +7,6 @@
import { Config } from '@google/gemini-cli-core';
import { SlashCommand } from '../ui/commands/types.js';
import { memoryCommand } from '../ui/commands/memoryCommand.js';
import { backgroundCommand } from '../ui/commands/backgroundCommand.js';
import { helpCommand } from '../ui/commands/helpCommand.js';
import { clearCommand } from '../ui/commands/clearCommand.js';
import { corgiCommand } from '../ui/commands/corgiCommand.js';
@ -34,7 +33,6 @@ const loadBuiltInCommands = async (
const allCommands = [
aboutCommand,
authCommand,
backgroundCommand(config),
bugCommand,
chatCommand,
clearCommand,

View File

@ -9,7 +9,6 @@ import { render } from 'ink-testing-library';
import { AppWrapper as App } from './App.js';
import {
Config as ServerConfig,
BackgroundAgentManager,
MCPServerConfig,
ApprovalMode,
ToolRegistry,
@ -52,7 +51,6 @@ interface MockServerConfig {
getSandbox: Mock<() => SandboxConfig | undefined>;
getTargetDir: Mock<() => string>;
getToolRegistry: Mock<() => ToolRegistry>; // Use imported ToolRegistry type
getBackgroundAgentManager: Mock<() => BackgroundAgentManager>;
getDebugMode: Mock<() => boolean>;
getQuestion: Mock<() => string | undefined>;
getFullContext: Mock<() => boolean>;
@ -119,7 +117,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
getSandbox: vi.fn(() => opts.sandbox),
getTargetDir: vi.fn(() => opts.targetDir || '/test/dir'),
getToolRegistry: vi.fn(() => ({}) as ToolRegistry), // Simple mock
getBackgroundAgentManager: vi.fn(() => new BackgroundAgentManager([])),
getDebugMode: vi.fn(() => opts.debugMode || false),
getQuestion: vi.fn(() => opts.question),
getFullContext: vi.fn(() => opts.fullContext ?? false),

View File

@ -1,262 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { SlashCommand, CommandContext } from './types.js';
import {
Config,
BackgroundAgentMessage,
partListUnionToString,
} from '@google/gemini-cli-core';
const MAX_STATUS_MESSAGE_LENGTH = 100;
function toMessageString(message?: BackgroundAgentMessage): string {
return partListUnionToString(message?.parts ?? []).trim();
}
function toOneliner(input: string, maxlength: number) {
let output = input.replace(/\r?\n|\r/g, ' ');
if (output.length > maxlength) {
output = output.substring(0, maxlength) + '...';
}
return output;
}
function getActiveAgent(context: CommandContext) {
const agent =
context.services.config?.getBackgroundAgentManager()?.activeAgent;
if (!agent) {
throw Error('There is no active background agent.');
}
return agent;
}
function addClientHistory(context: CommandContext, text: string) {
context.services.config!.getGeminiClient().addHistory({
role: 'user',
parts: [{ text }],
});
context.services.config!.getGeminiClient().addHistory({
role: 'model',
parts: [{ text: 'Got it.' }],
});
}
const startSubcommand: SlashCommand = {
name: 'start',
description:
'Start a new task with the provided prompt. Usage: /bg start <prompt>',
action: async (context, args) => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content: 'The `start` command requires a prompt.',
};
}
const agent = getActiveAgent(context);
const task = await agent.startTask(args);
addClientHistory(
context,
`I started a background task with id '${task.id}' and prompt:\n${args}`,
);
return {
type: 'message',
messageType: 'info',
content: `Started background task with id '${task.id}' and prompt:\n${args}`,
};
},
};
const stopSubcommand: SlashCommand = {
name: 'stop',
description: 'Stops a running task. Usage: /bg stop <task_id>',
action: async (context, args) => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content: 'The `stop` command requires a task id.',
};
}
const agent = getActiveAgent(context);
await agent.cancelTask(args);
addClientHistory(context, `I canceled the background task with id ${args}`);
return {
type: 'message',
messageType: 'info',
content: `Stopped background task with id ${args}.`,
};
},
};
const listSubcommand: SlashCommand = {
name: 'list',
description: 'List all tasks',
action: async (context, args) => {
if (args && args.trim() !== '') {
return {
type: 'message',
messageType: 'error',
content: 'The `list` command takes no arguments.',
};
}
const agent = getActiveAgent(context);
const tasks = await agent.listTasks();
let content: string;
if (tasks.length === 0) {
content = 'No background tasks found.';
} else {
const taskList = tasks
.map((task) => {
const shortStatus = toOneliner(
toMessageString(task.status.message),
MAX_STATUS_MESSAGE_LENGTH,
);
return ` - ${task.id}: (${task.status.state}) ${shortStatus}`;
})
.join('\n');
content = `Background tasks:\n${taskList}`;
}
return {
type: 'message',
messageType: 'info',
content,
};
},
};
const getSubcommand: SlashCommand = {
name: 'get',
description: 'View a task. Usage: /bg get <task_id>',
action: async (context, args) => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content: 'The `get` command requires a task id.',
};
}
const agent = getActiveAgent(context);
const task = await agent.getTask(args);
const content = `Task Details for ${task.id}:
Status: (${task.status.state}) ${toMessageString(task.status.message)}}`;
return {
type: 'message',
messageType: 'info',
content,
};
},
};
const logsSubcommand: SlashCommand = {
name: 'logs',
description: "View a task's recent logs. Usage: /bg log <task_id>",
action: async (context, args) => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content: 'The `log` command requires a task id.',
};
}
const agent = getActiveAgent(context);
const task = await agent.getTask(args, 5);
const contents = [
`Task logs for ${task.id}. status: (${task.status.state})`,
];
(task.history ?? []).forEach((message) => {
contents.push(toMessageString(message));
});
return {
type: 'message',
messageType: 'info',
content: contents.join('\n\n'),
};
},
};
const messageSubcommand: SlashCommand = {
name: 'message',
description:
'Send a message to a task. Usage: /bg message <task_id> <message>',
action: async (context, args) => {
if (!args || args.trim() === '' || !args.trim().includes(' ')) {
return {
type: 'message',
messageType: 'error',
content: 'The `message` command requires a task id and a message.',
};
}
const firstSpaceIndex = args.indexOf(' ');
const id = args.substring(0, firstSpaceIndex);
const message = args.substring(firstSpaceIndex + 1);
const agent = getActiveAgent(context);
await agent.messageTask(id, message);
addClientHistory(
context,
`I sent a message to the background task with id '${id}':\n${message}`,
);
return {
type: 'message',
messageType: 'info',
content: `Sent a message to the background task with id '${id}':\n${message}`,
};
},
};
const deleteSubcommand: SlashCommand = {
name: 'delete',
description: 'Deletes a task. Usage: /bg delete <task_id>',
action: async (context, args) => {
if (!args) {
return {
type: 'message',
messageType: 'error',
content: 'The `delete` command requires a task id.',
};
}
const agent = getActiveAgent(context);
await agent.deleteTask(args);
addClientHistory(context, `I deleted the background task with id ${args}`);
return {
type: 'message',
messageType: 'info',
content: `Task ${args} deleted.`,
};
},
};
export const backgroundCommand = (
config: Config | null,
): SlashCommand | null => {
if (!config?.getBackgroundAgentManager()?.activeAgent) {
return null;
}
return {
name: 'background',
altName: 'bg',
description: "Commands for managing the background agent's tasks",
subCommands: [
startSubcommand,
stopSubcommand,
listSubcommand,
getSubcommand,
logsSubcommand,
messageSubcommand,
deleteSubcommand,
],
};
};

View File

@ -1,126 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { MCPServerConfig } from '../config/config.js';
import { connectToMcpServer, discoverTools } from '../tools/mcp-client.js';
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
import {
BackgroundAgentTasksResponseSchema,
BackgroundAgentTaskResponseSchema,
BackgroundAgentTask,
} from './types.js';
export async function loadBackgroundAgent(
name: string,
config: MCPServerConfig,
debugMode: boolean,
): Promise<BackgroundAgent> {
const server = await connectToMcpServer(name, config, debugMode);
try {
const tools = await discoverTools(name, config, server);
return new BackgroundAgent(name, tools);
} catch (error) {
await server.close();
throw error;
}
}
export class BackgroundAgent {
readonly startTaskTool: DiscoveredMCPTool;
readonly getTaskTool: DiscoveredMCPTool;
readonly listTasksTool: DiscoveredMCPTool;
readonly messageTaskTool: DiscoveredMCPTool;
readonly deleteTaskTool: DiscoveredMCPTool;
readonly cancelTaskTool: DiscoveredMCPTool;
constructor(
readonly serverName: string,
tools: DiscoveredMCPTool[],
) {
const getToolOrFail = (name: string): DiscoveredMCPTool => {
for (const tool of tools) {
if (tool.serverToolName === name) {
return tool;
}
}
throw new Error(`missing expected tool: ${name}`);
};
this.startTaskTool = getToolOrFail('startTask');
this.getTaskTool = getToolOrFail('getTask');
this.listTasksTool = getToolOrFail('listTasks');
this.messageTaskTool = getToolOrFail('messageTask');
this.deleteTaskTool = getToolOrFail('deleteTask');
this.cancelTaskTool = getToolOrFail('cancelTask');
}
async startTask(prompt: string): Promise<BackgroundAgentTask> {
const resp = await this.callTool(this.startTaskTool, {
prompt: {
role: 'user',
parts: [{ text: prompt }],
},
});
const taskResp = await BackgroundAgentTaskResponseSchema.parseAsync(resp);
return taskResp.structuredContent;
}
async getTask(
id: string,
historyLength?: number,
): Promise<BackgroundAgentTask> {
const resp = await this.callTool(this.getTaskTool, {
id,
historyLength,
});
const taskResp = await BackgroundAgentTaskResponseSchema.parseAsync(resp);
return taskResp.structuredContent;
}
async listTasks(): Promise<BackgroundAgentTask[]> {
const resp = await this.callTool(this.listTasksTool, {});
const tasksResp = await BackgroundAgentTasksResponseSchema.parseAsync(resp);
return tasksResp.structuredContent;
}
async messageTask(id: string, message: string) {
await this.callTool(this.messageTaskTool, {
id,
message: {
role: 'user',
parts: [{ text: message }],
},
});
}
async deleteTask(id: string) {
await this.callTool(this.deleteTaskTool, { id });
}
async cancelTask(id: string) {
await this.callTool(this.cancelTaskTool, { id });
}
private async callTool(
tool: DiscoveredMCPTool,
params: Record<string, unknown>,
): Promise<Record<string, unknown>> {
const { llmContent: parts } = await tool.execute(params);
if (
!Array.isArray(parts) ||
parts.length !== 1 ||
typeof parts[0] !== 'object' ||
parts[0]?.functionResponse?.response === undefined
) {
throw new Error('Expected exactly one part with a functionResponse');
}
const resp = parts[0].functionResponse.response;
if ('isError' in resp && resp.isError) {
throw new Error(`Error calling ${tool.displayName}: ${resp}`);
}
return resp;
}
}

View File

@ -1,40 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { MCPServerConfig } from '../config/config.js';
import { BackgroundAgent, loadBackgroundAgent } from './backgroundAgent.js';
export async function loadBackgroundAgentManager(
backgroundAgentConfigs: Record<string, MCPServerConfig> | undefined,
debugMode: boolean,
): Promise<BackgroundAgentManager> {
const agents = await Promise.all(
Object.entries(backgroundAgentConfigs ?? {}).map(([name, config]) =>
loadBackgroundAgent(name, config, debugMode).catch((error) => {
console.error(`Error loading background agent '${name}': ${error}`);
return null;
}),
),
).then((agents) => agents.filter((agent) => agent !== null));
return new BackgroundAgentManager(agents);
}
export class BackgroundAgentManager {
// The active agent. May be empty if none are confgured.
activeAgent?: BackgroundAgent;
constructor(readonly backgroundAgents: BackgroundAgent[]) {
if (backgroundAgents.length !== 0) {
this.activeAgent = backgroundAgents[0];
}
}
setActiveAgentByName(name: string) {
this.activeAgent = this.backgroundAgents.find(
(agent) => agent.serverName === name,
);
}
}

View File

@ -1,107 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { z } from 'zod';
import { Outcome, Language, FunctionResponseScheduling } from '@google/genai';
// Should conform to Part in @google/genai
export const PartSchema = z.object({
videoMetadata: z
.object({
fps: z.number().optional(),
endOffset: z.string().optional(),
startOffset: z.string().optional(),
})
.optional(),
thought: z.boolean().optional(),
inlineData: z
.object({
displayName: z.string().optional(),
data: z.string(),
mimeType: z.string(),
})
.optional(),
fileData: z
.object({
displayName: z.string().optional(),
fileUri: z.string(),
mimeType: z.string(),
})
.optional(),
thoughtSignature: z.string().optional(),
codeExecutionResult: z
.object({
outcome: z.nativeEnum(Outcome).optional(),
output: z.string().optional(),
})
.optional(),
executableCode: z
.object({
code: z.string().optional(),
language: z.nativeEnum(Language).optional(),
})
.optional(),
functionCall: z
.object({
id: z.string().optional(),
args: z.record(z.unknown()).optional(),
name: z.string(),
})
.optional(),
functionResponse: z
.object({
willContinue: z.boolean().optional(),
scheduling: z.nativeEnum(FunctionResponseScheduling).optional(),
id: z.string().optional(),
name: z.string(),
response: z.record(z.unknown()).optional(),
})
.optional(),
text: z.string().optional(),
});
export const BackgroundAgentMessageSchema = z.object({
role: z.enum(['user', 'agent']).describe('The role of the sender.'),
parts: z.array(PartSchema).describe('The parts of the message.'),
});
export const BackgroundAgentTaskStatusSchema = z.object({
state: z.enum([
'submitted',
'working',
'input-required',
'completed',
'failed',
]),
message: BackgroundAgentMessageSchema.describe(
'Message describing the state of the task.',
).optional(),
});
export const BackgroundAgentTaskSchema = z.object({
id: z.string().describe('The id of the task. Must match `[a-zA-Z0-9.-_]+`'),
status: BackgroundAgentTaskStatusSchema.describe(
'The current status of the task.',
),
history: z
.array(BackgroundAgentMessageSchema)
.describe('Recent history of messages associated with this task')
.optional(),
});
export type BackgroundAgentMessage = z.infer<
typeof BackgroundAgentMessageSchema
>;
export type BackgroundAgentTask = z.infer<typeof BackgroundAgentTaskSchema>;
export const BackgroundAgentTaskResponseSchema = z.object({
structuredContent: BackgroundAgentTaskSchema,
});
export const BackgroundAgentTasksResponseSchema = z.object({
structuredContent: z.array(BackgroundAgentTaskSchema),
});

View File

@ -45,10 +45,6 @@ import {
DEFAULT_GEMINI_FLASH_MODEL,
} from './models.js';
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
import {
BackgroundAgentManager,
loadBackgroundAgentManager,
} from '../background/backgroundManager.js';
export enum ApprovalMode {
DEFAULT = 'default',
@ -131,7 +127,6 @@ export interface ConfigParameters {
toolCallCommand?: string;
mcpServerCommand?: string;
mcpServers?: Record<string, MCPServerConfig>;
backgroundAgents?: Record<string, MCPServerConfig>;
userMemory?: string;
geminiMdFileCount?: number;
approvalMode?: ApprovalMode;
@ -163,7 +158,6 @@ export interface ConfigParameters {
export class Config {
private toolRegistry!: ToolRegistry;
private backgroundAgentManager?: BackgroundAgentManager;
private readonly sessionId: string;
private contentGeneratorConfig!: ContentGeneratorConfig;
private readonly embeddingModel: string;
@ -178,7 +172,6 @@ export class Config {
private readonly toolCallCommand: string | undefined;
private readonly mcpServerCommand: string | undefined;
private readonly mcpServers: Record<string, MCPServerConfig> | undefined;
private readonly backgroundAgents?: Record<string, MCPServerConfig>;
private userMemory: string;
private geminiMdFileCount: number;
private approvalMode: ApprovalMode;
@ -231,7 +224,6 @@ export class Config {
this.toolCallCommand = params.toolCallCommand;
this.mcpServerCommand = params.mcpServerCommand;
this.mcpServers = params.mcpServers;
this.backgroundAgents = params.backgroundAgents;
this.userMemory = params.userMemory ?? '';
this.geminiMdFileCount = params.geminiMdFileCount ?? 0;
this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT;
@ -289,10 +281,6 @@ export class Config {
if (this.getCheckpointingEnabled()) {
await this.getGitService();
}
this.backgroundAgentManager = await loadBackgroundAgentManager(
this.backgroundAgents,
this.debugMode,
);
this.toolRegistry = await this.createToolRegistry();
}
@ -418,10 +406,6 @@ export class Config {
return this.mcpServers;
}
getBackgroundAgentManager(): BackgroundAgentManager | undefined {
return this.backgroundAgentManager;
}
getUserMemory(): string {
return this.userMemory;
}

View File

@ -24,9 +24,6 @@ export * from './code_assist/oauth2.js';
export * from './code_assist/server.js';
export * from './code_assist/types.js';
export * from './background/types.js';
export * from './background/backgroundManager.js';
// Export utilities
export * from './utils/paths.js';
export * from './utils/schemaValidator.js';

View File

@ -113,7 +113,9 @@ export class DiscoveredMCPTool extends BaseTool<ToolParams, ToolResult> {
args: params,
},
];
const responseParts = await this.mcpTool.callTool(functionCalls);
const responseParts: Part[] = await this.mcpTool.callTool(functionCalls);
return {
llmContent: responseParts,
returnDisplay: getStringifiedResultForDisplay(responseParts),

View File

@ -1,16 +0,0 @@
# Demo Background Agent
A pretend background agent that does not actually process tasks in the background. Configure in your settings.json with:
```javascript
"backgroundAgents": {
"demo-background-agent": {
"command": "npm",
"args": [
"run",
"start:demo-background-agent",
"--workspace=@google/gemini-cli-examples"
]
}
},
```

View File

@ -1,217 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const BackgroundAgentMessageSchema = z.object({
role: z.enum(['user', 'agent']),
parts: z.array(z.any()),
});
const BackgroundAgentTaskStatusSchema = z.object({
state: z.enum([
'submitted',
'working',
'input-required',
'completed',
'canceled',
'failed',
]),
message: BackgroundAgentMessageSchema.optional(),
});
const BackgroundAgentTaskSchema = z.object({
id: z.string(),
status: BackgroundAgentTaskStatusSchema,
history: z.array(BackgroundAgentMessageSchema).optional(),
});
type BackgroundAgentTask = z.infer<typeof BackgroundAgentTaskSchema>;
const server = new McpServer({
name: 'demo-background-agent',
version: '1.0.0',
});
const idToTask = new Map<string, BackgroundAgentTask>();
server.registerTool(
'startTask',
{
title: 'Start a new task',
description: 'Launches a new task asynchronously.',
inputSchema: { prompt: BackgroundAgentMessageSchema },
outputSchema: BackgroundAgentTaskSchema.shape,
},
({ prompt }) => {
const task: BackgroundAgentTask = {
id: crypto.randomUUID(),
status: {
state: 'submitted',
message: prompt,
},
history: [],
};
idToTask.set(task.id, task);
return {
content: [],
structuredContent: task,
};
},
);
server.registerTool(
'getTask',
{
title: 'Get a task',
inputSchema: { id: z.string() },
outputSchema: BackgroundAgentTaskSchema.shape,
},
({ id }) => {
const task = idToTask.get(id);
if (!task) {
return {
isError: true,
content: [
{
type: 'text',
text: 'No such task',
},
],
};
}
return {
content: [],
structuredContent: task,
};
},
);
server.registerTool(
'listTasks',
{
title: 'Lists tasks',
outputSchema: {
tasks: z.array(BackgroundAgentTaskSchema),
},
},
() => {
const out = {
tasks: Array.from(idToTask.values()),
};
return {
content: [],
structuredContent: out,
};
},
);
server.registerTool(
'messageTask',
{
title: 'Send a message to a task',
inputSchema: {
id: z.string(),
message: BackgroundAgentMessageSchema,
},
},
({ id, message }) => {
const task = idToTask.get(id);
if (!task) {
return {
isError: true,
content: [
{
type: 'text',
text: 'No such task',
},
],
};
}
task.history?.push(message);
task.status.message = message;
const statuses = BackgroundAgentTaskStatusSchema.shape.state.options;
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
task.status.state = randomStatus;
return {
content: [],
};
},
);
server.registerTool(
'deleteTask',
{
title: 'Delete a task',
inputSchema: { id: z.string() },
},
({ id }) => {
const task = idToTask.get(id);
if (!task) {
return {
isError: true,
content: [
{
type: 'text',
text: 'No such task',
},
],
};
}
idToTask.delete(id);
return {
content: [
{
type: 'text',
text: 'Task deleted',
},
],
};
},
);
server.registerTool(
'cancelTask',
{
title: 'Cancels a task',
inputSchema: { id: z.string() },
},
({ id }) => {
const task = idToTask.get(id);
if (!task) {
return {
isError: true,
content: [
{
type: 'text',
text: 'No such task',
},
],
};
}
task.status.state = 'canceled';
return {
content: [
{
type: 'text',
text: 'Task cancelled',
},
],
};
},
);
const transport = new StdioServerTransport();
await server.connect(transport);

View File

@ -1,17 +0,0 @@
{
"name": "@google/gemini-cli-examples",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"start:demo-background-agent": "tsx background_agent/demo-background-agent.ts",
"build": "echo 'nothing to build'"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.15.1",
"zod": "^3.23.8"
},
"devDependencies": {
"tsx": "^4.16.2"
}
}