opencodekit 0.15.11 → 0.15.12

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
@@ -750,7 +750,7 @@ var cac = (name = "") => new CAC(name);
750
750
  // package.json
751
751
  var package_default = {
752
752
  name: "opencodekit",
753
- version: "0.15.11",
753
+ version: "0.15.12",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  keywords: ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
756
756
  license: "MIT",
@@ -11,7 +11,7 @@
11
11
  "type-check": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@opencode-ai/plugin": "1.1.36"
14
+ "@opencode-ai/plugin": "1.1.39"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/node": "^25.0.3",
@@ -5,7 +5,11 @@
5
5
 
6
6
  import type { Plugin } from "@opencode-ai/plugin";
7
7
 
8
- const CLIENT_ID = "Iv1.b507a08c87ecfe98";
8
+ const CLIENT_ID = "Ov23li8tweQw6odWQebz";
9
+
10
+ // Add a small safety buffer when polling to avoid hitting the server
11
+ // slightly too early due to clock skew / timer drift.
12
+ const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000; // 3 seconds
9
13
 
10
14
  const HEADERS = {
11
15
  "User-Agent": "GitHubCopilotChat/0.35.0",
@@ -40,11 +44,12 @@ function getUrls(domain: string) {
40
44
  return {
41
45
  DEVICE_CODE_URL: `https://${domain}/login/device/code`,
42
46
  ACCESS_TOKEN_URL: `https://${domain}/login/oauth/access_token`,
43
- COPILOT_API_KEY_URL: `https://api.github.com/copilot_internal/v2/token`,
44
47
  };
45
48
  }
46
49
 
47
- export const CopilotAuthPlugin: Plugin = async ({ client }) => {
50
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
51
+
52
+ export const CopilotAuthPlugin: Plugin = async ({ client: _client }) => {
48
53
  return {
49
54
  auth: {
50
55
  provider: "github-copilot",
@@ -52,6 +57,11 @@ export const CopilotAuthPlugin: Plugin = async ({ client }) => {
52
57
  const info = await getAuth();
53
58
  if (!info || info.type !== "oauth") return {};
54
59
 
60
+ const enterpriseUrl = info.enterpriseUrl;
61
+ const baseURL = enterpriseUrl
62
+ ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}`
63
+ : undefined;
64
+
55
65
  if (provider && provider.models) {
56
66
  for (const model of Object.values(provider.models)) {
57
67
  model.cost = {
@@ -62,71 +72,47 @@ export const CopilotAuthPlugin: Plugin = async ({ client }) => {
62
72
  write: 0,
63
73
  },
64
74
  };
75
+
76
+ // Sync with official: Handle Claude routing and SDK mapping
77
+ const base =
78
+ baseURL ?? model.api.url ?? "https://api.githubcopilot.com";
79
+ const isClaude = model.id.includes("claude");
80
+
81
+ let url = base;
82
+ if (isClaude) {
83
+ if (!url.endsWith("/v1")) {
84
+ url = url.endsWith("/") ? `${url}v1` : `${url}/v1`;
85
+ }
86
+ }
87
+
88
+ model.api.url = url;
89
+ model.api.npm = isClaude
90
+ ? "@ai-sdk/anthropic"
91
+ : "@ai-sdk/github-copilot";
65
92
  }
66
93
  }
67
94
 
68
- const enterpriseUrl = info.enterpriseUrl;
69
- const baseURL = enterpriseUrl
70
- ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}`
71
- : "https://api.githubcopilot.com";
72
-
73
95
  return {
74
- baseURL,
75
96
  apiKey: "",
76
97
  async fetch(input, init) {
77
98
  const info = await getAuth();
78
- if (info.type !== "oauth") return {};
79
- if (!info.access) {
80
- const domain = info.enterpriseUrl
81
- ? normalizeDomain(info.enterpriseUrl)
82
- : "github.com";
83
- const urls = getUrls(domain);
84
-
85
- const response = await fetch(urls.COPILOT_API_KEY_URL, {
86
- headers: {
87
- Accept: "application/json",
88
- Authorization: `Bearer ${info.refresh}`,
89
- ...HEADERS,
90
- },
91
- });
92
-
93
- if (!response.ok) {
94
- throw new Error(`Token refresh failed: ${response.status}`);
95
- }
96
-
97
- const tokenData = await response.json();
98
-
99
- const saveProviderID = info.enterpriseUrl
100
- ? "github-copilot-enterprise"
101
- : "github-copilot";
102
- await client.auth.set({
103
- path: {
104
- id: saveProviderID,
105
- },
106
- body: {
107
- type: "oauth",
108
- refresh: info.refresh,
109
- access: tokenData.token,
110
- expires: tokenData.expires_at * 1000 - 5 * 60 * 1000,
111
- ...(info.enterpriseUrl && {
112
- enterpriseUrl: info.enterpriseUrl,
113
- }),
114
- },
115
- });
116
- info.access = tokenData.token;
117
- }
99
+ if (info.type !== "oauth") return fetch(input, init);
118
100
 
119
101
  let isAgentCall = false;
120
102
  let isVisionRequest = false;
121
103
  try {
122
104
  const body =
123
- typeof init.body === "string"
105
+ typeof init?.body === "string"
124
106
  ? JSON.parse(init.body)
125
- : init.body;
126
- if (body?.messages) {
127
- isAgentCall = body.messages.some(
128
- (msg: any) =>
129
- msg.role && ["tool", "assistant"].includes(msg.role),
107
+ : init?.body;
108
+
109
+ const url = input.toString();
110
+
111
+ // Completions API
112
+ if (body?.messages && url.includes("completions")) {
113
+ // Keep local logic: detect if any message is assistant/tool
114
+ isAgentCall = body.messages.some((msg: any) =>
115
+ ["tool", "assistant"].includes(msg.role),
130
116
  );
131
117
  isVisionRequest = body.messages.some(
132
118
  (msg: any) =>
@@ -135,34 +121,58 @@ export const CopilotAuthPlugin: Plugin = async ({ client }) => {
135
121
  );
136
122
  }
137
123
 
124
+ // Responses API
138
125
  if (body?.input) {
139
- const lastInput = body.input[body.input.length - 1];
140
-
141
- const isAssistant = lastInput?.role === "assistant";
142
- const hasAgentType = lastInput?.type
143
- ? RESPONSES_API_ALTERNATE_INPUT_TYPES.includes(lastInput.type)
144
- : false;
145
- isAgentCall = isAssistant || hasAgentType;
146
-
147
- isVisionRequest =
148
- Array.isArray(lastInput?.content) &&
149
- lastInput.content.some(
150
- (part: any) => part.type === "input_image",
151
- );
126
+ isAgentCall = body.input.some(
127
+ (item: any) =>
128
+ item?.role === "assistant" ||
129
+ (item?.type &&
130
+ RESPONSES_API_ALTERNATE_INPUT_TYPES.includes(item.type)),
131
+ );
132
+
133
+ isVisionRequest = body.input.some(
134
+ (item: any) =>
135
+ Array.isArray(item?.content) &&
136
+ item.content.some(
137
+ (part: any) => part.type === "input_image",
138
+ ),
139
+ );
140
+ }
141
+
142
+ // Messages API (Anthropic style)
143
+ if (body?.messages && !url.includes("completions")) {
144
+ isAgentCall = body.messages.some((msg: any) =>
145
+ ["tool", "assistant"].includes(msg.role),
146
+ );
147
+ isVisionRequest = body.messages.some(
148
+ (item: any) =>
149
+ Array.isArray(item?.content) &&
150
+ item.content.some(
151
+ (part: any) =>
152
+ part?.type === "image" ||
153
+ (part?.type === "tool_result" &&
154
+ Array.isArray(part?.content) &&
155
+ part.content.some(
156
+ (nested: any) => nested?.type === "image",
157
+ )),
158
+ ),
159
+ );
152
160
  }
153
161
  } catch {}
154
162
 
155
- const headers = {
156
- ...init.headers,
163
+ const headers: Record<string, string> = {
164
+ "x-initiator": isAgentCall ? "agent" : "user",
165
+ ...(init?.headers as Record<string, string>),
157
166
  ...HEADERS,
158
- Authorization: `Bearer ${info.access}`,
167
+ Authorization: `Bearer ${info.refresh}`,
159
168
  "Openai-Intent": "conversation-edits",
160
- "X-Initiator": isAgentCall ? "agent" : "user",
161
169
  };
170
+
162
171
  if (isVisionRequest) {
163
172
  headers["Copilot-Vision-Request"] = "true";
164
173
  }
165
174
 
175
+ // Official only deletes lowercase "authorization"
166
176
  delete headers["x-api-key"];
167
177
  delete headers["authorization"];
168
178
 
@@ -286,7 +296,7 @@ export const CopilotAuthPlugin: Plugin = async ({ client }) => {
286
296
  } = {
287
297
  type: "success",
288
298
  refresh: data.access_token,
289
- access: "",
299
+ access: data.access_token,
290
300
  expires: 0,
291
301
  };
292
302
 
@@ -299,16 +309,33 @@ export const CopilotAuthPlugin: Plugin = async ({ client }) => {
299
309
  }
300
310
 
301
311
  if (data.error === "authorization_pending") {
302
- await new Promise((resolve) =>
303
- setTimeout(resolve, deviceData.interval * 1000),
312
+ await sleep(
313
+ deviceData.interval * 1000 +
314
+ OAUTH_POLLING_SAFETY_MARGIN_MS,
304
315
  );
305
316
  continue;
306
317
  }
307
318
 
319
+ if (data.error === "slow_down") {
320
+ // Based on the RFC spec, we must add 5 seconds to our current polling interval.
321
+ let newInterval = (deviceData.interval + 5) * 1000;
322
+
323
+ if (
324
+ data.interval &&
325
+ typeof data.interval === "number" &&
326
+ data.interval > 0
327
+ ) {
328
+ newInterval = data.interval * 1000;
329
+ }
330
+
331
+ await sleep(newInterval + OAUTH_POLLING_SAFETY_MARGIN_MS);
332
+ continue;
333
+ }
334
+
308
335
  if (data.error) return { type: "failed" };
309
336
 
310
- await new Promise((resolve) =>
311
- setTimeout(resolve, deviceData.interval * 1000),
337
+ await sleep(
338
+ deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS,
312
339
  );
313
340
  continue;
314
341
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.15.11",
3
+ "version": "0.15.12",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "keywords": ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
6
6
  "license": "MIT",