opencode-kiro 0.4.0 → 0.5.0-beta.1

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/server.js CHANGED
@@ -1,258 +1,432 @@
1
- // src/server.ts
2
- import { readFileSync } from "fs";
3
- import { homedir } from "os";
4
- import { dirname, join } from "path";
5
- var KIRO_PLUGIN_NAME = "opencode-kiro";
6
- var server = async (input) => {
7
- const prompts = [
8
- {
9
- type: "select",
10
- key: "sidebar",
11
- message: "Enable the Kiro credits sidebar?",
12
- options: [
13
- { label: "Yes", value: "yes", hint: "writes tui.json; restart to apply" },
14
- { label: "No", value: "no" }
15
- ]
16
- }
17
- ];
1
+ // src/server/aisdk.ts
2
+ var KIRO_SDK_PACKAGE = "kiro-acp-ai-provider";
3
+ function createAisdkResources() {
18
4
  return {
19
- auth: {
20
- provider: "kiro",
21
- // options forwarded into createKiroAcp(). maps each model's limit.context into contextWindows by api.id; zero/missing skipped (SDK falls back to 1M).
22
- loader: async (_getAuth, provider) => {
23
- void notifyIfTokenExpired(input.client);
24
- return {
25
- cwd: input.directory ?? input.worktree,
26
- agent: "opencode",
27
- trustAllTools: true,
28
- mcpTimeout: 45,
29
- contextWindows: Object.fromEntries(
30
- Object.values(provider?.models ?? {}).filter((m) => m.api?.id && (m.limit?.context ?? 0) > 0).map((m) => [m.api.id, m.limit.context])
31
- )
32
- };
33
- },
34
- methods: [
35
- {
36
- type: "oauth",
37
- label: "Kiro CLI Login",
38
- prompts,
39
- async authorize(inputs) {
40
- const { verifyAuth } = await import("kiro-acp-ai-provider");
41
- const status = verifyAuth();
42
- if (!status.installed)
43
- throw new Error(
44
- "kiro-cli is not installed. Install it from https://kiro.dev/docs/cli/"
45
- );
46
- if (inputs?.sidebar === "yes") await enableSidebarConfig(tuiConfigPath(), input);
47
- if (status.authenticated) {
48
- return {
49
- url: "",
50
- instructions: "",
51
- method: "auto",
52
- async callback() {
53
- return readToken(status.tokenPath);
54
- }
55
- };
56
- }
57
- const { execFile } = await import("child_process");
58
- const child = execFile("kiro-cli", ["login"], {
59
- shell: process.platform === "win32"
60
- });
61
- return {
62
- url: "",
63
- instructions: "Complete Kiro authentication in the browser window that just opened. Waiting for login...",
64
- method: "auto",
65
- async callback() {
66
- const maxWait = 12e4;
67
- const start = Date.now();
68
- while (Date.now() - start < maxWait) {
69
- await new Promise((r) => setTimeout(r, 2e3));
70
- const check = verifyAuth();
71
- if (check.authenticated) {
72
- child.kill();
73
- return readToken(check.tokenPath);
74
- }
75
- }
76
- child.kill();
77
- throw new Error(
78
- "Kiro authentication timed out. Run `kiro-cli login` manually."
79
- );
80
- }
81
- };
82
- }
83
- }
84
- ]
85
- },
86
- // crash guard: core derefs an undefined kiro provider during auth init when a cred exists but the catalog lacks kiro.
87
- // only inject when authed; ??= is idempotent and won't clobber a real catalog entry.
88
- config: async (input2) => {
89
- if (!hasStoredKiroCredential()) return;
90
- input2.provider ??= {};
91
- input2.provider.kiro ??= {};
92
- },
93
- provider: {
94
- id: "kiro",
95
- // expose the exact runtime/models.dev intersection with catalog metadata intact
96
- async models(provider, ctx) {
97
- if (!ctx.auth) return provider.models;
98
- try {
99
- const { listModels } = await import("kiro-acp-ai-provider");
100
- const discoveredModels = await listModels({
101
- cwd: input.directory ?? input.worktree
102
- });
103
- const runtimeModels = new Map(discoveredModels.map((model) => [model.modelId, model]));
104
- if (runtimeModels.size !== discoveredModels.length) return provider.models;
105
- const models = {};
106
- for (const [catalogKey, catalogModel] of Object.entries(provider.models)) {
107
- const apiId = catalogModel.api?.id;
108
- if (typeof apiId !== "string") continue;
109
- const runtimeModel = runtimeModels.get(apiId);
110
- if (!runtimeModel) continue;
111
- if (runtimeModel.runtimeEfforts.length === 0) {
112
- models[catalogKey] = catalogModel;
113
- continue;
114
- }
115
- models[catalogKey] = {
116
- ...catalogModel,
117
- ...runtimeModel.baselineEffort === void 0 ? {} : {
118
- options: {
119
- ...catalogModel.options,
120
- reasoningEffort: runtimeModel.baselineEffort
121
- }
122
- },
123
- variants: {
124
- ...catalogModel.variants,
125
- ...Object.fromEntries(
126
- runtimeModel.runtimeEfforts.map((effort) => [
127
- effort,
128
- {
129
- ...catalogModel.variants?.[effort],
130
- reasoningEffort: effort
131
- }
132
- ])
133
- )
134
- }
135
- };
136
- }
137
- return models;
138
- } catch {
139
- return provider.models;
140
- }
5
+ ownedSdks: /* @__PURE__ */ new Set(),
6
+ cache: /* @__PURE__ */ new Map(),
7
+ disposeHook: void 0
8
+ };
9
+ }
10
+ function isKiroPackage(pkg) {
11
+ return pkg === KIRO_SDK_PACKAGE || pkg === `aisdk:${KIRO_SDK_PACKAGE}`;
12
+ }
13
+ function isJsonSafe(value) {
14
+ if (value === null) return true;
15
+ const kind = typeof value;
16
+ if (kind === "string" || kind === "number" || kind === "boolean") return true;
17
+ if (Array.isArray(value)) return value.every(isJsonSafe);
18
+ if (kind === "object") {
19
+ const proto = Object.getPrototypeOf(value);
20
+ if (proto !== Object.prototype && proto !== null) return false;
21
+ return Object.values(value).every(isJsonSafe);
22
+ }
23
+ return false;
24
+ }
25
+ function stableStringify(value) {
26
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
27
+ if (typeof value === "object" && value !== null) {
28
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`);
29
+ return `{${entries.join(",")}}`;
30
+ }
31
+ return JSON.stringify(value);
32
+ }
33
+ function stableOptionsKey(options) {
34
+ return isJsonSafe(options) ? stableStringify(options) : void 0;
35
+ }
36
+ async function registerAisdkHook(context, resources) {
37
+ const registration = await context.aisdk.hook("sdk", async (event) => {
38
+ if (!isKiroPackage(event.package)) return;
39
+ const { createKiroAcp } = await import("kiro-acp-ai-provider");
40
+ const key = stableOptionsKey(event.options);
41
+ let sdk = key === void 0 ? void 0 : resources.cache.get(key);
42
+ if (sdk === void 0) {
43
+ sdk = createKiroAcp(event.options);
44
+ resources.ownedSdks.add(sdk);
45
+ if (key !== void 0) resources.cache.set(key, sdk);
46
+ }
47
+ event.sdk = sdk;
48
+ });
49
+ resources.disposeHook = registration.dispose;
50
+ return async () => {
51
+ const errors = [];
52
+ resources.disposeHook = void 0;
53
+ try {
54
+ await registration.dispose();
55
+ } catch (error) {
56
+ errors.push(error);
57
+ }
58
+ const owned = Array.from(resources.ownedSdks);
59
+ resources.ownedSdks.clear();
60
+ resources.cache.clear();
61
+ for (const sdk of owned) {
62
+ try {
63
+ await sdk.shutdown();
64
+ } catch (error) {
65
+ errors.push(error);
141
66
  }
142
67
  }
68
+ if (errors.length > 0) throw new AggregateError(errors, "kiro aisdk cleanup failures");
143
69
  };
144
- };
145
- function kiroTokenPath() {
146
- return join(homedir(), ".aws", "sso", "cache", "kiro-auth-token.json");
147
70
  }
148
- async function readKiroTokenFile(tokenPath) {
149
- try {
150
- const { readFile } = await import("fs/promises");
151
- const parsed = JSON.parse(await readFile(tokenPath, "utf8"));
152
- return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
153
- } catch {
154
- return void 0;
71
+
72
+ // src/server/auth.ts
73
+ var KIRO_INTEGRATION_ID = "kiro";
74
+ var KIRO_INTEGRATION_NAME = "Kiro";
75
+ var KIRO_OAUTH_METHOD_ID = "kiro-cli-login";
76
+ var KIRO_OAUTH_METHOD_LABEL = "Kiro CLI Login";
77
+ var KIRO_DOCS_URL = "https://kiro.dev/docs/cli/";
78
+ var POLL_INTERVAL_MS = 2e3;
79
+ var MAX_WAIT_MS = 12e4;
80
+ var NOT_INSTALLED_MESSAGE = "kiro-cli is not installed. Install it from https://kiro.dev/docs/cli/";
81
+ var TIMEOUT_MESSAGE = "Kiro authentication timed out. Run `kiro-cli login` manually, then re-run `opencode auth login`.";
82
+ var LOGIN_INSTRUCTIONS = "Complete Kiro authentication in the browser window that just opened. Waiting for login...";
83
+ var ALREADY_AUTHENTICATED_INSTRUCTIONS = "Already authenticated with Kiro CLI.";
84
+ var SIDEBAR_INSTRUCTIONS = 'To enable the Kiro credits sidebar, add "opencode-kiro" to the "plugins" array in your global cli.json and restart opencode.';
85
+ function createAuthResources() {
86
+ return {
87
+ child: void 0,
88
+ pollTimer: void 0,
89
+ cancelPoll: void 0,
90
+ disposeRegistration: void 0
91
+ };
92
+ }
93
+ function releaseLoginResources(resources) {
94
+ if (resources.pollTimer !== void 0) {
95
+ clearTimeout(resources.pollTimer);
96
+ resources.pollTimer = void 0;
97
+ }
98
+ if (resources.child !== void 0) {
99
+ resources.child.kill();
100
+ resources.child = void 0;
101
+ }
102
+ if (resources.cancelPoll !== void 0) {
103
+ const cancel = resources.cancelPoll;
104
+ resources.cancelPoll = void 0;
105
+ cancel();
155
106
  }
156
107
  }
157
- async function readToken(tokenPath) {
158
- const { verifyAuth } = await import("kiro-acp-ai-provider");
159
- if (!verifyAuth().authenticated) return { type: "failed" };
160
- const token = tokenPath ? await readKiroTokenFile(tokenPath) : void 0;
161
- const access = typeof token?.accessToken === "string" ? token.accessToken : "";
162
- const refresh = typeof token?.refreshToken === "string" ? token.refreshToken : "";
108
+ function kiroCredential() {
163
109
  return {
164
- type: "success",
165
- refresh,
166
- // real refresh when present, else ""
167
- access: access || "authenticated",
168
- // cosmetic; opencode-core only needs presence
169
- // future expiry, refreshed each startup (server() re-runs per session) so core doesn't flag a logged-in user as expired. not the file value.
170
- expires: Date.now() + 8 * 60 * 60 * 1e3
110
+ type: "oauth",
111
+ methodID: KIRO_OAUTH_METHOD_ID,
112
+ refresh: "",
113
+ access: "kiro-cli",
114
+ expires: 0
171
115
  };
172
116
  }
173
- async function notifyIfTokenExpired(client) {
174
- try {
175
- const { verifyAuth } = await import("kiro-acp-ai-provider");
176
- if (verifyAuth().authenticated) return;
177
- const message = "Kiro is not logged in. Run 'kiro-cli login' to authenticate.";
178
- console.warn(message);
179
- void client?.tui?.showToast?.({ body: { message, variant: "warning" } })?.catch(() => {
180
- });
181
- } catch {
117
+ function withSidebarGuidance(instructions, inputs) {
118
+ return inputs["sidebar"] === "yes" ? `${instructions}
119
+
120
+ ${SIDEBAR_INSTRUCTIONS}` : instructions;
121
+ }
122
+ function pollForLogin(verifyAuth, resources) {
123
+ return new Promise((resolve, reject) => {
124
+ const start = Date.now();
125
+ resources.cancelPoll = () => {
126
+ reject(new Error("Kiro authentication was cancelled."));
127
+ };
128
+ const tick = () => {
129
+ resources.pollTimer = void 0;
130
+ if (verifyAuth().authenticated) {
131
+ resources.cancelPoll = void 0;
132
+ releaseLoginResources(resources);
133
+ resolve(kiroCredential());
134
+ return;
135
+ }
136
+ if (Date.now() - start >= MAX_WAIT_MS) {
137
+ resources.cancelPoll = void 0;
138
+ releaseLoginResources(resources);
139
+ reject(new Error(TIMEOUT_MESSAGE));
140
+ return;
141
+ }
142
+ resources.pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
143
+ };
144
+ resources.pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
145
+ });
146
+ }
147
+ async function authorize(inputs, resources) {
148
+ const { verifyAuth } = await import("kiro-acp-ai-provider");
149
+ const status = verifyAuth();
150
+ if (!status.installed) throw new Error(NOT_INSTALLED_MESSAGE);
151
+ if (status.authenticated) {
152
+ return {
153
+ url: KIRO_DOCS_URL,
154
+ instructions: withSidebarGuidance(ALREADY_AUTHENTICATED_INSTRUCTIONS, inputs),
155
+ mode: "auto",
156
+ callback: Promise.resolve(kiroCredential())
157
+ };
182
158
  }
159
+ const { execFile } = await import("child_process");
160
+ resources.child = execFile("kiro-cli", ["login"], {
161
+ shell: process.platform === "win32"
162
+ });
163
+ return {
164
+ url: KIRO_DOCS_URL,
165
+ instructions: withSidebarGuidance(LOGIN_INSTRUCTIONS, inputs),
166
+ mode: "auto",
167
+ callback: pollForLogin(verifyAuth, resources)
168
+ };
183
169
  }
184
- function hasStoredKiroCredential() {
185
- return authJsonPaths().some((path) => {
186
- try {
187
- const parsed = JSON.parse(readFileSync(path, "utf8"));
188
- return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "kiro" in parsed;
189
- } catch {
190
- return false;
191
- }
170
+ async function registerAuth(context, resources) {
171
+ const registration = await context.integration.transform((draft) => {
172
+ draft.update(KIRO_INTEGRATION_ID, (integration) => {
173
+ integration.name = KIRO_INTEGRATION_NAME;
174
+ });
175
+ draft.method.update({
176
+ integrationID: KIRO_INTEGRATION_ID,
177
+ method: {
178
+ id: KIRO_OAUTH_METHOD_ID,
179
+ type: "oauth",
180
+ label: KIRO_OAUTH_METHOD_LABEL,
181
+ prompts: [
182
+ {
183
+ type: "select",
184
+ key: "sidebar",
185
+ message: "Enable the Kiro credits sidebar?",
186
+ options: [
187
+ { label: "Yes", value: "yes", hint: "shows manual cli.json setup steps" },
188
+ { label: "No", value: "no" }
189
+ ]
190
+ }
191
+ ]
192
+ },
193
+ authorize: (inputs) => authorize(inputs, resources)
194
+ // no refresh callback: kiro-cli owns credential storage and refresh
195
+ });
192
196
  });
197
+ resources.disposeRegistration = registration.dispose;
198
+ return async () => {
199
+ releaseLoginResources(resources);
200
+ resources.disposeRegistration = void 0;
201
+ await registration.dispose();
202
+ };
203
+ }
204
+
205
+ // src/server/discovery.ts
206
+ var KIRO_PROVIDER_ID = "kiro";
207
+ var KIRO_PROVIDER_PACKAGE = "aisdk:kiro-acp-ai-provider";
208
+ function asString(value) {
209
+ return value;
193
210
  }
194
- function authJsonPaths() {
195
- if (process.env.XDG_DATA_HOME) return [join(process.env.XDG_DATA_HOME, "opencode", "auth.json")];
196
- const current = join(homedir(), ".local", "share", "opencode", "auth.json");
197
- if (process.platform !== "win32") return [current];
198
- return [current, join(homedir(), ".opencode", "auth.json")];
211
+ function createDiscoveryResources() {
212
+ return {
213
+ state: void 0,
214
+ disposeTransform: void 0,
215
+ eventIterator: void 0,
216
+ eventTask: void 0
217
+ };
199
218
  }
200
- function tuiConfigPath() {
201
- const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
202
- return join(base, "opencode", "tui.json");
219
+ function effortSettings(effort) {
220
+ return { effort };
203
221
  }
204
- function isSidebarConfigured(config) {
205
- if (!config) return false;
206
- const plugin = config.plugin;
207
- return Array.isArray(plugin) && plugin.includes(KIRO_PLUGIN_NAME);
222
+ function applyEfforts(model, runtimeModel) {
223
+ if (runtimeModel.runtimeEfforts.length === 0) return;
224
+ if (runtimeModel.baselineEffort !== void 0) {
225
+ model.settings = { ...model.settings, ...effortSettings(runtimeModel.baselineEffort) };
226
+ }
227
+ for (const effort of runtimeModel.runtimeEfforts) {
228
+ const existing = model.variants.find((variant) => asString(variant.id) === effort);
229
+ if (existing !== void 0) {
230
+ existing.settings = { ...existing.settings, ...effortSettings(effort) };
231
+ } else {
232
+ model.variants.push({
233
+ id: effort,
234
+ settings: effortSettings(effort)
235
+ });
236
+ }
237
+ }
208
238
  }
209
- async function enableSidebarConfig(path, input) {
210
- try {
211
- const { readFile, writeFile, mkdir } = await import("fs/promises");
212
- let raw;
239
+ function applyCatalogSnapshot(draft, state) {
240
+ const snapshot = state.snapshot;
241
+ if (snapshot === void 0 || snapshot.length === 0) return;
242
+ const runtime = new Map(snapshot.map((model) => [model.modelId, model]));
243
+ const record = draft.provider.get(KIRO_PROVIDER_ID);
244
+ const rich = record !== void 0 && record.models.size > 0;
245
+ const contextWindows = {};
246
+ if (rich) {
247
+ for (const [catalogKey, catalogModel] of Array.from(record.models.entries())) {
248
+ const runtimeModel = runtime.get(asString(catalogModel.modelID));
249
+ if (runtimeModel === void 0) {
250
+ draft.model.remove(KIRO_PROVIDER_ID, catalogKey);
251
+ continue;
252
+ }
253
+ draft.model.update(KIRO_PROVIDER_ID, catalogKey, (model) => {
254
+ applyEfforts(model, runtimeModel);
255
+ if (model.limit.context > 0) contextWindows[asString(model.modelID)] = model.limit.context;
256
+ });
257
+ }
258
+ } else {
259
+ for (const runtimeModel of snapshot) {
260
+ draft.model.update(KIRO_PROVIDER_ID, runtimeModel.modelId, (model) => {
261
+ model.name = runtimeModel.name || runtimeModel.modelId;
262
+ model.modelID = runtimeModel.modelId;
263
+ applyEfforts(model, runtimeModel);
264
+ if (model.limit.context > 0) contextWindows[asString(model.modelID)] = model.limit.context;
265
+ });
266
+ }
267
+ }
268
+ draft.provider.update(KIRO_PROVIDER_ID, (provider) => {
269
+ provider.name = KIRO_INTEGRATION_NAME;
270
+ provider.integrationID = KIRO_INTEGRATION_ID;
271
+ provider.package = KIRO_PROVIDER_PACKAGE;
272
+ provider.settings = {
273
+ ...provider.settings,
274
+ cwd: state.cwd,
275
+ agent: "opencode",
276
+ trustAllTools: true,
277
+ mcpTimeout: 45,
278
+ contextWindows
279
+ };
280
+ });
281
+ }
282
+ function createDiscover(context, state) {
283
+ async function runDiscovery(gen) {
213
284
  try {
214
- raw = await readFile(path, "utf8");
285
+ const { listModels } = await import("kiro-acp-ai-provider");
286
+ const discovered = await listModels({ cwd: state.cwd });
287
+ if (gen !== state.generation) return;
288
+ const uniqueIds = new Set(discovered.map((model) => model.modelId));
289
+ if (uniqueIds.size !== discovered.length) return;
290
+ state.snapshot = discovered;
291
+ await context.catalog.reload();
215
292
  } catch {
216
- raw = void 0;
217
293
  }
218
- let config;
219
- if (raw === void 0) {
220
- config = {};
221
- } else {
294
+ }
295
+ return async function discover(_reason) {
296
+ const active = await context.integration.connection.active(KIRO_INTEGRATION_ID);
297
+ if (!active) {
298
+ state.generation += 1;
299
+ state.snapshot = void 0;
300
+ await context.catalog.reload();
301
+ return;
302
+ }
303
+ if (state.inflight !== void 0) return state.inflight;
304
+ const gen = ++state.generation;
305
+ const run = runDiscovery(gen).finally(() => {
306
+ if (state.inflight === run) state.inflight = void 0;
307
+ });
308
+ state.inflight = run;
309
+ return run;
310
+ };
311
+ }
312
+ async function consumeEvents(iterator, discover) {
313
+ try {
314
+ while (true) {
315
+ const result = await iterator.next();
316
+ if (result.done) return;
317
+ const event = result.value;
318
+ if (event.type === "integration.connection.updated" && event.data.integrationID === KIRO_INTEGRATION_ID) {
319
+ try {
320
+ await discover("connection-updated");
321
+ } catch {
322
+ }
323
+ }
324
+ }
325
+ } catch {
326
+ }
327
+ }
328
+ async function registerDiscovery(context, resources) {
329
+ const { location } = await context.integration.list();
330
+ const state = {
331
+ cwd: location.directory,
332
+ snapshot: void 0,
333
+ generation: 0,
334
+ inflight: void 0
335
+ };
336
+ resources.state = state;
337
+ const discover = createDiscover(context, state);
338
+ const registration = await context.catalog.transform(
339
+ (draft) => applyCatalogSnapshot(draft, state)
340
+ );
341
+ resources.disposeTransform = registration.dispose;
342
+ const iterator = context.event.subscribe()[Symbol.asyncIterator]();
343
+ resources.eventIterator = iterator;
344
+ resources.eventTask = consumeEvents(iterator, discover);
345
+ if (await context.integration.connection.active(KIRO_INTEGRATION_ID)) {
346
+ void discover("setup").catch(() => {
347
+ });
348
+ }
349
+ return async () => {
350
+ state.generation += 1;
351
+ const errors = [];
352
+ const eventIterator = resources.eventIterator;
353
+ resources.eventIterator = void 0;
354
+ if (eventIterator?.return !== void 0) {
222
355
  try {
223
- const parsed = JSON.parse(raw);
224
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
225
- config = parsed;
226
- } catch {
227
- return;
356
+ await eventIterator.return();
357
+ } catch (error) {
358
+ errors.push(error);
359
+ }
360
+ }
361
+ const eventTask = resources.eventTask;
362
+ resources.eventTask = void 0;
363
+ if (eventTask !== void 0) await eventTask;
364
+ const disposeTransform = resources.disposeTransform;
365
+ resources.disposeTransform = void 0;
366
+ if (disposeTransform !== void 0) {
367
+ try {
368
+ await disposeTransform();
369
+ } catch (error) {
370
+ errors.push(error);
228
371
  }
229
372
  }
230
- if (isSidebarConfigured(config)) return;
231
- const plugin = Array.isArray(config.plugin) ? [...config.plugin] : [];
232
- if (!plugin.includes(KIRO_PLUGIN_NAME)) plugin.push(KIRO_PLUGIN_NAME);
233
- config.plugin = plugin;
234
- await mkdir(dirname(path), { recursive: true });
235
- await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf8");
373
+ if (errors.length > 0) throw new AggregateError(errors, "kiro discovery cleanup failures");
374
+ };
375
+ }
376
+
377
+ // src/server/lifecycle.ts
378
+ function createServerState() {
379
+ return {
380
+ auth: createAuthResources(),
381
+ discovery: createDiscoveryResources(),
382
+ aisdk: createAisdkResources(),
383
+ disposers: [],
384
+ disposed: false,
385
+ cleanup: void 0
386
+ };
387
+ }
388
+ async function disposeAll(disposers) {
389
+ const errors = [];
390
+ for (const dispose of [...disposers].reverse()) {
236
391
  try {
237
- await input.client.tui.showToast({
238
- body: {
239
- message: "Kiro credits sidebar enabled. Restart opencode to see it.",
240
- variant: "success"
241
- }
242
- });
243
- } catch {
392
+ await dispose();
393
+ } catch (error) {
394
+ if (error instanceof AggregateError) errors.push(...error.errors);
395
+ else errors.push(error);
244
396
  }
245
- } catch (err) {
246
- console.error("opencode-kiro: failed to enable the Kiro credits sidebar in tui.json:", err);
247
397
  }
398
+ if (errors.length > 0) throw new AggregateError(errors, "kiro server cleanup failures");
399
+ }
400
+ function buildCleanup(state) {
401
+ return () => {
402
+ if (state.cleanup !== void 0) return state.cleanup;
403
+ state.disposed = true;
404
+ state.cleanup = disposeAll(state.disposers);
405
+ return state.cleanup;
406
+ };
248
407
  }
249
- var KiroAuthPlugin = server;
250
- var server_default = { id: "kiro", server };
408
+
409
+ // src/server.ts
410
+ var plugin = {
411
+ id: "kiro",
412
+ async setup(context) {
413
+ const state = createServerState();
414
+ const cleanup = buildCleanup(state);
415
+ try {
416
+ state.disposers.push(await registerAuth(context, state.auth));
417
+ state.disposers.push(await registerDiscovery(context, state.discovery));
418
+ state.disposers.push(await registerAisdkHook(context, state.aisdk));
419
+ } catch (error) {
420
+ await Promise.resolve(cleanup()).catch(() => {
421
+ });
422
+ throw error;
423
+ }
424
+ return cleanup;
425
+ }
426
+ };
427
+ var server_default = plugin;
428
+ var KiroAuthPlugin = plugin;
251
429
  export {
252
430
  KiroAuthPlugin,
253
- server_default as default,
254
- enableSidebarConfig,
255
- kiroTokenPath,
256
- notifyIfTokenExpired,
257
- readToken
431
+ server_default as default
258
432
  };