Skip to content

Commit d3a623e

Browse files
committed
refactor(opencode): migrate kiro SDK to kiro-ai-provider npm package
1 parent 64c91a0 commit d3a623e

18 files changed

Lines changed: 57 additions & 3189 deletions

File tree

bun.lock

Lines changed: 10 additions & 163 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/opencode/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@
119119
"@opentui/solid": "0.1.97",
120120
"@parcel/watcher": "2.5.1",
121121
"@pierre/diffs": "catalog:",
122-
"@smithy/eventstream-codec": "4.2.12",
123-
"@smithy/types": "4.13.1",
124122
"@solid-primitives/event-bus": "1.1.2",
125123
"@solid-primitives/scheduled": "1.5.2",
126124
"@standard-schema/spec": "1.0.0",
@@ -145,6 +143,7 @@
145143
"hono-openapi": "catalog:",
146144
"ignore": "7.0.5",
147145
"jsonc-parser": "3.3.1",
146+
"kiro-ai-provider": "0.2.2",
148147
"mime-types": "3.0.2",
149148
"minimatch": "10.0.3",
150149
"npm-package-arg": "13.0.2",

packages/opencode/src/cli/cmd/tui/context/sync.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { useExit } from "./exit"
2929
import { useArgs } from "./args"
3030
import { batch, createEffect, on } from "solid-js"
3131
import { Log } from "@/util/log"
32-
import { getQuota } from "@/provider/sdk/kiro/kiro-quota"
32+
import { getQuota } from "kiro-ai-provider"
3333
import { ConsoleState, emptyConsoleState, type ConsoleState as ConsoleStateType } from "@/config/console-state"
3434

3535
export const { use: useSync, provider: SyncProvider } = createSimpleContext({

packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
22
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
33
import { createMemo, createSignal } from "solid-js"
4-
import { getQuota } from "@/provider/sdk/kiro/kiro-quota"
4+
import { getQuota } from "kiro-ai-provider"
55

66
const id = "internal:sidebar-context"
77

packages/opencode/src/plugin/kiro.ts

Lines changed: 26 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,10 @@
11
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
2-
import { Installation } from "@/installation"
3-
import { setTimeout as sleep } from "node:timers/promises"
4-
import path from "path"
5-
import os from "os"
2+
import { authenticate, getToken } from "kiro-ai-provider"
63

7-
const oidc = (region: string) => `https://oidc.${region}.amazonaws.com`
84
const BUILDER_ID_URL = "https://view.awsapps.com/start"
9-
const SCOPES = [
10-
"codewhisperer:completions",
11-
"codewhisperer:analysis",
12-
"codewhisperer:conversations",
13-
"codewhisperer:transformations",
14-
"codewhisperer:taskassist",
15-
]
16-
const GRANT_TYPES = ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"]
17-
const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code"
18-
const POLLING_MARGIN_MS = 3000
19-
const TOKEN_PATH = path.join(os.homedir(), ".aws", "sso", "cache", "kiro-auth-token.json")
20-
const CLIENT_PATH = path.join(os.homedir(), ".aws", "sso", "cache", "kiro-client-registration.json")
215
const USER_AGENT = "aws-sdk-js/1.0.27 ua/2.1 os/darwin lang/js api/codewhispererstreaming#1.0.27 m/E Kiro-opencode"
226
const USER_AGENT_SHORT = "aws-sdk-js/1.0.27 Kiro-opencode"
237

24-
function read<T>(filepath: string): Promise<T | undefined> {
25-
const file = Bun.file(filepath)
26-
return file
27-
.exists()
28-
.then((found) => (found ? file.text().then((text) => JSON.parse(text) as T) : undefined))
29-
.catch(() => undefined)
30-
}
31-
32-
function write(filepath: string, data: unknown): Promise<void> {
33-
return Bun.write(filepath, JSON.stringify(data, null, 2))
34-
.then(() => {})
35-
.catch(() => {})
36-
}
37-
38-
function mkdir(dir: string): Promise<void> {
39-
return import("fs/promises")
40-
.then((fs) => fs.mkdir(dir, { recursive: true }).then(() => {}))
41-
.catch(() => {})
42-
}
43-
448
export async function KiroAuthPlugin(_input: PluginInput): Promise<Hooks> {
459
return {
4610
auth: {
@@ -51,20 +15,18 @@ export async function KiroAuthPlugin(_input: PluginInput): Promise<Hooks> {
5115

5216
return {
5317
async fetch(request: RequestInfo | URL, init?: RequestInit) {
54-
const token = await read<{ accessToken: string }>(TOKEN_PATH)
18+
const token = await getToken()
5519
if (!token) return fetch(request, init)
5620

57-
const headers: Record<string, string> = {
58-
...(init?.headers as Record<string, string>),
59-
Authorization: `Bearer ${token.accessToken}`,
60-
"User-Agent": USER_AGENT,
61-
"x-amz-user-agent": USER_AGENT_SHORT,
62-
"x-amzn-codewhisperer-optout": "true",
63-
}
64-
6521
return fetch(request, {
6622
...init,
67-
headers,
23+
headers: {
24+
...(init?.headers as Record<string, string>),
25+
Authorization: `Bearer ${token}`,
26+
"User-Agent": USER_AGENT,
27+
"x-amz-user-agent": USER_AGENT_SHORT,
28+
"x-amzn-codewhisperer-optout": "true",
29+
},
6830
})
6931
},
7032
}
@@ -106,138 +68,29 @@ export async function KiroAuthPlugin(_input: PluginInput): Promise<Hooks> {
10668
? (inputs.region || process.env.AWS_SSO_REGION || "us-east-1")
10769
: "us-east-1"
10870

109-
const registration = await fetch(`${oidc(region)}/client/register`, {
110-
method: "POST",
111-
headers: {
112-
"Content-Type": "application/json",
113-
"User-Agent": `opencode/${Installation.VERSION}`,
114-
},
115-
body: JSON.stringify({
116-
clientName: "opencode-kiro",
117-
clientType: "public",
118-
scopes: SCOPES,
119-
grantTypes: GRANT_TYPES,
120-
issuerUrl: url,
121-
}),
122-
})
71+
const { promise: pending, resolve } = Promise.withResolvers<{ url: string; code: string }>()
12372

124-
if (!registration.ok) {
125-
throw new Error("Failed to register OIDC client")
126-
}
127-
128-
const client = (await registration.json()) as {
129-
clientId: string
130-
clientSecret: string
131-
clientIdIssuedAt: number
132-
clientSecretExpiresAt: number
133-
}
134-
135-
const device = await fetch(`${oidc(region)}/device_authorization`, {
136-
method: "POST",
137-
headers: {
138-
"Content-Type": "application/json",
139-
"User-Agent": `opencode/${Installation.VERSION}`,
140-
},
141-
body: JSON.stringify({
142-
clientId: client.clientId,
143-
clientSecret: client.clientSecret,
144-
startUrl: url,
145-
}),
73+
const auth = authenticate({
74+
startUrl: url,
75+
region,
76+
onVerification: (verify, code) => resolve({ url: verify, code }),
14677
})
14778

148-
if (!device.ok) {
149-
throw new Error("Failed to start device authorization")
150-
}
151-
152-
const auth = (await device.json()) as {
153-
verificationUri: string
154-
verificationUriComplete: string
155-
userCode: string
156-
deviceCode: string
157-
interval: number
158-
expiresIn: number
159-
}
79+
const verification = await pending
16080

16181
return {
162-
url: auth.verificationUriComplete,
163-
instructions: `Enter code: ${auth.userCode}`,
82+
url: verification.url,
83+
instructions: verification.code,
16484
method: "auto" as const,
165-
async callback() {
166-
const delay = { ms: auth.interval }
167-
168-
while (true) {
169-
const response = await fetch(`${oidc(region)}/token`, {
170-
method: "POST",
171-
headers: {
172-
"Content-Type": "application/json",
173-
"User-Agent": `opencode/${Installation.VERSION}`,
174-
},
175-
body: JSON.stringify({
176-
clientId: client.clientId,
177-
clientSecret: client.clientSecret,
178-
grantType: DEVICE_GRANT,
179-
deviceCode: auth.deviceCode,
180-
}),
181-
})
182-
183-
if (response.ok) {
184-
const tokens = (await response.json()) as {
185-
accessToken: string
186-
refreshToken: string
187-
expiresIn: number
188-
tokenType: string
189-
}
190-
191-
const expires = new Date(Date.now() + tokens.expiresIn * 1000)
192-
193-
await mkdir(path.dirname(TOKEN_PATH))
194-
195-
await write(TOKEN_PATH, {
196-
accessToken: tokens.accessToken,
197-
refreshToken: tokens.refreshToken,
198-
expiresAt: expires.toISOString(),
199-
region,
200-
clientId: client.clientId,
201-
clientSecret: client.clientSecret,
202-
})
203-
204-
await write(CLIENT_PATH, {
205-
clientId: client.clientId,
206-
clientSecret: client.clientSecret,
207-
clientIdIssuedAt: client.clientIdIssuedAt,
208-
clientSecretExpiresAt: client.clientSecretExpiresAt,
209-
})
210-
211-
return {
212-
type: "success" as const,
213-
refresh: tokens.refreshToken,
214-
access: tokens.accessToken,
215-
expires: expires.getTime(),
216-
}
217-
}
218-
219-
const error = (await response.json().catch(() => ({}))) as {
220-
error?: string
221-
error_description?: string
222-
}
223-
224-
if (error.error === "authorization_pending") {
225-
await sleep(delay.ms * 1000 + POLLING_MARGIN_MS)
226-
continue
227-
}
228-
229-
if (error.error === "slow_down") {
230-
delay.ms = delay.ms + 5
231-
await sleep(delay.ms * 1000 + POLLING_MARGIN_MS)
232-
continue
233-
}
234-
235-
if (error.error) return { type: "failed" as const }
236-
237-
await sleep(delay.ms * 1000 + POLLING_MARGIN_MS)
238-
continue
239-
}
240-
},
85+
callback: () =>
86+
auth
87+
.then((result) => ({
88+
type: "success" as const,
89+
refresh: result.refreshToken,
90+
access: result.accessToken,
91+
expires: Date.now() + 3600000,
92+
}))
93+
.catch(() => ({ type: "failed" as const })),
24194
}
24295
},
24396
},

packages/opencode/src/provider/provider.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { createOpenAI } from "@ai-sdk/openai"
3535
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
3636
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
3737
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/copilot"
38-
import { createKiro } from "./sdk/kiro"
38+
import { createKiro, hasToken, getApiRegion } from "kiro-ai-provider"
3939
import { createXai } from "@ai-sdk/xai"
4040
import { createMistral } from "@ai-sdk/mistral"
4141
import { createGroq } from "@ai-sdk/groq"
@@ -54,7 +54,6 @@ import {
5454
isWorkflowModel,
5555
discoverWorkflowModels,
5656
} from "gitlab-ai-provider"
57-
import { hasToken, getApiRegion } from "./sdk/kiro/kiro-auth"
5857
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
5958
import { GoogleAuth } from "google-auth-library"
6059
import { ProviderTransform } from "./transform"

packages/opencode/src/provider/sdk/kiro/index.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)