@nomomon/ai-sdk-provider-github-copilot 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/dist/index.cjs ADDED
@@ -0,0 +1,613 @@
1
+ 'use strict';
2
+
3
+ var provider = require('@ai-sdk/provider');
4
+ var providerUtils = require('@ai-sdk/provider-utils');
5
+ var copilotSdk = require('@github/copilot-sdk');
6
+
7
+ // src/errors.ts
8
+ var AUTH_ERROR_PATTERNS = [
9
+ "not authenticated",
10
+ "authentication",
11
+ "unauthorized",
12
+ "auth failed",
13
+ "please login",
14
+ "login required",
15
+ "invalid token",
16
+ "token expired"
17
+ ];
18
+ function createAuthenticationError(options) {
19
+ return new provider.LoadAPIKeyError({
20
+ message: options.message ?? "Authentication failed. Please ensure Copilot CLI is properly authenticated."
21
+ });
22
+ }
23
+ function createAPICallError(options) {
24
+ return new provider.APICallError({
25
+ message: options.message,
26
+ url: "copilot://session",
27
+ requestBodyValues: {},
28
+ statusCode: options.statusCode,
29
+ cause: options.cause,
30
+ isRetryable: options.isRetryable ?? false
31
+ });
32
+ }
33
+ function isAuthenticationError(error) {
34
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
35
+ return AUTH_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
36
+ }
37
+ function isAbortError(error) {
38
+ if (error && typeof error === "object") {
39
+ const e = error;
40
+ if (typeof e.name === "string" && e.name === "AbortError") return true;
41
+ if (typeof e.code === "string" && e.code.toUpperCase() === "ABORT_ERR") return true;
42
+ }
43
+ return false;
44
+ }
45
+ function handleCopilotError(error, _context) {
46
+ if (isAbortError(error)) {
47
+ throw error;
48
+ }
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ if (isAuthenticationError(error)) {
51
+ throw createAuthenticationError({ message });
52
+ }
53
+ throw createAPICallError({
54
+ message: message || "GitHub Copilot SDK error",
55
+ cause: error,
56
+ isRetryable: false
57
+ });
58
+ }
59
+
60
+ // src/convert-to-copilot-messages.ts
61
+ var IMAGE_URL_WARNING = "Image URLs are not supported by this provider; supply file paths as attachments.";
62
+ var IMAGE_BASE64_WARNING = "Base64/image data URLs require file paths. Write to temp file and pass path, or use attachments with path.";
63
+ function convertToCopilotMessages(prompt) {
64
+ const messages = [];
65
+ const warnings = [];
66
+ let systemMessage;
67
+ const attachments = [];
68
+ for (const message of prompt) {
69
+ switch (message.role) {
70
+ case "system": {
71
+ const content = message.content;
72
+ systemMessage = typeof content === "string" ? content : extractTextFromParts(content);
73
+ if (systemMessage?.trim()) {
74
+ messages.push(`System: ${systemMessage}`);
75
+ }
76
+ break;
77
+ }
78
+ case "user": {
79
+ const content = message.content;
80
+ if (typeof content === "string") {
81
+ messages.push(`User: ${content}`);
82
+ } else {
83
+ const textParts = [];
84
+ for (const part of content) {
85
+ if (part.type === "text") {
86
+ textParts.push(part.text);
87
+ } else if (part.type === "file") {
88
+ const fileInfo = extractFileAttachment(part);
89
+ if (fileInfo.path) {
90
+ attachments.push({
91
+ type: "file",
92
+ path: fileInfo.path,
93
+ displayName: fileInfo.displayName
94
+ });
95
+ } else if (fileInfo.warning) {
96
+ warnings.push(fileInfo.warning);
97
+ }
98
+ } else if (part.type === "image") {
99
+ warnings.push(IMAGE_BASE64_WARNING);
100
+ }
101
+ }
102
+ if (textParts.length > 0) {
103
+ messages.push(`User: ${textParts.join("\n")}`);
104
+ }
105
+ }
106
+ break;
107
+ }
108
+ case "assistant": {
109
+ const content = message.content;
110
+ if (typeof content === "string") {
111
+ messages.push(`Assistant: ${content}`);
112
+ } else {
113
+ const textParts = [];
114
+ for (const part of content) {
115
+ if (part.type === "text") {
116
+ textParts.push(part.text);
117
+ } else if (part.type === "tool-call") {
118
+ textParts.push(`[Tool call: ${part.toolName}]`);
119
+ } else if (part.type === "reasoning") {
120
+ textParts.push(`[Reasoning: ${part.text}]`);
121
+ }
122
+ }
123
+ if (textParts.length > 0) {
124
+ messages.push(`Assistant: ${textParts.join("\n")}`);
125
+ }
126
+ }
127
+ break;
128
+ }
129
+ case "tool": {
130
+ for (const part of message.content) {
131
+ if (part.type === "tool-result") {
132
+ const output = part.output;
133
+ let resultStr;
134
+ if (output.type === "text" || output.type === "error-text") {
135
+ resultStr = output.value;
136
+ } else if (output.type === "json" || output.type === "error-json") {
137
+ resultStr = JSON.stringify(output.value);
138
+ } else if (output.type === "execution-denied") {
139
+ resultStr = `[Execution denied${output.reason ? `: ${output.reason}` : ""}]`;
140
+ } else if (output.type === "content") {
141
+ resultStr = output.value.filter((p) => p.type === "text").map((p) => p.text).join("\n");
142
+ } else {
143
+ resultStr = "[Unknown output type]";
144
+ }
145
+ const isError = output.type === "error-text" || output.type === "error-json";
146
+ messages.push(
147
+ `Tool result (${part.toolName}): ${isError ? "Error: " : ""}${resultStr}`
148
+ );
149
+ }
150
+ }
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ const promptText = messages.join("\n\n");
156
+ return {
157
+ prompt: promptText,
158
+ systemMessage: systemMessage?.trim() || void 0,
159
+ attachments: attachments.length > 0 ? attachments : void 0,
160
+ warnings: warnings.length > 0 ? warnings : void 0
161
+ };
162
+ }
163
+ function extractTextFromParts(content) {
164
+ return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
165
+ }
166
+ function extractFileAttachment(part) {
167
+ if (part.type !== "file") return {};
168
+ const data = part.data;
169
+ if (typeof data === "string") {
170
+ if (data.startsWith("http://") || data.startsWith("https://")) {
171
+ return { warning: IMAGE_URL_WARNING };
172
+ }
173
+ if (data.startsWith("file://")) {
174
+ return { path: data.slice(7), displayName: part.filename };
175
+ }
176
+ if (data.startsWith("/") || /^[A-Za-z]:[\\/]/.test(data)) {
177
+ return { path: data, displayName: part.filename };
178
+ }
179
+ return { warning: IMAGE_BASE64_WARNING };
180
+ }
181
+ return {};
182
+ }
183
+
184
+ // src/map-copilot-finish-reason.ts
185
+ function mapCopilotFinishReason() {
186
+ return {
187
+ unified: "stop",
188
+ raw: void 0
189
+ };
190
+ }
191
+
192
+ // src/github-copilot-language-model.ts
193
+ function createEmptyUsage() {
194
+ return {
195
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
196
+ outputTokens: { total: 0, text: void 0, reasoning: void 0 },
197
+ raw: void 0
198
+ };
199
+ }
200
+ function convertCopilotUsage(event) {
201
+ const inputTokens = event.inputTokens ?? 0;
202
+ const outputTokens = event.outputTokens ?? 0;
203
+ const cacheRead = event.cacheReadTokens ?? 0;
204
+ const cacheWrite = 0;
205
+ return {
206
+ inputTokens: {
207
+ total: inputTokens + cacheRead + cacheWrite,
208
+ noCache: inputTokens,
209
+ cacheRead,
210
+ cacheWrite
211
+ },
212
+ outputTokens: {
213
+ total: outputTokens,
214
+ text: outputTokens,
215
+ reasoning: void 0
216
+ },
217
+ raw: event
218
+ };
219
+ }
220
+ var GitHubCopilotLanguageModel = class {
221
+ specificationVersion = "v3";
222
+ defaultObjectGenerationMode = "json";
223
+ supportsImageUrls = false;
224
+ supportedUrls = {};
225
+ supportsStructuredOutputs = false;
226
+ modelId;
227
+ settings;
228
+ getClient;
229
+ constructor(options) {
230
+ this.modelId = options.modelId;
231
+ this.settings = options.settings;
232
+ this.getClient = options.getClient;
233
+ }
234
+ get provider() {
235
+ return "github-copilot";
236
+ }
237
+ getEffectiveModel() {
238
+ return this.settings.model ?? this.modelId;
239
+ }
240
+ buildSessionConfig(streaming) {
241
+ return {
242
+ model: this.getEffectiveModel(),
243
+ sessionId: this.settings.sessionId,
244
+ streaming,
245
+ systemMessage: this.settings.systemMessage,
246
+ tools: this.settings.tools,
247
+ provider: this.settings.provider,
248
+ workingDirectory: this.settings.workingDirectory
249
+ };
250
+ }
251
+ generateWarnings(options) {
252
+ const warnings = [];
253
+ const unsupported = [];
254
+ if (options.temperature !== void 0) unsupported.push("temperature");
255
+ if (options.topP !== void 0) unsupported.push("topP");
256
+ if (options.topK !== void 0) unsupported.push("topK");
257
+ if (options.presencePenalty !== void 0) unsupported.push("presencePenalty");
258
+ if (options.frequencyPenalty !== void 0) unsupported.push("frequencyPenalty");
259
+ if (options.stopSequences?.length) unsupported.push("stopSequences");
260
+ if (options.seed !== void 0) unsupported.push("seed");
261
+ for (const param of unsupported) {
262
+ warnings.push({
263
+ type: "unsupported",
264
+ feature: param,
265
+ details: `GitHub Copilot SDK does not support the ${param} parameter. It will be ignored.`
266
+ });
267
+ }
268
+ return warnings;
269
+ }
270
+ async doGenerate(options) {
271
+ const {
272
+ prompt,
273
+ systemMessage,
274
+ attachments,
275
+ warnings: msgWarnings
276
+ } = convertToCopilotMessages(options.prompt);
277
+ const warnings = [
278
+ ...this.generateWarnings(options),
279
+ ...msgWarnings?.map((m) => ({ type: "other", message: m })) ?? []
280
+ ];
281
+ const client = this.getClient();
282
+ if (client.getState() !== "connected") {
283
+ await client.start();
284
+ }
285
+ const session = await client.createSession({
286
+ ...this.buildSessionConfig(false),
287
+ systemMessage: systemMessage ? { mode: "append", content: systemMessage } : this.settings.systemMessage
288
+ });
289
+ let abortListener;
290
+ if (options.abortSignal) {
291
+ abortListener = () => session.abort();
292
+ options.abortSignal.addEventListener("abort", abortListener, { once: true });
293
+ }
294
+ try {
295
+ const result = await session.sendAndWait(
296
+ { prompt, attachments },
297
+ options.abortSignal?.aborted ? 0 : 6e4
298
+ );
299
+ const content = [];
300
+ const text = result?.data?.content ?? "";
301
+ if (text) {
302
+ content.push({ type: "text", text });
303
+ }
304
+ let usage = createEmptyUsage();
305
+ const usageEvent = result?.data?.usage;
306
+ if (usageEvent && typeof usageEvent === "object") {
307
+ usage = convertCopilotUsage(usageEvent);
308
+ }
309
+ const finishReason = mapCopilotFinishReason();
310
+ return {
311
+ content,
312
+ finishReason,
313
+ usage,
314
+ warnings,
315
+ request: { body: { prompt, attachments } },
316
+ response: {
317
+ id: providerUtils.generateId(),
318
+ timestamp: /* @__PURE__ */ new Date(),
319
+ modelId: this.modelId
320
+ }
321
+ };
322
+ } catch (error) {
323
+ if (isAbortError(error)) {
324
+ throw options.abortSignal?.aborted ? options.abortSignal.reason : error;
325
+ }
326
+ handleCopilotError(error, { promptExcerpt: prompt.substring(0, 200) });
327
+ return void 0;
328
+ } finally {
329
+ if (options.abortSignal && abortListener) {
330
+ options.abortSignal.removeEventListener("abort", abortListener);
331
+ }
332
+ try {
333
+ await session.destroy();
334
+ } catch {
335
+ }
336
+ }
337
+ }
338
+ async doStream(options) {
339
+ const {
340
+ prompt,
341
+ systemMessage,
342
+ attachments,
343
+ warnings: msgWarnings
344
+ } = convertToCopilotMessages(options.prompt);
345
+ const warnings = [
346
+ ...this.generateWarnings(options),
347
+ ...msgWarnings?.map((m) => ({ type: "other", message: m })) ?? []
348
+ ];
349
+ const client = this.getClient();
350
+ if (client.getState() !== "connected") {
351
+ await client.start();
352
+ }
353
+ const session = await client.createSession({
354
+ ...this.buildSessionConfig(true),
355
+ systemMessage: systemMessage ? { mode: "append", content: systemMessage } : this.settings.systemMessage
356
+ });
357
+ const abortController = new AbortController();
358
+ let abortListener;
359
+ if (options.abortSignal?.aborted) {
360
+ abortController.abort(options.abortSignal.reason);
361
+ } else if (options.abortSignal) {
362
+ abortListener = () => {
363
+ session.abort();
364
+ abortController.abort(options.abortSignal?.reason);
365
+ };
366
+ options.abortSignal.addEventListener("abort", abortListener, { once: true });
367
+ }
368
+ const stream = new ReadableStream({
369
+ start: async (controller) => {
370
+ let textPartId;
371
+ let usage = createEmptyUsage();
372
+ const toolStates = /* @__PURE__ */ new Map();
373
+ try {
374
+ controller.enqueue({ type: "stream-start", warnings });
375
+ session.on((event) => {
376
+ if (event.type === "assistant.message_delta") {
377
+ const delta = event.data?.deltaContent;
378
+ if (delta) {
379
+ if (!textPartId) {
380
+ textPartId = providerUtils.generateId();
381
+ controller.enqueue({ type: "text-start", id: textPartId });
382
+ }
383
+ controller.enqueue({
384
+ type: "text-delta",
385
+ id: textPartId,
386
+ delta
387
+ });
388
+ }
389
+ } else if (event.type === "assistant.reasoning_delta") {
390
+ const delta = event.data?.deltaContent;
391
+ if (delta) {
392
+ const reasoningId = providerUtils.generateId();
393
+ controller.enqueue({ type: "reasoning-start", id: reasoningId });
394
+ controller.enqueue({
395
+ type: "reasoning-delta",
396
+ id: reasoningId,
397
+ delta
398
+ });
399
+ controller.enqueue({ type: "reasoning-end", id: reasoningId });
400
+ }
401
+ } else if (event.type === "assistant.message") {
402
+ const data = event.data;
403
+ if (data?.content && !textPartId) {
404
+ textPartId = providerUtils.generateId();
405
+ controller.enqueue({ type: "text-start", id: textPartId });
406
+ controller.enqueue({
407
+ type: "text-delta",
408
+ id: textPartId,
409
+ delta: data.content
410
+ });
411
+ controller.enqueue({ type: "text-end", id: textPartId });
412
+ }
413
+ if (data?.toolRequests?.length) {
414
+ for (const tr of data.toolRequests) {
415
+ const toolId = tr.toolCallId;
416
+ let state = toolStates.get(toolId);
417
+ if (!state) {
418
+ state = {
419
+ name: tr.name,
420
+ inputStarted: false,
421
+ callEmitted: false
422
+ };
423
+ toolStates.set(toolId, state);
424
+ }
425
+ if (!state.inputStarted) {
426
+ controller.enqueue({
427
+ type: "tool-input-start",
428
+ id: toolId,
429
+ toolName: tr.name,
430
+ providerExecuted: true,
431
+ dynamic: true
432
+ });
433
+ state.inputStarted = true;
434
+ }
435
+ const args = tr.arguments ?? {};
436
+ controller.enqueue({
437
+ type: "tool-input-delta",
438
+ id: toolId,
439
+ delta: JSON.stringify(args)
440
+ });
441
+ controller.enqueue({ type: "tool-input-end", id: toolId });
442
+ if (!state.callEmitted) {
443
+ controller.enqueue({
444
+ type: "tool-call",
445
+ toolCallId: toolId,
446
+ toolName: tr.name,
447
+ input: typeof args === "string" ? args : JSON.stringify(args),
448
+ providerExecuted: true,
449
+ dynamic: true
450
+ });
451
+ state.callEmitted = true;
452
+ }
453
+ }
454
+ }
455
+ } else if (event.type === "tool.execution_start") {
456
+ const data = event.data;
457
+ if (data) {
458
+ const toolId = data.toolCallId;
459
+ let state = toolStates.get(toolId);
460
+ if (!state) {
461
+ state = {
462
+ name: data.toolName,
463
+ inputStarted: true,
464
+ callEmitted: false
465
+ };
466
+ toolStates.set(toolId, state);
467
+ }
468
+ if (!state.callEmitted) {
469
+ controller.enqueue({
470
+ type: "tool-input-start",
471
+ id: toolId,
472
+ toolName: data.toolName,
473
+ providerExecuted: true,
474
+ dynamic: true
475
+ });
476
+ controller.enqueue({ type: "tool-input-end", id: toolId });
477
+ controller.enqueue({
478
+ type: "tool-call",
479
+ toolCallId: toolId,
480
+ toolName: data.toolName,
481
+ input: "{}",
482
+ providerExecuted: true,
483
+ dynamic: true
484
+ });
485
+ state.callEmitted = true;
486
+ }
487
+ }
488
+ } else if (event.type === "tool.execution_complete") {
489
+ const evt = event;
490
+ const data = evt.data;
491
+ if (data) {
492
+ const toolName = data.toolName ?? "unknown";
493
+ const result = data.success && data.result?.content ? data.result.content : data.error?.message ?? "Tool execution failed";
494
+ controller.enqueue({
495
+ type: "tool-result",
496
+ toolCallId: data.toolCallId,
497
+ toolName,
498
+ result,
499
+ isError: !data.success,
500
+ dynamic: true
501
+ });
502
+ }
503
+ } else if (event.type === "assistant.usage") {
504
+ const data = event.data;
505
+ if (data) {
506
+ usage = convertCopilotUsage(data);
507
+ }
508
+ } else if (event.type === "session.idle") {
509
+ if (textPartId) {
510
+ controller.enqueue({ type: "text-end", id: textPartId });
511
+ }
512
+ controller.enqueue({
513
+ type: "finish",
514
+ finishReason: mapCopilotFinishReason(),
515
+ usage
516
+ });
517
+ controller.close();
518
+ void session.destroy();
519
+ } else if (event.type === "session.error") {
520
+ const data = event.data;
521
+ controller.enqueue({
522
+ type: "error",
523
+ error: new Error(data?.message ?? "Session error")
524
+ });
525
+ controller.close();
526
+ void session.destroy();
527
+ }
528
+ });
529
+ await session.send({ prompt, attachments });
530
+ } catch (error) {
531
+ if (isAbortError(error)) {
532
+ controller.enqueue({
533
+ type: "error",
534
+ error: options.abortSignal?.aborted ? options.abortSignal.reason : error
535
+ });
536
+ } else {
537
+ handleCopilotError(error, { promptExcerpt: prompt.substring(0, 200) });
538
+ }
539
+ controller.close();
540
+ await session.destroy();
541
+ } finally {
542
+ if (options.abortSignal && abortListener) {
543
+ options.abortSignal.removeEventListener("abort", abortListener);
544
+ }
545
+ }
546
+ },
547
+ cancel: () => {
548
+ if (options.abortSignal && abortListener) {
549
+ options.abortSignal.removeEventListener("abort", abortListener);
550
+ }
551
+ }
552
+ });
553
+ return {
554
+ stream,
555
+ request: { body: { prompt, attachments } }
556
+ };
557
+ }
558
+ };
559
+ function createGitHubCopilot(options = {}) {
560
+ let clientInstance = null;
561
+ const getOrCreateClient = () => {
562
+ if (!clientInstance) {
563
+ clientInstance = new copilotSdk.CopilotClient(options.clientOptions ?? {});
564
+ }
565
+ return clientInstance;
566
+ };
567
+ const createModel = (modelId, settings = {}) => {
568
+ const mergedSettings = {
569
+ ...options.defaultSettings,
570
+ ...settings
571
+ };
572
+ return new GitHubCopilotLanguageModel({
573
+ modelId,
574
+ settings: mergedSettings,
575
+ getClient: getOrCreateClient
576
+ });
577
+ };
578
+ const provider$1 = function(modelId, settings) {
579
+ if (new.target) {
580
+ throw new Error("The GitHub Copilot model function cannot be called with the new keyword.");
581
+ }
582
+ return createModel(modelId, settings);
583
+ };
584
+ provider$1.languageModel = createModel;
585
+ provider$1.chat = createModel;
586
+ provider$1.specificationVersion = "v3";
587
+ provider$1.embeddingModel = (modelId) => {
588
+ throw new provider.NoSuchModelError({
589
+ modelId,
590
+ modelType: "embeddingModel"
591
+ });
592
+ };
593
+ provider$1.imageModel = (modelId) => {
594
+ throw new provider.NoSuchModelError({
595
+ modelId,
596
+ modelType: "imageModel"
597
+ });
598
+ };
599
+ provider$1.getClient = getOrCreateClient;
600
+ return provider$1;
601
+ }
602
+ var githubCopilot = createGitHubCopilot();
603
+
604
+ exports.GitHubCopilotLanguageModel = GitHubCopilotLanguageModel;
605
+ exports.createAPICallError = createAPICallError;
606
+ exports.createAuthenticationError = createAuthenticationError;
607
+ exports.createGitHubCopilot = createGitHubCopilot;
608
+ exports.githubCopilot = githubCopilot;
609
+ exports.handleCopilotError = handleCopilotError;
610
+ exports.isAbortError = isAbortError;
611
+ exports.isAuthenticationError = isAuthenticationError;
612
+ //# sourceMappingURL=index.cjs.map
613
+ //# sourceMappingURL=index.cjs.map