copilotPluginConverters.ts ×17

Frontier kind: Code frontier

unlabeled · c_26998e3a58b1

457 tests · 17536 LOC · 59 files · introduces 0 tests · 173 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
17 ranges173 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1715 ranges17536 lines · 59 files · Browse complete extent
All tests (intent)
457 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 173 introduced LOC across 17 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/copilot/copilotPluginConverters.ts 173 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- copilotPluginConverters.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { spawn } from 'child_process';
7 > import type { CustomAgentConfig, MCPServerConfig, SessionConfig } from '@github/copilot-sdk';
8 > import { Schemas } from '../../../../base/common/network.js';
9 > import { OperatingSystem, OS } from '../../../../base/common/platform.js';
10 > import { URI } from '../../../../base/common/uri.js';
11 > import { parseFrontMatter } from '../../../../base/common/yaml.js';
12 > import { IFileService } from '../../../files/common/files.js';
13 > import { McpServerType, type IMcpServerConfiguration } from '../../../mcp/common/mcpPlatformTypes.js';
14 > import type { IMcpServerDefinition, INamedPluginResource, IParsedAgent, IParsedHookCommand, IParsedHookGroup, IParsedPlugin } from '../../../agentPlugins/common/pluginParsers.js';
15 > import { type AgentCustomization, type ChildCustomization } from '../../common/state/protocol/state.js';
16 > import { dirname } from '../../../../base/common/path.js';
17 >
18 > type SessionHooks = NonNullable<SessionConfig['hooks']>;
19 > type PreToolUseHookInput = Parameters<NonNullable<SessionHooks['onPreToolUse']>>[0];
20 > type PostToolUseHookInput = Parameters<NonNullable<SessionHooks['onPostToolUse']>>[0];
21 > type UserPromptSubmittedHookInput = Parameters<NonNullable<SessionHooks['onUserPromptSubmitted']>>[0];
22 > type SessionStartHookInput = Parameters<NonNullable<SessionHooks['onSessionStart']>>[0];
23 > type SessionEndHookInput = Parameters<NonNullable<SessionHooks['onSessionEnd']>>[0];
24 > type ErrorOccurredHookInput = Parameters<NonNullable<SessionHooks['onErrorOccurred']>>[0];
25 >
26 > // ---------------------------------------------------------------------------
27 > // MCP servers
28 > // ---------------------------------------------------------------------------
29 >
30 > /**
31 > * Converts parsed MCP server definitions into the SDK's `mcpServers` config.
32 > */
33 > export function toSdkMcpServers(defs: readonly IMcpServerDefinition[]): Record<string, MCPServerConfig> {
34 const result: Record<string, MCPServerConfig> = {};
35 for (const def of defs) {
38 return result;
39 }
41 > /**
42 > * Converts root MCP server config maps into the SDK's `mcpServers` config.
43 > *
44 > * The map originates from user-controlled root config, where the schema cannot
45 > * express per-entry validation (no `additionalProperties`). Entries are
46 > * therefore treated as `unknown` and silently skipped unless they match one of
47 > * the two supported shapes (`stdio` with a `command`, or `http` with a `url`),
48 > * so a malformed entry can't surface as `command`/`url: undefined` in the SDK
49 > * config.
50 > */
51 > export function toSdkMcpServersFromConfigMap(servers: Record<string, unknown>): Record<string, MCPServerConfig> {
52 const result: Record<string, MCPServerConfig> = {};
53 for (const [name, config] of Object.entries(servers)) {
58 return result;
59 }
61 > /**
62 > * Narrows an untrusted value to a supported {@link IMcpServerConfiguration}:
63 > * a `stdio` server with a string `command`, or an `http` server with a string
64 > * `url`.
65 > */
66 function isSupportedMcpServerConfiguration(value: unknown): value is IMcpServerConfiguration {
67 if (!value || typeof value !== 'object') {
77 return false;
78 }
80 function toSdkMcpServer(_name: string, config: IMcpServerConfiguration): MCPServerConfig {
81 if (config.type === McpServerType.LOCAL) {
96 };
97 }
99 > /**
100 > * Ensures all env values are strings (the SDK requires `Record<string, string>`).
101 > */
102 function toStringEnv(env: Record<string, string | number | null>): Record<string, string> {
103 const result: Record<string, string> = {};
109 return result;
110 }
112 > // ---------------------------------------------------------------------------
113 > // Custom agents
114 > // ---------------------------------------------------------------------------
115 >
116 > /**
117 > * Converts parsed plugin agents into the SDK's `customAgents` config.
118 > *
119 > * Each agent file is read and (when present) its YAML frontmatter is parsed:
120 > * - `name` falls back to the agent's resource name (filename stem).
121 > * - `description` is forwarded verbatim.
122 > * - `tools` is forwarded as the SDK's allow-list; an empty / missing array
123 > * becomes `null` so the SDK grants the agent access to all tools.
124 > * - `prompt` is the markdown body that follows the frontmatter (or the
125 > * full file content when there is no frontmatter).
126 > */
127 export async function toSdkCustomAgents(agents: readonly INamedPluginResource[], fileService: IFileService): Promise<CustomAgentConfig[]> {
128 const configs: CustomAgentConfig[] = [];
173 return configs;
174 }
176 > /** A plugin's agents together with its on-disk location (if any). */
177 > export interface IPluginAgentsForSdk {
178 > readonly pluginDir?: URI;
179 > readonly agents: readonly INamedPluginResource[];
180 > }
181 >
182 > /**
183 > * Builds the SDK's `customAgents` config for a session.
184 > *
185 > * Agents contributed by plugins materialized into an on-disk (file-scheme)
186 > * directory are normally left out of `customAgents` and discovered by the SDK
187 > * through `pluginDirectories` instead, to avoid duplicates. However, the SDK
188 > * validates the session-start `agent:` option against `customAgents` *by name
189 > * only* — it does NOT consult `pluginDirectories`. So a selected plugin or
190 > * extension agent (e.g. one chosen in the agent picker) would otherwise fail
191 > * with "Custom agent '<name>' not found". This forces the resolved selection
192 > * into `customAgents` so it can be activated, while every other file-dir agent
193 > * continues to load via `pluginDirectories`.
194 > */
195 export async function toSdkSessionCustomAgents(
196 plugins: readonly IPluginAgentsForSdk[],
210 return customAgents;
211 }
213 > /**
214 > * Projects parsed plugin agents into their protocol-level
215 > * {@link AgentCustomization} shape.
216 > */
217 > export function toAgentCustomizations(agents: readonly IParsedAgent[]): AgentCustomization[] {
218 return agents.map(a => a.customization);
219 }
221 > /**
222 > * Collects every child customization (agent, skill, rule, hook, MCP
223 > * server) produced by a parsed plugin, deduped by id. This is the single
224 > * source of truth for populating a container customization's `children`
225 > * array — every projector that produced an SDK config above derives its
226 > * matching protocol child from the same parsed primitive.
227 > */
228 > export function toChildCustomizations(plugins: readonly IParsedPlugin[]): ChildCustomization[] {
229 const byId = new Map<string, ChildCustomization>();
230 const add = (c: ChildCustomization) => {
242 return [...byId.values()];
243 }
245 > // ---------------------------------------------------------------------------
246 > // Skill directories
247 > // ---------------------------------------------------------------------------
248 >
249 > /**
250 > * Converts parsed plugin skills into the SDK's `skillDirectories` config.
251 > * The SDK expects directory paths; we extract the parent directory of each SKILL.md.
252 > */
253 > export function toSdkSkillDirectories(skills: readonly INamedPluginResource[]): string[] {
254 return toSdkResourceDirectories(skills);
255 }
257 > /**
258 > * Converts parsed plugin instructions into the SDK's
259 > * `instructionDirectories` config.
260 > */
261 > export function toSdkInstructionDirectories(instructions: readonly INamedPluginResource[]): string[] {
262 return toSdkResourceDirectories(instructions);
263 }
265 function toSdkResourceDirectories(resources: readonly INamedPluginResource[]): string[] {
266 const seen = new Set<string>();
275 return result;
276 }
278 > // ---------------------------------------------------------------------------
279 > // Hooks
280 > // ---------------------------------------------------------------------------
281 >
282 > /**
283 > * Resolves the effective command for the current platform from a parsed hook command.
284 > */
285 function resolveEffectiveCommand(hook: IParsedHookCommand, os: OperatingSystem): string | undefined {
286 if (os === OperatingSystem.Windows && hook.windows) {
293 return hook.command;
294 }
296 > /**
297 > * Executes a hook command as a shell process. Returns the stdout on success,
298 > * or throws on non-zero exit code or timeout.
299 > */
300 function executeHookCommand(hook: IParsedHookCommand, stdin?: string): Promise<string> {
301 const command = resolveEffectiveCommand(hook, OS);
342 });
343 }
345 > /**
346 > * Runs a list of hook commands sequentially, passing `input` as JSON stdin.
347 > * Returns the parsed output of the first command that emits a valid JSON object,
348 > * or `undefined` if no command produces parseable JSON output.
349 > * Command failures are swallowed — hooks are non-fatal.
350 > */
351 async function runHookCommands(commands: readonly IParsedHookCommand[] | undefined, input: unknown): Promise<object | undefined> {
352 if (!commands) {
373 return undefined;
374 }
376 > /**
377 > * Mapping from canonical hook type identifiers to SDK SessionHooks handler keys.
378 > */
379 > const HOOK_TYPE_TO_SDK_KEY: Record<string, keyof SessionHooks> = {
380 > 'PreToolUse': 'onPreToolUse',
381 > 'PostToolUse': 'onPostToolUse',
382 > 'UserPromptSubmit': 'onUserPromptSubmitted',
383 > 'SessionStart': 'onSessionStart',
384 > 'SessionEnd': 'onSessionEnd',
385 > 'ErrorOccurred': 'onErrorOccurred',
386 > };
387 >
388 > /**
389 > * Converts parsed plugin hooks into SDK {@link SessionHooks} handler functions.
390 > *
391 > * Each handler executes the hook's shell commands sequentially when invoked.
392 > * Hook types that don't map to SDK handler keys are silently ignored.
393 > *
394 > * The optional `editTrackingHooks` parameter provides internal edit-tracking
395 > * callbacks from {@link CopilotAgentSession} that are merged with plugin hooks.
396 > */
397 > export function toSdkHooks(
398 hookGroups: readonly IParsedHookGroup[],
399 editTrackingHooks?: {
496 return hooks;
497 }
499 > /**
500 > * Checks whether two sets of parsed plugins produce equivalent SDK config.
501 > * Used to determine if a session needs to be refreshed.
502 > */
503 > export function parsedPluginsEqual(a: readonly IParsedPlugin[], b: readonly IParsedPlugin[]): boolean {
504 // Simple structural comparison via JSON serialization.
505 // We serialize only the essential fields, replacing URIs with strings.