@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.
@@ -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
+ };