@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,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,2 @@
1
+ export { KiroOAuthPlugin } from './plugin.js';
2
+ export default { id: 'kiro-auth', server: (await import('./plugin.js')).KiroOAuthPlugin };
@@ -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,7 @@
1
+ interface ParsedEvent {
2
+ type: string;
3
+ data: any;
4
+ }
5
+ export declare function parseAwsEventStreamBuffer(buffer: string): ParsedEvent[];
6
+ export declare function parseEventLine(line: string): any | null;
7
+ export {};
@@ -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;