opencodekit 0.20.0 → 0.20.1
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/index.js
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -94,6 +94,51 @@ function getUrls(domain: string) {
|
|
|
94
94
|
|
|
95
95
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
96
96
|
|
|
97
|
+
function getRequestUrl(input: RequestInfo | URL): string {
|
|
98
|
+
if (typeof input === "string") return input;
|
|
99
|
+
if (input instanceof URL) return input.toString();
|
|
100
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
101
|
+
return input.url;
|
|
102
|
+
}
|
|
103
|
+
return input.toString();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractClaudeThinkingBudget(body: any): number | undefined {
|
|
107
|
+
const candidates = [body?.thinking_budget, body?.thinking?.budget_tokens];
|
|
108
|
+
|
|
109
|
+
for (const candidate of candidates) {
|
|
110
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
111
|
+
const normalized = Math.trunc(candidate);
|
|
112
|
+
if (normalized > 0) return normalized;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getErrorMessage(error: unknown): string {
|
|
120
|
+
if (error instanceof Error) return error.message;
|
|
121
|
+
if (typeof error === "string") return error;
|
|
122
|
+
try {
|
|
123
|
+
return JSON.stringify(error);
|
|
124
|
+
} catch {
|
|
125
|
+
return String(error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isTransientUpstreamTimeoutError(error: unknown): boolean {
|
|
130
|
+
const message = getErrorMessage(error).toLowerCase();
|
|
131
|
+
return (
|
|
132
|
+
message.includes("upstream idle timeout") ||
|
|
133
|
+
message.includes("mid_stream") ||
|
|
134
|
+
message.includes("sse read timed out") ||
|
|
135
|
+
message.includes("socket connection was closed unexpectedly") ||
|
|
136
|
+
message.includes("connection reset") ||
|
|
137
|
+
message.includes("econnreset") ||
|
|
138
|
+
message.includes("etimedout")
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
97
142
|
// Rate limit handling configuration
|
|
98
143
|
const RATE_LIMIT_CONFIG = {
|
|
99
144
|
maxDelayMs: 60000, // Cap at 60 seconds
|
|
@@ -553,7 +598,7 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
553
598
|
? JSON.parse(init.body)
|
|
554
599
|
: init?.body;
|
|
555
600
|
|
|
556
|
-
const url = input
|
|
601
|
+
const url = getRequestUrl(input);
|
|
557
602
|
|
|
558
603
|
// Check if this is a Claude model request
|
|
559
604
|
const modelId = body?.model || "";
|
|
@@ -574,18 +619,34 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
574
619
|
// For Claude models, add thinking_budget to enable reasoning
|
|
575
620
|
// The Copilot API accepts this parameter and returns reasoning_text/reasoning_opaque
|
|
576
621
|
if (isClaudeModel) {
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
// Fix for "Invalid signature in thinking block" error:
|
|
581
|
-
// The Copilot API uses reasoning_text/reasoning_opaque format for thinking
|
|
582
|
-
// When these are passed back without proper signature, it causes errors
|
|
583
|
-
// Solution: Ensure reasoning_opaque is present when reasoning_text exists,
|
|
584
|
-
// or remove reasoning content entirely if signature is invalid/missing
|
|
622
|
+
const thinkingBudget = extractClaudeThinkingBudget(body);
|
|
623
|
+
const isThinkingEnabled = thinkingBudget != null;
|
|
624
|
+
|
|
585
625
|
const cleanedMessages = body.messages.map(
|
|
586
626
|
(msg: any, idx: number) => {
|
|
587
627
|
if (msg.role !== "assistant") return msg;
|
|
588
628
|
|
|
629
|
+
// If thinking is disabled, strip all reasoning metadata to prevent
|
|
630
|
+
// stale reasoning context from continuing across turns.
|
|
631
|
+
if (!isThinkingEnabled) {
|
|
632
|
+
const {
|
|
633
|
+
reasoning_text: _reasoningText,
|
|
634
|
+
reasoning_opaque: _reasoningOpaque,
|
|
635
|
+
...baseMsg
|
|
636
|
+
} = msg;
|
|
637
|
+
if (!Array.isArray(baseMsg.content)) return baseMsg;
|
|
638
|
+
|
|
639
|
+
const cleanedContent = baseMsg.content.filter(
|
|
640
|
+
(part: any) => part.type !== "thinking",
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
...baseMsg,
|
|
645
|
+
content:
|
|
646
|
+
cleanedContent.length > 0 ? cleanedContent : null,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
589
650
|
// Log message structure for debugging
|
|
590
651
|
log("debug", `Processing assistant message ${idx}`, {
|
|
591
652
|
has_reasoning_text: !!msg.reasoning_text,
|
|
@@ -605,11 +666,6 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
605
666
|
}
|
|
606
667
|
|
|
607
668
|
// If content is an array, strip ALL thinking blocks.
|
|
608
|
-
// Reasoning is communicated via reasoning_text/reasoning_opaque
|
|
609
|
-
// fields, not via thinking blocks in the content array.
|
|
610
|
-
// Even thinking blocks WITH signatures can cause
|
|
611
|
-
// "Invalid signature in thinking block" errors when
|
|
612
|
-
// signatures are expired or from a different context.
|
|
613
669
|
if (Array.isArray(msg.content)) {
|
|
614
670
|
const hasThinkingBlock = msg.content.some(
|
|
615
671
|
(part: any) => part.type === "thinking",
|
|
@@ -634,15 +690,31 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
634
690
|
},
|
|
635
691
|
);
|
|
636
692
|
|
|
637
|
-
|
|
638
|
-
...body,
|
|
693
|
+
const nextBody: Record<string, any> = {
|
|
694
|
+
...(modifiedBody || body),
|
|
639
695
|
messages: cleanedMessages,
|
|
640
|
-
thinking_budget: thinkingBudget,
|
|
641
696
|
};
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
thinking_budget
|
|
645
|
-
|
|
697
|
+
|
|
698
|
+
if (isThinkingEnabled) {
|
|
699
|
+
nextBody.thinking_budget = thinkingBudget;
|
|
700
|
+
log("info", `Adding thinking_budget for Claude model`, {
|
|
701
|
+
model: modelId,
|
|
702
|
+
thinking_budget: thinkingBudget,
|
|
703
|
+
});
|
|
704
|
+
} else {
|
|
705
|
+
delete nextBody.thinking_budget;
|
|
706
|
+
log(
|
|
707
|
+
"info",
|
|
708
|
+
`Claude thinking disabled for this request (no thinking budget set)`,
|
|
709
|
+
{ model: modelId },
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Copilot OpenAI-compatible endpoint expects `thinking_budget`.
|
|
714
|
+
// Remove Anthropic-style `thinking` object to avoid mixed payloads.
|
|
715
|
+
delete nextBody.thinking;
|
|
716
|
+
|
|
717
|
+
modifiedBody = nextBody;
|
|
646
718
|
}
|
|
647
719
|
|
|
648
720
|
// For GPT models (o1, gpt-5, etc.), add reasoning parameter
|
|
@@ -839,46 +911,82 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
839
911
|
}
|
|
840
912
|
}
|
|
841
913
|
|
|
842
|
-
|
|
843
|
-
if (currentModel) {
|
|
844
|
-
await shapeRequestForModel(currentModel);
|
|
845
|
-
}
|
|
846
|
-
const response = await fetch(input, activeFinalInit);
|
|
914
|
+
const maxFetchAttempts = 2;
|
|
847
915
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
916
|
+
for (let attempt = 1; attempt <= maxFetchAttempts; attempt++) {
|
|
917
|
+
try {
|
|
918
|
+
if (currentModel) {
|
|
919
|
+
await shapeRequestForModel(currentModel);
|
|
920
|
+
}
|
|
921
|
+
const response = await fetch(input, activeFinalInit);
|
|
852
922
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
);
|
|
923
|
+
if (response.status === 429) {
|
|
924
|
+
try {
|
|
925
|
+
await response.body?.cancel();
|
|
926
|
+
} catch {}
|
|
858
927
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
928
|
+
const retryAfterMs = parseRetryAfter(response);
|
|
929
|
+
const cooldownMs = clampCooldownMs(
|
|
930
|
+
retryAfterMs,
|
|
931
|
+
RATE_LIMIT_CONFIG.defaultCooldownMs,
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
if (currentModel) {
|
|
935
|
+
markModelRateLimited(currentModel, cooldownMs);
|
|
936
|
+
openFamilyCircuitBreaker(currentModel, cooldownMs);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
throw new Error(
|
|
940
|
+
`[Copilot] Rate limited: ${currentModel || "model"} cooling down. Retry in ${formatRetryAfter(Math.ceil(cooldownMs / 1000))}.`,
|
|
941
|
+
);
|
|
862
942
|
}
|
|
863
943
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
944
|
+
// Response transformation is handled by the custom SDK at
|
|
945
|
+
// .opencode/plugin/sdk/copilot/
|
|
946
|
+
return response;
|
|
947
|
+
} catch (error) {
|
|
948
|
+
const errorMessage = getErrorMessage(error);
|
|
949
|
+
if (
|
|
950
|
+
errorMessage.includes("Rate limited") ||
|
|
951
|
+
errorMessage.includes("Local request queue saturated")
|
|
952
|
+
) {
|
|
953
|
+
throw error instanceof Error
|
|
954
|
+
? error
|
|
955
|
+
: new Error(errorMessage);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (
|
|
959
|
+
attempt < maxFetchAttempts &&
|
|
960
|
+
isTransientUpstreamTimeoutError(error)
|
|
961
|
+
) {
|
|
962
|
+
const retryDelayMs = 750 * attempt;
|
|
963
|
+
log(
|
|
964
|
+
"warn",
|
|
965
|
+
`Transient upstream timeout from Copilot, retrying request`,
|
|
966
|
+
{
|
|
967
|
+
model: currentModel || undefined,
|
|
968
|
+
attempt,
|
|
969
|
+
retry_delay_ms: retryDelayMs,
|
|
970
|
+
error: errorMessage,
|
|
971
|
+
},
|
|
972
|
+
);
|
|
973
|
+
await sleep(retryDelayMs);
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
868
976
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
lastError.message.includes("Local request queue saturated")
|
|
877
|
-
) {
|
|
878
|
-
throw lastError;
|
|
977
|
+
if (isTransientUpstreamTimeoutError(error)) {
|
|
978
|
+
throw new Error(
|
|
979
|
+
`[Copilot] Upstream idle timeout while streaming ${currentModel || "request"}. Retry with a lower thinking budget or switch to a lower-latency Claude variant.`,
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
throw error;
|
|
879
984
|
}
|
|
880
|
-
throw error;
|
|
881
985
|
}
|
|
986
|
+
|
|
987
|
+
throw new Error(
|
|
988
|
+
`[Copilot] Failed request after ${maxFetchAttempts} attempts.`,
|
|
989
|
+
);
|
|
882
990
|
},
|
|
883
991
|
};
|
|
884
992
|
},
|