@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.
- package/README.md +85 -0
- package/dist/constants.d.ts +26 -0
- package/dist/constants.js +60 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/kiro/auth.d.ts +5 -0
- package/dist/kiro/auth.js +24 -0
- package/dist/kiro/oauth-idc.d.ts +24 -0
- package/dist/kiro/oauth-idc.js +132 -0
- package/dist/kiro/oauth-social.d.ts +17 -0
- package/dist/kiro/oauth-social.js +51 -0
- package/dist/plugin/accounts.d.ts +28 -0
- package/dist/plugin/accounts.js +156 -0
- package/dist/plugin/auth-page.d.ts +3 -0
- package/dist/plugin/auth-page.js +568 -0
- package/dist/plugin/cli.d.ts +6 -0
- package/dist/plugin/cli.js +98 -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 +125 -0
- package/dist/plugin/config/schema.d.ts +44 -0
- package/dist/plugin/config/schema.js +28 -0
- package/dist/plugin/debug.d.ts +2 -0
- package/dist/plugin/debug.js +9 -0
- package/dist/plugin/errors.d.ts +17 -0
- package/dist/plugin/errors.js +34 -0
- package/dist/plugin/logger.d.ts +4 -0
- package/dist/plugin/logger.js +37 -0
- package/dist/plugin/models.d.ts +3 -0
- package/dist/plugin/models.js +14 -0
- package/dist/plugin/oauth-parser.d.ts +5 -0
- package/dist/plugin/oauth-parser.js +23 -0
- package/dist/plugin/quota.d.ts +15 -0
- package/dist/plugin/quota.js +68 -0
- package/dist/plugin/recovery.d.ts +19 -0
- package/dist/plugin/recovery.js +302 -0
- package/dist/plugin/refresh-queue.d.ts +14 -0
- package/dist/plugin/refresh-queue.js +69 -0
- package/dist/plugin/request.d.ts +4 -0
- package/dist/plugin/request.js +240 -0
- package/dist/plugin/response.d.ts +6 -0
- package/dist/plugin/response.js +246 -0
- package/dist/plugin/server.d.ts +24 -0
- package/dist/plugin/server.js +96 -0
- package/dist/plugin/storage.d.ts +7 -0
- package/dist/plugin/storage.js +75 -0
- package/dist/plugin/streaming.d.ts +3 -0
- package/dist/plugin/streaming.js +503 -0
- package/dist/plugin/token.d.ts +2 -0
- package/dist/plugin/token.js +56 -0
- package/dist/plugin/types.d.ts +148 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/usage.d.ts +3 -0
- package/dist/plugin/usage.js +36 -0
- package/dist/plugin.d.ts +32 -0
- package/dist/plugin.js +222 -0
- package/dist/src/constants.d.ts +22 -0
- package/dist/src/constants.js +35 -0
- package/dist/src/kiro/auth.d.ts +5 -0
- package/dist/src/kiro/auth.js +69 -0
- package/dist/src/kiro/oauth-idc.d.ts +22 -0
- package/dist/src/kiro/oauth-idc.js +99 -0
- package/dist/src/kiro/oauth-social.d.ts +17 -0
- package/dist/src/kiro/oauth-social.js +69 -0
- package/dist/src/plugin/accounts.d.ts +23 -0
- package/dist/src/plugin/accounts.js +265 -0
- package/dist/src/plugin/cli.d.ts +6 -0
- package/dist/src/plugin/cli.js +98 -0
- package/dist/src/plugin/config/index.d.ts +3 -0
- package/dist/src/plugin/config/index.js +2 -0
- package/dist/src/plugin/config/loader.d.ts +7 -0
- package/dist/src/plugin/config/loader.js +143 -0
- package/dist/src/plugin/config/schema.d.ts +68 -0
- package/dist/src/plugin/config/schema.js +44 -0
- package/dist/src/plugin/debug.d.ts +2 -0
- package/dist/src/plugin/debug.js +9 -0
- package/dist/src/plugin/errors.d.ts +17 -0
- package/dist/src/plugin/errors.js +34 -0
- package/dist/src/plugin/logger.d.ts +4 -0
- package/dist/src/plugin/logger.js +17 -0
- package/dist/src/plugin/models.d.ts +3 -0
- package/dist/src/plugin/models.js +14 -0
- package/dist/src/plugin/oauth-parser.d.ts +5 -0
- package/dist/src/plugin/oauth-parser.js +23 -0
- package/dist/src/plugin/quota.d.ts +25 -0
- package/dist/src/plugin/quota.js +175 -0
- package/dist/src/plugin/recovery.d.ts +19 -0
- package/dist/src/plugin/recovery.js +302 -0
- package/dist/src/plugin/refresh-queue.d.ts +14 -0
- package/dist/src/plugin/refresh-queue.js +69 -0
- package/dist/src/plugin/request.d.ts +35 -0
- package/dist/src/plugin/request.js +411 -0
- package/dist/src/plugin/response.d.ts +6 -0
- package/dist/src/plugin/response.js +246 -0
- package/dist/src/plugin/server.d.ts +10 -0
- package/dist/src/plugin/server.js +203 -0
- package/dist/src/plugin/storage.d.ts +5 -0
- package/dist/src/plugin/storage.js +106 -0
- package/dist/src/plugin/streaming.d.ts +12 -0
- package/dist/src/plugin/streaming.js +444 -0
- package/dist/src/plugin/token.d.ts +8 -0
- package/dist/src/plugin/token.js +130 -0
- package/dist/src/plugin/types.d.ts +144 -0
- package/dist/src/plugin/types.js +0 -0
- package/dist/src/plugin/usage.d.ts +28 -0
- package/dist/src/plugin/usage.js +159 -0
- package/dist/src/plugin.d.ts +2 -0
- package/dist/src/plugin.js +341 -0
- package/package.json +57 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { AccountSelectionStrategySchema, KiroConfigSchema, RegionSchema, DEFAULT_CONFIG } from "./schema";
|
|
6
|
+
import * as logger from "../logger";
|
|
7
|
+
function getConfigDir() {
|
|
8
|
+
const platform = process.platform;
|
|
9
|
+
if (platform === "win32") {
|
|
10
|
+
return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
11
|
+
}
|
|
12
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
13
|
+
return join(xdgConfig, "opencode");
|
|
14
|
+
}
|
|
15
|
+
export function getUserConfigPath() {
|
|
16
|
+
return join(getConfigDir(), "kiro.json");
|
|
17
|
+
}
|
|
18
|
+
export function getProjectConfigPath(directory) {
|
|
19
|
+
return join(directory, ".opencode", "kiro.json");
|
|
20
|
+
}
|
|
21
|
+
export async function ensureConfigTemplate() {
|
|
22
|
+
const userConfigPath = getUserConfigPath();
|
|
23
|
+
if (existsSync(userConfigPath)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const template = {
|
|
27
|
+
account_selection_strategy: "sticky",
|
|
28
|
+
proactive_token_refresh: true,
|
|
29
|
+
session_recovery: true,
|
|
30
|
+
default_region: "us-east-1",
|
|
31
|
+
thinking_enabled: false,
|
|
32
|
+
thinking_budget_tokens: 20000
|
|
33
|
+
};
|
|
34
|
+
try {
|
|
35
|
+
await fs.mkdir(dirname(userConfigPath), { recursive: true });
|
|
36
|
+
await fs.writeFile(userConfigPath, JSON.stringify(template, null, 2), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
logger.warn(`Failed to create config template: ${String(error)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function loadConfigFile(path) {
|
|
43
|
+
try {
|
|
44
|
+
if (!existsSync(path)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const content = readFileSync(path, "utf-8");
|
|
48
|
+
const rawConfig = JSON.parse(content);
|
|
49
|
+
const result = KiroConfigSchema.partial().safeParse(rawConfig);
|
|
50
|
+
if (!result.success) {
|
|
51
|
+
const issues = result.error.issues.map(i => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
52
|
+
logger.warn(`Config validation error at ${path}: ${issues}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return result.data;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof SyntaxError) {
|
|
59
|
+
logger.warn(`Invalid JSON in config file ${path}: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
logger.warn(`Failed to load config file ${path}: ${String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function mergeConfigs(base, override) {
|
|
68
|
+
return {
|
|
69
|
+
...base,
|
|
70
|
+
...override,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function parseBooleanEnv(value, fallback) {
|
|
74
|
+
if (value === undefined) {
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
if (value === "1" || value === "true") {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (value === "0" || value === "false") {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
function parseNumberEnv(value, fallback) {
|
|
86
|
+
if (value === undefined) {
|
|
87
|
+
return fallback;
|
|
88
|
+
}
|
|
89
|
+
const parsed = Number(value);
|
|
90
|
+
if (isNaN(parsed)) {
|
|
91
|
+
return fallback;
|
|
92
|
+
}
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
function applyEnvOverrides(config) {
|
|
96
|
+
const env = process.env;
|
|
97
|
+
return {
|
|
98
|
+
...config,
|
|
99
|
+
quiet_mode: parseBooleanEnv(env.KIRO_QUIET_MODE, config.quiet_mode),
|
|
100
|
+
debug: parseBooleanEnv(env.KIRO_DEBUG, config.debug),
|
|
101
|
+
session_recovery: parseBooleanEnv(env.KIRO_SESSION_RECOVERY, config.session_recovery),
|
|
102
|
+
auto_resume: parseBooleanEnv(env.KIRO_AUTO_RESUME, config.auto_resume),
|
|
103
|
+
max_recovery_attempts: parseNumberEnv(env.KIRO_MAX_RECOVERY_ATTEMPTS, config.max_recovery_attempts),
|
|
104
|
+
proactive_token_refresh: parseBooleanEnv(env.KIRO_PROACTIVE_TOKEN_REFRESH, config.proactive_token_refresh),
|
|
105
|
+
token_refresh_interval_seconds: parseNumberEnv(env.KIRO_TOKEN_REFRESH_INTERVAL_SECONDS, config.token_refresh_interval_seconds),
|
|
106
|
+
token_refresh_buffer_seconds: parseNumberEnv(env.KIRO_TOKEN_REFRESH_BUFFER_SECONDS, config.token_refresh_buffer_seconds),
|
|
107
|
+
account_selection_strategy: env.KIRO_ACCOUNT_SELECTION_STRATEGY
|
|
108
|
+
? AccountSelectionStrategySchema.catch('sticky').parse(env.KIRO_ACCOUNT_SELECTION_STRATEGY)
|
|
109
|
+
: config.account_selection_strategy,
|
|
110
|
+
thinking_enabled: parseBooleanEnv(env.KIRO_THINKING_ENABLED, config.thinking_enabled),
|
|
111
|
+
thinking_budget_tokens: parseNumberEnv(env.KIRO_THINKING_BUDGET_TOKENS, config.thinking_budget_tokens),
|
|
112
|
+
default_region: env.KIRO_DEFAULT_REGION
|
|
113
|
+
? RegionSchema.catch('us-east-1').parse(env.KIRO_DEFAULT_REGION)
|
|
114
|
+
: config.default_region,
|
|
115
|
+
request_timeout_ms: parseNumberEnv(env.KIRO_REQUEST_TIMEOUT_MS, config.request_timeout_ms),
|
|
116
|
+
rate_limit_retry_delay_ms: parseNumberEnv(env.KIRO_RATE_LIMIT_RETRY_DELAY_MS, config.rate_limit_retry_delay_ms),
|
|
117
|
+
rate_limit_max_retries: parseNumberEnv(env.KIRO_RATE_LIMIT_MAX_RETRIES, config.rate_limit_max_retries),
|
|
118
|
+
quota_warning_threshold: parseNumberEnv(env.KIRO_QUOTA_WARNING_THRESHOLD, config.quota_warning_threshold),
|
|
119
|
+
usage_tracking_enabled: parseBooleanEnv(env.KIRO_USAGE_TRACKING_ENABLED, config.usage_tracking_enabled),
|
|
120
|
+
usage_fetch_interval_seconds: parseNumberEnv(env.KIRO_USAGE_FETCH_INTERVAL_SECONDS, config.usage_fetch_interval_seconds),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function loadConfig(directory) {
|
|
124
|
+
let config = { ...DEFAULT_CONFIG };
|
|
125
|
+
const userConfigPath = getUserConfigPath();
|
|
126
|
+
const userConfig = loadConfigFile(userConfigPath);
|
|
127
|
+
if (userConfig) {
|
|
128
|
+
config = mergeConfigs(config, userConfig);
|
|
129
|
+
}
|
|
130
|
+
const projectConfigPath = getProjectConfigPath(directory);
|
|
131
|
+
const projectConfig = loadConfigFile(projectConfigPath);
|
|
132
|
+
if (projectConfig) {
|
|
133
|
+
config = mergeConfigs(config, projectConfig);
|
|
134
|
+
}
|
|
135
|
+
config = applyEnvOverrides(config);
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
export function configExists(path) {
|
|
139
|
+
return existsSync(path);
|
|
140
|
+
}
|
|
141
|
+
export function getDefaultLogsDir() {
|
|
142
|
+
return join(getConfigDir(), "kiro-logs");
|
|
143
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const AccountSelectionStrategySchema: z.ZodEnum<["sticky", "round-robin"]>;
|
|
3
|
+
export type AccountSelectionStrategy = z.infer<typeof AccountSelectionStrategySchema>;
|
|
4
|
+
export declare const RegionSchema: z.ZodEnum<["us-east-1", "us-west-2"]>;
|
|
5
|
+
export type Region = z.infer<typeof RegionSchema>;
|
|
6
|
+
export declare const KiroConfigSchema: z.ZodObject<{
|
|
7
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
8
|
+
quiet_mode: z.ZodDefault<z.ZodBoolean>;
|
|
9
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
10
|
+
session_recovery: z.ZodDefault<z.ZodBoolean>;
|
|
11
|
+
auto_resume: z.ZodDefault<z.ZodBoolean>;
|
|
12
|
+
max_recovery_attempts: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
proactive_token_refresh: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
token_refresh_interval_seconds: z.ZodDefault<z.ZodNumber>;
|
|
15
|
+
token_refresh_buffer_seconds: z.ZodDefault<z.ZodNumber>;
|
|
16
|
+
account_selection_strategy: z.ZodDefault<z.ZodEnum<["sticky", "round-robin"]>>;
|
|
17
|
+
thinking_enabled: z.ZodDefault<z.ZodBoolean>;
|
|
18
|
+
thinking_budget_tokens: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
default_region: z.ZodDefault<z.ZodEnum<["us-east-1", "us-west-2"]>>;
|
|
20
|
+
request_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
rate_limit_retry_delay_ms: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
rate_limit_max_retries: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
quota_warning_threshold: z.ZodDefault<z.ZodNumber>;
|
|
24
|
+
usage_tracking_enabled: z.ZodDefault<z.ZodBoolean>;
|
|
25
|
+
usage_fetch_interval_seconds: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
quiet_mode: boolean;
|
|
28
|
+
debug: boolean;
|
|
29
|
+
session_recovery: boolean;
|
|
30
|
+
auto_resume: boolean;
|
|
31
|
+
max_recovery_attempts: number;
|
|
32
|
+
proactive_token_refresh: boolean;
|
|
33
|
+
token_refresh_interval_seconds: number;
|
|
34
|
+
token_refresh_buffer_seconds: number;
|
|
35
|
+
account_selection_strategy: "sticky" | "round-robin";
|
|
36
|
+
thinking_enabled: boolean;
|
|
37
|
+
thinking_budget_tokens: number;
|
|
38
|
+
default_region: "us-east-1" | "us-west-2";
|
|
39
|
+
request_timeout_ms: number;
|
|
40
|
+
rate_limit_retry_delay_ms: number;
|
|
41
|
+
rate_limit_max_retries: number;
|
|
42
|
+
quota_warning_threshold: number;
|
|
43
|
+
usage_tracking_enabled: boolean;
|
|
44
|
+
usage_fetch_interval_seconds: number;
|
|
45
|
+
$schema?: string | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
$schema?: string | undefined;
|
|
48
|
+
quiet_mode?: boolean | undefined;
|
|
49
|
+
debug?: boolean | undefined;
|
|
50
|
+
session_recovery?: boolean | undefined;
|
|
51
|
+
auto_resume?: boolean | undefined;
|
|
52
|
+
max_recovery_attempts?: number | undefined;
|
|
53
|
+
proactive_token_refresh?: boolean | undefined;
|
|
54
|
+
token_refresh_interval_seconds?: number | undefined;
|
|
55
|
+
token_refresh_buffer_seconds?: number | undefined;
|
|
56
|
+
account_selection_strategy?: "sticky" | "round-robin" | undefined;
|
|
57
|
+
thinking_enabled?: boolean | undefined;
|
|
58
|
+
thinking_budget_tokens?: number | undefined;
|
|
59
|
+
default_region?: "us-east-1" | "us-west-2" | undefined;
|
|
60
|
+
request_timeout_ms?: number | undefined;
|
|
61
|
+
rate_limit_retry_delay_ms?: number | undefined;
|
|
62
|
+
rate_limit_max_retries?: number | undefined;
|
|
63
|
+
quota_warning_threshold?: number | undefined;
|
|
64
|
+
usage_tracking_enabled?: boolean | undefined;
|
|
65
|
+
usage_fetch_interval_seconds?: number | undefined;
|
|
66
|
+
}>;
|
|
67
|
+
export type KiroConfig = z.infer<typeof KiroConfigSchema>;
|
|
68
|
+
export declare const DEFAULT_CONFIG: KiroConfig;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const AccountSelectionStrategySchema = z.enum(['sticky', 'round-robin']);
|
|
3
|
+
export const RegionSchema = z.enum(['us-east-1', 'us-west-2']);
|
|
4
|
+
export const KiroConfigSchema = z.object({
|
|
5
|
+
$schema: z.string().optional(),
|
|
6
|
+
quiet_mode: z.boolean().default(false),
|
|
7
|
+
debug: z.boolean().default(false),
|
|
8
|
+
session_recovery: z.boolean().default(true),
|
|
9
|
+
auto_resume: z.boolean().default(true),
|
|
10
|
+
max_recovery_attempts: z.number().min(1).max(10).default(3),
|
|
11
|
+
proactive_token_refresh: z.boolean().default(true),
|
|
12
|
+
token_refresh_interval_seconds: z.number().min(60).max(3600).default(300),
|
|
13
|
+
token_refresh_buffer_seconds: z.number().min(60).max(1800).default(600),
|
|
14
|
+
account_selection_strategy: AccountSelectionStrategySchema.default('sticky'),
|
|
15
|
+
thinking_enabled: z.boolean().default(false),
|
|
16
|
+
thinking_budget_tokens: z.number().min(1000).max(24576).default(20000),
|
|
17
|
+
default_region: RegionSchema.default('us-east-1'),
|
|
18
|
+
request_timeout_ms: z.number().min(10000).max(300000).default(120000),
|
|
19
|
+
rate_limit_retry_delay_ms: z.number().min(1000).max(60000).default(5000),
|
|
20
|
+
rate_limit_max_retries: z.number().min(0).max(10).default(3),
|
|
21
|
+
quota_warning_threshold: z.number().min(0).max(1).default(0.8),
|
|
22
|
+
usage_tracking_enabled: z.boolean().default(true),
|
|
23
|
+
usage_fetch_interval_seconds: z.number().min(60).max(3600).default(300),
|
|
24
|
+
});
|
|
25
|
+
export const DEFAULT_CONFIG = {
|
|
26
|
+
quiet_mode: false,
|
|
27
|
+
debug: false,
|
|
28
|
+
session_recovery: true,
|
|
29
|
+
auto_resume: true,
|
|
30
|
+
max_recovery_attempts: 3,
|
|
31
|
+
proactive_token_refresh: true,
|
|
32
|
+
token_refresh_interval_seconds: 300,
|
|
33
|
+
token_refresh_buffer_seconds: 600,
|
|
34
|
+
account_selection_strategy: 'sticky',
|
|
35
|
+
thinking_enabled: false,
|
|
36
|
+
thinking_budget_tokens: 20000,
|
|
37
|
+
default_region: 'us-east-1',
|
|
38
|
+
request_timeout_ms: 120000,
|
|
39
|
+
rate_limit_retry_delay_ms: 5000,
|
|
40
|
+
rate_limit_max_retries: 3,
|
|
41
|
+
quota_warning_threshold: 0.8,
|
|
42
|
+
usage_tracking_enabled: true,
|
|
43
|
+
usage_fetch_interval_seconds: 300,
|
|
44
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function isDebugEnabled() {
|
|
2
|
+
return !!process.env.DEBUG;
|
|
3
|
+
}
|
|
4
|
+
export function debugLog(context, message, data) {
|
|
5
|
+
if (isDebugEnabled()) {
|
|
6
|
+
const formattedData = data !== undefined ? ` ${JSON.stringify(data)}` : '';
|
|
7
|
+
console.debug(`[${context}] ${message}${formattedData}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class KiroTokenRefreshError extends Error {
|
|
2
|
+
code?: string;
|
|
3
|
+
originalError?: Error;
|
|
4
|
+
constructor(message: string, code?: string, originalError?: Error);
|
|
5
|
+
}
|
|
6
|
+
export declare class KiroQuotaExhaustedError extends Error {
|
|
7
|
+
recoveryTime?: number;
|
|
8
|
+
constructor(message: string, recoveryTime?: number);
|
|
9
|
+
}
|
|
10
|
+
export declare class KiroRateLimitError extends Error {
|
|
11
|
+
retryAfter?: number;
|
|
12
|
+
constructor(message: string, retryAfter?: number);
|
|
13
|
+
}
|
|
14
|
+
export declare class KiroAuthError extends Error {
|
|
15
|
+
statusCode?: number;
|
|
16
|
+
constructor(message: string, statusCode?: number);
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class KiroTokenRefreshError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
originalError;
|
|
4
|
+
constructor(message, code, originalError) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'KiroTokenRefreshError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.originalError = originalError;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class KiroQuotaExhaustedError extends Error {
|
|
12
|
+
recoveryTime;
|
|
13
|
+
constructor(message, recoveryTime) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'KiroQuotaExhaustedError';
|
|
16
|
+
this.recoveryTime = recoveryTime;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class KiroRateLimitError extends Error {
|
|
20
|
+
retryAfter;
|
|
21
|
+
constructor(message, retryAfter) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'KiroRateLimitError';
|
|
24
|
+
this.retryAfter = retryAfter;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class KiroAuthError extends Error {
|
|
28
|
+
statusCode;
|
|
29
|
+
constructor(message, statusCode) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'KiroAuthError';
|
|
32
|
+
this.statusCode = statusCode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function log(message: string, ...args: unknown[]): void;
|
|
2
|
+
export declare function error(message: string, ...args: unknown[]): void;
|
|
3
|
+
export declare function warn(message: string, ...args: unknown[]): void;
|
|
4
|
+
export declare function debug(message: string, ...args: unknown[]): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function formatTimestamp() {
|
|
2
|
+
return new Date().toISOString();
|
|
3
|
+
}
|
|
4
|
+
export function log(message, ...args) {
|
|
5
|
+
console.log(`[${formatTimestamp()}] ${message}`, ...args);
|
|
6
|
+
}
|
|
7
|
+
export function error(message, ...args) {
|
|
8
|
+
console.error(`[${formatTimestamp()}] ERROR: ${message}`, ...args);
|
|
9
|
+
}
|
|
10
|
+
export function warn(message, ...args) {
|
|
11
|
+
console.warn(`[${formatTimestamp()}] WARN: ${message}`, ...args);
|
|
12
|
+
}
|
|
13
|
+
export function debug(message, ...args) {
|
|
14
|
+
if (process.env.DEBUG) {
|
|
15
|
+
console.debug(`[${formatTimestamp()}] DEBUG: ${message}`, ...args);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MODEL_MAPPING, SUPPORTED_MODELS } from '../constants';
|
|
2
|
+
export function resolveKiroModel(model) {
|
|
3
|
+
const resolved = MODEL_MAPPING[model];
|
|
4
|
+
if (!resolved) {
|
|
5
|
+
throw new Error(`Unsupported model: ${model}. Supported models: ${SUPPORTED_MODELS.join(', ')}`);
|
|
6
|
+
}
|
|
7
|
+
return resolved;
|
|
8
|
+
}
|
|
9
|
+
export function getSupportedModels() {
|
|
10
|
+
return SUPPORTED_MODELS;
|
|
11
|
+
}
|
|
12
|
+
export function isModelSupported(model) {
|
|
13
|
+
return SUPPORTED_MODELS.includes(model);
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function parseOAuthCallbackInput(input) {
|
|
2
|
+
const trimmed = input.trim();
|
|
3
|
+
try {
|
|
4
|
+
const url = new URL(trimmed);
|
|
5
|
+
const code = url.searchParams.get('code');
|
|
6
|
+
const state = url.searchParams.get('state');
|
|
7
|
+
if (!code || !state) {
|
|
8
|
+
throw new Error('Missing code or state in URL');
|
|
9
|
+
}
|
|
10
|
+
return { code, state };
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
const codeMatch = trimmed.match(/code=([^&\s]+)/);
|
|
14
|
+
const stateMatch = trimmed.match(/state=([^&\s]+)/);
|
|
15
|
+
if (codeMatch?.[1] && stateMatch?.[1]) {
|
|
16
|
+
return {
|
|
17
|
+
code: decodeURIComponent(codeMatch[1]),
|
|
18
|
+
state: decodeURIComponent(stateMatch[1]),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
throw new Error('Invalid OAuth callback format. Expected full URL or query string with code and state parameters.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ManagedAccount, UsageLimits } from './types';
|
|
2
|
+
export type QuotaStatus = 'healthy' | 'warning' | 'exhausted';
|
|
3
|
+
export interface QuotaInfo {
|
|
4
|
+
status: QuotaStatus;
|
|
5
|
+
used: number;
|
|
6
|
+
limit: number;
|
|
7
|
+
remaining: number;
|
|
8
|
+
percentage: number;
|
|
9
|
+
recoveryTime?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function checkQuotaStatus(account: ManagedAccount): QuotaStatus;
|
|
12
|
+
export declare function updateAccountQuota(account: ManagedAccount, usage: UsageLimits): void;
|
|
13
|
+
export declare function getNextAvailableAccount(accounts: ManagedAccount[]): ManagedAccount | null;
|
|
14
|
+
export declare function sortAccountsByQuota(accounts: ManagedAccount[]): ManagedAccount[];
|
|
15
|
+
export declare function buildQuotaInfo(account: ManagedAccount): QuotaInfo;
|
|
16
|
+
export declare function filterHealthyAccounts(accounts: ManagedAccount[]): ManagedAccount[];
|
|
17
|
+
export declare function filterExhaustedAccounts(accounts: ManagedAccount[]): ManagedAccount[];
|
|
18
|
+
export declare function filterWarningAccounts(accounts: ManagedAccount[]): ManagedAccount[];
|
|
19
|
+
export declare function hasAvailableQuota(account: ManagedAccount): boolean;
|
|
20
|
+
export declare function isQuotaNearLimit(account: ManagedAccount, thresholdPercent?: number): boolean;
|
|
21
|
+
export declare function getAccountWithMostQuota(accounts: ManagedAccount[]): ManagedAccount | null;
|
|
22
|
+
export declare function getAccountWithLeastQuota(accounts: ManagedAccount[]): ManagedAccount | null;
|
|
23
|
+
export declare function getTotalQuotaInfo(accounts: ManagedAccount[]): QuotaInfo;
|
|
24
|
+
export declare function shouldRotateAccount(account: ManagedAccount, rotationThreshold?: number): boolean;
|
|
25
|
+
export declare function selectAccountForRequest(accounts: ManagedAccount[], strategy?: 'least-used' | 'most-quota'): ManagedAccount | null;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { calculateUsagePercentage, isQuotaExhausted, getRemainingCount } from './usage';
|
|
2
|
+
const WARNING_THRESHOLD_PERCENT = 80;
|
|
3
|
+
export function checkQuotaStatus(account) {
|
|
4
|
+
if (!account.usedCount || !account.limitCount) {
|
|
5
|
+
return 'healthy';
|
|
6
|
+
}
|
|
7
|
+
const usage = {
|
|
8
|
+
usedCount: account.usedCount,
|
|
9
|
+
limitCount: account.limitCount,
|
|
10
|
+
};
|
|
11
|
+
if (isQuotaExhausted(usage)) {
|
|
12
|
+
return 'exhausted';
|
|
13
|
+
}
|
|
14
|
+
const percentage = calculateUsagePercentage(usage);
|
|
15
|
+
if (percentage >= WARNING_THRESHOLD_PERCENT) {
|
|
16
|
+
return 'warning';
|
|
17
|
+
}
|
|
18
|
+
return 'healthy';
|
|
19
|
+
}
|
|
20
|
+
export function updateAccountQuota(account, usage) {
|
|
21
|
+
account.usedCount = usage.usedCount;
|
|
22
|
+
account.limitCount = usage.limitCount;
|
|
23
|
+
}
|
|
24
|
+
export function getNextAvailableAccount(accounts) {
|
|
25
|
+
const availableAccounts = accounts.filter(account => {
|
|
26
|
+
if (!account.isHealthy) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const status = checkQuotaStatus(account);
|
|
30
|
+
return status !== 'exhausted';
|
|
31
|
+
});
|
|
32
|
+
if (availableAccounts.length === 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return availableAccounts[0] || null;
|
|
36
|
+
}
|
|
37
|
+
export function sortAccountsByQuota(accounts) {
|
|
38
|
+
return [...accounts].sort((a, b) => {
|
|
39
|
+
const aRemaining = getRemainingQuota(a);
|
|
40
|
+
const bRemaining = getRemainingQuota(b);
|
|
41
|
+
return bRemaining - aRemaining;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function getRemainingQuota(account) {
|
|
45
|
+
if (!account.usedCount || !account.limitCount) {
|
|
46
|
+
return Infinity;
|
|
47
|
+
}
|
|
48
|
+
const usage = {
|
|
49
|
+
usedCount: account.usedCount,
|
|
50
|
+
limitCount: account.limitCount,
|
|
51
|
+
};
|
|
52
|
+
return getRemainingCount(usage);
|
|
53
|
+
}
|
|
54
|
+
export function buildQuotaInfo(account) {
|
|
55
|
+
const used = account.usedCount || 0;
|
|
56
|
+
const limit = account.limitCount || 0;
|
|
57
|
+
const remaining = Math.max(0, limit - used);
|
|
58
|
+
const percentage = limit > 0 ? Math.round((used / limit) * 100) : 0;
|
|
59
|
+
const status = checkQuotaStatus(account);
|
|
60
|
+
const info = {
|
|
61
|
+
status,
|
|
62
|
+
used,
|
|
63
|
+
limit,
|
|
64
|
+
remaining,
|
|
65
|
+
percentage,
|
|
66
|
+
};
|
|
67
|
+
if (account.recoveryTime) {
|
|
68
|
+
info.recoveryTime = account.recoveryTime;
|
|
69
|
+
}
|
|
70
|
+
return info;
|
|
71
|
+
}
|
|
72
|
+
export function filterHealthyAccounts(accounts) {
|
|
73
|
+
return accounts.filter(account => {
|
|
74
|
+
if (!account.isHealthy) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const status = checkQuotaStatus(account);
|
|
78
|
+
return status !== 'exhausted';
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function filterExhaustedAccounts(accounts) {
|
|
82
|
+
return accounts.filter(account => {
|
|
83
|
+
const status = checkQuotaStatus(account);
|
|
84
|
+
return status === 'exhausted';
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export function filterWarningAccounts(accounts) {
|
|
88
|
+
return accounts.filter(account => {
|
|
89
|
+
const status = checkQuotaStatus(account);
|
|
90
|
+
return status === 'warning';
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function hasAvailableQuota(account) {
|
|
94
|
+
const status = checkQuotaStatus(account);
|
|
95
|
+
return status !== 'exhausted';
|
|
96
|
+
}
|
|
97
|
+
export function isQuotaNearLimit(account, thresholdPercent = WARNING_THRESHOLD_PERCENT) {
|
|
98
|
+
if (!account.usedCount || !account.limitCount) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const usage = {
|
|
102
|
+
usedCount: account.usedCount,
|
|
103
|
+
limitCount: account.limitCount,
|
|
104
|
+
};
|
|
105
|
+
const percentage = calculateUsagePercentage(usage);
|
|
106
|
+
return percentage >= thresholdPercent;
|
|
107
|
+
}
|
|
108
|
+
export function getAccountWithMostQuota(accounts) {
|
|
109
|
+
const healthyAccounts = filterHealthyAccounts(accounts);
|
|
110
|
+
if (healthyAccounts.length === 0) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const sorted = sortAccountsByQuota(healthyAccounts);
|
|
114
|
+
return sorted[0] || null;
|
|
115
|
+
}
|
|
116
|
+
export function getAccountWithLeastQuota(accounts) {
|
|
117
|
+
const healthyAccounts = filterHealthyAccounts(accounts);
|
|
118
|
+
if (healthyAccounts.length === 0) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const sorted = sortAccountsByQuota(healthyAccounts);
|
|
122
|
+
return sorted[sorted.length - 1] || null;
|
|
123
|
+
}
|
|
124
|
+
export function getTotalQuotaInfo(accounts) {
|
|
125
|
+
let totalUsed = 0;
|
|
126
|
+
let totalLimit = 0;
|
|
127
|
+
for (const account of accounts) {
|
|
128
|
+
if (account.usedCount && account.limitCount) {
|
|
129
|
+
totalUsed += account.usedCount;
|
|
130
|
+
totalLimit += account.limitCount;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const remaining = Math.max(0, totalLimit - totalUsed);
|
|
134
|
+
const percentage = totalLimit > 0 ? Math.round((totalUsed / totalLimit) * 100) : 0;
|
|
135
|
+
let status = 'healthy';
|
|
136
|
+
if (totalUsed >= totalLimit) {
|
|
137
|
+
status = 'exhausted';
|
|
138
|
+
}
|
|
139
|
+
else if (percentage >= WARNING_THRESHOLD_PERCENT) {
|
|
140
|
+
status = 'warning';
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
status,
|
|
144
|
+
used: totalUsed,
|
|
145
|
+
limit: totalLimit,
|
|
146
|
+
remaining,
|
|
147
|
+
percentage,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export function shouldRotateAccount(account, rotationThreshold = 90) {
|
|
151
|
+
if (!account.usedCount || !account.limitCount) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const usage = {
|
|
155
|
+
usedCount: account.usedCount,
|
|
156
|
+
limitCount: account.limitCount,
|
|
157
|
+
};
|
|
158
|
+
const percentage = calculateUsagePercentage(usage);
|
|
159
|
+
return percentage >= rotationThreshold;
|
|
160
|
+
}
|
|
161
|
+
export function selectAccountForRequest(accounts, strategy = 'most-quota') {
|
|
162
|
+
const healthyAccounts = filterHealthyAccounts(accounts);
|
|
163
|
+
if (healthyAccounts.length === 0) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (strategy === 'most-quota') {
|
|
167
|
+
return getAccountWithMostQuota(healthyAccounts);
|
|
168
|
+
}
|
|
169
|
+
const sorted = healthyAccounts.sort((a, b) => {
|
|
170
|
+
const aLastUsed = a.lastUsed || 0;
|
|
171
|
+
const bLastUsed = b.lastUsed || 0;
|
|
172
|
+
return aLastUsed - bLastUsed;
|
|
173
|
+
});
|
|
174
|
+
return sorted[0] || null;
|
|
175
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RecoveryState {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
conversationId: string;
|
|
4
|
+
model: string;
|
|
5
|
+
lastMessageIndex: number;
|
|
6
|
+
errorCount: number;
|
|
7
|
+
lastError?: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RecoveryStorage {
|
|
11
|
+
version: 1;
|
|
12
|
+
sessions: Record<string, RecoveryState>;
|
|
13
|
+
}
|
|
14
|
+
export interface SessionRecoveryHook {
|
|
15
|
+
handleSessionError(error: any, sessionId: string): Promise<boolean>;
|
|
16
|
+
isRecoverableError(error: any): boolean;
|
|
17
|
+
clearSession(sessionId: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createSessionRecoveryHook(enabled: boolean, autoResume: boolean): SessionRecoveryHook;
|