@github/copilot-sdk 1.0.6 → 1.0.7-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/client.js +166 -38
- package/dist/cjs/copilotRequestHandler.js +9 -0
- package/dist/cjs/ffiRuntimeHost.js +282 -0
- package/dist/cjs/generated/rpc.js +63 -15
- package/dist/cjs/types.js +12 -0
- package/dist/client.d.ts +49 -0
- package/dist/client.js +156 -38
- package/dist/copilotRequestHandler.d.ts +3 -0
- package/dist/copilotRequestHandler.js +9 -0
- package/dist/ffiRuntimeHost.d.ts +40 -0
- package/dist/ffiRuntimeHost.js +248 -0
- package/dist/generated/rpc.d.ts +459 -9
- package/dist/generated/rpc.js +63 -15
- package/dist/generated/session-events.d.ts +174 -1
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +60 -1
- package/dist/types.js +12 -0
- package/package.json +3 -2
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import koffi from "koffi";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { PassThrough, Writable } from "node:stream";
|
|
5
|
+
const SYMBOL_PREFIX = "copilot_runtime_";
|
|
6
|
+
const KEEP_ALIVE_INTERVAL_MS = 1 << 30;
|
|
7
|
+
let loadedLibraryPath;
|
|
8
|
+
let loadedLibrary;
|
|
9
|
+
function loadLibrary(libraryPath) {
|
|
10
|
+
if (loadedLibrary) {
|
|
11
|
+
if (loadedLibraryPath !== libraryPath) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`An in-process FFI runtime library is already loaded from '${loadedLibraryPath}'; loading a different library from '${libraryPath}' in the same process is not supported.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return loadedLibrary;
|
|
17
|
+
}
|
|
18
|
+
const lib = koffi.load(libraryPath);
|
|
19
|
+
const outboundCallbackType = koffi.pointer(
|
|
20
|
+
koffi.proto(
|
|
21
|
+
`void ${SYMBOL_PREFIX}outbound(void *userData, uint8 *bytesPtr, size_t bytesLen)`
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
loadedLibrary = {
|
|
25
|
+
hostStart: lib.func(`${SYMBOL_PREFIX}host_start`, "uint32", [
|
|
26
|
+
"uint8*",
|
|
27
|
+
"size_t",
|
|
28
|
+
"uint8*",
|
|
29
|
+
"size_t"
|
|
30
|
+
]),
|
|
31
|
+
hostShutdown: lib.func(`${SYMBOL_PREFIX}host_shutdown`, "bool", ["uint32"]),
|
|
32
|
+
connectionOpen: lib.func(`${SYMBOL_PREFIX}connection_open`, "uint32", [
|
|
33
|
+
"uint32",
|
|
34
|
+
outboundCallbackType,
|
|
35
|
+
"void*",
|
|
36
|
+
"uint8*",
|
|
37
|
+
"size_t",
|
|
38
|
+
"uint8*",
|
|
39
|
+
"size_t",
|
|
40
|
+
"uint8*",
|
|
41
|
+
"size_t"
|
|
42
|
+
]),
|
|
43
|
+
connectionWrite: lib.func(`${SYMBOL_PREFIX}connection_write`, "bool", [
|
|
44
|
+
"uint32",
|
|
45
|
+
"uint8*",
|
|
46
|
+
"size_t"
|
|
47
|
+
]),
|
|
48
|
+
connectionClose: lib.func(`${SYMBOL_PREFIX}connection_close`, "bool", ["uint32"]),
|
|
49
|
+
outboundCallbackType
|
|
50
|
+
};
|
|
51
|
+
loadedLibraryPath = libraryPath;
|
|
52
|
+
return loadedLibrary;
|
|
53
|
+
}
|
|
54
|
+
function buildArgvJson(cliEntrypoint) {
|
|
55
|
+
const argv = cliEntrypoint.toLowerCase().endsWith(".js") ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] : [cliEntrypoint, "--embedded-host", "--no-auto-update"];
|
|
56
|
+
return Buffer.from(JSON.stringify(argv), "utf8");
|
|
57
|
+
}
|
|
58
|
+
function buildEnvJson(environment) {
|
|
59
|
+
if (!environment) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const obj = {};
|
|
63
|
+
for (const [key, value] of Object.entries(environment)) {
|
|
64
|
+
if (value !== void 0) {
|
|
65
|
+
obj[key] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(obj).length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return Buffer.from(JSON.stringify(obj), "utf8");
|
|
72
|
+
}
|
|
73
|
+
class FfiRuntimeHost {
|
|
74
|
+
constructor(libraryPath, cliEntrypoint, environment) {
|
|
75
|
+
this.libraryPath = libraryPath;
|
|
76
|
+
this.cliEntrypoint = cliEntrypoint;
|
|
77
|
+
this.environment = environment;
|
|
78
|
+
this.lib = loadLibrary(libraryPath);
|
|
79
|
+
this.receiveStream = new PassThrough();
|
|
80
|
+
this.sendStream = new Writable({
|
|
81
|
+
// connection_write enqueues the frame into the runtime's inbound channel and
|
|
82
|
+
// returns immediately, so a synchronous FFI call is sufficient here.
|
|
83
|
+
write: (chunk, _encoding, callback) => {
|
|
84
|
+
try {
|
|
85
|
+
this.writeFrame(chunk);
|
|
86
|
+
callback();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
callback(error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
libraryPath;
|
|
94
|
+
cliEntrypoint;
|
|
95
|
+
environment;
|
|
96
|
+
lib;
|
|
97
|
+
serverId = 0;
|
|
98
|
+
connectionId = 0;
|
|
99
|
+
disposed = false;
|
|
100
|
+
outboundCallback;
|
|
101
|
+
keepAliveTimer;
|
|
102
|
+
/** The stream JSON-RPC reads server→client frames from. */
|
|
103
|
+
receiveStream;
|
|
104
|
+
/** The stream JSON-RPC writes client→server frames to. */
|
|
105
|
+
sendStream;
|
|
106
|
+
/**
|
|
107
|
+
* Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host.
|
|
108
|
+
* The cdylib is resolved as `prebuilds/<prebuildsFolder>/runtime.node` relative to
|
|
109
|
+
* the entrypoint directory (the napi-rs `<node-platform>-<arch>` layout, e.g.
|
|
110
|
+
* `linux-x64`). Throws if it cannot be found.
|
|
111
|
+
*/
|
|
112
|
+
static create(cliEntrypoint, prebuildsFolder, environment) {
|
|
113
|
+
const fullEntrypoint = resolve(cliEntrypoint);
|
|
114
|
+
const distDir = dirname(fullEntrypoint);
|
|
115
|
+
const libraryPath = join(distDir, "prebuilds", prebuildsFolder, "runtime.node");
|
|
116
|
+
if (!existsSync(libraryPath)) {
|
|
117
|
+
throw new Error(`FFI runtime library not found. Looked for '${libraryPath}'.`);
|
|
118
|
+
}
|
|
119
|
+
return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Starts the in-process runtime: spawns the CLI worker via the native host,
|
|
123
|
+
* waits for readiness, and opens the FFI JSON-RPC connection.
|
|
124
|
+
*/
|
|
125
|
+
async start() {
|
|
126
|
+
const argvJson = buildArgvJson(this.cliEntrypoint);
|
|
127
|
+
const envJson = buildEnvJson(this.environment);
|
|
128
|
+
this.serverId = await new Promise((resolvePromise, rejectPromise) => {
|
|
129
|
+
this.lib.hostStart.async(
|
|
130
|
+
argvJson,
|
|
131
|
+
argvJson.length,
|
|
132
|
+
envJson,
|
|
133
|
+
envJson ? envJson.length : 0,
|
|
134
|
+
(error, result) => {
|
|
135
|
+
if (error) {
|
|
136
|
+
rejectPromise(error);
|
|
137
|
+
} else {
|
|
138
|
+
resolvePromise(result);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
if (!this.serverId) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`copilot_runtime_host_start failed (library '${this.libraryPath}', entrypoint '${this.cliEntrypoint}').`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
this.outboundCallback = koffi.register(
|
|
149
|
+
(_userData, bytesPtr, bytesLen) => this.feedInbound(bytesPtr, bytesLen),
|
|
150
|
+
this.lib.outboundCallbackType
|
|
151
|
+
);
|
|
152
|
+
this.connectionId = this.lib.connectionOpen(
|
|
153
|
+
this.serverId,
|
|
154
|
+
this.outboundCallback,
|
|
155
|
+
null,
|
|
156
|
+
null,
|
|
157
|
+
0,
|
|
158
|
+
null,
|
|
159
|
+
0,
|
|
160
|
+
null,
|
|
161
|
+
0
|
|
162
|
+
);
|
|
163
|
+
if (!this.connectionId) {
|
|
164
|
+
this.unregisterCallback();
|
|
165
|
+
this.lib.hostShutdown(this.serverId);
|
|
166
|
+
this.serverId = 0;
|
|
167
|
+
throw new Error("copilot_runtime_connection_open failed.");
|
|
168
|
+
}
|
|
169
|
+
this.keepAliveTimer = setInterval(() => {
|
|
170
|
+
}, KEEP_ALIVE_INTERVAL_MS);
|
|
171
|
+
}
|
|
172
|
+
writeFrame(frame) {
|
|
173
|
+
if (this.disposed || !this.connectionId) {
|
|
174
|
+
throw new Error("The in-process runtime connection is closed.");
|
|
175
|
+
}
|
|
176
|
+
const ok = this.lib.connectionWrite(this.connectionId, frame, frame.length);
|
|
177
|
+
if (!ok) {
|
|
178
|
+
throw new Error("Failed to write a frame to the in-process runtime connection.");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Native outbound (server→client) callback. koffi delivers it on the JS event loop
|
|
183
|
+
* via a threadsafe function, so the frame is decoded and written straight to
|
|
184
|
+
* {@link receiveStream}. The native pointer is only valid for this call, so the
|
|
185
|
+
* bytes are copied out before returning.
|
|
186
|
+
*/
|
|
187
|
+
feedInbound(bytesPtr, bytesLen) {
|
|
188
|
+
try {
|
|
189
|
+
if (this.disposed || this.receiveStream.writableEnded) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const length = Number(bytesLen);
|
|
193
|
+
if (!bytesPtr || length <= 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const bytes = koffi.decode(
|
|
197
|
+
bytesPtr,
|
|
198
|
+
koffi.array("uint8", length, "Typed")
|
|
199
|
+
);
|
|
200
|
+
this.receiveStream.write(Buffer.from(bytes));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(
|
|
203
|
+
`In-process FFI inbound callback failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
unregisterCallback() {
|
|
208
|
+
if (this.outboundCallback === void 0) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const callback = this.outboundCallback;
|
|
212
|
+
this.outboundCallback = void 0;
|
|
213
|
+
try {
|
|
214
|
+
koffi.unregister(callback);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Closes the FFI connection, shuts down the native host, and releases resources. */
|
|
219
|
+
dispose() {
|
|
220
|
+
if (this.disposed) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.disposed = true;
|
|
224
|
+
if (this.keepAliveTimer !== void 0) {
|
|
225
|
+
clearInterval(this.keepAliveTimer);
|
|
226
|
+
this.keepAliveTimer = void 0;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
if (this.connectionId) {
|
|
230
|
+
this.lib.connectionClose(this.connectionId);
|
|
231
|
+
this.connectionId = 0;
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
if (this.serverId) {
|
|
237
|
+
this.lib.hostShutdown(this.serverId);
|
|
238
|
+
this.serverId = 0;
|
|
239
|
+
}
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
this.receiveStream.end();
|
|
243
|
+
this.unregisterCallback();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export {
|
|
247
|
+
FfiRuntimeHost
|
|
248
|
+
};
|