opencodekit 0.18.23 → 0.18.25
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 +1 -1
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/copilot-auth.ts +111 -0
- package/dist/template/.opencode/plugin/lib/memory-hooks.ts +69 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -921,6 +921,7 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
921
921
|
let fallbacksUsed = 0;
|
|
922
922
|
let attempt = 0;
|
|
923
923
|
let recoveryCyclesUsed = 0;
|
|
924
|
+
let attempted400Recovery = false;
|
|
924
925
|
|
|
925
926
|
while (attempt <= RATE_LIMIT_CONFIG.maxRetries) {
|
|
926
927
|
try {
|
|
@@ -1067,6 +1068,116 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk }) => {
|
|
|
1067
1068
|
);
|
|
1068
1069
|
}
|
|
1069
1070
|
|
|
1071
|
+
// Handle 400 Bad Request with auto-recovery
|
|
1072
|
+
if (response.status === 400 && !attempted400Recovery) {
|
|
1073
|
+
let errorDetail = "Bad Request";
|
|
1074
|
+
try {
|
|
1075
|
+
const clonedResponse = response.clone();
|
|
1076
|
+
const errorBody = await clonedResponse.json();
|
|
1077
|
+
errorDetail =
|
|
1078
|
+
errorBody?.error?.message ||
|
|
1079
|
+
errorBody?.message ||
|
|
1080
|
+
"Bad Request";
|
|
1081
|
+
} catch {}
|
|
1082
|
+
|
|
1083
|
+
log(
|
|
1084
|
+
"warn",
|
|
1085
|
+
`[400-RECOVERY] Bad Request from Copilot API`,
|
|
1086
|
+
{
|
|
1087
|
+
model: currentModel,
|
|
1088
|
+
error_detail: errorDetail,
|
|
1089
|
+
attempt,
|
|
1090
|
+
},
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
// Check for recoverable 400 causes
|
|
1094
|
+
const isThinkingBlockError =
|
|
1095
|
+
/thinking.?block|invalid.*signature|reasoning.*invalid/i.test(
|
|
1096
|
+
errorDetail,
|
|
1097
|
+
);
|
|
1098
|
+
const isIdError =
|
|
1099
|
+
/invalid.*\bid\b|item.*\bid\b|unknown.*\bid\b|malformed.*\bid\b/i.test(
|
|
1100
|
+
errorDetail,
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
if (isThinkingBlockError || isIdError) {
|
|
1104
|
+
let bodyObj: any;
|
|
1105
|
+
try {
|
|
1106
|
+
bodyObj =
|
|
1107
|
+
typeof activeFinalInit.body === "string"
|
|
1108
|
+
? JSON.parse(activeFinalInit.body)
|
|
1109
|
+
: activeFinalInit.body;
|
|
1110
|
+
} catch {
|
|
1111
|
+
// Can't parse body — not recoverable
|
|
1112
|
+
log(
|
|
1113
|
+
"warn",
|
|
1114
|
+
`[400-RECOVERY] Cannot parse request body, giving up`,
|
|
1115
|
+
);
|
|
1116
|
+
return response;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Cancel original response body only after confirming we can recover
|
|
1120
|
+
try {
|
|
1121
|
+
await response.body?.cancel();
|
|
1122
|
+
} catch {}
|
|
1123
|
+
|
|
1124
|
+
if (isThinkingBlockError && bodyObj?.messages) {
|
|
1125
|
+
// Strip ALL thinking/reasoning content aggressively
|
|
1126
|
+
bodyObj.messages = bodyObj.messages.map(
|
|
1127
|
+
(msg: any) => {
|
|
1128
|
+
if (msg.role !== "assistant") return msg;
|
|
1129
|
+
const {
|
|
1130
|
+
reasoning_text: _rt,
|
|
1131
|
+
reasoning_opaque: _ro,
|
|
1132
|
+
...cleaned
|
|
1133
|
+
} = msg;
|
|
1134
|
+
if (Array.isArray(cleaned.content)) {
|
|
1135
|
+
cleaned.content = cleaned.content.filter(
|
|
1136
|
+
(part: any) => part.type !== "thinking",
|
|
1137
|
+
);
|
|
1138
|
+
if (cleaned.content.length === 0)
|
|
1139
|
+
cleaned.content = null;
|
|
1140
|
+
}
|
|
1141
|
+
return cleaned;
|
|
1142
|
+
},
|
|
1143
|
+
);
|
|
1144
|
+
delete bodyObj.thinking_budget;
|
|
1145
|
+
recovered = true;
|
|
1146
|
+
log(
|
|
1147
|
+
"info",
|
|
1148
|
+
`[400-RECOVERY] Stripped all thinking/reasoning content for retry`,
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (isIdError && bodyObj?.input) {
|
|
1153
|
+
bodyObj.input = sanitizeResponseInputIds(
|
|
1154
|
+
bodyObj.input,
|
|
1155
|
+
);
|
|
1156
|
+
recovered = true;
|
|
1157
|
+
log(
|
|
1158
|
+
"info",
|
|
1159
|
+
`[400-RECOVERY] Re-sanitized Responses API IDs for retry`,
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (recovered) {
|
|
1164
|
+
attempted400Recovery = true;
|
|
1165
|
+
activeFinalInit = {
|
|
1166
|
+
...activeFinalInit,
|
|
1167
|
+
body: JSON.stringify(bodyObj),
|
|
1168
|
+
};
|
|
1169
|
+
attempt++;
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Not recoverable — log detail and return original response
|
|
1175
|
+
log(
|
|
1176
|
+
"warn",
|
|
1177
|
+
`[400-RECOVERY] Non-recoverable 400: ${errorDetail}`,
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1070
1181
|
// Response transformation is handled by the custom SDK at
|
|
1071
1182
|
// .opencode/plugin/sdk/copilot/
|
|
1072
1183
|
return response;
|
|
@@ -43,15 +43,29 @@ interface HookDeps {
|
|
|
43
43
|
function extractErrorMessage(value: unknown, maxLen = 200): string {
|
|
44
44
|
if (!value) return "Unknown error";
|
|
45
45
|
if (typeof value === "string") return value.slice(0, maxLen);
|
|
46
|
-
if (value instanceof Error)
|
|
46
|
+
if (value instanceof Error)
|
|
47
|
+
return (value.message || value.name || "Error").slice(0, maxLen);
|
|
47
48
|
if (typeof value === "object" && value !== null) {
|
|
48
49
|
const obj = value as Record<string, unknown>;
|
|
50
|
+
|
|
51
|
+
// Handle OpenCode error structure: { name, data: { message, statusCode } }
|
|
52
|
+
if (typeof obj.data === "object" && obj.data !== null) {
|
|
53
|
+
const data = obj.data as Record<string, unknown>;
|
|
54
|
+
if (typeof data.message === "string") {
|
|
55
|
+
const prefix = typeof obj.name === "string" ? `${obj.name}: ` : "";
|
|
56
|
+
const status =
|
|
57
|
+
typeof data.statusCode === "number" ? ` (${data.statusCode})` : "";
|
|
58
|
+
return `${prefix}${data.message}${status}`.slice(0, maxLen);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
// Common error shapes: { message }, { error }, { error: { message } }
|
|
50
63
|
if (typeof obj.message === "string") return obj.message.slice(0, maxLen);
|
|
51
64
|
if (typeof obj.error === "string") return obj.error.slice(0, maxLen);
|
|
52
65
|
if (typeof obj.error === "object" && obj.error !== null) {
|
|
53
66
|
const inner = obj.error as Record<string, unknown>;
|
|
54
|
-
if (typeof inner.message === "string")
|
|
67
|
+
if (typeof inner.message === "string")
|
|
68
|
+
return inner.message.slice(0, maxLen);
|
|
55
69
|
}
|
|
56
70
|
// Last resort: JSON stringify with truncation
|
|
57
71
|
try {
|
|
@@ -123,7 +137,22 @@ export function createHooks(deps: HookDeps) {
|
|
|
123
137
|
// --- Session error: classify and guide ---
|
|
124
138
|
if (event.type === "session.error") {
|
|
125
139
|
const props = event.properties as Record<string, unknown> | undefined;
|
|
126
|
-
const
|
|
140
|
+
const errorObj = props?.error ?? props?.message ?? "Unknown error";
|
|
141
|
+
const errorMsg = extractErrorMessage(errorObj);
|
|
142
|
+
|
|
143
|
+
// Extract status code from error object for classification
|
|
144
|
+
const rawCode =
|
|
145
|
+
typeof errorObj === "object" && errorObj !== null
|
|
146
|
+
? ((errorObj as Record<string, unknown>).data as Record<string, unknown>
|
|
147
|
+
)?.statusCode ??
|
|
148
|
+
(errorObj as Record<string, unknown>).statusCode
|
|
149
|
+
: undefined;
|
|
150
|
+
const statusCode =
|
|
151
|
+
typeof rawCode === "number"
|
|
152
|
+
? rawCode
|
|
153
|
+
: typeof rawCode === "string"
|
|
154
|
+
? Number(rawCode) || undefined
|
|
155
|
+
: undefined;
|
|
127
156
|
|
|
128
157
|
// Log full error for debugging
|
|
129
158
|
await log(`Session error: ${errorMsg}`, "warn");
|
|
@@ -136,13 +165,39 @@ export function createHooks(deps: HookDeps) {
|
|
|
136
165
|
) {
|
|
137
166
|
guidance = "Context too large — use /compact or start a new session";
|
|
138
167
|
} else if (
|
|
139
|
-
/rate.?limit|
|
|
168
|
+
/rate.?limit|too many requests/i.test(errorMsg) ||
|
|
169
|
+
statusCode === 429
|
|
140
170
|
) {
|
|
141
171
|
guidance = "Rate limited — wait a moment and retry";
|
|
142
172
|
} else if (
|
|
143
|
-
/unauthorized|
|
|
173
|
+
/unauthorized|auth/i.test(errorMsg) ||
|
|
174
|
+
statusCode === 401 ||
|
|
175
|
+
statusCode === 403
|
|
144
176
|
) {
|
|
145
177
|
guidance = "Auth error — check API key or token";
|
|
178
|
+
} else if (
|
|
179
|
+
statusCode === 400 ||
|
|
180
|
+
/bad request|invalid.*request/i.test(errorMsg)
|
|
181
|
+
) {
|
|
182
|
+
// Sub-classify 400 errors for more specific guidance
|
|
183
|
+
if (
|
|
184
|
+
/thinking.?block|invalid.*signature|reasoning/i.test(errorMsg)
|
|
185
|
+
) {
|
|
186
|
+
guidance =
|
|
187
|
+
"Thinking block error — start a new session to reset";
|
|
188
|
+
} else if (
|
|
189
|
+
/context.*(too|exceed|length|large|limit)|too.?long|max.?length|content.?length/i.test(errorMsg)
|
|
190
|
+
) {
|
|
191
|
+
guidance =
|
|
192
|
+
"Request too large — use /compact to reduce context";
|
|
193
|
+
} else if (
|
|
194
|
+
/invalid.*\bid\b|item.*\bid\b|unknown.*\bid\b|malformed.*\bid\b/i.test(errorMsg)
|
|
195
|
+
) {
|
|
196
|
+
guidance = "API format error — start a new session";
|
|
197
|
+
} else {
|
|
198
|
+
guidance =
|
|
199
|
+
"Bad request — try starting a new session or using /compact";
|
|
200
|
+
}
|
|
146
201
|
} else if (
|
|
147
202
|
/timeout|ETIMEDOUT|ECONNRESET|network|fetch failed/i.test(errorMsg)
|
|
148
203
|
) {
|
|
@@ -152,14 +207,20 @@ export function createHooks(deps: HookDeps) {
|
|
|
152
207
|
) {
|
|
153
208
|
guidance = "API format error — try starting a new session";
|
|
154
209
|
} else if (
|
|
155
|
-
|
|
210
|
+
statusCode === 500 ||
|
|
211
|
+
statusCode === 502 ||
|
|
212
|
+
statusCode === 503 ||
|
|
213
|
+
statusCode === 504 ||
|
|
214
|
+
/internal server|service unavailable/i.test(errorMsg)
|
|
156
215
|
) {
|
|
157
216
|
guidance = "Server error — retry in a few seconds";
|
|
158
217
|
} else {
|
|
159
|
-
guidance =
|
|
218
|
+
guidance =
|
|
219
|
+
"Unexpected error — save work with observation tool if needed";
|
|
160
220
|
}
|
|
161
221
|
|
|
162
|
-
const short =
|
|
222
|
+
const short =
|
|
223
|
+
errorMsg.length > 100 ? `${errorMsg.slice(0, 100)}…` : errorMsg;
|
|
163
224
|
await showToast("Session Error", `${guidance} (${short})`, "warning");
|
|
164
225
|
}
|
|
165
226
|
},
|