@sunerpy/opencode-kiro-auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/LICENSE +678 -0
  2. package/README.md +270 -0
  3. package/dist/constants.d.ts +28 -0
  4. package/dist/constants.js +108 -0
  5. package/dist/core/account/account-selector.d.ts +25 -0
  6. package/dist/core/account/account-selector.js +80 -0
  7. package/dist/core/account/usage-tracker.d.ts +20 -0
  8. package/dist/core/account/usage-tracker.js +56 -0
  9. package/dist/core/auth/auth-handler.d.ts +18 -0
  10. package/dist/core/auth/auth-handler.js +236 -0
  11. package/dist/core/auth/idc-auth-method.d.ts +9 -0
  12. package/dist/core/auth/idc-auth-method.js +169 -0
  13. package/dist/core/auth/token-refresher.d.ts +23 -0
  14. package/dist/core/auth/token-refresher.js +85 -0
  15. package/dist/core/index.d.ts +9 -0
  16. package/dist/core/index.js +9 -0
  17. package/dist/core/request/error-handler.d.ts +32 -0
  18. package/dist/core/request/error-handler.js +159 -0
  19. package/dist/core/request/request-handler.d.ts +35 -0
  20. package/dist/core/request/request-handler.js +296 -0
  21. package/dist/core/request/response-handler.d.ts +8 -0
  22. package/dist/core/request/response-handler.js +137 -0
  23. package/dist/core/request/retry-strategy.d.ts +18 -0
  24. package/dist/core/request/retry-strategy.js +28 -0
  25. package/dist/index.d.ts +94 -0
  26. package/dist/index.js +2 -0
  27. package/dist/infrastructure/database/account-cache.d.ts +14 -0
  28. package/dist/infrastructure/database/account-cache.js +44 -0
  29. package/dist/infrastructure/database/account-repository.d.ts +12 -0
  30. package/dist/infrastructure/database/account-repository.js +66 -0
  31. package/dist/infrastructure/index.d.ts +7 -0
  32. package/dist/infrastructure/index.js +7 -0
  33. package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
  34. package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
  35. package/dist/infrastructure/transformers/history-builder.d.ts +16 -0
  36. package/dist/infrastructure/transformers/history-builder.js +226 -0
  37. package/dist/infrastructure/transformers/message-transformer.d.ts +5 -0
  38. package/dist/infrastructure/transformers/message-transformer.js +99 -0
  39. package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
  40. package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
  41. package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
  42. package/dist/infrastructure/transformers/tool-transformer.js +19 -0
  43. package/dist/kiro/auth.d.ts +4 -0
  44. package/dist/kiro/auth.js +25 -0
  45. package/dist/kiro/oauth-idc.d.ts +25 -0
  46. package/dist/kiro/oauth-idc.js +167 -0
  47. package/dist/plugin/accounts.d.ts +29 -0
  48. package/dist/plugin/accounts.js +266 -0
  49. package/dist/plugin/auth-bootstrap.d.ts +9 -0
  50. package/dist/plugin/auth-bootstrap.js +69 -0
  51. package/dist/plugin/auth-page.d.ts +4 -0
  52. package/dist/plugin/auth-page.js +721 -0
  53. package/dist/plugin/config/index.d.ts +3 -0
  54. package/dist/plugin/config/index.js +2 -0
  55. package/dist/plugin/config/loader.d.ts +6 -0
  56. package/dist/plugin/config/loader.js +129 -0
  57. package/dist/plugin/config/schema.d.ts +88 -0
  58. package/dist/plugin/config/schema.js +94 -0
  59. package/dist/plugin/effort.d.ts +46 -0
  60. package/dist/plugin/effort.js +113 -0
  61. package/dist/plugin/errors.d.ts +17 -0
  62. package/dist/plugin/errors.js +34 -0
  63. package/dist/plugin/health.d.ts +1 -0
  64. package/dist/plugin/health.js +13 -0
  65. package/dist/plugin/image-handler.d.ts +18 -0
  66. package/dist/plugin/image-handler.js +82 -0
  67. package/dist/plugin/logger.d.ts +8 -0
  68. package/dist/plugin/logger.js +84 -0
  69. package/dist/plugin/models.d.ts +2 -0
  70. package/dist/plugin/models.js +11 -0
  71. package/dist/plugin/request.d.ts +9 -0
  72. package/dist/plugin/request.js +287 -0
  73. package/dist/plugin/response.d.ts +3 -0
  74. package/dist/plugin/response.js +97 -0
  75. package/dist/plugin/sdk-client.d.ts +4 -0
  76. package/dist/plugin/sdk-client.js +55 -0
  77. package/dist/plugin/storage/locked-operations.d.ts +5 -0
  78. package/dist/plugin/storage/locked-operations.js +103 -0
  79. package/dist/plugin/storage/migrations.d.ts +4 -0
  80. package/dist/plugin/storage/migrations.js +155 -0
  81. package/dist/plugin/storage/sqlite.d.ts +18 -0
  82. package/dist/plugin/storage/sqlite.js +166 -0
  83. package/dist/plugin/streaming/index.d.ts +2 -0
  84. package/dist/plugin/streaming/index.js +2 -0
  85. package/dist/plugin/streaming/openai-converter.d.ts +2 -0
  86. package/dist/plugin/streaming/openai-converter.js +81 -0
  87. package/dist/plugin/streaming/sdk-stream-transformer.d.ts +1 -0
  88. package/dist/plugin/streaming/sdk-stream-transformer.js +274 -0
  89. package/dist/plugin/streaming/stream-parser.d.ts +5 -0
  90. package/dist/plugin/streaming/stream-parser.js +136 -0
  91. package/dist/plugin/streaming/stream-state.d.ts +5 -0
  92. package/dist/plugin/streaming/stream-state.js +59 -0
  93. package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
  94. package/dist/plugin/streaming/stream-transformer.js +295 -0
  95. package/dist/plugin/streaming/types.d.ts +25 -0
  96. package/dist/plugin/streaming/types.js +2 -0
  97. package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
  98. package/dist/plugin/sync/kiro-cli-parser.js +72 -0
  99. package/dist/plugin/sync/kiro-cli-profile.d.ts +1 -0
  100. package/dist/plugin/sync/kiro-cli-profile.js +30 -0
  101. package/dist/plugin/sync/kiro-cli.d.ts +2 -0
  102. package/dist/plugin/sync/kiro-cli.js +212 -0
  103. package/dist/plugin/sync/stale-accounts.d.ts +9 -0
  104. package/dist/plugin/sync/stale-accounts.js +31 -0
  105. package/dist/plugin/token.d.ts +2 -0
  106. package/dist/plugin/token.js +78 -0
  107. package/dist/plugin/types.d.ts +125 -0
  108. package/dist/plugin/types.js +0 -0
  109. package/dist/plugin/usage.d.ts +3 -0
  110. package/dist/plugin/usage.js +79 -0
  111. package/dist/plugin.d.ts +174 -0
  112. package/dist/plugin.js +170 -0
  113. package/package.json +74 -0
@@ -0,0 +1,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,2 @@
1
+ import type { KiroAuthDetails } from './types.js';
2
+ export declare function refreshAccessToken(auth: KiroAuthDetails): Promise<KiroAuthDetails>;
@@ -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,3 @@
1
+ import { KiroAuthDetails, ManagedAccount } from './types.js';
2
+ export declare function fetchUsageLimits(auth: KiroAuthDetails): Promise<any>;
3
+ export declare function updateAccountQuota(account: ManagedAccount, usage: any, accountManager?: any): void;
@@ -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
+ }
@@ -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);