diff --git a/docs/auth/byok.md b/docs/auth/byok.md
index 13ad8b055..6a2804f4d 100644
--- a/docs/auth/byok.md
+++ b/docs/auth/byok.md
@@ -79,6 +79,7 @@ const session = await client.createSession({
wireApi: "responses", // Use "completions" for older models
apiKey: process.env.FOUNDRY_API_KEY,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
session.on("assistant.message", (event) => {
@@ -350,12 +351,14 @@ When using BYOK, the `model` parameter is **required**:
// ❌ Error: Model required with custom provider
const session = await client.createSession({
provider: { type: "openai", baseUrl: "..." },
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// ✅ Correct: Model specified
const session = await client.createSession({
model: "gpt-4", // Required!
provider: { type: "openai", baseUrl: "..." },
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/compatibility.md b/docs/compatibility.md
index 268c077a3..4c3bc5a30 100644
--- a/docs/compatibility.md
+++ b/docs/compatibility.md
@@ -158,6 +158,7 @@ const session = await client.createSession({
backgroundCompactionThreshold: 0.80, // Start background compaction at 80% context utilization
bufferExhaustionThreshold: 0.95, // Block and compact at 95% context utilization
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/debugging.md b/docs/debugging.md
index 6183cccdf..c18762f95 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -376,8 +376,9 @@ const client = new CopilotClient({
```typescript
const session = await client.createSession({
tools: [myTool],
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
-
+
// Check registered tools
console.log("Registered tools:", session.getTools?.());
```
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 05bbde8dc..19cab363d 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -20,7 +20,7 @@ Before you begin, make sure you have:
- **GitHub Copilot CLI** installed and authenticated ([Installation guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli))
- Your preferred language runtime:
- - **Node.js** 18+ or **Python** 3.8+ or **Go** 1.21+ or **.NET** 8.0+
+ - **Node.js** 22.13.0+ / 23.5.0+ / 24+ or **Python** 3.8+ or **Go** 1.21+ or **.NET** 8.0+
Verify the CLI is working:
@@ -105,7 +105,7 @@ Create `index.ts`:
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
-const session = await client.createSession({ model: "gpt-4.1" });
+const session = await client.createSession({ model: "gpt-4.1", onPermissionRequest: async () => ({ kind: "approved" }) });
const response = await session.sendAndWait({ prompt: "What is 2 + 2?" });
console.log(response?.data.content);
@@ -248,6 +248,7 @@ const client = new CopilotClient();
const session = await client.createSession({
model: "gpt-4.1",
streaming: true,
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Listen for response chunks
@@ -624,6 +625,7 @@ const session = await client.createSession({
model: "gpt-4.1",
streaming: true,
tools: [getWeather],
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
session.on("assistant.message_delta", (event) => {
@@ -876,6 +878,7 @@ const session = await client.createSession({
model: "gpt-4.1",
streaming: true,
tools: [getWeather],
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
session.on("assistant.message_delta", (event) => {
@@ -1221,6 +1224,7 @@ const session = await client.createSession({
url: "https://api.githubcopilot.com/mcp/",
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -1238,6 +1242,7 @@ const session = await client.createSession({
description: "Reviews pull requests for best practices",
prompt: "You are an expert code reviewer. Focus on security, performance, and maintainability.",
}],
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -1250,6 +1255,7 @@ const session = await client.createSession({
systemMessage: {
content: "You are a helpful assistant for our engineering team. Always be concise.",
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/guides/session-persistence.md b/docs/guides/session-persistence.md
index 527f5ecc7..8df9ed29e 100644
--- a/docs/guides/session-persistence.md
+++ b/docs/guides/session-persistence.md
@@ -34,6 +34,7 @@ const client = new CopilotClient();
const session = await client.createSession({
sessionId: "user-123-task-456",
model: "gpt-5.2-codex",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Do some work...
@@ -111,10 +112,10 @@ flowchart LR
subgraph Day1["Day 1"]
A1[Client A:
createSession] --> A2[Work...]
end
-
+
A2 --> S[(💾 Storage:
~/.copilot/session-state/)]
S --> B1
-
+
subgraph Day2["Day 2"]
B1[Client B:
resumeSession] --> B2[Continue]
end
@@ -124,7 +125,7 @@ flowchart LR
```typescript
// Resume from a different client instance (or after restart)
-const session = await client.resumeSession("user-123-task-456");
+const session = await client.resumeSession("user-123-task-456", { onPermissionRequest: async () => ({ kind: "approved" }) });
// Continue where you left off
await session.sendAndWait({ prompt: "What did we discuss earlier?" });
@@ -192,6 +193,7 @@ When resuming a session, you can optionally reconfigure many settings. This is u
const session = await client.resumeSession("user-123-task-456", {
model: "claude-sonnet-4", // Switch to a different model
reasoningEffort: "high", // Increase reasoning effort
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -210,6 +212,7 @@ const session = await client.createSession({
apiKey: process.env.AZURE_OPENAI_KEY,
deploymentId: "my-gpt-deployment",
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// When resuming, you MUST re-provide the provider config
@@ -220,6 +223,7 @@ const resumed = await client.resumeSession("user-123-task-456", {
apiKey: process.env.AZURE_OPENAI_KEY, // Required again
deploymentId: "my-gpt-deployment",
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -311,7 +315,7 @@ const repoSessions = await client.listSessions({ repository: "owner/repo" });
async function cleanupExpiredSessions(maxAgeMs: number) {
const sessions = await client.listSessions();
const now = Date.now();
-
+
for (const session of sessions) {
const age = now - new Date(session.createdAt).getTime();
if (age > maxAgeMs) {
@@ -333,7 +337,7 @@ When a task completes, destroy the session explicitly rather than waiting for ti
try {
// Do work...
await session.sendAndWait({ prompt: "Complete the task" });
-
+
// Task complete - clean up
await session.destroy();
} catch (error) {
@@ -408,12 +412,12 @@ async function resumeSessionWithAuth(
): Promise {
// Parse user from session ID
const [sessionUserId] = sessionId.split("-");
-
+
if (sessionUserId !== currentUserId) {
throw new Error("Access denied: session belongs to another user");
}
-
- return client.resumeSession(sessionId);
+
+ return client.resumeSession(sessionId, { onPermissionRequest: async () => ({ kind: "approved" }) });
}
```
@@ -446,10 +450,10 @@ flowchart LR
subgraph Before["Container A"]
CLI1[CLI + Session X]
end
-
+
CLI1 --> |persist| Azure[(☁️ Azure File Share)]
Azure --> |restore| CLI2
-
+
subgraph After["Container B (restart)"]
CLI2[CLI + Session X]
end
@@ -469,6 +473,7 @@ const session = await client.createSession({
backgroundCompactionThreshold: 0.80, // Start compaction at 80% context
bufferExhaustionThreshold: 0.95, // Block at 95% if needed
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -499,11 +504,11 @@ async function withSessionLock(
): Promise {
const lockKey = `session-lock:${sessionId}`;
const acquired = await redis.set(lockKey, "locked", "NX", "EX", 300);
-
+
if (!acquired) {
throw new Error("Session is in use by another client");
}
-
+
try {
return await fn();
} finally {
@@ -513,7 +518,7 @@ async function withSessionLock(
// Usage
await withSessionLock("user-123-task-456", async () => {
- const session = await client.resumeSession("user-123-task-456");
+ const session = await client.resumeSession("user-123-task-456", { onPermissionRequest: async () => ({ kind: "approved" }) });
await session.sendAndWait({ prompt: "Continue the task" });
});
```
diff --git a/docs/guides/setup/azure-managed-identity.md b/docs/guides/setup/azure-managed-identity.md
index bfafc6f91..118a6d836 100644
--- a/docs/guides/setup/azure-managed-identity.md
+++ b/docs/guides/setup/azure-managed-identity.md
@@ -145,6 +145,7 @@ const session = await client.createSession({
bearerToken: tokenResponse.token,
wireApi: "responses",
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({ prompt: "Hello!" });
diff --git a/docs/guides/setup/backend-services.md b/docs/guides/setup/backend-services.md
index c9bc13f8d..e7a433345 100644
--- a/docs/guides/setup/backend-services.md
+++ b/docs/guides/setup/backend-services.md
@@ -99,6 +99,7 @@ const client = new CopilotClient({
const session = await client.createSession({
sessionId: `user-${userId}-${Date.now()}`,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({ prompt: req.body.message });
@@ -213,6 +214,7 @@ app.post("/chat", authMiddleware, async (req, res) => {
const session = await client.createSession({
sessionId: `user-${req.user.id}-chat`,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({
@@ -239,6 +241,7 @@ const session = await client.createSession({
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -280,11 +283,12 @@ app.post("/api/chat", async (req, res) => {
// Create or resume session
let session;
try {
- session = await client.resumeSession(sessionId);
+ session = await client.resumeSession(sessionId, { onPermissionRequest: async () => ({ kind: "approved" }) });
} catch {
session = await client.createSession({
sessionId,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
}
@@ -312,6 +316,7 @@ async function processJob(job: Job) {
const session = await client.createSession({
sessionId: `job-${job.id}`,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({
diff --git a/docs/guides/setup/bundled-cli.md b/docs/guides/setup/bundled-cli.md
index 6daf57b56..75afdfc9b 100644
--- a/docs/guides/setup/bundled-cli.md
+++ b/docs/guides/setup/bundled-cli.md
@@ -72,7 +72,7 @@ const client = new CopilotClient({
cliPath: path.join(__dirname, "vendor", "copilot"),
});
-const session = await client.createSession({ model: "gpt-4.1" });
+const session = await client.createSession({ model: "gpt-4.1", onPermissionRequest: async () => ({ kind: "approved" }) });
const response = await session.sendAndWait({ prompt: "Hello!" });
console.log(response?.data.content);
@@ -200,6 +200,7 @@ const session = await client.createSession({
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -219,11 +220,12 @@ const sessionId = `project-${projectName}`;
const session = await client.createSession({
sessionId,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// User closes app...
// Later, resume where they left off
-const resumed = await client.resumeSession(sessionId);
+const resumed = await client.resumeSession(sessionId, { onPermissionRequest: async () => ({ kind: "approved" }) });
```
Session state persists at `~/.copilot/session-state/{sessionId}/`.
diff --git a/docs/guides/setup/byok.md b/docs/guides/setup/byok.md
index 5b8b8a460..8ba1d4019 100644
--- a/docs/guides/setup/byok.md
+++ b/docs/guides/setup/byok.md
@@ -78,6 +78,7 @@ const session = await client.createSession({
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({ prompt: "Hello!" });
@@ -255,6 +256,7 @@ app.post("/chat", authMiddleware, async (req, res) => {
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY, // Your key, your billing
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({ prompt: req.body.message });
@@ -303,6 +305,7 @@ async function createSessionForCustomer(customerId: string) {
baseUrl: config.baseUrl,
apiKey: config.apiKey,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
}
```
@@ -321,6 +324,7 @@ const session = await client.createSession({
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Resume later — must re-provide provider config
@@ -330,6 +334,7 @@ const resumed = await client.resumeSession("task-123", {
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY, // Required again
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/guides/setup/github-oauth.md b/docs/guides/setup/github-oauth.md
index 07251c8fb..9f1152ede 100644
--- a/docs/guides/setup/github-oauth.md
+++ b/docs/guides/setup/github-oauth.md
@@ -134,6 +134,7 @@ const client = createClientForUser("gho_user_access_token");
const session = await client.createSession({
sessionId: `user-${userId}-session`,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({ prompt: "Hello!" });
diff --git a/docs/guides/setup/local-cli.md b/docs/guides/setup/local-cli.md
index a5fa906b8..c267f79af 100644
--- a/docs/guides/setup/local-cli.md
+++ b/docs/guides/setup/local-cli.md
@@ -37,7 +37,7 @@ The default configuration requires no options at all:
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
-const session = await client.createSession({ model: "gpt-4.1" });
+const session = await client.createSession({ model: "gpt-4.1", onPermissionRequest: async () => ({ kind: "approved" }) });
const response = await session.sendAndWait({ prompt: "Hello!" });
console.log(response?.data.content);
@@ -173,10 +173,11 @@ With the local CLI, sessions default to ephemeral. To create resumable sessions,
const session = await client.createSession({
sessionId: "my-project-analysis",
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Later, resume it
-const resumed = await client.resumeSession("my-project-analysis");
+const resumed = await client.resumeSession("my-project-analysis", { onPermissionRequest: async () => ({ kind: "approved" }) });
```
Session state is stored locally at `~/.copilot/session-state/{sessionId}/`.
diff --git a/docs/guides/setup/scaling.md b/docs/guides/setup/scaling.md
index fcdb716da..872a6b034 100644
--- a/docs/guides/setup/scaling.md
+++ b/docs/guides/setup/scaling.md
@@ -153,7 +153,7 @@ async function resumeSessionWithAuth(
if (sessionUserId !== currentUserId) {
throw new Error("Access denied: session belongs to another user");
}
- return sharedClient.resumeSession(sessionId);
+ return sharedClient.resumeSession(sessionId, { onPermissionRequest: async () => ({ kind: "approved" }) });
}
```
@@ -227,7 +227,7 @@ async function withSessionLock(
// Usage: serialize access to shared session
app.post("/team-chat", authMiddleware, async (req, res) => {
const result = await withSessionLock("team-project-review", async () => {
- const session = await client.resumeSession("team-project-review");
+ const session = await client.resumeSession("team-project-review", { onPermissionRequest: async () => ({ kind: "approved" }) });
return session.sendAndWait({ prompt: req.body.message });
});
@@ -323,6 +323,7 @@ app.post("/chat", async (req, res) => {
const session = await client.createSession({
sessionId: `user-${req.user.id}-chat`,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
const response = await session.sendAndWait({ prompt: req.body.message });
@@ -403,6 +404,7 @@ class SessionManager {
const session = await client.createSession({
sessionId,
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
this.activeSessions.set(sessionId, session);
@@ -449,6 +451,7 @@ For stateless API endpoints where each request is independent:
app.post("/api/analyze", async (req, res) => {
const session = await client.createSession({
model: "gpt-4.1",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
try {
@@ -478,6 +481,7 @@ app.post("/api/chat/start", async (req, res) => {
enabled: true,
backgroundCompactionThreshold: 0.80,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
res.json({ sessionId });
@@ -485,7 +489,7 @@ app.post("/api/chat/start", async (req, res) => {
// Continue the conversation
app.post("/api/chat/message", async (req, res) => {
- const session = await client.resumeSession(req.body.sessionId);
+ const session = await client.resumeSession(req.body.sessionId, { onPermissionRequest: async () => ({ kind: "approved" }) });
const response = await session.sendAndWait({ prompt: req.body.message });
res.json({ content: response?.data.content });
diff --git a/docs/guides/skills.md b/docs/guides/skills.md
index b2ea3ae7a..d049ac177 100644
--- a/docs/guides/skills.md
+++ b/docs/guides/skills.md
@@ -150,6 +150,7 @@ Disable specific skills while keeping others active:
const session = await client.createSession({
skillDirectories: ["./skills"],
disabledSkills: ["experimental-feature", "deprecated-tool"],
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/hooks/error-handling.md b/docs/hooks/error-handling.md
index 0f705868d..6abd2b0a7 100644
--- a/docs/hooks/error-handling.md
+++ b/docs/hooks/error-handling.md
@@ -98,6 +98,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -180,10 +181,11 @@ const session = await client.createSession({
cwd: input.cwd,
},
});
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -201,16 +203,17 @@ const session = await client.createSession({
hooks: {
onErrorOccurred: async (input) => {
const friendlyMessage = ERROR_MESSAGES[input.errorContext];
-
+
if (friendlyMessage) {
return {
userNotification: friendlyMessage,
};
}
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -228,6 +231,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -247,7 +251,7 @@ The tool failed. Here are some recovery suggestions:
`.trim(),
};
}
-
+
if (input.errorContext === "model_call" && input.error.includes("rate")) {
return {
errorHandling: "retry",
@@ -255,10 +259,11 @@ The tool failed. Here are some recovery suggestions:
userNotification: "Rate limit hit. Retrying...",
};
}
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -277,27 +282,28 @@ const session = await client.createSession({
hooks: {
onErrorOccurred: async (input, invocation) => {
const key = `${input.errorContext}:${input.error.substring(0, 50)}`;
-
+
const existing = errorStats.get(key) || {
count: 0,
lastOccurred: 0,
contexts: [],
};
-
+
existing.count++;
existing.lastOccurred = input.timestamp;
existing.contexts.push(invocation.sessionId);
-
+
errorStats.set(key, existing);
-
+
// Alert if error is recurring
if (existing.count >= 5) {
console.warn(`Recurring error detected: ${key} (${existing.count} times)`);
}
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -318,10 +324,11 @@ const session = await client.createSession({
timestamp: new Date(input.timestamp).toISOString(),
});
}
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -338,17 +345,17 @@ const session = await client.createSession({
sessionContext.set(invocation.sessionId, ctx);
return { permissionDecision: "allow" };
},
-
+
onUserPromptSubmitted: async (input, invocation) => {
const ctx = sessionContext.get(invocation.sessionId) || {};
ctx.lastPrompt = input.prompt.substring(0, 100);
sessionContext.set(invocation.sessionId, ctx);
return null;
},
-
+
onErrorOccurred: async (input, invocation) => {
const ctx = sessionContext.get(invocation.sessionId);
-
+
console.error(`Error in session ${invocation.sessionId}:`);
console.error(` Error: ${input.error}`);
console.error(` Context: ${input.errorContext}`);
@@ -358,10 +365,11 @@ const session = await client.createSession({
if (ctx?.lastPrompt) {
console.error(` Last prompt: ${ctx.lastPrompt}...`);
}
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/hooks/overview.md b/docs/hooks/overview.md
index a51ef0464..ab2a3c2ea 100644
--- a/docs/hooks/overview.md
+++ b/docs/hooks/overview.md
@@ -44,6 +44,7 @@ const session = await client.createSession({
return { additionalContext: "User prefers concise answers." };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -183,6 +184,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -203,6 +205,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -218,6 +221,7 @@ const session = await client.createSession({
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/hooks/post-tool-use.md b/docs/hooks/post-tool-use.md
index 0021e20a0..4578a5485 100644
--- a/docs/hooks/post-tool-use.md
+++ b/docs/hooks/post-tool-use.md
@@ -97,6 +97,7 @@ const session = await client.createSession({
return null; // Pass through unchanged
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -177,7 +178,7 @@ const session = await client.createSession({
for (const pattern of SENSITIVE_PATTERNS) {
redacted = redacted.replace(pattern, "[REDACTED]");
}
-
+
if (redacted !== input.toolResult) {
return { modifiedResult: redacted };
}
@@ -185,6 +186,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -197,7 +199,7 @@ const session = await client.createSession({
hooks: {
onPostToolUse: async (input) => {
const resultStr = JSON.stringify(input.toolResult);
-
+
if (resultStr.length > MAX_RESULT_LENGTH) {
return {
modifiedResult: {
@@ -211,6 +213,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -226,17 +229,18 @@ const session = await client.createSession({
additionalContext: "Tip: If the file doesn't exist, consider creating it or checking the path.",
};
}
-
+
// If shell command failed, add debugging hint
if (input.toolName === "shell" && input.toolResult?.exitCode !== 0) {
return {
additionalContext: "The command failed. Check if required dependencies are installed.",
};
}
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -259,6 +263,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -287,13 +292,14 @@ const session = await client.createSession({
result: input.toolResult,
success: !input.toolResult?.error,
});
-
+
// Optionally persist to database/file
await saveAuditLog(auditLog);
-
+
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -307,10 +313,10 @@ const session = await client.createSession({
onPostToolUse: async (input) => {
if (NOISY_TOOLS.includes(input.toolName)) {
// Summarize instead of showing full result
- const items = Array.isArray(input.toolResult)
- ? input.toolResult
+ const items = Array.isArray(input.toolResult)
+ ? input.toolResult
: input.toolResult?.items || [];
-
+
return {
modifiedResult: {
summary: `Found ${items.length} items`,
@@ -321,6 +327,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/hooks/pre-tool-use.md b/docs/hooks/pre-tool-use.md
index ac12df4fa..87f785d74 100644
--- a/docs/hooks/pre-tool-use.md
+++ b/docs/hooks/pre-tool-use.md
@@ -105,6 +105,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -186,6 +187,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -209,6 +211,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -222,10 +225,10 @@ const session = await client.createSession({
onPreToolUse: async (input) => {
if (input.toolName === "read_file" || input.toolName === "write_file") {
const args = input.toolArgs as { path: string };
- const isAllowed = ALLOWED_DIRECTORIES.some(dir =>
+ const isAllowed = ALLOWED_DIRECTORIES.some(dir =>
args.path.startsWith(dir)
);
-
+
if (!isAllowed) {
return {
permissionDecision: "deny",
@@ -236,6 +239,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -253,6 +257,7 @@ const session = await client.createSession({
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -271,6 +276,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/hooks/session-lifecycle.md b/docs/hooks/session-lifecycle.md
index 74f4666f4..5b03cab74 100644
--- a/docs/hooks/session-lifecycle.md
+++ b/docs/hooks/session-lifecycle.md
@@ -92,9 +92,9 @@ const session = await client.createSession({
hooks: {
onSessionStart: async (input, invocation) => {
console.log(`Session ${invocation.sessionId} started (${input.source})`);
-
+
const projectInfo = await detectProjectType(input.cwd);
-
+
return {
additionalContext: `
This is a ${projectInfo.type} project.
@@ -104,6 +104,7 @@ Package manager: ${projectInfo.packageManager}
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -115,9 +116,9 @@ Package manager: ${projectInfo.packageManager}
```python
async def on_session_start(input_data, invocation):
print(f"Session {invocation['session_id']} started ({input_data['source']})")
-
+
project_info = await detect_project_type(input_data["cwd"])
-
+
return {
"additionalContext": f"""
This is a {project_info['type']} project.
@@ -142,7 +143,7 @@ const session = await client.createSession({
if (input.source === "resume") {
// Load previous session state
const previousState = await loadSessionState(invocation.sessionId);
-
+
return {
additionalContext: `
Session resumed. Previous context:
@@ -154,6 +155,7 @@ Session resumed. Previous context:
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -164,9 +166,9 @@ const session = await client.createSession({
hooks: {
onSessionStart: async () => {
const preferences = await loadUserPreferences();
-
+
const contextParts = [];
-
+
if (preferences.language) {
contextParts.push(`Preferred language: ${preferences.language}`);
}
@@ -176,12 +178,13 @@ const session = await client.createSession({
if (preferences.verbosity === "concise") {
contextParts.push("Keep responses brief and to the point.");
}
-
+
return {
additionalContext: contextParts.join("\n"),
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -289,17 +292,18 @@ const session = await client.createSession({
onSessionEnd: async (input, invocation) => {
const startTime = sessionStartTimes.get(invocation.sessionId);
const duration = startTime ? input.timestamp - startTime : 0;
-
+
await recordMetrics({
sessionId: invocation.sessionId,
duration,
endReason: input.reason,
});
-
+
sessionStartTimes.delete(invocation.sessionId);
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -318,13 +322,13 @@ async def on_session_start(input_data, invocation):
async def on_session_end(input_data, invocation):
start_time = session_start_times.get(invocation["session_id"])
duration = input_data["timestamp"] - start_time if start_time else 0
-
+
await record_metrics({
"session_id": invocation["session_id"],
"duration": duration,
"end_reason": input_data["reason"],
})
-
+
session_start_times.pop(invocation["session_id"], None)
return None
@@ -351,7 +355,7 @@ const session = await client.createSession({
},
onSessionEnd: async (input, invocation) => {
const resources = sessionResources.get(invocation.sessionId);
-
+
if (resources) {
// Clean up temp files
for (const file of resources.tempFiles) {
@@ -359,11 +363,12 @@ const session = await client.createSession({
}
sessionResources.delete(invocation.sessionId);
}
-
+
console.log(`Session ${invocation.sessionId} ended: ${input.reason}`);
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -384,6 +389,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -395,10 +401,10 @@ const sessionData: Record {
- sessionData[invocation.sessionId] = {
- prompts: 0,
- tools: 0,
- startTime: input.timestamp
+ sessionData[invocation.sessionId] = {
+ prompts: 0,
+ tools: 0,
+ startTime: input.timestamp
};
return null;
},
@@ -420,11 +426,12 @@ Session Summary:
Tool calls: ${data.tools}
End reason: ${input.reason}
`.trim());
-
+
delete sessionData[invocation.sessionId];
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/hooks/user-prompt-submitted.md b/docs/hooks/user-prompt-submitted.md
index 3205b95cd..a4f74190e 100644
--- a/docs/hooks/user-prompt-submitted.md
+++ b/docs/hooks/user-prompt-submitted.md
@@ -93,6 +93,7 @@ const session = await client.createSession({
return null; // Pass through unchanged
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -157,7 +158,7 @@ const session = await client.createSession({
hooks: {
onUserPromptSubmitted: async (input) => {
const projectInfo = await getProjectInfo();
-
+
return {
additionalContext: `
Project: ${projectInfo.name}
@@ -167,6 +168,7 @@ Framework: ${projectInfo.framework}
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -194,6 +196,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -221,6 +224,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -242,6 +246,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -258,24 +263,25 @@ const session = await client.createSession({
hooks: {
onUserPromptSubmitted: async (input) => {
const prefs: UserPreferences = await loadUserPreferences();
-
+
const contextParts = [];
-
+
if (prefs.codeStyle === "concise") {
contextParts.push("User prefers concise code with minimal comments.");
} else {
contextParts.push("User prefers verbose code with detailed comments.");
}
-
+
if (prefs.experienceLevel === "beginner") {
contextParts.push("Explain concepts in simple terms.");
}
-
+
return {
additionalContext: contextParts.join(" "),
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -290,23 +296,24 @@ const session = await client.createSession({
hooks: {
onUserPromptSubmitted: async (input) => {
const now = Date.now();
-
+
// Remove timestamps outside the window
while (promptTimestamps.length > 0 && promptTimestamps[0] < now - RATE_WINDOW) {
promptTimestamps.shift();
}
-
+
if (promptTimestamps.length >= RATE_LIMIT) {
return {
reject: true,
rejectReason: `Rate limit exceeded. Please wait before sending more prompts.`,
};
}
-
+
promptTimestamps.push(now);
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -343,6 +350,7 @@ const session = await client.createSession({
return null;
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
diff --git a/docs/mcp/overview.md b/docs/mcp/overview.md
index aa2fba668..9ffba8cf4 100644
--- a/docs/mcp/overview.md
+++ b/docs/mcp/overview.md
@@ -52,6 +52,7 @@ const session = await client.createSession({
tools: ["*"],
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -180,6 +181,7 @@ async function main() {
tools: ["*"],
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
console.log("Session created:", session.sessionId);
diff --git a/nodejs/README.md b/nodejs/README.md
index 31558b8ab..acc1dde58 100644
--- a/nodejs/README.md
+++ b/nodejs/README.md
@@ -35,6 +35,7 @@ await client.start();
// Create a session
const session = await client.createSession({
model: "gpt-5",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Wait for response using typed event handlers
@@ -93,7 +94,7 @@ Stop the server and close all sessions. Returns a list of any errors encountered
Force stop the CLI server without graceful cleanup. Use when `stop()` takes too long.
-##### `createSession(config?: SessionConfig): Promise`
+##### `createSession(config: SessionConfig): Promise`
Create a new conversation session.
@@ -106,10 +107,11 @@ Create a new conversation session.
- `systemMessage?: SystemMessageConfig` - System message customization (see below)
- `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below)
- `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
+- `onPermissionRequest: PermissionHandler` - Handler for permission requests from the server. See [Permission Control](../docs/compatibility.md#permission-control) section.
- `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
- `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
-##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise`
+##### `resumeSession(sessionId: string, config: ResumeSessionConfig): Promise`
Resume an existing session. Returns the session with `workspacePath` populated if infinite sessions were enabled.
@@ -314,6 +316,7 @@ Enable streaming to receive assistant response chunks as they're generated:
const session = await client.createSession({
model: "gpt-5",
streaming: true,
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Wait for completion using typed event handlers
@@ -397,6 +400,7 @@ const session = await client.createSession({
},
}),
],
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -417,6 +421,7 @@ const session = await client.createSession({
`,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -431,6 +436,7 @@ const session = await client.createSession({
mode: "replace",
content: "You are a helpful assistant.",
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -440,7 +446,7 @@ By default, sessions use **infinite sessions** which automatically manage contex
```typescript
// Default: infinite sessions enabled with default thresholds
-const session = await client.createSession({ model: "gpt-5" });
+const session = await client.createSession({ model: "gpt-5", onPermissionRequest: async () => ({ kind: "approved" }) });
// Access the workspace path for checkpoints and files
console.log(session.workspacePath);
@@ -454,12 +460,14 @@ const session = await client.createSession({
backgroundCompactionThreshold: 0.80, // Start compacting at 80% context usage
bufferExhaustionThreshold: 0.95, // Block at 95% until compaction completes
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
// Disable infinite sessions
const session = await client.createSession({
model: "gpt-5",
infiniteSessions: { enabled: false },
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -471,8 +479,8 @@ When enabled, sessions emit compaction events:
### Multiple Sessions
```typescript
-const session1 = await client.createSession({ model: "gpt-5" });
-const session2 = await client.createSession({ model: "claude-sonnet-4.5" });
+const session1 = await client.createSession({ model: "gpt-5", onPermissionRequest: async () => ({ kind: "approved" }) });
+const session2 = await client.createSession({ model: "claude-sonnet-4.5", onPermissionRequest: async () => ({ kind: "approved" }) });
// Both sessions are independent
await session1.sendAndWait({ prompt: "Hello from session 1" });
@@ -485,6 +493,7 @@ await session2.sendAndWait({ prompt: "Hello from session 2" });
const session = await client.createSession({
sessionId: "my-custom-session-id",
model: "gpt-5",
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -526,6 +535,7 @@ const session = await client.createSession({
baseUrl: "http://localhost:11434/v1", // Ollama endpoint
// apiKey not required for Ollama
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
await session.sendAndWait({ prompt: "Hello!" });
@@ -541,6 +551,7 @@ const session = await client.createSession({
baseUrl: "https://my-api.example.com/v1",
apiKey: process.env.MY_API_KEY,
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -557,6 +568,7 @@ const session = await client.createSession({
apiVersion: "2024-10-21",
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -588,6 +600,7 @@ const session = await client.createSession({
wasFreeform: true, // Whether the answer was freeform (not from choices)
};
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -648,6 +661,7 @@ const session = await client.createSession({
};
},
},
+ onPermissionRequest: async () => ({ kind: "approved" }),
});
```
@@ -664,7 +678,7 @@ const session = await client.createSession({
```typescript
try {
- const session = await client.createSession();
+ const session = await client.createSession({ onPermissionRequest: async () => ({ kind: "approved" }) });
await session.send({ prompt: "Hello" });
} catch (error) {
console.error("Error:", error.message);
diff --git a/nodejs/examples/basic-example.ts b/nodejs/examples/basic-example.ts
index b0b993138..2556cd27e 100644
--- a/nodejs/examples/basic-example.ts
+++ b/nodejs/examples/basic-example.ts
@@ -22,7 +22,7 @@ const lookupFactTool = defineTool("lookup_fact", {
// Create client - will auto-start CLI server (searches PATH for "copilot")
const client = new CopilotClient({ logLevel: "info" });
-const session = await client.createSession({ tools: [lookupFactTool] });
+const session = await client.createSession({ tools: [lookupFactTool], onPermissionRequest: async () => ({ kind: "approved" }) });
console.log(`✅ Session created: ${session.sessionId}\n`);
// Listen to events