🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@github/copilot-sdk

Package Overview
Dependencies
Maintainers
23
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@github/copilot-sdk - npm Package Compare versions

Comparing version
1.0.3
to
1.0.4
+629
dist/cjs/copilotRequestHandler.js
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var copilotRequestHandler_exports = {};
__export(copilotRequestHandler_exports, {
CopilotRequestHandler: () => CopilotRequestHandler,
CopilotWebSocketCloseStatus: () => CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder: () => CopilotWebSocketForwarder,
CopilotWebSocketHandler: () => CopilotWebSocketHandler,
createCopilotRequestAdapter: () => createCopilotRequestAdapter
});
module.exports = __toCommonJS(copilotRequestHandler_exports);
const sharedTextDecoder = new TextDecoder("utf-8", { fatal: false });
const sharedTextEncoder = new TextEncoder();
const kBridge = /* @__PURE__ */ Symbol("copilotWebSocketResponseBridge");
const kCompletion = /* @__PURE__ */ Symbol("copilotWebSocketCompletion");
const kOpen = /* @__PURE__ */ Symbol("copilotWebSocketOpen");
const kSuppressCloseOnDispose = /* @__PURE__ */ Symbol("copilotWebSocketSuppressCloseOnDispose");
const kHandle = /* @__PURE__ */ Symbol("copilotRequestHandle");
class CopilotWebSocketCloseStatus {
constructor(description, errorCode, error) {
this.description = description;
this.errorCode = errorCode;
this.error = error;
}
description;
errorCode;
error;
static normalClosure = new CopilotWebSocketCloseStatus();
}
class CopilotWebSocketHandler {
#response;
#completion;
#resolveCompletion;
#closed = false;
[kSuppressCloseOnDispose] = false;
context;
constructor(context) {
this.context = context;
const bridge = context[kBridge];
if (!bridge) {
throw new Error("WebSocket response bridge is not attached");
}
this.#response = bridge;
this.#completion = new Promise((resolve) => {
this.#resolveCompletion = resolve;
});
}
async sendResponseMessage(data) {
await this.#response.write(data);
}
async close(status = CopilotWebSocketCloseStatus.normalClosure) {
if (this.#closed) {
return;
}
this.#closed = true;
if (status.error) {
await this.#response.error({
message: status.description ?? status.error.message,
code: status.errorCode
});
} else {
await this.#response.end();
}
this.#resolveCompletion(status);
}
async [Symbol.asyncDispose]() {
if (!this[kSuppressCloseOnDispose] && !this.#closed) {
await this.close(CopilotWebSocketCloseStatus.normalClosure);
}
}
/** @internal */
get [kCompletion]() {
return this.#completion;
}
/** @internal */
async [kOpen]() {
}
}
class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
#upstream = null;
constructor(context) {
super(context);
}
sendRequestMessage(data) {
if (this.#upstream?.readyState !== WebSocket.OPEN) {
return;
}
this.#upstream.send(data);
}
/** @internal */
async [kOpen]() {
if (this.#upstream) {
return;
}
const upstream = new WebSocket(this.context.url);
upstream.binaryType = "arraybuffer";
this.#upstream = upstream;
upstream.addEventListener("message", (event) => {
void this.sendResponseMessage(normalizeWsData(event.data)).catch(
async (err) => {
await this.close(
new CopilotWebSocketCloseStatus(
err instanceof Error ? err.message : String(err),
void 0,
err instanceof Error ? err : new Error(String(err))
)
);
}
);
});
upstream.addEventListener("close", () => {
void this.close(CopilotWebSocketCloseStatus.normalClosure);
});
upstream.addEventListener("error", () => {
void this.close(
new CopilotWebSocketCloseStatus(
"WebSocket error",
void 0,
new Error("WebSocket error")
)
);
});
await new Promise((resolve, reject) => {
if (upstream.readyState === WebSocket.OPEN) {
resolve();
return;
}
upstream.addEventListener("open", () => resolve(), { once: true });
upstream.addEventListener("error", () => reject(new Error("WebSocket error")), {
once: true
});
});
}
async close(status = CopilotWebSocketCloseStatus.normalClosure) {
try {
if (this.#upstream?.readyState === WebSocket.OPEN || this.#upstream?.readyState === WebSocket.CONNECTING) {
this.#upstream?.close();
}
} catch {
}
await super.close(status);
}
async [Symbol.asyncDispose]() {
try {
await super[Symbol.asyncDispose]();
} finally {
try {
this.#upstream?.close();
} catch {
}
}
}
}
class CopilotRequestHandler {
sendRequest(request, ctx) {
return fetch(request, { signal: ctx.signal });
}
openWebSocket(ctx) {
return Promise.resolve(new CopilotWebSocketForwarder(ctx));
}
/** @internal */
async [kHandle](exchange) {
const bridge = new CopilotWebSocketResponseBridge(exchange);
const ctx = {
requestId: exchange.requestId,
sessionId: exchange.sessionId,
transport: exchange.transport,
url: exchange.url,
headers: exchange.headers,
signal: exchange.signal,
[kBridge]: bridge
};
if (exchange.transport === "websocket") {
await this.#handleWebSocket(exchange, ctx);
} else {
await this.#handleHttp(exchange, ctx);
}
}
async #handleHttp(exchange, ctx) {
const request = await buildFetchRequest(exchange);
const response = await this.sendRequest(request, ctx);
await streamResponse(response, exchange);
}
async #handleWebSocket(exchange, ctx) {
const handler = await this.openWebSocket(ctx);
try {
await handler[kOpen]();
await ctx[kBridge].start();
let cancelled;
const clientSettled = (async () => {
for await (const chunk of exchange.requestBody) {
await handler.sendRequestMessage(decodeFrame(chunk));
}
return "client-complete";
})().catch((err) => {
cancelled = err;
return "client-error";
});
const first = await Promise.race([
clientSettled,
handler[kCompletion].then(() => "server-done")
]);
if (first === "client-error") {
handler[kSuppressCloseOnDispose] = true;
throw cancelled instanceof Error ? cancelled : new Error(String(cancelled));
}
if (first === "client-complete") {
await handler.close(CopilotWebSocketCloseStatus.normalClosure);
await handler[kCompletion];
return;
}
const status = await handler[kCompletion];
if (status.error) {
throw status.error;
}
} finally {
await handler[Symbol.asyncDispose]();
}
}
}
function createCopilotRequestAdapter(handler, getServerRpc) {
const pending = /* @__PURE__ */ new Map();
function getOrCreate(requestId) {
let exchange = pending.get(requestId);
if (!exchange) {
exchange = new CopilotRequestExchange(requestId, getServerRpc);
pending.set(requestId, exchange);
}
return exchange;
}
async function run(exchange) {
try {
await handler[kHandle](exchange);
if (!exchange.finished) {
await finalize(
exchange,
502,
"Copilot request handler returned without finalising the response (call responseBody.end() or .error())."
);
}
} catch (err) {
if (exchange.cancelled || exchange.signal.aborted) {
await finalize(exchange, 499, "Request cancelled by runtime", "cancelled");
return;
}
const message = err instanceof Error ? err.message : String(err);
await finalize(exchange, 502, message);
} finally {
pending.delete(exchange.requestId);
}
}
return {
async httpRequestStart(params) {
const exchange = getOrCreate(params.requestId);
exchange.setContext(params);
void run(exchange);
return {};
},
async httpRequestChunk(params) {
routeChunk(getOrCreate(params.requestId), params);
return {};
}
};
}
async function finalize(exchange, status, message, code) {
if (exchange.finished) {
return;
}
try {
if (!exchange.started) {
await exchange.startResponse({ status, headers: {} });
}
await exchange.errorResponse({ message, code });
} catch {
}
}
function routeChunk(exchange, params) {
if (params.cancel) {
exchange.pushCancel(params.cancelReason);
return;
}
if (params.data && params.data.length > 0) {
exchange.pushChunk(decodeChunkData(params.data, !!params.binary));
}
if (params.end) {
exchange.pushEnd();
}
}
class CopilotRequestExchange {
requestId;
sessionId;
method = "GET";
url = "";
headers = {};
transport = "http";
#getServerRpc;
#abort = new AbortController();
#buffer = [];
#waker = null;
#drained = false;
#started = false;
#finished = false;
#cancelled = false;
constructor(requestId, getServerRpc) {
this.requestId = requestId;
this.#getServerRpc = getServerRpc;
}
/** Fill in the request context once the matching start frame arrives. */
setContext(params) {
this.sessionId = params.sessionId;
this.method = params.method;
this.url = params.url;
this.headers = params.headers;
this.transport = params.transport ?? "http";
}
get signal() {
return this.#abort.signal;
}
get started() {
return this.#started;
}
get finished() {
return this.#finished;
}
get cancelled() {
return this.#cancelled;
}
// --- Request body feed (driven by the adapter as chunk frames arrive) ---
pushChunk(chunk) {
this.#push({ chunk });
}
pushEnd() {
this.#push({ end: true });
}
pushCancel(reason) {
this.#cancelled = true;
this.#abort.abort();
this.#push({ cancel: { reason } });
}
#push(item) {
this.#buffer.push(item);
const w = this.#waker;
this.#waker = null;
w?.();
}
/**
* Request body bytes, yielded as they arrive. A cancel frame surfaces as a
* thrown error so the handler's upstream call is torn down.
*/
get requestBody() {
return {
[Symbol.asyncIterator]: () => ({
next: async () => {
if (this.#drained) {
return { value: void 0, done: true };
}
while (this.#buffer.length === 0) {
await new Promise((resolve) => {
this.#waker = resolve;
});
}
const item = this.#buffer.shift();
if (item.cancel) {
this.#drained = true;
throw new Error(
item.cancel.reason ? `Request cancelled by runtime: ${item.cancel.reason}` : "Request cancelled by runtime"
);
}
if (item.end) {
this.#drained = true;
return { value: void 0, done: true };
}
return { value: item.chunk ?? new Uint8Array(), done: false };
}
})
};
}
// --- Response emit (driven by the handler). Strict state machine: ---
// startResponse once -> 0..N writeResponse -> exactly one of
// endResponse / errorResponse.
async startResponse(init) {
if (this.#started) {
throw new Error("Copilot request response start() called twice.");
}
if (this.#finished) {
throw new Error("Copilot request response already finished.");
}
this.#started = true;
await this.#rpc().llmInference.httpResponseStart({
requestId: this.requestId,
status: init.status,
statusText: init.statusText,
headers: init.headers ?? {}
});
}
async writeResponse(data) {
if (this.#cancelled) {
throw new Error("Copilot request was cancelled by the runtime.");
}
if (!this.#started) {
throw new Error("Copilot request response write() called before start().");
}
if (this.#finished) {
throw new Error("Copilot request response write() called after end()/error().");
}
const isString = typeof data === "string";
await this.#rpc().llmInference.httpResponseChunk({
requestId: this.requestId,
data: isString ? data : Buffer.from(data).toString("base64"),
binary: !isString,
end: false
});
}
async endResponse() {
if (this.#finished) {
return;
}
this.#finished = true;
await this.#rpc().llmInference.httpResponseChunk({
requestId: this.requestId,
data: "",
end: true
});
}
async errorResponse(error) {
if (this.#finished) {
return;
}
this.#finished = true;
await this.#rpc().llmInference.httpResponseChunk({
requestId: this.requestId,
data: "",
end: true,
error: { message: error.message, code: error.code }
});
}
#rpc() {
const r = this.#getServerRpc();
if (!r) {
throw new Error("Copilot request response used after RPC connection closed.");
}
return r;
}
}
const FORBIDDEN_REQUEST_HEADERS = /* @__PURE__ */ new Set([
"host",
"connection",
"content-length",
"transfer-encoding",
"keep-alive",
"upgrade",
"proxy-connection",
"te",
"trailer"
]);
async function buildFetchRequest(exchange) {
const headers = new Headers();
for (const [name, values] of Object.entries(exchange.headers)) {
if (!values) {
continue;
}
if (FORBIDDEN_REQUEST_HEADERS.has(name.toLowerCase())) {
continue;
}
for (const value of values) {
headers.append(name, value);
}
}
const method = exchange.method.toUpperCase();
const hasBody = method !== "GET" && method !== "HEAD";
let body;
if (hasBody) {
const buffered = await drainAsync(exchange.requestBody);
if (buffered.length > 0) {
body = buffered;
}
} else {
await drainAsync(exchange.requestBody);
}
return new Request(exchange.url, { method, headers, body });
}
async function drainAsync(stream) {
const parts = [];
let total = 0;
for await (const chunk of stream) {
parts.push(chunk);
total += chunk.byteLength;
}
if (parts.length === 0) {
return new Uint8Array(0);
}
if (parts.length === 1) {
return parts[0];
}
const out = new Uint8Array(total);
let off = 0;
for (const part of parts) {
out.set(part, off);
off += part.byteLength;
}
return out;
}
async function streamResponse(response, exchange) {
await exchange.startResponse({
status: response.status,
statusText: response.statusText || void 0,
headers: headersToMultiMap(response.headers)
});
const body = response.body;
if (!body) {
await exchange.endResponse();
return;
}
const reader = body.getReader();
try {
for (; ; ) {
const { value, done } = await reader.read();
if (done) {
break;
}
if (value && value.byteLength > 0) {
await exchange.writeResponse(value);
}
}
await exchange.endResponse();
} finally {
reader.releaseLock();
}
}
function headersToMultiMap(headers) {
const out = {};
headers.forEach((value, name) => {
if (name.toLowerCase() === "set-cookie") {
return;
}
const list = out[name] ?? (out[name] = []);
list.push(value);
});
const setCookies = headers.getSetCookie();
if (setCookies.length > 0) {
out["set-cookie"] = setCookies;
}
return out;
}
function decodeChunkData(data, binary) {
if (binary) {
return new Uint8Array(Buffer.from(data, "base64"));
}
return sharedTextEncoder.encode(data);
}
function decodeFrame(chunk) {
return sharedTextDecoder.decode(chunk);
}
function normalizeWsData(data) {
if (typeof data === "string") {
return data;
}
if (data instanceof Uint8Array) {
return data;
}
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
return new Uint8Array();
}
class CopilotWebSocketResponseBridge {
#exchange;
#started = false;
#completed = false;
#serial = Promise.resolve();
constructor(exchange) {
this.#exchange = exchange;
}
/** Emit the 101 upgrade head now, acknowledging the WebSocket connect. */
start() {
return this.#run(false, () => Promise.resolve());
}
write(data) {
return this.#run(false, () => this.#exchange.writeResponse(data));
}
end() {
return this.#run(true, () => this.#exchange.endResponse());
}
error(error) {
return this.#run(true, () => this.#exchange.errorResponse(error));
}
#run(terminal, action) {
const task = this.#serial.then(async () => {
if (this.#completed) {
return;
}
if (!this.#started) {
this.#started = true;
await this.#exchange.startResponse({ status: 101, headers: {} });
}
if (terminal) {
this.#completed = true;
}
await action();
});
this.#serial = task.catch(() => {
});
return task;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CopilotRequestHandler,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
CopilotWebSocketHandler,
createCopilotRequestAdapter
});
import type { LlmInferenceHeaders } from "./generated/rpc.js";
declare const kSuppressCloseOnDispose: unique symbol;
/**
* Per-request context handed to every {@link CopilotRequestHandler} hook.
*
* @experimental
*/
export interface CopilotRequestContext {
readonly requestId: string;
readonly sessionId?: string;
readonly transport: "http" | "websocket";
url: string;
headers: LlmInferenceHeaders;
readonly signal: AbortSignal;
}
/**
* Terminal status for a callback-owned WebSocket connection.
*
* @experimental
*/
export declare class CopilotWebSocketCloseStatus {
readonly description?: string | undefined;
readonly errorCode?: string | undefined;
readonly error?: Error | undefined;
static readonly normalClosure: CopilotWebSocketCloseStatus;
constructor(description?: string | undefined, errorCode?: string | undefined, error?: Error | undefined);
}
/**
* Lower-level WebSocket handler with no upstream connection.
*
* This is the abstract base shared by all WebSocket handlers. It does not open
* or forward to any upstream server on its own — subclass it directly only when
* you want to service a fully synthetic connection yourself (e.g. answer the
* runtime without any real backend). For the common case of mutating and
* forwarding traffic to the real upstream, subclass {@link CopilotWebSocketForwarder}
* instead, which connects upstream and forwards by default.
*
* @experimental
*/
export declare abstract class CopilotWebSocketHandler implements AsyncDisposable {
#private;
[kSuppressCloseOnDispose]: boolean;
protected readonly context: CopilotRequestContext;
protected constructor(context: CopilotRequestContext);
sendResponseMessage(data: string | Uint8Array): Promise<void>;
close(status?: CopilotWebSocketCloseStatus): Promise<void>;
abstract sendRequestMessage(data: string | Uint8Array): Promise<void> | void;
[Symbol.asyncDispose](): Promise<void>;
}
/**
* WebSocket handler that connects to the real upstream and forwards traffic by
* default. This is the type returned by the default
* {@link CopilotRequestHandler.openWebSocket}.
*
* Override nothing to get full pass-through. To mutate traffic, subclass this
* type and override a message hook, then call `super` to keep forwarding to the
* upstream. (Subclassing {@link CopilotWebSocketHandler} instead would drop
* forwarding entirely.)
*
* @experimental
*/
export declare class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
#private;
constructor(context: CopilotRequestContext);
sendRequestMessage(data: string | Uint8Array): void;
close(status?: CopilotWebSocketCloseStatus): Promise<void>;
[Symbol.asyncDispose](): Promise<void>;
}
/**
* Base class for SDK consumers who want to observe or mutate the outbound
* model-layer requests the runtime issues (for both CAPI and BYOK providers).
* Subclass and override {@link sendRequest} or {@link openWebSocket}; an
* instance that overrides nothing is a transparent pass-through.
*
* @experimental
*/
export declare class CopilotRequestHandler {
#private;
protected sendRequest(request: Request, ctx: CopilotRequestContext): Promise<Response>;
protected openWebSocket(ctx: CopilotRequestContext): Promise<CopilotWebSocketHandler>;
}
export {};
const sharedTextDecoder = new TextDecoder("utf-8", { fatal: false });
const sharedTextEncoder = new TextEncoder();
const kBridge = /* @__PURE__ */ Symbol("copilotWebSocketResponseBridge");
const kCompletion = /* @__PURE__ */ Symbol("copilotWebSocketCompletion");
const kOpen = /* @__PURE__ */ Symbol("copilotWebSocketOpen");
const kSuppressCloseOnDispose = /* @__PURE__ */ Symbol("copilotWebSocketSuppressCloseOnDispose");
const kHandle = /* @__PURE__ */ Symbol("copilotRequestHandle");
class CopilotWebSocketCloseStatus {
constructor(description, errorCode, error) {
this.description = description;
this.errorCode = errorCode;
this.error = error;
}
description;
errorCode;
error;
static normalClosure = new CopilotWebSocketCloseStatus();
}
class CopilotWebSocketHandler {
#response;
#completion;
#resolveCompletion;
#closed = false;
[kSuppressCloseOnDispose] = false;
context;
constructor(context) {
this.context = context;
const bridge = context[kBridge];
if (!bridge) {
throw new Error("WebSocket response bridge is not attached");
}
this.#response = bridge;
this.#completion = new Promise((resolve) => {
this.#resolveCompletion = resolve;
});
}
async sendResponseMessage(data) {
await this.#response.write(data);
}
async close(status = CopilotWebSocketCloseStatus.normalClosure) {
if (this.#closed) {
return;
}
this.#closed = true;
if (status.error) {
await this.#response.error({
message: status.description ?? status.error.message,
code: status.errorCode
});
} else {
await this.#response.end();
}
this.#resolveCompletion(status);
}
async [Symbol.asyncDispose]() {
if (!this[kSuppressCloseOnDispose] && !this.#closed) {
await this.close(CopilotWebSocketCloseStatus.normalClosure);
}
}
/** @internal */
get [kCompletion]() {
return this.#completion;
}
/** @internal */
async [kOpen]() {
}
}
class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
#upstream = null;
constructor(context) {
super(context);
}
sendRequestMessage(data) {
if (this.#upstream?.readyState !== WebSocket.OPEN) {
return;
}
this.#upstream.send(data);
}
/** @internal */
async [kOpen]() {
if (this.#upstream) {
return;
}
const upstream = new WebSocket(this.context.url);
upstream.binaryType = "arraybuffer";
this.#upstream = upstream;
upstream.addEventListener("message", (event) => {
void this.sendResponseMessage(normalizeWsData(event.data)).catch(
async (err) => {
await this.close(
new CopilotWebSocketCloseStatus(
err instanceof Error ? err.message : String(err),
void 0,
err instanceof Error ? err : new Error(String(err))
)
);
}
);
});
upstream.addEventListener("close", () => {
void this.close(CopilotWebSocketCloseStatus.normalClosure);
});
upstream.addEventListener("error", () => {
void this.close(
new CopilotWebSocketCloseStatus(
"WebSocket error",
void 0,
new Error("WebSocket error")
)
);
});
await new Promise((resolve, reject) => {
if (upstream.readyState === WebSocket.OPEN) {
resolve();
return;
}
upstream.addEventListener("open", () => resolve(), { once: true });
upstream.addEventListener("error", () => reject(new Error("WebSocket error")), {
once: true
});
});
}
async close(status = CopilotWebSocketCloseStatus.normalClosure) {
try {
if (this.#upstream?.readyState === WebSocket.OPEN || this.#upstream?.readyState === WebSocket.CONNECTING) {
this.#upstream?.close();
}
} catch {
}
await super.close(status);
}
async [Symbol.asyncDispose]() {
try {
await super[Symbol.asyncDispose]();
} finally {
try {
this.#upstream?.close();
} catch {
}
}
}
}
class CopilotRequestHandler {
sendRequest(request, ctx) {
return fetch(request, { signal: ctx.signal });
}
openWebSocket(ctx) {
return Promise.resolve(new CopilotWebSocketForwarder(ctx));
}
/** @internal */
async [kHandle](exchange) {
const bridge = new CopilotWebSocketResponseBridge(exchange);
const ctx = {
requestId: exchange.requestId,
sessionId: exchange.sessionId,
transport: exchange.transport,
url: exchange.url,
headers: exchange.headers,
signal: exchange.signal,
[kBridge]: bridge
};
if (exchange.transport === "websocket") {
await this.#handleWebSocket(exchange, ctx);
} else {
await this.#handleHttp(exchange, ctx);
}
}
async #handleHttp(exchange, ctx) {
const request = await buildFetchRequest(exchange);
const response = await this.sendRequest(request, ctx);
await streamResponse(response, exchange);
}
async #handleWebSocket(exchange, ctx) {
const handler = await this.openWebSocket(ctx);
try {
await handler[kOpen]();
await ctx[kBridge].start();
let cancelled;
const clientSettled = (async () => {
for await (const chunk of exchange.requestBody) {
await handler.sendRequestMessage(decodeFrame(chunk));
}
return "client-complete";
})().catch((err) => {
cancelled = err;
return "client-error";
});
const first = await Promise.race([
clientSettled,
handler[kCompletion].then(() => "server-done")
]);
if (first === "client-error") {
handler[kSuppressCloseOnDispose] = true;
throw cancelled instanceof Error ? cancelled : new Error(String(cancelled));
}
if (first === "client-complete") {
await handler.close(CopilotWebSocketCloseStatus.normalClosure);
await handler[kCompletion];
return;
}
const status = await handler[kCompletion];
if (status.error) {
throw status.error;
}
} finally {
await handler[Symbol.asyncDispose]();
}
}
}
function createCopilotRequestAdapter(handler, getServerRpc) {
const pending = /* @__PURE__ */ new Map();
function getOrCreate(requestId) {
let exchange = pending.get(requestId);
if (!exchange) {
exchange = new CopilotRequestExchange(requestId, getServerRpc);
pending.set(requestId, exchange);
}
return exchange;
}
async function run(exchange) {
try {
await handler[kHandle](exchange);
if (!exchange.finished) {
await finalize(
exchange,
502,
"Copilot request handler returned without finalising the response (call responseBody.end() or .error())."
);
}
} catch (err) {
if (exchange.cancelled || exchange.signal.aborted) {
await finalize(exchange, 499, "Request cancelled by runtime", "cancelled");
return;
}
const message = err instanceof Error ? err.message : String(err);
await finalize(exchange, 502, message);
} finally {
pending.delete(exchange.requestId);
}
}
return {
async httpRequestStart(params) {
const exchange = getOrCreate(params.requestId);
exchange.setContext(params);
void run(exchange);
return {};
},
async httpRequestChunk(params) {
routeChunk(getOrCreate(params.requestId), params);
return {};
}
};
}
async function finalize(exchange, status, message, code) {
if (exchange.finished) {
return;
}
try {
if (!exchange.started) {
await exchange.startResponse({ status, headers: {} });
}
await exchange.errorResponse({ message, code });
} catch {
}
}
function routeChunk(exchange, params) {
if (params.cancel) {
exchange.pushCancel(params.cancelReason);
return;
}
if (params.data && params.data.length > 0) {
exchange.pushChunk(decodeChunkData(params.data, !!params.binary));
}
if (params.end) {
exchange.pushEnd();
}
}
class CopilotRequestExchange {
requestId;
sessionId;
method = "GET";
url = "";
headers = {};
transport = "http";
#getServerRpc;
#abort = new AbortController();
#buffer = [];
#waker = null;
#drained = false;
#started = false;
#finished = false;
#cancelled = false;
constructor(requestId, getServerRpc) {
this.requestId = requestId;
this.#getServerRpc = getServerRpc;
}
/** Fill in the request context once the matching start frame arrives. */
setContext(params) {
this.sessionId = params.sessionId;
this.method = params.method;
this.url = params.url;
this.headers = params.headers;
this.transport = params.transport ?? "http";
}
get signal() {
return this.#abort.signal;
}
get started() {
return this.#started;
}
get finished() {
return this.#finished;
}
get cancelled() {
return this.#cancelled;
}
// --- Request body feed (driven by the adapter as chunk frames arrive) ---
pushChunk(chunk) {
this.#push({ chunk });
}
pushEnd() {
this.#push({ end: true });
}
pushCancel(reason) {
this.#cancelled = true;
this.#abort.abort();
this.#push({ cancel: { reason } });
}
#push(item) {
this.#buffer.push(item);
const w = this.#waker;
this.#waker = null;
w?.();
}
/**
* Request body bytes, yielded as they arrive. A cancel frame surfaces as a
* thrown error so the handler's upstream call is torn down.
*/
get requestBody() {
return {
[Symbol.asyncIterator]: () => ({
next: async () => {
if (this.#drained) {
return { value: void 0, done: true };
}
while (this.#buffer.length === 0) {
await new Promise((resolve) => {
this.#waker = resolve;
});
}
const item = this.#buffer.shift();
if (item.cancel) {
this.#drained = true;
throw new Error(
item.cancel.reason ? `Request cancelled by runtime: ${item.cancel.reason}` : "Request cancelled by runtime"
);
}
if (item.end) {
this.#drained = true;
return { value: void 0, done: true };
}
return { value: item.chunk ?? new Uint8Array(), done: false };
}
})
};
}
// --- Response emit (driven by the handler). Strict state machine: ---
// startResponse once -> 0..N writeResponse -> exactly one of
// endResponse / errorResponse.
async startResponse(init) {
if (this.#started) {
throw new Error("Copilot request response start() called twice.");
}
if (this.#finished) {
throw new Error("Copilot request response already finished.");
}
this.#started = true;
await this.#rpc().llmInference.httpResponseStart({
requestId: this.requestId,
status: init.status,
statusText: init.statusText,
headers: init.headers ?? {}
});
}
async writeResponse(data) {
if (this.#cancelled) {
throw new Error("Copilot request was cancelled by the runtime.");
}
if (!this.#started) {
throw new Error("Copilot request response write() called before start().");
}
if (this.#finished) {
throw new Error("Copilot request response write() called after end()/error().");
}
const isString = typeof data === "string";
await this.#rpc().llmInference.httpResponseChunk({
requestId: this.requestId,
data: isString ? data : Buffer.from(data).toString("base64"),
binary: !isString,
end: false
});
}
async endResponse() {
if (this.#finished) {
return;
}
this.#finished = true;
await this.#rpc().llmInference.httpResponseChunk({
requestId: this.requestId,
data: "",
end: true
});
}
async errorResponse(error) {
if (this.#finished) {
return;
}
this.#finished = true;
await this.#rpc().llmInference.httpResponseChunk({
requestId: this.requestId,
data: "",
end: true,
error: { message: error.message, code: error.code }
});
}
#rpc() {
const r = this.#getServerRpc();
if (!r) {
throw new Error("Copilot request response used after RPC connection closed.");
}
return r;
}
}
const FORBIDDEN_REQUEST_HEADERS = /* @__PURE__ */ new Set([
"host",
"connection",
"content-length",
"transfer-encoding",
"keep-alive",
"upgrade",
"proxy-connection",
"te",
"trailer"
]);
async function buildFetchRequest(exchange) {
const headers = new Headers();
for (const [name, values] of Object.entries(exchange.headers)) {
if (!values) {
continue;
}
if (FORBIDDEN_REQUEST_HEADERS.has(name.toLowerCase())) {
continue;
}
for (const value of values) {
headers.append(name, value);
}
}
const method = exchange.method.toUpperCase();
const hasBody = method !== "GET" && method !== "HEAD";
let body;
if (hasBody) {
const buffered = await drainAsync(exchange.requestBody);
if (buffered.length > 0) {
body = buffered;
}
} else {
await drainAsync(exchange.requestBody);
}
return new Request(exchange.url, { method, headers, body });
}
async function drainAsync(stream) {
const parts = [];
let total = 0;
for await (const chunk of stream) {
parts.push(chunk);
total += chunk.byteLength;
}
if (parts.length === 0) {
return new Uint8Array(0);
}
if (parts.length === 1) {
return parts[0];
}
const out = new Uint8Array(total);
let off = 0;
for (const part of parts) {
out.set(part, off);
off += part.byteLength;
}
return out;
}
async function streamResponse(response, exchange) {
await exchange.startResponse({
status: response.status,
statusText: response.statusText || void 0,
headers: headersToMultiMap(response.headers)
});
const body = response.body;
if (!body) {
await exchange.endResponse();
return;
}
const reader = body.getReader();
try {
for (; ; ) {
const { value, done } = await reader.read();
if (done) {
break;
}
if (value && value.byteLength > 0) {
await exchange.writeResponse(value);
}
}
await exchange.endResponse();
} finally {
reader.releaseLock();
}
}
function headersToMultiMap(headers) {
const out = {};
headers.forEach((value, name) => {
if (name.toLowerCase() === "set-cookie") {
return;
}
const list = out[name] ?? (out[name] = []);
list.push(value);
});
const setCookies = headers.getSetCookie();
if (setCookies.length > 0) {
out["set-cookie"] = setCookies;
}
return out;
}
function decodeChunkData(data, binary) {
if (binary) {
return new Uint8Array(Buffer.from(data, "base64"));
}
return sharedTextEncoder.encode(data);
}
function decodeFrame(chunk) {
return sharedTextDecoder.decode(chunk);
}
function normalizeWsData(data) {
if (typeof data === "string") {
return data;
}
if (data instanceof Uint8Array) {
return data;
}
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
return new Uint8Array();
}
class CopilotWebSocketResponseBridge {
#exchange;
#started = false;
#completed = false;
#serial = Promise.resolve();
constructor(exchange) {
this.#exchange = exchange;
}
/** Emit the 101 upgrade head now, acknowledging the WebSocket connect. */
start() {
return this.#run(false, () => Promise.resolve());
}
write(data) {
return this.#run(false, () => this.#exchange.writeResponse(data));
}
end() {
return this.#run(true, () => this.#exchange.endResponse());
}
error(error) {
return this.#run(true, () => this.#exchange.errorResponse(error));
}
#run(terminal, action) {
const task = this.#serial.then(async () => {
if (this.#completed) {
return;
}
if (!this.#started) {
this.#started = true;
await this.#exchange.startResponse({ status: 101, headers: {} });
}
if (terminal) {
this.#completed = true;
}
await action();
});
this.#serial = task.catch(() => {
});
return task;
}
}
export {
CopilotRequestHandler,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
CopilotWebSocketHandler,
createCopilotRequestAdapter
};
+8
-0

@@ -25,3 +25,7 @@ "use strict";

CopilotClient: () => import_client.CopilotClient,
CopilotRequestHandler: () => import_types2.CopilotRequestHandler,
CopilotSession: () => import_session.CopilotSession,
CopilotWebSocketCloseStatus: () => import_types2.CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder: () => import_types2.CopilotWebSocketForwarder,
CopilotWebSocketHandler: () => import_types2.CopilotWebSocketHandler,
RuntimeConnection: () => import_types.RuntimeConnection,

@@ -49,3 +53,7 @@ SYSTEM_MESSAGE_SECTIONS: () => import_types2.SYSTEM_MESSAGE_SECTIONS,

CopilotClient,
CopilotRequestHandler,
CopilotSession,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
CopilotWebSocketHandler,
RuntimeConnection,

@@ -52,0 +60,0 @@ SYSTEM_MESSAGE_SECTIONS,

+80
-9

@@ -41,3 +41,3 @@ "use strict";

const instance = value;
return typeof instance.instanceId === "string" && instance.instanceId.length > 0 && typeof instance.extensionId === "string" && instance.extensionId.length > 0 && typeof instance.canvasId === "string" && instance.canvasId.length > 0 && typeof instance.reopen === "boolean" && (instance.availability === "ready" || instance.availability === "stale");
return typeof instance.instanceId === "string" && instance.instanceId.length > 0 && typeof instance.extensionId === "string" && instance.extensionId.length > 0 && typeof instance.canvasId === "string" && instance.canvasId.length > 0;
}

@@ -67,2 +67,3 @@ class CopilotSession {

canvases = /* @__PURE__ */ new Map();
bearerTokenProviders = /* @__PURE__ */ new Map();
commandHandlers = /* @__PURE__ */ new Map();

@@ -80,2 +81,3 @@ permissionHandler;

openCanvasInstances = [];
disconnected = false;
/** @internal Client session API handlers, populated by CopilotClient during create/resume. */

@@ -184,2 +186,17 @@ clientSessionApis = {};

}
/** @internal */
_markDisconnected() {
this.disconnected = true;
this.eventHandlers.clear();
this.typedEventHandlers.clear();
this.toolHandlers.clear();
this.permissionHandler = void 0;
this.userInputHandler = void 0;
this.elicitationHandler = void 0;
this.exitPlanModeHandler = void 0;
this.autoModeSwitchHandler = void 0;
this.commandHandlers.clear();
this.canvases.clear();
this.transformCallbacks?.clear();
}
on(eventTypeOrHandler, handler) {

@@ -238,2 +255,5 @@ if (typeof eventTypeOrHandler === "string" && handler) {

_handleBroadcastEvent(event) {
if (this.disconnected) {
return;
}
if (event.type === "external_tool.requested") {

@@ -344,4 +364,10 @@ const { requestId, toolName } = event.data;

}
if (this.disconnected) {
return;
}
await this.rpc.tools.handlePendingToolCall({ requestId, result });
} catch (error) {
if (this.disconnected) {
return;
}
const message = error instanceof Error ? error.message : String(error);

@@ -369,4 +395,10 @@ try {

}
if (this.disconnected) {
return;
}
await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
} catch (_error) {
if (this.disconnected) {
return;
}
try {

@@ -407,4 +439,10 @@ await this.rpc.permissions.handlePendingPermissionRequest({

await handler({ sessionId: this.sessionId, command, commandName, args });
if (this.disconnected) {
return;
}
await this.rpc.commands.handlePendingCommand({ requestId });
} catch (error) {
if (this.disconnected) {
return;
}
const message = error instanceof Error ? error.message : String(error);

@@ -506,2 +544,39 @@ try {

/**
* Registers per-provider {@link GetBearerToken} callbacks for BYOK providers
* configured with managed-identity / on-demand bearer-token auth.
*
* The runtime never receives the callback itself; the SDK strips it from the
* provider config and instead sends `hasBearerTokenProvider: true`. When the
* runtime needs a token it issues a session-scoped `providerToken.getToken`
* request, which this handler routes to the matching per-provider callback.
*
* @param providers - Map of provider name → callback, or undefined/empty to clear.
* @internal This method is called internally when creating/resuming a session.
*/
registerBearerTokenProviders(providers) {
this.bearerTokenProviders.clear();
if (!providers || providers.size === 0) {
delete this.clientSessionApis.providerToken;
return;
}
for (const [name, callback] of providers) {
this.bearerTokenProviders.set(name, callback);
}
const self = this;
this.clientSessionApis.providerToken = {
async getToken(params) {
const callback = self.bearerTokenProviders.get(params.providerName);
if (!callback) {
throw new Error(
`No bearer-token provider registered for provider "${params.providerName}"`
);
}
const token = await callback({
providerName: params.providerName
});
return { token };
}
};
}
/**
* Registers command handlers for this session.

@@ -860,13 +935,9 @@ *

async disconnect() {
if (this.disconnected) {
return;
}
await this.connection.sendRequest("session.destroy", {
sessionId: this.sessionId
});
this.eventHandlers.clear();
this.typedEventHandlers.clear();
this.toolHandlers.clear();
this.permissionHandler = void 0;
this.userInputHandler = void 0;
this.elicitationHandler = void 0;
this.exitPlanModeHandler = void 0;
this.autoModeSwitchHandler = void 0;
this._markDisconnected();
}

@@ -873,0 +944,0 @@ /** Enables `await using session = ...` syntax for automatic cleanup. */

@@ -21,2 +21,6 @@ "use strict";

__export(types_exports, {
CopilotRequestHandler: () => import_copilotRequestHandler.CopilotRequestHandler,
CopilotWebSocketCloseStatus: () => import_copilotRequestHandler.CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder: () => import_copilotRequestHandler.CopilotWebSocketForwarder,
CopilotWebSocketHandler: () => import_copilotRequestHandler.CopilotWebSocketHandler,
RuntimeConnection: () => RuntimeConnection,

@@ -32,2 +36,3 @@ SYSTEM_MESSAGE_SECTIONS: () => SYSTEM_MESSAGE_SECTIONS,

var import_sessionFsProvider = require("./sessionFsProvider.js");
var import_copilotRequestHandler = require("./copilotRequestHandler.js");
const RuntimeConnection = {

@@ -107,3 +112,6 @@ /**

const SYSTEM_MESSAGE_SECTIONS = {
identity: { description: "Agent identity preamble and mode statement" },
preamble: { description: "Agent identity preamble and mode statement" },
identity: {
description: "Section group covering the identity preamble and its sibling sub-sections (tone, tool efficiency, etc.)"
},
tone: { description: "Response style, conciseness rules, output formatting preferences" },

@@ -130,2 +138,6 @@ tool_efficiency: { description: "Tool usage patterns, parallel calling, batching guidelines" },

0 && (module.exports = {
CopilotRequestHandler,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
CopilotWebSocketHandler,
RuntimeConnection,

@@ -132,0 +144,0 @@ SYSTEM_MESSAGE_SECTIONS,

+5
-33
import { createServerRpc } from "./generated/rpc.js";
import { CopilotSession } from "./session.js";
import type { CopilotClientOptions, GetAuthStatusResponse, GetStatusResponse, ModelInfo, ResumeSessionConfig, SessionConfig, SessionLifecycleEventType, SessionLifecycleHandler, SessionListFilter, SessionMetadata, TypedSessionLifecycleHandler } from "./types.js";
/**
* Main client for interacting with the Copilot CLI.
*
* The CopilotClient manages the connection to the Copilot CLI server and provides
* methods to create and manage conversation sessions. It can either spawn a CLI
* server process or connect to an existing server.
*
* @example
* ```typescript
* import { CopilotClient } from "@github/copilot-sdk";
*
* // Create a client with default options (spawns CLI server)
* const client = new CopilotClient();
*
* // Or connect to an existing server
* const client = new CopilotClient({ connection: RuntimeConnection.forUri("localhost:3000") });
*
* // Create a session
* const session = await client.createSession({ onPermissionRequest: approveAll, model: "gpt-4" });
*
* // Send messages and handle responses
* session.on((event) => {
* if (event.type === "assistant.message") {
* console.log(event.data.content);
* }
* });
* await session.send({ prompt: "Hello!" });
*
* // Clean up
* await session.disconnect();
* await client.stop();
* ```
*/
export declare class CopilotClient {

@@ -41,2 +8,3 @@ private cliStartTimeout;

private connection;
private messageWriter;
private socket;

@@ -71,2 +39,4 @@ private runtimePort;

private sessionFsConfig;
private requestHandler;
private llmInferenceHandlers;
/**

@@ -78,2 +48,3 @@ * Typed server-scoped RPC methods.

private logDebugTiming;
private logDebug;
/**

@@ -115,2 +86,3 @@ * Creates a new CopilotClient instance.

private setupSessionFs;
private setupLlmInference;
/**

@@ -117,0 +89,0 @@ * Starts the CLI server and establishes a connection.

@@ -11,4 +11,4 @@ /**

export { Canvas, CanvasError, createCanvas, type CanvasAction, type CanvasDeclaration, type CanvasHostContext, type CanvasHostContextCapabilities, type CanvasJsonSchema, type CanvasOptions, } from "./canvas.js";
export { defineTool, approveAll, convertMcpCallToolResult, createSessionFsAdapter, SYSTEM_MESSAGE_SECTIONS, } from "./types.js";
export { defineTool, approveAll, convertMcpCallToolResult, createSessionFsAdapter, CopilotRequestHandler, CopilotWebSocketHandler, CopilotWebSocketCloseStatus, CopilotWebSocketForwarder, SYSTEM_MESSAGE_SECTIONS, } from "./types.js";
export type * from "./generated/session-events.js";
export type { CommandContext, CommandDefinition, CommandHandler, CloudSessionOptions, CloudSessionRepository, AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, CopilotClientMode, CopilotClientOptions, StdioRuntimeConnection, TcpRuntimeConnection, UriRuntimeConnection, CustomAgentConfig, ElicitationFieldValue, ElicitationHandler, ElicitationParams, ElicitationContext, ElicitationResult, ElicitationSchema, ElicitationSchemaField, ExitPlanModeHandler, ExitPlanModeRequest, ExitPlanModeResult, ExtensionInfo, ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, LargeToolOutputConfig, MemoryConfiguration, UiInputOptions, MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, DefaultAgentConfig, MessageOptions, ModelBilling, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, ModelCapabilities, ModelCapabilitiesOverride, ModelInfo, ModelPolicy, NamedProviderConfig, PermissionHandler, PermissionRequest, PermissionRequestResult, ProviderConfig, ProviderModelConfig, RemoteSessionMode, ResumeSessionConfig, SectionOverride, SectionOverrideAction, SectionTransformFn, SessionCapabilities, SessionConfig, SessionConfigBase, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionLifecycleEvent, SessionLifecycleEventMetadata, SessionLifecycleEventType, SessionLifecycleHandler, SessionCreatedEvent, SessionDeletedEvent, SessionUpdatedEvent, SessionForegroundEvent, SessionBackgroundEvent, SessionContext, SessionListFilter, SessionMetadata, SessionUiApi, SessionFsConfig, SessionFsProvider, SessionFsFileInfo, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, SessionFsSqliteProvider, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, SystemMessageReplaceConfig, SystemMessageSection, TelemetryConfig, TraceContext, TraceContextProvider, Tool, ToolHandler, ToolInvocation, ToolTelemetry, ToolResultObject, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, } from "./types.js";
export type { CommandContext, CommandDefinition, CommandHandler, CloudSessionOptions, CloudSessionRepository, AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, CopilotClientMode, CopilotClientOptions, StdioRuntimeConnection, TcpRuntimeConnection, UriRuntimeConnection, CustomAgentConfig, ElicitationFieldValue, ElicitationHandler, ElicitationParams, ElicitationContext, ElicitationResult, ElicitationSchema, ElicitationSchemaField, ExitPlanModeHandler, ExitPlanModeRequest, ExitPlanModeResult, ExtensionInfo, ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, LargeToolOutputConfig, MemoryConfiguration, UiInputOptions, MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, DefaultAgentConfig, GetBearerToken, MessageOptions, ModelBilling, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, CapiSessionOptions, ModelCapabilities, ModelCapabilitiesOverride, ModelInfo, ModelPolicy, NamedProviderConfig, PermissionHandler, PermissionRequest, PermissionRequestResult, ProviderConfig, ProviderModelConfig, ProviderTokenArgs, RemoteSessionMode, ResumeSessionConfig, SectionOverride, SectionOverrideAction, SectionTransformFn, SessionCapabilities, SessionConfig, SessionConfigBase, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionLifecycleEvent, SessionLifecycleEventMetadata, SessionLifecycleEventType, SessionLifecycleHandler, SessionCreatedEvent, SessionDeletedEvent, SessionUpdatedEvent, SessionForegroundEvent, SessionBackgroundEvent, SessionContext, SessionListFilter, SessionMetadata, SessionUiApi, SessionFsConfig, SessionFsProvider, SessionFsFileInfo, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, SessionFsSqliteProvider, CopilotRequestContext, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, SystemMessageReplaceConfig, SystemMessageSection, TelemetryConfig, TraceContext, TraceContextProvider, Tool, ToolHandler, ToolInvocation, ToolTelemetry, ToolResultObject, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, } from "./types.js";

@@ -15,2 +15,6 @@ import { CopilotClient } from "./client.js";

createSessionFsAdapter,
CopilotRequestHandler,
CopilotWebSocketHandler,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
SYSTEM_MESSAGE_SECTIONS

@@ -23,3 +27,7 @@ } from "./types.js";

CopilotClient,
CopilotRequestHandler,
CopilotSession,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
CopilotWebSocketHandler,
RuntimeConnection,

@@ -26,0 +34,0 @@ SYSTEM_MESSAGE_SECTIONS,

@@ -41,2 +41,3 @@ import { createSessionRpc } from "./generated/rpc.js";

private canvases;
private bearerTokenProviders;
private commandHandlers;

@@ -54,2 +55,3 @@ private permissionHandler?;

private openCanvasInstances;
private disconnected;
/**

@@ -56,0 +58,0 @@ * Typed session-scoped RPC methods.

@@ -18,3 +18,3 @@ import { ConnectionError, ErrorCodes, ResponseError } from "vscode-jsonrpc/node.js";

const instance = value;
return typeof instance.instanceId === "string" && instance.instanceId.length > 0 && typeof instance.extensionId === "string" && instance.extensionId.length > 0 && typeof instance.canvasId === "string" && instance.canvasId.length > 0 && typeof instance.reopen === "boolean" && (instance.availability === "ready" || instance.availability === "stale");
return typeof instance.instanceId === "string" && instance.instanceId.length > 0 && typeof instance.extensionId === "string" && instance.extensionId.length > 0 && typeof instance.canvasId === "string" && instance.canvasId.length > 0;
}

@@ -44,2 +44,3 @@ class CopilotSession {

canvases = /* @__PURE__ */ new Map();
bearerTokenProviders = /* @__PURE__ */ new Map();
commandHandlers = /* @__PURE__ */ new Map();

@@ -57,2 +58,3 @@ permissionHandler;

openCanvasInstances = [];
disconnected = false;
/** @internal Client session API handlers, populated by CopilotClient during create/resume. */

@@ -161,2 +163,17 @@ clientSessionApis = {};

}
/** @internal */
_markDisconnected() {
this.disconnected = true;
this.eventHandlers.clear();
this.typedEventHandlers.clear();
this.toolHandlers.clear();
this.permissionHandler = void 0;
this.userInputHandler = void 0;
this.elicitationHandler = void 0;
this.exitPlanModeHandler = void 0;
this.autoModeSwitchHandler = void 0;
this.commandHandlers.clear();
this.canvases.clear();
this.transformCallbacks?.clear();
}
on(eventTypeOrHandler, handler) {

@@ -215,2 +232,5 @@ if (typeof eventTypeOrHandler === "string" && handler) {

_handleBroadcastEvent(event) {
if (this.disconnected) {
return;
}
if (event.type === "external_tool.requested") {

@@ -321,4 +341,10 @@ const { requestId, toolName } = event.data;

}
if (this.disconnected) {
return;
}
await this.rpc.tools.handlePendingToolCall({ requestId, result });
} catch (error) {
if (this.disconnected) {
return;
}
const message = error instanceof Error ? error.message : String(error);

@@ -346,4 +372,10 @@ try {

}
if (this.disconnected) {
return;
}
await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
} catch (_error) {
if (this.disconnected) {
return;
}
try {

@@ -384,4 +416,10 @@ await this.rpc.permissions.handlePendingPermissionRequest({

await handler({ sessionId: this.sessionId, command, commandName, args });
if (this.disconnected) {
return;
}
await this.rpc.commands.handlePendingCommand({ requestId });
} catch (error) {
if (this.disconnected) {
return;
}
const message = error instanceof Error ? error.message : String(error);

@@ -483,2 +521,39 @@ try {

/**
* Registers per-provider {@link GetBearerToken} callbacks for BYOK providers
* configured with managed-identity / on-demand bearer-token auth.
*
* The runtime never receives the callback itself; the SDK strips it from the
* provider config and instead sends `hasBearerTokenProvider: true`. When the
* runtime needs a token it issues a session-scoped `providerToken.getToken`
* request, which this handler routes to the matching per-provider callback.
*
* @param providers - Map of provider name → callback, or undefined/empty to clear.
* @internal This method is called internally when creating/resuming a session.
*/
registerBearerTokenProviders(providers) {
this.bearerTokenProviders.clear();
if (!providers || providers.size === 0) {
delete this.clientSessionApis.providerToken;
return;
}
for (const [name, callback] of providers) {
this.bearerTokenProviders.set(name, callback);
}
const self = this;
this.clientSessionApis.providerToken = {
async getToken(params) {
const callback = self.bearerTokenProviders.get(params.providerName);
if (!callback) {
throw new Error(
`No bearer-token provider registered for provider "${params.providerName}"`
);
}
const token = await callback({
providerName: params.providerName
});
return { token };
}
};
}
/**
* Registers command handlers for this session.

@@ -837,13 +912,9 @@ *

async disconnect() {
if (this.disconnected) {
return;
}
await this.connection.sendRequest("session.destroy", {
sessionId: this.sessionId
});
this.eventHandlers.clear();
this.typedEventHandlers.clear();
this.toolHandlers.clear();
this.permissionHandler = void 0;
this.userInputHandler = void 0;
this.elicitationHandler = void 0;
this.exitPlanModeHandler = void 0;
this.autoModeSwitchHandler = void 0;
this._markDisconnected();
}

@@ -850,0 +921,0 @@ /** Enables `await using session = ...` syntax for automatic cleanup. */

import { createSessionFsAdapter } from "./sessionFsProvider.js";
import {
CopilotRequestHandler,
CopilotWebSocketHandler,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder
} from "./copilotRequestHandler.js";
const RuntimeConnection = {

@@ -76,3 +82,6 @@ /**

const SYSTEM_MESSAGE_SECTIONS = {
identity: { description: "Agent identity preamble and mode statement" },
preamble: { description: "Agent identity preamble and mode statement" },
identity: {
description: "Section group covering the identity preamble and its sibling sub-sections (tone, tool efficiency, etc.)"
},
tone: { description: "Response style, conciseness rules, output formatting preferences" },

@@ -98,2 +107,6 @@ tool_efficiency: { description: "Tool usage patterns, parallel calling, batching guidelines" },

export {
CopilotRequestHandler,
CopilotWebSocketCloseStatus,
CopilotWebSocketForwarder,
CopilotWebSocketHandler,
RuntimeConnection,

@@ -100,0 +113,0 @@ SYSTEM_MESSAGE_SECTIONS,

@@ -7,3 +7,3 @@ {

},
"version": "1.0.3",
"version": "1.0.4",
"description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC",

@@ -60,3 +60,3 @@ "main": "./dist/cjs/index.js",

"dependencies": {
"@github/copilot": "^1.0.64-1",
"@github/copilot": "^1.0.65",
"vscode-jsonrpc": "^8.2.1",

@@ -68,2 +68,3 @@ "zod": "^4.3.6"

"@types/node": "^25.2.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.54.0",

@@ -82,3 +83,4 @@ "@typescript-eslint/parser": "^8.54.0",

"typescript": "^5.0.0",
"vitest": "^4.0.18"
"vitest": "^4.0.18",
"ws": "^8.21.0"
},

@@ -85,0 +87,0 @@ "engines": {

@@ -621,6 +621,8 @@ # Copilot SDK for Node.js/TypeScript

Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`. Use the `SYSTEM_MESSAGE_SECTIONS` constant for descriptions of each section.
Available section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`. Use the `SYSTEM_MESSAGE_SECTIONS` constant for descriptions of each section.
Each section override supports four actions:
`identity` and `tool_instructions` are section _groups_ that target a collection of related sub-sections as a unit. Use `preamble` to target just the identity preamble without affecting its sibling sub-sections.
Each section override supports five actions:
- **`replace`** — Replace the section content entirely

@@ -630,2 +632,3 @@ - **`remove`** — Remove the section from the prompt

- **`prepend`** — Add content before the existing section
- **`preserve`** — No-op that opts an individually-addressable section out of a group-level `remove`

@@ -689,2 +692,3 @@ Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.

defaults `memory` to disabled unless you set it explicitly.
For more background, see [About GitHub Copilot Memory](https://docs.github.com/en/copilot/concepts/agents/copilot-memory).

@@ -691,0 +695,0 @@ ```typescript

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display