@ritwikranjan/copilot-agent-framework
v0.1.0
Published
Framework-agnostic wrapper for GitHub Copilot SDK with stateless session management, audit logging, and pluggable persistence — built for horizontal scaling
Maintainers
Readme
@ritwikranjan/copilot-agent-framework
Framework-agnostic library for building stateless agents on top of the GitHub Copilot SDK. Provides session management, audit logging, streaming, and pluggable persistence — with zero Azure or Teams dependencies. Built for horizontal scaling.
Installation
npm install @ritwikranjan/copilot-agent-framework @github/copilot-sdkOr as a workspace dependency (within this monorepo):
{
"dependencies": {
"@ritwikranjan/copilot-agent-framework": "*"
}
}Quick Start
import {
CopilotService,
SessionManager,
InMemorySessionStore,
loadSystemPrompt,
} from '@ritwikranjan/copilot-agent-framework';
// 1. Create a session store (in-memory for dev, or your own DB implementation)
const sessionStore = new InMemorySessionStore();
// 2. Create a session manager
const sessionManager = new SessionManager({ store: sessionStore });
// 3. Create the Copilot service
const copilot = new CopilotService(
{
cliUrl: 'localhost:3000',
model: 'gpt-5.2',
agentName: 'my-agent',
systemPrompt: loadSystemPrompt(),
},
sessionManager,
);
// 4. Send a message
const response = await copilot.sendMessage(
'Hello!',
{ username: '[email protected]', hostname: 'my-app' },
{ conversationId: 'conv-1' },
);
console.log(response.response);Streaming
import type { IStreamHandler } from '@ritwikranjan/copilot-agent-framework';
// Implement the generic stream handler for your framework
const streamHandler: IStreamHandler = {
emit: (content) => process.stdout.write(content),
update: (status) => console.log(`Status: ${status}`),
};
await copilot.sendMessageStreaming(
'Write a poem',
{ username: '[email protected]', hostname: 'my-app' },
streamHandler,
{ conversationId: 'conv-1' },
);With Audit Logging
import {
AuditManager,
InMemoryAuditStore,
} from '@ritwikranjan/copilot-agent-framework';
const auditStore = new InMemoryAuditStore();
const auditManager = new AuditManager({ store: auditStore });
const copilot = new CopilotService(
{
cliUrl: 'localhost:3000',
model: 'gpt-5.2',
agentName: 'my-agent',
enableAudit: true,
},
sessionManager,
auditManager, // optional third argument
);Logging
The library is silent by default — no console.log output. Logs use the debug package under the hood.
Enable via environment variable:
DEBUG=copilot:* # All logs
DEBUG=copilot:session # Session manager only
DEBUG=copilot:audit # Audit manager only
DEBUG=copilot:service # CopilotService onlyOr inject a custom logger:
import { setLogger } from '@ritwikranjan/copilot-agent-framework';
// Route all SDK logs through your app's logger (e.g., winston, pino)
setLogger({
debug: (msg, ...args) => myLogger.debug(msg, ...args),
info: (msg, ...args) => myLogger.info(msg, ...args),
warn: (msg, ...args) => myLogger.warn(msg, ...args),
error: (msg, ...args) => myLogger.error(msg, ...args),
});You can also pass a logger per-component:
const sessionManager = new SessionManager({ store: myStore, logger: myLogger });
const auditManager = new AuditManager({ store: myStore, logger: myLogger });
const copilot = new CopilotService({ ...config, logger: myLogger }, sessionManager);API Reference
CopilotService
The main class for interacting with the Copilot SDK.
new CopilotService(config, sessionManager, auditManager?)| Method | Description |
| --- | --- |
| getConfig() | Returns current service configuration |
| sendMessage(message, userInfo, options?) | Send a synchronous message, returns CopilotResponse |
| sendMessageStreaming(message, userInfo, streamHandler, options?) | Send a message with streaming via IStreamHandler |
| getConversationSessionStatus(conversationId, username) | Check session status |
| endConversationSession(conversationId, username) | End a session |
| resumeConversationSession(conversationId, userInfo) | Resume an expired session |
CopilotServiceConfig
interface CopilotServiceConfig {
cliUrl: string; // CLI server URL (e.g., 'localhost:3000')
model: string; // Model name (e.g., 'gpt-5.2')
agentName: string; // Agent identifier
systemPrompt?: string; // System prompt content
mcpServers?: MCPServerConfig[]; // MCP server configurations
enableAudit?: boolean; // Enable audit logging (default: true)
logger?: ILogger; // Custom logger (default: debug-based, silent)
}SessionManager
Manages session lifecycle with pluggable persistence. Sessions expire after 12 hours by default.
new SessionManager({ store: ISessionStore })Configure a custom session expiration:
const sessionManager = new SessionManager({
store: sessionStore,
sessionExpirationMs: 4 * 60 * 60 * 1000, // 4 hours (default: 12 hours)
});| Method | Description |
| --- | --- |
| resolveSession(userInfo, options?) | Find or create a session |
| getSessionStatus(username, conversationId) | Check session expiration status |
| updateCopilotSessionId(username, sessionId, copilotSessionId) | Store SDK session ID for resume |
| touchSession(username, sessionId) | Update last activity timestamp |
| renameSession(username, sessionId, newName) | Rename a session |
| endSession(username, sessionId, status?) | End a session |
| endSessionByConversationId(username, conversationId, status?) | End by conversation ID |
| getSession(username, sessionId) | Get session by ID |
| getUserSessions(username) | List all user sessions |
AuditManager
Logs interactions and tool executions within a session.
new AuditManager({ store: IAuditStore })| Method | Description |
| --- | --- |
| setSession(sessionId) | Set active session for logging |
| startInteraction(userQuery) | Log start of user turn |
| completeInteraction(response?, reasoning?) | Log agent response |
| logToolStart(toolName, args?) | Log tool execution start |
| logToolComplete(toolId, result?, error?) | Log tool execution completion |
| logToolExecution(toolName, args?, result?, error?) | Log full tool execution in one call |
| getSessionInteractions() | Query interactions for current session |
| getSessionToolExecutions() | Query tool executions for current session |
Interfaces
ISessionStore
Implement this to use your own database for session persistence:
interface ISessionStore {
initialize(): Promise<void>;
createSession(sessionData: SessionInfo): Promise<SessionInfo>;
updateSession(sessionId: string, partitionKey: string, sessionData: SessionInfo): Promise<SessionInfo>;
getSession(sessionId: string, partitionKey: string): Promise<SessionInfo | null>;
getSessionByName(username: string, sessionName: string): Promise<SessionInfo | null>;
getSessionsByUser(username: string): Promise<SessionInfo[]>;
getSessionByConversationId(username: string, conversationId: string): Promise<SessionInfo | null>;
}IAuditStore
Implement this to use your own database for audit logging:
interface IAuditStore {
initialize(): Promise<void>;
createInteraction(data: Interaction): Promise<Interaction>;
updateInteraction(id: string, partitionKey: string, data: Interaction): Promise<Interaction>;
getInteractionsBySession(sessionId: string): Promise<Interaction[]>;
createToolExecution(data: ToolExecution): Promise<ToolExecution>;
updateToolExecution(id: string, partitionKey: string, data: ToolExecution): Promise<ToolExecution>;
getToolExecutionsBySession(sessionId: string): Promise<ToolExecution[]>;
getToolExecutionsByInteraction(sessionId: string, interactionId: string): Promise<ToolExecution[]>;
}IStreamHandler
Implement this to adapt streaming to your framework:
interface IStreamHandler {
emit(content: string): void; // Emit content chunk
update?(status: string): void; // Optional status update
}Built-in Stores
| Store | Description |
| --- | --- |
| InMemorySessionStore | In-memory session store for development and testing |
| InMemoryAuditStore | In-memory audit store for development and testing |
Both stores provide clear() and getCounts() helpers for test setup/teardown.
Note:
IAuditDataStore(combinedISessionStore & IAuditStore) is deprecated. Always implement the interfaces separately. The Teams Copilot Agent usesSessionCosmosStoreandAuditCosmosStoreas separate classes.
Utility Functions
| Function | Description |
| --- | --- |
| loadSystemPrompt(options?) | Load system prompt from env var or file paths |
| loadToolsConfig(options?) | Load MCP tools configuration from JSON file |
| buildMcpServersConfig(toolsConfig, extraEnv?) | Convert tools config to SDK format |
| formatRemainingTime(remainingMs) | Format milliseconds as "Xh Ym" |
Models
| Type | Description |
| --- | --- |
| UserInfo | User identity (username, hostname) |
| SessionInfo | Session metadata with expiration, conversation ID, SDK session ID |
| Interaction | User query + agent response within a session |
| ToolExecution | Tool call metadata (name, args, result, timing) |
| CopilotResponse | Standard response envelope with success/error/session info |
| SessionStatus | Enum: ACTIVE, COMPLETED, ERROR |
| ToolExecutionStatus | Enum: STARTED, COMPLETED, ERROR |
Example: Building an HTTP API
import express from 'express';
import {
CopilotService,
SessionManager,
AuditManager,
InMemorySessionStore,
InMemoryAuditStore,
loadSystemPrompt,
} from '@ritwikranjan/copilot-agent-framework';
const app = express();
app.use(express.json());
const sessionStore = new InMemorySessionStore();
const auditStore = new InMemoryAuditStore();
const sessionManager = new SessionManager({ store: sessionStore });
const auditManager = new AuditManager({ store: auditStore });
const copilot = new CopilotService(
{
cliUrl: process.env.CLI_URL || 'localhost:3000',
model: 'gpt-5.2',
agentName: 'http-agent',
systemPrompt: loadSystemPrompt(),
},
sessionManager,
auditManager,
);
// POST /chat — synchronous
app.post('/chat', async (req, res) => {
const { message, conversationId } = req.body;
const userInfo = { username: 'api-user', hostname: 'http' };
const response = await copilot.sendMessage(message, userInfo, { conversationId });
res.json(response);
});
// POST /chat/stream — streaming via SSE
app.post('/chat/stream', async (req, res) => {
const { message, conversationId } = req.body;
const userInfo = { username: 'api-user', hostname: 'http' };
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
const streamHandler = {
emit: (content: string) => res.write(`data: ${JSON.stringify({ content })}\n\n`),
update: (status: string) => res.write(`data: ${JSON.stringify({ status })}\n\n`),
};
const response = await copilot.sendMessageStreaming(message, userInfo, streamHandler, { conversationId });
res.write(`data: ${JSON.stringify({ done: true, ...response })}\n\n`);
res.end();
});
app.listen(3000);Testing
npm test # Run tests
npm run test:watch # Watch mode
npm run build # Build the packagePublishing to npm
The package is structured for direct publishing:
npm run build
npm publish --access publicLicense
MIT
