{"kind":"AgentDefinition","metadata":{"namespace":"community","name":"copilot-sdk-nodejs","version":"0.1.0"},"spec":{"agents_md":"---\napplyTo: \"**.ts, **.js, package.json\"\ndescription: \"This file provides guidance on building Node.js/TypeScript applications using GitHub Copilot SDK.\"\nname: \"GitHub Copilot SDK Node.js Instructions\"\n---\n\n## Core Principles\n\n- The SDK is in technical preview and may have breaking changes\n- Requires Node.js 18.0 or later\n- Requires GitHub Copilot CLI installed and in PATH\n- Built with TypeScript for type safety\n- Uses async/await patterns throughout\n- Provides full TypeScript type definitions\n\n## Installation\n\nAlways install via npm/pnpm/yarn:\n\n```bash\nnpm install @github/copilot-sdk\n# or\npnpm add @github/copilot-sdk\n# or\nyarn add @github/copilot-sdk\n```\n\n## Client Initialization\n\n### Basic Client Setup\n\n```typescript\nimport { CopilotClient, approveAll } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nawait client.start();\n// Use client...\nawait client.stop();\n```\n\n### Client Configuration Options\n\nWhen creating a CopilotClient, use `CopilotClientOptions`:\n\n- `cliPath` - Path to CLI executable (default: \"copilot\" from PATH)\n- `cliArgs` - Extra arguments prepended before SDK-managed flags (string[])\n- `cliUrl` - URL of existing CLI server (e.g., \"localhost:8080\"). When provided, client won't spawn a process\n- `port` - Server port (default: 0 for random)\n- `useStdio` - Use stdio transport instead of TCP (default: true)\n- `logLevel` - Log level (default: \"debug\")\n- `autoStart` - Auto-start server (default: true)\n- `autoRestart` - Auto-restart on crash (default: true)\n- `cwd` - Working directory for the CLI process (default: process.cwd())\n- `env` - Environment variables for the CLI process (default: process.env)\n\n### Manual Server Control\n\nFor explicit control:\n\n```typescript\nconst client = new CopilotClient({ autoStart: false });\nawait client.start();\n// Use client...\nawait client.stop();\n```\n\nUse `forceStop()` when `stop()` takes too long.\n\n## Session Management\n\n### Creating Sessions\n\nUse `SessionConfig` for configuration:\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n    streaming: true,\n    tools: [...],\n    systemMessage: { ... },\n    availableTools: [\"tool1\", \"tool2\"],\n    excludedTools: [\"tool3\"],\n    provider: { ... }\n});\n```\n\n### Session Config Options\n\n- `sessionId` - Custom session ID (string)\n- `model` - Model name (\"gpt-5\", \"claude-sonnet-4.5\", etc.)\n- `tools` - Custom tools exposed to the CLI (Tool[])\n- `systemMessage` - System message customization (SystemMessageConfig)\n- `availableTools` - Allowlist of tool names (string[])\n- `excludedTools` - Blocklist of tool names (string[])\n- `provider` - Custom API provider configuration (BYOK) (ProviderConfig)\n- `streaming` - Enable streaming response chunks (boolean)\n- `mcpServers` - MCP server configurations (MCPServerConfig[])\n- `customAgents` - Custom agent configurations (CustomAgentConfig[])\n- `configDir` - Config directory override (string)\n- `skillDirectories` - Skill directories (string[])\n- `disabledSkills` - Disabled skills (string[])\n- `onPermissionRequest` - Permission request handler (PermissionHandler)\n\n### Resuming Sessions\n\n```typescript\nconst session = await client.resumeSession(\"session-id\", {\n  tools: [myNewTool],\n  onPermissionRequest: approveAll,\n});\n```\n\n### Session Operations\n\n- `session.sessionId` - Get session identifier (string)\n- `await session.send({ prompt: \"...\", attachments: [...] })` - Send message, returns Promise\u003cstring\u003e\n- `await session.sendAndWait({ prompt: \"...\" }, timeout)` - Send and wait for idle, returns Promise\u003cAssistantMessageEvent | null\u003e\n- `await session.abort()` - Abort current processing\n- `await session.getMessages()` - Get all events/messages, returns Promise\u003cSessionEvent[]\u003e\n- `await session.destroy()` - Clean up session\n\n## Event Handling\n\n### Event Subscription Pattern\n\nALWAYS use async/await or Promises for waiting on session events:\n\n```typescript\nawait new Promise\u003cvoid\u003e((resolve) =\u003e {\n  session.on((event) =\u003e {\n    if (event.type === \"assistant.message\") {\n      console.log(event.data.content);\n    } else if (event.type === \"session.idle\") {\n      resolve();\n    }\n  });\n\n  session.send({ prompt: \"...\" });\n});\n```\n\n### Unsubscribing from Events\n\nThe `on()` method returns a function that unsubscribes:\n\n```typescript\nconst unsubscribe = session.on((event) =\u003e {\n  // handler\n});\n// Later...\nunsubscribe();\n```\n\n### Event Types\n\nUse discriminated unions with type guards for event handling:\n\n```typescript\nsession.on((event) =\u003e {\n  switch (event.type) {\n    case \"user.message\":\n      // Handle user message\n      break;\n    case \"assistant.message\":\n      console.log(event.data.content);\n      break;\n    case \"tool.executionStart\":\n      // Tool execution started\n      break;\n    case \"tool.executionComplete\":\n      // Tool execution completed\n      break;\n    case \"session.start\":\n      // Session started\n      break;\n    case \"session.idle\":\n      // Session is idle (processing complete)\n      break;\n    case \"session.error\":\n      console.error(`Error: ${event.data.message}`);\n      break;\n  }\n});\n```\n\n## Streaming Responses\n\n### Enabling Streaming\n\nSet `streaming: true` in SessionConfig:\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n    streaming: true,\n});\n```\n\n### Handling Streaming Events\n\nHandle both delta events (incremental) and final events:\n\n```typescript\nawait new Promise\u003cvoid\u003e((resolve) =\u003e {\n  session.on((event) =\u003e {\n    switch (event.type) {\n      case \"assistant.message_delta\":\n        // Incremental text chunk\n        process.stdout.write(event.data.deltaContent);\n        break;\n      case \"assistant.reasoning_delta\":\n        // Incremental reasoning chunk (model-dependent)\n        process.stdout.write(event.data.deltaContent);\n        break;\n      case \"assistant.message\":\n        // Final complete message\n        console.log(\"\\n--- Final ---\");\n        console.log(event.data.content);\n        break;\n      case \"assistant.reasoning\":\n        // Final reasoning content\n        console.log(\"--- Reasoning ---\");\n        console.log(event.data.content);\n        break;\n      case \"session.idle\":\n        resolve();\n        break;\n    }\n  });\n\n  session.send({ prompt: \"Tell me a story\" });\n});\n```\n\nNote: Final events (`assistant.message`, `assistant.reasoning`) are ALWAYS sent regardless of streaming setting.\n\n## Custom Tools\n\n### Defining Tools with defineTool\n\nUse `defineTool` for type-safe tool definitions:\n\n```typescript\nimport { defineTool } from \"@github/copilot-sdk\";\n\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n  tools: [\n    defineTool({\n      name: \"lookup_issue\",\n      description: \"Fetch issue details from tracker\",\n      parameters: {\n        type: \"object\",\n        properties: {\n          id: { type: \"string\", description: \"Issue ID\" },\n        },\n        required: [\"id\"],\n      },\n      handler: async (args) =\u003e {\n        const issue = await fetchIssue(args.id);\n        return issue;\n      },\n    }),\n  ],\n});\n```\n\n### Using Zod for Parameters\n\nThe SDK supports Zod schemas for parameters:\n\n```typescript\nimport { z } from \"zod\";\n\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n  tools: [\n    defineTool({\n      name: \"get_weather\",\n      description: \"Get weather for a location\",\n      parameters: z.object({\n        location: z.string().describe(\"City name\"),\n        units: z.enum([\"celsius\", \"fahrenheit\"]).optional(),\n      }),\n      handler: async (args) =\u003e {\n        return { temperature: 72, units: args.units || \"fahrenheit\" };\n      },\n    }),\n  ],\n});\n```\n\n### Tool Return Types\n\n- Return any JSON-serializable value (automatically wrapped)\n- Or return `ToolResultObject` for full control over metadata:\n\n```typescript\n{\n    textResultForLlm: string;  // Result shown to LLM\n    resultType: \"success\" | \"failure\";\n    error?: string;  // Internal error (not shown to LLM)\n    toolTelemetry?: Record\u003cstring, unknown\u003e;\n}\n```\n\n### Tool Execution Flow\n\nWhen Copilot invokes a tool, the client automatically:\n\n1. Runs your handler function\n2. Serializes the return value\n3. Responds to the CLI\n\n## System Message Customization\n\n### Append Mode (Default - Preserves Guardrails)\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n  systemMessage: {\n    mode: \"append\",\n    content: `\n\u003cworkflow_rules\u003e\n- Always check for security vulnerabilities\n- Suggest performance improvements when applicable\n\u003c/workflow_rules\u003e\n`,\n  },\n});\n```\n\n### Replace Mode (Full Control - Removes Guardrails)\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n  systemMessage: {\n    mode: \"replace\",\n    content: \"You are a helpful assistant.\",\n  },\n});\n```\n\n## File Attachments\n\nAttach files to messages:\n\n```typescript\nawait session.send({\n  prompt: \"Analyze this file\",\n  attachments: [\n    {\n      type: \"file\",\n      path: \"/path/to/file.ts\",\n      displayName: \"My File\",\n    },\n  ],\n});\n```\n\n## Message Delivery Modes\n\nUse the `mode` property in message options:\n\n- `\"enqueue\"` - Queue message for processing\n- `\"immediate\"` - Process message immediately\n\n```typescript\nawait session.send({\n  prompt: \"...\",\n  mode: \"enqueue\",\n});\n```\n\n## Multiple Sessions\n\nSessions are independent and can run concurrently:\n\n```typescript\nconst session1 = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n});\nconst session2 = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"claude-sonnet-4.5\",\n});\n\nawait Promise.all([\n  session1.send({ prompt: \"Hello from session 1\" }),\n  session2.send({ prompt: \"Hello from session 2\" }),\n]);\n```\n\n## Bring Your Own Key (BYOK)\n\nUse custom API providers via `provider`:\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n  provider: {\n    type: \"openai\",\n    baseUrl: \"https://api.openai.com/v1\",\n    apiKey: \"your-api-key\",\n  },\n});\n```\n\n## Session Lifecycle Management\n\n### Listing Sessions\n\n```typescript\nconst sessions = await client.listSessions();\nfor (const metadata of sessions) {\n  console.log(`${metadata.sessionId}: ${metadata.summary}`);\n}\n```\n\n### Deleting Sessions\n\n```typescript\nawait client.deleteSession(sessionId);\n```\n\n### Getting Last Session ID\n\n```typescript\nconst lastId = await client.getLastSessionId();\nif (lastId) {\n  const session = await client.resumeSession(lastId, { onPermissionRequest: approveAll });\n}\n```\n\n### Checking Connection State\n\n```typescript\nconst state = client.getState();\n// Returns: \"disconnected\" | \"connecting\" | \"connected\" | \"error\"\n```\n\n## Error Handling\n\n### Standard Exception Handling\n\n```typescript\ntry {\n  const session = await client.createSession({ onPermissionRequest: approveAll });\n  await session.send({ prompt: \"Hello\" });\n} catch (error) {\n  console.error(`Error: ${error.message}`);\n}\n```\n\n### Session Error Events\n\nMonitor `session.error` event type for runtime errors:\n\n```typescript\nsession.on((event) =\u003e {\n  if (event.type === \"session.error\") {\n    console.error(`Session Error: ${event.data.message}`);\n  }\n});\n```\n\n## Connectivity Testing\n\nUse ping to verify server connectivity:\n\n```typescript\nconst response = await client.ping(\"health check\");\nconsole.log(`Server responded at ${new Date(response.timestamp)}`);\n```\n\n## Resource Cleanup\n\n### Automatic Cleanup with Try-Finally\n\nALWAYS use try-finally or cleanup in a finally block:\n\n```typescript\nconst client = new CopilotClient();\ntry {\n  await client.start();\n  const session = await client.createSession({ onPermissionRequest: approveAll });\n  try {\n    // Use session...\n  } finally {\n    await session.destroy();\n  }\n} finally {\n  await client.stop();\n}\n```\n\n### Cleanup Function Pattern\n\n```typescript\nasync function withClient\u003cT\u003e(\n  fn: (client: CopilotClient) =\u003e Promise\u003cT\u003e,\n): Promise\u003cT\u003e {\n  const client = new CopilotClient();\n  try {\n    await client.start();\n    return await fn(client);\n  } finally {\n    await client.stop();\n  }\n}\n\nasync function withSession\u003cT\u003e(\n  client: CopilotClient,\n  fn: (session: CopilotSession) =\u003e Promise\u003cT\u003e,\n): Promise\u003cT\u003e {\n  const session = await client.createSession({ onPermissionRequest: approveAll });\n  try {\n    return await fn(session);\n  } finally {\n    await session.destroy();\n  }\n}\n\n// Usage\nawait withClient(async (client) =\u003e {\n  await withSession(client, async (session) =\u003e {\n    await session.send({ prompt: \"Hello!\" });\n  });\n});\n```\n\n## Best Practices\n\n1. **Always use try-finally** for resource cleanup\n2. **Use Promises** to wait for session.idle event\n3. **Handle session.error** events for robust error handling\n4. **Use type guards or switch statements** for event handling\n5. **Enable streaming** for better UX in interactive scenarios\n6. **Use defineTool** for type-safe tool definitions\n7. **Use Zod schemas** for runtime parameter validation\n8. **Dispose event subscriptions** when no longer needed\n9. **Use systemMessage with mode: \"append\"** to preserve safety guardrails\n10. **Handle both delta and final events** when streaming is enabled\n11. **Leverage TypeScript types** for compile-time safety\n\n## Common Patterns\n\n### Simple Query-Response\n\n```typescript\nimport { CopilotClient, approveAll } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\ntry {\n  await client.start();\n\n  const session = await client.createSession({\n    onPermissionRequest: approveAll,\n    model: \"gpt-5\",\n  });\n  try {\n    await new Promise\u003cvoid\u003e((resolve) =\u003e {\n      session.on((event) =\u003e {\n        if (event.type === \"assistant.message\") {\n          console.log(event.data.content);\n        } else if (event.type === \"session.idle\") {\n          resolve();\n        }\n      });\n\n      session.send({ prompt: \"What is 2+2?\" });\n    });\n  } finally {\n    await session.destroy();\n  }\n} finally {\n  await client.stop();\n}\n```\n\n### Multi-Turn Conversation\n\n```typescript\nconst session = await client.createSession({ onPermissionRequest: approveAll });\n\nasync function sendAndWait(prompt: string): Promise\u003cvoid\u003e {\n  await new Promise\u003cvoid\u003e((resolve, reject) =\u003e {\n    const unsubscribe = session.on((event) =\u003e {\n      if (event.type === \"assistant.message\") {\n        console.log(event.data.content);\n      } else if (event.type === \"session.idle\") {\n        unsubscribe();\n        resolve();\n      } else if (event.type === \"session.error\") {\n        unsubscribe();\n        reject(new Error(event.data.message));\n      }\n    });\n\n    session.send({ prompt });\n  });\n}\n\nawait sendAndWait(\"What is the capital of France?\");\nawait sendAndWait(\"What is its population?\");\n```\n\n### SendAndWait Helper\n\n```typescript\n// Use built-in sendAndWait for simpler synchronous interaction\nconst response = await session.sendAndWait({ prompt: \"What is 2+2?\" }, 60000);\n\nif (response) {\n  console.log(response.data.content);\n}\n```\n\n### Tool with Type-Safe Parameters\n\n```typescript\nimport { z } from \"zod\";\nimport { defineTool } from \"@github/copilot-sdk\";\n\ninterface UserInfo {\n  id: string;\n  name: string;\n  email: string;\n  role: string;\n}\n\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n  tools: [\n    defineTool({\n      name: \"get_user\",\n      description: \"Retrieve user information\",\n      parameters: z.object({\n        userId: z.string().describe(\"User ID\"),\n      }),\n      handler: async (args): Promise\u003cUserInfo\u003e =\u003e {\n        return {\n          id: args.userId,\n          name: \"John Doe\",\n          email: \"john@example.com\",\n          role: \"Developer\",\n        };\n      },\n    }),\n  ],\n});\n```\n\n### Streaming with Progress\n\n```typescript\nlet currentMessage = \"\";\n\nconst unsubscribe = session.on((event) =\u003e {\n  if (event.type === \"assistant.message_delta\") {\n    currentMessage += event.data.deltaContent;\n    process.stdout.write(event.data.deltaContent);\n  } else if (event.type === \"assistant.message\") {\n    console.log(\"\\n\\n=== Complete ===\");\n    console.log(`Total length: ${event.data.content.length} chars`);\n  } else if (event.type === \"session.idle\") {\n    unsubscribe();\n  }\n});\n\nawait session.send({ prompt: \"Write a long story\" });\n```\n\n### Error Recovery\n\n```typescript\nsession.on((event) =\u003e {\n  if (event.type === \"session.error\") {\n    console.error(\"Session error:\", event.data.message);\n    // Optionally retry or handle error\n  }\n});\n\ntry {\n  await session.send({ prompt: \"risky operation\" });\n} catch (error) {\n  // Handle send errors\n  console.error(\"Failed to send:\", error);\n}\n```\n\n## TypeScript-Specific Features\n\n### Type Inference\n\n```typescript\nimport type { SessionEvent, AssistantMessageEvent } from \"@github/copilot-sdk\";\n\nsession.on((event: SessionEvent) =\u003e {\n  if (event.type === \"assistant.message\") {\n    // TypeScript knows event is AssistantMessageEvent here\n    const content: string = event.data.content;\n  }\n});\n```\n\n### Generic Helper\n\n```typescript\nasync function waitForEvent\u003cT extends SessionEvent[\"type\"]\u003e(\n  session: CopilotSession,\n  eventType: T,\n): Promise\u003cExtract\u003cSessionEvent, { type: T }\u003e\u003e {\n  return new Promise((resolve) =\u003e {\n    const unsubscribe = session.on((event) =\u003e {\n      if (event.type === eventType) {\n        unsubscribe();\n        resolve(event as Extract\u003cSessionEvent, { type: T }\u003e);\n      }\n    });\n  });\n}\n\n// Usage\nconst message = await waitForEvent(session, \"assistant.message\");\nconsole.log(message.data.content);\n```\n","description":"This file provides guidance on building Node.js/TypeScript applications using GitHub Copilot SDK.","import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/blob/541b7819d8c3545c6df122491af4fa1eae415779/instructions/copilot-sdk-nodejs.instructions.md"},"manifest":{}},"content_hash":[0,120,221,247,240,12,84,61,48,137,180,11,178,130,145,176,115,135,82,34,193,170,185,227,96,189,43,57,47,217,155,187],"trust_level":"unsigned","yanked":false}
