@hsupu/copilot-api 0.7.18-beta.3 → 0.7.19
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/config.example.yaml +2 -1
- package/dist/main.mjs +248 -91
- package/dist/main.mjs.map +1 -1
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -63,24 +63,25 @@ const DEFAULT_MODEL_OVERRIDES = {
|
|
|
63
63
|
};
|
|
64
64
|
const state = {
|
|
65
65
|
accountType: "individual",
|
|
66
|
-
modelIndex: /* @__PURE__ */ new Map(),
|
|
67
|
-
modelIds: /* @__PURE__ */ new Set(),
|
|
68
|
-
showGitHubToken: false,
|
|
69
|
-
verbose: false,
|
|
70
66
|
autoTruncate: true,
|
|
71
67
|
compressToolResultsBeforeTruncate: true,
|
|
72
68
|
convertServerToolsToCustom: true,
|
|
73
|
-
modelOverrides: { ...DEFAULT_MODEL_OVERRIDES },
|
|
74
69
|
dedupToolCalls: false,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
systemPromptOverrides: [],
|
|
70
|
+
fetchTimeout: 300,
|
|
71
|
+
filterToolSearchBlocks: false,
|
|
78
72
|
historyLimit: 200,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
modelIds: /* @__PURE__ */ new Set(),
|
|
74
|
+
modelIndex: /* @__PURE__ */ new Map(),
|
|
75
|
+
modelOverrides: { ...DEFAULT_MODEL_OVERRIDES },
|
|
76
|
+
rewriteSystemReminders: false,
|
|
77
|
+
showGitHubToken: false,
|
|
82
78
|
shutdownAbortWait: 120,
|
|
83
|
-
|
|
79
|
+
shutdownGracefulWait: 60,
|
|
80
|
+
staleRequestMaxAge: 600,
|
|
81
|
+
streamIdleTimeout: 300,
|
|
82
|
+
systemPromptOverrides: [],
|
|
83
|
+
truncateReadToolResult: false,
|
|
84
|
+
verbose: false
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
//#endregion
|
|
@@ -105,7 +106,7 @@ const GITHUB_API_VERSION = "2022-11-28";
|
|
|
105
106
|
*/
|
|
106
107
|
const INTERACTION_ID = randomUUID();
|
|
107
108
|
const copilotBaseUrl = (state) => state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
|
|
108
|
-
const copilotHeaders = (state,
|
|
109
|
+
const copilotHeaders = (state, opts) => {
|
|
109
110
|
const headers = {
|
|
110
111
|
Authorization: `Bearer ${state.copilotToken}`,
|
|
111
112
|
"content-type": standardHeaders()["content-type"],
|
|
@@ -113,13 +114,17 @@ const copilotHeaders = (state, vision = false) => {
|
|
|
113
114
|
"editor-version": `vscode/${state.vsCodeVersion}`,
|
|
114
115
|
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
115
116
|
"user-agent": USER_AGENT,
|
|
116
|
-
"openai-intent": "conversation-panel",
|
|
117
|
+
"openai-intent": opts?.intent ?? "conversation-panel",
|
|
117
118
|
"x-github-api-version": COPILOT_API_VERSION,
|
|
118
119
|
"x-request-id": randomUUID(),
|
|
119
120
|
"X-Interaction-Id": INTERACTION_ID,
|
|
120
121
|
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
121
122
|
};
|
|
122
|
-
if (vision) headers["copilot-vision-request"] = "true";
|
|
123
|
+
if (opts?.vision) headers["copilot-vision-request"] = "true";
|
|
124
|
+
if (opts?.modelRequestHeaders) {
|
|
125
|
+
const coreKeysLower = new Set(Object.keys(headers).map((k) => k.toLowerCase()));
|
|
126
|
+
for (const [key, value] of Object.entries(opts.modelRequestHeaders)) if (!coreKeysLower.has(key.toLowerCase())) headers[key] = value;
|
|
127
|
+
}
|
|
123
128
|
return headers;
|
|
124
129
|
};
|
|
125
130
|
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
@@ -744,7 +749,10 @@ function forwardError(c, error) {
|
|
|
744
749
|
consola.warn(`HTTP 429: Rate limit exceeded`);
|
|
745
750
|
return c.json(formattedError, 429);
|
|
746
751
|
}
|
|
747
|
-
|
|
752
|
+
if (typeof errorJson === "string") {
|
|
753
|
+
const preview = errorJson.trimStart().startsWith("<") ? `[HTML ${errorJson.length} bytes]` : truncateForLog(errorJson, 200);
|
|
754
|
+
consola.error(`HTTP ${error.status}: ${preview}`);
|
|
755
|
+
} else consola.error(`HTTP ${error.status}:`, errorJson);
|
|
748
756
|
return c.json({ error: {
|
|
749
757
|
message: error.responseText,
|
|
750
758
|
type: "error"
|
|
@@ -984,6 +992,11 @@ function formatErrorWithCause(error) {
|
|
|
984
992
|
if (error.cause instanceof Error && error.cause.message && error.cause.message !== error.message) msg += ` (cause: ${stripBunVerboseHint(error.cause.message)})`;
|
|
985
993
|
return msg;
|
|
986
994
|
}
|
|
995
|
+
/** Truncate a string for log display, adding ellipsis if truncated */
|
|
996
|
+
function truncateForLog(text, maxLen) {
|
|
997
|
+
if (text.length <= maxLen) return text;
|
|
998
|
+
return `${text.slice(0, maxLen)}… (${text.length} bytes total)`;
|
|
999
|
+
}
|
|
987
1000
|
/** Extract error message with fallback. For HTTPError, extracts the actual API error response. */
|
|
988
1001
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
989
1002
|
if (error instanceof Error) {
|
|
@@ -1203,7 +1216,10 @@ function generateId(randomLength = 7) {
|
|
|
1203
1216
|
//#region src/lib/token/github-client.ts
|
|
1204
1217
|
/** GitHub OAuth API client — device code flow and user info */
|
|
1205
1218
|
const getGitHubUser = async () => {
|
|
1206
|
-
const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {
|
|
1219
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {
|
|
1220
|
+
headers: githubHeaders(state),
|
|
1221
|
+
signal: AbortSignal.timeout(15e3)
|
|
1222
|
+
});
|
|
1207
1223
|
if (!response.ok) throw await HTTPError.fromResponse("Failed to get GitHub user", response);
|
|
1208
1224
|
return await response.json();
|
|
1209
1225
|
};
|
|
@@ -1214,7 +1230,8 @@ const getDeviceCode = async () => {
|
|
|
1214
1230
|
body: JSON.stringify({
|
|
1215
1231
|
client_id: GITHUB_CLIENT_ID,
|
|
1216
1232
|
scope: "read:user"
|
|
1217
|
-
})
|
|
1233
|
+
}),
|
|
1234
|
+
signal: AbortSignal.timeout(15e3)
|
|
1218
1235
|
});
|
|
1219
1236
|
if (!response.ok) throw await HTTPError.fromResponse("Failed to get device code", response);
|
|
1220
1237
|
return await response.json();
|
|
@@ -1231,7 +1248,8 @@ async function pollAccessToken(deviceCode) {
|
|
|
1231
1248
|
client_id: GITHUB_CLIENT_ID,
|
|
1232
1249
|
device_code: deviceCode.device_code,
|
|
1233
1250
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1234
|
-
})
|
|
1251
|
+
}),
|
|
1252
|
+
signal: AbortSignal.timeout(15e3)
|
|
1235
1253
|
});
|
|
1236
1254
|
if (!response.ok) {
|
|
1237
1255
|
await sleep(sleepDuration);
|
|
@@ -1405,7 +1423,9 @@ var DeviceAuthProvider = class extends GitHubTokenProvider {
|
|
|
1405
1423
|
refreshable: true
|
|
1406
1424
|
};
|
|
1407
1425
|
} catch (error) {
|
|
1426
|
+
const cause = error instanceof TypeError && error.cause ? error.cause : void 0;
|
|
1408
1427
|
consola.error("Device authorization failed:", error);
|
|
1428
|
+
if (cause) consola.error("Caused by:", cause);
|
|
1409
1429
|
return null;
|
|
1410
1430
|
}
|
|
1411
1431
|
}
|
|
@@ -1735,6 +1755,17 @@ const checkUsage = defineCommand({
|
|
|
1735
1755
|
}
|
|
1736
1756
|
});
|
|
1737
1757
|
|
|
1758
|
+
//#endregion
|
|
1759
|
+
//#region src/lib/fetch-utils.ts
|
|
1760
|
+
/**
|
|
1761
|
+
* Create an AbortSignal for fetch timeout if configured.
|
|
1762
|
+
* Controls the time from request start to receiving response headers.
|
|
1763
|
+
* Returns undefined if fetchTimeout is 0 (disabled).
|
|
1764
|
+
*/
|
|
1765
|
+
function createFetchSignal() {
|
|
1766
|
+
return state.fetchTimeout > 0 ? AbortSignal.timeout(state.fetchTimeout * 1e3) : void 0;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1738
1769
|
//#endregion
|
|
1739
1770
|
//#region src/lib/models/client.ts
|
|
1740
1771
|
/** Fetch models from Copilot API and cache in global state */
|
|
@@ -1743,7 +1774,10 @@ async function cacheModels() {
|
|
|
1743
1774
|
rebuildModelIndex();
|
|
1744
1775
|
}
|
|
1745
1776
|
const getModels = async () => {
|
|
1746
|
-
const response = await fetch(`${copilotBaseUrl(state)}/models`, {
|
|
1777
|
+
const response = await fetch(`${copilotBaseUrl(state)}/models`, {
|
|
1778
|
+
headers: copilotHeaders(state),
|
|
1779
|
+
signal: createFetchSignal()
|
|
1780
|
+
});
|
|
1747
1781
|
if (!response.ok) throw await HTTPError.fromResponse("Failed to get models", response);
|
|
1748
1782
|
return await response.json();
|
|
1749
1783
|
};
|
|
@@ -4247,7 +4281,7 @@ const setupClaudeCode = defineCommand({
|
|
|
4247
4281
|
|
|
4248
4282
|
//#endregion
|
|
4249
4283
|
//#region package.json
|
|
4250
|
-
var version = "0.7.
|
|
4284
|
+
var version = "0.7.19";
|
|
4251
4285
|
|
|
4252
4286
|
//#endregion
|
|
4253
4287
|
//#region src/lib/config/config.ts
|
|
@@ -4346,6 +4380,7 @@ async function applyConfigToState() {
|
|
|
4346
4380
|
if (a.convert_server_tools_to_custom !== void 0) state.convertServerToolsToCustom = a.convert_server_tools_to_custom;
|
|
4347
4381
|
if (a.dedup_tool_calls !== void 0) state.dedupToolCalls = a.dedup_tool_calls === true ? "input" : a.dedup_tool_calls;
|
|
4348
4382
|
if (a.truncate_read_tool_result !== void 0) state.truncateReadToolResult = a.truncate_read_tool_result;
|
|
4383
|
+
if (a.filter_tool_search_blocks !== void 0) state.filterToolSearchBlocks = a.filter_tool_search_blocks;
|
|
4349
4384
|
if (a.rewrite_system_reminders !== void 0) {
|
|
4350
4385
|
if (typeof a.rewrite_system_reminders === "boolean") state.rewriteSystemReminders = a.rewrite_system_reminders;
|
|
4351
4386
|
else if (Array.isArray(a.rewrite_system_reminders)) state.rewriteSystemReminders = compileRewriteRules(a.rewrite_system_reminders);
|
|
@@ -4557,7 +4592,8 @@ function toHistoryResponse(entryData) {
|
|
|
4557
4592
|
input_tokens: r.usage.input_tokens,
|
|
4558
4593
|
output_tokens: r.usage.output_tokens,
|
|
4559
4594
|
cache_read_input_tokens: r.usage.cache_read_input_tokens,
|
|
4560
|
-
cache_creation_input_tokens: r.usage.cache_creation_input_tokens
|
|
4595
|
+
cache_creation_input_tokens: r.usage.cache_creation_input_tokens,
|
|
4596
|
+
output_tokens_details: r.usage.output_tokens_details
|
|
4561
4597
|
},
|
|
4562
4598
|
stop_reason: r.stop_reason,
|
|
4563
4599
|
error: r.error,
|
|
@@ -5059,7 +5095,18 @@ function responsesInputToMessages(input) {
|
|
|
5059
5095
|
content: `[item_reference: ${item.id ?? "unknown"}]`
|
|
5060
5096
|
});
|
|
5061
5097
|
break;
|
|
5062
|
-
|
|
5098
|
+
case "reasoning":
|
|
5099
|
+
messages.push({
|
|
5100
|
+
role: "assistant",
|
|
5101
|
+
content: `[reasoning: ${item.id ?? "unknown"}]`
|
|
5102
|
+
});
|
|
5103
|
+
break;
|
|
5104
|
+
default:
|
|
5105
|
+
if (item.type && item.id) messages.push({
|
|
5106
|
+
role: "system",
|
|
5107
|
+
content: `[${item.type}: ${item.id}]`
|
|
5108
|
+
});
|
|
5109
|
+
break;
|
|
5063
5110
|
}
|
|
5064
5111
|
return messages;
|
|
5065
5112
|
}
|
|
@@ -5083,6 +5130,10 @@ function responsesOutputToContent(output) {
|
|
|
5083
5130
|
arguments: item.arguments
|
|
5084
5131
|
}
|
|
5085
5132
|
});
|
|
5133
|
+
if (item.type === "reasoning") {
|
|
5134
|
+
const summaryText = item.summary.map((s) => s.text).filter(Boolean).join("\n");
|
|
5135
|
+
if (summaryText) textParts.push(`[Reasoning: ${summaryText}]`);
|
|
5136
|
+
}
|
|
5086
5137
|
}
|
|
5087
5138
|
if (textParts.length === 0 && toolCalls.length === 0) return null;
|
|
5088
5139
|
return {
|
|
@@ -5104,7 +5155,9 @@ function createResponsesStreamAccumulator() {
|
|
|
5104
5155
|
responseId: "",
|
|
5105
5156
|
toolCalls: [],
|
|
5106
5157
|
toolCallMap: /* @__PURE__ */ new Map(),
|
|
5107
|
-
contentParts: []
|
|
5158
|
+
contentParts: [],
|
|
5159
|
+
reasoningTokens: 0,
|
|
5160
|
+
cachedInputTokens: 0
|
|
5108
5161
|
};
|
|
5109
5162
|
}
|
|
5110
5163
|
/** Get the final accumulated content string */
|
|
@@ -5129,6 +5182,8 @@ function accumulateResponsesStreamEvent(event, acc) {
|
|
|
5129
5182
|
if (event.response.usage) {
|
|
5130
5183
|
acc.inputTokens = event.response.usage.input_tokens;
|
|
5131
5184
|
acc.outputTokens = event.response.usage.output_tokens;
|
|
5185
|
+
acc.reasoningTokens = event.response.usage.output_tokens_details?.reasoning_tokens ?? 0;
|
|
5186
|
+
acc.cachedInputTokens = event.response.usage.input_tokens_details?.cached_tokens ?? 0;
|
|
5132
5187
|
}
|
|
5133
5188
|
break;
|
|
5134
5189
|
case "response.failed":
|
|
@@ -5303,30 +5358,31 @@ function mapAnthropicContentBlocks(acc) {
|
|
|
5303
5358
|
const { _generic: _, ...rest } = block;
|
|
5304
5359
|
return rest;
|
|
5305
5360
|
}
|
|
5306
|
-
|
|
5361
|
+
if ("_brand" in block) return {
|
|
5362
|
+
type: block.type,
|
|
5363
|
+
tool_use_id: block.tool_use_id,
|
|
5364
|
+
content: block.content
|
|
5365
|
+
};
|
|
5366
|
+
const narrowed = block;
|
|
5367
|
+
switch (narrowed.type) {
|
|
5307
5368
|
case "text": return {
|
|
5308
5369
|
type: "text",
|
|
5309
|
-
text:
|
|
5370
|
+
text: narrowed.text
|
|
5310
5371
|
};
|
|
5311
5372
|
case "thinking": return {
|
|
5312
5373
|
type: "thinking",
|
|
5313
|
-
thinking:
|
|
5374
|
+
thinking: narrowed.thinking
|
|
5314
5375
|
};
|
|
5315
5376
|
case "redacted_thinking": return { type: "redacted_thinking" };
|
|
5316
5377
|
case "tool_use":
|
|
5317
5378
|
case "server_tool_use": return {
|
|
5318
|
-
type:
|
|
5319
|
-
id:
|
|
5320
|
-
name:
|
|
5321
|
-
input: safeParseJson(
|
|
5322
|
-
};
|
|
5323
|
-
case "web_search_tool_result": return {
|
|
5324
|
-
type: "web_search_tool_result",
|
|
5325
|
-
tool_use_id: block.tool_use_id,
|
|
5326
|
-
content: block.content
|
|
5379
|
+
type: narrowed.type,
|
|
5380
|
+
id: narrowed.id,
|
|
5381
|
+
name: narrowed.name,
|
|
5382
|
+
input: safeParseJson(narrowed.input)
|
|
5327
5383
|
};
|
|
5328
5384
|
default: {
|
|
5329
|
-
const unknown =
|
|
5385
|
+
const unknown = narrowed;
|
|
5330
5386
|
consola.warn(`[recording] Unhandled content block type in stream result: ${unknown.type}`);
|
|
5331
5387
|
return { type: unknown.type };
|
|
5332
5388
|
}
|
|
@@ -5379,6 +5435,7 @@ function buildOpenAIResponseData(acc, fallbackModel) {
|
|
|
5379
5435
|
usage: {
|
|
5380
5436
|
input_tokens: acc.inputTokens,
|
|
5381
5437
|
output_tokens: acc.outputTokens,
|
|
5438
|
+
...acc.reasoningTokens > 0 && { output_tokens_details: { reasoning_tokens: acc.reasoningTokens } },
|
|
5382
5439
|
...acc.cachedTokens > 0 && { cache_read_input_tokens: acc.cachedTokens }
|
|
5383
5440
|
},
|
|
5384
5441
|
stop_reason: acc.finishReason || void 0,
|
|
@@ -5414,7 +5471,9 @@ function buildResponsesResponseData(acc, fallbackModel) {
|
|
|
5414
5471
|
model: acc.model || fallbackModel,
|
|
5415
5472
|
usage: {
|
|
5416
5473
|
input_tokens: acc.inputTokens,
|
|
5417
|
-
output_tokens: acc.outputTokens
|
|
5474
|
+
output_tokens: acc.outputTokens,
|
|
5475
|
+
...acc.reasoningTokens > 0 && { output_tokens_details: { reasoning_tokens: acc.reasoningTokens } },
|
|
5476
|
+
...acc.cachedInputTokens > 0 && { cache_read_input_tokens: acc.cachedInputTokens }
|
|
5418
5477
|
},
|
|
5419
5478
|
stop_reason: acc.status || void 0,
|
|
5420
5479
|
content: finalContent || toolCalls.length > 0 ? {
|
|
@@ -5595,26 +5654,20 @@ async function processResponsesInstructions(instructions, model) {
|
|
|
5595
5654
|
return processSystemPromptText(instructions, model);
|
|
5596
5655
|
}
|
|
5597
5656
|
|
|
5598
|
-
//#endregion
|
|
5599
|
-
//#region src/lib/fetch-utils.ts
|
|
5600
|
-
/**
|
|
5601
|
-
* Create an AbortSignal for fetch timeout if configured.
|
|
5602
|
-
* Controls the time from request start to receiving response headers.
|
|
5603
|
-
* Returns undefined if fetchTimeout is 0 (disabled).
|
|
5604
|
-
*/
|
|
5605
|
-
function createFetchSignal() {
|
|
5606
|
-
return state.fetchTimeout > 0 ? AbortSignal.timeout(state.fetchTimeout * 1e3) : void 0;
|
|
5607
|
-
}
|
|
5608
|
-
|
|
5609
5657
|
//#endregion
|
|
5610
5658
|
//#region src/lib/openai/responses-client.ts
|
|
5611
5659
|
/** Call Copilot /responses endpoint */
|
|
5612
|
-
const createResponses = async (payload) => {
|
|
5660
|
+
const createResponses = async (payload, opts) => {
|
|
5613
5661
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
5614
5662
|
const enableVision = hasVisionContent(payload.input);
|
|
5615
5663
|
const isAgentCall = Array.isArray(payload.input) && payload.input.some((item) => item.role === "assistant" || item.type === "function_call" || item.type === "function_call_output");
|
|
5664
|
+
const modelSupportsVision = opts?.resolvedModel?.capabilities?.supports?.vision !== false;
|
|
5616
5665
|
const headers = {
|
|
5617
|
-
...copilotHeaders(state,
|
|
5666
|
+
...copilotHeaders(state, {
|
|
5667
|
+
vision: enableVision && modelSupportsVision,
|
|
5668
|
+
modelRequestHeaders: opts?.resolvedModel?.request_headers,
|
|
5669
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
5670
|
+
}),
|
|
5618
5671
|
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
5619
5672
|
};
|
|
5620
5673
|
const fetchSignal = createFetchSignal();
|
|
@@ -5740,7 +5793,7 @@ function createTokenRefreshStrategy() {
|
|
|
5740
5793
|
* centralizes that configuration to avoid duplication.
|
|
5741
5794
|
*/
|
|
5742
5795
|
/** Create the FormatAdapter for Responses API pipeline execution */
|
|
5743
|
-
function createResponsesAdapter() {
|
|
5796
|
+
function createResponsesAdapter(selectedModel) {
|
|
5744
5797
|
return {
|
|
5745
5798
|
format: "openai-responses",
|
|
5746
5799
|
sanitize: (p) => ({
|
|
@@ -5748,7 +5801,7 @@ function createResponsesAdapter() {
|
|
|
5748
5801
|
removedCount: 0,
|
|
5749
5802
|
systemReminderRemovals: 0
|
|
5750
5803
|
}),
|
|
5751
|
-
execute: (p) => executeWithAdaptiveRateLimit(() => createResponses(p)),
|
|
5804
|
+
execute: (p) => executeWithAdaptiveRateLimit(() => createResponses(p, { resolvedModel: selectedModel })),
|
|
5752
5805
|
logPayloadSize: (p) => {
|
|
5753
5806
|
const count = typeof p.input === "string" ? 1 : p.input.length;
|
|
5754
5807
|
consola.debug(`Responses payload: ${count} input item(s), model: ${p.model}`);
|
|
@@ -5840,7 +5893,7 @@ async function handleResponseCreate(ws, payload) {
|
|
|
5840
5893
|
model: resolvedModel,
|
|
5841
5894
|
clientModel: requestedModel
|
|
5842
5895
|
});
|
|
5843
|
-
const adapter = createResponsesAdapter();
|
|
5896
|
+
const adapter = createResponsesAdapter(selectedModel);
|
|
5844
5897
|
const strategies = createResponsesStrategies();
|
|
5845
5898
|
try {
|
|
5846
5899
|
const iterator = (await executeRequestPipeline({
|
|
@@ -6658,12 +6711,17 @@ function createTruncationResponseMarkerOpenAI(result) {
|
|
|
6658
6711
|
|
|
6659
6712
|
//#endregion
|
|
6660
6713
|
//#region src/lib/openai/client.ts
|
|
6661
|
-
const createChatCompletions = async (payload) => {
|
|
6714
|
+
const createChatCompletions = async (payload, opts) => {
|
|
6662
6715
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
6663
6716
|
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
|
|
6664
6717
|
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
6718
|
+
const modelSupportsVision = opts?.resolvedModel?.capabilities?.supports?.vision !== false;
|
|
6665
6719
|
const headers = {
|
|
6666
|
-
...copilotHeaders(state,
|
|
6720
|
+
...copilotHeaders(state, {
|
|
6721
|
+
vision: enableVision && modelSupportsVision,
|
|
6722
|
+
modelRequestHeaders: opts?.resolvedModel?.request_headers,
|
|
6723
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
6724
|
+
}),
|
|
6667
6725
|
"X-Initiator": isAgentCall ? "agent" : "user"
|
|
6668
6726
|
};
|
|
6669
6727
|
const fetchSignal = createFetchSignal();
|
|
@@ -6792,6 +6850,7 @@ function createOpenAIStreamAccumulator() {
|
|
|
6792
6850
|
inputTokens: 0,
|
|
6793
6851
|
outputTokens: 0,
|
|
6794
6852
|
cachedTokens: 0,
|
|
6853
|
+
reasoningTokens: 0,
|
|
6795
6854
|
finishReason: "",
|
|
6796
6855
|
content: "",
|
|
6797
6856
|
toolCalls: [],
|
|
@@ -6805,6 +6864,7 @@ function accumulateOpenAIStreamEvent(parsed, acc) {
|
|
|
6805
6864
|
acc.inputTokens = parsed.usage.prompt_tokens;
|
|
6806
6865
|
acc.outputTokens = parsed.usage.completion_tokens;
|
|
6807
6866
|
if (parsed.usage.prompt_tokens_details?.cached_tokens !== void 0) acc.cachedTokens = parsed.usage.prompt_tokens_details.cached_tokens;
|
|
6867
|
+
if (parsed.usage.completion_tokens_details?.reasoning_tokens !== void 0) acc.reasoningTokens = parsed.usage.completion_tokens_details.reasoning_tokens;
|
|
6808
6868
|
}
|
|
6809
6869
|
const choice = parsed.choices[0];
|
|
6810
6870
|
if (choice) {
|
|
@@ -7057,7 +7117,7 @@ async function executeRequest(opts) {
|
|
|
7057
7117
|
const adapter = {
|
|
7058
7118
|
format: "openai-chat-completions",
|
|
7059
7119
|
sanitize: (p) => sanitizeOpenAIMessages(p),
|
|
7060
|
-
execute: (p) => executeWithAdaptiveRateLimit(() => createChatCompletions(p)),
|
|
7120
|
+
execute: (p) => executeWithAdaptiveRateLimit(() => createChatCompletions(p, { resolvedModel: selectedModel })),
|
|
7061
7121
|
logPayloadSize: (p) => logPayloadSizeInfo(p, selectedModel)
|
|
7062
7122
|
};
|
|
7063
7123
|
const strategies = [
|
|
@@ -7228,7 +7288,8 @@ const createEmbeddings = async (payload) => {
|
|
|
7228
7288
|
const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {
|
|
7229
7289
|
method: "POST",
|
|
7230
7290
|
headers: copilotHeaders(state),
|
|
7231
|
-
body: JSON.stringify(normalizedPayload)
|
|
7291
|
+
body: JSON.stringify(normalizedPayload),
|
|
7292
|
+
signal: createFetchSignal()
|
|
7232
7293
|
});
|
|
7233
7294
|
if (!response.ok) throw await HTTPError.fromResponse("Failed to create embeddings", response);
|
|
7234
7295
|
return await response.json();
|
|
@@ -7946,9 +8007,6 @@ function contentToText(content, options) {
|
|
|
7946
8007
|
case "server_tool_use":
|
|
7947
8008
|
parts.push(`[server_tool_use: ${block.name}]`, JSON.stringify(block.input));
|
|
7948
8009
|
break;
|
|
7949
|
-
case "web_search_tool_result":
|
|
7950
|
-
parts.push(`[web_search_tool_result]`);
|
|
7951
|
-
break;
|
|
7952
8010
|
default: {
|
|
7953
8011
|
const genericBlock = block;
|
|
7954
8012
|
if ("tool_use_id" in genericBlock && genericBlock.type !== "image") {
|
|
@@ -8647,7 +8705,7 @@ const NON_DEFERRED_TOOL_NAMES = new Set([
|
|
|
8647
8705
|
"switch_agent",
|
|
8648
8706
|
...CLAUDE_CODE_OFFICIAL_TOOLS
|
|
8649
8707
|
]);
|
|
8650
|
-
const TOOL_SEARCH_TOOL_NAME = "tool_search_tool_regex";
|
|
8708
|
+
const TOOL_SEARCH_TOOL_NAME$1 = "tool_search_tool_regex";
|
|
8651
8709
|
const TOOL_SEARCH_TOOL_TYPE = "tool_search_tool_regex_20251119";
|
|
8652
8710
|
const EMPTY_INPUT_SCHEMA = {
|
|
8653
8711
|
type: "object",
|
|
@@ -8706,12 +8764,12 @@ function buildHistoryToolStubs(historyToolNames) {
|
|
|
8706
8764
|
* Returns a new array — never mutates the input.
|
|
8707
8765
|
*/
|
|
8708
8766
|
function processToolPipeline(tools, modelId, messages) {
|
|
8709
|
-
const
|
|
8767
|
+
const existingNamesLower = new Set(tools.map((t) => t.name.toLowerCase()));
|
|
8710
8768
|
const toolSearchEnabled = modelSupportsToolSearch(modelId);
|
|
8711
8769
|
const historyToolNames = toolSearchEnabled ? collectHistoryToolNames(messages) : void 0;
|
|
8712
8770
|
const result = [];
|
|
8713
8771
|
if (toolSearchEnabled) result.push({
|
|
8714
|
-
name: TOOL_SEARCH_TOOL_NAME,
|
|
8772
|
+
name: TOOL_SEARCH_TOOL_NAME$1,
|
|
8715
8773
|
type: TOOL_SEARCH_TOOL_TYPE,
|
|
8716
8774
|
defer_loading: false
|
|
8717
8775
|
});
|
|
@@ -8723,7 +8781,7 @@ function processToolPipeline(tools, modelId, messages) {
|
|
|
8723
8781
|
defer_loading: true
|
|
8724
8782
|
} : normalized);
|
|
8725
8783
|
}
|
|
8726
|
-
for (const name of CLAUDE_CODE_OFFICIAL_TOOLS) if (!
|
|
8784
|
+
for (const name of CLAUDE_CODE_OFFICIAL_TOOLS) if (!existingNamesLower.has(name.toLowerCase())) {
|
|
8727
8785
|
const stub = {
|
|
8728
8786
|
name,
|
|
8729
8787
|
description: `Claude Code ${name} tool`,
|
|
@@ -8914,7 +8972,7 @@ function adjustThinkingBudget(wire) {
|
|
|
8914
8972
|
* Create messages using Anthropic-style API directly.
|
|
8915
8973
|
* Calls Copilot's native Anthropic endpoint for Anthropic-vendor models.
|
|
8916
8974
|
*/
|
|
8917
|
-
async function createAnthropicMessages(payload) {
|
|
8975
|
+
async function createAnthropicMessages(payload, opts) {
|
|
8918
8976
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
8919
8977
|
const wire = buildWirePayload(payload);
|
|
8920
8978
|
adjustThinkingBudget(wire);
|
|
@@ -8927,8 +8985,13 @@ async function createAnthropicMessages(payload) {
|
|
|
8927
8985
|
return msg.content.some((block) => block.type === "image");
|
|
8928
8986
|
});
|
|
8929
8987
|
const isAgentCall = messages.some((msg) => msg.role === "assistant");
|
|
8988
|
+
const modelSupportsVision = opts?.resolvedModel?.capabilities?.supports?.vision !== false;
|
|
8930
8989
|
const headers = {
|
|
8931
|
-
...copilotHeaders(state,
|
|
8990
|
+
...copilotHeaders(state, {
|
|
8991
|
+
vision: enableVision && modelSupportsVision,
|
|
8992
|
+
modelRequestHeaders: opts?.resolvedModel?.request_headers,
|
|
8993
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
8994
|
+
}),
|
|
8932
8995
|
"X-Initiator": isAgentCall ? "agent" : "user",
|
|
8933
8996
|
"anthropic-version": "2023-06-01",
|
|
8934
8997
|
...buildAnthropicBetaHeaders(model)
|
|
@@ -9063,25 +9126,29 @@ function handleContentBlockStart(index, block, acc) {
|
|
|
9063
9126
|
input: ""
|
|
9064
9127
|
};
|
|
9065
9128
|
break;
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9129
|
+
default:
|
|
9130
|
+
if (isServerToolResultType(block.type) && "tool_use_id" in block) {
|
|
9131
|
+
newBlock = {
|
|
9132
|
+
_brand: "server_tool_result",
|
|
9133
|
+
type: block.type,
|
|
9134
|
+
tool_use_id: block.tool_use_id,
|
|
9135
|
+
content: block.content
|
|
9136
|
+
};
|
|
9137
|
+
break;
|
|
9138
|
+
}
|
|
9139
|
+
consola.warn(`[stream-accumulator] Unknown content block type: ${block.type}`);
|
|
9076
9140
|
newBlock = {
|
|
9077
|
-
...
|
|
9141
|
+
...block,
|
|
9078
9142
|
_generic: true
|
|
9079
9143
|
};
|
|
9080
9144
|
break;
|
|
9081
|
-
}
|
|
9082
9145
|
}
|
|
9083
9146
|
acc.contentBlocks[index] = newBlock;
|
|
9084
9147
|
}
|
|
9148
|
+
/** Check if a block type is a server-side tool result (ends with _tool_result, but not plain tool_result) */
|
|
9149
|
+
function isServerToolResultType(type) {
|
|
9150
|
+
return type !== "tool_result" && type.endsWith("_tool_result");
|
|
9151
|
+
}
|
|
9085
9152
|
function handleContentBlockDelta(index, delta, acc, copilotAnnotations) {
|
|
9086
9153
|
const block = acc.contentBlocks[index];
|
|
9087
9154
|
if (!block) return;
|
|
@@ -9524,7 +9591,7 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, reqCtx) {
|
|
|
9524
9591
|
const adapter = {
|
|
9525
9592
|
format: "anthropic-messages",
|
|
9526
9593
|
sanitize: (p) => sanitizeAnthropicMessages(preprocessTools(p)),
|
|
9527
|
-
execute: (p) => executeWithAdaptiveRateLimit(() => createAnthropicMessages(p)),
|
|
9594
|
+
execute: (p) => executeWithAdaptiveRateLimit(() => createAnthropicMessages(p, { resolvedModel: selectedModel })),
|
|
9528
9595
|
logPayloadSize: (p) => logPayloadSizeInfoAnthropic(p, selectedModel)
|
|
9529
9596
|
};
|
|
9530
9597
|
const strategies = [
|
|
@@ -9606,6 +9673,7 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
9606
9673
|
let eventsIn = 0;
|
|
9607
9674
|
let currentBlockType = "";
|
|
9608
9675
|
let firstEventLogged = false;
|
|
9676
|
+
const toolSearchFilter = state.filterToolSearchBlocks ? createToolSearchBlockFilter() : null;
|
|
9609
9677
|
try {
|
|
9610
9678
|
for await (const { raw: rawEvent, parsed } of processAnthropicStream(response, acc, clientAbortSignal)) {
|
|
9611
9679
|
const dataLen = rawEvent.data?.length ?? 0;
|
|
@@ -9638,8 +9706,10 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
9638
9706
|
const delta = parsed.delta;
|
|
9639
9707
|
if (delta.type === "text_delta" && delta.text) checkRepetition(delta.text);
|
|
9640
9708
|
}
|
|
9709
|
+
const forwardData = toolSearchFilter ? toolSearchFilter.rewriteEvent(parsed, rawEvent.data ?? "") : rawEvent.data ?? "";
|
|
9710
|
+
if (forwardData === null) continue;
|
|
9641
9711
|
await stream.writeSSE({
|
|
9642
|
-
data:
|
|
9712
|
+
data: forwardData,
|
|
9643
9713
|
event: rawEvent.event,
|
|
9644
9714
|
id: rawEvent.id !== void 0 ? String(rawEvent.id) : void 0,
|
|
9645
9715
|
retry: rawEvent.retry
|
|
@@ -9688,6 +9758,7 @@ function handleDirectAnthropicNonStreamingResponse(c, response, reqCtx, truncate
|
|
|
9688
9758
|
});
|
|
9689
9759
|
let finalResponse = response;
|
|
9690
9760
|
if (state.verbose && truncateResult?.wasTruncated) finalResponse = prependMarkerToResponse(response, createTruncationMarker$1(truncateResult));
|
|
9761
|
+
if (state.filterToolSearchBlocks) finalResponse = filterToolSearchBlocksFromResponse(finalResponse);
|
|
9691
9762
|
return c.json(finalResponse);
|
|
9692
9763
|
}
|
|
9693
9764
|
/** Convert SanitizationStats to the format expected by rewrites */
|
|
@@ -9701,6 +9772,69 @@ function toSanitizationInfo(stats) {
|
|
|
9701
9772
|
systemReminderRemovals: stats.systemReminderRemovals
|
|
9702
9773
|
};
|
|
9703
9774
|
}
|
|
9775
|
+
const TOOL_SEARCH_TOOL_NAME = "tool_search_tool_regex";
|
|
9776
|
+
const TOOL_SEARCH_RESULT_TYPE = "tool_search_tool_result";
|
|
9777
|
+
/** Check if a content block is an internal tool_search block */
|
|
9778
|
+
function isToolSearchBlock(block) {
|
|
9779
|
+
if (block.type === "server_tool_use" && block.name === TOOL_SEARCH_TOOL_NAME) return true;
|
|
9780
|
+
if (block.type === TOOL_SEARCH_RESULT_TYPE) return true;
|
|
9781
|
+
return false;
|
|
9782
|
+
}
|
|
9783
|
+
/**
|
|
9784
|
+
* Filters tool_search blocks from the SSE stream before forwarding to the client.
|
|
9785
|
+
* Handles index remapping so block indices remain dense/sequential after filtering.
|
|
9786
|
+
*/
|
|
9787
|
+
function createToolSearchBlockFilter() {
|
|
9788
|
+
const filteredIndices = /* @__PURE__ */ new Set();
|
|
9789
|
+
const clientIndexMap = /* @__PURE__ */ new Map();
|
|
9790
|
+
let nextClientIndex = 0;
|
|
9791
|
+
function getClientIndex(apiIndex) {
|
|
9792
|
+
let idx = clientIndexMap.get(apiIndex);
|
|
9793
|
+
if (idx === void 0) {
|
|
9794
|
+
idx = nextClientIndex++;
|
|
9795
|
+
clientIndexMap.set(apiIndex, idx);
|
|
9796
|
+
}
|
|
9797
|
+
return idx;
|
|
9798
|
+
}
|
|
9799
|
+
return { rewriteEvent(parsed, rawData) {
|
|
9800
|
+
if (!parsed) return rawData;
|
|
9801
|
+
if (parsed.type === "content_block_start") {
|
|
9802
|
+
const block = parsed.content_block;
|
|
9803
|
+
if (isToolSearchBlock(block)) {
|
|
9804
|
+
filteredIndices.add(parsed.index);
|
|
9805
|
+
return null;
|
|
9806
|
+
}
|
|
9807
|
+
if (filteredIndices.size === 0) {
|
|
9808
|
+
getClientIndex(parsed.index);
|
|
9809
|
+
return rawData;
|
|
9810
|
+
}
|
|
9811
|
+
const clientIndex = getClientIndex(parsed.index);
|
|
9812
|
+
if (clientIndex === parsed.index) return rawData;
|
|
9813
|
+
const obj = JSON.parse(rawData);
|
|
9814
|
+
obj.index = clientIndex;
|
|
9815
|
+
return JSON.stringify(obj);
|
|
9816
|
+
}
|
|
9817
|
+
if (parsed.type === "content_block_delta" || parsed.type === "content_block_stop") {
|
|
9818
|
+
if (filteredIndices.has(parsed.index)) return null;
|
|
9819
|
+
if (filteredIndices.size === 0) return rawData;
|
|
9820
|
+
const clientIndex = getClientIndex(parsed.index);
|
|
9821
|
+
if (clientIndex === parsed.index) return rawData;
|
|
9822
|
+
const obj = JSON.parse(rawData);
|
|
9823
|
+
obj.index = clientIndex;
|
|
9824
|
+
return JSON.stringify(obj);
|
|
9825
|
+
}
|
|
9826
|
+
return rawData;
|
|
9827
|
+
} };
|
|
9828
|
+
}
|
|
9829
|
+
/** Filter tool_search blocks from a non-streaming response */
|
|
9830
|
+
function filterToolSearchBlocksFromResponse(response) {
|
|
9831
|
+
const filtered = response.content.filter((block) => !isToolSearchBlock(block));
|
|
9832
|
+
if (filtered.length === response.content.length) return response;
|
|
9833
|
+
return {
|
|
9834
|
+
...response,
|
|
9835
|
+
content: filtered
|
|
9836
|
+
};
|
|
9837
|
+
}
|
|
9704
9838
|
|
|
9705
9839
|
//#endregion
|
|
9706
9840
|
//#region src/routes/messages/route.ts
|
|
@@ -9808,9 +9942,9 @@ async function handleResponses(c) {
|
|
|
9808
9942
|
/** Pass through to Copilot /responses endpoint directly */
|
|
9809
9943
|
async function handleDirectResponses(opts) {
|
|
9810
9944
|
const { c, payload, reqCtx } = opts;
|
|
9811
|
-
const adapter = createResponsesAdapter();
|
|
9812
|
-
const strategies = createResponsesStrategies();
|
|
9813
9945
|
const selectedModel = state.modelIndex.get(payload.model);
|
|
9946
|
+
const adapter = createResponsesAdapter(selectedModel);
|
|
9947
|
+
const strategies = createResponsesStrategies();
|
|
9814
9948
|
try {
|
|
9815
9949
|
const pipelineResult = await executeRequestPipeline({
|
|
9816
9950
|
adapter,
|
|
@@ -9832,7 +9966,8 @@ async function handleDirectResponses(opts) {
|
|
|
9832
9966
|
usage: {
|
|
9833
9967
|
input_tokens: responsesResponse.usage?.input_tokens ?? 0,
|
|
9834
9968
|
output_tokens: responsesResponse.usage?.output_tokens ?? 0,
|
|
9835
|
-
...responsesResponse.usage?.input_tokens_details?.cached_tokens && { cache_read_input_tokens: responsesResponse.usage.input_tokens_details.cached_tokens }
|
|
9969
|
+
...responsesResponse.usage?.input_tokens_details?.cached_tokens && { cache_read_input_tokens: responsesResponse.usage.input_tokens_details.cached_tokens },
|
|
9970
|
+
...responsesResponse.usage?.output_tokens_details?.reasoning_tokens && { output_tokens_details: { reasoning_tokens: responsesResponse.usage.output_tokens_details.reasoning_tokens } }
|
|
9836
9971
|
},
|
|
9837
9972
|
stop_reason: responsesResponse.status,
|
|
9838
9973
|
content
|
|
@@ -9867,8 +10002,12 @@ async function handleDirectResponses(opts) {
|
|
|
9867
10002
|
streamEventsIn: eventsIn
|
|
9868
10003
|
});
|
|
9869
10004
|
try {
|
|
9870
|
-
|
|
9871
|
-
|
|
10005
|
+
const event = JSON.parse(rawEvent.data);
|
|
10006
|
+
accumulateResponsesStreamEvent(event, acc);
|
|
10007
|
+
await stream.writeSSE({
|
|
10008
|
+
event: rawEvent.event ?? event.type,
|
|
10009
|
+
data: rawEvent.data
|
|
10010
|
+
});
|
|
9872
10011
|
} catch {}
|
|
9873
10012
|
}
|
|
9874
10013
|
}
|
|
@@ -9878,10 +10017,13 @@ async function handleDirectResponses(opts) {
|
|
|
9878
10017
|
consola.error("[Responses] Stream error:", error);
|
|
9879
10018
|
reqCtx.fail(acc.model || payload.model, error);
|
|
9880
10019
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9881
|
-
await stream.writeSSE({
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
10020
|
+
await stream.writeSSE({
|
|
10021
|
+
event: "error",
|
|
10022
|
+
data: JSON.stringify({ error: {
|
|
10023
|
+
message: errorMessage,
|
|
10024
|
+
type: error instanceof StreamIdleTimeoutError ? "timeout_error" : "server_error"
|
|
10025
|
+
} })
|
|
10026
|
+
});
|
|
9885
10027
|
}
|
|
9886
10028
|
});
|
|
9887
10029
|
} catch (error) {
|
|
@@ -10032,7 +10174,16 @@ function parseIntOrDefault(value, defaultValue) {
|
|
|
10032
10174
|
const parsed = Number.parseInt(value, 10);
|
|
10033
10175
|
return Number.isFinite(parsed) ? parsed : defaultValue;
|
|
10034
10176
|
}
|
|
10177
|
+
const VALID_ACCOUNT_TYPES = [
|
|
10178
|
+
"individual",
|
|
10179
|
+
"business",
|
|
10180
|
+
"enterprise"
|
|
10181
|
+
];
|
|
10035
10182
|
async function runServer(options) {
|
|
10183
|
+
if (!VALID_ACCOUNT_TYPES.includes(options.accountType)) {
|
|
10184
|
+
consola.error(`Invalid account type: "${options.accountType}". Must be one of: ${VALID_ACCOUNT_TYPES.join(", ")}`);
|
|
10185
|
+
process.exit(1);
|
|
10186
|
+
}
|
|
10036
10187
|
if (options.verbose) {
|
|
10037
10188
|
consola.level = 5;
|
|
10038
10189
|
state.verbose = true;
|
|
@@ -10085,6 +10236,10 @@ async function runServer(options) {
|
|
|
10085
10236
|
on("[timeouts]", "Timeouts", parts.join(", "));
|
|
10086
10237
|
}
|
|
10087
10238
|
on("[history_limit]", "History", state.historyLimit === 0 ? "unlimited" : `max=${state.historyLimit}`);
|
|
10239
|
+
on("[shutdown]", "Shutdown", `graceful=${state.shutdownGracefulWait}s, abort=${state.shutdownAbortWait}s`);
|
|
10240
|
+
if (state.systemPromptOverrides.length > 0) on("[system_prompt_overrides]", "System prompt overrides", `${state.systemPromptOverrides.length} rules`);
|
|
10241
|
+
if (config.system_prompt_prepend) on("[system_prompt_prepend]", "System prompt prepend", `${config.system_prompt_prepend.length} chars`);
|
|
10242
|
+
if (config.system_prompt_append) on("[system_prompt_append]", "System prompt append", `${config.system_prompt_append.length} chars`);
|
|
10088
10243
|
consola.info(`Configuration:\n${configLines.join("\n")}`);
|
|
10089
10244
|
if (options.rateLimit) initAdaptiveRateLimiter({
|
|
10090
10245
|
baseRetryIntervalSeconds: rlRetryInterval,
|
|
@@ -10102,7 +10257,9 @@ async function runServer(options) {
|
|
|
10102
10257
|
try {
|
|
10103
10258
|
await cacheModels();
|
|
10104
10259
|
} catch (error) {
|
|
10105
|
-
consola.
|
|
10260
|
+
consola.error("Failed to fetch models from Copilot API:", error instanceof Error ? error.message : error);
|
|
10261
|
+
consola.error(`Verify that --account-type "${state.accountType}" is correct. Available types: ${VALID_ACCOUNT_TYPES.join(", ")}`);
|
|
10262
|
+
process.exit(1);
|
|
10106
10263
|
}
|
|
10107
10264
|
consola.info(`Available models:\n${state.models?.data.map((m) => formatModelInfo(m)).join("\n")}`);
|
|
10108
10265
|
await loadPersistedLimits();
|