@agent-platform/ui 0.0.6 → 0.0.7

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 (67) hide show
  1. package/README.md +5 -0
  2. package/dist/components/agent/agent-container-state.d.ts +2 -0
  3. package/dist/components/agent/agent-container-state.js +52 -0
  4. package/dist/components/agent/agent-container-view.d.ts +3 -0
  5. package/dist/components/agent/agent-container-view.js +22 -0
  6. package/dist/components/agent/agent-container-view.test.d.ts +1 -0
  7. package/dist/components/agent/agent-container-view.test.js +16 -0
  8. package/dist/components/agent/agent-container.d.ts +1 -1
  9. package/dist/components/agent/agent-container.js +6 -44
  10. package/dist/components/agent/agent-greeting.d.ts +1 -1
  11. package/dist/components/agent/agent-greeting.js +4 -4
  12. package/dist/components/agent/agent-header.d.ts +1 -1
  13. package/dist/components/agent/agent-header.js +6 -6
  14. package/dist/components/agent/agent-home-cards.d.ts +2 -2
  15. package/dist/components/agent/agent-home-cards.js +8 -6
  16. package/dist/components/agent/agent-screen.d.ts +1 -1
  17. package/dist/components/agent/agent-screen.js +5 -2
  18. package/dist/components/agent/approval-ui-model.d.ts +15 -0
  19. package/dist/components/agent/approval-ui-model.js +27 -0
  20. package/dist/components/agent/approval-ui-model.test.d.ts +1 -0
  21. package/dist/components/agent/approval-ui-model.test.js +39 -0
  22. package/dist/components/agent/defaults.d.ts +0 -6
  23. package/dist/components/agent/defaults.js +0 -11
  24. package/dist/components/agent/index.d.ts +0 -2
  25. package/dist/components/agent/index.js +0 -1
  26. package/dist/components/agent/input-mode.d.ts +5 -0
  27. package/dist/components/agent/input-mode.js +9 -0
  28. package/dist/components/agent/message/index.d.ts +1 -1
  29. package/dist/components/agent/message/index.js +1 -1
  30. package/dist/components/agent/message/message-item.js +3 -9
  31. package/dist/components/agent/message/message-list.js +6 -11
  32. package/dist/components/agent/message/message-loading.d.ts +0 -6
  33. package/dist/components/agent/message/message-loading.js +0 -6
  34. package/dist/components/agent/message/tool-call-card.js +2 -0
  35. package/dist/components/agent/message/utils.d.ts +8 -0
  36. package/dist/components/agent/message/utils.js +21 -0
  37. package/dist/components/agent/provider/agent-context.d.ts +1 -0
  38. package/dist/components/agent/provider/agent-context.js +3 -0
  39. package/dist/components/agent/provider/agent-provider.d.ts +1 -1
  40. package/dist/components/agent/provider/agent-provider.js +19 -5
  41. package/dist/components/agent/provider/index.d.ts +1 -1
  42. package/dist/components/agent/provider/runtime-config.js +14 -5
  43. package/dist/components/agent/provider/types.d.ts +17 -15
  44. package/dist/components/agent/types.d.ts +28 -3
  45. package/dist/components/ui/button.d.ts +1 -1
  46. package/dist/index.d.ts +2 -2
  47. package/dist/index.js +1 -1
  48. package/dist/modules/agent/agent.repository.d.ts +58 -0
  49. package/dist/modules/agent/agent.repository.js +235 -0
  50. package/dist/modules/agent/agent.repository.test.d.ts +1 -0
  51. package/dist/modules/agent/agent.repository.test.js +64 -0
  52. package/dist/modules/agent/domain/chat-state.d.ts +64 -0
  53. package/dist/modules/agent/domain/chat-state.js +148 -0
  54. package/dist/modules/agent/domain/chat-state.test.d.ts +1 -0
  55. package/dist/modules/agent/domain/chat-state.test.js +72 -0
  56. package/dist/modules/agent/use-agent-chat.d.ts +6 -0
  57. package/dist/modules/agent/use-agent-chat.js +106 -0
  58. package/dist/modules/agent/usecases/process-stream.d.ts +26 -0
  59. package/dist/modules/agent/usecases/process-stream.js +112 -0
  60. package/dist/modules/agent/usecases/process-stream.test.d.ts +1 -0
  61. package/dist/modules/agent/usecases/process-stream.test.js +91 -0
  62. package/dist/modules/agent/usecases/send-message.d.ts +21 -0
  63. package/dist/modules/agent/usecases/send-message.js +298 -0
  64. package/dist/modules/agent/usecases/send-message.test.d.ts +1 -0
  65. package/dist/modules/agent/usecases/send-message.test.js +257 -0
  66. package/dist/styles/globals.css +0 -56
  67. package/package.json +3 -5
@@ -0,0 +1,148 @@
1
+ function buildAssistantContent(text, toolCalls) {
2
+ const content = [];
3
+ if (text) {
4
+ content.push({ type: 'text', text });
5
+ }
6
+ for (const toolCall of toolCalls) {
7
+ content.push({ type: 'tool-call', toolCall });
8
+ }
9
+ return content;
10
+ }
11
+ function updateAssistantMessage(messages, assistantMessageId, update) {
12
+ const updated = [...messages];
13
+ const targetIndex = updated.findIndex((message) => message.id === assistantMessageId);
14
+ const existing = targetIndex >= 0 ? updated[targetIndex] : undefined;
15
+ if (!existing) {
16
+ return updated;
17
+ }
18
+ updated[targetIndex] = update(existing);
19
+ return updated;
20
+ }
21
+ function createUserMessage(id, text) {
22
+ return {
23
+ id,
24
+ role: 'user',
25
+ content: [{ type: 'text', text }],
26
+ };
27
+ }
28
+ function createAssistantStreamingMessage(id) {
29
+ return {
30
+ id,
31
+ role: 'assistant',
32
+ content: [],
33
+ isStreaming: true,
34
+ };
35
+ }
36
+ export function createInitialChatState() {
37
+ return {
38
+ messages: [],
39
+ threadId: null,
40
+ isLoading: false,
41
+ error: null,
42
+ pendingToolCalls: new Map(),
43
+ };
44
+ }
45
+ export function appendIntentText(current, intentMessage) {
46
+ if (!intentMessage.trim()) {
47
+ return current;
48
+ }
49
+ return current ? `${current}\n${intentMessage}` : intentMessage;
50
+ }
51
+ export function selectPendingToolCalls(state) {
52
+ return Array.from(state.pendingToolCalls.values());
53
+ }
54
+ export function chatStateReducer(state, action) {
55
+ switch (action.type) {
56
+ case 'START_CHAT': {
57
+ return {
58
+ ...state,
59
+ messages: [
60
+ ...state.messages,
61
+ createUserMessage(action.payload.userMessageId, action.payload.text),
62
+ createAssistantStreamingMessage(action.payload.assistantMessageId),
63
+ ],
64
+ isLoading: true,
65
+ error: null,
66
+ };
67
+ }
68
+ case 'START_ASSISTANT_MESSAGE': {
69
+ return {
70
+ ...state,
71
+ messages: [
72
+ ...state.messages,
73
+ createAssistantStreamingMessage(action.payload.assistantMessageId),
74
+ ],
75
+ };
76
+ }
77
+ case 'UPDATE_ASSISTANT_PROGRESS': {
78
+ return {
79
+ ...state,
80
+ messages: updateAssistantMessage(state.messages, action.payload.assistantMessageId, (message) => ({
81
+ id: message.id,
82
+ role: message.role,
83
+ content: buildAssistantContent(action.payload.text, action.payload.toolCalls),
84
+ isStreaming: true,
85
+ })),
86
+ };
87
+ }
88
+ case 'FINISH_ASSISTANT_MESSAGE': {
89
+ return {
90
+ ...state,
91
+ messages: updateAssistantMessage(state.messages, action.payload.assistantMessageId, (message) => ({
92
+ id: message.id,
93
+ role: message.role,
94
+ content: message.content,
95
+ isStreaming: false,
96
+ })),
97
+ };
98
+ }
99
+ case 'UPSERT_TOOL_CALL_STATE': {
100
+ const nextPendingToolCalls = new Map(state.pendingToolCalls);
101
+ const current = nextPendingToolCalls.get(action.payload.toolCall.toolCallId);
102
+ if (current) {
103
+ nextPendingToolCalls.set(action.payload.toolCall.toolCallId, {
104
+ ...current,
105
+ status: action.payload.status,
106
+ result: action.payload.result,
107
+ error: action.payload.error,
108
+ });
109
+ }
110
+ else {
111
+ nextPendingToolCalls.set(action.payload.toolCall.toolCallId, {
112
+ toolCall: action.payload.toolCall,
113
+ status: action.payload.status,
114
+ result: action.payload.result,
115
+ error: action.payload.error,
116
+ });
117
+ }
118
+ return {
119
+ ...state,
120
+ pendingToolCalls: nextPendingToolCalls,
121
+ };
122
+ }
123
+ case 'SET_LOADING': {
124
+ return {
125
+ ...state,
126
+ isLoading: action.payload.isLoading,
127
+ };
128
+ }
129
+ case 'SET_ERROR': {
130
+ return {
131
+ ...state,
132
+ error: action.payload.error,
133
+ };
134
+ }
135
+ case 'SET_THREAD_ID': {
136
+ return {
137
+ ...state,
138
+ threadId: action.payload.threadId,
139
+ };
140
+ }
141
+ case 'RESET_CHAT': {
142
+ return createInitialChatState();
143
+ }
144
+ default: {
145
+ return state;
146
+ }
147
+ }
148
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { appendIntentText, chatStateReducer, createInitialChatState, selectPendingToolCalls, } from './chat-state';
3
+ function buildToolCall(id) {
4
+ return {
5
+ toolCallId: id,
6
+ toolName: 'search',
7
+ input: { keyword: 'alice' },
8
+ };
9
+ }
10
+ describe('chatStateReducer', () => {
11
+ it('START_CHATでユーザーメッセージと空のassistantメッセージを追加する', () => {
12
+ const state = createInitialChatState();
13
+ const next = chatStateReducer(state, {
14
+ type: 'START_CHAT',
15
+ payload: {
16
+ userMessageId: 'user-1',
17
+ text: 'hello',
18
+ assistantMessageId: 'assistant-1',
19
+ },
20
+ });
21
+ expect(next.isLoading).toBe(true);
22
+ expect(next.error).toBeNull();
23
+ expect(next.messages).toHaveLength(2);
24
+ expect(next.messages[0]).toMatchObject({ id: 'user-1', role: 'user' });
25
+ expect(next.messages[1]).toMatchObject({ id: 'assistant-1', role: 'assistant', isStreaming: true });
26
+ });
27
+ it('UPSERT_TOOL_CALL_STATEでawaiting-approvalを含む状態遷移を更新できる', () => {
28
+ const toolCall = buildToolCall('tc-1');
29
+ const pending = chatStateReducer(createInitialChatState(), {
30
+ type: 'UPSERT_TOOL_CALL_STATE',
31
+ payload: { toolCall, status: 'pending' },
32
+ });
33
+ const awaitingApproval = chatStateReducer(pending, {
34
+ type: 'UPSERT_TOOL_CALL_STATE',
35
+ payload: { toolCall, status: 'awaiting-approval' },
36
+ });
37
+ const awaitingState = selectPendingToolCalls(awaitingApproval)[0];
38
+ expect(awaitingState?.status).toBe('awaiting-approval');
39
+ const executing = chatStateReducer(awaitingApproval, {
40
+ type: 'UPSERT_TOOL_CALL_STATE',
41
+ payload: { toolCall, status: 'executing' },
42
+ });
43
+ const completed = chatStateReducer(executing, {
44
+ type: 'UPSERT_TOOL_CALL_STATE',
45
+ payload: { toolCall, status: 'completed', result: { ok: true } },
46
+ });
47
+ const toolCallState = selectPendingToolCalls(completed)[0];
48
+ expect(toolCallState).toBeDefined();
49
+ expect(toolCallState?.status).toBe('completed');
50
+ expect(toolCallState?.result).toEqual({ ok: true });
51
+ });
52
+ it('RESET_CHATでSSOTを初期化する', () => {
53
+ const started = chatStateReducer(createInitialChatState(), {
54
+ type: 'START_CHAT',
55
+ payload: {
56
+ userMessageId: 'user-1',
57
+ text: 'hello',
58
+ assistantMessageId: 'assistant-1',
59
+ },
60
+ });
61
+ const next = chatStateReducer(started, { type: 'RESET_CHAT' });
62
+ expect(next).toEqual(createInitialChatState());
63
+ });
64
+ });
65
+ describe('appendIntentText', () => {
66
+ it('既存テキストの末尾にintentを改行付きで連結する', () => {
67
+ expect(appendIntentText('line1', 'line2')).toBe('line1\nline2');
68
+ });
69
+ it('intentが空文字の場合は既存テキストを返す', () => {
70
+ expect(appendIntentText('line1', ' ')).toBe('line1');
71
+ });
72
+ });
@@ -0,0 +1,6 @@
1
+ import type { AgentContextValue, ResolvedAgentRuntimeConfig } from '../../components/agent/provider/types';
2
+ interface UseAgentChatOptions {
3
+ config: ResolvedAgentRuntimeConfig;
4
+ }
5
+ export declare function useAgentChat(options: UseAgentChatOptions): AgentContextValue;
6
+ export {};
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+ import { useCallback, useMemo, useReducer, useRef, useState } from 'react';
3
+ import { createAgentRepository } from './agent.repository';
4
+ import { chatStateReducer, createInitialChatState, selectPendingToolCalls, } from './domain/chat-state';
5
+ import { sendMessageUseCase } from './usecases/send-message';
6
+ export function useAgentChat(options) {
7
+ const { config } = options;
8
+ const { endpoint, apiBaseUrl, agentId, onError, authToken, getAuthToken, getAgentHeaders, disableToolApiAuthHeader, } = config;
9
+ const [state, dispatch] = useReducer(chatStateReducer, undefined, createInitialChatState);
10
+ const [activeApprovalRequest, setActiveApprovalRequest] = useState(null);
11
+ const abortControllerRef = useRef(null);
12
+ const approvalResolverRef = useRef(null);
13
+ const stateRef = useRef(state);
14
+ stateRef.current = state;
15
+ const settleApproval = useCallback((approved) => {
16
+ const resolver = approvalResolverRef.current;
17
+ if (!resolver) {
18
+ return;
19
+ }
20
+ approvalResolverRef.current = null;
21
+ setActiveApprovalRequest(null);
22
+ resolver(approved);
23
+ }, []);
24
+ const requestToolApproval = useCallback((toolCall) => {
25
+ if (approvalResolverRef.current) {
26
+ settleApproval(false);
27
+ }
28
+ const actionLabel = toolCall.apiRequest?.actionLabel?.trim() ||
29
+ `${toolCall.apiRequest?.method ?? ''} ${toolCall.apiRequest?.path ?? ''}`.trim() ||
30
+ toolCall.toolName;
31
+ const riskLevel = toolCall.apiRequest?.riskLevel ?? 'write';
32
+ setActiveApprovalRequest({
33
+ toolCall,
34
+ actionLabel,
35
+ riskLevel,
36
+ });
37
+ return new Promise((resolve) => {
38
+ approvalResolverRef.current = resolve;
39
+ });
40
+ }, [settleApproval]);
41
+ const approveToolCall = useCallback(() => {
42
+ settleApproval(true);
43
+ }, [settleApproval]);
44
+ const rejectToolCall = useCallback(() => {
45
+ settleApproval(false);
46
+ }, [settleApproval]);
47
+ const repository = useMemo(() => createAgentRepository({
48
+ authToken,
49
+ getAuthToken,
50
+ disableToolApiAuthHeader,
51
+ }), [authToken, getAuthToken, disableToolApiAuthHeader]);
52
+ const sendMessage = useCallback(async (message) => {
53
+ abortControllerRef.current = new AbortController();
54
+ try {
55
+ await sendMessageUseCase({
56
+ message,
57
+ repository,
58
+ dispatch,
59
+ getState: () => stateRef.current,
60
+ config: {
61
+ endpoint,
62
+ apiBaseUrl,
63
+ agentId,
64
+ getAgentHeaders,
65
+ disableToolApiAuthHeader,
66
+ },
67
+ onError,
68
+ signal: abortControllerRef.current.signal,
69
+ requestToolApproval,
70
+ });
71
+ }
72
+ finally {
73
+ abortControllerRef.current = null;
74
+ }
75
+ }, [
76
+ repository,
77
+ endpoint,
78
+ apiBaseUrl,
79
+ agentId,
80
+ getAgentHeaders,
81
+ disableToolApiAuthHeader,
82
+ onError,
83
+ requestToolApproval,
84
+ ]);
85
+ const clearChat = useCallback(() => {
86
+ if (abortControllerRef.current) {
87
+ abortControllerRef.current.abort();
88
+ }
89
+ settleApproval(false);
90
+ dispatch({ type: 'RESET_CHAT' });
91
+ }, [settleApproval]);
92
+ const pendingToolCalls = useMemo(() => selectPendingToolCalls(state), [state.pendingToolCalls, state]);
93
+ return {
94
+ messages: state.messages,
95
+ threadId: state.threadId,
96
+ isLoading: state.isLoading,
97
+ error: state.error,
98
+ pendingToolCalls,
99
+ activeApprovalRequest,
100
+ approveToolCall,
101
+ rejectToolCall,
102
+ sendMessage,
103
+ clearChat,
104
+ config,
105
+ };
106
+ }
@@ -0,0 +1,26 @@
1
+ import type { AgentStreamEvent, ToolCallData } from '../../../components/agent/provider/types';
2
+ import { type AgentChatAction, type AgentChatState } from '../domain/chat-state';
3
+ export interface ProcessStreamEventsResult {
4
+ clientToolCalls: ToolCallData[];
5
+ clientToolResults: {
6
+ toolCallId: string;
7
+ result: unknown;
8
+ isError?: boolean;
9
+ }[];
10
+ progress: ProcessStreamProgress;
11
+ }
12
+ export interface ProcessStreamProgress {
13
+ accumulatedText: string;
14
+ toolCalls: ToolCallData[];
15
+ serverResolvedToolCallIds: Set<string>;
16
+ }
17
+ export interface ProcessStreamEventsInput {
18
+ events: AgentStreamEvent[];
19
+ assistantMessageId: string;
20
+ baseState: AgentChatState;
21
+ dispatch: (action: AgentChatAction) => void;
22
+ progress?: ProcessStreamProgress;
23
+ onError?: (errorMessage: string) => void;
24
+ }
25
+ export declare function createProcessStreamProgress(): ProcessStreamProgress;
26
+ export declare function processStreamEvents(input: ProcessStreamEventsInput): ProcessStreamEventsResult;
@@ -0,0 +1,112 @@
1
+ import { appendIntentText } from '../domain/chat-state';
2
+ export function createProcessStreamProgress() {
3
+ return {
4
+ accumulatedText: '',
5
+ toolCalls: [],
6
+ serverResolvedToolCallIds: new Set(),
7
+ };
8
+ }
9
+ function normalizeErrorMessage(error) {
10
+ if (typeof error === 'string' && error.trim().length > 0) {
11
+ return error;
12
+ }
13
+ if (typeof error === 'object' &&
14
+ error !== null &&
15
+ 'message' in error &&
16
+ typeof error.message === 'string') {
17
+ return error.message;
18
+ }
19
+ if (error !== undefined) {
20
+ try {
21
+ return JSON.stringify(error);
22
+ }
23
+ catch {
24
+ return String(error);
25
+ }
26
+ }
27
+ return 'Unknown error';
28
+ }
29
+ export function processStreamEvents(input) {
30
+ const { events, assistantMessageId, dispatch, onError } = input;
31
+ let accumulatedText = input.progress?.accumulatedText ?? '';
32
+ const toolCalls = input.progress ? [...input.progress.toolCalls] : [];
33
+ const serverResolvedToolCallIds = input.progress
34
+ ? new Set(input.progress.serverResolvedToolCallIds)
35
+ : new Set();
36
+ for (const event of events) {
37
+ switch (event.type) {
38
+ case 'thread-id': {
39
+ dispatch({ type: 'SET_THREAD_ID', payload: { threadId: event.payload.threadId } });
40
+ break;
41
+ }
42
+ case 'text-delta': {
43
+ accumulatedText += event.payload.text;
44
+ dispatch({
45
+ type: 'UPDATE_ASSISTANT_PROGRESS',
46
+ payload: { assistantMessageId, text: accumulatedText, toolCalls },
47
+ });
48
+ break;
49
+ }
50
+ case 'tool-intent': {
51
+ accumulatedText = appendIntentText(accumulatedText, event.payload.message);
52
+ dispatch({
53
+ type: 'UPDATE_ASSISTANT_PROGRESS',
54
+ payload: { assistantMessageId, text: accumulatedText, toolCalls },
55
+ });
56
+ break;
57
+ }
58
+ case 'tool-call': {
59
+ toolCalls.push(event.payload);
60
+ dispatch({
61
+ type: 'UPSERT_TOOL_CALL_STATE',
62
+ payload: {
63
+ toolCall: event.payload,
64
+ status: 'pending',
65
+ },
66
+ });
67
+ dispatch({
68
+ type: 'UPDATE_ASSISTANT_PROGRESS',
69
+ payload: { assistantMessageId, text: accumulatedText, toolCalls },
70
+ });
71
+ break;
72
+ }
73
+ case 'tool-result': {
74
+ serverResolvedToolCallIds.add(event.payload.toolCallId);
75
+ const resolvedToolCall = toolCalls.find((toolCall) => toolCall.toolCallId === event.payload.toolCallId) ??
76
+ input.baseState.pendingToolCalls.get(event.payload.toolCallId)?.toolCall;
77
+ if (!resolvedToolCall) {
78
+ break;
79
+ }
80
+ dispatch({
81
+ type: 'UPSERT_TOOL_CALL_STATE',
82
+ payload: {
83
+ toolCall: resolvedToolCall,
84
+ status: event.payload.isError ? 'error' : 'completed',
85
+ result: event.payload.result,
86
+ error: event.payload.isError ? normalizeErrorMessage(event.payload.result) : undefined,
87
+ },
88
+ });
89
+ break;
90
+ }
91
+ case 'finish': {
92
+ dispatch({ type: 'FINISH_ASSISTANT_MESSAGE', payload: { assistantMessageId } });
93
+ break;
94
+ }
95
+ case 'error': {
96
+ const errorMessage = normalizeErrorMessage(event.payload.error);
97
+ dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } });
98
+ onError?.(errorMessage);
99
+ break;
100
+ }
101
+ }
102
+ }
103
+ return {
104
+ clientToolCalls: toolCalls.filter((toolCall) => !serverResolvedToolCallIds.has(toolCall.toolCallId)),
105
+ clientToolResults: [],
106
+ progress: {
107
+ accumulatedText,
108
+ toolCalls,
109
+ serverResolvedToolCallIds,
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createInitialChatState } from '../domain/chat-state';
3
+ import { createProcessStreamProgress, processStreamEvents } from './process-stream';
4
+ function buildToolCall(id) {
5
+ return {
6
+ toolCallId: id,
7
+ toolName: 'searchCandidates',
8
+ input: { keyword: 'alice' },
9
+ };
10
+ }
11
+ describe('processStreamEvents', () => {
12
+ it('tool-result未到達のtool-callをclientToolCallsとして返す', () => {
13
+ const toolCall = buildToolCall('tc-1');
14
+ const events = [
15
+ { type: 'text-delta', payload: { text: 'hello' } },
16
+ { type: 'tool-call', payload: toolCall },
17
+ { type: 'finish', payload: {} },
18
+ ];
19
+ const state = createInitialChatState();
20
+ const actions = [];
21
+ const result = processStreamEvents({
22
+ events,
23
+ assistantMessageId: 'assistant-1',
24
+ baseState: state,
25
+ dispatch: (action) => actions.push(action),
26
+ });
27
+ expect(actions.length).toBeGreaterThan(0);
28
+ expect(result.clientToolCalls).toEqual([toolCall]);
29
+ expect(result.clientToolResults).toEqual([]);
30
+ });
31
+ it('tool-result受領済みIDはclientToolCallsから除外する', () => {
32
+ const toolCall = buildToolCall('tc-1');
33
+ const events = [
34
+ { type: 'tool-call', payload: toolCall },
35
+ { type: 'tool-result', payload: { toolCallId: 'tc-1', result: { ok: true } } },
36
+ { type: 'finish', payload: {} },
37
+ ];
38
+ const result = processStreamEvents({
39
+ events,
40
+ assistantMessageId: 'assistant-1',
41
+ baseState: createInitialChatState(),
42
+ dispatch: () => undefined,
43
+ });
44
+ expect(result.clientToolCalls).toEqual([]);
45
+ });
46
+ it('progressを引き継いで複数チャンクのtext/tool-callを累積する', () => {
47
+ const toolCall = buildToolCall('tc-2');
48
+ const state = createInitialChatState();
49
+ const first = processStreamEvents({
50
+ events: [
51
+ { type: 'text-delta', payload: { text: 'hel' } },
52
+ { type: 'tool-call', payload: toolCall },
53
+ ],
54
+ assistantMessageId: 'assistant-1',
55
+ baseState: state,
56
+ dispatch: () => undefined,
57
+ progress: createProcessStreamProgress(),
58
+ });
59
+ const second = processStreamEvents({
60
+ events: [
61
+ { type: 'text-delta', payload: { text: 'lo' } },
62
+ { type: 'finish', payload: {} },
63
+ ],
64
+ assistantMessageId: 'assistant-1',
65
+ baseState: state,
66
+ dispatch: () => undefined,
67
+ progress: first.progress,
68
+ });
69
+ expect(second.progress.accumulatedText).toBe('hello');
70
+ expect(second.clientToolCalls).toEqual([toolCall]);
71
+ });
72
+ it('progressを引き継いだtool-result受領でclientToolCallsから除外される', () => {
73
+ const toolCall = buildToolCall('tc-3');
74
+ const state = createInitialChatState();
75
+ const first = processStreamEvents({
76
+ events: [{ type: 'tool-call', payload: toolCall }],
77
+ assistantMessageId: 'assistant-1',
78
+ baseState: state,
79
+ dispatch: () => undefined,
80
+ progress: createProcessStreamProgress(),
81
+ });
82
+ const second = processStreamEvents({
83
+ events: [{ type: 'tool-result', payload: { toolCallId: 'tc-3', result: { ok: true } } }],
84
+ assistantMessageId: 'assistant-1',
85
+ baseState: state,
86
+ dispatch: () => undefined,
87
+ progress: first.progress,
88
+ });
89
+ expect(second.clientToolCalls).toEqual([]);
90
+ });
91
+ });
@@ -0,0 +1,21 @@
1
+ import type { AgentHeadersProvider, ToolCallData } from '../../../components/agent/provider/types';
2
+ import type { AgentRepository } from '../agent.repository';
3
+ import type { AgentChatAction, AgentChatState } from '../domain/chat-state';
4
+ export interface SendMessageUseCaseConfig {
5
+ endpoint: string;
6
+ apiBaseUrl: string;
7
+ agentId?: string;
8
+ disableToolApiAuthHeader?: boolean;
9
+ getAgentHeaders?: AgentHeadersProvider;
10
+ }
11
+ export interface SendMessageUseCaseInput {
12
+ message: string;
13
+ repository: AgentRepository;
14
+ dispatch: (action: AgentChatAction) => void;
15
+ getState: () => AgentChatState;
16
+ config: SendMessageUseCaseConfig;
17
+ onError?: (errorMessage: string) => void;
18
+ signal?: AbortSignal;
19
+ requestToolApproval?: (toolCall: ToolCallData) => Promise<boolean>;
20
+ }
21
+ export declare function sendMessageUseCase(input: SendMessageUseCaseInput): Promise<void>;