@kervnet/opencode-kiro-auth 1.5.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/README.md +159 -0
- package/dist/constants.d.ts +24 -0
- package/dist/constants.js +55 -0
- package/dist/core/account/account-selector.d.ts +25 -0
- package/dist/core/account/account-selector.js +84 -0
- package/dist/core/account/usage-tracker.d.ts +17 -0
- package/dist/core/account/usage-tracker.js +39 -0
- package/dist/core/auth/auth-handler.d.ts +15 -0
- package/dist/core/auth/auth-handler.js +43 -0
- package/dist/core/auth/idc-auth-method.d.ts +17 -0
- package/dist/core/auth/idc-auth-method.js +200 -0
- package/dist/core/auth/token-refresher.d.ts +22 -0
- package/dist/core/auth/token-refresher.js +53 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/request/error-handler.d.ts +30 -0
- package/dist/core/request/error-handler.js +113 -0
- package/dist/core/request/request-handler.d.ts +27 -0
- package/dist/core/request/request-handler.js +199 -0
- package/dist/core/request/response-handler.d.ts +5 -0
- package/dist/core/request/response-handler.js +61 -0
- package/dist/core/request/retry-strategy.d.ts +18 -0
- package/dist/core/request/retry-strategy.js +28 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/infrastructure/database/account-cache.d.ts +14 -0
- package/dist/infrastructure/database/account-cache.js +44 -0
- package/dist/infrastructure/database/account-repository.d.ts +12 -0
- package/dist/infrastructure/database/account-repository.js +64 -0
- package/dist/infrastructure/index.d.ts +7 -0
- package/dist/infrastructure/index.js +7 -0
- package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
- package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
- package/dist/infrastructure/transformers/history-builder.d.ts +5 -0
- package/dist/infrastructure/transformers/history-builder.js +171 -0
- package/dist/infrastructure/transformers/message-transformer.d.ts +6 -0
- package/dist/infrastructure/transformers/message-transformer.js +102 -0
- package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
- package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
- package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
- package/dist/infrastructure/transformers/tool-transformer.js +19 -0
- package/dist/kiro/auth.d.ts +4 -0
- package/dist/kiro/auth.js +25 -0
- package/dist/kiro/oauth-idc.d.ts +24 -0
- package/dist/kiro/oauth-idc.js +151 -0
- package/dist/plugin/accounts.d.ts +29 -0
- package/dist/plugin/accounts.js +235 -0
- package/dist/plugin/auth-page.d.ts +3 -0
- package/dist/plugin/auth-page.js +573 -0
- package/dist/plugin/cli.d.ts +8 -0
- package/dist/plugin/cli.js +103 -0
- package/dist/plugin/config/index.d.ts +3 -0
- package/dist/plugin/config/index.js +2 -0
- package/dist/plugin/config/loader.d.ts +6 -0
- package/dist/plugin/config/loader.js +129 -0
- package/dist/plugin/config/schema.d.ts +56 -0
- package/dist/plugin/config/schema.js +36 -0
- package/dist/plugin/errors.d.ts +17 -0
- package/dist/plugin/errors.js +34 -0
- package/dist/plugin/health.d.ts +1 -0
- package/dist/plugin/health.js +9 -0
- package/dist/plugin/image-handler.d.ts +14 -0
- package/dist/plugin/image-handler.js +64 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +63 -0
- package/dist/plugin/models.d.ts +1 -0
- package/dist/plugin/models.js +8 -0
- package/dist/plugin/request.d.ts +2 -0
- package/dist/plugin/request.js +239 -0
- package/dist/plugin/response.d.ts +3 -0
- package/dist/plugin/response.js +95 -0
- package/dist/plugin/server.d.ts +24 -0
- package/dist/plugin/server.js +166 -0
- package/dist/plugin/storage/locked-operations.d.ts +5 -0
- package/dist/plugin/storage/locked-operations.js +91 -0
- package/dist/plugin/storage/migrations.d.ts +2 -0
- package/dist/plugin/storage/migrations.js +109 -0
- package/dist/plugin/storage/sqlite.d.ts +17 -0
- package/dist/plugin/storage/sqlite.js +134 -0
- package/dist/plugin/streaming/index.d.ts +2 -0
- package/dist/plugin/streaming/index.js +2 -0
- package/dist/plugin/streaming/openai-converter.d.ts +2 -0
- package/dist/plugin/streaming/openai-converter.js +68 -0
- package/dist/plugin/streaming/stream-parser.d.ts +5 -0
- package/dist/plugin/streaming/stream-parser.js +136 -0
- package/dist/plugin/streaming/stream-state.d.ts +5 -0
- package/dist/plugin/streaming/stream-state.js +59 -0
- package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
- package/dist/plugin/streaming/stream-transformer.js +248 -0
- package/dist/plugin/streaming/types.d.ts +25 -0
- package/dist/plugin/streaming/types.js +2 -0
- package/dist/plugin/sync/aws-sso.d.ts +2 -0
- package/dist/plugin/sync/aws-sso.js +50 -0
- package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
- package/dist/plugin/sync/kiro-cli-parser.js +72 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +197 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +79 -0
- package/dist/plugin/types.d.ts +109 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +45 -0
- package/dist/plugin.d.ts +32 -0
- package/dist/plugin.js +37 -0
- package/package.json +65 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export function runMigrations(db) {
|
|
2
|
+
migrateToUniqueRefreshToken(db);
|
|
3
|
+
migrateRealEmailColumn(db);
|
|
4
|
+
migrateUsageTable(db);
|
|
5
|
+
}
|
|
6
|
+
function migrateToUniqueRefreshToken(db) {
|
|
7
|
+
const hasIndex = db
|
|
8
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_refresh_token_unique'")
|
|
9
|
+
.get();
|
|
10
|
+
if (hasIndex)
|
|
11
|
+
return;
|
|
12
|
+
db.run('BEGIN TRANSACTION');
|
|
13
|
+
try {
|
|
14
|
+
const duplicates = db
|
|
15
|
+
.prepare(`
|
|
16
|
+
SELECT refresh_token, COUNT(*) as count
|
|
17
|
+
FROM accounts
|
|
18
|
+
GROUP BY refresh_token
|
|
19
|
+
HAVING count > 1
|
|
20
|
+
`)
|
|
21
|
+
.all();
|
|
22
|
+
for (const dup of duplicates) {
|
|
23
|
+
const accounts = db
|
|
24
|
+
.prepare('SELECT * FROM accounts WHERE refresh_token = ? ORDER BY last_used DESC, expires_at DESC')
|
|
25
|
+
.all(dup.refresh_token);
|
|
26
|
+
if (accounts.length > 1) {
|
|
27
|
+
const keep = accounts[0];
|
|
28
|
+
const remove = accounts.slice(1);
|
|
29
|
+
const mergedUsedCount = Math.max(...accounts.map((a) => a.used_count || 0));
|
|
30
|
+
const mergedLimitCount = Math.max(...accounts.map((a) => a.limit_count || 0));
|
|
31
|
+
const mergedLastUsed = Math.max(...accounts.map((a) => a.last_used || 0));
|
|
32
|
+
const mergedFailCount = Math.max(...accounts.map((a) => a.fail_count || 0));
|
|
33
|
+
db.prepare(`
|
|
34
|
+
UPDATE accounts SET
|
|
35
|
+
used_count = ?,
|
|
36
|
+
limit_count = ?,
|
|
37
|
+
last_used = ?,
|
|
38
|
+
fail_count = ?
|
|
39
|
+
WHERE id = ?
|
|
40
|
+
`).run(mergedUsedCount, mergedLimitCount, mergedLastUsed, mergedFailCount, keep.id);
|
|
41
|
+
for (const acc of remove) {
|
|
42
|
+
db.prepare('DELETE FROM accounts WHERE id = ?').run(acc.id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
db.run('CREATE UNIQUE INDEX idx_refresh_token_unique ON accounts(refresh_token)');
|
|
47
|
+
db.run('COMMIT');
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
db.run('ROLLBACK');
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function migrateRealEmailColumn(db) {
|
|
55
|
+
const columns = db.prepare('PRAGMA table_info(accounts)').all();
|
|
56
|
+
const names = new Set(columns.map((c) => c.name));
|
|
57
|
+
if (names.has('real_email')) {
|
|
58
|
+
db.run('BEGIN TRANSACTION');
|
|
59
|
+
try {
|
|
60
|
+
db.run("UPDATE accounts SET email = real_email WHERE real_email IS NOT NULL AND real_email != '' AND email LIKE 'builder-id@aws.amazon.com%'");
|
|
61
|
+
db.run(`
|
|
62
|
+
CREATE TABLE accounts_new (
|
|
63
|
+
id TEXT PRIMARY KEY, email TEXT NOT NULL, auth_method TEXT NOT NULL,
|
|
64
|
+
region TEXT NOT NULL, client_id TEXT, client_secret TEXT, profile_arn TEXT,
|
|
65
|
+
refresh_token TEXT NOT NULL, access_token TEXT NOT NULL, expires_at INTEGER NOT NULL,
|
|
66
|
+
rate_limit_reset INTEGER DEFAULT 0, is_healthy INTEGER DEFAULT 1, unhealthy_reason TEXT,
|
|
67
|
+
recovery_time INTEGER, fail_count INTEGER DEFAULT 0, last_used INTEGER DEFAULT 0,
|
|
68
|
+
used_count INTEGER DEFAULT 0, limit_count INTEGER DEFAULT 0, last_sync INTEGER DEFAULT 0
|
|
69
|
+
)
|
|
70
|
+
`);
|
|
71
|
+
db.run(`
|
|
72
|
+
INSERT INTO accounts_new (id, email, auth_method, region, client_id, client_secret, profile_arn, 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)
|
|
73
|
+
SELECT id, email, auth_method, region, client_id, client_secret, profile_arn, 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
|
|
74
|
+
`);
|
|
75
|
+
db.run('DROP TABLE accounts');
|
|
76
|
+
db.run('ALTER TABLE accounts_new RENAME TO accounts');
|
|
77
|
+
db.run('COMMIT');
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
db.run('ROLLBACK');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const needed = {
|
|
85
|
+
fail_count: 'INTEGER DEFAULT 0',
|
|
86
|
+
used_count: 'INTEGER DEFAULT 0',
|
|
87
|
+
limit_count: 'INTEGER DEFAULT 0',
|
|
88
|
+
last_sync: 'INTEGER DEFAULT 0'
|
|
89
|
+
};
|
|
90
|
+
for (const [n, d] of Object.entries(needed)) {
|
|
91
|
+
if (!names.has(n))
|
|
92
|
+
db.run(`ALTER TABLE accounts ADD COLUMN ${n} ${d}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function migrateUsageTable(db) {
|
|
97
|
+
const hasUsageTable = db
|
|
98
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='usage'")
|
|
99
|
+
.get();
|
|
100
|
+
if (hasUsageTable) {
|
|
101
|
+
db.run(`
|
|
102
|
+
UPDATE accounts SET
|
|
103
|
+
used_count = COALESCE((SELECT used_count FROM usage WHERE usage.account_id = accounts.id), used_count),
|
|
104
|
+
limit_count = COALESCE((SELECT limit_count FROM usage WHERE usage.account_id = accounts.id), limit_count),
|
|
105
|
+
last_sync = COALESCE((SELECT last_sync FROM usage WHERE usage.account_id = accounts.id), last_sync)
|
|
106
|
+
`);
|
|
107
|
+
db.run('DROP TABLE usage');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ManagedAccount } from '../types';
|
|
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
|
+
private rowToAccount;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare function createDatabase(path?: string): KiroDatabase;
|
|
17
|
+
export declare const kiroDb: KiroDatabase;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
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';
|
|
6
|
+
import { runMigrations } from './migrations';
|
|
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.run('PRAGMA busy_timeout = 5000');
|
|
24
|
+
this.init();
|
|
25
|
+
}
|
|
26
|
+
init() {
|
|
27
|
+
this.db.run('PRAGMA journal_mode = WAL');
|
|
28
|
+
this.db.run(`
|
|
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, client_id TEXT, client_secret TEXT, profile_arn TEXT,
|
|
32
|
+
refresh_token TEXT NOT NULL, access_token TEXT NOT NULL, expires_at INTEGER NOT NULL,
|
|
33
|
+
rate_limit_reset INTEGER DEFAULT 0, is_healthy INTEGER DEFAULT 1, unhealthy_reason TEXT,
|
|
34
|
+
recovery_time INTEGER, fail_count INTEGER DEFAULT 0, last_used INTEGER DEFAULT 0,
|
|
35
|
+
used_count INTEGER DEFAULT 0, limit_count INTEGER DEFAULT 0, last_sync INTEGER DEFAULT 0
|
|
36
|
+
)
|
|
37
|
+
`);
|
|
38
|
+
runMigrations(this.db);
|
|
39
|
+
}
|
|
40
|
+
getAccounts() {
|
|
41
|
+
return this.db.prepare('SELECT * FROM accounts').all();
|
|
42
|
+
}
|
|
43
|
+
upsertAccountInternal(acc) {
|
|
44
|
+
this.db
|
|
45
|
+
.prepare(`
|
|
46
|
+
INSERT INTO accounts (
|
|
47
|
+
id, email, auth_method, region, client_id, client_secret,
|
|
48
|
+
profile_arn, refresh_token, access_token, expires_at, rate_limit_reset,
|
|
49
|
+
is_healthy, unhealthy_reason, recovery_time, fail_count, last_used,
|
|
50
|
+
used_count, limit_count, last_sync
|
|
51
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
52
|
+
ON CONFLICT(refresh_token) DO UPDATE SET
|
|
53
|
+
id=excluded.id, email=excluded.email, auth_method=excluded.auth_method,
|
|
54
|
+
region=excluded.region, client_id=excluded.client_id, client_secret=excluded.client_secret,
|
|
55
|
+
profile_arn=excluded.profile_arn, access_token=excluded.access_token, expires_at=excluded.expires_at,
|
|
56
|
+
rate_limit_reset=excluded.rate_limit_reset, is_healthy=excluded.is_healthy,
|
|
57
|
+
unhealthy_reason=excluded.unhealthy_reason, recovery_time=excluded.recovery_time,
|
|
58
|
+
fail_count=excluded.fail_count, last_used=excluded.last_used,
|
|
59
|
+
used_count=excluded.used_count, limit_count=excluded.limit_count, last_sync=excluded.last_sync
|
|
60
|
+
`)
|
|
61
|
+
.run(acc.id, acc.email, acc.authMethod, acc.region, acc.clientId || null, acc.clientSecret || null, acc.profileArn || 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);
|
|
62
|
+
}
|
|
63
|
+
async upsertAccount(acc) {
|
|
64
|
+
await withDatabaseLock(this.path, async () => {
|
|
65
|
+
const existing = this.getAccounts().map(this.rowToAccount);
|
|
66
|
+
const merged = mergeAccounts(existing, [acc]);
|
|
67
|
+
const deduplicated = deduplicateAccounts(merged);
|
|
68
|
+
this.db.run('BEGIN TRANSACTION');
|
|
69
|
+
try {
|
|
70
|
+
for (const account of deduplicated) {
|
|
71
|
+
this.upsertAccountInternal(account);
|
|
72
|
+
}
|
|
73
|
+
this.db.run('COMMIT');
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
this.db.run('ROLLBACK');
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async batchUpsertAccounts(accounts) {
|
|
82
|
+
await withDatabaseLock(this.path, async () => {
|
|
83
|
+
const existing = this.getAccounts().map(this.rowToAccount);
|
|
84
|
+
const merged = mergeAccounts(existing, accounts);
|
|
85
|
+
const deduplicated = deduplicateAccounts(merged);
|
|
86
|
+
this.db.run('BEGIN TRANSACTION');
|
|
87
|
+
try {
|
|
88
|
+
for (const account of deduplicated) {
|
|
89
|
+
this.upsertAccountInternal(account);
|
|
90
|
+
}
|
|
91
|
+
this.db.run('COMMIT');
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
this.db.run('ROLLBACK');
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async deleteAccount(id) {
|
|
100
|
+
await withDatabaseLock(this.path, async () => {
|
|
101
|
+
this.db.prepare('DELETE FROM accounts WHERE id = ?').run(id);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
rowToAccount(row) {
|
|
105
|
+
return {
|
|
106
|
+
id: row.id,
|
|
107
|
+
email: row.email,
|
|
108
|
+
authMethod: row.auth_method,
|
|
109
|
+
region: row.region,
|
|
110
|
+
clientId: row.client_id,
|
|
111
|
+
clientSecret: row.client_secret,
|
|
112
|
+
profileArn: row.profile_arn,
|
|
113
|
+
refreshToken: row.refresh_token,
|
|
114
|
+
accessToken: row.access_token,
|
|
115
|
+
expiresAt: row.expires_at,
|
|
116
|
+
rateLimitResetTime: row.rate_limit_reset,
|
|
117
|
+
isHealthy: row.is_healthy === 1,
|
|
118
|
+
unhealthyReason: row.unhealthy_reason,
|
|
119
|
+
recoveryTime: row.recovery_time,
|
|
120
|
+
failCount: row.fail_count,
|
|
121
|
+
lastUsed: row.last_used,
|
|
122
|
+
usedCount: row.used_count,
|
|
123
|
+
limitCount: row.limit_count,
|
|
124
|
+
lastSync: row.last_sync
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
close() {
|
|
128
|
+
this.db.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function createDatabase(path) {
|
|
132
|
+
return new KiroDatabase(path);
|
|
133
|
+
}
|
|
134
|
+
export const kiroDb = new KiroDatabase();
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
base.choices.push({
|
|
19
|
+
index: 0,
|
|
20
|
+
delta: { reasoning_content: event.delta.thinking },
|
|
21
|
+
finish_reason: null
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (event.delta.type === 'input_json_delta') {
|
|
25
|
+
base.choices.push({
|
|
26
|
+
index: 0,
|
|
27
|
+
delta: {
|
|
28
|
+
tool_calls: [
|
|
29
|
+
{
|
|
30
|
+
index: event.index,
|
|
31
|
+
function: { arguments: event.delta.partial_json }
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
finish_reason: null
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
|
40
|
+
base.choices.push({
|
|
41
|
+
index: 0,
|
|
42
|
+
delta: {
|
|
43
|
+
tool_calls: [
|
|
44
|
+
{
|
|
45
|
+
index: event.index,
|
|
46
|
+
id: event.content_block.id,
|
|
47
|
+
type: 'function',
|
|
48
|
+
function: { name: event.content_block.name, arguments: '' }
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
finish_reason: null
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else if (event.type === 'message_delta') {
|
|
56
|
+
base.choices.push({
|
|
57
|
+
index: 0,
|
|
58
|
+
delta: {},
|
|
59
|
+
finish_reason: event.delta.stop_reason === 'tool_use' ? 'tool_calls' : 'stop'
|
|
60
|
+
});
|
|
61
|
+
base.usage = {
|
|
62
|
+
prompt_tokens: event.usage?.input_tokens || 0,
|
|
63
|
+
completion_tokens: event.usage?.output_tokens || 0,
|
|
64
|
+
total_tokens: (event.usage?.input_tokens || 0) + (event.usage?.output_tokens || 0)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return base;
|
|
68
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { parseEventLine } from '../../infrastructure/transformers/event-stream-parser.js';
|
|
2
|
+
export function parseStreamBuffer(buffer) {
|
|
3
|
+
const events = [];
|
|
4
|
+
let remaining = buffer;
|
|
5
|
+
let searchStart = 0;
|
|
6
|
+
while (true) {
|
|
7
|
+
const contentStart = remaining.indexOf('{"content":', searchStart);
|
|
8
|
+
const nameStart = remaining.indexOf('{"name":', searchStart);
|
|
9
|
+
const followupStart = remaining.indexOf('{"followupPrompt":', searchStart);
|
|
10
|
+
const inputStart = remaining.indexOf('{"input":', searchStart);
|
|
11
|
+
const stopStart = remaining.indexOf('{"stop":', searchStart);
|
|
12
|
+
const contextUsageStart = remaining.indexOf('{"contextUsagePercentage":', searchStart);
|
|
13
|
+
const candidates = [
|
|
14
|
+
contentStart,
|
|
15
|
+
nameStart,
|
|
16
|
+
followupStart,
|
|
17
|
+
inputStart,
|
|
18
|
+
stopStart,
|
|
19
|
+
contextUsageStart
|
|
20
|
+
].filter((pos) => pos >= 0);
|
|
21
|
+
if (candidates.length === 0)
|
|
22
|
+
break;
|
|
23
|
+
const jsonStart = Math.min(...candidates);
|
|
24
|
+
if (jsonStart < 0)
|
|
25
|
+
break;
|
|
26
|
+
let braceCount = 0;
|
|
27
|
+
let jsonEnd = -1;
|
|
28
|
+
let inString = false;
|
|
29
|
+
let escapeNext = false;
|
|
30
|
+
for (let i = jsonStart; i < remaining.length; i++) {
|
|
31
|
+
const char = remaining[i];
|
|
32
|
+
if (escapeNext) {
|
|
33
|
+
escapeNext = false;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (char === '\\') {
|
|
37
|
+
escapeNext = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (char === '"') {
|
|
41
|
+
inString = !inString;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!inString) {
|
|
45
|
+
if (char === '{') {
|
|
46
|
+
braceCount++;
|
|
47
|
+
}
|
|
48
|
+
else if (char === '}') {
|
|
49
|
+
braceCount--;
|
|
50
|
+
if (braceCount === 0) {
|
|
51
|
+
jsonEnd = i;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (jsonEnd < 0) {
|
|
58
|
+
remaining = remaining.substring(jsonStart);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const jsonStr = remaining.substring(jsonStart, jsonEnd + 1);
|
|
62
|
+
const parsed = parseEventLine(jsonStr);
|
|
63
|
+
if (parsed) {
|
|
64
|
+
if (parsed.content !== undefined && !parsed.followupPrompt) {
|
|
65
|
+
events.push({ type: 'content', data: parsed.content });
|
|
66
|
+
}
|
|
67
|
+
else if (parsed.name && parsed.toolUseId) {
|
|
68
|
+
events.push({
|
|
69
|
+
type: 'toolUse',
|
|
70
|
+
data: {
|
|
71
|
+
name: parsed.name,
|
|
72
|
+
toolUseId: parsed.toolUseId,
|
|
73
|
+
input: parsed.input || '',
|
|
74
|
+
stop: parsed.stop || false
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (parsed.input !== undefined && !parsed.name) {
|
|
79
|
+
events.push({
|
|
80
|
+
type: 'toolUseInput',
|
|
81
|
+
data: {
|
|
82
|
+
input: parsed.input
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined) {
|
|
87
|
+
events.push({
|
|
88
|
+
type: 'toolUseStop',
|
|
89
|
+
data: {
|
|
90
|
+
stop: parsed.stop
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else if (parsed.contextUsagePercentage !== undefined) {
|
|
95
|
+
events.push({
|
|
96
|
+
type: 'contextUsage',
|
|
97
|
+
data: {
|
|
98
|
+
contextUsagePercentage: parsed.contextUsagePercentage
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
searchStart = jsonEnd + 1;
|
|
104
|
+
if (searchStart >= remaining.length) {
|
|
105
|
+
remaining = '';
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (searchStart > 0 && remaining.length > 0) {
|
|
110
|
+
remaining = remaining.substring(searchStart);
|
|
111
|
+
}
|
|
112
|
+
return { events, remaining };
|
|
113
|
+
}
|
|
114
|
+
export function findRealTag(buffer, tag) {
|
|
115
|
+
const codeBlockPattern = /```[\s\S]*?```/g;
|
|
116
|
+
const codeBlocks = [];
|
|
117
|
+
let match;
|
|
118
|
+
while ((match = codeBlockPattern.exec(buffer)) !== null) {
|
|
119
|
+
codeBlocks.push([match.index, match.index + match[0].length]);
|
|
120
|
+
}
|
|
121
|
+
let pos = 0;
|
|
122
|
+
while ((pos = buffer.indexOf(tag, pos)) !== -1) {
|
|
123
|
+
let inCodeBlock = false;
|
|
124
|
+
for (const [start, end] of codeBlocks) {
|
|
125
|
+
if (pos >= start && pos < end) {
|
|
126
|
+
inCodeBlock = true;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!inCodeBlock) {
|
|
131
|
+
return pos;
|
|
132
|
+
}
|
|
133
|
+
pos += tag.length;
|
|
134
|
+
}
|
|
135
|
+
return -1;
|
|
136
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { StreamEvent, StreamState } from './types.js';
|
|
2
|
+
export declare function ensureBlockStart(blockType: 'thinking' | 'text', streamState: StreamState): StreamEvent[];
|
|
3
|
+
export declare function stopBlock(index: number | null, streamState: StreamState): StreamEvent[];
|
|
4
|
+
export declare function createTextDeltaEvents(text: string, streamState: StreamState): StreamEvent[];
|
|
5
|
+
export declare function createThinkingDeltaEvents(thinking: string, streamState: StreamState): StreamEvent[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function ensureBlockStart(blockType, streamState) {
|
|
2
|
+
if (blockType === 'thinking') {
|
|
3
|
+
if (streamState.thinkingBlockIndex != null)
|
|
4
|
+
return [];
|
|
5
|
+
const idx = streamState.nextBlockIndex++;
|
|
6
|
+
streamState.thinkingBlockIndex = idx;
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
type: 'content_block_start',
|
|
10
|
+
index: idx,
|
|
11
|
+
content_block: { type: 'thinking', thinking: '' }
|
|
12
|
+
}
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
if (blockType === 'text') {
|
|
16
|
+
if (streamState.textBlockIndex != null)
|
|
17
|
+
return [];
|
|
18
|
+
const idx = streamState.nextBlockIndex++;
|
|
19
|
+
streamState.textBlockIndex = idx;
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
type: 'content_block_start',
|
|
23
|
+
index: idx,
|
|
24
|
+
content_block: { type: 'text', text: '' }
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
export function stopBlock(index, streamState) {
|
|
31
|
+
if (index == null)
|
|
32
|
+
return [];
|
|
33
|
+
if (streamState.stoppedBlocks.has(index))
|
|
34
|
+
return [];
|
|
35
|
+
streamState.stoppedBlocks.add(index);
|
|
36
|
+
return [{ type: 'content_block_stop', index }];
|
|
37
|
+
}
|
|
38
|
+
export function createTextDeltaEvents(text, streamState) {
|
|
39
|
+
if (!text)
|
|
40
|
+
return [];
|
|
41
|
+
const events = [];
|
|
42
|
+
events.push(...ensureBlockStart('text', streamState));
|
|
43
|
+
events.push({
|
|
44
|
+
type: 'content_block_delta',
|
|
45
|
+
index: streamState.textBlockIndex,
|
|
46
|
+
delta: { type: 'text_delta', text }
|
|
47
|
+
});
|
|
48
|
+
return events;
|
|
49
|
+
}
|
|
50
|
+
export function createThinkingDeltaEvents(thinking, streamState) {
|
|
51
|
+
const events = [];
|
|
52
|
+
events.push(...ensureBlockStart('thinking', streamState));
|
|
53
|
+
events.push({
|
|
54
|
+
type: 'content_block_delta',
|
|
55
|
+
index: streamState.thinkingBlockIndex,
|
|
56
|
+
delta: { type: 'thinking_delta', thinking }
|
|
57
|
+
});
|
|
58
|
+
return events;
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function transformKiroStream(response: Response, model: string, conversationId: string): AsyncGenerator<any>;
|