@zhafron/opencode-kiro-auth 1.0.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 (110) hide show
  1. package/README.md +85 -0
  2. package/dist/constants.d.ts +26 -0
  3. package/dist/constants.js +60 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +1 -0
  6. package/dist/kiro/auth.d.ts +5 -0
  7. package/dist/kiro/auth.js +24 -0
  8. package/dist/kiro/oauth-idc.d.ts +24 -0
  9. package/dist/kiro/oauth-idc.js +132 -0
  10. package/dist/kiro/oauth-social.d.ts +17 -0
  11. package/dist/kiro/oauth-social.js +51 -0
  12. package/dist/plugin/accounts.d.ts +28 -0
  13. package/dist/plugin/accounts.js +156 -0
  14. package/dist/plugin/auth-page.d.ts +3 -0
  15. package/dist/plugin/auth-page.js +568 -0
  16. package/dist/plugin/cli.d.ts +6 -0
  17. package/dist/plugin/cli.js +98 -0
  18. package/dist/plugin/config/index.d.ts +3 -0
  19. package/dist/plugin/config/index.js +2 -0
  20. package/dist/plugin/config/loader.d.ts +6 -0
  21. package/dist/plugin/config/loader.js +125 -0
  22. package/dist/plugin/config/schema.d.ts +44 -0
  23. package/dist/plugin/config/schema.js +28 -0
  24. package/dist/plugin/debug.d.ts +2 -0
  25. package/dist/plugin/debug.js +9 -0
  26. package/dist/plugin/errors.d.ts +17 -0
  27. package/dist/plugin/errors.js +34 -0
  28. package/dist/plugin/logger.d.ts +4 -0
  29. package/dist/plugin/logger.js +37 -0
  30. package/dist/plugin/models.d.ts +3 -0
  31. package/dist/plugin/models.js +14 -0
  32. package/dist/plugin/oauth-parser.d.ts +5 -0
  33. package/dist/plugin/oauth-parser.js +23 -0
  34. package/dist/plugin/quota.d.ts +15 -0
  35. package/dist/plugin/quota.js +68 -0
  36. package/dist/plugin/recovery.d.ts +19 -0
  37. package/dist/plugin/recovery.js +302 -0
  38. package/dist/plugin/refresh-queue.d.ts +14 -0
  39. package/dist/plugin/refresh-queue.js +69 -0
  40. package/dist/plugin/request.d.ts +4 -0
  41. package/dist/plugin/request.js +240 -0
  42. package/dist/plugin/response.d.ts +6 -0
  43. package/dist/plugin/response.js +246 -0
  44. package/dist/plugin/server.d.ts +24 -0
  45. package/dist/plugin/server.js +96 -0
  46. package/dist/plugin/storage.d.ts +7 -0
  47. package/dist/plugin/storage.js +75 -0
  48. package/dist/plugin/streaming.d.ts +3 -0
  49. package/dist/plugin/streaming.js +503 -0
  50. package/dist/plugin/token.d.ts +2 -0
  51. package/dist/plugin/token.js +56 -0
  52. package/dist/plugin/types.d.ts +148 -0
  53. package/dist/plugin/types.js +0 -0
  54. package/dist/plugin/usage.d.ts +3 -0
  55. package/dist/plugin/usage.js +36 -0
  56. package/dist/plugin.d.ts +32 -0
  57. package/dist/plugin.js +222 -0
  58. package/dist/src/constants.d.ts +22 -0
  59. package/dist/src/constants.js +35 -0
  60. package/dist/src/kiro/auth.d.ts +5 -0
  61. package/dist/src/kiro/auth.js +69 -0
  62. package/dist/src/kiro/oauth-idc.d.ts +22 -0
  63. package/dist/src/kiro/oauth-idc.js +99 -0
  64. package/dist/src/kiro/oauth-social.d.ts +17 -0
  65. package/dist/src/kiro/oauth-social.js +69 -0
  66. package/dist/src/plugin/accounts.d.ts +23 -0
  67. package/dist/src/plugin/accounts.js +265 -0
  68. package/dist/src/plugin/cli.d.ts +6 -0
  69. package/dist/src/plugin/cli.js +98 -0
  70. package/dist/src/plugin/config/index.d.ts +3 -0
  71. package/dist/src/plugin/config/index.js +2 -0
  72. package/dist/src/plugin/config/loader.d.ts +7 -0
  73. package/dist/src/plugin/config/loader.js +143 -0
  74. package/dist/src/plugin/config/schema.d.ts +68 -0
  75. package/dist/src/plugin/config/schema.js +44 -0
  76. package/dist/src/plugin/debug.d.ts +2 -0
  77. package/dist/src/plugin/debug.js +9 -0
  78. package/dist/src/plugin/errors.d.ts +17 -0
  79. package/dist/src/plugin/errors.js +34 -0
  80. package/dist/src/plugin/logger.d.ts +4 -0
  81. package/dist/src/plugin/logger.js +17 -0
  82. package/dist/src/plugin/models.d.ts +3 -0
  83. package/dist/src/plugin/models.js +14 -0
  84. package/dist/src/plugin/oauth-parser.d.ts +5 -0
  85. package/dist/src/plugin/oauth-parser.js +23 -0
  86. package/dist/src/plugin/quota.d.ts +25 -0
  87. package/dist/src/plugin/quota.js +175 -0
  88. package/dist/src/plugin/recovery.d.ts +19 -0
  89. package/dist/src/plugin/recovery.js +302 -0
  90. package/dist/src/plugin/refresh-queue.d.ts +14 -0
  91. package/dist/src/plugin/refresh-queue.js +69 -0
  92. package/dist/src/plugin/request.d.ts +35 -0
  93. package/dist/src/plugin/request.js +411 -0
  94. package/dist/src/plugin/response.d.ts +6 -0
  95. package/dist/src/plugin/response.js +246 -0
  96. package/dist/src/plugin/server.d.ts +10 -0
  97. package/dist/src/plugin/server.js +203 -0
  98. package/dist/src/plugin/storage.d.ts +5 -0
  99. package/dist/src/plugin/storage.js +106 -0
  100. package/dist/src/plugin/streaming.d.ts +12 -0
  101. package/dist/src/plugin/streaming.js +444 -0
  102. package/dist/src/plugin/token.d.ts +8 -0
  103. package/dist/src/plugin/token.js +130 -0
  104. package/dist/src/plugin/types.d.ts +144 -0
  105. package/dist/src/plugin/types.js +0 -0
  106. package/dist/src/plugin/usage.d.ts +28 -0
  107. package/dist/src/plugin/usage.js +159 -0
  108. package/dist/src/plugin.d.ts +2 -0
  109. package/dist/src/plugin.js +341 -0
  110. package/package.json +57 -0
@@ -0,0 +1,246 @@
1
+ export function parseEventStream(rawResponse) {
2
+ const parsedFromEvents = parseEventStreamChunk(rawResponse);
3
+ let fullResponseText = parsedFromEvents.content;
4
+ let allToolCalls = [...parsedFromEvents.toolCalls];
5
+ const rawBracketToolCalls = parseBracketToolCalls(rawResponse);
6
+ if (rawBracketToolCalls.length > 0) {
7
+ allToolCalls.push(...rawBracketToolCalls);
8
+ }
9
+ const uniqueToolCalls = deduplicateToolCalls(allToolCalls);
10
+ if (uniqueToolCalls.length > 0) {
11
+ fullResponseText = cleanToolCallsFromText(fullResponseText, uniqueToolCalls);
12
+ }
13
+ return {
14
+ content: fullResponseText,
15
+ toolCalls: uniqueToolCalls,
16
+ stopReason: parsedFromEvents.stopReason,
17
+ inputTokens: parsedFromEvents.inputTokens,
18
+ outputTokens: parsedFromEvents.outputTokens
19
+ };
20
+ }
21
+ function parseEventStreamChunk(rawText) {
22
+ const events = parseAwsEventStreamBuffer(rawText);
23
+ let content = '';
24
+ const toolCallsMap = new Map();
25
+ let stopReason;
26
+ let inputTokens;
27
+ let outputTokens;
28
+ let contextUsagePercentage;
29
+ for (const event of events) {
30
+ if (event.type === 'content' && event.data) {
31
+ content += event.data;
32
+ }
33
+ else if (event.type === 'toolUse') {
34
+ const { name, toolUseId, input } = event.data;
35
+ if (name && toolUseId) {
36
+ if (toolCallsMap.has(toolUseId)) {
37
+ const existing = toolCallsMap.get(toolUseId);
38
+ existing.input = existing.input + (input || '');
39
+ }
40
+ else {
41
+ toolCallsMap.set(toolUseId, {
42
+ toolUseId,
43
+ name,
44
+ input: input || ''
45
+ });
46
+ }
47
+ }
48
+ }
49
+ else if (event.type === 'toolUseInput') {
50
+ const lastToolCall = Array.from(toolCallsMap.values()).pop();
51
+ if (lastToolCall) {
52
+ lastToolCall.input = lastToolCall.input + (event.data.input || '');
53
+ }
54
+ }
55
+ else if (event.type === 'toolUseStop') {
56
+ stopReason = 'tool_use';
57
+ }
58
+ else if (event.type === 'contextUsage') {
59
+ contextUsagePercentage = event.data.contextUsagePercentage;
60
+ }
61
+ }
62
+ const toolCalls = Array.from(toolCallsMap.values()).map((tc) => {
63
+ let parsedInput = tc.input;
64
+ if (typeof tc.input === 'string' && tc.input.trim()) {
65
+ try {
66
+ parsedInput = JSON.parse(tc.input);
67
+ }
68
+ catch (e) {
69
+ parsedInput = tc.input;
70
+ }
71
+ }
72
+ return {
73
+ toolUseId: tc.toolUseId,
74
+ name: tc.name,
75
+ input: parsedInput
76
+ };
77
+ });
78
+ if (contextUsagePercentage !== undefined) {
79
+ const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
80
+ outputTokens = estimateTokens(content);
81
+ inputTokens = Math.max(0, totalTokens - outputTokens);
82
+ }
83
+ return {
84
+ content,
85
+ toolCalls,
86
+ stopReason: stopReason || (toolCalls.length > 0 ? 'tool_use' : 'end_turn'),
87
+ inputTokens,
88
+ outputTokens
89
+ };
90
+ }
91
+ function parseAwsEventStreamBuffer(buffer) {
92
+ const events = [];
93
+ let remaining = buffer;
94
+ let searchStart = 0;
95
+ while (true) {
96
+ const contentStart = remaining.indexOf('{"content":', searchStart);
97
+ const nameStart = remaining.indexOf('{"name":', searchStart);
98
+ const followupStart = remaining.indexOf('{"followupPrompt":', searchStart);
99
+ const inputStart = remaining.indexOf('{"input":', searchStart);
100
+ const stopStart = remaining.indexOf('{"stop":', searchStart);
101
+ const contextUsageStart = remaining.indexOf('{"contextUsagePercentage":', searchStart);
102
+ const candidates = [contentStart, nameStart, followupStart, inputStart, stopStart, contextUsageStart].filter((pos) => pos >= 0);
103
+ if (candidates.length === 0)
104
+ break;
105
+ const jsonStart = Math.min(...candidates);
106
+ if (jsonStart < 0)
107
+ break;
108
+ let braceCount = 0;
109
+ let jsonEnd = -1;
110
+ let inString = false;
111
+ let escapeNext = false;
112
+ for (let i = jsonStart; i < remaining.length; i++) {
113
+ const char = remaining[i];
114
+ if (escapeNext) {
115
+ escapeNext = false;
116
+ continue;
117
+ }
118
+ if (char === '\\') {
119
+ escapeNext = true;
120
+ continue;
121
+ }
122
+ if (char === '"') {
123
+ inString = !inString;
124
+ continue;
125
+ }
126
+ if (!inString) {
127
+ if (char === '{') {
128
+ braceCount++;
129
+ }
130
+ else if (char === '}') {
131
+ braceCount--;
132
+ if (braceCount === 0) {
133
+ jsonEnd = i;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ if (jsonEnd < 0) {
140
+ break;
141
+ }
142
+ const jsonStr = remaining.substring(jsonStart, jsonEnd + 1);
143
+ const parsed = parseEventLine(jsonStr);
144
+ if (parsed) {
145
+ if (parsed.content !== undefined && !parsed.followupPrompt) {
146
+ events.push({ type: 'content', data: parsed.content });
147
+ }
148
+ else if (parsed.name && parsed.toolUseId) {
149
+ events.push({
150
+ type: 'toolUse',
151
+ data: {
152
+ name: parsed.name,
153
+ toolUseId: parsed.toolUseId,
154
+ input: parsed.input || '',
155
+ stop: parsed.stop || false
156
+ }
157
+ });
158
+ }
159
+ else if (parsed.input !== undefined && !parsed.name) {
160
+ events.push({
161
+ type: 'toolUseInput',
162
+ data: {
163
+ input: parsed.input
164
+ }
165
+ });
166
+ }
167
+ else if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined) {
168
+ events.push({
169
+ type: 'toolUseStop',
170
+ data: {
171
+ stop: parsed.stop
172
+ }
173
+ });
174
+ }
175
+ else if (parsed.contextUsagePercentage !== undefined) {
176
+ events.push({
177
+ type: 'contextUsage',
178
+ data: {
179
+ contextUsagePercentage: parsed.contextUsagePercentage
180
+ }
181
+ });
182
+ }
183
+ }
184
+ searchStart = jsonEnd + 1;
185
+ if (searchStart >= remaining.length) {
186
+ break;
187
+ }
188
+ }
189
+ return events;
190
+ }
191
+ export function parseEventLine(line) {
192
+ try {
193
+ return JSON.parse(line);
194
+ }
195
+ catch (e) {
196
+ return null;
197
+ }
198
+ }
199
+ export function parseBracketToolCalls(text) {
200
+ const toolCalls = [];
201
+ const pattern = /\[Called\s+(\w+)\s+with\s+args:\s*(\{[^}]*(?:\{[^}]*\}[^}]*)*\})\]/gs;
202
+ let match;
203
+ while ((match = pattern.exec(text)) !== null) {
204
+ const funcName = match[1];
205
+ const argsStr = match[2];
206
+ if (!funcName || !argsStr)
207
+ continue;
208
+ try {
209
+ const args = JSON.parse(argsStr);
210
+ toolCalls.push({
211
+ toolUseId: `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
212
+ name: funcName,
213
+ input: args
214
+ });
215
+ }
216
+ catch (e) {
217
+ continue;
218
+ }
219
+ }
220
+ return toolCalls;
221
+ }
222
+ export function deduplicateToolCalls(toolCalls) {
223
+ const seen = new Set();
224
+ const unique = [];
225
+ for (const tc of toolCalls) {
226
+ if (!seen.has(tc.toolUseId)) {
227
+ seen.add(tc.toolUseId);
228
+ unique.push(tc);
229
+ }
230
+ }
231
+ return unique;
232
+ }
233
+ export function cleanToolCallsFromText(text, toolCalls) {
234
+ let cleaned = text;
235
+ for (const tc of toolCalls) {
236
+ const funcName = tc.name;
237
+ const escapedName = funcName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
238
+ const pattern = new RegExp(`\\[Called\\s+${escapedName}\\s+with\\s+args:\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\}\\]`, 'gs');
239
+ cleaned = cleaned.replace(pattern, '');
240
+ }
241
+ cleaned = cleaned.replace(/\s+/g, ' ').trim();
242
+ return cleaned;
243
+ }
244
+ function estimateTokens(text) {
245
+ return Math.ceil(text.length / 4);
246
+ }
@@ -0,0 +1,24 @@
1
+ import type { KiroRegion } from './types';
2
+ export interface KiroIDCTokenResult {
3
+ email: string;
4
+ accessToken: string;
5
+ refreshToken: string;
6
+ expiresAt: number;
7
+ clientId: string;
8
+ clientSecret: string;
9
+ }
10
+ export interface IDCAuthData {
11
+ verificationUrl: string;
12
+ verificationUriComplete: string;
13
+ userCode: string;
14
+ deviceCode: string;
15
+ clientId: string;
16
+ clientSecret: string;
17
+ interval: number;
18
+ expiresIn: number;
19
+ region: KiroRegion;
20
+ }
21
+ export declare function startIDCAuthServer(authData: IDCAuthData, port?: number): Promise<{
22
+ url: string;
23
+ waitForAuth: () => Promise<KiroIDCTokenResult>;
24
+ }>;
@@ -0,0 +1,96 @@
1
+ import { createServer } from 'node:http';
2
+ import { getIDCAuthHtml, getSuccessHtml, getErrorHtml } from './auth-page';
3
+ export function startIDCAuthServer(authData, port = 19847) {
4
+ return new Promise((resolve, reject) => {
5
+ let server = null;
6
+ let timeoutId = null;
7
+ let resolver = null;
8
+ let rejector = null;
9
+ const status = { status: 'pending' };
10
+ const cleanup = () => {
11
+ if (timeoutId)
12
+ clearTimeout(timeoutId);
13
+ if (server)
14
+ server.close();
15
+ };
16
+ const sendHtml = (res, html) => {
17
+ res.writeHead(200, { 'Content-Type': 'text/html' });
18
+ res.end(html);
19
+ };
20
+ const poll = async () => {
21
+ try {
22
+ const body = new URLSearchParams({
23
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
24
+ device_code: authData.deviceCode,
25
+ client_id: authData.clientId,
26
+ client_secret: authData.clientSecret
27
+ });
28
+ const res = await fetch(`https://oidc.${authData.region}.amazonaws.com/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() });
29
+ const d = await res.json();
30
+ if (res.ok) {
31
+ const acc = d.access_token, ref = d.refresh_token, exp = Date.now() + d.expires_in * 1000;
32
+ const infoRes = await fetch('https://view.awsapps.com/api/user/info', { headers: { Authorization: `Bearer ${acc}` } });
33
+ const info = await infoRes.json();
34
+ const email = info.email || info.userName || 'builder-id@aws.amazon.com';
35
+ status.status = 'success';
36
+ if (resolver)
37
+ resolver({ email, accessToken: acc, refreshToken: ref, expiresAt: exp, clientId: authData.clientId, clientSecret: authData.clientSecret });
38
+ setTimeout(cleanup, 2000);
39
+ }
40
+ else if (d.error === 'authorization_pending')
41
+ setTimeout(poll, authData.interval * 1000);
42
+ else {
43
+ status.status = 'failed';
44
+ status.error = d.error_description || d.error;
45
+ if (rejector)
46
+ rejector(new Error(status.error));
47
+ setTimeout(cleanup, 2000);
48
+ }
49
+ }
50
+ catch (e) {
51
+ status.status = 'failed';
52
+ status.error = e.message;
53
+ if (rejector)
54
+ rejector(e);
55
+ setTimeout(cleanup, 2000);
56
+ }
57
+ };
58
+ server = createServer((req, res) => {
59
+ const u = req.url || '';
60
+ if (u === '/' || u.startsWith('/?'))
61
+ sendHtml(res, getIDCAuthHtml(authData.verificationUriComplete, authData.userCode, `http://127.0.0.1:${port}/status`));
62
+ else if (u === '/status') {
63
+ res.writeHead(200, { 'Content-Type': 'application/json' });
64
+ res.end(JSON.stringify(status));
65
+ }
66
+ else if (u === '/success')
67
+ sendHtml(res, getSuccessHtml());
68
+ else if (u === '/error')
69
+ sendHtml(res, getErrorHtml(status.error || 'Failed'));
70
+ else {
71
+ res.writeHead(404);
72
+ res.end();
73
+ }
74
+ });
75
+ server.on('error', (e) => {
76
+ cleanup();
77
+ reject(e);
78
+ });
79
+ server.listen(port, '127.0.0.1', () => {
80
+ timeoutId = setTimeout(() => {
81
+ status.status = 'timeout';
82
+ if (rejector)
83
+ rejector(new Error('Timeout'));
84
+ cleanup();
85
+ }, 900000);
86
+ poll();
87
+ resolve({
88
+ url: `http://127.0.0.1:${port}`,
89
+ waitForAuth: () => new Promise((rv, rj) => {
90
+ resolver = rv;
91
+ rejector = rj;
92
+ })
93
+ });
94
+ });
95
+ });
96
+ }
@@ -0,0 +1,7 @@
1
+ import type { AccountStorage, UsageStorage } from './types';
2
+ export declare function getStoragePath(): string;
3
+ export declare function getUsagePath(): string;
4
+ export declare function loadAccounts(): Promise<AccountStorage>;
5
+ export declare function saveAccounts(storage: AccountStorage): Promise<void>;
6
+ export declare function loadUsage(): Promise<UsageStorage>;
7
+ export declare function saveUsage(storage: UsageStorage): Promise<void>;
@@ -0,0 +1,75 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import lockfile from 'proper-lockfile';
5
+ import { xdgConfig } from 'xdg-basedir';
6
+ import * as logger from './logger';
7
+ const LOCK_OPTIONS = {
8
+ stale: 10000,
9
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 1000, factor: 2 }
10
+ };
11
+ function getBaseDir() {
12
+ return join(xdgConfig || join(process.env.HOME || '', '.config'), 'opencode');
13
+ }
14
+ export function getStoragePath() {
15
+ return join(getBaseDir(), 'kiro-accounts.json');
16
+ }
17
+ export function getUsagePath() {
18
+ return join(getBaseDir(), 'kiro-usage.json');
19
+ }
20
+ async function withLock(path, fn) {
21
+ await fs.mkdir(dirname(path), { recursive: true });
22
+ try {
23
+ await fs.access(path);
24
+ }
25
+ catch {
26
+ await fs.writeFile(path, '{}');
27
+ }
28
+ let release = null;
29
+ try {
30
+ release = await lockfile.lock(path, LOCK_OPTIONS);
31
+ return await fn();
32
+ }
33
+ catch (error) {
34
+ logger.error(`File lock failed for ${path}`, error);
35
+ throw error;
36
+ }
37
+ finally {
38
+ if (release)
39
+ await release();
40
+ }
41
+ }
42
+ export async function loadAccounts() {
43
+ try {
44
+ const content = await fs.readFile(getStoragePath(), 'utf-8');
45
+ return JSON.parse(content);
46
+ }
47
+ catch {
48
+ return { version: 1, accounts: [], activeIndex: -1 };
49
+ }
50
+ }
51
+ export async function saveAccounts(storage) {
52
+ const path = getStoragePath();
53
+ await withLock(path, async () => {
54
+ const tmp = `${path}.${randomBytes(6).toString('hex')}.tmp`;
55
+ await fs.writeFile(tmp, JSON.stringify(storage, null, 2));
56
+ await fs.rename(tmp, path);
57
+ });
58
+ }
59
+ export async function loadUsage() {
60
+ try {
61
+ const content = await fs.readFile(getUsagePath(), 'utf-8');
62
+ return JSON.parse(content);
63
+ }
64
+ catch {
65
+ return { version: 1, usage: {} };
66
+ }
67
+ }
68
+ export async function saveUsage(storage) {
69
+ const path = getUsagePath();
70
+ await withLock(path, async () => {
71
+ const tmp = `${path}.${randomBytes(6).toString('hex')}.tmp`;
72
+ await fs.writeFile(tmp, JSON.stringify(storage, null, 2));
73
+ await fs.rename(tmp, path);
74
+ });
75
+ }
@@ -0,0 +1,3 @@
1
+ export declare function transformKiroStream(response: Response, model: string, conversationId: string): AsyncGenerator<any>;
2
+ export declare function findRealTag(buffer: string, tag: string): number;
3
+ export declare function estimateTokens(text: string): number;