@sunerpy/opencode-kiro-auth 0.1.0

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 (113) hide show
  1. package/LICENSE +678 -0
  2. package/README.md +270 -0
  3. package/dist/constants.d.ts +28 -0
  4. package/dist/constants.js +108 -0
  5. package/dist/core/account/account-selector.d.ts +25 -0
  6. package/dist/core/account/account-selector.js +80 -0
  7. package/dist/core/account/usage-tracker.d.ts +20 -0
  8. package/dist/core/account/usage-tracker.js +56 -0
  9. package/dist/core/auth/auth-handler.d.ts +18 -0
  10. package/dist/core/auth/auth-handler.js +236 -0
  11. package/dist/core/auth/idc-auth-method.d.ts +9 -0
  12. package/dist/core/auth/idc-auth-method.js +169 -0
  13. package/dist/core/auth/token-refresher.d.ts +23 -0
  14. package/dist/core/auth/token-refresher.js +85 -0
  15. package/dist/core/index.d.ts +9 -0
  16. package/dist/core/index.js +9 -0
  17. package/dist/core/request/error-handler.d.ts +32 -0
  18. package/dist/core/request/error-handler.js +159 -0
  19. package/dist/core/request/request-handler.d.ts +35 -0
  20. package/dist/core/request/request-handler.js +296 -0
  21. package/dist/core/request/response-handler.d.ts +8 -0
  22. package/dist/core/request/response-handler.js +137 -0
  23. package/dist/core/request/retry-strategy.d.ts +18 -0
  24. package/dist/core/request/retry-strategy.js +28 -0
  25. package/dist/index.d.ts +94 -0
  26. package/dist/index.js +2 -0
  27. package/dist/infrastructure/database/account-cache.d.ts +14 -0
  28. package/dist/infrastructure/database/account-cache.js +44 -0
  29. package/dist/infrastructure/database/account-repository.d.ts +12 -0
  30. package/dist/infrastructure/database/account-repository.js +66 -0
  31. package/dist/infrastructure/index.d.ts +7 -0
  32. package/dist/infrastructure/index.js +7 -0
  33. package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
  34. package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
  35. package/dist/infrastructure/transformers/history-builder.d.ts +16 -0
  36. package/dist/infrastructure/transformers/history-builder.js +226 -0
  37. package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
  38. package/dist/infrastructure/transformers/message-transformer.js +99 -0
  39. package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
  40. package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
  41. package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
  42. package/dist/infrastructure/transformers/tool-transformer.js +19 -0
  43. package/dist/kiro/auth.d.ts +4 -0
  44. package/dist/kiro/auth.js +25 -0
  45. package/dist/kiro/oauth-idc.d.ts +25 -0
  46. package/dist/kiro/oauth-idc.js +167 -0
  47. package/dist/plugin/accounts.d.ts +29 -0
  48. package/dist/plugin/accounts.js +266 -0
  49. package/dist/plugin/auth-bootstrap.d.ts +9 -0
  50. package/dist/plugin/auth-bootstrap.js +69 -0
  51. package/dist/plugin/auth-page.d.ts +4 -0
  52. package/dist/plugin/auth-page.js +721 -0
  53. package/dist/plugin/config/index.d.ts +3 -0
  54. package/dist/plugin/config/index.js +2 -0
  55. package/dist/plugin/config/loader.d.ts +6 -0
  56. package/dist/plugin/config/loader.js +129 -0
  57. package/dist/plugin/config/schema.d.ts +88 -0
  58. package/dist/plugin/config/schema.js +94 -0
  59. package/dist/plugin/effort.d.ts +46 -0
  60. package/dist/plugin/effort.js +113 -0
  61. package/dist/plugin/errors.d.ts +17 -0
  62. package/dist/plugin/errors.js +34 -0
  63. package/dist/plugin/health.d.ts +1 -0
  64. package/dist/plugin/health.js +13 -0
  65. package/dist/plugin/image-handler.d.ts +18 -0
  66. package/dist/plugin/image-handler.js +82 -0
  67. package/dist/plugin/logger.d.ts +8 -0
  68. package/dist/plugin/logger.js +84 -0
  69. package/dist/plugin/models.d.ts +2 -0
  70. package/dist/plugin/models.js +11 -0
  71. package/dist/plugin/request.d.ts +9 -0
  72. package/dist/plugin/request.js +287 -0
  73. package/dist/plugin/response.d.ts +3 -0
  74. package/dist/plugin/response.js +97 -0
  75. package/dist/plugin/sdk-client.d.ts +4 -0
  76. package/dist/plugin/sdk-client.js +55 -0
  77. package/dist/plugin/storage/locked-operations.d.ts +5 -0
  78. package/dist/plugin/storage/locked-operations.js +103 -0
  79. package/dist/plugin/storage/migrations.d.ts +4 -0
  80. package/dist/plugin/storage/migrations.js +155 -0
  81. package/dist/plugin/storage/sqlite.d.ts +18 -0
  82. package/dist/plugin/storage/sqlite.js +166 -0
  83. package/dist/plugin/streaming/index.d.ts +2 -0
  84. package/dist/plugin/streaming/index.js +2 -0
  85. package/dist/plugin/streaming/openai-converter.d.ts +2 -0
  86. package/dist/plugin/streaming/openai-converter.js +81 -0
  87. package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
  88. package/dist/plugin/streaming/sdk-stream-transformer.js +274 -0
  89. package/dist/plugin/streaming/stream-parser.d.ts +5 -0
  90. package/dist/plugin/streaming/stream-parser.js +136 -0
  91. package/dist/plugin/streaming/stream-state.d.ts +5 -0
  92. package/dist/plugin/streaming/stream-state.js +59 -0
  93. package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
  94. package/dist/plugin/streaming/stream-transformer.js +295 -0
  95. package/dist/plugin/streaming/types.d.ts +25 -0
  96. package/dist/plugin/streaming/types.js +2 -0
  97. package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
  98. package/dist/plugin/sync/kiro-cli-parser.js +72 -0
  99. package/dist/plugin/sync/kiro-cli-profile.d.ts +1 -0
  100. package/dist/plugin/sync/kiro-cli-profile.js +30 -0
  101. package/dist/plugin/sync/kiro-cli.d.ts +2 -0
  102. package/dist/plugin/sync/kiro-cli.js +212 -0
  103. package/dist/plugin/sync/stale-accounts.d.ts +9 -0
  104. package/dist/plugin/sync/stale-accounts.js +31 -0
  105. package/dist/plugin/token.d.ts +2 -0
  106. package/dist/plugin/token.js +78 -0
  107. package/dist/plugin/types.d.ts +125 -0
  108. package/dist/plugin/types.js +0 -0
  109. package/dist/plugin/usage.d.ts +3 -0
  110. package/dist/plugin/usage.js +79 -0
  111. package/dist/plugin.d.ts +174 -0
  112. package/dist/plugin.js +170 -0
  113. package/package.json +74 -0
@@ -0,0 +1,155 @@
1
+ export function runMigrations(db) {
2
+ migrateToUniqueRefreshToken(db);
3
+ migrateRealEmailColumn(db);
4
+ migrateUsageTable(db);
5
+ migrateStartUrlColumn(db);
6
+ migrateOidcRegionColumn(db);
7
+ migrateDropRefreshTokenUniqueIndex(db);
8
+ }
9
+ function migrateToUniqueRefreshToken(db) {
10
+ const hasIndex = db
11
+ .prepare("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_refresh_token_unique'")
12
+ .get();
13
+ if (hasIndex)
14
+ return;
15
+ db.exec('BEGIN TRANSACTION');
16
+ try {
17
+ const duplicates = db
18
+ .prepare(`
19
+ SELECT refresh_token, COUNT(*) as count
20
+ FROM accounts
21
+ GROUP BY refresh_token
22
+ HAVING count > 1
23
+ `)
24
+ .all();
25
+ for (const dup of duplicates) {
26
+ const accounts = db
27
+ .prepare('SELECT * FROM accounts WHERE refresh_token = ? ORDER BY last_used DESC, expires_at DESC')
28
+ .all(dup.refresh_token);
29
+ if (accounts.length > 1) {
30
+ const keep = accounts[0];
31
+ const remove = accounts.slice(1);
32
+ const mergedUsedCount = Math.max(...accounts.map((a) => a.used_count || 0));
33
+ const mergedLimitCount = Math.max(...accounts.map((a) => a.limit_count || 0));
34
+ const mergedLastUsed = Math.max(...accounts.map((a) => a.last_used || 0));
35
+ const mergedFailCount = Math.max(...accounts.map((a) => a.fail_count || 0));
36
+ db.prepare(`
37
+ UPDATE accounts SET
38
+ used_count = ?,
39
+ limit_count = ?,
40
+ last_used = ?,
41
+ fail_count = ?
42
+ WHERE id = ?
43
+ `).run(mergedUsedCount, mergedLimitCount, mergedLastUsed, mergedFailCount, keep.id);
44
+ for (const acc of remove) {
45
+ db.prepare('DELETE FROM accounts WHERE id = ?').run(acc.id);
46
+ }
47
+ }
48
+ }
49
+ db.exec('CREATE UNIQUE INDEX idx_refresh_token_unique ON accounts(refresh_token)');
50
+ db.exec('COMMIT');
51
+ }
52
+ catch (e) {
53
+ db.exec('ROLLBACK');
54
+ throw e;
55
+ }
56
+ }
57
+ function migrateRealEmailColumn(db) {
58
+ const columns = db.prepare('PRAGMA table_info(accounts)').all();
59
+ const names = new Set(columns.map((c) => c.name));
60
+ if (names.has('real_email')) {
61
+ db.exec('BEGIN TRANSACTION');
62
+ try {
63
+ db.exec("UPDATE accounts SET email = real_email WHERE real_email IS NOT NULL AND real_email != '' AND email LIKE 'builder-id@aws.amazon.com%'");
64
+ db.exec(`
65
+ CREATE TABLE accounts_new (
66
+ id TEXT PRIMARY KEY, email TEXT NOT NULL, auth_method TEXT NOT NULL,
67
+ region TEXT NOT NULL, oidc_region TEXT, client_id TEXT, client_secret TEXT, profile_arn TEXT,
68
+ start_url TEXT,
69
+ refresh_token TEXT NOT NULL, access_token TEXT NOT NULL, expires_at INTEGER NOT NULL,
70
+ rate_limit_reset INTEGER DEFAULT 0, is_healthy INTEGER DEFAULT 1, unhealthy_reason TEXT,
71
+ recovery_time INTEGER, fail_count INTEGER DEFAULT 0, last_used INTEGER DEFAULT 0,
72
+ used_count INTEGER DEFAULT 0, limit_count INTEGER DEFAULT 0, last_sync INTEGER DEFAULT 0
73
+ )
74
+ `);
75
+ db.exec(`
76
+ INSERT INTO accounts_new (id, email, auth_method, region, oidc_region, client_id, client_secret, profile_arn, start_url, refresh_token, access_token, expires_at, rate_limit_reset, is_healthy, unhealthy_reason, recovery_time, fail_count, last_used, used_count, limit_count, last_sync)
77
+ SELECT id, email, auth_method, region, NULL, client_id, client_secret, profile_arn, NULL, refresh_token, access_token, expires_at, COALESCE(rate_limit_reset, 0), COALESCE(is_healthy, 1), unhealthy_reason, recovery_time, COALESCE(fail_count, 0), COALESCE(last_used, 0), 0, 0, 0 FROM accounts
78
+ `);
79
+ db.exec('DROP TABLE accounts');
80
+ db.exec('ALTER TABLE accounts_new RENAME TO accounts');
81
+ db.exec('COMMIT');
82
+ }
83
+ catch (e) {
84
+ db.exec('ROLLBACK');
85
+ }
86
+ }
87
+ else {
88
+ const needed = {
89
+ fail_count: 'INTEGER DEFAULT 0',
90
+ used_count: 'INTEGER DEFAULT 0',
91
+ limit_count: 'INTEGER DEFAULT 0',
92
+ last_sync: 'INTEGER DEFAULT 0'
93
+ };
94
+ for (const [n, d] of Object.entries(needed)) {
95
+ if (!names.has(n))
96
+ db.exec(`ALTER TABLE accounts ADD COLUMN ${n} ${d}`);
97
+ }
98
+ }
99
+ }
100
+ function migrateUsageTable(db) {
101
+ const hasUsageTable = db
102
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='usage'")
103
+ .get();
104
+ if (hasUsageTable) {
105
+ db.exec(`
106
+ UPDATE accounts SET
107
+ used_count = COALESCE((SELECT used_count FROM usage WHERE usage.account_id = accounts.id), used_count),
108
+ limit_count = COALESCE((SELECT limit_count FROM usage WHERE usage.account_id = accounts.id), limit_count),
109
+ last_sync = COALESCE((SELECT last_sync FROM usage WHERE usage.account_id = accounts.id), last_sync)
110
+ `);
111
+ db.exec('DROP TABLE usage');
112
+ }
113
+ }
114
+ function migrateStartUrlColumn(db) {
115
+ const columns = db.prepare('PRAGMA table_info(accounts)').all();
116
+ const names = new Set(columns.map((c) => c.name));
117
+ if (!names.has('start_url')) {
118
+ db.exec('ALTER TABLE accounts ADD COLUMN start_url TEXT');
119
+ }
120
+ }
121
+ function migrateOidcRegionColumn(db) {
122
+ const columns = db.prepare('PRAGMA table_info(accounts)').all();
123
+ const names = new Set(columns.map((c) => c.name));
124
+ if (!names.has('oidc_region')) {
125
+ db.exec('ALTER TABLE accounts ADD COLUMN oidc_region TEXT');
126
+ }
127
+ // Backfill: historically `region` was used for both service + OIDC.
128
+ db.exec("UPDATE accounts SET oidc_region = region WHERE oidc_region IS NULL OR oidc_region = ''");
129
+ }
130
+ function migrateDropRefreshTokenUniqueIndex(db) {
131
+ // Drop the UNIQUE index on refresh_token — it was only needed for ON CONFLICT(refresh_token)
132
+ // upsert mechanics. Now that we use ON CONFLICT(id), this index is unnecessary and actively
133
+ // harmful: duplicate rows (same account, different legacy vs hash id) share the same
134
+ // refresh_token, causing UNIQUE constraint violations on every upsert.
135
+ db.exec('DROP INDEX IF EXISTS idx_refresh_token_unique');
136
+ // Clean up duplicate rows: same email + same refresh_token but different ids.
137
+ // Keep the deterministic hash id (64-char hex), delete legacy kiro-cli-sync-* rows.
138
+ const duplicates = db
139
+ .prepare(`SELECT email, refresh_token FROM accounts
140
+ GROUP BY email, refresh_token
141
+ HAVING COUNT(*) > 1`)
142
+ .all();
143
+ for (const dup of duplicates) {
144
+ const rows = db
145
+ .prepare(`SELECT id FROM accounts WHERE email = ? AND refresh_token = ?
146
+ ORDER BY
147
+ CASE WHEN id LIKE 'kiro-cli-sync-%' THEN 1 ELSE 0 END ASC,
148
+ last_used DESC, expires_at DESC`)
149
+ .all(dup.email, dup.refresh_token);
150
+ // Keep the first row (deterministic hash id preferred), delete the rest
151
+ for (const row of rows.slice(1)) {
152
+ db.prepare('DELETE FROM accounts WHERE id = ?').run(row.id);
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,18 @@
1
+ import type { ManagedAccount } from '../types.js';
2
+ export declare const DB_PATH: string;
3
+ export declare class KiroDatabase {
4
+ private db;
5
+ private path;
6
+ constructor(path?: string);
7
+ private init;
8
+ getAccounts(): any[];
9
+ private upsertAccountInternal;
10
+ upsertAccount(acc: ManagedAccount): Promise<void>;
11
+ batchUpsertAccounts(accounts: ManagedAccount[]): Promise<void>;
12
+ deleteAccount(id: string): Promise<void>;
13
+ markAccountsUnhealthy(ids: string[], reason: string): Promise<void>;
14
+ private rowToAccount;
15
+ close(): void;
16
+ }
17
+ export declare function createDatabase(path?: string): KiroDatabase;
18
+ export declare const kiroDb: KiroDatabase;
@@ -0,0 +1,166 @@
1
+ import Database from 'libsql';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { deduplicateAccounts, mergeAccounts, withDatabaseLock } from './locked-operations.js';
6
+ import { runMigrations } from './migrations.js';
7
+ function getBaseDir() {
8
+ const p = process.platform;
9
+ if (p === 'win32')
10
+ return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
11
+ return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
12
+ }
13
+ export const DB_PATH = join(getBaseDir(), 'kiro.db');
14
+ export class KiroDatabase {
15
+ db;
16
+ path;
17
+ constructor(path = DB_PATH) {
18
+ this.path = path;
19
+ const dir = join(path, '..');
20
+ if (!existsSync(dir))
21
+ mkdirSync(dir, { recursive: true });
22
+ this.db = new Database(path);
23
+ this.db.pragma('busy_timeout = 5000');
24
+ this.init();
25
+ }
26
+ init() {
27
+ this.db.pragma('journal_mode = WAL');
28
+ this.db.exec(`
29
+ CREATE TABLE IF NOT EXISTS accounts (
30
+ id TEXT PRIMARY KEY, email TEXT NOT NULL, auth_method TEXT NOT NULL,
31
+ region TEXT NOT NULL, oidc_region TEXT, client_id TEXT, client_secret TEXT, profile_arn TEXT,
32
+ start_url TEXT,
33
+ refresh_token TEXT NOT NULL, access_token TEXT NOT NULL, expires_at INTEGER NOT NULL,
34
+ rate_limit_reset INTEGER DEFAULT 0, is_healthy INTEGER DEFAULT 1, unhealthy_reason TEXT,
35
+ recovery_time INTEGER, fail_count INTEGER DEFAULT 0, last_used INTEGER DEFAULT 0,
36
+ used_count INTEGER DEFAULT 0, limit_count INTEGER DEFAULT 0, last_sync INTEGER DEFAULT 0
37
+ )
38
+ `);
39
+ runMigrations(this.db);
40
+ }
41
+ getAccounts() {
42
+ return this.db.prepare('SELECT * FROM accounts').all();
43
+ }
44
+ upsertAccountInternal(acc) {
45
+ this.db
46
+ .prepare(`
47
+ INSERT INTO accounts (
48
+ id, email, auth_method, region, oidc_region, client_id, client_secret,
49
+ profile_arn, start_url, refresh_token, access_token, expires_at, rate_limit_reset,
50
+ is_healthy, unhealthy_reason, recovery_time, fail_count, last_used,
51
+ used_count, limit_count, last_sync
52
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
53
+ ON CONFLICT(id) DO UPDATE SET
54
+ id=excluded.id, email=excluded.email, auth_method=excluded.auth_method,
55
+ region=excluded.region, oidc_region=excluded.oidc_region, client_id=excluded.client_id, client_secret=excluded.client_secret,
56
+ profile_arn=excluded.profile_arn, start_url=excluded.start_url, refresh_token=excluded.refresh_token,
57
+ access_token=excluded.access_token, expires_at=excluded.expires_at,
58
+ rate_limit_reset=excluded.rate_limit_reset, is_healthy=excluded.is_healthy,
59
+ unhealthy_reason=excluded.unhealthy_reason, recovery_time=excluded.recovery_time,
60
+ fail_count=excluded.fail_count, last_used=excluded.last_used,
61
+ used_count=excluded.used_count, limit_count=excluded.limit_count, last_sync=excluded.last_sync
62
+ `)
63
+ .run(acc.id, acc.email, acc.authMethod, acc.region, acc.oidcRegion || null, acc.clientId || null, acc.clientSecret || null, acc.profileArn || null, acc.startUrl || null, acc.refreshToken, acc.accessToken, acc.expiresAt, acc.rateLimitResetTime || 0, acc.isHealthy ? 1 : 0, acc.unhealthyReason || null, acc.recoveryTime || null, acc.failCount || 0, acc.lastUsed || 0, acc.usedCount || 0, acc.limitCount || 0, acc.lastSync || 0);
64
+ }
65
+ async upsertAccount(acc) {
66
+ await withDatabaseLock(this.path, async () => {
67
+ const existing = this.getAccounts().map(this.rowToAccount);
68
+ const merged = mergeAccounts(existing, [acc]);
69
+ const deduplicated = deduplicateAccounts(merged);
70
+ this.db.exec('BEGIN TRANSACTION');
71
+ try {
72
+ for (const account of deduplicated) {
73
+ this.upsertAccountInternal(account);
74
+ }
75
+ this.db.exec('COMMIT');
76
+ }
77
+ catch (e) {
78
+ this.db.exec('ROLLBACK');
79
+ throw e;
80
+ }
81
+ });
82
+ }
83
+ async batchUpsertAccounts(accounts) {
84
+ await withDatabaseLock(this.path, async () => {
85
+ const existing = this.getAccounts().map(this.rowToAccount);
86
+ const merged = mergeAccounts(existing, accounts);
87
+ const deduplicated = deduplicateAccounts(merged);
88
+ this.db.exec('BEGIN TRANSACTION');
89
+ try {
90
+ for (const account of deduplicated) {
91
+ this.upsertAccountInternal(account);
92
+ }
93
+ this.db.exec('COMMIT');
94
+ }
95
+ catch (e) {
96
+ this.db.exec('ROLLBACK');
97
+ throw e;
98
+ }
99
+ });
100
+ }
101
+ async deleteAccount(id) {
102
+ await withDatabaseLock(this.path, async () => {
103
+ this.db.prepare('DELETE FROM accounts WHERE id = ?').run(id);
104
+ });
105
+ }
106
+ async markAccountsUnhealthy(ids, reason) {
107
+ if (ids.length === 0)
108
+ return;
109
+ await withDatabaseLock(this.path, async () => {
110
+ const now = Date.now();
111
+ this.db.exec('BEGIN TRANSACTION');
112
+ try {
113
+ const stmt = this.db.prepare(`
114
+ UPDATE accounts
115
+ SET is_healthy = 0,
116
+ unhealthy_reason = ?,
117
+ recovery_time = NULL,
118
+ fail_count = 10,
119
+ rate_limit_reset = 0,
120
+ last_sync = ?
121
+ WHERE id = ?
122
+ `);
123
+ for (const id of ids) {
124
+ stmt.run(reason, now, id);
125
+ }
126
+ this.db.exec('COMMIT');
127
+ }
128
+ catch (e) {
129
+ this.db.exec('ROLLBACK');
130
+ throw e;
131
+ }
132
+ });
133
+ }
134
+ rowToAccount(row) {
135
+ return {
136
+ id: row.id,
137
+ email: row.email,
138
+ authMethod: row.auth_method,
139
+ region: row.region,
140
+ oidcRegion: row.oidc_region || undefined,
141
+ clientId: row.client_id,
142
+ clientSecret: row.client_secret,
143
+ profileArn: row.profile_arn,
144
+ startUrl: row.start_url || undefined,
145
+ refreshToken: row.refresh_token,
146
+ accessToken: row.access_token,
147
+ expiresAt: row.expires_at,
148
+ rateLimitResetTime: row.rate_limit_reset,
149
+ isHealthy: row.is_healthy === 1,
150
+ unhealthyReason: row.unhealthy_reason,
151
+ recoveryTime: row.recovery_time,
152
+ failCount: row.fail_count,
153
+ lastUsed: row.last_used,
154
+ usedCount: row.used_count,
155
+ limitCount: row.limit_count,
156
+ lastSync: row.last_sync
157
+ };
158
+ }
159
+ close() {
160
+ this.db.close();
161
+ }
162
+ }
163
+ export function createDatabase(path) {
164
+ return new KiroDatabase(path);
165
+ }
166
+ export const kiroDb = new KiroDatabase();
@@ -0,0 +1,2 @@
1
+ export { findRealTag } from './stream-parser.js';
2
+ export { transformKiroStream } from './stream-transformer.js';
@@ -0,0 +1,2 @@
1
+ export { findRealTag } from './stream-parser.js';
2
+ export { transformKiroStream } from './stream-transformer.js';
@@ -0,0 +1,2 @@
1
+ import { StreamEvent } from './types.js';
2
+ export declare function convertToOpenAI(event: StreamEvent, id: string, model: string): any;
@@ -0,0 +1,81 @@
1
+ export function convertToOpenAI(event, id, model) {
2
+ const base = {
3
+ id,
4
+ object: 'chat.completion.chunk',
5
+ created: Math.floor(Date.now() / 1000),
6
+ model,
7
+ choices: []
8
+ };
9
+ if (event.type === 'content_block_delta') {
10
+ if (event.delta.type === 'text_delta') {
11
+ base.choices.push({
12
+ index: 0,
13
+ delta: { content: event.delta.text },
14
+ finish_reason: null
15
+ });
16
+ }
17
+ else if (event.delta.type === 'thinking_delta') {
18
+ // reasoning_content is supported by some OpenAI-compatible clients;
19
+ // emit it but also skip if empty to avoid noise.
20
+ if (event.delta.thinking) {
21
+ base.choices.push({
22
+ index: 0,
23
+ delta: { reasoning_content: event.delta.thinking },
24
+ finish_reason: null
25
+ });
26
+ }
27
+ }
28
+ else if (event.delta.type === 'input_json_delta') {
29
+ base.choices.push({
30
+ index: 0,
31
+ delta: {
32
+ tool_calls: [
33
+ {
34
+ index: event.index,
35
+ function: { arguments: event.delta.partial_json }
36
+ }
37
+ ]
38
+ },
39
+ finish_reason: null
40
+ });
41
+ }
42
+ }
43
+ else if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
44
+ base.choices.push({
45
+ index: 0,
46
+ delta: {
47
+ tool_calls: [
48
+ {
49
+ index: event.index,
50
+ id: event.content_block.id,
51
+ type: 'function',
52
+ function: { name: event.content_block.name, arguments: '' }
53
+ }
54
+ ]
55
+ },
56
+ finish_reason: null
57
+ });
58
+ }
59
+ else if (event.type === 'message_delta') {
60
+ base.choices.push({
61
+ index: 0,
62
+ delta: {},
63
+ finish_reason: event.delta.stop_reason === 'tool_use' ? 'tool_calls' : 'stop'
64
+ });
65
+ base.usage = {
66
+ prompt_tokens: event.usage?.input_tokens || 0,
67
+ completion_tokens: event.usage?.output_tokens || 0,
68
+ total_tokens: (event.usage?.input_tokens || 0) + (event.usage?.output_tokens || 0)
69
+ };
70
+ }
71
+ else {
72
+ // Skip Anthropic-specific events that @ai-sdk/openai-compatible doesn't understand
73
+ // (content_block_start for text/thinking, content_block_stop, message_stop, etc.)
74
+ // Returning null signals the caller to skip this event.
75
+ return null;
76
+ }
77
+ // Don't emit chunks with empty choices — the SDK may mishandle them.
78
+ if (base.choices.length === 0)
79
+ return null;
80
+ return base;
81
+ }
@@ -0,0 +1 @@
1
+ export declare function transformSdkStream(sdkResponse: any, model: string, conversationId: string): AsyncGenerator<any>;