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