@flamingo-stack/openframe-frontend-core 0.0.172 → 0.0.173
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/dist/{chunk-AY3E5FKM.cjs → chunk-2MEBOTV4.cjs} +2263 -1698
- package/dist/chunk-2MEBOTV4.cjs.map +1 -0
- package/dist/{chunk-FEEPEOW2.js → chunk-J3ZCNPDM.js} +6128 -5563
- package/dist/chunk-J3ZCNPDM.js.map +1 -0
- package/dist/components/chat/approval-batch-message.d.ts +10 -0
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -0
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/chat-message-loader.d.ts +23 -0
- package/dist/components/chat/chat-message-loader.d.ts.map +1 -0
- package/dist/components/chat/hooks/index.d.ts +1 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-collapsible.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-delayed-flag.d.ts +25 -0
- package/dist/components/chat/hooks/use-delayed-flag.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
- package/dist/components/chat/index.d.ts +3 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/thinking-display.d.ts.map +1 -1
- package/dist/components/chat/tool-call-blocks.d.ts +18 -0
- package/dist/components/chat/tool-call-blocks.d.ts.map +1 -0
- package/dist/components/chat/tool-execution-display.d.ts +1 -1
- package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
- package/dist/components/chat/types/api.types.d.ts +16 -1
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +2 -2
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +54 -1
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/network.types.d.ts +2 -0
- package/dist/components/chat/types/network.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +14 -1
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +1 -0
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +32 -4
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts +2 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/chat/utils/tool-call-helpers.d.ts +38 -0
- package/dist/components/chat/utils/tool-call-helpers.d.ts.map +1 -0
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/index.cjs +14 -2
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +13 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/ui/index.cjs +14 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +13 -1
- package/dist/components/ui/textarea.d.ts +11 -0
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/index.cjs +14 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -1
- package/package.json +3 -3
- package/src/components/chat/approval-batch-message.tsx +237 -0
- package/src/components/chat/chat-input.tsx +34 -106
- package/src/components/chat/chat-message-enhanced.tsx +11 -0
- package/src/components/chat/chat-message-list.tsx +22 -6
- package/src/components/chat/chat-message-loader.tsx +67 -0
- package/src/components/chat/hooks/index.ts +1 -0
- package/src/components/chat/hooks/use-collapsible.ts +10 -1
- package/src/components/chat/hooks/use-delayed-flag.ts +56 -0
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +109 -20
- package/src/components/chat/index.ts +3 -0
- package/src/components/chat/thinking-display.tsx +76 -12
- package/src/components/chat/tool-call-blocks.tsx +58 -0
- package/src/components/chat/tool-execution-display.tsx +60 -81
- package/src/components/chat/types/api.types.ts +22 -3
- package/src/components/chat/types/component.types.ts +2 -2
- package/src/components/chat/types/message.types.ts +58 -1
- package/src/components/chat/types/network.types.ts +2 -0
- package/src/components/chat/types/processing.types.ts +13 -2
- package/src/components/chat/utils/chunk-parser.ts +40 -5
- package/src/components/chat/utils/extract-incomplete-message-state.ts +19 -1
- package/src/components/chat/utils/index.ts +3 -0
- package/src/components/chat/utils/message-segment-accumulator.ts +136 -12
- package/src/components/chat/utils/process-historical-messages.ts +88 -13
- package/src/components/chat/utils/tool-call-helpers.ts +105 -0
- package/src/components/ui/textarea.tsx +107 -25
- package/dist/chunk-AY3E5FKM.cjs.map +0 -1
- package/dist/chunk-FEEPEOW2.js.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Component prop types
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { ComponentType, HTMLAttributes, TextareaHTMLAttributes } from 'react'
|
|
6
6
|
import type { AssistantType, AuthorType, ChatApprovalStatus, ConnectionStatus } from './chat.types'
|
|
7
7
|
import type { ApprovalRequestData, Message, MessageSegment, ToolExecutionData } from './message.types'
|
|
8
8
|
import type { ChatRef } from '../chat-ref.types'
|
|
@@ -242,7 +242,7 @@ export interface ChatTypingIndicatorProps extends HTMLAttributes<HTMLDivElement>
|
|
|
242
242
|
|
|
243
243
|
// ========== Tool Execution Display Props ==========
|
|
244
244
|
|
|
245
|
-
export interface ToolExecutionDisplayProps extends
|
|
245
|
+
export interface ToolExecutionDisplayProps extends HTMLAttributes<HTMLDivElement> {
|
|
246
246
|
message: ToolExecutionData
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -38,6 +38,12 @@ export interface ToolExecutionData {
|
|
|
38
38
|
parameters?: Record<string, any>
|
|
39
39
|
result?: string
|
|
40
40
|
success?: boolean
|
|
41
|
+
/**
|
|
42
|
+
* Backend-issued id (matches `PendingToolCallData.toolExecutionRequestId`).
|
|
43
|
+
* When present, lets the accumulator merge this execution event into the
|
|
44
|
+
* matching approval batch row instead of emitting a standalone segment.
|
|
45
|
+
*/
|
|
46
|
+
toolExecutionRequestId?: string
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// ========== Approval Request Types ==========
|
|
@@ -57,6 +63,45 @@ export interface ApprovalResultData {
|
|
|
57
63
|
approvalType?: string
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Single tool call inside a batch approval request.
|
|
68
|
+
* Mirrors backend PendingToolCallDto.
|
|
69
|
+
*/
|
|
70
|
+
export interface PendingToolCallData {
|
|
71
|
+
toolExecutionRequestId: string
|
|
72
|
+
toolName: string
|
|
73
|
+
toolTitle?: string
|
|
74
|
+
toolExplanation?: string
|
|
75
|
+
toolType?: string
|
|
76
|
+
requiresApproval: boolean
|
|
77
|
+
approvalType?: string | null
|
|
78
|
+
toolCallArguments?: Record<string, any> | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Per-tool execution state inside an approval batch.
|
|
83
|
+
* Populated by EXECUTING_TOOL / EXECUTED_TOOL chunks that carry a
|
|
84
|
+
* `toolExecutionRequestId` matching one of the batch's tool calls.
|
|
85
|
+
*/
|
|
86
|
+
export interface ApprovalBatchExecutionState {
|
|
87
|
+
status: 'executing' | 'done'
|
|
88
|
+
result?: string
|
|
89
|
+
success?: boolean
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ApprovalBatchData {
|
|
93
|
+
approvalRequestId: string
|
|
94
|
+
/** Highest approval type required across the batch (e.g. ADMIN beats CLIENT). */
|
|
95
|
+
approvalType: string
|
|
96
|
+
toolCalls: PendingToolCallData[]
|
|
97
|
+
/**
|
|
98
|
+
* Keyed by `PendingToolCallData.toolExecutionRequestId`. Absent before
|
|
99
|
+
* approval; rows without an entry render as "queued" (loader) once the
|
|
100
|
+
* batch itself is approved.
|
|
101
|
+
*/
|
|
102
|
+
executions?: Record<string, ApprovalBatchExecutionState>
|
|
103
|
+
}
|
|
104
|
+
|
|
60
105
|
// ========== Message Segment Types ==========
|
|
61
106
|
|
|
62
107
|
export type TextSegment = {
|
|
@@ -82,6 +127,14 @@ export type ApprovalRequestSegment = {
|
|
|
82
127
|
onReject?: (requestId?: string) => void | Promise<void>
|
|
83
128
|
}
|
|
84
129
|
|
|
130
|
+
export type ApprovalBatchSegment = {
|
|
131
|
+
type: 'approval_batch'
|
|
132
|
+
data: ApprovalBatchData
|
|
133
|
+
status?: ChatApprovalStatus
|
|
134
|
+
onApprove?: (requestId?: string) => void | Promise<void>
|
|
135
|
+
onReject?: (requestId?: string) => void | Promise<void>
|
|
136
|
+
}
|
|
137
|
+
|
|
85
138
|
export type ErrorSegment = {
|
|
86
139
|
type: 'error'
|
|
87
140
|
title: string
|
|
@@ -94,7 +147,7 @@ export type ContextCompactionSegment = {
|
|
|
94
147
|
summary?: string
|
|
95
148
|
}
|
|
96
149
|
|
|
97
|
-
export type MessageSegment = TextSegment | ThinkingSegment | ToolExecutionSegment | ApprovalRequestSegment | ErrorSegment | ContextCompactionSegment
|
|
150
|
+
export type MessageSegment = TextSegment | ThinkingSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | ErrorSegment | ContextCompactionSegment
|
|
98
151
|
|
|
99
152
|
export type MessageContent = string | MessageSegment[]
|
|
100
153
|
|
|
@@ -119,6 +172,7 @@ export interface ExecutingToolMessageData extends MessageDataBase {
|
|
|
119
172
|
integratedToolType?: string
|
|
120
173
|
toolFunction?: string
|
|
121
174
|
parameters?: Record<string, any>
|
|
175
|
+
toolExecutionRequestId?: string
|
|
122
176
|
}
|
|
123
177
|
|
|
124
178
|
export interface ExecutedToolMessageData extends MessageDataBase {
|
|
@@ -128,6 +182,7 @@ export interface ExecutedToolMessageData extends MessageDataBase {
|
|
|
128
182
|
parameters?: Record<string, any>
|
|
129
183
|
result?: string
|
|
130
184
|
success?: boolean
|
|
185
|
+
toolExecutionRequestId?: string
|
|
131
186
|
}
|
|
132
187
|
|
|
133
188
|
export interface ApprovalRequestMessageData extends MessageDataBase {
|
|
@@ -136,6 +191,8 @@ export interface ApprovalRequestMessageData extends MessageDataBase {
|
|
|
136
191
|
approvalType?: string
|
|
137
192
|
command?: string
|
|
138
193
|
explanation?: string
|
|
194
|
+
/** Present when the approval is a batch of tool calls (new format). */
|
|
195
|
+
toolCalls?: PendingToolCallData[]
|
|
139
196
|
}
|
|
140
197
|
|
|
141
198
|
export interface ApprovalResultMessageData extends MessageDataBase {
|
|
@@ -41,6 +41,7 @@ export interface ChunkData {
|
|
|
41
41
|
parameters?: Record<string, any>
|
|
42
42
|
result?: string
|
|
43
43
|
success?: boolean
|
|
44
|
+
toolExecutionRequestId?: string
|
|
44
45
|
error?: string
|
|
45
46
|
details?: string
|
|
46
47
|
approvalRequestId?: string
|
|
@@ -49,6 +50,7 @@ export interface ChunkData {
|
|
|
49
50
|
command?: string
|
|
50
51
|
explanation?: string
|
|
51
52
|
approved?: boolean
|
|
53
|
+
toolCalls?: any[]
|
|
52
54
|
modelName?: string
|
|
53
55
|
providerName?: string
|
|
54
56
|
provider?: string
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Contains types for message parsing, accumulation, and processing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { MessageSegment, ProcessedMessage, ToolExecutionSegment, TokenUsageData } from './message.types'
|
|
6
|
+
import type { MessageSegment, ProcessedMessage, ToolExecutionSegment, TokenUsageData, PendingToolCallData } from './message.types'
|
|
7
7
|
import type { ChatApprovalStatus, AssistantType } from './chat.types'
|
|
8
8
|
import type { ChunkData, NatsMessageType } from './network.types'
|
|
9
9
|
|
|
@@ -18,6 +18,7 @@ export type ParsedChunkAction =
|
|
|
18
18
|
| { action: 'thinking'; text: string }
|
|
19
19
|
| { action: 'tool_execution'; segment: ToolExecutionSegment }
|
|
20
20
|
| { action: 'approval_request'; requestId: string; command: string; explanation?: string; approvalType: string }
|
|
21
|
+
| { action: 'approval_batch'; requestId: string; approvalType: string; toolCalls: PendingToolCallData[] }
|
|
21
22
|
| { action: 'approval_result'; requestId: string; approved: boolean; approvalType: string }
|
|
22
23
|
| { action: 'message_request'; text: string; ownerType?: string; displayName?: string }
|
|
23
24
|
| { action: 'token_usage'; data: TokenUsageData }
|
|
@@ -43,7 +44,10 @@ export interface AccumulatorState {
|
|
|
43
44
|
toolFunction: string
|
|
44
45
|
parameters?: Record<string, any>
|
|
45
46
|
}>
|
|
46
|
-
escalatedApprovals?: Map<
|
|
47
|
+
escalatedApprovals?: Map<
|
|
48
|
+
string,
|
|
49
|
+
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
50
|
+
>
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
// ========== Message Processing Options ==========
|
|
@@ -65,6 +69,13 @@ export interface MessageProcessingOptions {
|
|
|
65
69
|
approvalStatuses?: Record<string, ChatApprovalStatus>
|
|
66
70
|
/** Approval types to display directly (others get escalated) - defaults to all types */
|
|
67
71
|
displayApprovalTypes?: string[]
|
|
72
|
+
/**
|
|
73
|
+
* Consumer-owned. Forwarded by the host app (e.g. oss-tenant chat client
|
|
74
|
+
* reads its `'batch-approvals'` feature flag and passes it down). The lib
|
|
75
|
+
* defaults to legacy single-card rendering when this is omitted — it will
|
|
76
|
+
* not enable the batch UI on its own.
|
|
77
|
+
*/
|
|
78
|
+
batchApprovalsEnabled?: boolean
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
// ========== Chunk Processing Types ==========
|
|
@@ -2,7 +2,26 @@
|
|
|
2
2
|
* Utilities for parsing NATS chunks into structured actions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { MESSAGE_TYPE, type ChunkData, type ParsedChunkAction, type ToolExecutionSegment } from '../types'
|
|
5
|
+
import { MESSAGE_TYPE, type ChunkData, type ParsedChunkAction, type ToolExecutionSegment, type PendingToolCallData } from '../types'
|
|
6
|
+
|
|
7
|
+
function normalizeToolCalls(raw: unknown): PendingToolCallData[] {
|
|
8
|
+
if (!Array.isArray(raw)) return []
|
|
9
|
+
return raw
|
|
10
|
+
.filter((item): item is Record<string, any> => !!item && typeof item === 'object')
|
|
11
|
+
.map((item) => ({
|
|
12
|
+
toolExecutionRequestId: String(item.toolExecutionRequestId ?? ''),
|
|
13
|
+
toolName: String(item.toolName ?? ''),
|
|
14
|
+
toolTitle: typeof item.toolTitle === 'string' ? item.toolTitle : undefined,
|
|
15
|
+
toolExplanation: typeof item.toolExplanation === 'string' ? item.toolExplanation : undefined,
|
|
16
|
+
toolType: typeof item.toolType === 'string' ? item.toolType : undefined,
|
|
17
|
+
requiresApproval: item.requiresApproval === true,
|
|
18
|
+
approvalType: typeof item.approvalType === 'string' ? item.approvalType : null,
|
|
19
|
+
toolCallArguments:
|
|
20
|
+
item.toolCallArguments && typeof item.toolCallArguments === 'object'
|
|
21
|
+
? (item.toolCallArguments as Record<string, any>)
|
|
22
|
+
: null,
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
6
25
|
|
|
7
26
|
/**
|
|
8
27
|
* Parse a raw NATS chunk into a structured action
|
|
@@ -56,10 +75,11 @@ export function parseChunkToAction(chunk: unknown): ParsedChunkAction | null {
|
|
|
56
75
|
integratedToolType: data.integratedToolType || '',
|
|
57
76
|
toolFunction: data.toolFunction || '',
|
|
58
77
|
parameters: data.parameters,
|
|
78
|
+
toolExecutionRequestId: typeof data.toolExecutionRequestId === 'string' ? data.toolExecutionRequestId : undefined,
|
|
59
79
|
}
|
|
60
80
|
}
|
|
61
81
|
}
|
|
62
|
-
|
|
82
|
+
|
|
63
83
|
case MESSAGE_TYPE.EXECUTED_TOOL:
|
|
64
84
|
return {
|
|
65
85
|
action: 'tool_execution',
|
|
@@ -72,18 +92,33 @@ export function parseChunkToAction(chunk: unknown): ParsedChunkAction | null {
|
|
|
72
92
|
parameters: data.parameters,
|
|
73
93
|
result: data.result,
|
|
74
94
|
success: data.success,
|
|
95
|
+
toolExecutionRequestId: typeof data.toolExecutionRequestId === 'string' ? data.toolExecutionRequestId : undefined,
|
|
75
96
|
}
|
|
76
97
|
}
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
case MESSAGE_TYPE.APPROVAL_REQUEST:
|
|
100
|
+
case MESSAGE_TYPE.APPROVAL_REQUEST: {
|
|
101
|
+
const requestId = data.approvalRequestId || data.approval_request_id || ''
|
|
102
|
+
const approvalType = data.approvalType || 'USER'
|
|
103
|
+
const toolCalls = normalizeToolCalls(data.toolCalls)
|
|
104
|
+
|
|
105
|
+
if (toolCalls.length > 0) {
|
|
106
|
+
return {
|
|
107
|
+
action: 'approval_batch',
|
|
108
|
+
requestId,
|
|
109
|
+
approvalType,
|
|
110
|
+
toolCalls,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
80
114
|
return {
|
|
81
115
|
action: 'approval_request',
|
|
82
|
-
requestId
|
|
116
|
+
requestId,
|
|
83
117
|
command: data.command || '',
|
|
84
118
|
explanation: data.explanation,
|
|
85
|
-
approvalType
|
|
119
|
+
approvalType,
|
|
86
120
|
}
|
|
121
|
+
}
|
|
87
122
|
|
|
88
123
|
case MESSAGE_TYPE.APPROVAL_RESULT:
|
|
89
124
|
return {
|
|
@@ -33,7 +33,9 @@ export function extractIncompleteMessageState(
|
|
|
33
33
|
switch (segment.type) {
|
|
34
34
|
case 'tool_execution':
|
|
35
35
|
if (segment.data.type === 'EXECUTING_TOOL') {
|
|
36
|
-
const toolKey =
|
|
36
|
+
const toolKey =
|
|
37
|
+
segment.data.toolExecutionRequestId ||
|
|
38
|
+
`${segment.data.integratedToolType}-${segment.data.toolFunction}`
|
|
37
39
|
executingTools.set(toolKey, {
|
|
38
40
|
integratedToolType: segment.data.integratedToolType,
|
|
39
41
|
toolFunction: segment.data.toolFunction,
|
|
@@ -54,6 +56,22 @@ export function extractIncompleteMessageState(
|
|
|
54
56
|
}
|
|
55
57
|
break
|
|
56
58
|
|
|
59
|
+
case 'approval_batch': {
|
|
60
|
+
// Treat a batch as in-progress until every tool call has a
|
|
61
|
+
// `done` execution OR the batch was rejected. Otherwise the realtime
|
|
62
|
+
// accumulator won't hold the segment and post-approval EXECUTED_TOOL
|
|
63
|
+
// chunks won't be able to merge into it via `applyExecutionToBatch`.
|
|
64
|
+
const allDone =
|
|
65
|
+
!!segment.data.executions &&
|
|
66
|
+
segment.data.toolCalls.every(
|
|
67
|
+
(c) => segment.data.executions?.[c.toolExecutionRequestId]?.status === 'done',
|
|
68
|
+
)
|
|
69
|
+
if (segment.status !== 'rejected' && !allDone) {
|
|
70
|
+
hasIncompleteState = true
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
case 'context_compaction':
|
|
58
76
|
if (segment.status === 'started') {
|
|
59
77
|
hasIncompleteState = true
|
|
@@ -12,9 +12,12 @@ import type {
|
|
|
12
12
|
MessageSegment,
|
|
13
13
|
ToolExecutionSegment,
|
|
14
14
|
ApprovalRequestSegment,
|
|
15
|
+
ApprovalBatchSegment,
|
|
16
|
+
ApprovalBatchExecutionState,
|
|
15
17
|
ContextCompactionSegment,
|
|
16
18
|
ErrorSegment,
|
|
17
19
|
PendingApproval,
|
|
20
|
+
PendingToolCallData,
|
|
18
21
|
AccumulatorState,
|
|
19
22
|
ChatApprovalStatus,
|
|
20
23
|
} from '../types'
|
|
@@ -136,13 +139,27 @@ export class MessageSegmentAccumulator {
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
/**
|
|
139
|
-
* Add a tool execution segment
|
|
140
|
-
*
|
|
142
|
+
* Add a tool execution segment.
|
|
143
|
+
*
|
|
144
|
+
* Routing:
|
|
145
|
+
* 1) If `toolExecutionRequestId` matches a tool call inside an existing
|
|
146
|
+
* `approval_batch` segment, merge the state into that batch's
|
|
147
|
+
* `executions` map (no standalone segment is pushed).
|
|
148
|
+
* 2) Otherwise: pair EXECUTING ↔ EXECUTED by `toolExecutionRequestId`.
|
|
149
|
+
* If no id is present (older backends), fall back to
|
|
150
|
+
* `(integratedToolType, toolFunction)` so repeat calls of the same
|
|
151
|
+
* function don't all bucket under one key.
|
|
141
152
|
*/
|
|
142
153
|
addToolExecution(segment: ToolExecutionSegment): MessageSegment[] {
|
|
143
154
|
const toolData = segment.data
|
|
144
|
-
const
|
|
145
|
-
|
|
155
|
+
const execId = toolData.toolExecutionRequestId
|
|
156
|
+
|
|
157
|
+
if (execId && this.applyExecutionToBatch(execId, toolData)) {
|
|
158
|
+
return this.getSegments()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const toolKey = execId || `${toolData.integratedToolType}-${toolData.toolFunction}`
|
|
162
|
+
|
|
146
163
|
if (toolData.type === 'EXECUTING_TOOL') {
|
|
147
164
|
this.executingTools.set(toolKey, {
|
|
148
165
|
integratedToolType: toolData.integratedToolType,
|
|
@@ -155,10 +172,12 @@ export class MessageSegmentAccumulator {
|
|
|
155
172
|
(s): s is ToolExecutionSegment =>
|
|
156
173
|
s.type === 'tool_execution' &&
|
|
157
174
|
s.data.type === 'EXECUTING_TOOL' &&
|
|
158
|
-
|
|
159
|
-
|
|
175
|
+
(execId
|
|
176
|
+
? s.data.toolExecutionRequestId === execId
|
|
177
|
+
: s.data.integratedToolType === toolData.integratedToolType &&
|
|
178
|
+
s.data.toolFunction === toolData.toolFunction),
|
|
160
179
|
)
|
|
161
|
-
|
|
180
|
+
|
|
162
181
|
const executingTool = this.executingTools.get(toolKey)
|
|
163
182
|
const mergedSegment: ToolExecutionSegment = {
|
|
164
183
|
type: 'tool_execution',
|
|
@@ -167,19 +186,50 @@ export class MessageSegmentAccumulator {
|
|
|
167
186
|
parameters: toolData.parameters || executingTool?.parameters,
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
|
-
|
|
189
|
+
|
|
171
190
|
if (existingIndex !== -1) {
|
|
172
191
|
this.segments[existingIndex] = mergedSegment
|
|
173
192
|
} else {
|
|
174
193
|
this.segments.push(mergedSegment)
|
|
175
194
|
}
|
|
176
|
-
|
|
195
|
+
|
|
177
196
|
this.executingTools.delete(toolKey)
|
|
178
197
|
}
|
|
179
|
-
|
|
198
|
+
|
|
180
199
|
return this.getSegments()
|
|
181
200
|
}
|
|
182
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Try to merge a tool execution event into an existing approval_batch
|
|
204
|
+
* segment whose `toolCalls` contains the same `toolExecutionRequestId`.
|
|
205
|
+
* Returns true when a batch was updated, false when no batch matches.
|
|
206
|
+
*/
|
|
207
|
+
private applyExecutionToBatch(execId: string, toolData: ToolExecutionSegment['data']): boolean {
|
|
208
|
+
let matched = false
|
|
209
|
+
this.segments = this.segments.map((seg) => {
|
|
210
|
+
if (matched) return seg
|
|
211
|
+
if (seg.type !== 'approval_batch') return seg
|
|
212
|
+
const hasCall = seg.data.toolCalls.some((c) => c.toolExecutionRequestId === execId)
|
|
213
|
+
if (!hasCall) return seg
|
|
214
|
+
|
|
215
|
+
const prev: ApprovalBatchExecutionState | undefined = seg.data.executions?.[execId]
|
|
216
|
+
const next: ApprovalBatchExecutionState =
|
|
217
|
+
toolData.type === 'EXECUTED_TOOL'
|
|
218
|
+
? { status: 'done', result: toolData.result, success: toolData.success }
|
|
219
|
+
: { status: 'executing', result: prev?.result, success: prev?.success }
|
|
220
|
+
|
|
221
|
+
matched = true
|
|
222
|
+
return {
|
|
223
|
+
...seg,
|
|
224
|
+
data: {
|
|
225
|
+
...seg.data,
|
|
226
|
+
executions: { ...(seg.data.executions ?? {}), [execId]: next },
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
return matched
|
|
231
|
+
}
|
|
232
|
+
|
|
183
233
|
/**
|
|
184
234
|
* Track a pending approval request
|
|
185
235
|
*/
|
|
@@ -209,7 +259,67 @@ export class MessageSegmentAccumulator {
|
|
|
209
259
|
onApprove: this.callbacks.onApprove,
|
|
210
260
|
onReject: this.callbacks.onReject,
|
|
211
261
|
}
|
|
212
|
-
|
|
262
|
+
|
|
263
|
+
this.segments.push(segment)
|
|
264
|
+
return this.getSegments()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Add a batch approval segment containing multiple tool calls. Upserts by
|
|
269
|
+
* `approvalRequestId`: when a batch with the same id is already in the
|
|
270
|
+
* accumulator, the existing segment is updated in place rather than a
|
|
271
|
+
* second segment being pushed. This matters for the consumer-store replay
|
|
272
|
+
* path, which feeds `[existing..., new...]` into `replaySegments` and would
|
|
273
|
+
* otherwise produce two batch segments for the same approval after a
|
|
274
|
+
* status flip or per-tool execution merge.
|
|
275
|
+
*
|
|
276
|
+
* `approvalType` is the highest-privilege type required across the batch.
|
|
277
|
+
* `executions` is forwarded as-is. On upsert, a new `executions` object
|
|
278
|
+
* overrides the existing one (so the latest replay wins).
|
|
279
|
+
*/
|
|
280
|
+
addApprovalBatch(
|
|
281
|
+
approvalRequestId: string,
|
|
282
|
+
approvalType: string,
|
|
283
|
+
toolCalls: PendingToolCallData[],
|
|
284
|
+
status: ChatApprovalStatus = 'pending',
|
|
285
|
+
executions?: Record<string, ApprovalBatchExecutionState>,
|
|
286
|
+
): MessageSegment[] {
|
|
287
|
+
const existingIndex = this.segments.findIndex(
|
|
288
|
+
(s): s is ApprovalBatchSegment =>
|
|
289
|
+
s.type === 'approval_batch' && s.data.approvalRequestId === approvalRequestId,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if (existingIndex !== -1) {
|
|
293
|
+
const existing = this.segments[existingIndex] as ApprovalBatchSegment
|
|
294
|
+
const mergedExecutions = executions ?? existing.data.executions
|
|
295
|
+
this.segments[existingIndex] = {
|
|
296
|
+
...existing,
|
|
297
|
+
data: {
|
|
298
|
+
approvalRequestId,
|
|
299
|
+
approvalType,
|
|
300
|
+
toolCalls,
|
|
301
|
+
...(mergedExecutions ? { executions: mergedExecutions } : {}),
|
|
302
|
+
},
|
|
303
|
+
status,
|
|
304
|
+
onApprove: this.callbacks.onApprove,
|
|
305
|
+
onReject: this.callbacks.onReject,
|
|
306
|
+
}
|
|
307
|
+
return this.getSegments()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const segment: ApprovalBatchSegment = {
|
|
311
|
+
type: 'approval_batch',
|
|
312
|
+
data: {
|
|
313
|
+
approvalRequestId,
|
|
314
|
+
approvalType,
|
|
315
|
+
toolCalls,
|
|
316
|
+
...(executions ? { executions } : {}),
|
|
317
|
+
},
|
|
318
|
+
status,
|
|
319
|
+
onApprove: this.callbacks.onApprove,
|
|
320
|
+
onReject: this.callbacks.onReject,
|
|
321
|
+
}
|
|
322
|
+
|
|
213
323
|
this.segments.push(segment)
|
|
214
324
|
return this.getSegments()
|
|
215
325
|
}
|
|
@@ -249,13 +359,16 @@ export class MessageSegmentAccumulator {
|
|
|
249
359
|
}
|
|
250
360
|
|
|
251
361
|
/**
|
|
252
|
-
* Update status of an existing approval segment
|
|
362
|
+
* Update status of an existing approval segment (single or batch)
|
|
253
363
|
*/
|
|
254
364
|
updateApprovalStatus(requestId: string, status: ChatApprovalStatus): MessageSegment[] {
|
|
255
365
|
this.segments = this.segments.map(segment => {
|
|
256
366
|
if (segment.type === 'approval_request' && segment.data.requestId === requestId) {
|
|
257
367
|
return { ...segment, status }
|
|
258
368
|
}
|
|
369
|
+
if (segment.type === 'approval_batch' && segment.data.approvalRequestId === requestId) {
|
|
370
|
+
return { ...segment, status }
|
|
371
|
+
}
|
|
259
372
|
return segment
|
|
260
373
|
})
|
|
261
374
|
return this.getSegments()
|
|
@@ -366,6 +479,17 @@ export class MessageSegmentAccumulator {
|
|
|
366
479
|
)
|
|
367
480
|
break
|
|
368
481
|
}
|
|
482
|
+
case 'approval_batch': {
|
|
483
|
+
const { data, status } = segment
|
|
484
|
+
this.addApprovalBatch(
|
|
485
|
+
data.approvalRequestId,
|
|
486
|
+
data.approvalType,
|
|
487
|
+
data.toolCalls,
|
|
488
|
+
status,
|
|
489
|
+
data.executions,
|
|
490
|
+
)
|
|
491
|
+
break
|
|
492
|
+
}
|
|
369
493
|
case 'error':
|
|
370
494
|
this.addError(segment.title, segment.details)
|
|
371
495
|
break
|