@kervnet/opencode-kiro-auth 1.5.1

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 (106) hide show
  1. package/README.md +159 -0
  2. package/dist/constants.d.ts +24 -0
  3. package/dist/constants.js +55 -0
  4. package/dist/core/account/account-selector.d.ts +25 -0
  5. package/dist/core/account/account-selector.js +84 -0
  6. package/dist/core/account/usage-tracker.d.ts +17 -0
  7. package/dist/core/account/usage-tracker.js +39 -0
  8. package/dist/core/auth/auth-handler.d.ts +15 -0
  9. package/dist/core/auth/auth-handler.js +43 -0
  10. package/dist/core/auth/idc-auth-method.d.ts +17 -0
  11. package/dist/core/auth/idc-auth-method.js +200 -0
  12. package/dist/core/auth/token-refresher.d.ts +22 -0
  13. package/dist/core/auth/token-refresher.js +53 -0
  14. package/dist/core/index.d.ts +9 -0
  15. package/dist/core/index.js +9 -0
  16. package/dist/core/request/error-handler.d.ts +30 -0
  17. package/dist/core/request/error-handler.js +113 -0
  18. package/dist/core/request/request-handler.d.ts +27 -0
  19. package/dist/core/request/request-handler.js +199 -0
  20. package/dist/core/request/response-handler.d.ts +5 -0
  21. package/dist/core/request/response-handler.js +61 -0
  22. package/dist/core/request/retry-strategy.d.ts +18 -0
  23. package/dist/core/request/retry-strategy.js +28 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.js +1 -0
  26. package/dist/infrastructure/database/account-cache.d.ts +14 -0
  27. package/dist/infrastructure/database/account-cache.js +44 -0
  28. package/dist/infrastructure/database/account-repository.d.ts +12 -0
  29. package/dist/infrastructure/database/account-repository.js +64 -0
  30. package/dist/infrastructure/index.d.ts +7 -0
  31. package/dist/infrastructure/index.js +7 -0
  32. package/dist/infrastructure/transformers/event-stream-parser.d.ts +7 -0
  33. package/dist/infrastructure/transformers/event-stream-parser.js +115 -0
  34. package/dist/infrastructure/transformers/history-builder.d.ts +5 -0
  35. package/dist/infrastructure/transformers/history-builder.js +171 -0
  36. package/dist/infrastructure/transformers/message-transformer.d.ts +6 -0
  37. package/dist/infrastructure/transformers/message-transformer.js +102 -0
  38. package/dist/infrastructure/transformers/tool-call-parser.d.ts +4 -0
  39. package/dist/infrastructure/transformers/tool-call-parser.js +45 -0
  40. package/dist/infrastructure/transformers/tool-transformer.d.ts +2 -0
  41. package/dist/infrastructure/transformers/tool-transformer.js +19 -0
  42. package/dist/kiro/auth.d.ts +4 -0
  43. package/dist/kiro/auth.js +25 -0
  44. package/dist/kiro/oauth-idc.d.ts +24 -0
  45. package/dist/kiro/oauth-idc.js +151 -0
  46. package/dist/plugin/accounts.d.ts +29 -0
  47. package/dist/plugin/accounts.js +235 -0
  48. package/dist/plugin/auth-page.d.ts +3 -0
  49. package/dist/plugin/auth-page.js +573 -0
  50. package/dist/plugin/cli.d.ts +8 -0
  51. package/dist/plugin/cli.js +103 -0
  52. package/dist/plugin/config/index.d.ts +3 -0
  53. package/dist/plugin/config/index.js +2 -0
  54. package/dist/plugin/config/loader.d.ts +6 -0
  55. package/dist/plugin/config/loader.js +129 -0
  56. package/dist/plugin/config/schema.d.ts +56 -0
  57. package/dist/plugin/config/schema.js +36 -0
  58. package/dist/plugin/errors.d.ts +17 -0
  59. package/dist/plugin/errors.js +34 -0
  60. package/dist/plugin/health.d.ts +1 -0
  61. package/dist/plugin/health.js +9 -0
  62. package/dist/plugin/image-handler.d.ts +14 -0
  63. package/dist/plugin/image-handler.js +64 -0
  64. package/dist/plugin/logger.d.ts +8 -0
  65. package/dist/plugin/logger.js +63 -0
  66. package/dist/plugin/models.d.ts +1 -0
  67. package/dist/plugin/models.js +8 -0
  68. package/dist/plugin/request.d.ts +2 -0
  69. package/dist/plugin/request.js +239 -0
  70. package/dist/plugin/response.d.ts +3 -0
  71. package/dist/plugin/response.js +95 -0
  72. package/dist/plugin/server.d.ts +24 -0
  73. package/dist/plugin/server.js +166 -0
  74. package/dist/plugin/storage/locked-operations.d.ts +5 -0
  75. package/dist/plugin/storage/locked-operations.js +91 -0
  76. package/dist/plugin/storage/migrations.d.ts +2 -0
  77. package/dist/plugin/storage/migrations.js +109 -0
  78. package/dist/plugin/storage/sqlite.d.ts +17 -0
  79. package/dist/plugin/storage/sqlite.js +134 -0
  80. package/dist/plugin/streaming/index.d.ts +2 -0
  81. package/dist/plugin/streaming/index.js +2 -0
  82. package/dist/plugin/streaming/openai-converter.d.ts +2 -0
  83. package/dist/plugin/streaming/openai-converter.js +68 -0
  84. package/dist/plugin/streaming/stream-parser.d.ts +5 -0
  85. package/dist/plugin/streaming/stream-parser.js +136 -0
  86. package/dist/plugin/streaming/stream-state.d.ts +5 -0
  87. package/dist/plugin/streaming/stream-state.js +59 -0
  88. package/dist/plugin/streaming/stream-transformer.d.ts +1 -0
  89. package/dist/plugin/streaming/stream-transformer.js +248 -0
  90. package/dist/plugin/streaming/types.d.ts +25 -0
  91. package/dist/plugin/streaming/types.js +2 -0
  92. package/dist/plugin/sync/aws-sso.d.ts +2 -0
  93. package/dist/plugin/sync/aws-sso.js +50 -0
  94. package/dist/plugin/sync/kiro-cli-parser.d.ts +8 -0
  95. package/dist/plugin/sync/kiro-cli-parser.js +72 -0
  96. package/dist/plugin/sync/kiro-cli.d.ts +2 -0
  97. package/dist/plugin/sync/kiro-cli.js +197 -0
  98. package/dist/plugin/token.d.ts +2 -0
  99. package/dist/plugin/token.js +79 -0
  100. package/dist/plugin/types.d.ts +109 -0
  101. package/dist/plugin/types.js +0 -0
  102. package/dist/plugin/usage.d.ts +3 -0
  103. package/dist/plugin/usage.js +45 -0
  104. package/dist/plugin.d.ts +32 -0
  105. package/dist/plugin.js +37 -0
  106. package/package.json +65 -0
@@ -0,0 +1,61 @@
1
+ import { parseEventStream } from '../../plugin/response';
2
+ import { transformKiroStream } from '../../plugin/streaming/index.js';
3
+ export class ResponseHandler {
4
+ async handleSuccess(response, model, conversationId, streaming) {
5
+ if (streaming) {
6
+ return this.handleStreaming(response, model, conversationId);
7
+ }
8
+ return this.handleNonStreaming(response, model, conversationId);
9
+ }
10
+ async handleStreaming(response, model, conversationId) {
11
+ const s = transformKiroStream(response, model, conversationId);
12
+ return new Response(new ReadableStream({
13
+ async start(c) {
14
+ try {
15
+ for await (const e of s) {
16
+ c.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(e)}\n\n`));
17
+ }
18
+ c.close();
19
+ }
20
+ catch (err) {
21
+ c.error(err);
22
+ }
23
+ }
24
+ }), { headers: { 'Content-Type': 'text/event-stream' } });
25
+ }
26
+ async handleNonStreaming(response, model, conversationId) {
27
+ const text = await response.text();
28
+ const p = parseEventStream(text);
29
+ const oai = {
30
+ id: conversationId,
31
+ object: 'chat.completion',
32
+ created: Math.floor(Date.now() / 1000),
33
+ model,
34
+ choices: [
35
+ {
36
+ index: 0,
37
+ message: { role: 'assistant', content: p.content },
38
+ finish_reason: p.stopReason === 'tool_use' ? 'tool_calls' : 'stop'
39
+ }
40
+ ],
41
+ usage: {
42
+ prompt_tokens: p.inputTokens || 0,
43
+ completion_tokens: p.outputTokens || 0,
44
+ total_tokens: (p.inputTokens || 0) + (p.outputTokens || 0)
45
+ }
46
+ };
47
+ if (p.toolCalls.length > 0) {
48
+ oai.choices[0].message.tool_calls = p.toolCalls.map((tc) => ({
49
+ id: tc.toolUseId,
50
+ type: 'function',
51
+ function: {
52
+ name: tc.name,
53
+ arguments: typeof tc.input === 'string' ? tc.input : JSON.stringify(tc.input)
54
+ }
55
+ }));
56
+ }
57
+ return new Response(JSON.stringify(oai), {
58
+ headers: { 'Content-Type': 'application/json' }
59
+ });
60
+ }
61
+ }
@@ -0,0 +1,18 @@
1
+ interface RetryConfig {
2
+ max_request_iterations: number;
3
+ request_timeout_ms: number;
4
+ }
5
+ interface RetryContext {
6
+ iterations: number;
7
+ startTime: number;
8
+ }
9
+ export declare class RetryStrategy {
10
+ private config;
11
+ constructor(config: RetryConfig);
12
+ shouldContinue(context: RetryContext): {
13
+ canContinue: boolean;
14
+ error?: string;
15
+ };
16
+ createContext(): RetryContext;
17
+ }
18
+ export {};
@@ -0,0 +1,28 @@
1
+ export class RetryStrategy {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ shouldContinue(context) {
7
+ context.iterations++;
8
+ if (context.iterations > this.config.max_request_iterations) {
9
+ return {
10
+ canContinue: false,
11
+ error: `Exceeded max iterations (${this.config.max_request_iterations})`
12
+ };
13
+ }
14
+ if (Date.now() - context.startTime > this.config.request_timeout_ms) {
15
+ return {
16
+ canContinue: false,
17
+ error: 'Request timeout'
18
+ };
19
+ }
20
+ return { canContinue: true };
21
+ }
22
+ createContext() {
23
+ return {
24
+ iterations: 0,
25
+ startTime: Date.now()
26
+ };
27
+ }
28
+ }
@@ -0,0 +1,3 @@
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';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { KiroOAuthPlugin } from './plugin.js';
@@ -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,64 @@
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
+ clientId: r.client_id,
19
+ clientSecret: r.client_secret,
20
+ profileArn: r.profile_arn,
21
+ refreshToken: r.refresh_token,
22
+ accessToken: r.access_token,
23
+ expiresAt: r.expires_at,
24
+ rateLimitResetTime: r.rate_limit_reset,
25
+ isHealthy: r.is_healthy === 1,
26
+ unhealthyReason: r.unhealthy_reason,
27
+ recoveryTime: r.recovery_time,
28
+ failCount: r.fail_count || 0,
29
+ lastUsed: r.last_used,
30
+ usedCount: r.used_count,
31
+ limitCount: r.limit_count,
32
+ lastSync: r.last_sync
33
+ }));
34
+ this.cache.setAll(accounts);
35
+ return accounts;
36
+ }
37
+ async findById(id) {
38
+ const cached = this.cache.get(id);
39
+ if (cached) {
40
+ return cached;
41
+ }
42
+ const accounts = await this.findAll();
43
+ return accounts.find((a) => a.id === id) || null;
44
+ }
45
+ async save(account) {
46
+ await kiroDb.upsertAccount(account);
47
+ this.cache.invalidate(account.id);
48
+ }
49
+ async delete(id) {
50
+ await kiroDb.deleteAccount(id);
51
+ this.cache.invalidate(id);
52
+ }
53
+ async findHealthyAccounts() {
54
+ const all = await this.findAll();
55
+ return all.filter((a) => a.isHealthy);
56
+ }
57
+ async batchSave(accounts) {
58
+ await kiroDb.batchUpsertAccounts(accounts);
59
+ this.cache.invalidateAll();
60
+ }
61
+ invalidateCache() {
62
+ this.cache.invalidateAll();
63
+ }
64
+ }
@@ -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,5 @@
1
+ import type { CodeWhispererMessage } from '../../plugin/types';
2
+ export declare function buildHistory(msgs: any[], resolved: string, system: string | undefined, toolResultLimit: number): CodeWhispererMessage[];
3
+ export declare function truncateHistory(history: CodeWhispererMessage[], historyLimit: number): CodeWhispererMessage[];
4
+ export declare function historyHasToolCalling(history: CodeWhispererMessage[]): boolean;
5
+ export declare function extractToolNamesFromHistory(history: CodeWhispererMessage[]): Set<string>;
@@ -0,0 +1,171 @@
1
+ import { KIRO_CONSTANTS } from '../../constants.js';
2
+ import { convertImagesToKiroFormat, extractAllImages, extractTextFromParts } from '../../plugin/image-handler.js';
3
+ import { getContentText, sanitizeHistory, truncate } from './message-transformer.js';
4
+ import { deduplicateToolResults } from './tool-transformer.js';
5
+ export function buildHistory(msgs, resolved, system, toolResultLimit) {
6
+ let history = [];
7
+ let firstUserIndex = -1;
8
+ for (let i = 0; i < msgs.length; i++) {
9
+ if (msgs[i].role === 'user') {
10
+ firstUserIndex = i;
11
+ break;
12
+ }
13
+ }
14
+ if (system) {
15
+ if (firstUserIndex !== -1) {
16
+ const m = msgs[firstUserIndex];
17
+ const oldContent = getContentText(m);
18
+ if (Array.isArray(m.content)) {
19
+ m.content = [
20
+ { type: 'text', text: `${system}\n\n${oldContent}` },
21
+ ...m.content.filter((p) => p.type !== 'text')
22
+ ];
23
+ }
24
+ else
25
+ m.content = `${system}\n\n${oldContent}`;
26
+ }
27
+ else {
28
+ history.push({
29
+ userInputMessage: {
30
+ content: system,
31
+ modelId: resolved,
32
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR
33
+ }
34
+ });
35
+ }
36
+ }
37
+ for (let i = 0; i < msgs.length - 1; i++) {
38
+ const m = msgs[i];
39
+ if (!m)
40
+ continue;
41
+ if (m.role === 'user') {
42
+ const uim = { content: '', modelId: resolved, origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR };
43
+ const trs = [];
44
+ if (Array.isArray(m.content)) {
45
+ uim.content = extractTextFromParts(m.content);
46
+ for (const p of m.content) {
47
+ if (p.type === 'tool_result') {
48
+ trs.push({
49
+ content: [{ text: truncate(getContentText(p.content || p), toolResultLimit) }],
50
+ status: 'success',
51
+ toolUseId: p.tool_use_id
52
+ });
53
+ }
54
+ }
55
+ const unifiedImages = extractAllImages(m.content);
56
+ if (unifiedImages.length > 0) {
57
+ uim.images = convertImagesToKiroFormat(unifiedImages);
58
+ }
59
+ }
60
+ else {
61
+ uim.content = getContentText(m);
62
+ }
63
+ if (trs.length)
64
+ uim.userInputMessageContext = { toolResults: deduplicateToolResults(trs) };
65
+ const prev = history[history.length - 1];
66
+ if (prev && prev.userInputMessage)
67
+ history.push({ assistantResponseMessage: { content: 'Continue' } });
68
+ history.push({ userInputMessage: uim });
69
+ }
70
+ else if (m.role === 'tool') {
71
+ const trs = [];
72
+ if (m.tool_results) {
73
+ for (const tr of m.tool_results)
74
+ trs.push({
75
+ content: [{ text: truncate(getContentText(tr), toolResultLimit) }],
76
+ status: 'success',
77
+ toolUseId: tr.tool_call_id
78
+ });
79
+ }
80
+ else {
81
+ trs.push({
82
+ content: [{ text: truncate(getContentText(m), toolResultLimit) }],
83
+ status: 'success',
84
+ toolUseId: m.tool_call_id
85
+ });
86
+ }
87
+ const prev = history[history.length - 1];
88
+ if (prev && prev.userInputMessage)
89
+ history.push({ assistantResponseMessage: { content: 'Continue' } });
90
+ history.push({
91
+ userInputMessage: {
92
+ content: 'Tool results provided.',
93
+ modelId: resolved,
94
+ origin: KIRO_CONSTANTS.ORIGIN_AI_EDITOR,
95
+ userInputMessageContext: { toolResults: deduplicateToolResults(trs) }
96
+ }
97
+ });
98
+ }
99
+ else if (m.role === 'assistant') {
100
+ const arm = { content: '' };
101
+ const tus = [];
102
+ let th = '';
103
+ if (Array.isArray(m.content)) {
104
+ for (const p of m.content) {
105
+ if (p.type === 'text')
106
+ arm.content += p.text || '';
107
+ else if (p.type === 'thinking')
108
+ th += p.thinking || p.text || '';
109
+ else if (p.type === 'tool_use')
110
+ tus.push({ input: p.input, name: p.name, toolUseId: p.id });
111
+ }
112
+ }
113
+ else
114
+ arm.content = getContentText(m);
115
+ if (m.tool_calls && Array.isArray(m.tool_calls)) {
116
+ for (const tc of m.tool_calls) {
117
+ tus.push({
118
+ input: typeof tc.function?.arguments === 'string'
119
+ ? JSON.parse(tc.function.arguments)
120
+ : tc.function?.arguments,
121
+ name: tc.function?.name,
122
+ toolUseId: tc.id
123
+ });
124
+ }
125
+ }
126
+ if (th)
127
+ arm.content = arm.content
128
+ ? `<thinking>${th}</thinking>\n\n${arm.content}`
129
+ : `<thinking>${th}</thinking>`;
130
+ if (tus.length)
131
+ arm.toolUses = tus;
132
+ if (!arm.content && !arm.toolUses) {
133
+ continue;
134
+ }
135
+ history.push({ assistantResponseMessage: arm });
136
+ }
137
+ }
138
+ return history;
139
+ }
140
+ export function truncateHistory(history, historyLimit) {
141
+ let sanitized = sanitizeHistory(history);
142
+ let historySize = JSON.stringify(sanitized).length;
143
+ while (historySize > historyLimit && sanitized.length > 2) {
144
+ sanitized.shift();
145
+ while (sanitized.length > 0) {
146
+ const first = sanitized[0];
147
+ if (first && first.userInputMessage)
148
+ break;
149
+ sanitized.shift();
150
+ }
151
+ sanitized = sanitizeHistory(sanitized);
152
+ historySize = JSON.stringify(sanitized).length;
153
+ }
154
+ return sanitized;
155
+ }
156
+ export function historyHasToolCalling(history) {
157
+ return history.some((h) => h.assistantResponseMessage?.toolUses ||
158
+ h.userInputMessage?.userInputMessageContext?.toolResults);
159
+ }
160
+ export function extractToolNamesFromHistory(history) {
161
+ const toolNames = new Set();
162
+ for (const h of history) {
163
+ if (h.assistantResponseMessage?.toolUses) {
164
+ for (const tu of h.assistantResponseMessage.toolUses) {
165
+ if (tu.name)
166
+ toolNames.add(tu.name);
167
+ }
168
+ }
169
+ }
170
+ return toolNames;
171
+ }
@@ -0,0 +1,6 @@
1
+ import type { CodeWhispererMessage } from '../../plugin/types';
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;
6
+ export declare function truncate(s: string, max: number): string;
@@ -0,0 +1,102 @@
1
+ export function sanitizeHistory(history) {
2
+ const result = [];
3
+ for (let i = 0; i < history.length; i++) {
4
+ const m = history[i];
5
+ if (!m)
6
+ continue;
7
+ if (m.assistantResponseMessage?.toolUses) {
8
+ const next = history[i + 1];
9
+ if (next?.userInputMessage?.userInputMessageContext?.toolResults) {
10
+ result.push(m);
11
+ }
12
+ }
13
+ else if (m.userInputMessage?.userInputMessageContext?.toolResults) {
14
+ const prev = result[result.length - 1];
15
+ if (prev?.assistantResponseMessage?.toolUses) {
16
+ result.push(m);
17
+ }
18
+ }
19
+ else {
20
+ result.push(m);
21
+ }
22
+ }
23
+ if (result.length > 0) {
24
+ const first = result[0];
25
+ if (!first ||
26
+ !first.userInputMessage ||
27
+ first.userInputMessage.userInputMessageContext?.toolResults) {
28
+ return [];
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ export function findOriginalToolCall(msgs, toolUseId) {
34
+ for (const m of msgs) {
35
+ if (m.role === 'assistant') {
36
+ if (m.tool_calls) {
37
+ for (const tc of m.tool_calls)
38
+ if (tc.id === toolUseId)
39
+ return tc;
40
+ }
41
+ if (Array.isArray(m.content)) {
42
+ for (const p of m.content)
43
+ if (p.type === 'tool_use' && p.id === toolUseId)
44
+ return p;
45
+ }
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ export function mergeAdjacentMessages(msgs) {
51
+ const merged = [];
52
+ for (const m of msgs) {
53
+ if (!merged.length)
54
+ merged.push({ ...m });
55
+ else {
56
+ const last = merged[merged.length - 1];
57
+ if (last && m.role === last.role) {
58
+ if (Array.isArray(last.content) && Array.isArray(m.content))
59
+ last.content.push(...m.content);
60
+ else if (typeof last.content === 'string' && typeof m.content === 'string')
61
+ last.content += '\n' + m.content;
62
+ else if (Array.isArray(last.content) && typeof m.content === 'string')
63
+ last.content.push({ type: 'text', text: m.content });
64
+ else if (typeof last.content === 'string' && Array.isArray(m.content))
65
+ last.content = [{ type: 'text', text: last.content }, ...m.content];
66
+ if (m.tool_calls) {
67
+ if (!last.tool_calls)
68
+ last.tool_calls = [];
69
+ last.tool_calls.push(...m.tool_calls);
70
+ }
71
+ if (m.role === 'tool') {
72
+ if (!last.tool_results)
73
+ last.tool_results = [{ content: last.content, tool_call_id: last.tool_call_id }];
74
+ last.tool_results.push({ content: m.content, tool_call_id: m.tool_call_id });
75
+ }
76
+ }
77
+ else
78
+ merged.push({ ...m });
79
+ }
80
+ }
81
+ return merged;
82
+ }
83
+ export function getContentText(m) {
84
+ if (!m)
85
+ return '';
86
+ if (typeof m === 'string')
87
+ return m;
88
+ if (typeof m.content === 'string')
89
+ return m.content;
90
+ if (Array.isArray(m.content))
91
+ return m.content
92
+ .filter((p) => p.type === 'text')
93
+ .map((p) => p.text || '')
94
+ .join('');
95
+ return m.text || '';
96
+ }
97
+ export function truncate(s, max) {
98
+ if (s.length <= max)
99
+ return s;
100
+ const half = Math.floor(max / 2);
101
+ return s.substring(0, half) + '\n... [TRUNCATED] ...\n' + s.substring(s.length - half);
102
+ }