copilot-router 1.0.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 +241 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +32 -0
- package/dist/lib/api-config.d.ts +15 -0
- package/dist/lib/api-config.js +30 -0
- package/dist/lib/database.d.ts +60 -0
- package/dist/lib/database.js +228 -0
- package/dist/lib/error.d.ts +11 -0
- package/dist/lib/error.js +34 -0
- package/dist/lib/state.d.ts +9 -0
- package/dist/lib/state.js +3 -0
- package/dist/lib/token-manager.d.ts +95 -0
- package/dist/lib/token-manager.js +241 -0
- package/dist/lib/utils.d.ts +8 -0
- package/dist/lib/utils.js +10 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +97 -0
- package/dist/routes/anthropic/routes.d.ts +2 -0
- package/dist/routes/anthropic/routes.js +155 -0
- package/dist/routes/anthropic/stream-translation.d.ts +3 -0
- package/dist/routes/anthropic/stream-translation.js +136 -0
- package/dist/routes/anthropic/translation.d.ts +4 -0
- package/dist/routes/anthropic/translation.js +241 -0
- package/dist/routes/anthropic/types.d.ts +165 -0
- package/dist/routes/anthropic/types.js +2 -0
- package/dist/routes/anthropic/utils.d.ts +2 -0
- package/dist/routes/anthropic/utils.js +12 -0
- package/dist/routes/auth/routes.d.ts +2 -0
- package/dist/routes/auth/routes.js +158 -0
- package/dist/routes/gemini/routes.d.ts +2 -0
- package/dist/routes/gemini/routes.js +163 -0
- package/dist/routes/gemini/translation.d.ts +5 -0
- package/dist/routes/gemini/translation.js +215 -0
- package/dist/routes/gemini/types.d.ts +63 -0
- package/dist/routes/gemini/types.js +2 -0
- package/dist/routes/openai/routes.d.ts +2 -0
- package/dist/routes/openai/routes.js +215 -0
- package/dist/routes/utility/routes.d.ts +2 -0
- package/dist/routes/utility/routes.js +28 -0
- package/dist/services/copilot/create-chat-completions.d.ts +130 -0
- package/dist/services/copilot/create-chat-completions.js +32 -0
- package/dist/services/copilot/create-embeddings.d.ts +20 -0
- package/dist/services/copilot/create-embeddings.js +19 -0
- package/dist/services/copilot/get-models.d.ts +51 -0
- package/dist/services/copilot/get-models.js +45 -0
- package/dist/services/github/get-device-code.d.ts +11 -0
- package/dist/services/github/get-device-code.js +21 -0
- package/dist/services/github/get-user.d.ts +11 -0
- package/dist/services/github/get-user.js +17 -0
- package/dist/services/github/poll-access-token.d.ts +13 -0
- package/dist/services/github/poll-access-token.js +56 -0
- package/package.json +56 -0
- package/public/index.html +419 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
import { streamSSE } from "hono/streaming";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { forwardError } from "../../lib/error.js";
|
|
5
|
+
import { tokenManager } from "../../lib/token-manager.js";
|
|
6
|
+
import { createChatCompletions, } from "../../services/copilot/create-chat-completions.js";
|
|
7
|
+
import { createEmbeddings, } from "../../services/copilot/create-embeddings.js";
|
|
8
|
+
import { getModels, getModelsForAllTokens } from "../../services/copilot/get-models.js";
|
|
9
|
+
const CommonResponseError = z.object({
|
|
10
|
+
error: z.object({
|
|
11
|
+
message: z.string(),
|
|
12
|
+
type: z.string(),
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
// Chat completions route
|
|
16
|
+
const chatCompletionsRoute = createRoute({
|
|
17
|
+
method: "post",
|
|
18
|
+
path: "/chat/completions",
|
|
19
|
+
tags: ["OpenAI API"],
|
|
20
|
+
summary: "Create a chat completion",
|
|
21
|
+
description: "Create a chat completion using the OpenAI-compatible API interface.",
|
|
22
|
+
request: {
|
|
23
|
+
body: {
|
|
24
|
+
content: {
|
|
25
|
+
"application/json": {
|
|
26
|
+
schema: z.object({}).passthrough(),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
responses: {
|
|
32
|
+
200: {
|
|
33
|
+
content: {
|
|
34
|
+
"application/json": {
|
|
35
|
+
schema: z.object({}).passthrough(),
|
|
36
|
+
},
|
|
37
|
+
"text/event-stream": {
|
|
38
|
+
schema: z.string(),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
description: "Successfully created chat completion",
|
|
42
|
+
},
|
|
43
|
+
400: {
|
|
44
|
+
content: { "application/json": { schema: CommonResponseError } },
|
|
45
|
+
description: "Bad request",
|
|
46
|
+
},
|
|
47
|
+
500: {
|
|
48
|
+
content: { "application/json": { schema: CommonResponseError } },
|
|
49
|
+
description: "Internal server error",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
// Models route
|
|
54
|
+
const modelsRoute = createRoute({
|
|
55
|
+
method: "get",
|
|
56
|
+
path: "/models",
|
|
57
|
+
tags: ["OpenAI API"],
|
|
58
|
+
summary: "List available models",
|
|
59
|
+
description: "List all available models from GitHub Copilot.",
|
|
60
|
+
responses: {
|
|
61
|
+
200: {
|
|
62
|
+
content: {
|
|
63
|
+
"application/json": {
|
|
64
|
+
schema: z.object({
|
|
65
|
+
object: z.string(),
|
|
66
|
+
data: z.array(z.object({}).passthrough()),
|
|
67
|
+
has_more: z.boolean(),
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
description: "Successfully retrieved models",
|
|
72
|
+
},
|
|
73
|
+
500: {
|
|
74
|
+
content: { "application/json": { schema: CommonResponseError } },
|
|
75
|
+
description: "Internal server error",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
// Embeddings route
|
|
80
|
+
const embeddingsRoute = createRoute({
|
|
81
|
+
method: "post",
|
|
82
|
+
path: "/embeddings",
|
|
83
|
+
tags: ["OpenAI API"],
|
|
84
|
+
summary: "Create embeddings",
|
|
85
|
+
description: "Create embeddings for the provided input text.",
|
|
86
|
+
request: {
|
|
87
|
+
body: {
|
|
88
|
+
content: {
|
|
89
|
+
"application/json": {
|
|
90
|
+
schema: z.object({
|
|
91
|
+
input: z.union([z.string(), z.array(z.string())]),
|
|
92
|
+
model: z.string(),
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
responses: {
|
|
99
|
+
200: {
|
|
100
|
+
content: {
|
|
101
|
+
"application/json": {
|
|
102
|
+
schema: z.object({}).passthrough(),
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
description: "Successfully created embeddings",
|
|
106
|
+
},
|
|
107
|
+
500: {
|
|
108
|
+
content: { "application/json": { schema: CommonResponseError } },
|
|
109
|
+
description: "Internal server error",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const isNonStreaming = (response) => Object.hasOwn(response, "choices");
|
|
114
|
+
export function registerOpenAIRoutes(app) {
|
|
115
|
+
// POST /chat/completions
|
|
116
|
+
app.openapi(chatCompletionsRoute, async (c) => {
|
|
117
|
+
try {
|
|
118
|
+
const payload = await c.req.json();
|
|
119
|
+
consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
|
|
120
|
+
// Get a random token entry for load balancing
|
|
121
|
+
const tokenEntry = tokenManager.getRandomTokenEntry();
|
|
122
|
+
if (!tokenEntry) {
|
|
123
|
+
return c.json({ error: { message: "No active tokens available", type: "auth_error" } }, 503);
|
|
124
|
+
}
|
|
125
|
+
// Fetch models if needed
|
|
126
|
+
if (!tokenEntry.models) {
|
|
127
|
+
try {
|
|
128
|
+
tokenEntry.models = await getModels(tokenEntry);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
consola.warn("Could not fetch models for max_tokens lookup");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const selectedModel = tokenEntry.models?.data.find((model) => model.id === payload.model);
|
|
135
|
+
if (!payload.max_tokens && selectedModel) {
|
|
136
|
+
payload.max_tokens = selectedModel.capabilities.limits.max_output_tokens;
|
|
137
|
+
}
|
|
138
|
+
const response = await createChatCompletions(payload, tokenEntry);
|
|
139
|
+
if (isNonStreaming(response)) {
|
|
140
|
+
consola.debug("Non-streaming response");
|
|
141
|
+
return c.json(response);
|
|
142
|
+
}
|
|
143
|
+
consola.debug("Streaming response");
|
|
144
|
+
return streamSSE(c, async (stream) => {
|
|
145
|
+
for await (const chunk of response) {
|
|
146
|
+
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
147
|
+
await stream.writeSSE(chunk);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return await forwardError(c, error);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// GET /models
|
|
156
|
+
app.openapi(modelsRoute, async (c) => {
|
|
157
|
+
try {
|
|
158
|
+
// Check for grouped query parameter
|
|
159
|
+
const grouped = c.req.query("grouped") === "true";
|
|
160
|
+
if (grouped) {
|
|
161
|
+
// Return models grouped by token
|
|
162
|
+
const tokenModels = await getModelsForAllTokens();
|
|
163
|
+
return c.json({
|
|
164
|
+
object: "list",
|
|
165
|
+
grouped: true,
|
|
166
|
+
tokens: tokenModels.map((tm) => ({
|
|
167
|
+
token_id: tm.tokenId,
|
|
168
|
+
username: tm.username,
|
|
169
|
+
account_type: tm.accountType,
|
|
170
|
+
error: tm.error,
|
|
171
|
+
models: tm.models?.data.map((model) => ({
|
|
172
|
+
id: model.id,
|
|
173
|
+
object: "model",
|
|
174
|
+
type: "model",
|
|
175
|
+
created: 0,
|
|
176
|
+
created_at: new Date(0).toISOString(),
|
|
177
|
+
owned_by: model.vendor,
|
|
178
|
+
display_name: model.name,
|
|
179
|
+
})) ?? [],
|
|
180
|
+
})),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Get models from first active token
|
|
184
|
+
const modelsResponse = await getModels();
|
|
185
|
+
const models = modelsResponse.data.map((model) => ({
|
|
186
|
+
id: model.id,
|
|
187
|
+
object: "model",
|
|
188
|
+
type: "model",
|
|
189
|
+
created: 0,
|
|
190
|
+
created_at: new Date(0).toISOString(),
|
|
191
|
+
owned_by: model.vendor,
|
|
192
|
+
display_name: model.name,
|
|
193
|
+
}));
|
|
194
|
+
return c.json({
|
|
195
|
+
object: "list",
|
|
196
|
+
data: models,
|
|
197
|
+
has_more: false,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return await forwardError(c, error);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
// POST /embeddings
|
|
205
|
+
app.openapi(embeddingsRoute, async (c) => {
|
|
206
|
+
try {
|
|
207
|
+
const payload = await c.req.json();
|
|
208
|
+
const response = await createEmbeddings(payload);
|
|
209
|
+
return c.json(response);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return await forwardError(c, error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { tokenManager } from "../../lib/token-manager.js";
|
|
2
|
+
export function registerUtilityRoutes(app) {
|
|
3
|
+
// GET /
|
|
4
|
+
app.get("/", (c) => {
|
|
5
|
+
const counts = tokenManager.getTokenCount();
|
|
6
|
+
return c.text(`Copilot Router is running (${counts.active}/${counts.total} active tokens)`);
|
|
7
|
+
});
|
|
8
|
+
// GET /token
|
|
9
|
+
app.get("/token", (c) => {
|
|
10
|
+
try {
|
|
11
|
+
const entries = tokenManager.getActiveTokenEntries();
|
|
12
|
+
const tokens = entries.map(e => ({
|
|
13
|
+
id: e.id,
|
|
14
|
+
username: e.username,
|
|
15
|
+
copilot_token: e.copilotToken?.substring(0, 20) + "...",
|
|
16
|
+
expires_at: e.copilotTokenExpiresAt?.toISOString(),
|
|
17
|
+
}));
|
|
18
|
+
return c.json({
|
|
19
|
+
count: tokens.length,
|
|
20
|
+
tokens,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error("Error fetching token:", error);
|
|
25
|
+
return c.json({ error: { message: "Failed to fetch token", type: "error" } }, 500);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type TokenEntry } from "../../lib/token-manager.js";
|
|
2
|
+
export declare const createChatCompletions: (payload: ChatCompletionsPayload, tokenEntry?: TokenEntry) => Promise<ChatCompletionResponse | AsyncGenerator<import("fetch-event-stream").ServerSentEventMessage, void, unknown>>;
|
|
3
|
+
export interface ChatCompletionChunk {
|
|
4
|
+
id: string;
|
|
5
|
+
object: "chat.completion.chunk";
|
|
6
|
+
created: number;
|
|
7
|
+
model: string;
|
|
8
|
+
choices: Array<Choice>;
|
|
9
|
+
system_fingerprint?: string;
|
|
10
|
+
usage?: {
|
|
11
|
+
prompt_tokens: number;
|
|
12
|
+
completion_tokens: number;
|
|
13
|
+
total_tokens: number;
|
|
14
|
+
prompt_tokens_details?: {
|
|
15
|
+
cached_tokens: number;
|
|
16
|
+
};
|
|
17
|
+
completion_tokens_details?: {
|
|
18
|
+
accepted_prediction_tokens: number;
|
|
19
|
+
rejected_prediction_tokens: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface Delta {
|
|
24
|
+
content?: string | null;
|
|
25
|
+
role?: "user" | "assistant" | "system" | "tool";
|
|
26
|
+
tool_calls?: Array<{
|
|
27
|
+
index: number;
|
|
28
|
+
id?: string;
|
|
29
|
+
type?: "function";
|
|
30
|
+
function?: {
|
|
31
|
+
name?: string;
|
|
32
|
+
arguments?: string;
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
interface Choice {
|
|
37
|
+
index: number;
|
|
38
|
+
delta: Delta;
|
|
39
|
+
finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null;
|
|
40
|
+
logprobs: object | null;
|
|
41
|
+
}
|
|
42
|
+
export interface ChatCompletionResponse {
|
|
43
|
+
id: string;
|
|
44
|
+
object: "chat.completion";
|
|
45
|
+
created: number;
|
|
46
|
+
model: string;
|
|
47
|
+
choices: Array<ChoiceNonStreaming>;
|
|
48
|
+
system_fingerprint?: string;
|
|
49
|
+
usage?: {
|
|
50
|
+
prompt_tokens: number;
|
|
51
|
+
completion_tokens: number;
|
|
52
|
+
total_tokens: number;
|
|
53
|
+
prompt_tokens_details?: {
|
|
54
|
+
cached_tokens: number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
interface ResponseMessage {
|
|
59
|
+
role: "assistant";
|
|
60
|
+
content: string | null;
|
|
61
|
+
tool_calls?: Array<ToolCall>;
|
|
62
|
+
}
|
|
63
|
+
interface ChoiceNonStreaming {
|
|
64
|
+
index: number;
|
|
65
|
+
message: ResponseMessage;
|
|
66
|
+
logprobs: object | null;
|
|
67
|
+
finish_reason: "stop" | "length" | "tool_calls" | "content_filter";
|
|
68
|
+
}
|
|
69
|
+
export interface ChatCompletionsPayload {
|
|
70
|
+
messages: Array<Message>;
|
|
71
|
+
model: string;
|
|
72
|
+
temperature?: number | null;
|
|
73
|
+
top_p?: number | null;
|
|
74
|
+
max_tokens?: number | null;
|
|
75
|
+
stop?: string | Array<string> | null;
|
|
76
|
+
n?: number | null;
|
|
77
|
+
stream?: boolean | null;
|
|
78
|
+
frequency_penalty?: number | null;
|
|
79
|
+
presence_penalty?: number | null;
|
|
80
|
+
logit_bias?: Record<string, number> | null;
|
|
81
|
+
logprobs?: boolean | null;
|
|
82
|
+
response_format?: {
|
|
83
|
+
type: "json_object";
|
|
84
|
+
} | null;
|
|
85
|
+
seed?: number | null;
|
|
86
|
+
tools?: Array<Tool> | null;
|
|
87
|
+
tool_choice?: "none" | "auto" | "required" | {
|
|
88
|
+
type: "function";
|
|
89
|
+
function: {
|
|
90
|
+
name: string;
|
|
91
|
+
};
|
|
92
|
+
} | null;
|
|
93
|
+
user?: string | null;
|
|
94
|
+
}
|
|
95
|
+
export interface Tool {
|
|
96
|
+
type: "function";
|
|
97
|
+
function: {
|
|
98
|
+
name: string;
|
|
99
|
+
description?: string;
|
|
100
|
+
parameters: Record<string, unknown>;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export interface Message {
|
|
104
|
+
role: "user" | "assistant" | "system" | "tool" | "developer";
|
|
105
|
+
content: string | Array<ContentPart> | null;
|
|
106
|
+
name?: string;
|
|
107
|
+
tool_calls?: Array<ToolCall>;
|
|
108
|
+
tool_call_id?: string;
|
|
109
|
+
}
|
|
110
|
+
export interface ToolCall {
|
|
111
|
+
id: string;
|
|
112
|
+
type: "function";
|
|
113
|
+
function: {
|
|
114
|
+
name: string;
|
|
115
|
+
arguments: string;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export type ContentPart = TextPart | ImagePart;
|
|
119
|
+
export interface TextPart {
|
|
120
|
+
type: "text";
|
|
121
|
+
text: string;
|
|
122
|
+
}
|
|
123
|
+
export interface ImagePart {
|
|
124
|
+
type: "image_url";
|
|
125
|
+
image_url: {
|
|
126
|
+
url: string;
|
|
127
|
+
detail?: "low" | "high" | "auto";
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import { events } from "fetch-event-stream";
|
|
3
|
+
import { copilotHeadersForEntry, copilotBaseUrlForEntry } from "../../lib/api-config.js";
|
|
4
|
+
import { HTTPError } from "../../lib/error.js";
|
|
5
|
+
import { tokenManager } from "../../lib/token-manager.js";
|
|
6
|
+
export const createChatCompletions = async (payload, tokenEntry) => {
|
|
7
|
+
// Get token entry - use provided one or get random for load balancing
|
|
8
|
+
const entry = tokenEntry || tokenManager.getRandomTokenEntry();
|
|
9
|
+
if (!entry)
|
|
10
|
+
throw new Error("No active tokens available");
|
|
11
|
+
const enableVision = payload.messages.some((x) => typeof x.content !== "string" &&
|
|
12
|
+
x.content?.some((x) => x.type === "image_url"));
|
|
13
|
+
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
14
|
+
const headers = {
|
|
15
|
+
...copilotHeadersForEntry(entry, enableVision),
|
|
16
|
+
"X-Initiator": isAgentCall ? "agent" : "user",
|
|
17
|
+
};
|
|
18
|
+
const response = await fetch(`${copilotBaseUrlForEntry(entry)}/chat/completions`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers,
|
|
21
|
+
body: JSON.stringify(payload),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
consola.error("Failed to create chat completions", response);
|
|
25
|
+
tokenManager.reportError(entry.id);
|
|
26
|
+
throw new HTTPError("Failed to create chat completions", response);
|
|
27
|
+
}
|
|
28
|
+
if (payload.stream) {
|
|
29
|
+
return events(response);
|
|
30
|
+
}
|
|
31
|
+
return (await response.json());
|
|
32
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type TokenEntry } from "../../lib/token-manager.js";
|
|
2
|
+
export declare const createEmbeddings: (payload: EmbeddingRequest, tokenEntry?: TokenEntry) => Promise<EmbeddingResponse>;
|
|
3
|
+
export interface EmbeddingRequest {
|
|
4
|
+
input: string | Array<string>;
|
|
5
|
+
model: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Embedding {
|
|
8
|
+
object: string;
|
|
9
|
+
embedding: Array<number>;
|
|
10
|
+
index: number;
|
|
11
|
+
}
|
|
12
|
+
export interface EmbeddingResponse {
|
|
13
|
+
object: string;
|
|
14
|
+
data: Array<Embedding>;
|
|
15
|
+
model: string;
|
|
16
|
+
usage: {
|
|
17
|
+
prompt_tokens: number;
|
|
18
|
+
total_tokens: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { copilotHeadersForEntry, copilotBaseUrlForEntry } from "../../lib/api-config.js";
|
|
2
|
+
import { HTTPError } from "../../lib/error.js";
|
|
3
|
+
import { tokenManager } from "../../lib/token-manager.js";
|
|
4
|
+
export const createEmbeddings = async (payload, tokenEntry) => {
|
|
5
|
+
// Get token entry - use provided one or get random for load balancing
|
|
6
|
+
const entry = tokenEntry || tokenManager.getRandomTokenEntry();
|
|
7
|
+
if (!entry)
|
|
8
|
+
throw new Error("No active tokens available");
|
|
9
|
+
const response = await fetch(`${copilotBaseUrlForEntry(entry)}/embeddings`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: copilotHeadersForEntry(entry),
|
|
12
|
+
body: JSON.stringify(payload),
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
tokenManager.reportError(entry.id);
|
|
16
|
+
throw new HTTPError("Failed to create embeddings", response);
|
|
17
|
+
}
|
|
18
|
+
return (await response.json());
|
|
19
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type TokenEntry } from "../../lib/token-manager.js";
|
|
2
|
+
export declare const getModels: (tokenEntry?: TokenEntry) => Promise<ModelsResponse>;
|
|
3
|
+
/**
|
|
4
|
+
* Get models for all active token entries (for grouped display)
|
|
5
|
+
*/
|
|
6
|
+
export declare const getModelsForAllTokens: () => Promise<TokenModelsResult[]>;
|
|
7
|
+
export interface TokenModelsResult {
|
|
8
|
+
tokenId: number;
|
|
9
|
+
username: string | null;
|
|
10
|
+
accountType: string;
|
|
11
|
+
models: ModelsResponse | null;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ModelsResponse {
|
|
15
|
+
data: Array<Model>;
|
|
16
|
+
object: string;
|
|
17
|
+
}
|
|
18
|
+
interface ModelLimits {
|
|
19
|
+
max_context_window_tokens?: number;
|
|
20
|
+
max_output_tokens?: number;
|
|
21
|
+
max_prompt_tokens?: number;
|
|
22
|
+
max_inputs?: number;
|
|
23
|
+
}
|
|
24
|
+
interface ModelSupports {
|
|
25
|
+
tool_calls?: boolean;
|
|
26
|
+
parallel_tool_calls?: boolean;
|
|
27
|
+
dimensions?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface ModelCapabilities {
|
|
30
|
+
family: string;
|
|
31
|
+
limits: ModelLimits;
|
|
32
|
+
object: string;
|
|
33
|
+
supports: ModelSupports;
|
|
34
|
+
tokenizer: string;
|
|
35
|
+
type: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Model {
|
|
38
|
+
capabilities: ModelCapabilities;
|
|
39
|
+
id: string;
|
|
40
|
+
model_picker_enabled: boolean;
|
|
41
|
+
name: string;
|
|
42
|
+
object: string;
|
|
43
|
+
preview: boolean;
|
|
44
|
+
vendor: string;
|
|
45
|
+
version: string;
|
|
46
|
+
policy?: {
|
|
47
|
+
state: string;
|
|
48
|
+
terms: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { copilotBaseUrlForEntry, copilotHeadersForEntry } from "../../lib/api-config.js";
|
|
2
|
+
import { HTTPError } from "../../lib/error.js";
|
|
3
|
+
import { tokenManager } from "../../lib/token-manager.js";
|
|
4
|
+
export const getModels = async (tokenEntry) => {
|
|
5
|
+
// Get token entry - use provided one or get first active one
|
|
6
|
+
const entry = tokenEntry || tokenManager.getActiveTokenEntries()[0];
|
|
7
|
+
if (!entry)
|
|
8
|
+
throw new Error("No active tokens available");
|
|
9
|
+
const response = await fetch(`${copilotBaseUrlForEntry(entry)}/models`, {
|
|
10
|
+
headers: copilotHeadersForEntry(entry),
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
tokenManager.reportError(entry.id);
|
|
14
|
+
throw new HTTPError("Failed to get models", response);
|
|
15
|
+
}
|
|
16
|
+
return (await response.json());
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Get models for all active token entries (for grouped display)
|
|
20
|
+
*/
|
|
21
|
+
export const getModelsForAllTokens = async () => {
|
|
22
|
+
const entries = tokenManager.getActiveTokenEntries();
|
|
23
|
+
const results = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
try {
|
|
26
|
+
const models = await getModels(entry);
|
|
27
|
+
results.push({
|
|
28
|
+
tokenId: entry.id,
|
|
29
|
+
username: entry.username,
|
|
30
|
+
accountType: entry.accountType,
|
|
31
|
+
models,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
results.push({
|
|
36
|
+
tokenId: entry.id,
|
|
37
|
+
username: entry.username,
|
|
38
|
+
accountType: entry.accountType,
|
|
39
|
+
models: null,
|
|
40
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const GITHUB_BASE_URL = "https://github.com";
|
|
2
|
+
export declare const GITHUB_CLIENT_ID = "178c6fc778ccc68e1d6a";
|
|
3
|
+
export declare const GITHUB_APP_SCOPES: string;
|
|
4
|
+
export declare function getDeviceCode(): Promise<DeviceCodeResponse>;
|
|
5
|
+
export interface DeviceCodeResponse {
|
|
6
|
+
device_code: string;
|
|
7
|
+
user_code: string;
|
|
8
|
+
verification_uri: string;
|
|
9
|
+
expires_in: number;
|
|
10
|
+
interval: number;
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { standardHeaders } from "../../lib/api-config.js";
|
|
2
|
+
import { HTTPError } from "../../lib/error.js";
|
|
3
|
+
export const GITHUB_BASE_URL = "https://github.com";
|
|
4
|
+
// GitHub CLI (gh) OAuth App Client ID
|
|
5
|
+
export const GITHUB_CLIENT_ID = "178c6fc778ccc68e1d6a";
|
|
6
|
+
// Scopes matching `gh auth login` defaults
|
|
7
|
+
export const GITHUB_APP_SCOPES = ["gist", "read:org", "repo"].join(" ");
|
|
8
|
+
export async function getDeviceCode() {
|
|
9
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: standardHeaders(),
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
client_id: GITHUB_CLIENT_ID,
|
|
14
|
+
scope: GITHUB_APP_SCOPES,
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new HTTPError("Failed to get device code", response);
|
|
19
|
+
}
|
|
20
|
+
return (await response.json());
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get GitHub user info for a specific token
|
|
3
|
+
*/
|
|
4
|
+
export declare function getGitHubUserForToken(githubToken: string): Promise<GithubUserResponse>;
|
|
5
|
+
export interface GithubUserResponse {
|
|
6
|
+
login: string;
|
|
7
|
+
id: number;
|
|
8
|
+
avatar_url: string;
|
|
9
|
+
name: string | null;
|
|
10
|
+
email: string | null;
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { GITHUB_API_BASE_URL, standardHeaders } from "../../lib/api-config.js";
|
|
2
|
+
import { HTTPError } from "../../lib/error.js";
|
|
3
|
+
/**
|
|
4
|
+
* Get GitHub user info for a specific token
|
|
5
|
+
*/
|
|
6
|
+
export async function getGitHubUserForToken(githubToken) {
|
|
7
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {
|
|
8
|
+
headers: {
|
|
9
|
+
authorization: `token ${githubToken}`,
|
|
10
|
+
...standardHeaders(),
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new HTTPError("Failed to get GitHub user", response);
|
|
15
|
+
}
|
|
16
|
+
return (await response.json());
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DeviceCodeResponse } from "./get-device-code.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check access token once (for client-side polling)
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkAccessToken(deviceCode: string): Promise<{
|
|
6
|
+
access_token?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
error_description?: string;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Poll for access token after device code flow (blocking, for CLI use)
|
|
12
|
+
*/
|
|
13
|
+
export declare function pollAccessToken(deviceCode: DeviceCodeResponse): Promise<string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import { standardHeaders } from "../../lib/api-config.js";
|
|
3
|
+
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID, } from "./get-device-code.js";
|
|
4
|
+
/**
|
|
5
|
+
* Sleep helper
|
|
6
|
+
*/
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check access token once (for client-side polling)
|
|
12
|
+
*/
|
|
13
|
+
export async function checkAccessToken(deviceCode) {
|
|
14
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: standardHeaders(),
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
client_id: GITHUB_CLIENT_ID,
|
|
19
|
+
device_code: deviceCode,
|
|
20
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const text = await response.text();
|
|
25
|
+
consola.error("Failed to check access token:", text);
|
|
26
|
+
return { error: "request_failed", error_description: text };
|
|
27
|
+
}
|
|
28
|
+
const json = await response.json();
|
|
29
|
+
consola.debug("Check access token response:", json);
|
|
30
|
+
return {
|
|
31
|
+
access_token: json.access_token,
|
|
32
|
+
error: json.error,
|
|
33
|
+
error_description: json.error_description,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Poll for access token after device code flow (blocking, for CLI use)
|
|
38
|
+
*/
|
|
39
|
+
export async function pollAccessToken(deviceCode) {
|
|
40
|
+
// Interval is in seconds, we need to multiply by 1000 to get milliseconds
|
|
41
|
+
// Adding another second to be safe
|
|
42
|
+
const sleepDuration = (deviceCode.interval + 1) * 1000;
|
|
43
|
+
consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
|
|
44
|
+
while (true) {
|
|
45
|
+
const result = await checkAccessToken(deviceCode.device_code);
|
|
46
|
+
if (result.access_token) {
|
|
47
|
+
return result.access_token;
|
|
48
|
+
}
|
|
49
|
+
else if (result.error === "expired_token") {
|
|
50
|
+
throw new Error("Device code expired. Please try again.");
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await sleep(sleepDuration);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|