opencode-kilocode-auth 1.0.5 → 1.0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/plugin.ts +79 -13
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-kilocode-auth",
3
3
  "module": "index.ts",
4
- "version": "1.0.5",
4
+ "version": "1.0.7",
5
5
  "author": "ported from Kilo Code",
6
6
  "description": "OpenCode plugin for Kilo Code authentication with support for various models including Giga Potato",
7
7
  "files": [
package/src/plugin.ts CHANGED
@@ -2,11 +2,15 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin"
2
2
  import {
3
3
  KILOCODE_PROVIDER_ID,
4
4
  DEFAULT_MODEL_ID,
5
+ DEVICE_AUTH_POLL_INTERVAL_MS,
5
6
  } from "./constants"
6
7
  import {
8
+ initiateDeviceAuth,
9
+ pollDeviceAuth,
7
10
  getKilocodeProfile,
8
11
  getKilocodeDefaultModel,
9
12
  getKilocodeModels,
13
+ openBrowserUrl,
10
14
  getApiUrl,
11
15
  } from "./kilocode/auth"
12
16
 
@@ -76,7 +80,7 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
76
80
 
77
81
  const currentKilocodeAuth = currentAuth as KilocodeAuth
78
82
  const currentToken = currentKilocodeAuth.type === "api"
79
- ? currentKilocodeAuth.apiKey
83
+ ? currentKilocodeAuth.key
80
84
  : currentKilocodeAuth.refresh
81
85
 
82
86
  if (!currentToken) {
@@ -99,18 +103,22 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
99
103
  }
100
104
  }
101
105
 
102
- // Set Kilo Code authorization and required headers
106
+ // EXACT headers from Kilo Code VSCode extension (DEFAULT_HEADERS + customRequestOptions)
107
+ // Authorization is set by OpenAI SDK internally
103
108
  headers.set("Authorization", `Bearer ${currentToken}`)
104
- headers.set("x-api-key", currentToken) // Kilo Code uses both
109
+
110
+ // DEFAULT_HEADERS from kilocode/src/api/providers/constants.ts
105
111
  headers.set("HTTP-Referer", "https://kilocode.ai")
106
112
  headers.set("X-Title", "Kilo Code")
107
113
  headers.set("X-KiloCode-Version", "4.151.0")
108
114
  headers.set("User-Agent", "Kilo-Code/4.151.0")
115
+
116
+ // customRequestOptions from KilocodeOpenrouterHandler
109
117
  headers.set("X-KiloCode-EditorName", "Visual Studio Code 1.96.0")
110
118
 
111
- // Add organization header if present
119
+ // Organization header (exact casing from headers.ts)
112
120
  if (currentKilocodeAuth.organizationId) {
113
- headers.set("X-KILOCODE-ORGANIZATIONID", currentKilocodeAuth.organizationId)
121
+ headers.set("X-KiloCode-OrganizationId", currentKilocodeAuth.organizationId)
114
122
  }
115
123
 
116
124
  // Rewrite URL to Kilo Code endpoint
@@ -139,7 +147,72 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
139
147
  },
140
148
 
141
149
  methods: [
142
- // API key is the primary method - get it from https://app.kilo.ai
150
+ {
151
+ type: "oauth",
152
+ label: "Login with Kilo Code (Device Auth)",
153
+ async authorize() {
154
+ const authData = await initiateDeviceAuth()
155
+ const { code, verificationUrl, expiresIn } = authData
156
+
157
+ openBrowserUrl(verificationUrl)
158
+
159
+ return {
160
+ url: verificationUrl,
161
+ instructions: `Enter code: ${code}`,
162
+ method: "auto" as const,
163
+
164
+ async callback() {
165
+ const maxAttempts = Math.ceil((expiresIn * 1000) / DEVICE_AUTH_POLL_INTERVAL_MS)
166
+ let attempt = 0
167
+
168
+ while (attempt < maxAttempts) {
169
+ await new Promise((resolve) => setTimeout(resolve, DEVICE_AUTH_POLL_INTERVAL_MS))
170
+
171
+ try {
172
+ const pollResult = await pollDeviceAuth(code)
173
+
174
+ if (pollResult.status === "approved" && pollResult.token) {
175
+ let organizationId: string | undefined
176
+ try {
177
+ const profile = await getKilocodeProfile(pollResult.token)
178
+ if (profile.organizations && profile.organizations.length > 0) {
179
+ organizationId = profile.organizations[0].id
180
+ }
181
+ } catch {
182
+ // Continue without organization
183
+ }
184
+
185
+ const model = await getKilocodeDefaultModel(pollResult.token, organizationId)
186
+
187
+ return {
188
+ type: "success" as const,
189
+ refresh: pollResult.token,
190
+ access: pollResult.token,
191
+ expires: Date.now() + 365 * 24 * 60 * 60 * 1000,
192
+ organizationId,
193
+ model,
194
+ } as any
195
+ }
196
+
197
+ if (pollResult.status === "denied") {
198
+ return { type: "failed" as const }
199
+ }
200
+
201
+ if (pollResult.status === "expired") {
202
+ return { type: "failed" as const }
203
+ }
204
+ } catch {
205
+ // Continue polling on error
206
+ }
207
+
208
+ attempt++
209
+ }
210
+
211
+ return { type: "failed" as const }
212
+ },
213
+ }
214
+ },
215
+ },
143
216
  {
144
217
  type: "api",
145
218
  label: "Enter API Key (get from app.kilo.ai)",
@@ -147,13 +220,6 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
147
220
  ],
148
221
  },
149
222
 
150
- // Add custom headers for Kilo Code requests
151
- "chat.headers": async (input, output) => {
152
- if (input.model.providerID !== KILOCODE_PROVIDER_ID) return
153
-
154
- output.headers["X-KILOCODE-EDITORNAME"] = "opencode"
155
- output.headers["X-KILOCODE-SESSIONID"] = input.sessionID
156
- },
157
223
  }
158
224
  }
159
225