opencodekit 0.21.6 → 0.21.8

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
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  //#endregion
22
22
  //#region package.json
23
- var version = "0.21.6";
23
+ var version = "0.21.8";
24
24
 
25
25
  //#endregion
26
26
  //#region src/utils/license.ts
Binary file
@@ -600,9 +600,12 @@ export const CopilotAuthPlugin: Plugin = async ({ client: sdk, directory }) => {
600
600
  continue;
601
601
  }
602
602
 
603
- // Route OpenAI-compatible Copilot models through the local file-based
604
- // SDK so GPT/Gemini keep the reasoning_opaque and Responses fixes.
605
- model.api.npm = localCopilotSdk;
603
+ // Route OpenAI-compatible Copilot models through the bundled
604
+ // @ai-sdk/github-copilot SDK. This SDK is shipped with OpenCode and
605
+ // supports both /chat/completions (gpt-4*, older) and /v1/responses
606
+ // (gpt-5.x reasoning models), so projects without local @ai-sdk/*
607
+ // dependencies can initialize the provider without ProviderInitError.
608
+ model.api.npm = "@ai-sdk/github-copilot";
606
609
  }
607
610
  }
608
611
 
@@ -42,43 +42,115 @@ interface HookDeps {
42
42
  * Handles: Error instances, objects with .message, strings, and arbitrary objects.
43
43
  * Never returns "[object Object]".
44
44
  */
45
+ function truncate(value: string, maxLen: number): string {
46
+ return value.length > maxLen ? `${value.slice(0, maxLen - 1)}…` : value;
47
+ }
48
+
49
+ function isRecord(value: unknown): value is Record<string, unknown> {
50
+ return typeof value === "object" && value !== null;
51
+ }
52
+
53
+ function addDetail(parts: string[], seen: Set<string>, value: unknown) {
54
+ if (typeof value !== "string") return;
55
+ const detail = value.trim();
56
+ if (!detail || seen.has(detail)) return;
57
+ seen.add(detail);
58
+ parts.push(detail);
59
+ }
60
+
61
+ function collectErrorDetails(
62
+ value: unknown,
63
+ parts: string[],
64
+ seen: Set<string>,
65
+ depth = 0,
66
+ ) {
67
+ if (!value || depth > 2) return;
68
+ if (typeof value === "string") {
69
+ addDetail(parts, seen, value);
70
+ return;
71
+ }
72
+ if (value instanceof Error) {
73
+ addDetail(parts, seen, value.message || value.name);
74
+ collectErrorDetails(value.cause, parts, seen, depth + 1);
75
+ return;
76
+ }
77
+ if (!isRecord(value)) return;
78
+
79
+ addDetail(parts, seen, value.message);
80
+ addDetail(parts, seen, value.name);
81
+ addDetail(parts, seen, value.code);
82
+ addDetail(parts, seen, value.providerID);
83
+ addDetail(parts, seen, value.modelID);
84
+
85
+ collectErrorDetails(value.data, parts, seen, depth + 1);
86
+ collectErrorDetails(value.error, parts, seen, depth + 1);
87
+ collectErrorDetails(value.cause, parts, seen, depth + 1);
88
+ }
89
+
90
+ function safeStringifyError(value: unknown, maxLen = 1000): string {
91
+ try {
92
+ const json = JSON.stringify(value, (key, nestedValue) => {
93
+ if (/authorization|api.?key|token|secret|password/i.test(key)) {
94
+ return "[redacted]";
95
+ }
96
+ if (nestedValue instanceof Error) {
97
+ return {
98
+ name: nestedValue.name,
99
+ message: nestedValue.message,
100
+ cause: nestedValue.cause,
101
+ };
102
+ }
103
+ return nestedValue;
104
+ });
105
+ return truncate(json, maxLen);
106
+ } catch {
107
+ return "Non-serializable error object";
108
+ }
109
+ }
110
+
45
111
  function extractErrorMessage(value: unknown, maxLen = 200): string {
46
112
  if (!value) return "Unknown error";
47
- if (typeof value === "string") return value.slice(0, maxLen);
113
+ if (typeof value === "string") return truncate(value, maxLen);
48
114
  if (value instanceof Error)
49
- return (value.message || value.name || "Error").slice(0, maxLen);
50
- if (typeof value === "object" && value !== null) {
51
- const obj = value as Record<string, unknown>;
115
+ return truncate(value.message || value.name || "Error", maxLen);
116
+ if (isRecord(value)) {
117
+ const obj = value;
118
+ const parts: string[] = [];
119
+ const seen = new Set<string>();
120
+ collectErrorDetails(value, parts, seen);
52
121
 
53
122
  // Handle OpenCode error structure: { name, data: { message, statusCode } }
54
- if (typeof obj.data === "object" && obj.data !== null) {
55
- const data = obj.data as Record<string, unknown>;
123
+ if (isRecord(obj.data)) {
124
+ const data = obj.data;
56
125
  if (typeof data.message === "string") {
57
126
  const prefix = typeof obj.name === "string" ? `${obj.name}: ` : "";
58
127
  const status =
59
128
  typeof data.statusCode === "number" ? ` (${data.statusCode})` : "";
60
- return `${prefix}${data.message}${status}`.slice(0, maxLen);
129
+ const cause = parts
130
+ .filter((part) => part !== obj.name && part !== data.message)
131
+ .join(" — ");
132
+ return truncate(
133
+ `${prefix}${data.message}${status}${cause ? ` — ${cause}` : ""}`,
134
+ maxLen,
135
+ );
61
136
  }
62
137
  }
63
138
 
139
+ if (parts.length > 0) return truncate(parts.join(" — "), maxLen);
140
+
64
141
  // Common error shapes: { message }, { error }, { error: { message } }
65
- if (typeof obj.message === "string") return obj.message.slice(0, maxLen);
66
- if (typeof obj.error === "string") return obj.error.slice(0, maxLen);
67
- if (typeof obj.error === "object" && obj.error !== null) {
68
- const inner = obj.error as Record<string, unknown>;
142
+ if (typeof obj.message === "string") return truncate(obj.message, maxLen);
143
+ if (typeof obj.error === "string") return truncate(obj.error, maxLen);
144
+ if (isRecord(obj.error)) {
145
+ const inner = obj.error;
69
146
  if (typeof inner.message === "string")
70
- return inner.message.slice(0, maxLen);
147
+ return truncate(inner.message, maxLen);
71
148
  }
72
149
  // Last resort: JSON stringify with truncation
73
- try {
74
- const json = JSON.stringify(value);
75
- return json.slice(0, maxLen);
76
- } catch {
77
- return "Non-serializable error object";
78
- }
150
+ return safeStringifyError(value, maxLen);
79
151
  }
80
152
  // number, boolean, symbol, etc.
81
- return String(value).slice(0, maxLen);
153
+ return truncate(String(value), maxLen);
82
154
  }
83
155
 
84
156
  export function createHooks(deps: HookDeps) {
@@ -153,9 +225,12 @@ export function createHooks(deps: HookDeps) {
153
225
  // Extract status code from error object for classification
154
226
  const rawCode =
155
227
  typeof errorObj === "object" && errorObj !== null
156
- ? ((errorObj as Record<string, unknown>).data as Record<string, unknown>
157
- )?.statusCode ??
158
- (errorObj as Record<string, unknown>).statusCode
228
+ ? ((
229
+ (errorObj as Record<string, unknown>).data as Record<
230
+ string,
231
+ unknown
232
+ >
233
+ )?.statusCode ?? (errorObj as Record<string, unknown>).statusCode)
159
234
  : undefined;
160
235
  const statusCode =
161
236
  typeof rawCode === "number"
@@ -164,12 +239,19 @@ export function createHooks(deps: HookDeps) {
164
239
  ? Number(rawCode) || undefined
165
240
  : undefined;
166
241
 
167
- // Log full error for debugging
168
- await log(`Session error: ${errorMsg}`, "warn");
242
+ // Log structured error details for debugging. Toasts stay short; logs carry
243
+ // the payload needed to trace provider/config failures.
244
+ await log(
245
+ `Session error: ${errorMsg}; payload=${safeStringifyError(errorObj)}`,
246
+ "warn",
247
+ );
169
248
 
170
249
  // Classify error and provide specific guidance
171
250
  let guidance: string;
172
- if (
251
+ if (/ProviderInitError/i.test(errorMsg)) {
252
+ guidance =
253
+ "Provider initialization failed — check .opencode/plugin provider config and run with --print-logs --log-level DEBUG";
254
+ } else if (
173
255
  /token.{0,20}(exceed|limit)/i.test(errorMsg) ||
174
256
  errorMsg.includes("context_length_exceeded")
175
257
  ) {
@@ -190,18 +272,18 @@ export function createHooks(deps: HookDeps) {
190
272
  /bad request|invalid.*request/i.test(errorMsg)
191
273
  ) {
192
274
  // Sub-classify 400 errors for more specific guidance
193
- if (
194
- /thinking.?block|invalid.*signature|reasoning/i.test(errorMsg)
195
- ) {
196
- guidance =
197
- "Thinking block error — start a new session to reset";
275
+ if (/thinking.?block|invalid.*signature|reasoning/i.test(errorMsg)) {
276
+ guidance = "Thinking block error — start a new session to reset";
198
277
  } else if (
199
- /context.*(too|exceed|length|large|limit)|too.?long|max.?length|content.?length/i.test(errorMsg)
278
+ /context.*(too|exceed|length|large|limit)|too.?long|max.?length|content.?length/i.test(
279
+ errorMsg,
280
+ )
200
281
  ) {
201
- guidance =
202
- "Request too large — use /compact to reduce context";
282
+ guidance = "Request too large — use /compact to reduce context";
203
283
  } else if (
204
- /invalid.*\bid\b|item.*\bid\b|unknown.*\bid\b|malformed.*\bid\b/i.test(errorMsg)
284
+ /invalid.*\bid\b|item.*\bid\b|unknown.*\bid\b|malformed.*\bid\b/i.test(
285
+ errorMsg,
286
+ )
205
287
  ) {
206
288
  guidance = "API format error — start a new session";
207
289
  } else {
@@ -212,9 +294,7 @@ export function createHooks(deps: HookDeps) {
212
294
  /timeout|ETIMEDOUT|ECONNRESET|network|fetch failed/i.test(errorMsg)
213
295
  ) {
214
296
  guidance = "Network error — check connection and retry";
215
- } else if (
216
- /invalid.*signature|thinking block/i.test(errorMsg)
217
- ) {
297
+ } else if (/invalid.*signature|thinking block/i.test(errorMsg)) {
218
298
  guidance = "API format error — try starting a new session";
219
299
  } else if (
220
300
  statusCode === 500 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.21.6",
3
+ "version": "0.21.8",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "keywords": [
6
6
  "agents",