copilot-api-plus 1.4.9 → 1.5.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/{account-manager-D4DftPxS.js → account-manager-DktL5osZ.js} +4 -2
- package/dist/account-manager-DktL5osZ.js.map +1 -0
- package/dist/error-BaXXuCDb.js +3 -0
- package/dist/get-user-Ct5NqLcM.js +3 -0
- package/dist/main.js +449 -10
- package/dist/main.js.map +1 -1
- package/dist/{token-DYGcLmSO.js → token-B8FDrdsQ.js} +2 -2
- package/dist/{token-DYGcLmSO.js.map → token-B8FDrdsQ.js.map} +1 -1
- package/dist/token-DEcUuJp7.js +4 -0
- package/package.json +1 -1
- package/dist/account-manager-D4DftPxS.js.map +0 -1
- package/dist/error-rdTm4jb1.js +0 -3
- package/dist/get-user-p_Kr8XWd.js +0 -3
- package/dist/token-CsABqA-G.js +0 -4
package/dist/main.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, accountManager, cacheModels, cacheVSCodeVersion, copilotBaseUrl, copilotHeaders, ensurePaths, findModel, forwardError, getAccountDispatcher, getCopilotUsage, initProxyFromEnv, isAccountProxied, isNullish, isProxyActive, notifyStreamEnd, notifyStreamStart, resetAccountConnections, resetConnections, rootCause, sleep, standardHeaders, state } from "./account-manager-
|
|
3
|
-
import { clearGithubToken, getDeviceCode, pollAccessToken, refreshCopilotToken, setupCopilotToken, setupGitHubToken, stopCopilotTokenRefresh } from "./token-
|
|
2
|
+
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, accountManager, cacheModels, cacheVSCodeVersion, copilotBaseUrl, copilotHeaders, ensurePaths, findModel, forwardError, getAccountDispatcher, getCopilotUsage, initProxyFromEnv, isAccountProxied, isNullish, isProxyActive, notifyStreamEnd, notifyStreamStart, resetAccountConnections, resetConnections, rootCause, sleep, standardHeaders, state } from "./account-manager-DktL5osZ.js";
|
|
3
|
+
import { clearGithubToken, getDeviceCode, pollAccessToken, refreshCopilotToken, setupCopilotToken, setupGitHubToken, stopCopilotTokenRefresh } from "./token-B8FDrdsQ.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
import consola from "consola";
|
|
7
|
-
import { timingSafeEqual } from "node:crypto";
|
|
7
|
+
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
@@ -1899,6 +1899,7 @@ function injectIntoAnthropicPayload(payload) {
|
|
|
1899
1899
|
function injectIntoOpenAIPayload(payload) {
|
|
1900
1900
|
if (!ENABLED) return payload;
|
|
1901
1901
|
const msgs = payload.messages;
|
|
1902
|
+
if (!Array.isArray(msgs)) return payload;
|
|
1902
1903
|
for (const m of msgs) {
|
|
1903
1904
|
if (m.role !== "system" && m.role !== "developer") continue;
|
|
1904
1905
|
if (typeof m.content === "string" && alreadyInjected(m.content)) return payload;
|
|
@@ -2041,6 +2042,7 @@ function stripSystemReminders(payload) {
|
|
|
2041
2042
|
* nothing changed.
|
|
2042
2043
|
*/
|
|
2043
2044
|
function stripOpenAIReminders(payload) {
|
|
2045
|
+
if (!Array.isArray(payload.messages)) return payload;
|
|
2044
2046
|
let changed = false;
|
|
2045
2047
|
const newMessages = payload.messages.map((m) => {
|
|
2046
2048
|
if (m.content === null) return m;
|
|
@@ -2304,6 +2306,396 @@ function overrideMessageStartEventModel(rawData, requestedModel) {
|
|
|
2304
2306
|
}
|
|
2305
2307
|
}
|
|
2306
2308
|
|
|
2309
|
+
//#endregion
|
|
2310
|
+
//#region src/services/copilot/responses-translator.ts
|
|
2311
|
+
function partsToInputContent(parts, role) {
|
|
2312
|
+
return parts.map((part) => {
|
|
2313
|
+
if (part.type === "text") return role === "assistant" ? {
|
|
2314
|
+
type: "output_text",
|
|
2315
|
+
text: part.text
|
|
2316
|
+
} : {
|
|
2317
|
+
type: "input_text",
|
|
2318
|
+
text: part.text
|
|
2319
|
+
};
|
|
2320
|
+
return {
|
|
2321
|
+
type: "input_image",
|
|
2322
|
+
image_url: part.image_url.url,
|
|
2323
|
+
detail: part.image_url.detail
|
|
2324
|
+
};
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
function stringToInputContent(text, role) {
|
|
2328
|
+
return role === "assistant" ? [{
|
|
2329
|
+
type: "output_text",
|
|
2330
|
+
text
|
|
2331
|
+
}] : [{
|
|
2332
|
+
type: "input_text",
|
|
2333
|
+
text
|
|
2334
|
+
}];
|
|
2335
|
+
}
|
|
2336
|
+
function messageContent(message) {
|
|
2337
|
+
if (message.content === null) return [];
|
|
2338
|
+
if (typeof message.content === "string") return stringToInputContent(message.content, message.role);
|
|
2339
|
+
return partsToInputContent(message.content, message.role);
|
|
2340
|
+
}
|
|
2341
|
+
function translateAssistantWithToolCalls(message) {
|
|
2342
|
+
const items = [];
|
|
2343
|
+
const content = messageContent(message);
|
|
2344
|
+
if (content.length > 0) items.push({
|
|
2345
|
+
type: "message",
|
|
2346
|
+
role: "assistant",
|
|
2347
|
+
content
|
|
2348
|
+
});
|
|
2349
|
+
for (const call of message.tool_calls ?? []) items.push({
|
|
2350
|
+
type: "function_call",
|
|
2351
|
+
call_id: call.id,
|
|
2352
|
+
name: call.function.name,
|
|
2353
|
+
arguments: call.function.arguments
|
|
2354
|
+
});
|
|
2355
|
+
return items;
|
|
2356
|
+
}
|
|
2357
|
+
function translateMessage(message) {
|
|
2358
|
+
if (message.role === "tool") {
|
|
2359
|
+
const text = typeof message.content === "string" ? message.content : messageContent(message).map((c) => c.type === "input_text" || c.type === "output_text" ? c.text : "").join("");
|
|
2360
|
+
return [{
|
|
2361
|
+
type: "function_call_output",
|
|
2362
|
+
call_id: message.tool_call_id ?? "",
|
|
2363
|
+
output: text
|
|
2364
|
+
}];
|
|
2365
|
+
}
|
|
2366
|
+
if (message.role === "assistant" && message.tool_calls?.length) return translateAssistantWithToolCalls(message);
|
|
2367
|
+
return [{
|
|
2368
|
+
type: "message",
|
|
2369
|
+
role: message.role,
|
|
2370
|
+
content: messageContent(message)
|
|
2371
|
+
}];
|
|
2372
|
+
}
|
|
2373
|
+
function translateTool(tool) {
|
|
2374
|
+
return {
|
|
2375
|
+
type: "function",
|
|
2376
|
+
name: tool.function.name,
|
|
2377
|
+
description: tool.function.description,
|
|
2378
|
+
parameters: tool.function.parameters
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
function translateToolChoice(choice) {
|
|
2382
|
+
if (!choice) return void 0;
|
|
2383
|
+
if (typeof choice === "string") return choice;
|
|
2384
|
+
return {
|
|
2385
|
+
type: "function",
|
|
2386
|
+
name: choice.function.name
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
function translateReasoning(effort) {
|
|
2390
|
+
if (!effort) return void 0;
|
|
2391
|
+
if (effort === "max") return { effort: "high" };
|
|
2392
|
+
return { effort };
|
|
2393
|
+
}
|
|
2394
|
+
function chatToResponsesPayload(payload) {
|
|
2395
|
+
let instructions;
|
|
2396
|
+
const remainingMessages = [];
|
|
2397
|
+
let sawNonSystem = false;
|
|
2398
|
+
for (const msg of payload.messages) {
|
|
2399
|
+
if (msg.role === "system" && !sawNonSystem) {
|
|
2400
|
+
const text = typeof msg.content === "string" ? msg.content : messageContent(msg).map((c) => c.type === "input_text" || c.type === "output_text" ? c.text : "").join("\n");
|
|
2401
|
+
instructions = instructions ? `${instructions}\n\n${text}` : text;
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
sawNonSystem = true;
|
|
2405
|
+
remainingMessages.push(msg);
|
|
2406
|
+
}
|
|
2407
|
+
const input = remainingMessages.flatMap((m) => translateMessage(m));
|
|
2408
|
+
const maxOutput = payload.max_completion_tokens ?? payload.max_tokens ?? void 0;
|
|
2409
|
+
return {
|
|
2410
|
+
model: payload.model,
|
|
2411
|
+
input,
|
|
2412
|
+
instructions,
|
|
2413
|
+
tools: payload.tools?.map((t) => translateTool(t)),
|
|
2414
|
+
tool_choice: translateToolChoice(payload.tool_choice),
|
|
2415
|
+
reasoning: translateReasoning(payload.reasoning_effort),
|
|
2416
|
+
max_output_tokens: maxOutput ?? void 0,
|
|
2417
|
+
temperature: payload.temperature ?? void 0,
|
|
2418
|
+
top_p: payload.top_p ?? void 0,
|
|
2419
|
+
parallel_tool_calls: void 0,
|
|
2420
|
+
stream: payload.stream ?? void 0
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
function mapUsage(usage) {
|
|
2424
|
+
if (!usage) return void 0;
|
|
2425
|
+
return {
|
|
2426
|
+
prompt_tokens: usage.input_tokens ?? 0,
|
|
2427
|
+
completion_tokens: usage.output_tokens ?? 0,
|
|
2428
|
+
total_tokens: usage.total_tokens ?? (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
|
|
2429
|
+
...usage.input_tokens_details?.cached_tokens !== void 0 && { prompt_tokens_details: { cached_tokens: usage.input_tokens_details.cached_tokens } }
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
function extractAssistantText(output) {
|
|
2433
|
+
let text = "";
|
|
2434
|
+
for (const item of output) {
|
|
2435
|
+
if (item.type !== "message") continue;
|
|
2436
|
+
for (const part of item.content) text += part.text;
|
|
2437
|
+
}
|
|
2438
|
+
return text;
|
|
2439
|
+
}
|
|
2440
|
+
function extractToolCalls(output) {
|
|
2441
|
+
const calls = [];
|
|
2442
|
+
for (const item of output) if (item.type === "function_call") calls.push({
|
|
2443
|
+
id: item.call_id,
|
|
2444
|
+
type: "function",
|
|
2445
|
+
function: {
|
|
2446
|
+
name: item.name,
|
|
2447
|
+
arguments: item.arguments
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
return calls;
|
|
2451
|
+
}
|
|
2452
|
+
function responsesToChatResponse(resp, requestedModel) {
|
|
2453
|
+
const text = extractAssistantText(resp.output);
|
|
2454
|
+
const toolCalls = extractToolCalls(resp.output);
|
|
2455
|
+
const finishReason = toolCalls.length > 0 ? "tool_calls" : "stop";
|
|
2456
|
+
return {
|
|
2457
|
+
id: resp.id,
|
|
2458
|
+
object: "chat.completion",
|
|
2459
|
+
created: resp.created_at ?? Math.floor(Date.now() / 1e3),
|
|
2460
|
+
model: requestedModel,
|
|
2461
|
+
choices: [{
|
|
2462
|
+
index: 0,
|
|
2463
|
+
message: {
|
|
2464
|
+
role: "assistant",
|
|
2465
|
+
content: text || null,
|
|
2466
|
+
...toolCalls.length > 0 && { tool_calls: toolCalls }
|
|
2467
|
+
},
|
|
2468
|
+
logprobs: null,
|
|
2469
|
+
finish_reason: finishReason
|
|
2470
|
+
}],
|
|
2471
|
+
usage: mapUsage(resp.usage)
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function makeChunk(s, choice) {
|
|
2475
|
+
return { data: JSON.stringify({
|
|
2476
|
+
id: s.responseId,
|
|
2477
|
+
object: "chat.completion.chunk",
|
|
2478
|
+
created: s.created,
|
|
2479
|
+
model: s.requestedModel,
|
|
2480
|
+
choices: [choice]
|
|
2481
|
+
}) };
|
|
2482
|
+
}
|
|
2483
|
+
function ensureRoleChunk(s) {
|
|
2484
|
+
if (s.roleEmitted) return null;
|
|
2485
|
+
s.roleEmitted = true;
|
|
2486
|
+
return makeChunk(s, {
|
|
2487
|
+
index: 0,
|
|
2488
|
+
delta: {
|
|
2489
|
+
role: "assistant",
|
|
2490
|
+
content: ""
|
|
2491
|
+
},
|
|
2492
|
+
finish_reason: null,
|
|
2493
|
+
logprobs: null
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
function getToolIndex(s, key) {
|
|
2497
|
+
let idx = s.toolIndexById.get(key);
|
|
2498
|
+
if (idx === void 0) {
|
|
2499
|
+
idx = s.nextToolIndex++;
|
|
2500
|
+
s.toolIndexById.set(key, idx);
|
|
2501
|
+
}
|
|
2502
|
+
return idx;
|
|
2503
|
+
}
|
|
2504
|
+
function* handleTextDelta(s, delta) {
|
|
2505
|
+
const roleChunk = ensureRoleChunk(s);
|
|
2506
|
+
if (roleChunk) yield roleChunk;
|
|
2507
|
+
yield makeChunk(s, {
|
|
2508
|
+
index: 0,
|
|
2509
|
+
delta: { content: delta },
|
|
2510
|
+
finish_reason: null,
|
|
2511
|
+
logprobs: null
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
function* handleFunctionCallAdded(s, item) {
|
|
2515
|
+
s.hasToolCalls = true;
|
|
2516
|
+
const key = item.call_id || item.id || "";
|
|
2517
|
+
const idx = getToolIndex(s, key);
|
|
2518
|
+
const roleChunk = ensureRoleChunk(s);
|
|
2519
|
+
if (roleChunk) yield roleChunk;
|
|
2520
|
+
yield makeChunk(s, {
|
|
2521
|
+
index: 0,
|
|
2522
|
+
delta: { tool_calls: [{
|
|
2523
|
+
index: idx,
|
|
2524
|
+
id: item.call_id,
|
|
2525
|
+
type: "function",
|
|
2526
|
+
function: {
|
|
2527
|
+
name: item.name,
|
|
2528
|
+
arguments: ""
|
|
2529
|
+
}
|
|
2530
|
+
}] },
|
|
2531
|
+
finish_reason: null,
|
|
2532
|
+
logprobs: null
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
function* handleArgumentsDelta(s, itemId, delta) {
|
|
2536
|
+
const idx = getToolIndex(s, itemId);
|
|
2537
|
+
yield makeChunk(s, {
|
|
2538
|
+
index: 0,
|
|
2539
|
+
delta: { tool_calls: [{
|
|
2540
|
+
index: idx,
|
|
2541
|
+
function: { arguments: delta }
|
|
2542
|
+
}] },
|
|
2543
|
+
finish_reason: null,
|
|
2544
|
+
logprobs: null
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
function buildUsageChunk(s, usage) {
|
|
2548
|
+
if (!usage) return null;
|
|
2549
|
+
const chunk = {
|
|
2550
|
+
id: s.responseId,
|
|
2551
|
+
object: "chat.completion.chunk",
|
|
2552
|
+
created: s.created,
|
|
2553
|
+
model: s.requestedModel,
|
|
2554
|
+
choices: [],
|
|
2555
|
+
usage: {
|
|
2556
|
+
prompt_tokens: usage.prompt_tokens,
|
|
2557
|
+
completion_tokens: usage.completion_tokens,
|
|
2558
|
+
total_tokens: usage.total_tokens,
|
|
2559
|
+
...usage.prompt_tokens_details && { prompt_tokens_details: usage.prompt_tokens_details }
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
return { data: JSON.stringify(chunk) };
|
|
2563
|
+
}
|
|
2564
|
+
function* handleCompleted(s, response) {
|
|
2565
|
+
const finishReason = s.hasToolCalls ? "tool_calls" : "stop";
|
|
2566
|
+
yield makeChunk(s, {
|
|
2567
|
+
index: 0,
|
|
2568
|
+
delta: {},
|
|
2569
|
+
finish_reason: finishReason,
|
|
2570
|
+
logprobs: null
|
|
2571
|
+
});
|
|
2572
|
+
const usageChunk = buildUsageChunk(s, mapUsage(response.usage));
|
|
2573
|
+
if (usageChunk) yield usageChunk;
|
|
2574
|
+
yield { data: "[DONE]" };
|
|
2575
|
+
}
|
|
2576
|
+
function parseEvent(data) {
|
|
2577
|
+
try {
|
|
2578
|
+
return JSON.parse(data);
|
|
2579
|
+
} catch {
|
|
2580
|
+
return null;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
function handleTerminalEvent(event) {
|
|
2584
|
+
const message = event.message ?? event.response?.error?.message ?? "Responses API error";
|
|
2585
|
+
throw new Error(message);
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Dispatch a single Responses-API event to the right handler.
|
|
2589
|
+
* Returns generator of chunks and a boolean (true = stream complete).
|
|
2590
|
+
*/
|
|
2591
|
+
function* dispatchEvent(s, event) {
|
|
2592
|
+
switch (event.type) {
|
|
2593
|
+
case "response.output_text.delta":
|
|
2594
|
+
if (event.delta) yield* handleTextDelta(s, event.delta);
|
|
2595
|
+
return false;
|
|
2596
|
+
case "response.output_item.added":
|
|
2597
|
+
if (event.item?.type === "function_call") yield* handleFunctionCallAdded(s, event.item);
|
|
2598
|
+
return false;
|
|
2599
|
+
case "response.function_call_arguments.delta":
|
|
2600
|
+
if (event.delta !== void 0) yield* handleArgumentsDelta(s, event.item_id ?? "", event.delta);
|
|
2601
|
+
return false;
|
|
2602
|
+
case "response.completed":
|
|
2603
|
+
if (event.response) {
|
|
2604
|
+
yield* handleCompleted(s, event.response);
|
|
2605
|
+
return true;
|
|
2606
|
+
}
|
|
2607
|
+
return false;
|
|
2608
|
+
case "response.failed":
|
|
2609
|
+
case "response.error": handleTerminalEvent(event);
|
|
2610
|
+
default: return false;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Translate a Responses-API SSE stream into Chat Completions SSE messages.
|
|
2615
|
+
*
|
|
2616
|
+
* Yields `{ data: <stringified chat-completion-chunk> }` objects so the
|
|
2617
|
+
* route handler can feed them straight into `stream.writeSSE()` — same
|
|
2618
|
+
* shape as the existing `events()` output for `/chat/completions`.
|
|
2619
|
+
*/
|
|
2620
|
+
async function* responsesStreamToChatChunks(source, requestedModel) {
|
|
2621
|
+
const s = {
|
|
2622
|
+
responseId: `chatcmpl-${randomUUID().replaceAll("-", "")}`,
|
|
2623
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2624
|
+
requestedModel,
|
|
2625
|
+
roleEmitted: false,
|
|
2626
|
+
hasToolCalls: false,
|
|
2627
|
+
toolIndexById: /* @__PURE__ */ new Map(),
|
|
2628
|
+
nextToolIndex: 0
|
|
2629
|
+
};
|
|
2630
|
+
for await (const sse of source) {
|
|
2631
|
+
if (!sse.data || sse.data === "[DONE]") continue;
|
|
2632
|
+
const event = parseEvent(sse.data);
|
|
2633
|
+
if (!event) continue;
|
|
2634
|
+
if (yield* dispatchEvent(s, event)) return;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
//#endregion
|
|
2639
|
+
//#region src/services/copilot/create-responses.ts
|
|
2640
|
+
/**
|
|
2641
|
+
* Call Copilot's `/v1/responses` with a Chat Completions payload and
|
|
2642
|
+
* return either a Chat-style response or an SSE generator that yields
|
|
2643
|
+
* already-translated Chat Completion chunks (one per `data:` line).
|
|
2644
|
+
*
|
|
2645
|
+
* Currently only supports single-account mode. Multi-account routing
|
|
2646
|
+
* for Responses-only models can be added in a follow-up if needed.
|
|
2647
|
+
*/
|
|
2648
|
+
async function createResponsesAsChat(payload) {
|
|
2649
|
+
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
2650
|
+
const responsesPayload = chatToResponsesPayload(payload);
|
|
2651
|
+
const url = `${copilotBaseUrl(state)}/v1/responses`;
|
|
2652
|
+
const enableVision = responsesPayload.input.some((item) => item.type === "message" && item.content.some((c) => c.type === "input_image"));
|
|
2653
|
+
const isAgentCall = payload.messages.some((m) => ["assistant", "tool"].includes(m.role));
|
|
2654
|
+
const buildHeaders = () => ({
|
|
2655
|
+
...copilotHeaders(state, enableVision),
|
|
2656
|
+
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
2657
|
+
});
|
|
2658
|
+
const bodyString = JSON.stringify(responsesPayload);
|
|
2659
|
+
consola.debug("Sending request to Copilot (/v1/responses):", {
|
|
2660
|
+
model: responsesPayload.model,
|
|
2661
|
+
endpoint: url,
|
|
2662
|
+
stream: responsesPayload.stream
|
|
2663
|
+
});
|
|
2664
|
+
let response = await fetchWithRetry(url, () => ({
|
|
2665
|
+
method: "POST",
|
|
2666
|
+
headers: buildHeaders(),
|
|
2667
|
+
body: bodyString
|
|
2668
|
+
}));
|
|
2669
|
+
if (response.status === 401) {
|
|
2670
|
+
consola.warn("Copilot token expired, refreshing and retrying...");
|
|
2671
|
+
try {
|
|
2672
|
+
await refreshCopilotToken();
|
|
2673
|
+
response = await fetchWithTimeout$1(url, {
|
|
2674
|
+
method: "POST",
|
|
2675
|
+
headers: buildHeaders(),
|
|
2676
|
+
body: bodyString
|
|
2677
|
+
});
|
|
2678
|
+
} catch {}
|
|
2679
|
+
}
|
|
2680
|
+
if (!response.ok) {
|
|
2681
|
+
const errorBody = await response.text();
|
|
2682
|
+
consola.error("Failed /v1/responses request", {
|
|
2683
|
+
status: response.status,
|
|
2684
|
+
statusText: response.statusText,
|
|
2685
|
+
body: errorBody
|
|
2686
|
+
});
|
|
2687
|
+
throw new HTTPError(`Failed to call /v1/responses: ${response.status} ${errorBody}`, response);
|
|
2688
|
+
}
|
|
2689
|
+
if (payload.stream) {
|
|
2690
|
+
const sse = events(response);
|
|
2691
|
+
const translated = responsesStreamToChatChunks(sse, payload.model);
|
|
2692
|
+
translated.__accountInfo = { apiBaseUrl: copilotBaseUrl(state) };
|
|
2693
|
+
return translated;
|
|
2694
|
+
}
|
|
2695
|
+
const responsesResult = await response.json();
|
|
2696
|
+
return responsesToChatResponse(responsesResult, payload.model);
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2307
2699
|
//#endregion
|
|
2308
2700
|
//#region src/services/copilot/create-chat-completions.ts
|
|
2309
2701
|
/**
|
|
@@ -2520,6 +2912,15 @@ function logThinkingInjection(original, injected, resolvedModel) {
|
|
|
2520
2912
|
else if (injected.reasoning_effort && injected.reasoning_effort !== original.reasoning_effort) consola.debug(`Thinking: injected reasoning_effort=${injected.reasoning_effort} for "${resolvedModel}"`);
|
|
2521
2913
|
else if (reasoningUnsupportedModels.has(resolvedModel)) consola.debug(`Thinking: skipped — "${resolvedModel}" does not support reasoning`);
|
|
2522
2914
|
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Models known to require `/v1/responses` (and reject `/chat/completions`
|
|
2917
|
+
* with `unsupported_api_for_model`). Learned at runtime — once a model
|
|
2918
|
+
* hits the 400, all future requests for it skip the chat-completions
|
|
2919
|
+
* attempt and go straight to the Responses API.
|
|
2920
|
+
*
|
|
2921
|
+
* Cleared on process restart so Copilot routing changes self-heal.
|
|
2922
|
+
*/
|
|
2923
|
+
const responsesApiOnlyModels = /* @__PURE__ */ new Set();
|
|
2523
2924
|
const createChatCompletions = async (payload) => {
|
|
2524
2925
|
const resolvedModel = modelRouter.resolveModel(payload.model);
|
|
2525
2926
|
const routedPayload = resolvedModel !== payload.model ? {
|
|
@@ -2527,6 +2928,10 @@ const createChatCompletions = async (payload) => {
|
|
|
2527
2928
|
model: resolvedModel
|
|
2528
2929
|
} : payload;
|
|
2529
2930
|
if (resolvedModel !== payload.model) consola.debug(`Model routed: ${payload.model} → ${resolvedModel}`);
|
|
2931
|
+
if (responsesApiOnlyModels.has(resolvedModel)) {
|
|
2932
|
+
consola.debug(`Model "${resolvedModel}" cached as Responses-only — using /v1/responses`);
|
|
2933
|
+
return createResponsesAsChat(routedPayload);
|
|
2934
|
+
}
|
|
2530
2935
|
const thinkingPayload = injectThinking(routedPayload, resolvedModel);
|
|
2531
2936
|
const wasInjected = thinkingPayload.reasoning_effort !== routedPayload.reasoning_effort || thinkingPayload.thinking_budget !== routedPayload.thinking_budget;
|
|
2532
2937
|
logThinkingInjection(routedPayload, thinkingPayload, resolvedModel);
|
|
@@ -2542,6 +2947,11 @@ const createChatCompletions = async (payload) => {
|
|
|
2542
2947
|
releaseSlot();
|
|
2543
2948
|
return result;
|
|
2544
2949
|
} catch (error) {
|
|
2950
|
+
const responsesRetry = handle400UnsupportedApiError(error, {
|
|
2951
|
+
resolvedModel,
|
|
2952
|
+
routedPayload
|
|
2953
|
+
}, releaseSlot);
|
|
2954
|
+
if (responsesRetry !== void 0) return responsesRetry;
|
|
2545
2955
|
const maxTokensRetry = handle400MaxTokensError(error, {
|
|
2546
2956
|
resolvedModel,
|
|
2547
2957
|
routedPayload: thinkingPayload
|
|
@@ -2559,6 +2969,35 @@ const createChatCompletions = async (payload) => {
|
|
|
2559
2969
|
}
|
|
2560
2970
|
};
|
|
2561
2971
|
/**
|
|
2972
|
+
* Handle Copilot's `unsupported_api_for_model` 400 — the model only
|
|
2973
|
+
* accepts /v1/responses, not /chat/completions (e.g. gpt-5.5). Mark the
|
|
2974
|
+
* model so future requests skip the failing attempt, then retry via the
|
|
2975
|
+
* Responses API translator.
|
|
2976
|
+
*/
|
|
2977
|
+
function handle400UnsupportedApiError(error, ctx, releaseSlot) {
|
|
2978
|
+
if (!(error instanceof HTTPError) || error.response.status !== 400) return void 0;
|
|
2979
|
+
const errMsg = error.message;
|
|
2980
|
+
if (!errMsg.includes("unsupported_api_for_model") && !errMsg.includes("not accessible via the /chat/completions endpoint")) return void 0;
|
|
2981
|
+
responsesApiOnlyModels.add(ctx.resolvedModel);
|
|
2982
|
+
consola.debug(`Model "${ctx.resolvedModel}" requires /v1/responses — switching for future requests`);
|
|
2983
|
+
return (async () => {
|
|
2984
|
+
try {
|
|
2985
|
+
const result = await createResponsesAsChat(ctx.routedPayload);
|
|
2986
|
+
if (Symbol.asyncIterator in result) {
|
|
2987
|
+
const accountInfo = result.__accountInfo;
|
|
2988
|
+
const wrapped$1 = wrapGeneratorWithRelease(result, releaseSlot, accountInfo);
|
|
2989
|
+
wrapped$1.__accountInfo = accountInfo;
|
|
2990
|
+
return wrapped$1;
|
|
2991
|
+
}
|
|
2992
|
+
releaseSlot();
|
|
2993
|
+
return result;
|
|
2994
|
+
} catch (retryError) {
|
|
2995
|
+
releaseSlot();
|
|
2996
|
+
throw retryError;
|
|
2997
|
+
}
|
|
2998
|
+
})();
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
2562
3001
|
* Handle 400 errors caused by `max_tokens` being rejected — o-series and
|
|
2563
3002
|
* GPT-5.x require `max_completion_tokens` instead. Learns at runtime:
|
|
2564
3003
|
* adds the model to `maxCompletionTokensModels` and retries once with the
|
|
@@ -3502,12 +3941,12 @@ function getAnthropicThinkingBlocks(reasoningContent) {
|
|
|
3502
3941
|
thinking: reasoningContent
|
|
3503
3942
|
}];
|
|
3504
3943
|
}
|
|
3505
|
-
function getAnthropicTextBlocks(messageContent) {
|
|
3506
|
-
if (typeof messageContent === "string") return [{
|
|
3944
|
+
function getAnthropicTextBlocks(messageContent$1) {
|
|
3945
|
+
if (typeof messageContent$1 === "string") return [{
|
|
3507
3946
|
type: "text",
|
|
3508
|
-
text: messageContent
|
|
3947
|
+
text: messageContent$1
|
|
3509
3948
|
}];
|
|
3510
|
-
if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({
|
|
3949
|
+
if (Array.isArray(messageContent$1)) return messageContent$1.filter((part) => part.type === "text").map((part) => ({
|
|
3511
3950
|
type: "text",
|
|
3512
3951
|
text: part.text
|
|
3513
3952
|
}));
|
|
@@ -4556,7 +4995,7 @@ async function validateGitHubToken(token) {
|
|
|
4556
4995
|
state.githubToken = token;
|
|
4557
4996
|
consola.info("Using provided GitHub token");
|
|
4558
4997
|
try {
|
|
4559
|
-
const { getGitHubUser } = await import("./get-user-
|
|
4998
|
+
const { getGitHubUser } = await import("./get-user-Ct5NqLcM.js");
|
|
4560
4999
|
const user = await getGitHubUser();
|
|
4561
5000
|
consola.info(`Logged in as ${user.login}`);
|
|
4562
5001
|
} catch (error) {
|
|
@@ -4618,10 +5057,10 @@ async function runServer(options$1) {
|
|
|
4618
5057
|
try {
|
|
4619
5058
|
await setupCopilotToken();
|
|
4620
5059
|
} catch (error) {
|
|
4621
|
-
const { HTTPError: HTTPError$1 } = await import("./error-
|
|
5060
|
+
const { HTTPError: HTTPError$1 } = await import("./error-BaXXuCDb.js");
|
|
4622
5061
|
if (error instanceof HTTPError$1 && error.response.status === 401) {
|
|
4623
5062
|
consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
|
|
4624
|
-
const { clearGithubToken: clearGithubToken$1 } = await import("./token-
|
|
5063
|
+
const { clearGithubToken: clearGithubToken$1 } = await import("./token-DEcUuJp7.js");
|
|
4625
5064
|
await clearGithubToken$1();
|
|
4626
5065
|
consola.info("Please restart to re-authenticate");
|
|
4627
5066
|
}
|