@zhafron/opencode-kiro-auth 1.2.5 → 1.2.7
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/plugin/cli.d.ts +7 -0
- package/dist/plugin/cli.js +38 -0
- package/dist/plugin/request.js +57 -27
- package/dist/plugin/response.js +1 -1
- package/dist/plugin/streaming.js +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +125 -1
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function promptAddAnotherAccount(currentCount: number): Promise<boolean>;
|
|
2
|
+
export type LoginMode = 'add' | 'fresh';
|
|
3
|
+
export interface ExistingAccountInfo {
|
|
4
|
+
email?: string;
|
|
5
|
+
index: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function promptLoginMode(existingAccounts: ExistingAccountInfo[]): Promise<LoginMode>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
export async function promptAddAnotherAccount(currentCount) {
|
|
4
|
+
const rl = createInterface({ input, output });
|
|
5
|
+
try {
|
|
6
|
+
const answer = await rl.question(`Add another account? (${currentCount} added) (y/n): `);
|
|
7
|
+
const normalized = answer.trim().toLowerCase();
|
|
8
|
+
return normalized === 'y' || normalized === 'yes';
|
|
9
|
+
}
|
|
10
|
+
finally {
|
|
11
|
+
rl.close();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function promptLoginMode(existingAccounts) {
|
|
15
|
+
const rl = createInterface({ input, output });
|
|
16
|
+
try {
|
|
17
|
+
console.log(`\n${existingAccounts.length} account(s) saved:`);
|
|
18
|
+
for (const acc of existingAccounts) {
|
|
19
|
+
const label = acc.email || `Account ${acc.index + 1}`;
|
|
20
|
+
console.log(` ${acc.index + 1}. ${label}`);
|
|
21
|
+
}
|
|
22
|
+
console.log('');
|
|
23
|
+
while (true) {
|
|
24
|
+
const answer = await rl.question('(a)dd new account(s) or (f)resh start? [a/f]: ');
|
|
25
|
+
const normalized = answer.trim().toLowerCase();
|
|
26
|
+
if (normalized === 'a' || normalized === 'add') {
|
|
27
|
+
return 'add';
|
|
28
|
+
}
|
|
29
|
+
if (normalized === 'f' || normalized === 'fresh') {
|
|
30
|
+
return 'fresh';
|
|
31
|
+
}
|
|
32
|
+
console.log("Please enter 'a' to add accounts or 'f' to start fresh.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
rl.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/plugin/request.js
CHANGED
|
@@ -2,10 +2,39 @@ import * as crypto from 'crypto';
|
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import { KIRO_CONSTANTS } from '../constants.js';
|
|
4
4
|
import { resolveKiroModel } from './models.js';
|
|
5
|
-
|
|
5
|
+
function truncate(s, max) {
|
|
6
|
+
if (s.length <= max)
|
|
7
|
+
return s;
|
|
8
|
+
const half = Math.floor(max / 2);
|
|
9
|
+
return s.substring(0, half) + '\n... [TRUNCATED] ...\n' + s.substring(s.length - half);
|
|
10
|
+
}
|
|
11
|
+
function sanitizeHistory(history) {
|
|
12
|
+
const result = [];
|
|
13
|
+
for (let i = 0; i < history.length; i++) {
|
|
14
|
+
const m = history[i];
|
|
15
|
+
if (!m)
|
|
16
|
+
continue;
|
|
17
|
+
if (m.assistantResponseMessage?.toolUses) {
|
|
18
|
+
const next = history[i + 1];
|
|
19
|
+
if (next?.userInputMessage?.userInputMessageContext?.toolResults) {
|
|
20
|
+
result.push(m);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (m.userInputMessage?.userInputMessageContext?.toolResults) {
|
|
24
|
+
const prev = result[result.length - 1];
|
|
25
|
+
if (prev?.assistantResponseMessage?.toolUses) {
|
|
26
|
+
result.push(m);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
result.push(m);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
6
35
|
export function transformToCodeWhisperer(url, body, model, auth, think = false, budget = 20000) {
|
|
7
36
|
const req = typeof body === 'string' ? JSON.parse(body) : body;
|
|
8
|
-
const { messages, tools, system
|
|
37
|
+
const { messages, tools, system } = req;
|
|
9
38
|
const convId = crypto.randomUUID();
|
|
10
39
|
if (!messages || messages.length === 0)
|
|
11
40
|
throw new Error('No messages');
|
|
@@ -20,7 +49,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
20
49
|
if (lastMsg && lastMsg.role === 'assistant' && getContentText(lastMsg) === '{')
|
|
21
50
|
msgs.pop();
|
|
22
51
|
const cwTools = tools ? convertToolsToCodeWhisperer(tools) : [];
|
|
23
|
-
|
|
52
|
+
let history = [];
|
|
24
53
|
let firstUserIndex = -1;
|
|
25
54
|
for (let i = 0; i < msgs.length; i++) {
|
|
26
55
|
if (msgs[i].role === 'user') {
|
|
@@ -38,9 +67,8 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
38
67
|
...m.content.filter((p) => p.type !== 'text')
|
|
39
68
|
];
|
|
40
69
|
}
|
|
41
|
-
else
|
|
70
|
+
else
|
|
42
71
|
m.content = `${sys}\n\n${oldContent}`;
|
|
43
|
-
}
|
|
44
72
|
}
|
|
45
73
|
else {
|
|
46
74
|
history.push({
|
|
@@ -65,7 +93,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
65
93
|
uim.content += p.text || '';
|
|
66
94
|
else if (p.type === 'tool_result')
|
|
67
95
|
trs.push({
|
|
68
|
-
content: [{ text: getContentText(p.content || p) }],
|
|
96
|
+
content: [{ text: truncate(getContentText(p.content || p), 250000) }],
|
|
69
97
|
status: 'success',
|
|
70
98
|
toolUseId: p.tool_use_id
|
|
71
99
|
});
|
|
@@ -83,9 +111,8 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
83
111
|
if (trs.length)
|
|
84
112
|
uim.userInputMessageContext = { toolResults: deduplicateToolResults(trs) };
|
|
85
113
|
const prev = history[history.length - 1];
|
|
86
|
-
if (prev && prev.userInputMessage)
|
|
114
|
+
if (prev && prev.userInputMessage)
|
|
87
115
|
history.push({ assistantResponseMessage: { content: 'Continue' } });
|
|
88
|
-
}
|
|
89
116
|
history.push({ userInputMessage: uim });
|
|
90
117
|
}
|
|
91
118
|
else if (m.role === 'tool') {
|
|
@@ -93,22 +120,21 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
93
120
|
if (m.tool_results) {
|
|
94
121
|
for (const tr of m.tool_results)
|
|
95
122
|
trs.push({
|
|
96
|
-
content: [{ text: getContentText(tr) }],
|
|
123
|
+
content: [{ text: truncate(getContentText(tr), 250000) }],
|
|
97
124
|
status: 'success',
|
|
98
125
|
toolUseId: tr.tool_call_id
|
|
99
126
|
});
|
|
100
127
|
}
|
|
101
128
|
else {
|
|
102
129
|
trs.push({
|
|
103
|
-
content: [{ text: getContentText(m) }],
|
|
130
|
+
content: [{ text: truncate(getContentText(m), 250000) }],
|
|
104
131
|
status: 'success',
|
|
105
132
|
toolUseId: m.tool_call_id
|
|
106
133
|
});
|
|
107
134
|
}
|
|
108
135
|
const prev = history[history.length - 1];
|
|
109
|
-
if (prev && prev.userInputMessage)
|
|
136
|
+
if (prev && prev.userInputMessage)
|
|
110
137
|
history.push({ assistantResponseMessage: { content: 'Continue' } });
|
|
111
|
-
}
|
|
112
138
|
history.push({
|
|
113
139
|
userInputMessage: {
|
|
114
140
|
content: 'Tool results provided.',
|
|
@@ -132,9 +158,8 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
132
158
|
tus.push({ input: p.input, name: p.name, toolUseId: p.id });
|
|
133
159
|
}
|
|
134
160
|
}
|
|
135
|
-
else
|
|
161
|
+
else
|
|
136
162
|
arm.content = getContentText(m);
|
|
137
|
-
}
|
|
138
163
|
if (m.tool_calls && Array.isArray(m.tool_calls)) {
|
|
139
164
|
for (const tc of m.tool_calls) {
|
|
140
165
|
tus.push({
|
|
@@ -155,6 +180,19 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
155
180
|
history.push({ assistantResponseMessage: arm });
|
|
156
181
|
}
|
|
157
182
|
}
|
|
183
|
+
history = sanitizeHistory(history);
|
|
184
|
+
let historySize = JSON.stringify(history).length;
|
|
185
|
+
while (historySize > 850000 && history.length > 2) {
|
|
186
|
+
history.shift();
|
|
187
|
+
while (history.length > 0) {
|
|
188
|
+
const first = history[0];
|
|
189
|
+
if (first && first.userInputMessage)
|
|
190
|
+
break;
|
|
191
|
+
history.shift();
|
|
192
|
+
}
|
|
193
|
+
history = sanitizeHistory(history);
|
|
194
|
+
historySize = JSON.stringify(history).length;
|
|
195
|
+
}
|
|
158
196
|
const curMsg = msgs[msgs.length - 1];
|
|
159
197
|
if (!curMsg)
|
|
160
198
|
throw new Error('Empty');
|
|
@@ -206,14 +244,14 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
206
244
|
if (curMsg.tool_results) {
|
|
207
245
|
for (const tr of curMsg.tool_results)
|
|
208
246
|
curTrs.push({
|
|
209
|
-
content: [{ text: getContentText(tr) }],
|
|
247
|
+
content: [{ text: truncate(getContentText(tr), 250000) }],
|
|
210
248
|
status: 'success',
|
|
211
249
|
toolUseId: tr.tool_call_id
|
|
212
250
|
});
|
|
213
251
|
}
|
|
214
252
|
else {
|
|
215
253
|
curTrs.push({
|
|
216
|
-
content: [{ text: getContentText(curMsg) }],
|
|
254
|
+
content: [{ text: truncate(getContentText(curMsg), 250000) }],
|
|
217
255
|
status: 'success',
|
|
218
256
|
toolUseId: curMsg.tool_call_id
|
|
219
257
|
});
|
|
@@ -225,7 +263,7 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
225
263
|
curContent += p.text || '';
|
|
226
264
|
else if (p.type === 'tool_result')
|
|
227
265
|
curTrs.push({
|
|
228
|
-
content: [{ text: getContentText(p.content || p) }],
|
|
266
|
+
content: [{ text: truncate(getContentText(p.content || p), 250000) }],
|
|
229
267
|
status: 'success',
|
|
230
268
|
toolUseId: p.tool_use_id
|
|
231
269
|
});
|
|
@@ -254,10 +292,8 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
254
292
|
}
|
|
255
293
|
}
|
|
256
294
|
};
|
|
257
|
-
if (history.length > 0)
|
|
258
|
-
;
|
|
295
|
+
if (history.length > 0)
|
|
259
296
|
request.conversationState.history = history;
|
|
260
|
-
}
|
|
261
297
|
const uim = request.conversationState.currentMessage.userInputMessage;
|
|
262
298
|
if (uim) {
|
|
263
299
|
if (curImgs.length)
|
|
@@ -277,17 +313,11 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
277
313
|
const existingToolNames = new Set(existingTools.map((t) => t.toolSpecification?.name).filter(Boolean));
|
|
278
314
|
const missingToolNames = Array.from(toolNamesInHistory).filter((name) => !existingToolNames.has(name));
|
|
279
315
|
if (missingToolNames.length > 0) {
|
|
280
|
-
logger.log(`[Kiro] Adding ${missingToolNames.length} missing tool definitions: ${missingToolNames.join(', ')}`);
|
|
281
316
|
const placeholderTools = missingToolNames.map((name) => ({
|
|
282
317
|
toolSpecification: {
|
|
283
318
|
name,
|
|
284
319
|
description: 'Tool',
|
|
285
|
-
inputSchema: {
|
|
286
|
-
json: {
|
|
287
|
-
type: 'object',
|
|
288
|
-
properties: {}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
320
|
+
inputSchema: { json: { type: 'object', properties: {} } }
|
|
291
321
|
}
|
|
292
322
|
}));
|
|
293
323
|
if (!uim.userInputMessageContext)
|
package/dist/plugin/response.js
CHANGED
|
@@ -76,7 +76,7 @@ function parseEventStreamChunk(rawText) {
|
|
|
76
76
|
};
|
|
77
77
|
});
|
|
78
78
|
if (contextUsagePercentage !== undefined) {
|
|
79
|
-
const totalTokens = Math.round((
|
|
79
|
+
const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
|
|
80
80
|
outputTokens = estimateTokens(content);
|
|
81
81
|
inputTokens = Math.max(0, totalTokens - outputTokens);
|
|
82
82
|
}
|
package/dist/plugin/streaming.js
CHANGED
|
@@ -224,7 +224,7 @@ export async function* transformKiroStream(response, model, conversationId) {
|
|
|
224
224
|
}
|
|
225
225
|
outputTokens = estimateTokens(totalContent);
|
|
226
226
|
if (contextUsagePercentage !== null && contextUsagePercentage > 0) {
|
|
227
|
-
const totalTokens = Math.round((
|
|
227
|
+
const totalTokens = Math.round((200000 * contextUsagePercentage) / 100);
|
|
228
228
|
inputTokens = Math.max(0, totalTokens - outputTokens);
|
|
229
229
|
}
|
|
230
230
|
yield convertToOpenAI({
|
package/dist/plugin.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const createKiroPlugin: (id: string) => ({ client, directory }: a
|
|
|
10
10
|
id: string;
|
|
11
11
|
label: string;
|
|
12
12
|
type: string;
|
|
13
|
-
authorize: () => Promise<unknown>;
|
|
13
|
+
authorize: (inputs?: any) => Promise<unknown>;
|
|
14
14
|
}[];
|
|
15
15
|
};
|
|
16
16
|
}>;
|
|
@@ -26,7 +26,7 @@ export declare const KiroOAuthPlugin: ({ client, directory }: any) => Promise<{
|
|
|
26
26
|
id: string;
|
|
27
27
|
label: string;
|
|
28
28
|
type: string;
|
|
29
|
-
authorize: () => Promise<unknown>;
|
|
29
|
+
authorize: (inputs?: any) => Promise<unknown>;
|
|
30
30
|
}[];
|
|
31
31
|
};
|
|
32
32
|
}>;
|
package/dist/plugin.js
CHANGED
|
@@ -10,6 +10,7 @@ import { fetchUsageLimits, updateAccountQuota } from './plugin/usage';
|
|
|
10
10
|
import { authorizeKiroIDC } from './kiro/oauth-idc';
|
|
11
11
|
import { startIDCAuthServer } from './plugin/server';
|
|
12
12
|
import { KiroTokenRefreshError } from './plugin/errors';
|
|
13
|
+
import { promptAddAnotherAccount, promptLoginMode } from './plugin/cli';
|
|
13
14
|
import { KIRO_CONSTANTS } from './constants';
|
|
14
15
|
import * as logger from './plugin/logger';
|
|
15
16
|
const KIRO_PROVIDER_ID = 'kiro';
|
|
@@ -292,8 +293,131 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
292
293
|
id: 'idc',
|
|
293
294
|
label: 'AWS Builder ID (IDC)',
|
|
294
295
|
type: 'oauth',
|
|
295
|
-
authorize: async () => new Promise(async (resolve) => {
|
|
296
|
+
authorize: async (inputs) => new Promise(async (resolve) => {
|
|
296
297
|
const region = config.default_region;
|
|
298
|
+
if (inputs) {
|
|
299
|
+
const accounts = [];
|
|
300
|
+
let startFresh = true;
|
|
301
|
+
const existingAm = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
302
|
+
if (existingAm.getAccountCount() > 0) {
|
|
303
|
+
const existingAccounts = existingAm.getAccounts().map((acc, idx) => ({
|
|
304
|
+
email: acc.realEmail || acc.email,
|
|
305
|
+
index: idx
|
|
306
|
+
}));
|
|
307
|
+
const loginMode = await promptLoginMode(existingAccounts);
|
|
308
|
+
startFresh = loginMode === 'fresh';
|
|
309
|
+
console.log(startFresh
|
|
310
|
+
? '\nStarting fresh - existing accounts will be replaced.\n'
|
|
311
|
+
: '\nAdding to existing accounts.\n');
|
|
312
|
+
}
|
|
313
|
+
while (true) {
|
|
314
|
+
console.log(`\n=== Kiro IDC Auth (Account ${accounts.length + 1}) ===\n`);
|
|
315
|
+
const result = await (async () => {
|
|
316
|
+
try {
|
|
317
|
+
const authData = await authorizeKiroIDC(region);
|
|
318
|
+
const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
|
|
319
|
+
console.log('OAuth URL:\n' + url + '\n');
|
|
320
|
+
openBrowser(url);
|
|
321
|
+
const res = await waitForAuth();
|
|
322
|
+
return res;
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
return { type: 'failed', error: e.message };
|
|
326
|
+
}
|
|
327
|
+
})();
|
|
328
|
+
if ('type' in result && result.type === 'failed') {
|
|
329
|
+
if (accounts.length === 0) {
|
|
330
|
+
return resolve({
|
|
331
|
+
url: '',
|
|
332
|
+
instructions: `Authentication failed: ${result.error}`,
|
|
333
|
+
method: 'auto',
|
|
334
|
+
callback: async () => ({ type: 'failed' })
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
console.warn(`[opencode-kiro-auth] Skipping failed account ${accounts.length + 1}: ${result.error}`);
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
const successResult = result;
|
|
341
|
+
accounts.push(successResult);
|
|
342
|
+
const isFirstAccount = accounts.length === 1;
|
|
343
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
344
|
+
if (isFirstAccount && startFresh) {
|
|
345
|
+
am.getAccounts().forEach((acc) => am.removeAccount(acc));
|
|
346
|
+
}
|
|
347
|
+
const acc = {
|
|
348
|
+
id: generateAccountId(),
|
|
349
|
+
email: successResult.email,
|
|
350
|
+
authMethod: 'idc',
|
|
351
|
+
region,
|
|
352
|
+
clientId: successResult.clientId,
|
|
353
|
+
clientSecret: successResult.clientSecret,
|
|
354
|
+
refreshToken: successResult.refreshToken,
|
|
355
|
+
accessToken: successResult.accessToken,
|
|
356
|
+
expiresAt: successResult.expiresAt,
|
|
357
|
+
rateLimitResetTime: 0,
|
|
358
|
+
isHealthy: true
|
|
359
|
+
};
|
|
360
|
+
try {
|
|
361
|
+
const u = await fetchUsageLimits({
|
|
362
|
+
refresh: encodeRefreshToken({
|
|
363
|
+
refreshToken: successResult.refreshToken,
|
|
364
|
+
clientId: successResult.clientId,
|
|
365
|
+
clientSecret: successResult.clientSecret,
|
|
366
|
+
authMethod: 'idc'
|
|
367
|
+
}),
|
|
368
|
+
access: successResult.accessToken,
|
|
369
|
+
expires: successResult.expiresAt,
|
|
370
|
+
authMethod: 'idc',
|
|
371
|
+
region,
|
|
372
|
+
clientId: successResult.clientId,
|
|
373
|
+
clientSecret: successResult.clientSecret,
|
|
374
|
+
email: successResult.email
|
|
375
|
+
});
|
|
376
|
+
am.updateUsage(acc.id, {
|
|
377
|
+
usedCount: u.usedCount,
|
|
378
|
+
limitCount: u.limitCount,
|
|
379
|
+
realEmail: u.email
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
logger.warn(`Initial usage fetch failed: ${e.message}`, e);
|
|
384
|
+
}
|
|
385
|
+
am.addAccount(acc);
|
|
386
|
+
await am.saveToDisk();
|
|
387
|
+
showToast(`Account ${accounts.length} authenticated${successResult.email ? ` (${successResult.email})` : ''}`, 'success');
|
|
388
|
+
let currentAccountCount = accounts.length;
|
|
389
|
+
try {
|
|
390
|
+
const currentStorage = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
391
|
+
currentAccountCount = currentStorage.getAccountCount();
|
|
392
|
+
}
|
|
393
|
+
catch { }
|
|
394
|
+
const addAnother = await promptAddAnotherAccount(currentAccountCount);
|
|
395
|
+
if (!addAnother) {
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const primary = accounts[0];
|
|
400
|
+
if (!primary) {
|
|
401
|
+
return resolve({
|
|
402
|
+
url: '',
|
|
403
|
+
instructions: 'Authentication cancelled',
|
|
404
|
+
method: 'auto',
|
|
405
|
+
callback: async () => ({ type: 'failed' })
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
let actualAccountCount = accounts.length;
|
|
409
|
+
try {
|
|
410
|
+
const finalStorage = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
411
|
+
actualAccountCount = finalStorage.getAccountCount();
|
|
412
|
+
}
|
|
413
|
+
catch { }
|
|
414
|
+
return resolve({
|
|
415
|
+
url: '',
|
|
416
|
+
instructions: `Multi-account setup complete (${actualAccountCount} account(s)).`,
|
|
417
|
+
method: 'auto',
|
|
418
|
+
callback: async () => ({ type: 'success', key: primary.accessToken })
|
|
419
|
+
});
|
|
420
|
+
}
|
|
297
421
|
try {
|
|
298
422
|
const authData = await authorizeKiroIDC(region);
|
|
299
423
|
const { url, waitForAuth } = await startIDCAuthServer(authData, config.auth_server_port_start, config.auth_server_port_range);
|