@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.
- package/LICENSE +678 -0
- package/README.md +270 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +108 -0
- package/dist/core/account/account-selector.d.ts +25 -0
- package/dist/core/account/account-selector.js +80 -0
- package/dist/core/account/usage-tracker.d.ts +20 -0
- package/dist/core/account/usage-tracker.js +56 -0
- package/dist/core/auth/auth-handler.d.ts +18 -0
- package/dist/core/auth/auth-handler.js +236 -0
- package/dist/core/auth/idc-auth-method.d.ts +9 -0
- package/dist/core/auth/idc-auth-method.js +169 -0
- package/dist/core/auth/token-refresher.d.ts +23 -0
- package/dist/core/auth/token-refresher.js +85 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/request/error-handler.d.ts +32 -0
- package/dist/core/request/error-handler.js +159 -0
- package/dist/core/request/request-handler.d.ts +35 -0
- package/dist/core/request/request-handler.js +296 -0
- package/dist/core/request/response-handler.d.ts +8 -0
- package/dist/core/request/response-handler.js +137 -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 +94 -0
- package/dist/index.js +2 -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 +66 -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 +16 -0
- package/dist/infrastructure/transformers/history-builder.js +226 -0
- package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
- package/dist/infrastructure/transformers/message-transformer.js +99 -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 +25 -0
- package/dist/kiro/oauth-idc.js +167 -0
- package/dist/plugin/accounts.d.ts +29 -0
- package/dist/plugin/accounts.js +266 -0
- package/dist/plugin/auth-bootstrap.d.ts +9 -0
- package/dist/plugin/auth-bootstrap.js +69 -0
- package/dist/plugin/auth-page.d.ts +4 -0
- package/dist/plugin/auth-page.js +721 -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 +88 -0
- package/dist/plugin/config/schema.js +94 -0
- package/dist/plugin/effort.d.ts +46 -0
- package/dist/plugin/effort.js +113 -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 +13 -0
- package/dist/plugin/image-handler.d.ts +18 -0
- package/dist/plugin/image-handler.js +82 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +84 -0
- package/dist/plugin/models.d.ts +2 -0
- package/dist/plugin/models.js +11 -0
- package/dist/plugin/request.d.ts +9 -0
- package/dist/plugin/request.js +287 -0
- package/dist/plugin/response.d.ts +3 -0
- package/dist/plugin/response.js +97 -0
- package/dist/plugin/sdk-client.d.ts +4 -0
- package/dist/plugin/sdk-client.js +55 -0
- package/dist/plugin/storage/locked-operations.d.ts +5 -0
- package/dist/plugin/storage/locked-operations.js +103 -0
- package/dist/plugin/storage/migrations.d.ts +4 -0
- package/dist/plugin/storage/migrations.js +155 -0
- package/dist/plugin/storage/sqlite.d.ts +18 -0
- package/dist/plugin/storage/sqlite.js +166 -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 +81 -0
- package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
- package/dist/plugin/streaming/sdk-stream-transformer.js +274 -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 +295 -0
- package/dist/plugin/streaming/types.d.ts +25 -0
- package/dist/plugin/streaming/types.js +2 -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-profile.d.ts +1 -0
- package/dist/plugin/sync/kiro-cli-profile.js +30 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +212 -0
- package/dist/plugin/sync/stale-accounts.d.ts +9 -0
- package/dist/plugin/sync/stale-accounts.js +31 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +78 -0
- package/dist/plugin/types.d.ts +125 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +79 -0
- package/dist/plugin.d.ts +174 -0
- package/dist/plugin.js +170 -0
- package/package.json +74 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isPermanentError } from '../health.js';
|
|
2
|
+
export const STALE_CLI_ACCOUNT_REASON = 'InvalidTokenException: Replaced by active Kiro CLI account during sync';
|
|
3
|
+
export function getStaleKiroCliAccountIds(accounts, syncedAccounts) {
|
|
4
|
+
if (syncedAccounts.length === 0)
|
|
5
|
+
return [];
|
|
6
|
+
const syncedIds = new Set(syncedAccounts.map((acc) => acc.id));
|
|
7
|
+
return accounts
|
|
8
|
+
.filter((account) => {
|
|
9
|
+
if (!account?.id || syncedIds.has(account.id))
|
|
10
|
+
return false;
|
|
11
|
+
const authMethod = account.auth_method || account.authMethod;
|
|
12
|
+
const email = account.email;
|
|
13
|
+
const clientId = account.client_id || account.clientId;
|
|
14
|
+
const profileArn = account.profile_arn || account.profileArn;
|
|
15
|
+
const lastSync = account.last_sync || account.lastSync || 0;
|
|
16
|
+
const unhealthyReason = account.unhealthy_reason || account.unhealthyReason;
|
|
17
|
+
return syncedAccounts.some((synced) => {
|
|
18
|
+
const sameAuthMethod = authMethod === synced.authMethod;
|
|
19
|
+
const sameEmail = !!email && email === synced.email;
|
|
20
|
+
const sameClient = !!clientId && clientId === synced.clientId;
|
|
21
|
+
const sameProfile = !!profileArn && profileArn === synced.profileArn;
|
|
22
|
+
const sameIdentity = sameEmail || sameClient || sameProfile;
|
|
23
|
+
if (sameAuthMethod && sameIdentity)
|
|
24
|
+
return true;
|
|
25
|
+
if (sameAuthMethod && lastSync > 0)
|
|
26
|
+
return true;
|
|
27
|
+
return isPermanentError(unhealthyReason) && sameIdentity;
|
|
28
|
+
});
|
|
29
|
+
})
|
|
30
|
+
.map((account) => account.id);
|
|
31
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { decodeRefreshToken, encodeRefreshToken } from '../kiro/auth.js';
|
|
2
|
+
import { KiroTokenRefreshError } from './errors.js';
|
|
3
|
+
export async function refreshAccessToken(auth) {
|
|
4
|
+
const p = decodeRefreshToken(auth.refresh);
|
|
5
|
+
const isIdc = auth.authMethod === 'idc';
|
|
6
|
+
const oidcRegion = auth.oidcRegion || auth.region;
|
|
7
|
+
const url = isIdc
|
|
8
|
+
? `https://oidc.${oidcRegion}.amazonaws.com/token`
|
|
9
|
+
: `https://prod.${auth.region}.auth.desktop.kiro.dev/refreshToken`;
|
|
10
|
+
if (isIdc && (!p.clientId || !p.clientSecret)) {
|
|
11
|
+
throw new KiroTokenRefreshError('Missing creds', 'MISSING_CREDENTIALS');
|
|
12
|
+
}
|
|
13
|
+
const requestBody = isIdc
|
|
14
|
+
? {
|
|
15
|
+
refreshToken: p.refreshToken,
|
|
16
|
+
clientId: p.clientId,
|
|
17
|
+
clientSecret: p.clientSecret,
|
|
18
|
+
grantType: 'refresh_token'
|
|
19
|
+
}
|
|
20
|
+
: {
|
|
21
|
+
refreshToken: p.refreshToken
|
|
22
|
+
};
|
|
23
|
+
const ua = isIdc
|
|
24
|
+
? 'aws-sdk-js/3.738.0 ua/2.1 os/other lang/js md/browser#unknown_unknown api/sso-oidc#3.738.0 m/E KiroIDE'
|
|
25
|
+
: 'aws-sdk-js/3.0.0 KiroIDE-0.1.0 os/macos lang/js md/nodejs/18.0.0';
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(url, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
Accept: 'application/json',
|
|
32
|
+
'amz-sdk-request': 'attempt=1; max=1',
|
|
33
|
+
'x-amzn-kiro-agent-mode': 'vibe',
|
|
34
|
+
'user-agent': ua,
|
|
35
|
+
Connection: 'close'
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify(requestBody)
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const txt = await res.text();
|
|
41
|
+
let data = {};
|
|
42
|
+
try {
|
|
43
|
+
data = JSON.parse(txt);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
data = { message: txt };
|
|
47
|
+
}
|
|
48
|
+
throw new KiroTokenRefreshError(`Refresh failed: ${data.message || data.error_description || txt}`, data.__type || data.error || `HTTP_${res.status}`);
|
|
49
|
+
}
|
|
50
|
+
const d = await res.json();
|
|
51
|
+
const acc = d.access_token || d.accessToken;
|
|
52
|
+
if (!acc)
|
|
53
|
+
throw new KiroTokenRefreshError('No access token', 'INVALID_RESPONSE');
|
|
54
|
+
const upP = {
|
|
55
|
+
refreshToken: d.refresh_token || d.refreshToken || p.refreshToken,
|
|
56
|
+
clientId: p.clientId,
|
|
57
|
+
clientSecret: p.clientSecret,
|
|
58
|
+
authMethod: auth.authMethod
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
refresh: encodeRefreshToken(upP),
|
|
62
|
+
access: acc,
|
|
63
|
+
expires: Date.now() + (d.expires_in || d.expiresIn || 3600) * 1000,
|
|
64
|
+
authMethod: auth.authMethod,
|
|
65
|
+
region: auth.region,
|
|
66
|
+
oidcRegion: auth.oidcRegion,
|
|
67
|
+
profileArn: auth.profileArn,
|
|
68
|
+
clientId: auth.clientId,
|
|
69
|
+
clientSecret: auth.clientSecret,
|
|
70
|
+
email: auth.email || d.userInfo?.email
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof KiroTokenRefreshError)
|
|
75
|
+
throw error;
|
|
76
|
+
throw new KiroTokenRefreshError(`Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'NETWORK_ERROR', error instanceof Error ? error : undefined);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { EffortSchema, RegionSchema } from './config/schema.js';
|
|
3
|
+
export type KiroAuthMethod = 'idc' | 'desktop';
|
|
4
|
+
export type KiroRegion = z.infer<typeof RegionSchema>;
|
|
5
|
+
export type Effort = z.infer<typeof EffortSchema>;
|
|
6
|
+
export interface KiroAuthDetails {
|
|
7
|
+
refresh: string;
|
|
8
|
+
access: string;
|
|
9
|
+
expires: number;
|
|
10
|
+
authMethod: KiroAuthMethod;
|
|
11
|
+
region: KiroRegion;
|
|
12
|
+
oidcRegion?: KiroRegion;
|
|
13
|
+
clientId?: string;
|
|
14
|
+
clientSecret?: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
profileArn?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RefreshParts {
|
|
19
|
+
refreshToken: string;
|
|
20
|
+
clientId?: string;
|
|
21
|
+
clientSecret?: string;
|
|
22
|
+
profileArn?: string;
|
|
23
|
+
authMethod?: KiroAuthMethod;
|
|
24
|
+
}
|
|
25
|
+
export interface ManagedAccount {
|
|
26
|
+
id: string;
|
|
27
|
+
email: string;
|
|
28
|
+
authMethod: KiroAuthMethod;
|
|
29
|
+
region: KiroRegion;
|
|
30
|
+
oidcRegion?: KiroRegion;
|
|
31
|
+
clientId?: string;
|
|
32
|
+
clientSecret?: string;
|
|
33
|
+
profileArn?: string;
|
|
34
|
+
startUrl?: string;
|
|
35
|
+
refreshToken: string;
|
|
36
|
+
accessToken: string;
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
rateLimitResetTime: number;
|
|
39
|
+
isHealthy: boolean;
|
|
40
|
+
unhealthyReason?: string;
|
|
41
|
+
recoveryTime?: number;
|
|
42
|
+
failCount: number;
|
|
43
|
+
usedCount?: number;
|
|
44
|
+
limitCount?: number;
|
|
45
|
+
lastSync?: number;
|
|
46
|
+
lastUsed?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface CodeWhispererMessage {
|
|
49
|
+
userInputMessage?: {
|
|
50
|
+
content: string;
|
|
51
|
+
modelId: string;
|
|
52
|
+
origin: string;
|
|
53
|
+
images?: Array<{
|
|
54
|
+
format: string;
|
|
55
|
+
source: {
|
|
56
|
+
bytes: Uint8Array;
|
|
57
|
+
};
|
|
58
|
+
}>;
|
|
59
|
+
userInputMessageContext?: {
|
|
60
|
+
toolResults?: Array<{
|
|
61
|
+
toolUseId: string;
|
|
62
|
+
content: Array<{
|
|
63
|
+
text?: string;
|
|
64
|
+
}>;
|
|
65
|
+
status?: string;
|
|
66
|
+
}>;
|
|
67
|
+
tools?: Array<{
|
|
68
|
+
toolSpecification: {
|
|
69
|
+
name: string;
|
|
70
|
+
description: string;
|
|
71
|
+
inputSchema: {
|
|
72
|
+
json: Record<string, unknown>;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
assistantResponseMessage?: {
|
|
79
|
+
content: string;
|
|
80
|
+
toolUses?: Array<{
|
|
81
|
+
input: any;
|
|
82
|
+
name: string;
|
|
83
|
+
toolUseId: string;
|
|
84
|
+
}>;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export interface CodeWhispererRequest {
|
|
88
|
+
conversationState: {
|
|
89
|
+
chatTriggerType: string;
|
|
90
|
+
conversationId: string;
|
|
91
|
+
history?: CodeWhispererMessage[];
|
|
92
|
+
currentMessage: CodeWhispererMessage;
|
|
93
|
+
};
|
|
94
|
+
profileArn?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface ToolCall {
|
|
97
|
+
toolUseId: string;
|
|
98
|
+
name: string;
|
|
99
|
+
input: string | Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
export interface ParsedResponse {
|
|
102
|
+
content: string;
|
|
103
|
+
toolCalls: ToolCall[];
|
|
104
|
+
stopReason?: string;
|
|
105
|
+
inputTokens?: number;
|
|
106
|
+
outputTokens?: number;
|
|
107
|
+
}
|
|
108
|
+
export interface PreparedRequest {
|
|
109
|
+
url: string;
|
|
110
|
+
init: RequestInit;
|
|
111
|
+
streaming: boolean;
|
|
112
|
+
effectiveModel: string;
|
|
113
|
+
conversationId: string;
|
|
114
|
+
}
|
|
115
|
+
export interface SdkPreparedRequest {
|
|
116
|
+
conversationState: CodeWhispererRequest['conversationState'];
|
|
117
|
+
profileArn?: string;
|
|
118
|
+
streaming: boolean;
|
|
119
|
+
effectiveModel: string;
|
|
120
|
+
conversationId: string;
|
|
121
|
+
region: string;
|
|
122
|
+
/** Resolved effort level for thinking models */
|
|
123
|
+
effort?: Effort;
|
|
124
|
+
}
|
|
125
|
+
export type AccountSelectionStrategy = 'sticky' | 'round-robin' | 'lowest-usage';
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export async function fetchUsageLimits(auth) {
|
|
2
|
+
// Try different parameter combinations
|
|
3
|
+
const attempts = [
|
|
4
|
+
{ resourceType: 'AGENTIC_REQUEST', origin: 'AI_EDITOR' },
|
|
5
|
+
{ origin: 'AI_EDITOR' },
|
|
6
|
+
{ resourceType: 'CONVERSATION', origin: 'AI_EDITOR' },
|
|
7
|
+
{}
|
|
8
|
+
];
|
|
9
|
+
let lastError = null;
|
|
10
|
+
for (const [index, params] of attempts.entries()) {
|
|
11
|
+
const url = new URL(`https://q.${auth.region}.amazonaws.com/getUsageLimits`);
|
|
12
|
+
url.searchParams.set('isEmailRequired', 'true');
|
|
13
|
+
if (params.origin)
|
|
14
|
+
url.searchParams.set('origin', params.origin);
|
|
15
|
+
if (params.resourceType)
|
|
16
|
+
url.searchParams.set('resourceType', params.resourceType);
|
|
17
|
+
if (auth.profileArn)
|
|
18
|
+
url.searchParams.set('profileArn', auth.profileArn);
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(url.toString(), {
|
|
21
|
+
method: 'GET',
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${auth.access}`,
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'x-amzn-kiro-agent-mode': 'vibe',
|
|
26
|
+
'amz-sdk-request': 'attempt=1; max=1'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
const body = await res.text().catch(() => '');
|
|
31
|
+
const requestId = res.headers.get('x-amzn-requestid') ||
|
|
32
|
+
res.headers.get('x-amzn-request-id') ||
|
|
33
|
+
res.headers.get('x-amz-request-id') ||
|
|
34
|
+
'';
|
|
35
|
+
const errType = res.headers.get('x-amzn-errortype') || res.headers.get('x-amzn-error-type') || '';
|
|
36
|
+
if (body.includes('FEATURE_NOT_SUPPORTED') && index < attempts.length - 1) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const msg = body && body.length > 0
|
|
40
|
+
? `${body.slice(0, 2000)}${body.length > 2000 ? '…' : ''}`
|
|
41
|
+
: `HTTP ${res.status}`;
|
|
42
|
+
lastError = new Error(`Status: ${res.status}${errType ? ` (${errType})` : ''}${requestId ? ` [${requestId}]` : ''}: ${msg}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
let usedCount = 0, limitCount = 0;
|
|
47
|
+
if (Array.isArray(data.usageBreakdownList)) {
|
|
48
|
+
for (const s of data.usageBreakdownList) {
|
|
49
|
+
if (s.freeTrialInfo) {
|
|
50
|
+
usedCount += s.freeTrialInfo.currentUsage || 0;
|
|
51
|
+
limitCount += s.freeTrialInfo.usageLimit || 0;
|
|
52
|
+
}
|
|
53
|
+
usedCount += s.currentUsage || 0;
|
|
54
|
+
limitCount += s.usageLimit || 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { usedCount, limitCount, email: data.userInfo?.email };
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
61
|
+
if (index < attempts.length - 1)
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw lastError || new Error('All getUsageLimits attempts failed');
|
|
66
|
+
}
|
|
67
|
+
export function updateAccountQuota(account, usage, accountManager) {
|
|
68
|
+
const meta = {
|
|
69
|
+
usedCount: usage.usedCount || 0,
|
|
70
|
+
limitCount: usage.limitCount || 0,
|
|
71
|
+
email: usage.email
|
|
72
|
+
};
|
|
73
|
+
account.usedCount = meta.usedCount;
|
|
74
|
+
account.limitCount = meta.limitCount;
|
|
75
|
+
if (usage.email)
|
|
76
|
+
account.email = usage.email;
|
|
77
|
+
if (accountManager)
|
|
78
|
+
accountManager.updateUsage(account.id, meta);
|
|
79
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
export declare const createKiroPlugin: (id: string) => ({ client, directory }: any) => Promise<{
|
|
2
|
+
config: (input: any) => Promise<void>;
|
|
3
|
+
auth: {
|
|
4
|
+
provider: string;
|
|
5
|
+
loader: (getAuth: any) => Promise<{
|
|
6
|
+
apiKey: string;
|
|
7
|
+
baseURL: string;
|
|
8
|
+
fetch: (input: any, init?: any) => Promise<Response>;
|
|
9
|
+
}>;
|
|
10
|
+
methods: ({
|
|
11
|
+
type: "oauth";
|
|
12
|
+
label: string;
|
|
13
|
+
prompts?: Array<{
|
|
14
|
+
type: "text";
|
|
15
|
+
key: string;
|
|
16
|
+
message: string;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
validate?: (value: string) => string | undefined;
|
|
19
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
20
|
+
when?: {
|
|
21
|
+
key: string;
|
|
22
|
+
op: "eq" | "neq";
|
|
23
|
+
value: string;
|
|
24
|
+
};
|
|
25
|
+
} | {
|
|
26
|
+
type: "select";
|
|
27
|
+
key: string;
|
|
28
|
+
message: string;
|
|
29
|
+
options: Array<{
|
|
30
|
+
label: string;
|
|
31
|
+
value: string;
|
|
32
|
+
hint?: string;
|
|
33
|
+
}>;
|
|
34
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
35
|
+
when?: {
|
|
36
|
+
key: string;
|
|
37
|
+
op: "eq" | "neq";
|
|
38
|
+
value: string;
|
|
39
|
+
};
|
|
40
|
+
}>;
|
|
41
|
+
authorize(inputs?: Record<string, string>): Promise<import("@opencode-ai/plugin").AuthOAuthResult>;
|
|
42
|
+
} | {
|
|
43
|
+
type: "api";
|
|
44
|
+
label: string;
|
|
45
|
+
prompts?: Array<{
|
|
46
|
+
type: "text";
|
|
47
|
+
key: string;
|
|
48
|
+
message: string;
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
validate?: (value: string) => string | undefined;
|
|
51
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
52
|
+
when?: {
|
|
53
|
+
key: string;
|
|
54
|
+
op: "eq" | "neq";
|
|
55
|
+
value: string;
|
|
56
|
+
};
|
|
57
|
+
} | {
|
|
58
|
+
type: "select";
|
|
59
|
+
key: string;
|
|
60
|
+
message: string;
|
|
61
|
+
options: Array<{
|
|
62
|
+
label: string;
|
|
63
|
+
value: string;
|
|
64
|
+
hint?: string;
|
|
65
|
+
}>;
|
|
66
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
67
|
+
when?: {
|
|
68
|
+
key: string;
|
|
69
|
+
op: "eq" | "neq";
|
|
70
|
+
value: string;
|
|
71
|
+
};
|
|
72
|
+
}>;
|
|
73
|
+
authorize?(inputs?: Record<string, string>): Promise<{
|
|
74
|
+
type: "success";
|
|
75
|
+
key: string;
|
|
76
|
+
provider?: string;
|
|
77
|
+
metadata?: Record<string, string>;
|
|
78
|
+
} | {
|
|
79
|
+
type: "failed";
|
|
80
|
+
}>;
|
|
81
|
+
})[];
|
|
82
|
+
};
|
|
83
|
+
provider: {
|
|
84
|
+
id: string;
|
|
85
|
+
models: (provider: any) => Promise<Record<string, any>>;
|
|
86
|
+
};
|
|
87
|
+
}>;
|
|
88
|
+
export declare const KiroOAuthPlugin: ({ client, directory }: any) => Promise<{
|
|
89
|
+
config: (input: any) => Promise<void>;
|
|
90
|
+
auth: {
|
|
91
|
+
provider: string;
|
|
92
|
+
loader: (getAuth: any) => Promise<{
|
|
93
|
+
apiKey: string;
|
|
94
|
+
baseURL: string;
|
|
95
|
+
fetch: (input: any, init?: any) => Promise<Response>;
|
|
96
|
+
}>;
|
|
97
|
+
methods: ({
|
|
98
|
+
type: "oauth";
|
|
99
|
+
label: string;
|
|
100
|
+
prompts?: Array<{
|
|
101
|
+
type: "text";
|
|
102
|
+
key: string;
|
|
103
|
+
message: string;
|
|
104
|
+
placeholder?: string;
|
|
105
|
+
validate?: (value: string) => string | undefined;
|
|
106
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
107
|
+
when?: {
|
|
108
|
+
key: string;
|
|
109
|
+
op: "eq" | "neq";
|
|
110
|
+
value: string;
|
|
111
|
+
};
|
|
112
|
+
} | {
|
|
113
|
+
type: "select";
|
|
114
|
+
key: string;
|
|
115
|
+
message: string;
|
|
116
|
+
options: Array<{
|
|
117
|
+
label: string;
|
|
118
|
+
value: string;
|
|
119
|
+
hint?: string;
|
|
120
|
+
}>;
|
|
121
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
122
|
+
when?: {
|
|
123
|
+
key: string;
|
|
124
|
+
op: "eq" | "neq";
|
|
125
|
+
value: string;
|
|
126
|
+
};
|
|
127
|
+
}>;
|
|
128
|
+
authorize(inputs?: Record<string, string>): Promise<import("@opencode-ai/plugin").AuthOAuthResult>;
|
|
129
|
+
} | {
|
|
130
|
+
type: "api";
|
|
131
|
+
label: string;
|
|
132
|
+
prompts?: Array<{
|
|
133
|
+
type: "text";
|
|
134
|
+
key: string;
|
|
135
|
+
message: string;
|
|
136
|
+
placeholder?: string;
|
|
137
|
+
validate?: (value: string) => string | undefined;
|
|
138
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
139
|
+
when?: {
|
|
140
|
+
key: string;
|
|
141
|
+
op: "eq" | "neq";
|
|
142
|
+
value: string;
|
|
143
|
+
};
|
|
144
|
+
} | {
|
|
145
|
+
type: "select";
|
|
146
|
+
key: string;
|
|
147
|
+
message: string;
|
|
148
|
+
options: Array<{
|
|
149
|
+
label: string;
|
|
150
|
+
value: string;
|
|
151
|
+
hint?: string;
|
|
152
|
+
}>;
|
|
153
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
154
|
+
when?: {
|
|
155
|
+
key: string;
|
|
156
|
+
op: "eq" | "neq";
|
|
157
|
+
value: string;
|
|
158
|
+
};
|
|
159
|
+
}>;
|
|
160
|
+
authorize?(inputs?: Record<string, string>): Promise<{
|
|
161
|
+
type: "success";
|
|
162
|
+
key: string;
|
|
163
|
+
provider?: string;
|
|
164
|
+
metadata?: Record<string, string>;
|
|
165
|
+
} | {
|
|
166
|
+
type: "failed";
|
|
167
|
+
}>;
|
|
168
|
+
})[];
|
|
169
|
+
};
|
|
170
|
+
provider: {
|
|
171
|
+
id: string;
|
|
172
|
+
models: (provider: any) => Promise<Record<string, any>>;
|
|
173
|
+
};
|
|
174
|
+
}>;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { KIRO_CONSTANTS } from './constants.js';
|
|
2
|
+
import { AuthHandler } from './core/auth/auth-handler.js';
|
|
3
|
+
import { RequestHandler } from './core/request/request-handler.js';
|
|
4
|
+
import { AccountCache } from './infrastructure/database/account-cache.js';
|
|
5
|
+
import { AccountRepository } from './infrastructure/database/account-repository.js';
|
|
6
|
+
import { AccountManager } from './plugin/accounts.js';
|
|
7
|
+
import { bootstrapAuthIfNeeded } from './plugin/auth-bootstrap.js';
|
|
8
|
+
import { loadConfig } from './plugin/config/index.js';
|
|
9
|
+
const KIRO_PROVIDER_ID = 'kiro-auth';
|
|
10
|
+
export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
11
|
+
const config = loadConfig(directory);
|
|
12
|
+
const showToast = (message, variant) => {
|
|
13
|
+
client.tui.showToast({ body: { message, variant } }).catch(() => { });
|
|
14
|
+
};
|
|
15
|
+
const cache = new AccountCache(60000);
|
|
16
|
+
const repository = new AccountRepository(cache);
|
|
17
|
+
const authHandler = new AuthHandler(config, repository);
|
|
18
|
+
const accountManager = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
19
|
+
authHandler.setAccountManager(accountManager);
|
|
20
|
+
const requestHandler = new RequestHandler(accountManager, config, repository, client);
|
|
21
|
+
// Compute the base URL once so both the config hook and auth loader use the same value
|
|
22
|
+
const baseURL = KIRO_CONSTANTS.BASE_URL.replace('/generateAssistantResponse', '').replace('{{region}}', config.default_region || 'us-east-1');
|
|
23
|
+
return {
|
|
24
|
+
config: async (input) => {
|
|
25
|
+
// Ensure there's an auth entry so OpenCode calls the loader on startup.
|
|
26
|
+
// This is a no-op if the entry already exists.
|
|
27
|
+
bootstrapAuthIfNeeded(id);
|
|
28
|
+
if (!input.provider)
|
|
29
|
+
input.provider = {};
|
|
30
|
+
if (!input.provider[id])
|
|
31
|
+
input.provider[id] = {};
|
|
32
|
+
// Always set npm and api — these must be present regardless of whether
|
|
33
|
+
// the user has already defined the provider in their opencode.json.
|
|
34
|
+
input.provider[id].npm = '@ai-sdk/openai-compatible';
|
|
35
|
+
// Set the base URL at the provider level. OpenCode reads provider.api as
|
|
36
|
+
// model.api.url, which resolveSDK() uses to construct the endpoint URL.
|
|
37
|
+
// Only set if not already overridden by the user.
|
|
38
|
+
if (!input.provider[id].api) {
|
|
39
|
+
input.provider[id].api = baseURL;
|
|
40
|
+
}
|
|
41
|
+
if (!input.provider[id].models) {
|
|
42
|
+
input.provider[id].models = {
|
|
43
|
+
auto: {
|
|
44
|
+
name: 'Auto (1.0x)',
|
|
45
|
+
limit: { context: 200000, output: 64000 },
|
|
46
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
47
|
+
},
|
|
48
|
+
// Claude Sonnet
|
|
49
|
+
'claude-sonnet-4': {
|
|
50
|
+
name: 'Claude Sonnet 4.0 (1.3x)',
|
|
51
|
+
limit: { context: 200000, output: 64000 },
|
|
52
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
53
|
+
},
|
|
54
|
+
'claude-sonnet-4-5': {
|
|
55
|
+
name: 'Claude Sonnet 4.5 (1.3x)',
|
|
56
|
+
limit: { context: 200000, output: 64000 },
|
|
57
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
58
|
+
},
|
|
59
|
+
'claude-sonnet-4-6': {
|
|
60
|
+
name: 'Claude Sonnet 4.6 (1.3x)',
|
|
61
|
+
limit: { context: 1000000, output: 64000 },
|
|
62
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
63
|
+
},
|
|
64
|
+
'claude-sonnet-5': {
|
|
65
|
+
name: 'Claude Sonnet 5 (1.3x)',
|
|
66
|
+
limit: { context: 1000000, output: 64000 },
|
|
67
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
68
|
+
},
|
|
69
|
+
// Claude Haiku
|
|
70
|
+
'claude-haiku-4-5': {
|
|
71
|
+
name: 'Claude Haiku 4.5 (0.4x)',
|
|
72
|
+
limit: { context: 200000, output: 64000 },
|
|
73
|
+
modalities: { input: ['text', 'image'], output: ['text'] }
|
|
74
|
+
},
|
|
75
|
+
// Claude Opus
|
|
76
|
+
'claude-opus-4-5': {
|
|
77
|
+
name: 'Claude Opus 4.5 (2.2x)',
|
|
78
|
+
limit: { context: 200000, output: 64000 },
|
|
79
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
80
|
+
},
|
|
81
|
+
'claude-opus-4-6': {
|
|
82
|
+
name: 'Claude Opus 4.6 (2.2x)',
|
|
83
|
+
limit: { context: 1000000, output: 64000 },
|
|
84
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
85
|
+
},
|
|
86
|
+
'claude-opus-4-7': {
|
|
87
|
+
name: 'Claude Opus 4.7 (2.2x)',
|
|
88
|
+
limit: { context: 1000000, output: 64000 },
|
|
89
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
90
|
+
},
|
|
91
|
+
'claude-opus-4-8': {
|
|
92
|
+
name: 'Claude Opus 4.8 (2.2x)',
|
|
93
|
+
limit: { context: 1000000, output: 64000 },
|
|
94
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
95
|
+
},
|
|
96
|
+
'claude-opus-4-8-thinking': {
|
|
97
|
+
name: 'Claude Opus 4.8 Thinking (2.2x)',
|
|
98
|
+
limit: { context: 1000000, output: 64000 },
|
|
99
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }
|
|
100
|
+
},
|
|
101
|
+
// Open weight models
|
|
102
|
+
'deepseek-3.2': {
|
|
103
|
+
name: 'DeepSeek 3.2 (0.25x)',
|
|
104
|
+
limit: { context: 128000, output: 64000 },
|
|
105
|
+
modalities: { input: ['text'], output: ['text'] }
|
|
106
|
+
},
|
|
107
|
+
'glm-5': {
|
|
108
|
+
name: 'GLM-5 (0.5x)',
|
|
109
|
+
limit: { context: 200000, output: 64000 },
|
|
110
|
+
modalities: { input: ['text'], output: ['text'] }
|
|
111
|
+
},
|
|
112
|
+
'minimax-m2.5': {
|
|
113
|
+
name: 'MiniMax M2.5 (0.25x)',
|
|
114
|
+
limit: { context: 200000, output: 64000 },
|
|
115
|
+
modalities: { input: ['text'], output: ['text'] }
|
|
116
|
+
},
|
|
117
|
+
'minimax-m2.1': {
|
|
118
|
+
name: 'MiniMax M2.1 (0.15x)',
|
|
119
|
+
limit: { context: 200000, output: 64000 },
|
|
120
|
+
modalities: { input: ['text'], output: ['text'] }
|
|
121
|
+
},
|
|
122
|
+
'qwen3-coder-next': {
|
|
123
|
+
name: 'Qwen3 Coder Next (0.05x)',
|
|
124
|
+
limit: { context: 256000, output: 64000 },
|
|
125
|
+
modalities: { input: ['text'], output: ['text'] }
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
auth: {
|
|
131
|
+
provider: id,
|
|
132
|
+
loader: async (getAuth) => {
|
|
133
|
+
await getAuth();
|
|
134
|
+
await authHandler.initialize(showToast);
|
|
135
|
+
return {
|
|
136
|
+
apiKey: '',
|
|
137
|
+
// Provide baseURL explicitly so the @ai-sdk/openai-compatible provider
|
|
138
|
+
// always has a valid URL. The custom fetch below intercepts all Kiro
|
|
139
|
+
// API calls, so this value is only used for URL construction.
|
|
140
|
+
baseURL,
|
|
141
|
+
fetch: (input, init) => requestHandler.handle(input, init, showToast)
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
methods: authHandler.getMethods()
|
|
145
|
+
},
|
|
146
|
+
provider: {
|
|
147
|
+
id,
|
|
148
|
+
models: async (provider) => {
|
|
149
|
+
const models = provider?.models || {};
|
|
150
|
+
const normalized = {};
|
|
151
|
+
for (const [modelID, model] of Object.entries(models)) {
|
|
152
|
+
const modelInfo = model;
|
|
153
|
+
normalized[modelID] = {
|
|
154
|
+
...modelInfo,
|
|
155
|
+
api: {
|
|
156
|
+
...(modelInfo.api || {}),
|
|
157
|
+
npm: '@ai-sdk/openai-compatible',
|
|
158
|
+
// Ensure url is always set. modelInfo.api.url should already be
|
|
159
|
+
// populated from the config hook's provider.api field, but we
|
|
160
|
+
// set it explicitly as a fallback for any edge cases.
|
|
161
|
+
url: modelInfo.api?.url || baseURL
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return normalized;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
export const KiroOAuthPlugin = createKiroPlugin(KIRO_PROVIDER_ID);
|