@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export { KiroOAuthPlugin } from './plugin.js';
|
|
2
|
+
export type { KiroConfig } from './plugin/config/index.js';
|
|
3
|
+
export type { KiroAuthMethod, KiroRegion, ManagedAccount } from './plugin/types.js';
|
|
4
|
+
declare const _default: {
|
|
5
|
+
id: string;
|
|
6
|
+
server: ({ client, directory }: any) => Promise<{
|
|
7
|
+
config: (input: any) => Promise<void>;
|
|
8
|
+
auth: {
|
|
9
|
+
provider: string;
|
|
10
|
+
loader: (getAuth: any) => Promise<{
|
|
11
|
+
apiKey: string;
|
|
12
|
+
baseURL: string;
|
|
13
|
+
fetch: (input: any, init?: any) => Promise<Response>;
|
|
14
|
+
}>;
|
|
15
|
+
methods: ({
|
|
16
|
+
type: "oauth";
|
|
17
|
+
label: string;
|
|
18
|
+
prompts?: Array<{
|
|
19
|
+
type: "text";
|
|
20
|
+
key: string;
|
|
21
|
+
message: string;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
validate?: (value: string) => string | undefined;
|
|
24
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
25
|
+
when?: {
|
|
26
|
+
key: string;
|
|
27
|
+
op: "eq" | "neq";
|
|
28
|
+
value: string;
|
|
29
|
+
};
|
|
30
|
+
} | {
|
|
31
|
+
type: "select";
|
|
32
|
+
key: string;
|
|
33
|
+
message: string;
|
|
34
|
+
options: Array<{
|
|
35
|
+
label: string;
|
|
36
|
+
value: string;
|
|
37
|
+
hint?: string;
|
|
38
|
+
}>;
|
|
39
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
40
|
+
when?: {
|
|
41
|
+
key: string;
|
|
42
|
+
op: "eq" | "neq";
|
|
43
|
+
value: string;
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
46
|
+
authorize(inputs?: Record<string, string>): Promise<import("@opencode-ai/plugin").AuthOAuthResult>;
|
|
47
|
+
} | {
|
|
48
|
+
type: "api";
|
|
49
|
+
label: string;
|
|
50
|
+
prompts?: Array<{
|
|
51
|
+
type: "text";
|
|
52
|
+
key: string;
|
|
53
|
+
message: string;
|
|
54
|
+
placeholder?: string;
|
|
55
|
+
validate?: (value: string) => string | undefined;
|
|
56
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
57
|
+
when?: {
|
|
58
|
+
key: string;
|
|
59
|
+
op: "eq" | "neq";
|
|
60
|
+
value: string;
|
|
61
|
+
};
|
|
62
|
+
} | {
|
|
63
|
+
type: "select";
|
|
64
|
+
key: string;
|
|
65
|
+
message: string;
|
|
66
|
+
options: Array<{
|
|
67
|
+
label: string;
|
|
68
|
+
value: string;
|
|
69
|
+
hint?: string;
|
|
70
|
+
}>;
|
|
71
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
72
|
+
when?: {
|
|
73
|
+
key: string;
|
|
74
|
+
op: "eq" | "neq";
|
|
75
|
+
value: string;
|
|
76
|
+
};
|
|
77
|
+
}>;
|
|
78
|
+
authorize?(inputs?: Record<string, string>): Promise<{
|
|
79
|
+
type: "success";
|
|
80
|
+
key: string;
|
|
81
|
+
provider?: string;
|
|
82
|
+
metadata?: Record<string, string>;
|
|
83
|
+
} | {
|
|
84
|
+
type: "failed";
|
|
85
|
+
}>;
|
|
86
|
+
})[];
|
|
87
|
+
};
|
|
88
|
+
provider: {
|
|
89
|
+
id: string;
|
|
90
|
+
models: (provider: any) => Promise<Record<string, any>>;
|
|
91
|
+
};
|
|
92
|
+
}>;
|
|
93
|
+
};
|
|
94
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class AccountCache {
|
|
2
|
+
private cache;
|
|
3
|
+
private allAccountsCache;
|
|
4
|
+
private lastUpdate;
|
|
5
|
+
private ttl;
|
|
6
|
+
constructor(ttlMs?: number);
|
|
7
|
+
getAll(): any[] | null;
|
|
8
|
+
setAll(accounts: any[]): void;
|
|
9
|
+
get(id: string): any | null;
|
|
10
|
+
set(id: string, account: any): void;
|
|
11
|
+
invalidate(id: string): void;
|
|
12
|
+
invalidateAll(): void;
|
|
13
|
+
private isExpired;
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class AccountCache {
|
|
2
|
+
cache = new Map();
|
|
3
|
+
allAccountsCache = null;
|
|
4
|
+
lastUpdate = 0;
|
|
5
|
+
ttl;
|
|
6
|
+
constructor(ttlMs = 60000) {
|
|
7
|
+
this.ttl = ttlMs;
|
|
8
|
+
}
|
|
9
|
+
getAll() {
|
|
10
|
+
if (this.isExpired()) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return this.allAccountsCache ? [...this.allAccountsCache] : null;
|
|
14
|
+
}
|
|
15
|
+
setAll(accounts) {
|
|
16
|
+
this.allAccountsCache = [...accounts];
|
|
17
|
+
this.lastUpdate = Date.now();
|
|
18
|
+
for (const acc of accounts) {
|
|
19
|
+
this.cache.set(acc.id, acc);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
get(id) {
|
|
23
|
+
if (this.isExpired()) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return this.cache.get(id) || null;
|
|
27
|
+
}
|
|
28
|
+
set(id, account) {
|
|
29
|
+
this.cache.set(id, account);
|
|
30
|
+
this.lastUpdate = Date.now();
|
|
31
|
+
}
|
|
32
|
+
invalidate(id) {
|
|
33
|
+
this.cache.delete(id);
|
|
34
|
+
this.allAccountsCache = null;
|
|
35
|
+
}
|
|
36
|
+
invalidateAll() {
|
|
37
|
+
this.cache.clear();
|
|
38
|
+
this.allAccountsCache = null;
|
|
39
|
+
this.lastUpdate = 0;
|
|
40
|
+
}
|
|
41
|
+
isExpired() {
|
|
42
|
+
return Date.now() - this.lastUpdate > this.ttl;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AccountCache } from './account-cache.js';
|
|
2
|
+
export declare class AccountRepository {
|
|
3
|
+
private cache;
|
|
4
|
+
constructor(cache: AccountCache);
|
|
5
|
+
findAll(): Promise<any[]>;
|
|
6
|
+
findById(id: string): Promise<any | null>;
|
|
7
|
+
save(account: any): Promise<void>;
|
|
8
|
+
delete(id: string): Promise<void>;
|
|
9
|
+
findHealthyAccounts(): Promise<any[]>;
|
|
10
|
+
batchSave(accounts: any[]): Promise<void>;
|
|
11
|
+
invalidateCache(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { kiroDb } from '../../plugin/storage/sqlite.js';
|
|
2
|
+
export class AccountRepository {
|
|
3
|
+
cache;
|
|
4
|
+
constructor(cache) {
|
|
5
|
+
this.cache = cache;
|
|
6
|
+
}
|
|
7
|
+
async findAll() {
|
|
8
|
+
const cached = this.cache.getAll();
|
|
9
|
+
if (cached) {
|
|
10
|
+
return cached;
|
|
11
|
+
}
|
|
12
|
+
const rows = kiroDb.getAccounts();
|
|
13
|
+
const accounts = rows.map((r) => ({
|
|
14
|
+
id: r.id,
|
|
15
|
+
email: r.email,
|
|
16
|
+
authMethod: r.auth_method,
|
|
17
|
+
region: r.region,
|
|
18
|
+
oidcRegion: r.oidc_region || undefined,
|
|
19
|
+
clientId: r.client_id,
|
|
20
|
+
clientSecret: r.client_secret,
|
|
21
|
+
profileArn: r.profile_arn,
|
|
22
|
+
startUrl: r.start_url || undefined,
|
|
23
|
+
refreshToken: r.refresh_token,
|
|
24
|
+
accessToken: r.access_token,
|
|
25
|
+
expiresAt: r.expires_at,
|
|
26
|
+
rateLimitResetTime: r.rate_limit_reset,
|
|
27
|
+
isHealthy: r.is_healthy === 1,
|
|
28
|
+
unhealthyReason: r.unhealthy_reason,
|
|
29
|
+
recoveryTime: r.recovery_time,
|
|
30
|
+
failCount: r.fail_count || 0,
|
|
31
|
+
lastUsed: r.last_used,
|
|
32
|
+
usedCount: r.used_count,
|
|
33
|
+
limitCount: r.limit_count,
|
|
34
|
+
lastSync: r.last_sync
|
|
35
|
+
}));
|
|
36
|
+
this.cache.setAll(accounts);
|
|
37
|
+
return accounts;
|
|
38
|
+
}
|
|
39
|
+
async findById(id) {
|
|
40
|
+
const cached = this.cache.get(id);
|
|
41
|
+
if (cached) {
|
|
42
|
+
return cached;
|
|
43
|
+
}
|
|
44
|
+
const accounts = await this.findAll();
|
|
45
|
+
return accounts.find((a) => a.id === id) || null;
|
|
46
|
+
}
|
|
47
|
+
async save(account) {
|
|
48
|
+
await kiroDb.upsertAccount(account);
|
|
49
|
+
this.cache.invalidate(account.id);
|
|
50
|
+
}
|
|
51
|
+
async delete(id) {
|
|
52
|
+
await kiroDb.deleteAccount(id);
|
|
53
|
+
this.cache.invalidate(id);
|
|
54
|
+
}
|
|
55
|
+
async findHealthyAccounts() {
|
|
56
|
+
const all = await this.findAll();
|
|
57
|
+
return all.filter((a) => a.isHealthy);
|
|
58
|
+
}
|
|
59
|
+
async batchSave(accounts) {
|
|
60
|
+
await kiroDb.batchUpsertAccounts(accounts);
|
|
61
|
+
this.cache.invalidateAll();
|
|
62
|
+
}
|
|
63
|
+
invalidateCache() {
|
|
64
|
+
this.cache.invalidateAll();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './database/account-cache.js';
|
|
2
|
+
export * from './database/account-repository.js';
|
|
3
|
+
export * from './transformers/event-stream-parser.js';
|
|
4
|
+
export * from './transformers/history-builder.js';
|
|
5
|
+
export * from './transformers/message-transformer.js';
|
|
6
|
+
export * from './transformers/tool-call-parser.js';
|
|
7
|
+
export * from './transformers/tool-transformer.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './database/account-cache.js';
|
|
2
|
+
export * from './database/account-repository.js';
|
|
3
|
+
export * from './transformers/event-stream-parser.js';
|
|
4
|
+
export * from './transformers/history-builder.js';
|
|
5
|
+
export * from './transformers/message-transformer.js';
|
|
6
|
+
export * from './transformers/tool-call-parser.js';
|
|
7
|
+
export * from './transformers/tool-transformer.js';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export function parseAwsEventStreamBuffer(buffer) {
|
|
2
|
+
const events = [];
|
|
3
|
+
let remaining = buffer;
|
|
4
|
+
let searchStart = 0;
|
|
5
|
+
while (true) {
|
|
6
|
+
const contentStart = remaining.indexOf('{"content":', searchStart);
|
|
7
|
+
const nameStart = remaining.indexOf('{"name":', searchStart);
|
|
8
|
+
const followupStart = remaining.indexOf('{"followupPrompt":', searchStart);
|
|
9
|
+
const inputStart = remaining.indexOf('{"input":', searchStart);
|
|
10
|
+
const stopStart = remaining.indexOf('{"stop":', searchStart);
|
|
11
|
+
const contextUsageStart = remaining.indexOf('{"contextUsagePercentage":', searchStart);
|
|
12
|
+
const candidates = [
|
|
13
|
+
contentStart,
|
|
14
|
+
nameStart,
|
|
15
|
+
followupStart,
|
|
16
|
+
inputStart,
|
|
17
|
+
stopStart,
|
|
18
|
+
contextUsageStart
|
|
19
|
+
].filter((pos) => pos >= 0);
|
|
20
|
+
if (candidates.length === 0)
|
|
21
|
+
break;
|
|
22
|
+
const jsonStart = Math.min(...candidates);
|
|
23
|
+
if (jsonStart < 0)
|
|
24
|
+
break;
|
|
25
|
+
let braceCount = 0;
|
|
26
|
+
let jsonEnd = -1;
|
|
27
|
+
let inString = false;
|
|
28
|
+
let escapeNext = false;
|
|
29
|
+
for (let i = jsonStart; i < remaining.length; i++) {
|
|
30
|
+
const char = remaining[i];
|
|
31
|
+
if (escapeNext) {
|
|
32
|
+
escapeNext = false;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === '\\') {
|
|
36
|
+
escapeNext = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (char === '"') {
|
|
40
|
+
inString = !inString;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!inString) {
|
|
44
|
+
if (char === '{') {
|
|
45
|
+
braceCount++;
|
|
46
|
+
}
|
|
47
|
+
else if (char === '}') {
|
|
48
|
+
braceCount--;
|
|
49
|
+
if (braceCount === 0) {
|
|
50
|
+
jsonEnd = i;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (jsonEnd < 0) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const jsonStr = remaining.substring(jsonStart, jsonEnd + 1);
|
|
60
|
+
const parsed = parseEventLine(jsonStr);
|
|
61
|
+
if (parsed) {
|
|
62
|
+
if (parsed.content !== undefined && !parsed.followupPrompt) {
|
|
63
|
+
events.push({ type: 'content', data: parsed.content });
|
|
64
|
+
}
|
|
65
|
+
else if (parsed.name && parsed.toolUseId) {
|
|
66
|
+
events.push({
|
|
67
|
+
type: 'toolUse',
|
|
68
|
+
data: {
|
|
69
|
+
name: parsed.name,
|
|
70
|
+
toolUseId: parsed.toolUseId,
|
|
71
|
+
input: parsed.input || '',
|
|
72
|
+
stop: parsed.stop || false
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else if (parsed.input !== undefined && !parsed.name) {
|
|
77
|
+
events.push({
|
|
78
|
+
type: 'toolUseInput',
|
|
79
|
+
data: {
|
|
80
|
+
input: parsed.input
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined) {
|
|
85
|
+
events.push({
|
|
86
|
+
type: 'toolUseStop',
|
|
87
|
+
data: {
|
|
88
|
+
stop: parsed.stop
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (parsed.contextUsagePercentage !== undefined) {
|
|
93
|
+
events.push({
|
|
94
|
+
type: 'contextUsage',
|
|
95
|
+
data: {
|
|
96
|
+
contextUsagePercentage: parsed.contextUsagePercentage
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
searchStart = jsonEnd + 1;
|
|
102
|
+
if (searchStart >= remaining.length) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return events;
|
|
107
|
+
}
|
|
108
|
+
export function parseEventLine(line) {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(line);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CodeWhispererMessage } from '../../plugin/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Collapse agentic loop sequences in the built history.
|
|
4
|
+
*
|
|
5
|
+
* Each agentic iteration gets a fresh conversationId, so the model re-derives its preamble
|
|
6
|
+
* (intent detection, greeting) every iteration. When replayed for the next user turn, the
|
|
7
|
+
* model sees duplicate preambles and gets confused.
|
|
8
|
+
*
|
|
9
|
+
* Strips text from intermediate ASST(toolUses)→USER(toolResults) pairs, keeping only the
|
|
10
|
+
* first assistant text and all tool_use/tool_result pairs.
|
|
11
|
+
*/
|
|
12
|
+
export declare function collapseAgenticLoops(history: CodeWhispererMessage[]): CodeWhispererMessage[];
|
|
13
|
+
export declare function buildHistory(msgs: any[], resolved: string): CodeWhispererMessage[];
|
|
14
|
+
export declare function injectSystemPrompt(history: CodeWhispererMessage[], system: string | undefined, resolved: string): CodeWhispererMessage[];
|
|
15
|
+
export declare function historyHasToolCalling(history: CodeWhispererMessage[]): boolean;
|
|
16
|
+
export declare function extractToolNamesFromHistory(history: CodeWhispererMessage[]): Set<string>;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { KIRO_CONSTANTS } from '../../constants.js';
|
|
2
|
+
import { convertImagesToKiroFormat, extractAllImages, extractTextFromParts } from '../../plugin/image-handler.js';
|
|
3
|
+
import { getContentText } from './message-transformer.js';
|
|
4
|
+
import { deduplicateToolResults } from './tool-transformer.js';
|
|
5
|
+
/**
|
|
6
|
+
* Collapse agentic loop sequences in the built history.
|
|
7
|
+
*
|
|
8
|
+
* Each agentic iteration gets a fresh conversationId, so the model re-derives its preamble
|
|
9
|
+
* (intent detection, greeting) every iteration. When replayed for the next user turn, the
|
|
10
|
+
* model sees duplicate preambles and gets confused.
|
|
11
|
+
*
|
|
12
|
+
* Strips text from intermediate ASST(toolUses)→USER(toolResults) pairs, keeping only the
|
|
13
|
+
* first assistant text and all tool_use/tool_result pairs.
|
|
14
|
+
*/
|
|
15
|
+
export function collapseAgenticLoops(history) {
|
|
16
|
+
if (history.length < 4)
|
|
17
|
+
return history;
|
|
18
|
+
const result = [];
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (i < history.length) {
|
|
21
|
+
const entry = history[i];
|
|
22
|
+
if (entry?.assistantResponseMessage?.toolUses &&
|
|
23
|
+
i + 1 < history.length &&
|
|
24
|
+
history[i + 1]?.userInputMessage?.userInputMessageContext?.toolResults) {
|
|
25
|
+
const seqStart = i;
|
|
26
|
+
let j = i;
|
|
27
|
+
while (j < history.length) {
|
|
28
|
+
const asst = history[j];
|
|
29
|
+
if (!asst?.assistantResponseMessage?.toolUses)
|
|
30
|
+
break;
|
|
31
|
+
const nextUser = j + 1 < history.length ? history[j + 1] : null;
|
|
32
|
+
if (!nextUser?.userInputMessage?.userInputMessageContext?.toolResults)
|
|
33
|
+
break;
|
|
34
|
+
j += 2;
|
|
35
|
+
}
|
|
36
|
+
const seqEnd = j;
|
|
37
|
+
const pairCount = (seqEnd - seqStart) / 2;
|
|
38
|
+
if (pairCount > 1) {
|
|
39
|
+
for (let k = seqStart; k < seqEnd; k += 2) {
|
|
40
|
+
const asst = history[k];
|
|
41
|
+
const user = history[k + 1];
|
|
42
|
+
if (k === seqStart) {
|
|
43
|
+
result.push(asst);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
result.push({
|
|
47
|
+
assistantResponseMessage: {
|
|
48
|
+
content: '[system: tool calling continues]',
|
|
49
|
+
toolUses: asst.assistantResponseMessage.toolUses
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
result.push(user);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
for (let k = seqStart; k < seqEnd; k++) {
|
|
58
|
+
result.push(history[k]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
i = seqEnd;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
result.push(entry);
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
export function buildHistory(msgs, resolved) {
|
|
71
|
+
let history = [];
|
|
72
|
+
for (let i = 0; i < msgs.length - 1; i++) {
|
|
73
|
+
const m = msgs[i];
|
|
74
|
+
if (!m)
|
|
75
|
+
continue;
|
|
76
|
+
if (m.role === 'user') {
|
|
77
|
+
const uim = { content: '', modelId: resolved, origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR };
|
|
78
|
+
const trs = [];
|
|
79
|
+
if (Array.isArray(m.content)) {
|
|
80
|
+
uim.content = extractTextFromParts(m.content);
|
|
81
|
+
for (const p of m.content) {
|
|
82
|
+
if (p.type === 'tool_result') {
|
|
83
|
+
trs.push({
|
|
84
|
+
content: [{ text: getContentText(p.content || p) }],
|
|
85
|
+
status: 'success',
|
|
86
|
+
toolUseId: p.tool_use_id
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const unifiedImages = extractAllImages(m.content);
|
|
91
|
+
if (unifiedImages.length > 0) {
|
|
92
|
+
const { images, omitted } = convertImagesToKiroFormat(unifiedImages);
|
|
93
|
+
uim.images = images;
|
|
94
|
+
if (omitted > 0) {
|
|
95
|
+
uim.content += `\n\n[${omitted} image(s) omitted due to API limits]`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
uim.content = getContentText(m);
|
|
101
|
+
}
|
|
102
|
+
if (trs.length)
|
|
103
|
+
uim.userInputMessageContext = { toolResults: deduplicateToolResults(trs) };
|
|
104
|
+
const prev = history[history.length - 1];
|
|
105
|
+
if (prev && prev.userInputMessage)
|
|
106
|
+
history.push({ assistantResponseMessage: { content: '[system: conversation continues]' } });
|
|
107
|
+
history.push({ userInputMessage: uim });
|
|
108
|
+
}
|
|
109
|
+
else if (m.role === 'tool') {
|
|
110
|
+
const trs = [];
|
|
111
|
+
if (m.tool_results) {
|
|
112
|
+
for (const tr of m.tool_results)
|
|
113
|
+
trs.push({
|
|
114
|
+
content: [{ text: getContentText(tr) }],
|
|
115
|
+
status: 'success',
|
|
116
|
+
toolUseId: tr.tool_call_id
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
trs.push({
|
|
121
|
+
content: [{ text: getContentText(m) }],
|
|
122
|
+
status: 'success',
|
|
123
|
+
toolUseId: m.tool_call_id
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const prev = history[history.length - 1];
|
|
127
|
+
if (prev && prev.userInputMessage)
|
|
128
|
+
history.push({ assistantResponseMessage: { content: '[system: conversation continues]' } });
|
|
129
|
+
history.push({
|
|
130
|
+
userInputMessage: {
|
|
131
|
+
content: 'Tool results provided.',
|
|
132
|
+
modelId: resolved,
|
|
133
|
+
origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR,
|
|
134
|
+
userInputMessageContext: { toolResults: deduplicateToolResults(trs) }
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else if (m.role === 'assistant') {
|
|
139
|
+
const arm = { content: '' };
|
|
140
|
+
const tus = [];
|
|
141
|
+
let th = '';
|
|
142
|
+
if (Array.isArray(m.content)) {
|
|
143
|
+
for (const p of m.content) {
|
|
144
|
+
if (p.type === 'text')
|
|
145
|
+
arm.content += p.text || '';
|
|
146
|
+
else if (p.type === 'thinking')
|
|
147
|
+
th += p.thinking || p.text || '';
|
|
148
|
+
else if (p.type === 'tool_use')
|
|
149
|
+
tus.push({ input: p.input, name: p.name, toolUseId: p.id });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
arm.content = getContentText(m);
|
|
154
|
+
if (m.tool_calls && Array.isArray(m.tool_calls)) {
|
|
155
|
+
for (const tc of m.tool_calls) {
|
|
156
|
+
tus.push({
|
|
157
|
+
input: typeof tc.function?.arguments === 'string'
|
|
158
|
+
? JSON.parse(tc.function.arguments)
|
|
159
|
+
: tc.function?.arguments,
|
|
160
|
+
name: tc.function?.name,
|
|
161
|
+
toolUseId: tc.id
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (th)
|
|
166
|
+
arm.content = arm.content
|
|
167
|
+
? `<thinking>${th}</thinking>\n\n${arm.content}`
|
|
168
|
+
: `<thinking>${th}</thinking>`;
|
|
169
|
+
if (tus.length)
|
|
170
|
+
arm.toolUses = tus;
|
|
171
|
+
if (!arm.content && !arm.toolUses) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const prevMsg = history[history.length - 1];
|
|
175
|
+
if (prevMsg && prevMsg.assistantResponseMessage) {
|
|
176
|
+
// Merge consecutive assistant messages instead of injecting synthetic user turn
|
|
177
|
+
const prev = prevMsg.assistantResponseMessage;
|
|
178
|
+
if (arm.content) {
|
|
179
|
+
prev.content = prev.content ? `${prev.content}\n\n${arm.content}` : arm.content;
|
|
180
|
+
}
|
|
181
|
+
if (arm.toolUses) {
|
|
182
|
+
prev.toolUses = [...(prev.toolUses || []), ...arm.toolUses];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
history.push({ assistantResponseMessage: arm });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return collapseAgenticLoops(history);
|
|
191
|
+
}
|
|
192
|
+
export function injectSystemPrompt(history, system, resolved) {
|
|
193
|
+
if (!system)
|
|
194
|
+
return history;
|
|
195
|
+
const firstUserMsg = history.find((h) => !!h.userInputMessage);
|
|
196
|
+
if (firstUserMsg && firstUserMsg.userInputMessage) {
|
|
197
|
+
const oldContent = firstUserMsg.userInputMessage.content || '';
|
|
198
|
+
firstUserMsg.userInputMessage.content = `${system}\n\n${oldContent}`;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
history.unshift({
|
|
202
|
+
userInputMessage: {
|
|
203
|
+
content: system,
|
|
204
|
+
modelId: resolved,
|
|
205
|
+
origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return history;
|
|
210
|
+
}
|
|
211
|
+
export function historyHasToolCalling(history) {
|
|
212
|
+
return history.some((h) => h.assistantResponseMessage?.toolUses ||
|
|
213
|
+
h.userInputMessage?.userInputMessageContext?.toolResults);
|
|
214
|
+
}
|
|
215
|
+
export function extractToolNamesFromHistory(history) {
|
|
216
|
+
const toolNames = new Set();
|
|
217
|
+
for (const h of history) {
|
|
218
|
+
if (h.assistantResponseMessage?.toolUses) {
|
|
219
|
+
for (const tu of h.assistantResponseMessage.toolUses) {
|
|
220
|
+
if (tu.name)
|
|
221
|
+
toolNames.add(tu.name);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return toolNames;
|
|
226
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CodeWhispererMessage } from '../../plugin/types.js';
|
|
2
|
+
export declare function sanitizeHistory(history: CodeWhispererMessage[]): CodeWhispererMessage[];
|
|
3
|
+
export declare function findOriginalToolCall(msgs: any[], toolUseId: string): any | null;
|
|
4
|
+
export declare function mergeAdjacentMessages(msgs: any[]): any[];
|
|
5
|
+
export declare function getContentText(m: any): string;
|