copilot-relay 0.3.6 → 0.3.7
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/README.md +4 -0
- package/config.default.yaml +2 -1
- package/dist/main.js +48 -7
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,10 @@ for live model discovery, compatible thinking efforts, and copy-paste updates.
|
|
|
123
123
|
Any other `logLevel` value is invalid and stops startup.
|
|
124
124
|
|
|
125
125
|
Valid `thinkEffort`: `none`, `low`, `medium`, `high`, `xhigh`, `max`.
|
|
126
|
+
It is a fallback, not a forced override: Claude Code's `output_config.effort`
|
|
127
|
+
wins, followed by the legacy `reasoning_effort` field, then `thinkEffort` when
|
|
128
|
+
neither request field supplies a value. The selected effort is preserved through
|
|
129
|
+
all model and WebSearch passes; existing config values are not rewritten.
|
|
126
130
|
|
|
127
131
|
`upstreamTimeoutSeconds` controls the maximum time a single Claude request can
|
|
128
132
|
spend waiting on upstream Copilot calls, including chat, Responses, preflight,
|
package/config.default.yaml
CHANGED
|
@@ -39,7 +39,8 @@ logLevel: info
|
|
|
39
39
|
# midnight, which is what makes this setting take effect at all.
|
|
40
40
|
logRetentionDays: 3
|
|
41
41
|
|
|
42
|
-
#
|
|
42
|
+
# Fallback upstream effort when Claude Code does not specify one.
|
|
43
|
+
# Request output_config.effort wins, followed by legacy reasoning_effort.
|
|
43
44
|
# Valid values: none, low, medium, high, xhigh, max.
|
|
44
45
|
thinkEffort: max
|
|
45
46
|
|
package/dist/main.js
CHANGED
|
@@ -547,6 +547,29 @@ const defaultModelRouting = {
|
|
|
547
547
|
opusModel: "claude-opus-5"
|
|
548
548
|
};
|
|
549
549
|
const isReasoningEffort = (value) => value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
|
|
550
|
+
const invalidRequestEffort = (message) => new HTTPError(message, new Response(JSON.stringify({
|
|
551
|
+
type: "error",
|
|
552
|
+
error: {
|
|
553
|
+
type: "invalid_request_error",
|
|
554
|
+
message
|
|
555
|
+
}
|
|
556
|
+
}), {
|
|
557
|
+
status: 400,
|
|
558
|
+
headers: { "content-type": "application/json" }
|
|
559
|
+
}), message);
|
|
560
|
+
function getRequestReasoningEffort(request) {
|
|
561
|
+
let nativeEffort;
|
|
562
|
+
const outputConfig = request.output_config;
|
|
563
|
+
if (outputConfig !== void 0 && outputConfig !== null) {
|
|
564
|
+
if (typeof outputConfig !== "object" || Array.isArray(outputConfig)) throw invalidRequestEffort("Invalid output_config: expected an object.");
|
|
565
|
+
if ("effort" in outputConfig) nativeEffort = outputConfig.effort;
|
|
566
|
+
}
|
|
567
|
+
const requested = nativeEffort ?? request.reasoning_effort;
|
|
568
|
+
if (requested === void 0 || requested === null) return void 0;
|
|
569
|
+
if (!isReasoningEffort(requested)) throw invalidRequestEffort(`Invalid ${nativeEffort !== void 0 && nativeEffort !== null ? "output_config.effort" : "reasoning_effort"}: expected none, low, medium, high, xhigh, or max.`);
|
|
570
|
+
return requested;
|
|
571
|
+
}
|
|
572
|
+
const resolveReasoningEffort = (requested) => requested ?? runtimeState.thinkEffort ?? defaultReasoningEffort;
|
|
550
573
|
const oneMillionContextModelPattern = /^(gpt-5\.6-sol|gpt-6-astra)(?:\[1m\])*$/i;
|
|
551
574
|
const normalizeOneMillionContextModel = (model) => {
|
|
552
575
|
return oneMillionContextModelPattern.exec(model)?.[1]?.toLowerCase();
|
|
@@ -844,7 +867,7 @@ const serializeConfig = (config) => [
|
|
|
844
867
|
"# Number of days to keep files in ~/.copilot-relay/logs.",
|
|
845
868
|
`logRetentionDays: ${config.logRetentionDays}`,
|
|
846
869
|
"",
|
|
847
|
-
"#
|
|
870
|
+
"# Fallback effort when the request omits it: none, low, medium, high, xhigh, max.",
|
|
848
871
|
`thinkEffort: ${config.thinkEffort}`,
|
|
849
872
|
"",
|
|
850
873
|
"# Max seconds for one request's upstream calls; 0 disables the relay deadline.",
|
|
@@ -1993,7 +2016,7 @@ const sanitizeReasoningEffortForModel = (_modelId, reasoningEffort) => {
|
|
|
1993
2016
|
return reasoningEffort ?? void 0;
|
|
1994
2017
|
};
|
|
1995
2018
|
const getRequestedReasoningEffort = (payload) => {
|
|
1996
|
-
return sanitizeReasoningEffortForModel(payload.model,
|
|
2019
|
+
return sanitizeReasoningEffortForModel(payload.model, resolveReasoningEffort(getRequestReasoningEffort(payload)));
|
|
1997
2020
|
};
|
|
1998
2021
|
const sanitizeUserIdentifier = (user) => {
|
|
1999
2022
|
if (!user) return;
|
|
@@ -2058,7 +2081,8 @@ const messagesIncludeImage = (messages) => messages.some((msg) => typeof msg.con
|
|
|
2058
2081
|
const createChatCompletions = async (config, payload, options = {}) => {
|
|
2059
2082
|
const client = options.client ?? "generic";
|
|
2060
2083
|
const requestedModel = options.requestedModel ?? payload.model;
|
|
2061
|
-
const requestedThinkEffort = options.requestedThinkEffort ?? payload
|
|
2084
|
+
const requestedThinkEffort = options.requestedThinkEffort ?? getRequestReasoningEffort(payload) ?? "none";
|
|
2085
|
+
const reasoningEffort = getRequestedReasoningEffort(payload);
|
|
2062
2086
|
const requestedThinking = options.requestedThinking ?? "none";
|
|
2063
2087
|
const signal = createCopilotRequestSignal(options.signal, options.timeoutMs);
|
|
2064
2088
|
const upstreamModelId = routeModelId(payload.model);
|
|
@@ -2069,6 +2093,7 @@ const createChatCompletions = async (config, payload, options = {}) => {
|
|
|
2069
2093
|
const upstreamPayload = {
|
|
2070
2094
|
...payload,
|
|
2071
2095
|
model: upstreamModelId,
|
|
2096
|
+
reasoning_effort: reasoningEffort,
|
|
2072
2097
|
max_tokens: maxTokens,
|
|
2073
2098
|
stream: bufferResponse ? true : payload.stream
|
|
2074
2099
|
};
|
|
@@ -2325,7 +2350,7 @@ function translateToOpenAI(payload, _settings, toolNameMapper) {
|
|
|
2325
2350
|
stream: payload.stream,
|
|
2326
2351
|
temperature: payload.temperature,
|
|
2327
2352
|
top_p: payload.top_p,
|
|
2328
|
-
reasoning_effort:
|
|
2353
|
+
reasoning_effort: resolveReasoningEffort(getRequestReasoningEffort(payload)),
|
|
2329
2354
|
user: payload.metadata?.user_id,
|
|
2330
2355
|
tools,
|
|
2331
2356
|
tool_choice: tools && tools.length > 0 ? translateClaudeToolChoiceToOpenAI(payload.tool_choice, mapper) : void 0
|
|
@@ -2770,6 +2795,7 @@ const buildWebSearchRequestPayload = (payload, requestedQuery, model) => ({
|
|
|
2770
2795
|
model,
|
|
2771
2796
|
input: buildSearchInput(payload, requestedQuery),
|
|
2772
2797
|
tools: [{ type: "web_search_preview" }],
|
|
2798
|
+
reasoning: { effort: resolveReasoningEffort(getRequestReasoningEffort(payload)) },
|
|
2773
2799
|
max_output_tokens: Math.max(256, Math.min(payload.max_tokens ?? 1024, 1200)),
|
|
2774
2800
|
temperature: payload.temperature,
|
|
2775
2801
|
top_p: payload.top_p
|
|
@@ -3190,7 +3216,7 @@ const createTokenCountModel = (modelId, tokenizer = "o200k_base") => ({
|
|
|
3190
3216
|
id: modelId
|
|
3191
3217
|
});
|
|
3192
3218
|
const isNonStreamingResponse = (response) => typeof response === "object" && response !== null && Object.hasOwn(response, "choices");
|
|
3193
|
-
const getClaudeRequestedThinkEffort = (payload) => payload
|
|
3219
|
+
const getClaudeRequestedThinkEffort = (payload) => getRequestReasoningEffort(payload) ?? "none";
|
|
3194
3220
|
const getClaudeRequestedThinking = (payload) => {
|
|
3195
3221
|
if (!payload.thinking) return "none";
|
|
3196
3222
|
return [`type:${payload.thinking.type}`, `budget:${payload.thinking.budget_tokens ?? "none"}`].join(",");
|
|
@@ -3368,7 +3394,10 @@ const handleClaudeMessageRequest = async (config, claudePayload, requestSignal,
|
|
|
3368
3394
|
const webSearchToolCall = shouldLetModelDecideWebSearch ? getClaudeWebSearchToolCallFromChatResponse(effectiveResponse, toolNameMapper) : void 0;
|
|
3369
3395
|
let claudeResponse;
|
|
3370
3396
|
if (webSearchToolCall) {
|
|
3371
|
-
const search = await createClaudeWebSearchExecution(config,
|
|
3397
|
+
const search = await createClaudeWebSearchExecution(config, {
|
|
3398
|
+
...claudePayload,
|
|
3399
|
+
reasoning_effort: openAIPayload.reasoning_effort
|
|
3400
|
+
}, webSearchToolCall.query, {
|
|
3372
3401
|
requestId,
|
|
3373
3402
|
signal: requestSignal,
|
|
3374
3403
|
timeoutMs: config.upstreamTimeoutMs
|
|
@@ -3441,6 +3470,14 @@ claudeRoutes.post("/messages", async (c) => {
|
|
|
3441
3470
|
const config = c.get("config");
|
|
3442
3471
|
const requestId = c.get("requestId");
|
|
3443
3472
|
const claudePayload = await c.req.json();
|
|
3473
|
+
try {
|
|
3474
|
+
getRequestReasoningEffort(claudePayload);
|
|
3475
|
+
} catch (error) {
|
|
3476
|
+
if (!(error instanceof HTTPError)) throw error;
|
|
3477
|
+
c.set("requestErrorMessage", error.message);
|
|
3478
|
+
log.error(`request_id=${requestId} ${error.message}`);
|
|
3479
|
+
return error.response;
|
|
3480
|
+
}
|
|
3444
3481
|
const requestSignal = createCopilotRequestSignal(c.req.raw.signal, config.upstreamTimeoutMs);
|
|
3445
3482
|
log.debug("Full Claude request payload", { payload: claudePayload });
|
|
3446
3483
|
if (claudePayload.stream) return streamSSE(c, async (stream) => {
|
|
@@ -3500,6 +3537,10 @@ claudeRoutes.post("/messages/count_tokens", async (c) => {
|
|
|
3500
3537
|
return c.json({ input_tokens: Math.max(1, finalTokenCount) });
|
|
3501
3538
|
} catch (error) {
|
|
3502
3539
|
log.error("Error counting tokens:", error);
|
|
3540
|
+
if (error instanceof HTTPError) {
|
|
3541
|
+
c.set("requestErrorMessage", error.message);
|
|
3542
|
+
return error.response;
|
|
3543
|
+
}
|
|
3503
3544
|
return c.json({ input_tokens: 1 });
|
|
3504
3545
|
}
|
|
3505
3546
|
});
|
|
@@ -3627,7 +3668,7 @@ async function startRelay(appConfig) {
|
|
|
3627
3668
|
};
|
|
3628
3669
|
applyRuntimeConfig(appConfig);
|
|
3629
3670
|
log.info(`Log level: ${appConfig.logLevel}`);
|
|
3630
|
-
log.info(`
|
|
3671
|
+
log.info(`Default think effort: ${appConfig.thinkEffort}`);
|
|
3631
3672
|
log.info(`Upstream timeout: ${appConfig.upstreamTimeoutSeconds}s`);
|
|
3632
3673
|
const authSession = await setupProxyAuth(config);
|
|
3633
3674
|
try {
|