Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"icon": "assets/copilot.png",
"pricing": "Trial",
"engines": {
"vscode": "^1.109.0-20260121",
"vscode": "^1.109.0-20260124",
"npm": ">=9.0.0",
"node": ">=22.14.0"
},
Expand Down Expand Up @@ -3579,6 +3579,15 @@
"tags": [
"experimental"
]
},
"github.copilot.chat.implementAgent.model": {
"type": "string",
"default": "",
"scope": "resource",
"markdownDescription": "%github.copilot.config.implementAgent.model%",
"tags": [
"experimental"
]
}
}
},
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@
"github.copilot.config.organizationInstructions.enabled": "When enabled, Copilot will load custom instructions defined by your GitHub Organization.",
"github.copilot.config.planAgent.additionalTools": "Additional tools to enable for the Plan agent, on top of built-in tools. Use fully-qualified tool names (e.g., `github/issue_read`, `mcp_server/tool_name`).",
"github.copilot.config.planAgent.model": "Override the language model used by the Plan agent. Leave empty to use the default model.",
"github.copilot.config.implementAgent.model": "Override the language model used by the Implement agent. Leave empty to use the default model.",
"copilot.toolSet.editing.description": "Edit files in your workspace",
"copilot.toolSet.read.description": "Read files in your workspace",
"copilot.toolSet.search.description": "Search files in your workspace",
Expand Down
166 changes: 166 additions & 0 deletions src/extension/agents/vscode-node/implementAgentProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { AGENT_FILE_EXTENSION } from '../../../platform/customInstructions/common/promptTypes';
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
import { ILogService } from '../../../platform/log/common/logService';
import { Disposable } from '../../../util/vs/base/common/lifecycle';

/**
* Complete Implement agent configuration
*/
interface ImplementAgentConfig {
name: string;
description: string;
model?: string;
body: string;
}

/**
* Base Implement agent configuration - embedded from Implement.agent.md
* This avoids runtime file loading and YAML parsing dependencies.
*/
const BASE_IMPLEMENT_AGENT_CONFIG: ImplementAgentConfig = {
name: 'Implement',
description: 'Executes an existing plan',
body: `You are an IMPLEMENTATION AGENT.

You receive a plan that has already been created by the user or a planning agent. Your role is to carry out that plan by executing its steps in order.

Focus on implementation, not planning or redesigning. Follow the plan as written and aim to achieve its intended outcome.

If the plan is unclear or cannot be followed as written, pause and ask for clarification

You are successful when the plan's stated outcome is achieved, with **no unrequested changes**.

## Guidelines
- Follow the plan's steps in sequence
- Complete each step before moving to the next
- Avoid introducing new scope or features
- Limit changes to what is necessary to implement the plan

<stopping_rules>
Stop implementation and hand back to **Plan** if:
- Required information or files are missing
- A step cannot be completed as described
- You need to make a significant decision not covered by the plan
</stopping_rules>

<implement_style_guide>
- Be concise and practical
- Explain actions briefly when helpful
- Summarize changes at the end if appropriate

Your tone should reflect: **"I am implementing the plan."**
</implement_style_guide>`
};

/**
* Builds .agent.md content from a configuration object using string formatting.
*/
export function buildImplementAgentMarkdown(config: ImplementAgentConfig): string {
const lines: string[] = ['---'];

// Simple scalar fields
lines.push(`name: ${config.name}`);
lines.push(`description: ${config.description}`);

// Model (optional)
if (config.model) {
lines.push(`model: ${config.model}`);
}

lines.push('---');
lines.push(config.body);

return lines.join('\n');
}

/**
* Provides the Implement agent dynamically with settings-based customization.
*
* This provider uses an embedded configuration and generates .agent.md content
* with settings-based customization (model override).
*/
export class ImplementAgentProvider extends Disposable implements vscode.ChatCustomAgentProvider {
readonly label = vscode.l10n.t('Implement Agent');

private static readonly CACHE_DIR = 'implement-agent';
private static readonly AGENT_FILENAME = `Implement${AGENT_FILE_EXTENSION}`;

private readonly _onDidChangeCustomAgents = this._register(new vscode.EventEmitter<void>());
readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event;

constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
@IFileSystemService private readonly fileSystemService: IFileSystemService,
@ILogService private readonly logService: ILogService,
) {
super();

// Listen for settings changes to refresh agents
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(ConfigKey.ImplementAgentModel.fullyQualifiedId)) {
this.logService.trace('[ImplementAgentProvider] Settings changed, refreshing agent');
this._onDidChangeCustomAgents.fire();
}
}));
}

async provideCustomAgents(
_context: unknown,
_token: vscode.CancellationToken
): Promise<vscode.ChatResource[]> {
// Build config with settings-based customization
const config = this.buildCustomizedConfig();

// Generate .agent.md content
const content = buildImplementAgentMarkdown(config);

// Write to cache file and return URI
const fileUri = await this.writeCacheFile(content);
return [{ uri: fileUri }];
}

private async writeCacheFile(content: string): Promise<vscode.Uri> {
const cacheDir = vscode.Uri.joinPath(
this.extensionContext.globalStorageUri,
ImplementAgentProvider.CACHE_DIR
);

// Ensure cache directory exists
try {
await this.fileSystemService.stat(cacheDir);
} catch {
await this.fileSystemService.createDirectory(cacheDir);
}

const fileUri = vscode.Uri.joinPath(cacheDir, ImplementAgentProvider.AGENT_FILENAME);
await this.fileSystemService.writeFile(fileUri, new TextEncoder().encode(content));
this.logService.trace(`[ImplementAgentProvider] Wrote agent file: ${fileUri.toString()}`);
return fileUri;
}

private buildCustomizedConfig(): ImplementAgentConfig {
const modelOverride = this.configurationService.getConfig(ConfigKey.ImplementAgentModel);

// Start with base config
const config: ImplementAgentConfig = {
...BASE_IMPLEMENT_AGENT_CONFIG,
};

// Apply model override
if (modelOverride) {
config.model = modelOverride;
this.logService.trace(`[ImplementAgentProvider] Applied model override: ${modelOverride}`);
}

return config;
}
}
2 changes: 1 addition & 1 deletion src/extension/agents/vscode-node/planAgentProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const BASE_PLAN_AGENT_CONFIG: PlanAgentConfig = {
handoffs: [
{
label: 'Start Implementation',
agent: 'agent',
agent: 'Implement',
prompt: 'Start implementation',
send: true
},
Expand Down
5 changes: 5 additions & 0 deletions src/extension/agents/vscode-node/promptFileContrib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IInstantiationService } from '../../../util/vs/platform/instantiation/c
import { IExtensionContribution } from '../../common/contributions';
import { GitHubOrgCustomAgentProvider } from './githubOrgCustomAgentProvider';
import { GitHubOrgInstructionsProvider } from './githubOrgInstructionsProvider';
import { ImplementAgentProvider } from './implementAgentProvider';
import { PlanAgentProvider } from './planAgentProvider';

export class PromptFileContribution extends Disposable implements IExtensionContribution {
Expand All @@ -33,6 +34,10 @@ export class PromptFileContribution extends Disposable implements IExtensionCont
// Register Plan agent provider for dynamic settings-based customization
const planProvider = instantiationService.createInstance(PlanAgentProvider);
this._register(vscode.chat.registerCustomAgentProvider(planProvider));

// Register Implement agent provider for dynamic settings-based customization
const implementProvider = instantiationService.createInstance(ImplementAgentProvider);
this._register(vscode.chat.registerCustomAgentProvider(implementProvider));
}

// Register instructions provider
Expand Down
Loading