copilot-relay 0.2.6 → 0.2.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 +5 -0
- package/dist/main.js +30 -19
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,11 @@ Public API:
|
|
|
24
24
|
- `POST /v1/messages/count_tokens`
|
|
25
25
|
- `GET /v1/models`
|
|
26
26
|
- `GET /healthz`
|
|
27
|
+
- `GET|HEAD /api/hello`
|
|
28
|
+
|
|
29
|
+
`/api/hello` is a static reachability probe Claude Code sends on startup and
|
|
30
|
+
around real traffic. Like `/healthz` it never contacts Copilot, so it proves the
|
|
31
|
+
relay is listening and nothing more.
|
|
27
32
|
|
|
28
33
|
Claude WebSearch is bridge-managed: when the model selects the WebSearch tool,
|
|
29
34
|
the relay executes Copilot `/responses` with `web_search_preview`, then sends the
|
package/dist/main.js
CHANGED
|
@@ -1508,6 +1508,7 @@ function getFinishReason(response, hasFunctionCalls) {
|
|
|
1508
1508
|
//#region src/copilot/chat.ts
|
|
1509
1509
|
const usesMaxCompletionTokens = (modelId) => modelId.startsWith("gpt-5");
|
|
1510
1510
|
const continuePrefillPrompt$1 = "Continue the assistant response from the previous assistant message.";
|
|
1511
|
+
const continueAfterNonUserPrompt = "Continue based on the context above.";
|
|
1511
1512
|
const maxUserLength = 64;
|
|
1512
1513
|
const sanitizeReasoningEffortForModel = (_modelId, reasoningEffort) => {
|
|
1513
1514
|
return reasoningEffort ?? void 0;
|
|
@@ -1536,7 +1537,14 @@ const buildRequestPayload = (payload) => {
|
|
|
1536
1537
|
};
|
|
1537
1538
|
const normalizeFinalAssistantPrefill$1 = (payload) => {
|
|
1538
1539
|
const lastMessage = payload.messages.at(-1);
|
|
1539
|
-
if (lastMessage
|
|
1540
|
+
if (!lastMessage || lastMessage.role === "user") return payload;
|
|
1541
|
+
if (lastMessage.role !== "assistant") return {
|
|
1542
|
+
...payload,
|
|
1543
|
+
messages: [...payload.messages, {
|
|
1544
|
+
role: "user",
|
|
1545
|
+
content: continueAfterNonUserPrompt
|
|
1546
|
+
}]
|
|
1547
|
+
};
|
|
1540
1548
|
const messages = [...payload.messages];
|
|
1541
1549
|
if (typeof lastMessage.content !== "string") {
|
|
1542
1550
|
messages.pop();
|
|
@@ -2435,10 +2443,10 @@ const getWebSearchResultText = (search) => {
|
|
|
2435
2443
|
].join("\n");
|
|
2436
2444
|
};
|
|
2437
2445
|
const createWebSearchResultContextMessage = (search) => ({
|
|
2438
|
-
role: "
|
|
2446
|
+
role: "user",
|
|
2439
2447
|
content: [
|
|
2440
2448
|
"Trusted bridge retrieval context: the assistant selected web_search, and copilot-relay executed it.",
|
|
2441
|
-
"Use this context
|
|
2449
|
+
"Use this context to complete the request. Do not describe it as user-provided or injected.",
|
|
2442
2450
|
"If the user requested a specific output format, answer using only matching information from this context.",
|
|
2443
2451
|
"If the user asked for a URL only, output only that URL with no surrounding text.",
|
|
2444
2452
|
"",
|
|
@@ -2447,24 +2455,26 @@ const createWebSearchResultContextMessage = (search) => ({
|
|
|
2447
2455
|
getWebSearchResultText(search)
|
|
2448
2456
|
].join("\n")
|
|
2449
2457
|
});
|
|
2450
|
-
const
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2458
|
+
const isWebSearchTool = (tool, toolNameMapper) => isClaudeWebSearchToolName(toolNameMapper.toClaude(tool.function.name));
|
|
2459
|
+
const removeWebSearchTool = (tools, toolNameMapper) => {
|
|
2460
|
+
if (!tools || tools.length === 0) return;
|
|
2461
|
+
const remaining = tools.filter((tool) => !isWebSearchTool(tool, toolNameMapper));
|
|
2462
|
+
return remaining.length > 0 ? remaining : void 0;
|
|
2463
|
+
};
|
|
2464
|
+
const normalizeFinalToolChoice = (toolChoice, tools, toolNameMapper) => {
|
|
2465
|
+
if (!tools || tools.length === 0) return;
|
|
2466
|
+
if (toolChoice === "required") return "auto";
|
|
2467
|
+
if (toolChoice && typeof toolChoice === "object" && isClaudeWebSearchToolName(toolNameMapper.toClaude(toolChoice.function.name))) return "auto";
|
|
2468
|
+
return toolChoice;
|
|
2469
|
+
};
|
|
2470
|
+
const createFinalWebSearchPayload = (payload, search, toolNameMapper) => {
|
|
2471
|
+
const tools = removeWebSearchTool(payload.tools, toolNameMapper);
|
|
2462
2472
|
return {
|
|
2463
2473
|
...payload,
|
|
2464
2474
|
stream: false,
|
|
2465
|
-
tools
|
|
2466
|
-
tool_choice:
|
|
2467
|
-
messages: [...messages, createWebSearchResultContextMessage(search)]
|
|
2475
|
+
tools,
|
|
2476
|
+
tool_choice: normalizeFinalToolChoice(payload.tool_choice, tools, toolNameMapper),
|
|
2477
|
+
messages: [...payload.messages, createWebSearchResultContextMessage(search)]
|
|
2468
2478
|
};
|
|
2469
2479
|
};
|
|
2470
2480
|
const mergeWebSearchAndFinalResponse = (searchResponse, finalResponse) => ({
|
|
@@ -2744,7 +2754,7 @@ const handleClaudeMessageRequest = async (config, claudePayload, requestSignal,
|
|
|
2744
2754
|
const searchResponse = createClaudeWebSearchResponse(search);
|
|
2745
2755
|
if (search.results.length === 0) claudeResponse = searchResponse;
|
|
2746
2756
|
else {
|
|
2747
|
-
const finalResponse = await createChatCompletions(config, createFinalWebSearchPayload(openAIPayload, search), {
|
|
2757
|
+
const finalResponse = await createChatCompletions(config, createFinalWebSearchPayload(openAIPayload, search, toolNameMapper), {
|
|
2748
2758
|
client: "claude",
|
|
2749
2759
|
requestedModel: claudePayload.model,
|
|
2750
2760
|
requestedThinkEffort: getClaudeRequestedThinkEffort(claudePayload),
|
|
@@ -2924,6 +2934,7 @@ const createServer = (config) => {
|
|
|
2924
2934
|
status: "ok"
|
|
2925
2935
|
}));
|
|
2926
2936
|
app.get("/healthz", (c) => c.json({ ok: true }));
|
|
2937
|
+
app.on(["GET", "HEAD"], "/api/hello", (c) => c.body(null, 200));
|
|
2927
2938
|
app.route("/v1", claudeRoutes);
|
|
2928
2939
|
app.notFound(async (c) => {
|
|
2929
2940
|
const message = "Unsupported Claude API route";
|