opencode-kilocode-auth 1.0.0 → 1.0.3

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 +28 -92
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.0",
4
+ "version": "1.0.3",
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
@@ -1,28 +1,23 @@
1
1
  import type { Hooks, PluginInput } from "@opencode-ai/plugin"
2
2
  import {
3
3
  KILOCODE_PROVIDER_ID,
4
- KILOCODE_API_BASE_URL,
5
- DEFAULT_HEADERS,
6
4
  DEFAULT_MODEL_ID,
7
- DEVICE_AUTH_POLL_INTERVAL_MS,
8
5
  } from "./constants"
9
6
  import {
10
- initiateDeviceAuth,
11
- pollDeviceAuth,
12
7
  getKilocodeProfile,
13
8
  getKilocodeDefaultModel,
14
9
  getKilocodeModels,
15
- openBrowserUrl,
16
10
  getApiUrl,
17
11
  } from "./kilocode/auth"
18
12
 
19
13
  interface KilocodeAuth {
20
- type: "oauth"
21
- refresh: string // token
14
+ type: "oauth" | "api"
15
+ refresh?: string // token (for oauth)
22
16
  access?: string
23
17
  expires?: number
24
18
  organizationId?: string
25
19
  model?: string
20
+ apiKey?: string // for api type
26
21
  }
27
22
 
28
23
  /**
@@ -40,10 +35,13 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
40
35
 
41
36
  async loader(getAuth, provider) {
42
37
  const auth = await getAuth()
43
- if (!auth || auth.type !== "oauth") return {}
38
+ if (!auth) return {}
44
39
 
45
40
  const kilocodeAuth = auth as KilocodeAuth
46
- const token = kilocodeAuth.refresh
41
+ // Support both OAuth (refresh token) and API key authentication
42
+ const token = kilocodeAuth.type === "api"
43
+ ? kilocodeAuth.apiKey
44
+ : kilocodeAuth.refresh
47
45
 
48
46
  if (!token) return {}
49
47
 
@@ -64,7 +62,7 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
64
62
  }
65
63
 
66
64
  // Build base URL for OpenRouter-compatible API
67
- const baseURL = getApiUrl("/api/openrouter/v1", token)
65
+ const baseURL = getApiUrl("/api/openrouter", token)
68
66
 
69
67
  return {
70
68
  baseURL,
@@ -72,12 +70,18 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
72
70
 
73
71
  async fetch(requestInput: RequestInfo | URL, init?: RequestInit) {
74
72
  const currentAuth = await getAuth()
75
- if (!currentAuth || currentAuth.type !== "oauth") {
73
+ if (!currentAuth) {
76
74
  return fetch(requestInput, init)
77
75
  }
78
76
 
79
77
  const currentKilocodeAuth = currentAuth as KilocodeAuth
80
- const currentToken = currentKilocodeAuth.refresh
78
+ const currentToken = currentKilocodeAuth.type === "api"
79
+ ? currentKilocodeAuth.apiKey
80
+ : currentKilocodeAuth.refresh
81
+
82
+ if (!currentToken) {
83
+ return fetch(requestInput, init)
84
+ }
81
85
 
82
86
  // Build headers
83
87
  const headers = new Headers()
@@ -95,9 +99,13 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
95
99
  }
96
100
  }
97
101
 
98
- // Set Kilo Code authorization
102
+ // Set Kilo Code authorization and required headers
99
103
  headers.set("Authorization", `Bearer ${currentToken}`)
100
- headers.set("X-KILOCODE-EDITORNAME", "opencode")
104
+ headers.set("HTTP-Referer", "https://kilocode.ai")
105
+ headers.set("X-Title", "Kilo Code")
106
+ headers.set("X-KiloCode-Version", "3.16.3")
107
+ headers.set("User-Agent", "Kilo-Code/3.16.3")
108
+ headers.set("X-KiloCode-EditorName", "Visual Studio Code 1.96.0")
101
109
 
102
110
  // Add organization header if present
103
111
  if (currentKilocodeAuth.organizationId) {
@@ -111,9 +119,10 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
111
119
  : new URL(typeof requestInput === "string" ? requestInput : (requestInput as Request).url)
112
120
 
113
121
  // Map to Kilo Code OpenRouter-compatible endpoint
122
+ // Kilo Code uses: https://api.kilo.ai/api/openrouter/chat/completions
114
123
  let url: URL
115
- if (parsed.pathname.includes("/chat/completions") || parsed.pathname.includes("/v1/chat/completions")) {
116
- url = new URL(getApiUrl("/api/openrouter/v1/chat/completions", currentToken))
124
+ if (parsed.pathname.includes("/chat/completions")) {
125
+ url = new URL(getApiUrl("/api/openrouter/chat/completions", currentToken))
117
126
  } else if (parsed.pathname.includes("/models")) {
118
127
  url = new URL(getApiUrl("/api/openrouter/models", currentToken))
119
128
  } else {
@@ -129,81 +138,10 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
129
138
  },
130
139
 
131
140
  methods: [
132
- {
133
- type: "oauth",
134
- label: "Login with Kilo Code",
135
- async authorize() {
136
- // Initiate device auth flow
137
- const authData = await initiateDeviceAuth()
138
- const { code, verificationUrl, expiresIn } = authData
139
-
140
- // Open browser for user
141
- openBrowserUrl(verificationUrl)
142
-
143
- return {
144
- url: verificationUrl,
145
- instructions: `Enter code: ${code}`,
146
- method: "auto" as const,
147
-
148
- async callback() {
149
- // Poll for authorization
150
- const maxAttempts = Math.ceil((expiresIn * 1000) / DEVICE_AUTH_POLL_INTERVAL_MS)
151
- let attempt = 0
152
-
153
- while (attempt < maxAttempts) {
154
- await new Promise((resolve) => setTimeout(resolve, DEVICE_AUTH_POLL_INTERVAL_MS))
155
-
156
- try {
157
- const pollResult = await pollDeviceAuth(code)
158
-
159
- if (pollResult.status === "approved" && pollResult.token) {
160
- // Get profile for organization
161
- let organizationId: string | undefined
162
- try {
163
- const profile = await getKilocodeProfile(pollResult.token)
164
- if (profile.organizations && profile.organizations.length > 0) {
165
- organizationId = profile.organizations[0].id
166
- }
167
- } catch {
168
- // Continue without organization
169
- }
170
-
171
- // Get default model
172
- const model = await getKilocodeDefaultModel(pollResult.token, organizationId)
173
-
174
- return {
175
- type: "success" as const,
176
- refresh: pollResult.token,
177
- access: pollResult.token,
178
- expires: Date.now() + 365 * 24 * 60 * 60 * 1000, // 1 year (tokens don't expire normally)
179
- // Store extra info in the auth
180
- organizationId,
181
- model,
182
- } as any
183
- }
184
-
185
- if (pollResult.status === "denied") {
186
- return { type: "failed" as const }
187
- }
188
-
189
- if (pollResult.status === "expired") {
190
- return { type: "failed" as const }
191
- }
192
- } catch {
193
- // Continue polling on error
194
- }
195
-
196
- attempt++
197
- }
198
-
199
- return { type: "failed" as const }
200
- },
201
- }
202
- },
203
- },
141
+ // API key is the primary method - get it from https://app.kilo.ai
204
142
  {
205
143
  type: "api",
206
- label: "Enter Kilo Code API Token",
144
+ label: "Enter API Key (get from app.kilo.ai)",
207
145
  },
208
146
  ],
209
147
  },
@@ -220,8 +158,6 @@ export async function KilocodeAuthPlugin(input: PluginInput): Promise<Hooks> {
220
158
 
221
159
  // Export utilities for external use
222
160
  export {
223
- initiateDeviceAuth,
224
- pollDeviceAuth,
225
161
  getKilocodeProfile,
226
162
  getKilocodeDefaultModel,
227
163
  getKilocodeModels,