n8n-nodes-github-copilot 4.2.1 → 4.4.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.
Files changed (37) hide show
  1. package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.d.ts +1 -0
  2. package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js +47 -15
  3. package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.js +37 -0
  4. package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.d.ts +1 -0
  5. package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js +174 -10
  6. package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.d.ts +1 -0
  7. package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js +65 -4
  8. package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.js +64 -27
  9. package/dist/nodes/GitHubCopilotPGVector/GitHubCopilotPGVector.node.d.ts +10 -0
  10. package/dist/nodes/GitHubCopilotPGVector/GitHubCopilotPGVector.node.js +421 -0
  11. package/dist/package.json +3 -4
  12. package/dist/shared/models/DynamicModelLoader.d.ts +1 -0
  13. package/dist/shared/models/DynamicModelLoader.js +12 -0
  14. package/dist/shared/models/GitHubCopilotModels.d.ts +14 -8
  15. package/dist/shared/models/GitHubCopilotModels.js +255 -74
  16. package/dist/shared/utils/DynamicModelsManager.d.ts +11 -0
  17. package/dist/shared/utils/DynamicModelsManager.js +50 -0
  18. package/dist/shared/utils/GitHubCopilotApiUtils.d.ts +1 -0
  19. package/dist/shared/utils/GitHubCopilotApiUtils.js +85 -6
  20. package/package.json +3 -4
  21. package/shared/icons/copilot.svg +0 -34
  22. package/shared/index.ts +0 -27
  23. package/shared/models/DynamicModelLoader.ts +0 -124
  24. package/shared/models/GitHubCopilotModels.ts +0 -420
  25. package/shared/models/ModelVersionRequirements.ts +0 -165
  26. package/shared/properties/ModelProperties.ts +0 -52
  27. package/shared/properties/ModelSelectionProperty.ts +0 -68
  28. package/shared/utils/DynamicModelsManager.ts +0 -355
  29. package/shared/utils/EmbeddingsApiUtils.ts +0 -135
  30. package/shared/utils/FileChunkingApiUtils.ts +0 -176
  31. package/shared/utils/FileOptimizationUtils.ts +0 -210
  32. package/shared/utils/GitHubCopilotApiUtils.ts +0 -407
  33. package/shared/utils/GitHubCopilotEndpoints.ts +0 -212
  34. package/shared/utils/GitHubDeviceFlowHandler.ts +0 -276
  35. package/shared/utils/OAuthTokenManager.ts +0 -196
  36. package/shared/utils/provider-injection.ts +0 -277
  37. package/shared/utils/version-detection.ts +0 -145
@@ -1,355 +0,0 @@
1
- /**
2
- * Dynamic Models Manager
3
- *
4
- * Manages GitHub Copilot models dynamically based on user authentication.
5
- * Fetches and caches available models per user token.
6
- */
7
-
8
- import { GITHUB_COPILOT_API } from "./GitHubCopilotEndpoints";
9
-
10
- /**
11
- * Model information from GitHub Copilot API
12
- */
13
- interface CopilotModel {
14
- id: string;
15
- name: string;
16
- display_name?: string;
17
- model_picker_enabled?: boolean;
18
- model_picker_category?: "lightweight" | "versatile" | "powerful" | string;
19
- capabilities?: any;
20
- vendor?: string;
21
- version?: string;
22
- preview?: boolean;
23
- /** Billing information - only available with X-GitHub-Api-Version: 2025-05-01 header */
24
- billing?: {
25
- is_premium: boolean;
26
- multiplier: number;
27
- restricted_to?: string[];
28
- };
29
- is_chat_default?: boolean;
30
- is_chat_fallback?: boolean;
31
- }
32
-
33
- /**
34
- * API response format
35
- */
36
- interface ModelsResponse {
37
- data: CopilotModel[];
38
- }
39
-
40
- /**
41
- * Cached models with metadata
42
- */
43
- interface ModelCache {
44
- models: CopilotModel[];
45
- fetchedAt: number;
46
- expiresAt: number;
47
- tokenHash: string;
48
- }
49
-
50
- /**
51
- * Dynamic Models Manager
52
- * Fetches and caches available models per authenticated user
53
- */
54
- export class DynamicModelsManager {
55
- private static cache: Map<string, ModelCache> = new Map();
56
- private static readonly CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour
57
- private static readonly MIN_REFRESH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
58
-
59
- /**
60
- * Generate a hash for the token (for cache key)
61
- */
62
- private static hashToken(token: string): string {
63
- // Simple hash for cache key (not cryptographic)
64
- let hash = 0;
65
- for (let i = 0; i < token.length; i++) {
66
- const char = token.charCodeAt(i);
67
- hash = (hash << 5) - hash + char;
68
- hash = hash & hash; // Convert to 32-bit integer
69
- }
70
- return `models_${Math.abs(hash).toString(36)}`;
71
- }
72
-
73
- /**
74
- * Fetch models from GitHub Copilot API
75
- */
76
- private static async fetchModelsFromAPI(oauthToken: string): Promise<CopilotModel[]> {
77
- const url = `${GITHUB_COPILOT_API.BASE_URL}${GITHUB_COPILOT_API.ENDPOINTS.MODELS}`;
78
-
79
- console.log("🔄 Fetching available models from GitHub Copilot API...");
80
-
81
- const response = await fetch(url, {
82
- method: "GET",
83
- headers: {
84
- Authorization: `Bearer ${oauthToken}`,
85
- Accept: "application/json",
86
- "Content-Type": "application/json",
87
- "User-Agent": "GitHubCopilotChat/0.35.0",
88
- "Editor-Version": "vscode/1.96.0",
89
- "Editor-Plugin-Version": "copilot-chat/0.35.0",
90
- // CRITICAL: This API version returns billing.multiplier field
91
- // Source: microsoft/vscode-copilot-chat networking.ts
92
- "X-GitHub-Api-Version": "2025-05-01",
93
- "X-Interaction-Type": "model-access",
94
- "OpenAI-Intent": "model-access",
95
- "Copilot-Integration-Id": "vscode-chat",
96
- },
97
- });
98
-
99
- if (!response.ok) {
100
- const errorText = await response.text();
101
- console.error(`❌ Failed to fetch models: ${response.status} ${response.statusText}`);
102
- console.error(`❌ Error details: ${errorText}`);
103
- throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
104
- }
105
-
106
- const data = (await response.json()) as ModelsResponse;
107
-
108
- // Return ALL models (no filtering by model_picker_enabled)
109
- console.log(`✅ Fetched ${data.data.length} models from API`);
110
-
111
- return data.data;
112
- }
113
-
114
- /**
115
- * Get models for authenticated user (with caching)
116
- */
117
- public static async getAvailableModels(oauthToken: string): Promise<CopilotModel[]> {
118
- const tokenHash = this.hashToken(oauthToken);
119
- const now = Date.now();
120
-
121
- // Check cache
122
- const cached = this.cache.get(tokenHash);
123
- if (cached && cached.expiresAt > now) {
124
- const remainingMinutes = Math.round((cached.expiresAt - now) / 60000);
125
- console.log(`✅ Using cached models (expires in ${remainingMinutes} minutes)`);
126
- return cached.models;
127
- }
128
-
129
- // Check if we should wait before refreshing (avoid spam)
130
- if (cached && now - cached.fetchedAt < this.MIN_REFRESH_INTERVAL_MS) {
131
- const waitSeconds = Math.round((this.MIN_REFRESH_INTERVAL_MS - (now - cached.fetchedAt)) / 1000);
132
- console.log(`⏰ Models fetched recently, using cache (min refresh interval: ${waitSeconds}s)`);
133
- return cached.models;
134
- }
135
-
136
- // Fetch from API
137
- try {
138
- const models = await this.fetchModelsFromAPI(oauthToken);
139
-
140
- // Cache the result
141
- this.cache.set(tokenHash, {
142
- models,
143
- fetchedAt: now,
144
- expiresAt: now + this.CACHE_DURATION_MS,
145
- tokenHash,
146
- });
147
-
148
- return models;
149
- } catch (error) {
150
- console.error("❌ Failed to fetch models from API:", error);
151
-
152
- // Return cached models if available (even if expired)
153
- if (cached) {
154
- console.log("⚠️ Using expired cache as fallback");
155
- return cached.models;
156
- }
157
-
158
- // No cache available, throw error
159
- throw error;
160
- }
161
- }
162
-
163
- /**
164
- * Filter models by type (chat, embeddings, etc.)
165
- */
166
- public static filterModelsByType(models: CopilotModel[], type: string): CopilotModel[] {
167
- return models.filter((model) => {
168
- const modelType = (model.capabilities as any)?.type;
169
- return modelType === type;
170
- });
171
- }
172
-
173
- /**
174
- * Get cost multiplier from API billing data or fallback to estimation
175
- *
176
- * With X-GitHub-Api-Version: 2025-05-01, the API returns:
177
- * - billing.multiplier: 0, 0.33, 1, 3, or 10
178
- * - billing.is_premium: boolean
179
- *
180
- * Display format: "0x", "0.33x", "1x", "3x", "10x"
181
- */
182
- private static getCostMultiplier(model: CopilotModel): string {
183
- // BEST: Use API billing data if available (requires 2025-05-01 header)
184
- if (model.billing?.multiplier !== undefined) {
185
- return `${model.billing.multiplier}x`;
186
- }
187
-
188
- // FALLBACK: Estimate based on model ID patterns
189
- // This is used when API doesn't return billing data
190
- const id = model.id.toLowerCase();
191
-
192
- // === 0x FREE TIER ===
193
- // GPT-4 series (legacy, included in subscription)
194
- if (id === 'gpt-4.1' || id.startsWith('gpt-4.1-')) return '0x';
195
- if (id === 'gpt-4o' || id.startsWith('gpt-4o-')) return '0x';
196
- if (id === 'gpt-4' || id === 'gpt-4-0613') return '0x';
197
- // Mini models
198
- if (id === 'gpt-5-mini') return '0x';
199
- if (id === 'gpt-4o-mini' || id.startsWith('gpt-4o-mini-')) return '0x';
200
- // Grok fast models
201
- if (id.includes('grok') && id.includes('fast')) return '0x';
202
- // Raptor mini (ID: oswe-vscode-prime)
203
- if (id === 'oswe-vscode-prime' || id.includes('oswe-vscode')) return '0x';
204
-
205
- // === 0.33x ECONOMY TIER ===
206
- // Claude Haiku (economy)
207
- if (id.includes('haiku')) return '0.33x';
208
- // Gemini Flash models
209
- if (id.includes('flash')) return '0.33x';
210
- // Codex-Mini models
211
- if (id.includes('codex-mini')) return '0.33x';
212
-
213
- // === 10x ULTRA PREMIUM ===
214
- // Claude Opus 4.1 specifically
215
- if (id === 'claude-opus-41' || id === 'claude-opus-4.1') return '10x';
216
-
217
- // === 3x PREMIUM TIER ===
218
- // Claude Opus 4.5
219
- if (id.includes('opus')) return '3x';
220
-
221
- // === 1x STANDARD TIER (default for most models) ===
222
- // GPT-5 series (including Codex variants - they are 1x, not 3x!)
223
- // Claude Sonnet
224
- // Gemini Pro
225
- // Everything else
226
- return '1x';
227
- }
228
-
229
- /**
230
- * Convert models to n8n options format with capability badges
231
- */
232
- public static modelsToN8nOptions(models: CopilotModel[]): Array<{
233
- name: string;
234
- value: string;
235
- description?: string;
236
- }> {
237
- // First pass: count how many models share the same display name
238
- const nameCount = new Map<string, number>();
239
- models.forEach((model) => {
240
- const displayName = model.display_name || model.name || model.id;
241
- nameCount.set(displayName, (nameCount.get(displayName) || 0) + 1);
242
- });
243
-
244
- return models.map((model) => {
245
- // Build capability badges/chips
246
- const badges: string[] = [];
247
-
248
- if (model.capabilities) {
249
- const supports = (model.capabilities as any).supports || {};
250
-
251
- // Check each capability and add corresponding badge
252
- if (supports.streaming) badges.push("🔄 Streaming");
253
- if (supports.tool_calls) badges.push("🔧 Tools");
254
- if (supports.vision) badges.push("👁️ Vision");
255
- if (supports.structured_outputs) badges.push("📋 Structured");
256
- if (supports.parallel_tool_calls) badges.push("⚡ Parallel");
257
-
258
- // Check for thinking capabilities (reasoning models)
259
- if (supports.max_thinking_budget) badges.push("🧠 Reasoning");
260
- }
261
-
262
- // Build display name with badges and cost multiplier (VS Code style: "Model Name • 1x")
263
- const displayName = model.display_name || model.name || model.id;
264
- const costMultiplier = this.getCostMultiplier(model);
265
- const badgesText = badges.length > 0 ? ` [${badges.join(" • ")}]` : "";
266
-
267
- // Check if this display name has duplicates
268
- const hasDuplicates = (nameCount.get(displayName) || 0) > 1;
269
-
270
- // Get category label (capitalize first letter)
271
- const category = model.model_picker_category || "";
272
- const categoryLabel = category ? ` - ${category.charAt(0).toUpperCase() + category.slice(1)}` : "";
273
-
274
- // Format: "Model Name • 0x - Lightweight [badges]"
275
- const multiplierDisplay = ` • ${costMultiplier}${categoryLabel}`;
276
-
277
- // Build description with more details
278
- let description = "";
279
- if (model.capabilities) {
280
- const limits = (model.capabilities as any).limits || {};
281
- const parts: string[] = [];
282
-
283
- // If duplicates exist, add model ID
284
- if (hasDuplicates) {
285
- parts.push(`ID: ${model.id}`);
286
- }
287
-
288
- if (limits.max_context_window_tokens) {
289
- parts.push(`Context: ${(limits.max_context_window_tokens / 1000).toFixed(0)}k`);
290
- }
291
- if (limits.max_output_tokens) {
292
- parts.push(`Output: ${(limits.max_output_tokens / 1000).toFixed(0)}k`);
293
- }
294
- if (model.vendor) {
295
- parts.push(`Provider: ${model.vendor}`);
296
- }
297
-
298
- description = parts.join(" • ");
299
- } else {
300
- // No capabilities, just show ID if duplicates
301
- if (hasDuplicates) {
302
- description = `ID: ${model.id}`;
303
- }
304
- }
305
-
306
- return {
307
- name: `${displayName}${multiplierDisplay}${badgesText}`,
308
- value: model.id,
309
- description: description || undefined,
310
- };
311
- });
312
- }
313
-
314
- /**
315
- * Clear cache for specific token
316
- */
317
- public static clearCache(oauthToken: string): void {
318
- const tokenHash = this.hashToken(oauthToken);
319
- this.cache.delete(tokenHash);
320
- console.log("🗑️ Cleared models cache");
321
- }
322
-
323
- /**
324
- * Clear all cached models
325
- */
326
- public static clearAllCache(): void {
327
- this.cache.clear();
328
- console.log("🗑️ Cleared all models cache");
329
- }
330
-
331
- /**
332
- * Get cache info for debugging
333
- */
334
- public static getCacheInfo(oauthToken: string): {
335
- cached: boolean;
336
- modelsCount: number;
337
- expiresIn: number;
338
- fetchedAt: string;
339
- } | null {
340
- const tokenHash = this.hashToken(oauthToken);
341
- const cached = this.cache.get(tokenHash);
342
-
343
- if (!cached) {
344
- return null;
345
- }
346
-
347
- const now = Date.now();
348
- return {
349
- cached: true,
350
- modelsCount: cached.models.length,
351
- expiresIn: Math.max(0, cached.expiresAt - now),
352
- fetchedAt: new Date(cached.fetchedAt).toISOString(),
353
- };
354
- }
355
- }
@@ -1,135 +0,0 @@
1
- /**
2
- * Embeddings API Utilities
3
- *
4
- * Shared functions for making embeddings API requests.
5
- * Used by both GitHubCopilotEmbeddings node and GitHubCopilotTest node.
6
- */
7
-
8
- import { GitHubCopilotEndpoints, GITHUB_COPILOT_API } from "./GitHubCopilotEndpoints";
9
-
10
- /**
11
- * Embedding Response from GitHub Copilot API
12
- */
13
- export interface EmbeddingResponse {
14
- object: string;
15
- data: Array<{
16
- object: string;
17
- index: number;
18
- embedding: number[];
19
- }>;
20
- model: string;
21
- usage: {
22
- prompt_tokens: number;
23
- total_tokens: number;
24
- };
25
- }
26
-
27
- /**
28
- * Embedding Request Body
29
- */
30
- export interface EmbeddingRequest {
31
- model: string;
32
- input: string[];
33
- dimensions?: number;
34
- encoding_format?: "float" | "base64";
35
- user?: string;
36
- }
37
-
38
- /**
39
- * Execute embeddings request with retry logic
40
- *
41
- * @param oauthToken - OAuth token for authentication
42
- * @param requestBody - Embeddings request body
43
- * @param enableRetry - Whether to enable retry on TPM quota errors
44
- * @param maxRetries - Maximum number of retry attempts
45
- * @returns Embedding response
46
- */
47
- export async function executeEmbeddingsRequest(
48
- oauthToken: string,
49
- requestBody: EmbeddingRequest,
50
- enableRetry = true,
51
- maxRetries = 3,
52
- ): Promise<EmbeddingResponse> {
53
- let lastError: Error | null = null;
54
-
55
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
56
- try {
57
- const response = await fetch(
58
- GitHubCopilotEndpoints.getEmbeddingsUrl(),
59
- {
60
- method: "POST",
61
- headers: GitHubCopilotEndpoints.getEmbeddingsHeaders(oauthToken),
62
- body: JSON.stringify(requestBody),
63
- },
64
- );
65
-
66
- if (!response.ok) {
67
- const errorText = await response.text();
68
-
69
- // Check if it's a retryable error (TPM quota)
70
- if (
71
- GitHubCopilotEndpoints.isTpmQuotaError(response.status) &&
72
- enableRetry &&
73
- attempt < maxRetries
74
- ) {
75
- const delay = GitHubCopilotEndpoints.getRetryDelay(attempt + 1);
76
- console.log(
77
- `Embeddings attempt ${attempt + 1} failed with ${response.status}, retrying in ${delay}ms...`,
78
- );
79
- await new Promise((resolve) => setTimeout(resolve, delay));
80
- continue;
81
- }
82
-
83
- throw new Error(
84
- `Embeddings API Error ${response.status}: ${errorText}`,
85
- );
86
- }
87
-
88
- const data = (await response.json()) as EmbeddingResponse;
89
- return data;
90
- } catch (error) {
91
- lastError = error instanceof Error ? error : new Error(String(error));
92
-
93
- if (attempt < maxRetries && enableRetry) {
94
- const delay = GitHubCopilotEndpoints.getRetryDelay(attempt + 1);
95
- console.log(`Embeddings attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
96
- await new Promise((resolve) => setTimeout(resolve, delay));
97
- continue;
98
- }
99
-
100
- throw lastError;
101
- }
102
- }
103
-
104
- throw lastError || new Error("Maximum retry attempts exceeded for embeddings request");
105
- }
106
-
107
- /**
108
- * Simple embeddings request without retry logic
109
- *
110
- * @param oauthToken - OAuth token for authentication
111
- * @param requestBody - Embeddings request body
112
- * @returns Embedding response
113
- */
114
- export async function executeEmbeddingsRequestSimple(
115
- oauthToken: string,
116
- requestBody: EmbeddingRequest,
117
- ): Promise<EmbeddingResponse> {
118
- const response = await fetch(
119
- GitHubCopilotEndpoints.getEmbeddingsUrl(),
120
- {
121
- method: "POST",
122
- headers: GitHubCopilotEndpoints.getEmbeddingsHeaders(oauthToken),
123
- body: JSON.stringify(requestBody),
124
- },
125
- );
126
-
127
- if (!response.ok) {
128
- const errorText = await response.text();
129
- throw new Error(
130
- `Embeddings API Error ${response.status}: ${errorText}`,
131
- );
132
- }
133
-
134
- return (await response.json()) as EmbeddingResponse;
135
- }
@@ -1,176 +0,0 @@
1
- import { GitHubCopilotEndpoints } from './GitHubCopilotEndpoints';
2
-
3
- export interface ChunkRequest {
4
- content: string;
5
- embed: boolean;
6
- qos: 'Batch' | 'Online';
7
- }
8
-
9
- export interface Chunk {
10
- content: string;
11
- embedding?: number[];
12
- start: number;
13
- end: number;
14
- metadata?: Record<string, unknown>;
15
- }
16
-
17
- export interface ChunkResponse {
18
- chunks: Chunk[];
19
- total: number;
20
- contentLength: number;
21
- }
22
-
23
- /**
24
- * Chunk a file using GitHub Copilot API
25
- */
26
- export async function chunkFile(
27
- token: string,
28
- fileContent: string,
29
- embeddings = true,
30
- qos: 'Batch' | 'Online' = 'Online',
31
- ): Promise<ChunkResponse> {
32
- const url = 'https://api.githubcopilot.com/chunks';
33
- const headers = GitHubCopilotEndpoints.getAuthHeaders(token, true);
34
-
35
- const requestBody: ChunkRequest = {
36
- content: fileContent,
37
- embed: embeddings,
38
- qos,
39
- };
40
-
41
- console.log(`🔪 Chunking file (${fileContent.length} chars, embed=${embeddings}, qos=${qos})`);
42
-
43
- const response = await fetch(url, {
44
- method: 'POST',
45
- headers,
46
- body: JSON.stringify(requestBody),
47
- });
48
-
49
- if (!response.ok) {
50
- const errorText = await response.text();
51
- throw new Error(`Chunking API error: ${response.status} ${response.statusText}. ${errorText}`);
52
- }
53
-
54
- const data = (await response.json()) as ChunkResponse;
55
- console.log(`✅ Chunked into ${data.chunks.length} chunks`);
56
-
57
- return data;
58
- }
59
-
60
- /**
61
- * Calculate cosine similarity between two vectors
62
- */
63
- function cosineSimilarity(a: number[], b: number[]): number {
64
- if (a.length !== b.length) {
65
- throw new Error('Vectors must have same length');
66
- }
67
-
68
- let dotProduct = 0;
69
- let normA = 0;
70
- let normB = 0;
71
-
72
- for (let i = 0; i < a.length; i++) {
73
- dotProduct += a[i] * b[i];
74
- normA += a[i] * a[i];
75
- normB += b[i] * b[i];
76
- }
77
-
78
- return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
79
- }
80
-
81
- /**
82
- * Select the most relevant chunks based on query embedding
83
- */
84
- export function selectRelevantChunks(
85
- chunks: Chunk[],
86
- queryEmbedding: number[],
87
- maxTokens = 10000,
88
- minRelevance = 0.5,
89
- ): string {
90
- if (!chunks.length) {
91
- return '';
92
- }
93
-
94
- const rankedChunks = chunks
95
- .filter((chunk) => chunk.embedding)
96
- .map((chunk) => ({
97
- chunk,
98
- relevance: cosineSimilarity(chunk.embedding!, queryEmbedding),
99
- }))
100
- .filter((item) => item.relevance >= minRelevance)
101
- .sort((a, b) => b.relevance - a.relevance);
102
-
103
- const selectedChunks: string[] = [];
104
- let totalTokens = 0;
105
-
106
- for (const item of rankedChunks) {
107
- const chunkTokens = Math.ceil(item.chunk.content.length / 4);
108
- if (totalTokens + chunkTokens > maxTokens) {
109
- break;
110
- }
111
- selectedChunks.push(item.chunk.content);
112
- totalTokens += chunkTokens;
113
- console.log(
114
- ` ✓ Selected chunk (relevance: ${item.relevance.toFixed(3)}, tokens: ~${chunkTokens})`,
115
- );
116
- }
117
-
118
- console.log(
119
- `📊 Selected ${selectedChunks.length}/${rankedChunks.length} chunks (~${totalTokens} tokens)`,
120
- );
121
-
122
- return selectedChunks.join('\n\n---\n\n');
123
- }
124
-
125
- /**
126
- * Select top chunks by order (without relevance ranking)
127
- */
128
- export function selectTopChunks(chunks: Chunk[], maxTokens = 10000): string {
129
- const selectedChunks: string[] = [];
130
- let totalTokens = 0;
131
-
132
- for (const chunk of chunks) {
133
- const chunkTokens = Math.ceil(chunk.content.length / 4);
134
- if (totalTokens + chunkTokens > maxTokens) {
135
- break;
136
- }
137
- selectedChunks.push(chunk.content);
138
- totalTokens += chunkTokens;
139
- }
140
-
141
- console.log(`📊 Selected ${selectedChunks.length}/${chunks.length} chunks (~${totalTokens} tokens)`);
142
-
143
- return selectedChunks.join('\n\n---\n\n');
144
- }
145
-
146
- /**
147
- * Estimate token count for text
148
- */
149
- export function estimateTokens(text: string): number {
150
- return Math.ceil(text.length / 4);
151
- }
152
-
153
- /**
154
- * Get embedding for a query string
155
- */
156
- export async function getQueryEmbedding(token: string, query: string): Promise<number[]> {
157
- const url = 'https://api.githubcopilot.com/embeddings';
158
- const headers = GitHubCopilotEndpoints.getEmbeddingsHeaders(token);
159
-
160
- const response = await fetch(url, {
161
- method: 'POST',
162
- headers,
163
- body: JSON.stringify({
164
- input: [query],
165
- model: 'text-embedding-3-small',
166
- }),
167
- });
168
-
169
- if (!response.ok) {
170
- const errorText = await response.text();
171
- throw new Error(`Embeddings API error: ${response.status} ${response.statusText}. ${errorText}`);
172
- }
173
-
174
- const data = (await response.json()) as { data: Array<{ embedding: number[] }> };
175
- return data.data[0].embedding;
176
- }