@witqq/agent-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/backends/claude.cjs +718 -0
- package/dist/backends/claude.cjs.map +1 -0
- package/dist/backends/claude.d.cts +118 -0
- package/dist/backends/claude.d.ts +118 -0
- package/dist/backends/claude.js +714 -0
- package/dist/backends/claude.js.map +1 -0
- package/dist/backends/copilot.cjs +651 -0
- package/dist/backends/copilot.cjs.map +1 -0
- package/dist/backends/copilot.d.cts +121 -0
- package/dist/backends/copilot.d.ts +121 -0
- package/dist/backends/copilot.js +647 -0
- package/dist/backends/copilot.js.map +1 -0
- package/dist/backends/vercel-ai.cjs +620 -0
- package/dist/backends/vercel-ai.cjs.map +1 -0
- package/dist/backends/vercel-ai.d.cts +99 -0
- package/dist/backends/vercel-ai.d.ts +99 -0
- package/dist/backends/vercel-ai.js +615 -0
- package/dist/backends/vercel-ai.js.map +1 -0
- package/dist/index.cjs +505 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +444 -0
- package/dist/index.d.ts +444 -0
- package/dist/index.js +453 -0
- package/dist/index.js.map +1 -0
- package/dist/types-JVBEqeDw.d.cts +288 -0
- package/dist/types-JVBEqeDw.d.ts +288 -0
- package/package.json +110 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
function getTextContent(content) {
|
|
3
|
+
if (typeof content === "string") return content;
|
|
4
|
+
return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var AgentSDKError = class extends Error {
|
|
9
|
+
constructor(message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = "AgentSDKError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var ReentrancyError = class extends AgentSDKError {
|
|
15
|
+
constructor() {
|
|
16
|
+
super("Agent is already running. Await the current run before starting another.");
|
|
17
|
+
this.name = "ReentrancyError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var DisposedError = class extends AgentSDKError {
|
|
21
|
+
constructor(entity) {
|
|
22
|
+
super(`${entity} has been disposed and cannot be used.`);
|
|
23
|
+
this.name = "DisposedError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var SubprocessError = class extends AgentSDKError {
|
|
27
|
+
constructor(message, options) {
|
|
28
|
+
super(message, options);
|
|
29
|
+
this.name = "SubprocessError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var AbortError = class extends AgentSDKError {
|
|
33
|
+
constructor() {
|
|
34
|
+
super("Agent run was aborted.");
|
|
35
|
+
this.name = "AbortError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/base-agent.ts
|
|
40
|
+
var BaseAgent = class {
|
|
41
|
+
state = "idle";
|
|
42
|
+
abortController = null;
|
|
43
|
+
config;
|
|
44
|
+
constructor(config) {
|
|
45
|
+
this.config = Object.freeze({ ...config });
|
|
46
|
+
}
|
|
47
|
+
// ─── Public Interface ─────────────────────────────────────────
|
|
48
|
+
async run(prompt, options) {
|
|
49
|
+
this.guardReentrancy();
|
|
50
|
+
this.guardDisposed();
|
|
51
|
+
const ac = this.createAbortController(options?.signal);
|
|
52
|
+
this.state = "running";
|
|
53
|
+
try {
|
|
54
|
+
const messages = [{ role: "user", content: prompt }];
|
|
55
|
+
return await this.executeRun(messages, options, ac.signal);
|
|
56
|
+
} finally {
|
|
57
|
+
this.state = "idle";
|
|
58
|
+
this.abortController = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async runWithContext(messages, options) {
|
|
62
|
+
this.guardReentrancy();
|
|
63
|
+
this.guardDisposed();
|
|
64
|
+
const ac = this.createAbortController(options?.signal);
|
|
65
|
+
this.state = "running";
|
|
66
|
+
try {
|
|
67
|
+
return await this.executeRun(messages, options, ac.signal);
|
|
68
|
+
} finally {
|
|
69
|
+
this.state = "idle";
|
|
70
|
+
this.abortController = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async runStructured(prompt, schema, options) {
|
|
74
|
+
this.guardReentrancy();
|
|
75
|
+
this.guardDisposed();
|
|
76
|
+
const ac = this.createAbortController(options?.signal);
|
|
77
|
+
this.state = "running";
|
|
78
|
+
try {
|
|
79
|
+
const messages = [{ role: "user", content: prompt }];
|
|
80
|
+
return await this.executeRunStructured(
|
|
81
|
+
messages,
|
|
82
|
+
schema,
|
|
83
|
+
options,
|
|
84
|
+
ac.signal
|
|
85
|
+
);
|
|
86
|
+
} finally {
|
|
87
|
+
this.state = "idle";
|
|
88
|
+
this.abortController = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async *stream(prompt, options) {
|
|
92
|
+
this.guardReentrancy();
|
|
93
|
+
this.guardDisposed();
|
|
94
|
+
const ac = this.createAbortController(options?.signal);
|
|
95
|
+
this.state = "streaming";
|
|
96
|
+
try {
|
|
97
|
+
const messages = [{ role: "user", content: prompt }];
|
|
98
|
+
yield* this.executeStream(messages, options, ac.signal);
|
|
99
|
+
} finally {
|
|
100
|
+
this.state = "idle";
|
|
101
|
+
this.abortController = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
abort() {
|
|
105
|
+
if (this.abortController) {
|
|
106
|
+
this.abortController.abort();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
getState() {
|
|
110
|
+
return this.state;
|
|
111
|
+
}
|
|
112
|
+
getConfig() {
|
|
113
|
+
return this.config;
|
|
114
|
+
}
|
|
115
|
+
/** Mark agent as disposed. Override to add cleanup. */
|
|
116
|
+
dispose() {
|
|
117
|
+
this.abort();
|
|
118
|
+
this.state = "disposed";
|
|
119
|
+
}
|
|
120
|
+
// ─── Guards ───────────────────────────────────────────────────
|
|
121
|
+
guardReentrancy() {
|
|
122
|
+
if (this.state === "running" || this.state === "streaming") {
|
|
123
|
+
throw new ReentrancyError();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
guardDisposed() {
|
|
127
|
+
if (this.state === "disposed") {
|
|
128
|
+
throw new DisposedError("Agent");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Throw AbortError if signal is already aborted */
|
|
132
|
+
checkAbort(signal) {
|
|
133
|
+
if (signal.aborted) {
|
|
134
|
+
throw new AbortError();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ─── Internal Helpers ─────────────────────────────────────────
|
|
138
|
+
createAbortController(externalSignal) {
|
|
139
|
+
const ac = new AbortController();
|
|
140
|
+
this.abortController = ac;
|
|
141
|
+
if (externalSignal) {
|
|
142
|
+
if (externalSignal.aborted) {
|
|
143
|
+
ac.abort();
|
|
144
|
+
} else {
|
|
145
|
+
externalSignal.addEventListener("abort", () => ac.abort(), {
|
|
146
|
+
once: true
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return ac;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/utils/schema.ts
|
|
155
|
+
function zodToJsonSchema(schema) {
|
|
156
|
+
const schemaAny = schema;
|
|
157
|
+
if ("jsonSchema" in schema && typeof schemaAny.jsonSchema === "function") {
|
|
158
|
+
return schemaAny.jsonSchema();
|
|
159
|
+
}
|
|
160
|
+
return extractSchemaFromDef(schema);
|
|
161
|
+
}
|
|
162
|
+
function extractSchemaFromDef(schema) {
|
|
163
|
+
const def = schema._def;
|
|
164
|
+
const typeName = def.typeName;
|
|
165
|
+
switch (typeName) {
|
|
166
|
+
case "ZodString":
|
|
167
|
+
return { type: "string" };
|
|
168
|
+
case "ZodNumber":
|
|
169
|
+
return { type: "number" };
|
|
170
|
+
case "ZodBoolean":
|
|
171
|
+
return { type: "boolean" };
|
|
172
|
+
case "ZodNull":
|
|
173
|
+
return { type: "null" };
|
|
174
|
+
case "ZodArray":
|
|
175
|
+
return {
|
|
176
|
+
type: "array",
|
|
177
|
+
items: extractSchemaFromDef(def.type)
|
|
178
|
+
};
|
|
179
|
+
case "ZodObject": {
|
|
180
|
+
const shape = schema.shape;
|
|
181
|
+
const properties = {};
|
|
182
|
+
const required = [];
|
|
183
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
184
|
+
const valueDef = value._def;
|
|
185
|
+
if (valueDef.typeName === "ZodOptional") {
|
|
186
|
+
properties[key] = extractSchemaFromDef(valueDef.innerType);
|
|
187
|
+
} else {
|
|
188
|
+
properties[key] = extractSchemaFromDef(value);
|
|
189
|
+
required.push(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties,
|
|
195
|
+
...required.length > 0 ? { required } : {}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
case "ZodOptional":
|
|
199
|
+
return extractSchemaFromDef(def.innerType);
|
|
200
|
+
case "ZodEnum":
|
|
201
|
+
return { type: "string", enum: def.values };
|
|
202
|
+
default:
|
|
203
|
+
return {};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/backends/copilot.ts
|
|
208
|
+
var sdkModule = null;
|
|
209
|
+
async function loadSDK() {
|
|
210
|
+
if (sdkModule) return sdkModule;
|
|
211
|
+
try {
|
|
212
|
+
sdkModule = await import('@github/copilot-sdk');
|
|
213
|
+
return sdkModule;
|
|
214
|
+
} catch {
|
|
215
|
+
throw new SubprocessError(
|
|
216
|
+
"@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function _injectSDK(mock) {
|
|
221
|
+
sdkModule = mock;
|
|
222
|
+
}
|
|
223
|
+
function _resetSDK() {
|
|
224
|
+
sdkModule = null;
|
|
225
|
+
}
|
|
226
|
+
function mapToolsToSDK(tools) {
|
|
227
|
+
return tools.map((tool) => ({
|
|
228
|
+
name: tool.name,
|
|
229
|
+
description: tool.description,
|
|
230
|
+
parameters: zodToJsonSchema(tool.parameters),
|
|
231
|
+
handler: async (args) => {
|
|
232
|
+
const result = await tool.execute(args);
|
|
233
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
function buildPermissionHandler(config) {
|
|
238
|
+
const onPermission = config.supervisor?.onPermission;
|
|
239
|
+
if (!onPermission) return void 0;
|
|
240
|
+
const permissionStore = config.permissionStore;
|
|
241
|
+
return async (request) => {
|
|
242
|
+
const toolName = String(request.kind);
|
|
243
|
+
if (permissionStore && await permissionStore.isApproved(toolName)) {
|
|
244
|
+
return { kind: "approved" };
|
|
245
|
+
}
|
|
246
|
+
const unifiedRequest = {
|
|
247
|
+
toolName,
|
|
248
|
+
toolArgs: { ...request },
|
|
249
|
+
rawSDKRequest: request
|
|
250
|
+
};
|
|
251
|
+
const ac = new AbortController();
|
|
252
|
+
const decision = await onPermission(unifiedRequest, ac.signal);
|
|
253
|
+
if (decision.allowed) {
|
|
254
|
+
if (permissionStore && decision.scope) {
|
|
255
|
+
await permissionStore.approve(toolName, decision.scope);
|
|
256
|
+
}
|
|
257
|
+
return { kind: "approved" };
|
|
258
|
+
}
|
|
259
|
+
return { kind: "denied-interactively-by-user" };
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function buildUserInputHandler(config) {
|
|
263
|
+
const onAskUser = config.supervisor?.onAskUser;
|
|
264
|
+
if (!onAskUser) return void 0;
|
|
265
|
+
return async (request) => {
|
|
266
|
+
const ac = new AbortController();
|
|
267
|
+
const response = await onAskUser(
|
|
268
|
+
{
|
|
269
|
+
question: request.question,
|
|
270
|
+
choices: request.choices,
|
|
271
|
+
allowFreeform: request.allowFreeform
|
|
272
|
+
},
|
|
273
|
+
ac.signal
|
|
274
|
+
);
|
|
275
|
+
return { answer: response.answer, wasFreeform: response.wasFreeform };
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
var ToolCallTracker = class {
|
|
279
|
+
map = /* @__PURE__ */ new Map();
|
|
280
|
+
trackStart(toolCallId, toolName, args) {
|
|
281
|
+
this.map.set(toolCallId, { toolName, args });
|
|
282
|
+
}
|
|
283
|
+
getInfo(toolCallId) {
|
|
284
|
+
return this.map.get(toolCallId);
|
|
285
|
+
}
|
|
286
|
+
clear() {
|
|
287
|
+
this.map.clear();
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var ThinkingTracker = class {
|
|
291
|
+
active = false;
|
|
292
|
+
isActive() {
|
|
293
|
+
return this.active;
|
|
294
|
+
}
|
|
295
|
+
startThinking() {
|
|
296
|
+
this.active = true;
|
|
297
|
+
}
|
|
298
|
+
/** Returns true if thinking was active and is now ended. */
|
|
299
|
+
endThinking() {
|
|
300
|
+
if (!this.active) return false;
|
|
301
|
+
this.active = false;
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
function mapSessionEvent(event, tracker, thinkingTracker) {
|
|
306
|
+
const data = event.data;
|
|
307
|
+
switch (event.type) {
|
|
308
|
+
case "assistant.message_delta": {
|
|
309
|
+
const textEvent = {
|
|
310
|
+
type: "text_delta",
|
|
311
|
+
text: String(data.deltaContent ?? "")
|
|
312
|
+
};
|
|
313
|
+
if (thinkingTracker.endThinking()) {
|
|
314
|
+
return [{ type: "thinking_end" }, textEvent];
|
|
315
|
+
}
|
|
316
|
+
return textEvent;
|
|
317
|
+
}
|
|
318
|
+
case "assistant.reasoning":
|
|
319
|
+
case "assistant.reasoning_delta":
|
|
320
|
+
if (!thinkingTracker.isActive()) {
|
|
321
|
+
thinkingTracker.startThinking();
|
|
322
|
+
return { type: "thinking_start" };
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
case "tool.execution_start": {
|
|
326
|
+
const toolCallId = String(data.toolCallId ?? "");
|
|
327
|
+
const toolName = String(data.toolName ?? "unknown");
|
|
328
|
+
const args = data.arguments ?? {};
|
|
329
|
+
tracker.trackStart(toolCallId, toolName, args);
|
|
330
|
+
const toolStartEvent = { type: "tool_call_start", toolName, args };
|
|
331
|
+
if (thinkingTracker.endThinking()) {
|
|
332
|
+
return [{ type: "thinking_end" }, toolStartEvent];
|
|
333
|
+
}
|
|
334
|
+
return toolStartEvent;
|
|
335
|
+
}
|
|
336
|
+
case "tool.execution_complete": {
|
|
337
|
+
const toolCallId = String(data.toolCallId ?? "");
|
|
338
|
+
const info = tracker.getInfo(toolCallId);
|
|
339
|
+
const resultContent = data.result?.content;
|
|
340
|
+
return {
|
|
341
|
+
type: "tool_call_end",
|
|
342
|
+
toolName: info?.toolName ?? "unknown",
|
|
343
|
+
result: resultContent ?? null
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
case "assistant.usage":
|
|
347
|
+
return {
|
|
348
|
+
type: "usage_update",
|
|
349
|
+
promptTokens: Number(data.inputTokens ?? 0),
|
|
350
|
+
completionTokens: Number(data.outputTokens ?? 0)
|
|
351
|
+
};
|
|
352
|
+
case "session.error":
|
|
353
|
+
return {
|
|
354
|
+
type: "error",
|
|
355
|
+
error: String(data.message ?? "Unknown error"),
|
|
356
|
+
recoverable: false
|
|
357
|
+
};
|
|
358
|
+
case "assistant.message": {
|
|
359
|
+
const doneEvent = {
|
|
360
|
+
type: "done",
|
|
361
|
+
finalOutput: String(data.content ?? "")
|
|
362
|
+
};
|
|
363
|
+
if (thinkingTracker.endThinking()) {
|
|
364
|
+
return [{ type: "thinking_end" }, doneEvent];
|
|
365
|
+
}
|
|
366
|
+
return doneEvent;
|
|
367
|
+
}
|
|
368
|
+
default:
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
var CopilotAgent = class extends BaseAgent {
|
|
373
|
+
getClient;
|
|
374
|
+
sdkTools;
|
|
375
|
+
sessionConfig;
|
|
376
|
+
constructor(config, getClient) {
|
|
377
|
+
super(config);
|
|
378
|
+
this.getClient = getClient;
|
|
379
|
+
this.sdkTools = mapToolsToSDK(config.tools);
|
|
380
|
+
this.sessionConfig = {
|
|
381
|
+
model: config.model,
|
|
382
|
+
tools: this.sdkTools,
|
|
383
|
+
systemMessage: { mode: "replace", content: config.systemPrompt },
|
|
384
|
+
onPermissionRequest: buildPermissionHandler(config),
|
|
385
|
+
onUserInputRequest: buildUserInputHandler(config)
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async createSession(streaming) {
|
|
389
|
+
const client = await this.getClient();
|
|
390
|
+
return client.createSession({ ...this.sessionConfig, streaming });
|
|
391
|
+
}
|
|
392
|
+
// ─── executeRun ─────────────────────────────────────────────────
|
|
393
|
+
async executeRun(messages, _options, signal) {
|
|
394
|
+
this.checkAbort(signal);
|
|
395
|
+
const session = await this.createSession(false);
|
|
396
|
+
const prompt = extractLastUserPrompt(messages);
|
|
397
|
+
const tracker = new ToolCallTracker();
|
|
398
|
+
const toolCalls = [];
|
|
399
|
+
let usage;
|
|
400
|
+
const unsubscribe = session.on((event) => {
|
|
401
|
+
if (event.type === "tool.execution_start") {
|
|
402
|
+
tracker.trackStart(
|
|
403
|
+
String(event.data.toolCallId ?? ""),
|
|
404
|
+
String(event.data.toolName ?? "unknown"),
|
|
405
|
+
event.data.arguments ?? {}
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (event.type === "tool.execution_complete") {
|
|
409
|
+
const info = tracker.getInfo(String(event.data.toolCallId ?? ""));
|
|
410
|
+
const resultContent = event.data.result?.content;
|
|
411
|
+
toolCalls.push({
|
|
412
|
+
toolName: info?.toolName ?? "unknown",
|
|
413
|
+
args: info?.args ?? {},
|
|
414
|
+
result: resultContent ?? null,
|
|
415
|
+
approved: Boolean(event.data.success ?? true)
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (event.type === "assistant.usage") {
|
|
419
|
+
usage = {
|
|
420
|
+
promptTokens: Number(event.data.inputTokens ?? 0),
|
|
421
|
+
completionTokens: Number(event.data.outputTokens ?? 0)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
const onAbort = () => {
|
|
426
|
+
session.abort().catch(() => {
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
430
|
+
try {
|
|
431
|
+
const response = await session.sendAndWait({ prompt });
|
|
432
|
+
const output = response?.data?.content ?? null;
|
|
433
|
+
return {
|
|
434
|
+
output,
|
|
435
|
+
structuredOutput: void 0,
|
|
436
|
+
toolCalls,
|
|
437
|
+
messages: [
|
|
438
|
+
...messages,
|
|
439
|
+
...output !== null ? [{ role: "assistant", content: output }] : []
|
|
440
|
+
],
|
|
441
|
+
usage
|
|
442
|
+
};
|
|
443
|
+
} finally {
|
|
444
|
+
signal.removeEventListener("abort", onAbort);
|
|
445
|
+
unsubscribe();
|
|
446
|
+
tracker.clear();
|
|
447
|
+
session.destroy().catch(() => {
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// ─── executeRunStructured ───────────────────────────────────────
|
|
452
|
+
async executeRunStructured(messages, schema, options, signal) {
|
|
453
|
+
const jsonSchema = zodToJsonSchema(schema.schema);
|
|
454
|
+
const instruction = `
|
|
455
|
+
|
|
456
|
+
You MUST respond with ONLY valid JSON matching this schema:
|
|
457
|
+
` + JSON.stringify(jsonSchema, null, 2);
|
|
458
|
+
const augmented = [...messages];
|
|
459
|
+
const lastIdx = augmented.length - 1;
|
|
460
|
+
if (lastIdx >= 0 && augmented[lastIdx].role === "user") {
|
|
461
|
+
const orig = augmented[lastIdx];
|
|
462
|
+
augmented[lastIdx] = {
|
|
463
|
+
role: "user",
|
|
464
|
+
content: getTextContent(orig.content) + instruction
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const result = await this.executeRun(augmented, options, signal);
|
|
468
|
+
let structuredOutput;
|
|
469
|
+
if (result.output) {
|
|
470
|
+
try {
|
|
471
|
+
const jsonMatch = result.output.match(
|
|
472
|
+
/```(?:json)?\s*([\s\S]*?)```/
|
|
473
|
+
);
|
|
474
|
+
const raw = jsonMatch ? jsonMatch[1].trim() : result.output.trim();
|
|
475
|
+
structuredOutput = schema.schema.parse(JSON.parse(raw));
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
...result,
|
|
481
|
+
structuredOutput
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
// ─── executeStream ──────────────────────────────────────────────
|
|
485
|
+
async *executeStream(messages, _options, signal) {
|
|
486
|
+
this.checkAbort(signal);
|
|
487
|
+
const session = await this.createSession(true);
|
|
488
|
+
const prompt = extractLastUserPrompt(messages);
|
|
489
|
+
const tracker = new ToolCallTracker();
|
|
490
|
+
const thinkingTracker = new ThinkingTracker();
|
|
491
|
+
const queue = [];
|
|
492
|
+
let notify = null;
|
|
493
|
+
const push = (item) => {
|
|
494
|
+
queue.push(item);
|
|
495
|
+
if (notify) {
|
|
496
|
+
notify();
|
|
497
|
+
notify = null;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
const waitForItem = () => new Promise((resolve) => {
|
|
501
|
+
notify = resolve;
|
|
502
|
+
});
|
|
503
|
+
const unsubscribe = session.on((event) => {
|
|
504
|
+
const mapped = mapSessionEvent(event, tracker, thinkingTracker);
|
|
505
|
+
if (mapped) {
|
|
506
|
+
if (Array.isArray(mapped)) {
|
|
507
|
+
for (const e of mapped) push({ event: e });
|
|
508
|
+
} else {
|
|
509
|
+
push({ event: mapped });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (event.type === "session.idle") {
|
|
513
|
+
push({ done: true });
|
|
514
|
+
} else if (event.type === "session.error") {
|
|
515
|
+
push({
|
|
516
|
+
error: new Error(
|
|
517
|
+
String(event.data.message ?? "Session error")
|
|
518
|
+
)
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const onAbort = () => {
|
|
523
|
+
session.abort().catch(() => {
|
|
524
|
+
});
|
|
525
|
+
push({ error: new AbortError() });
|
|
526
|
+
};
|
|
527
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
528
|
+
try {
|
|
529
|
+
await session.send({ prompt });
|
|
530
|
+
while (true) {
|
|
531
|
+
while (queue.length === 0) await waitForItem();
|
|
532
|
+
const item = queue.shift();
|
|
533
|
+
if ("done" in item) break;
|
|
534
|
+
if ("error" in item) throw item.error;
|
|
535
|
+
yield item.event;
|
|
536
|
+
}
|
|
537
|
+
} finally {
|
|
538
|
+
signal.removeEventListener("abort", onAbort);
|
|
539
|
+
unsubscribe();
|
|
540
|
+
tracker.clear();
|
|
541
|
+
session.destroy().catch(() => {
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// ─── dispose ────────────────────────────────────────────────────
|
|
546
|
+
dispose() {
|
|
547
|
+
super.dispose();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
function extractLastUserPrompt(messages) {
|
|
551
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
552
|
+
const msg = messages[i];
|
|
553
|
+
if (msg.role === "user") {
|
|
554
|
+
return getTextContent(msg.content);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return "";
|
|
558
|
+
}
|
|
559
|
+
var CopilotAgentService = class {
|
|
560
|
+
name = "copilot";
|
|
561
|
+
client = null;
|
|
562
|
+
clientPromise = null;
|
|
563
|
+
disposed = false;
|
|
564
|
+
options;
|
|
565
|
+
constructor(options) {
|
|
566
|
+
this.options = options;
|
|
567
|
+
}
|
|
568
|
+
async ensureClient() {
|
|
569
|
+
if (this.disposed) throw new DisposedError("CopilotAgentService");
|
|
570
|
+
if (this.client) return this.client;
|
|
571
|
+
if (this.clientPromise) return this.clientPromise;
|
|
572
|
+
this.clientPromise = (async () => {
|
|
573
|
+
try {
|
|
574
|
+
const sdk = await loadSDK();
|
|
575
|
+
const client = new sdk.CopilotClient({
|
|
576
|
+
cliPath: this.options.cliPath,
|
|
577
|
+
cwd: this.options.workingDirectory,
|
|
578
|
+
useStdio: true,
|
|
579
|
+
autoStart: false,
|
|
580
|
+
autoRestart: true,
|
|
581
|
+
logLevel: "error",
|
|
582
|
+
githubToken: this.options.githubToken,
|
|
583
|
+
useLoggedInUser: this.options.useLoggedInUser ?? true
|
|
584
|
+
});
|
|
585
|
+
await client.start();
|
|
586
|
+
this.client = client;
|
|
587
|
+
return client;
|
|
588
|
+
} catch (e) {
|
|
589
|
+
this.clientPromise = null;
|
|
590
|
+
throw e;
|
|
591
|
+
}
|
|
592
|
+
})();
|
|
593
|
+
return this.clientPromise;
|
|
594
|
+
}
|
|
595
|
+
createAgent(config) {
|
|
596
|
+
if (this.disposed) throw new DisposedError("CopilotAgentService");
|
|
597
|
+
return new CopilotAgent(config, () => this.ensureClient());
|
|
598
|
+
}
|
|
599
|
+
async listModels() {
|
|
600
|
+
const client = await this.ensureClient();
|
|
601
|
+
const models = await client.listModels();
|
|
602
|
+
return models.map((m) => ({
|
|
603
|
+
id: m.id,
|
|
604
|
+
name: m.name,
|
|
605
|
+
provider: "copilot"
|
|
606
|
+
}));
|
|
607
|
+
}
|
|
608
|
+
async validate() {
|
|
609
|
+
const errors = [];
|
|
610
|
+
try {
|
|
611
|
+
const client = await this.ensureClient();
|
|
612
|
+
const auth = await client.getAuthStatus();
|
|
613
|
+
if (!auth.isAuthenticated) {
|
|
614
|
+
errors.push(
|
|
615
|
+
"Not authenticated with GitHub Copilot. Run 'copilot auth login'."
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
} catch (e) {
|
|
619
|
+
errors.push(
|
|
620
|
+
`Failed to connect to Copilot CLI: ${e instanceof Error ? e.message : String(e)}`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
return { valid: errors.length === 0, errors };
|
|
624
|
+
}
|
|
625
|
+
async dispose() {
|
|
626
|
+
if (this.disposed) return;
|
|
627
|
+
this.disposed = true;
|
|
628
|
+
if (this.clientPromise) {
|
|
629
|
+
try {
|
|
630
|
+
await this.clientPromise;
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (this.client) {
|
|
635
|
+
await this.client.stop();
|
|
636
|
+
this.client = null;
|
|
637
|
+
}
|
|
638
|
+
this.clientPromise = null;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
function createCopilotService(options) {
|
|
642
|
+
return new CopilotAgentService(options);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export { _injectSDK, _resetSDK, createCopilotService };
|
|
646
|
+
//# sourceMappingURL=copilot.js.map
|
|
647
|
+
//# sourceMappingURL=copilot.js.map
|