@agent-platform/ui 0.0.6 → 0.0.8
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/README.md +5 -0
- package/dist/components/agent/agent-container-state.d.ts +2 -0
- package/dist/components/agent/agent-container-state.js +52 -0
- package/dist/components/agent/agent-container-view.d.ts +3 -0
- package/dist/components/agent/agent-container-view.js +22 -0
- package/dist/components/agent/agent-container-view.test.d.ts +1 -0
- package/dist/components/agent/agent-container-view.test.js +16 -0
- package/dist/components/agent/agent-container.d.ts +1 -1
- package/dist/components/agent/agent-container.js +6 -44
- package/dist/components/agent/agent-greeting.d.ts +1 -1
- package/dist/components/agent/agent-greeting.js +4 -4
- package/dist/components/agent/agent-header.d.ts +1 -1
- package/dist/components/agent/agent-header.js +6 -6
- package/dist/components/agent/agent-home-cards.d.ts +2 -2
- package/dist/components/agent/agent-home-cards.js +8 -6
- package/dist/components/agent/agent-screen.d.ts +1 -1
- package/dist/components/agent/agent-screen.js +5 -2
- package/dist/components/agent/approval-ui-model.d.ts +15 -0
- package/dist/components/agent/approval-ui-model.js +27 -0
- package/dist/components/agent/approval-ui-model.test.d.ts +1 -0
- package/dist/components/agent/approval-ui-model.test.js +39 -0
- package/dist/components/agent/defaults.d.ts +0 -6
- package/dist/components/agent/defaults.js +0 -11
- package/dist/components/agent/index.d.ts +0 -2
- package/dist/components/agent/index.js +0 -1
- package/dist/components/agent/input-mode.d.ts +5 -0
- package/dist/components/agent/input-mode.js +9 -0
- package/dist/components/agent/message/index.d.ts +1 -1
- package/dist/components/agent/message/index.js +1 -1
- package/dist/components/agent/message/message-item.js +3 -9
- package/dist/components/agent/message/message-list.js +6 -11
- package/dist/components/agent/message/message-loading.d.ts +0 -6
- package/dist/components/agent/message/message-loading.js +0 -6
- package/dist/components/agent/message/tool-call-card.js +2 -0
- package/dist/components/agent/message/utils.d.ts +8 -0
- package/dist/components/agent/message/utils.js +21 -0
- package/dist/components/agent/provider/agent-context.d.ts +1 -0
- package/dist/components/agent/provider/agent-context.js +3 -0
- package/dist/components/agent/provider/agent-provider.d.ts +1 -1
- package/dist/components/agent/provider/agent-provider.js +19 -5
- package/dist/components/agent/provider/index.d.ts +1 -1
- package/dist/components/agent/provider/runtime-config.js +14 -5
- package/dist/components/agent/provider/types.d.ts +17 -15
- package/dist/components/agent/types.d.ts +28 -3
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/modules/agent/agent.repository.d.ts +58 -0
- package/dist/modules/agent/agent.repository.js +235 -0
- package/dist/modules/agent/agent.repository.test.d.ts +1 -0
- package/dist/modules/agent/agent.repository.test.js +64 -0
- package/dist/modules/agent/domain/chat-state.d.ts +64 -0
- package/dist/modules/agent/domain/chat-state.js +148 -0
- package/dist/modules/agent/domain/chat-state.test.d.ts +1 -0
- package/dist/modules/agent/domain/chat-state.test.js +72 -0
- package/dist/modules/agent/use-agent-chat.d.ts +6 -0
- package/dist/modules/agent/use-agent-chat.js +106 -0
- package/dist/modules/agent/usecases/process-stream.d.ts +26 -0
- package/dist/modules/agent/usecases/process-stream.js +112 -0
- package/dist/modules/agent/usecases/process-stream.test.d.ts +1 -0
- package/dist/modules/agent/usecases/process-stream.test.js +91 -0
- package/dist/modules/agent/usecases/send-message.d.ts +21 -0
- package/dist/modules/agent/usecases/send-message.js +298 -0
- package/dist/modules/agent/usecases/send-message.test.d.ts +1 -0
- package/dist/modules/agent/usecases/send-message.test.js +257 -0
- package/dist/styles/globals.css +0 -56
- package/package.json +3 -5
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { useAgentChat } from '../../../modules/agent/use-agent-chat';
|
|
3
5
|
import { AgentContext } from './agent-context';
|
|
4
6
|
import { resolveAgentRuntimeConfig } from './runtime-config';
|
|
5
|
-
import { useAgentChatInternal } from './use-agent-chat';
|
|
6
7
|
/**
|
|
7
8
|
* エージェントチャット機能を提供する内部Provider
|
|
8
9
|
* 公開APIは AgentScreen / AgentPopupWidget を使用する。
|
|
9
10
|
*/
|
|
10
|
-
export function AgentProvider({ children, agentId,
|
|
11
|
+
export function AgentProvider({ children, agentId, onError, authToken, getAuthToken, getAgentHeaders, disableToolApiAuthHeader, toolApprovalLabels, }) {
|
|
11
12
|
const runtimeConfig = resolveAgentRuntimeConfig();
|
|
12
|
-
const value =
|
|
13
|
+
const value = useAgentChat({
|
|
13
14
|
config: {
|
|
14
15
|
endpoint: runtimeConfig.endpoint,
|
|
15
16
|
agentId,
|
|
16
17
|
apiBaseUrl: runtimeConfig.apiBaseUrl,
|
|
17
|
-
executeClientTool,
|
|
18
18
|
onError,
|
|
19
19
|
authToken,
|
|
20
20
|
getAuthToken,
|
|
21
21
|
getAgentHeaders,
|
|
22
22
|
disableToolApiAuthHeader,
|
|
23
|
+
toolApprovalLabels,
|
|
23
24
|
},
|
|
24
25
|
});
|
|
25
|
-
|
|
26
|
+
const contextValue = useMemo(() => value, [
|
|
27
|
+
value.config,
|
|
28
|
+
value.clearChat,
|
|
29
|
+
value.error,
|
|
30
|
+
value.isLoading,
|
|
31
|
+
value.messages,
|
|
32
|
+
value.pendingToolCalls,
|
|
33
|
+
value.activeApprovalRequest,
|
|
34
|
+
value.approveToolCall,
|
|
35
|
+
value.rejectToolCall,
|
|
36
|
+
value.sendMessage,
|
|
37
|
+
value.threadId,
|
|
38
|
+
]);
|
|
39
|
+
return _jsx(AgentContext.Provider, { value: contextValue, children: children });
|
|
26
40
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { useAgentContext } from './agent-context';
|
|
2
2
|
export type { AgentProviderProps } from './agent-provider';
|
|
3
3
|
export { AgentProvider } from './agent-provider';
|
|
4
|
-
export type { AgentContextValue, AgentMessage, AgentProviderConfig,
|
|
4
|
+
export type { ActiveToolApprovalRequest, AgentContextValue, AgentMessage, AgentProviderConfig, ToolApprovalLabels, AgentStreamEvent, FinishPayload, MessageContentPart, ToolCallData, ToolCallState, ToolCallStatus, ToolResultData, } from './types';
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
2
2
|
const PRODUCTION_AGENT_ENDPOINT = 'https://agent-server-prod--agent-platform-dev-8e3ae.asia-east1.hosted.app/api/agent';
|
|
3
3
|
const DEVELOPMENT_AGENT_ENDPOINT = 'http://localhost:3002/api/agent';
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
: 'https://demo-scout-api-prod--agent-platform-dev-8e3ae.asia-east1.hosted.app/api';
|
|
4
|
+
const PRODUCTION_API_BASE_URL = 'https://demo-scout-api-prod--agent-platform-dev-8e3ae.asia-east1.hosted.app/api';
|
|
5
|
+
const DEVELOPMENT_API_BASE_URL = 'http://localhost:3001/api';
|
|
7
6
|
function normalizeUrl(url) {
|
|
8
7
|
return url.replace(/\/$/, '');
|
|
9
8
|
}
|
|
10
9
|
export function resolveAgentRuntimeConfig() {
|
|
10
|
+
const endpoint = process.env.NEXT_PUBLIC_AGENT_ENDPOINT
|
|
11
|
+
? process.env.NEXT_PUBLIC_AGENT_ENDPOINT
|
|
12
|
+
: isDevelopment
|
|
13
|
+
? DEVELOPMENT_AGENT_ENDPOINT
|
|
14
|
+
: PRODUCTION_AGENT_ENDPOINT;
|
|
15
|
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL
|
|
16
|
+
? process.env.NEXT_PUBLIC_API_URL
|
|
17
|
+
: isDevelopment
|
|
18
|
+
? DEVELOPMENT_API_BASE_URL
|
|
19
|
+
: PRODUCTION_API_BASE_URL;
|
|
11
20
|
return {
|
|
12
|
-
endpoint: normalizeUrl(
|
|
13
|
-
apiBaseUrl: normalizeUrl(
|
|
21
|
+
endpoint: normalizeUrl(endpoint),
|
|
22
|
+
apiBaseUrl: normalizeUrl(apiBaseUrl),
|
|
14
23
|
};
|
|
15
24
|
}
|
|
@@ -2,7 +2,7 @@ export type { AgentStreamEvent, ToolCallData, ToolResultData, FinishPayload, Mes
|
|
|
2
2
|
/**
|
|
3
3
|
* ツール実行状態
|
|
4
4
|
*/
|
|
5
|
-
export type ToolCallStatus = 'pending' | 'executing' | 'completed' | 'error';
|
|
5
|
+
export type ToolCallStatus = 'pending' | 'awaiting-approval' | 'executing' | 'completed' | 'error';
|
|
6
6
|
/**
|
|
7
7
|
* ツールコール状態(UI表示用)
|
|
8
8
|
*/
|
|
@@ -12,25 +12,23 @@ export interface ToolCallState {
|
|
|
12
12
|
result?: unknown;
|
|
13
13
|
error?: string;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
export interface ActiveToolApprovalRequest {
|
|
16
|
+
toolCall: import('@agent-platform/server').ToolCallData;
|
|
17
|
+
actionLabel: string;
|
|
18
|
+
riskLevel: 'read' | 'write' | 'destructive';
|
|
19
|
+
}
|
|
20
|
+
export interface ToolApprovalLabels {
|
|
21
|
+
title?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
approveButton?: string;
|
|
24
|
+
rejectButton?: string;
|
|
25
|
+
}
|
|
22
26
|
export type AgentHeadersProvider = () => Record<string, string> | Promise<Record<string, string>>;
|
|
23
27
|
/**
|
|
24
28
|
* AgentProvider設定
|
|
25
29
|
*/
|
|
26
30
|
export interface AgentProviderConfig {
|
|
27
31
|
agentId?: string;
|
|
28
|
-
/** 通信先URLは packages/ui 内部のruntime-configで解決される */
|
|
29
|
-
/**
|
|
30
|
-
* @deprecated 旧実装との後方互換用。
|
|
31
|
-
* executeOn: 'client' のツールをブラウザ側で実行するコールバック
|
|
32
|
-
*/
|
|
33
|
-
executeClientTool?: ClientToolExecutor;
|
|
34
32
|
onError?: (error: string) => void;
|
|
35
33
|
/** 静的なJWTトークン */
|
|
36
34
|
authToken?: string;
|
|
@@ -40,6 +38,8 @@ export interface AgentProviderConfig {
|
|
|
40
38
|
getAgentHeaders?: AgentHeadersProvider;
|
|
41
39
|
/** trueの場合、Tool APIへのAuthorizationヘッダー付与を無効化する */
|
|
42
40
|
disableToolApiAuthHeader?: boolean;
|
|
41
|
+
/** 承認UI文言の上書き */
|
|
42
|
+
toolApprovalLabels?: ToolApprovalLabels;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Agent通信先を解決済みの内部設定
|
|
@@ -57,8 +57,10 @@ export interface AgentContextValue {
|
|
|
57
57
|
isLoading: boolean;
|
|
58
58
|
error: string | null;
|
|
59
59
|
pendingToolCalls: ToolCallState[];
|
|
60
|
+
activeApprovalRequest: ActiveToolApprovalRequest | null;
|
|
61
|
+
approveToolCall: () => void;
|
|
62
|
+
rejectToolCall: () => void;
|
|
60
63
|
sendMessage: (message: string) => Promise<void>;
|
|
61
|
-
loadThread: (threadId: string) => Promise<void>;
|
|
62
64
|
clearChat: () => void;
|
|
63
65
|
config: ResolvedAgentRuntimeConfig;
|
|
64
66
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ReactNode } from
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ActiveToolApprovalRequest, ToolApprovalLabels, ToolCallState } from './provider/types';
|
|
2
3
|
export interface AgentHeaderProps {
|
|
3
4
|
historyLabel?: string;
|
|
4
5
|
onHistoryClick?: () => void;
|
|
@@ -31,9 +32,33 @@ export interface AgentInputProps {
|
|
|
31
32
|
isLoading?: boolean;
|
|
32
33
|
className?: string;
|
|
33
34
|
}
|
|
35
|
+
export interface AgentChatStateShape {
|
|
36
|
+
showHeader: boolean;
|
|
37
|
+
effectiveHeaderProps: AgentHeaderProps;
|
|
38
|
+
showGreeting: boolean;
|
|
39
|
+
greetingProps?: AgentGreetingProps;
|
|
40
|
+
showHomeCards: boolean;
|
|
41
|
+
cards?: AgentHomeCardProps[];
|
|
42
|
+
onCardClick?: (cardId: string) => void;
|
|
43
|
+
autoShowMessages: boolean;
|
|
44
|
+
showInput: boolean;
|
|
45
|
+
effectiveInputProps: AgentInputProps;
|
|
46
|
+
className?: string;
|
|
47
|
+
children?: ReactNode;
|
|
48
|
+
messages: import('@agent-platform/server').AgentMessage[];
|
|
49
|
+
pendingToolCalls: ToolCallState[];
|
|
50
|
+
activeApprovalRequest: ActiveToolApprovalRequest | null;
|
|
51
|
+
approveToolCall: () => void;
|
|
52
|
+
rejectToolCall: () => void;
|
|
53
|
+
toolApprovalLabels?: ToolApprovalLabels;
|
|
54
|
+
error: string | null;
|
|
55
|
+
isLoading: boolean;
|
|
56
|
+
mergedToolNameLabels: Record<string, string>;
|
|
57
|
+
hasMessages: boolean;
|
|
58
|
+
}
|
|
34
59
|
export interface AgentContainerProps {
|
|
35
60
|
showHeader?: boolean;
|
|
36
|
-
headerProps?: Omit<AgentHeaderProps,
|
|
61
|
+
headerProps?: Omit<AgentHeaderProps, 'className'>;
|
|
37
62
|
showGreeting?: boolean;
|
|
38
63
|
greetingProps?: AgentGreetingProps;
|
|
39
64
|
showHomeCards?: boolean;
|
|
@@ -50,7 +75,7 @@ export interface AgentContainerProps {
|
|
|
50
75
|
*/
|
|
51
76
|
autoShowMessages?: boolean;
|
|
52
77
|
showInput?: boolean;
|
|
53
|
-
inputProps?: Omit<AgentInputProps,
|
|
78
|
+
inputProps?: Omit<AgentInputProps, 'className'>;
|
|
54
79
|
className?: string;
|
|
55
80
|
children?: ReactNode;
|
|
56
81
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type VariantProps } from 'class-variance-authority';
|
|
2
2
|
import type * as React from 'react';
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
|
-
variant?: "
|
|
4
|
+
variant?: "destructive" | "link" | "default" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
5
5
|
size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AgentScreen
|
|
2
|
-
export type { AgentScreenProps,
|
|
1
|
+
export { AgentScreen } from './components/agent';
|
|
2
|
+
export type { AgentScreenProps, AgentHomeCardProps } from './components/agent';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AgentScreen
|
|
1
|
+
export { AgentScreen } from './components/agent';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AgentStreamEvent, ToolCallData } from '../../components/agent/provider/types';
|
|
2
|
+
export interface ToolExecutionResult {
|
|
3
|
+
output: unknown;
|
|
4
|
+
isError?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ContinuePayload {
|
|
7
|
+
threadId: string | null;
|
|
8
|
+
agentId: string;
|
|
9
|
+
toolResults: {
|
|
10
|
+
toolCallId: string;
|
|
11
|
+
result: unknown;
|
|
12
|
+
isError?: boolean;
|
|
13
|
+
}[];
|
|
14
|
+
toolCalls: {
|
|
15
|
+
toolCallId: string;
|
|
16
|
+
toolName: string;
|
|
17
|
+
args: unknown;
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
export interface SendChatParams {
|
|
21
|
+
endpoint: string;
|
|
22
|
+
threadId: string | null;
|
|
23
|
+
message: string;
|
|
24
|
+
agentId: string;
|
|
25
|
+
headers: Record<string, string>;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
export interface ContinueChatParams {
|
|
29
|
+
endpoint: string;
|
|
30
|
+
payload: ContinuePayload;
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
export interface ExecuteToolCallParams {
|
|
35
|
+
toolCall: ToolCallData;
|
|
36
|
+
apiBaseUrl: string;
|
|
37
|
+
authHeaders: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
export interface AgentRepository {
|
|
40
|
+
buildAgentHeaders: (getAgentHeaders?: () => Record<string, string> | Promise<Record<string, string>>) => Promise<Record<string, string>>;
|
|
41
|
+
buildToolApiHeaders: () => Promise<Record<string, string>>;
|
|
42
|
+
sendChat: (params: SendChatParams) => Promise<Response>;
|
|
43
|
+
continueChat: (params: ContinueChatParams) => Promise<Response>;
|
|
44
|
+
collectStreamEvents: (response: Response, options?: CollectStreamEventsOptions) => Promise<AgentStreamEvent[]>;
|
|
45
|
+
executeToolCall: (params: ExecuteToolCallParams) => Promise<ToolExecutionResult>;
|
|
46
|
+
parseErrorResponse: (response: Response, fallback: string) => Promise<string>;
|
|
47
|
+
}
|
|
48
|
+
export interface CollectStreamEventsOptions {
|
|
49
|
+
onEvents?: (events: AgentStreamEvent[]) => void;
|
|
50
|
+
}
|
|
51
|
+
interface CreateAgentRepositoryOptions {
|
|
52
|
+
fetchFn?: typeof fetch;
|
|
53
|
+
authToken?: string;
|
|
54
|
+
getAuthToken?: () => string | Promise<string>;
|
|
55
|
+
disableToolApiAuthHeader?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export declare function createAgentRepository(options: CreateAgentRepositoryOptions): AgentRepository;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
function parseSSEBuffer(buffer) {
|
|
2
|
+
const events = [];
|
|
3
|
+
const lines = buffer.split('\n');
|
|
4
|
+
const remaining = lines.pop() || '';
|
|
5
|
+
for (const line of lines) {
|
|
6
|
+
if (!line.startsWith('data: ')) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
const jsonString = line.slice(6);
|
|
10
|
+
if (!jsonString) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const event = JSON.parse(jsonString);
|
|
15
|
+
events.push(event);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.warn('Failed to parse SSE event:', error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { events, remaining };
|
|
22
|
+
}
|
|
23
|
+
function isLikelyJwtToken(token) {
|
|
24
|
+
const normalized = token.startsWith('Bearer ') ? token.slice(7) : token;
|
|
25
|
+
return normalized.split('.').length === 3;
|
|
26
|
+
}
|
|
27
|
+
function buildToolEndpointRequest(toolCall, apiBaseUrl) {
|
|
28
|
+
if (!toolCall.apiRequest) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const { apiRequest, input } = toolCall;
|
|
32
|
+
const inputData = (input ?? {});
|
|
33
|
+
const method = apiRequest.method.toUpperCase();
|
|
34
|
+
let path = apiRequest.path;
|
|
35
|
+
const usedParams = new Set();
|
|
36
|
+
for (const paramName of apiRequest.pathParams) {
|
|
37
|
+
const value = inputData[paramName];
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
path = path.replace(`{${paramName}}`, encodeURIComponent(String(value)));
|
|
42
|
+
usedParams.add(paramName);
|
|
43
|
+
}
|
|
44
|
+
const queryParams = {};
|
|
45
|
+
const bodyData = {};
|
|
46
|
+
for (const key of apiRequest.queryParams) {
|
|
47
|
+
const value = inputData[key];
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
queryParams[key] = String(value);
|
|
52
|
+
usedParams.add(key);
|
|
53
|
+
}
|
|
54
|
+
for (const key of apiRequest.bodyFields) {
|
|
55
|
+
const value = inputData[key];
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
bodyData[key] = value;
|
|
60
|
+
usedParams.add(key);
|
|
61
|
+
}
|
|
62
|
+
for (const [key, value] of Object.entries(inputData)) {
|
|
63
|
+
if (usedParams.has(key) || value === undefined) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (method === 'GET' || method === 'DELETE') {
|
|
67
|
+
queryParams[key] = String(value);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
bodyData[key] = value;
|
|
71
|
+
}
|
|
72
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
73
|
+
return {
|
|
74
|
+
method,
|
|
75
|
+
url: `${apiBaseUrl}${path}${queryString ? `?${queryString}` : ''}`,
|
|
76
|
+
bodyData,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function createAgentRepository(options) {
|
|
80
|
+
const { fetchFn = fetch, authToken, getAuthToken, disableToolApiAuthHeader = false, } = options;
|
|
81
|
+
const resolveToken = async () => {
|
|
82
|
+
return getAuthToken ? getAuthToken() : authToken;
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
buildAgentHeaders: async (getAgentHeaders) => {
|
|
86
|
+
const headers = {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
};
|
|
89
|
+
const token = await resolveToken();
|
|
90
|
+
if (token) {
|
|
91
|
+
headers.Authorization = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
|
|
92
|
+
}
|
|
93
|
+
const customHeaders = getAgentHeaders ? await getAgentHeaders() : undefined;
|
|
94
|
+
if (!customHeaders) {
|
|
95
|
+
return headers;
|
|
96
|
+
}
|
|
97
|
+
for (const [key, value] of Object.entries(customHeaders)) {
|
|
98
|
+
if (value) {
|
|
99
|
+
headers[key] = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return headers;
|
|
103
|
+
},
|
|
104
|
+
buildToolApiHeaders: async () => {
|
|
105
|
+
const headers = {};
|
|
106
|
+
if (disableToolApiAuthHeader) {
|
|
107
|
+
return headers;
|
|
108
|
+
}
|
|
109
|
+
const token = await resolveToken();
|
|
110
|
+
if (!token) {
|
|
111
|
+
return headers;
|
|
112
|
+
}
|
|
113
|
+
const resolvedToken = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
|
|
114
|
+
if (!isLikelyJwtToken(resolvedToken)) {
|
|
115
|
+
return headers;
|
|
116
|
+
}
|
|
117
|
+
headers.Authorization = resolvedToken;
|
|
118
|
+
return headers;
|
|
119
|
+
},
|
|
120
|
+
sendChat: async ({ endpoint, threadId, message, agentId, headers, signal }) => {
|
|
121
|
+
return fetchFn(`${endpoint}/chat`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers,
|
|
124
|
+
credentials: 'include',
|
|
125
|
+
body: JSON.stringify({ threadId, message, agentId }),
|
|
126
|
+
signal,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
continueChat: async ({ endpoint, payload, headers, signal }) => {
|
|
130
|
+
return fetchFn(`${endpoint}/chat/tool-result`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers,
|
|
133
|
+
credentials: 'include',
|
|
134
|
+
body: JSON.stringify(payload),
|
|
135
|
+
signal,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
collectStreamEvents: async (response, options) => {
|
|
139
|
+
const reader = response.body?.getReader();
|
|
140
|
+
if (!reader) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const decoder = new TextDecoder();
|
|
144
|
+
const events = [];
|
|
145
|
+
let buffer = '';
|
|
146
|
+
try {
|
|
147
|
+
while (true) {
|
|
148
|
+
const { done, value } = await reader.read();
|
|
149
|
+
if (done) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
buffer += decoder.decode(value, { stream: true });
|
|
153
|
+
const parsed = parseSSEBuffer(buffer);
|
|
154
|
+
buffer = parsed.remaining;
|
|
155
|
+
events.push(...parsed.events);
|
|
156
|
+
if (parsed.events.length > 0) {
|
|
157
|
+
options?.onEvents?.(parsed.events);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (buffer.startsWith('data: ')) {
|
|
161
|
+
try {
|
|
162
|
+
const event = JSON.parse(buffer.slice(6));
|
|
163
|
+
events.push(event);
|
|
164
|
+
options?.onEvents?.([event]);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.warn('Failed to parse remaining SSE event:', error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return events;
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
reader.releaseLock();
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
executeToolCall: async ({ toolCall, apiBaseUrl, authHeaders }) => {
|
|
177
|
+
const request = buildToolEndpointRequest(toolCall, apiBaseUrl);
|
|
178
|
+
if (!request) {
|
|
179
|
+
return { output: 'No API endpoint defined for this tool', isError: true };
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const fetchOptions = {
|
|
183
|
+
method: request.method,
|
|
184
|
+
headers: {
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
...authHeaders,
|
|
187
|
+
},
|
|
188
|
+
credentials: 'include',
|
|
189
|
+
};
|
|
190
|
+
if ((request.method === 'POST' || request.method === 'PUT' || request.method === 'PATCH') &&
|
|
191
|
+
Object.keys(request.bodyData).length > 0) {
|
|
192
|
+
fetchOptions.body = JSON.stringify(request.bodyData);
|
|
193
|
+
}
|
|
194
|
+
const response = await fetchFn(request.url, fetchOptions);
|
|
195
|
+
const text = await response.text();
|
|
196
|
+
let data;
|
|
197
|
+
try {
|
|
198
|
+
data = text ? JSON.parse(text) : null;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
data = { raw: text, status: response.status };
|
|
202
|
+
}
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
return { output: data, isError: true };
|
|
205
|
+
}
|
|
206
|
+
return { output: data };
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
output: error instanceof Error ? error.message : 'Network error',
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
parseErrorResponse: async (response, fallback) => {
|
|
216
|
+
let errorMessage = fallback;
|
|
217
|
+
try {
|
|
218
|
+
const contentType = response.headers.get('content-type');
|
|
219
|
+
if (contentType?.includes('application/json')) {
|
|
220
|
+
const errorData = await response.json();
|
|
221
|
+
if (typeof errorData?.error === 'string') {
|
|
222
|
+
return errorData.error;
|
|
223
|
+
}
|
|
224
|
+
if (typeof errorData?.message === 'string') {
|
|
225
|
+
return errorData.message;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.warn('Failed to parse error response:', error);
|
|
231
|
+
}
|
|
232
|
+
return errorMessage;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createAgentRepository } from './agent.repository';
|
|
3
|
+
describe('createAgentRepository', () => {
|
|
4
|
+
it('parseErrorResponseはjsonのerror/messageを優先して返す', async () => {
|
|
5
|
+
const repository = createAgentRepository({
|
|
6
|
+
fetchFn: vi.fn(),
|
|
7
|
+
getAuthToken: undefined,
|
|
8
|
+
authToken: undefined,
|
|
9
|
+
disableToolApiAuthHeader: false,
|
|
10
|
+
});
|
|
11
|
+
const response = new Response(JSON.stringify({ error: 'failed' }), {
|
|
12
|
+
status: 400,
|
|
13
|
+
headers: { 'content-type': 'application/json' },
|
|
14
|
+
});
|
|
15
|
+
await expect(repository.parseErrorResponse(response, 'fallback')).resolves.toBe('failed');
|
|
16
|
+
});
|
|
17
|
+
it('collectStreamEventsはSSEのdata行をイベント配列に変換する', async () => {
|
|
18
|
+
const repository = createAgentRepository({
|
|
19
|
+
fetchFn: vi.fn(),
|
|
20
|
+
getAuthToken: undefined,
|
|
21
|
+
authToken: undefined,
|
|
22
|
+
disableToolApiAuthHeader: false,
|
|
23
|
+
});
|
|
24
|
+
const stream = new ReadableStream({
|
|
25
|
+
start(controller) {
|
|
26
|
+
controller.enqueue(new TextEncoder().encode('data: {"type":"text-delta","payload":{"text":"hi"}}\n\n'));
|
|
27
|
+
controller.close();
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const response = new Response(stream);
|
|
31
|
+
const events = await repository.collectStreamEvents(response);
|
|
32
|
+
expect(events).toEqual([{ type: 'text-delta', payload: { text: 'hi' } }]);
|
|
33
|
+
});
|
|
34
|
+
it('collectStreamEventsはストリーム完了前にonEventsを呼び出す', async () => {
|
|
35
|
+
const repository = createAgentRepository({
|
|
36
|
+
fetchFn: vi.fn(),
|
|
37
|
+
getAuthToken: undefined,
|
|
38
|
+
authToken: undefined,
|
|
39
|
+
disableToolApiAuthHeader: false,
|
|
40
|
+
});
|
|
41
|
+
const encoder = new TextEncoder();
|
|
42
|
+
let closeStream = null;
|
|
43
|
+
const stream = new ReadableStream({
|
|
44
|
+
start(controller) {
|
|
45
|
+
controller.enqueue(encoder.encode('data: {"type":"text-delta","payload":{"text":"hel"}}\n\n'));
|
|
46
|
+
closeStream = () => {
|
|
47
|
+
controller.enqueue(encoder.encode('data: {"type":"text-delta","payload":{"text":"lo"}}\n\n'));
|
|
48
|
+
controller.close();
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const response = new Response(stream);
|
|
53
|
+
const onEvents = vi.fn();
|
|
54
|
+
const collectPromise = repository.collectStreamEvents(response, { onEvents });
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
56
|
+
expect(onEvents).toHaveBeenCalledWith([{ type: 'text-delta', payload: { text: 'hel' } }]);
|
|
57
|
+
closeStream?.();
|
|
58
|
+
const events = await collectPromise;
|
|
59
|
+
expect(events).toEqual([
|
|
60
|
+
{ type: 'text-delta', payload: { text: 'hel' } },
|
|
61
|
+
{ type: 'text-delta', payload: { text: 'lo' } },
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AgentMessage, ToolCallData, ToolCallState, ToolCallStatus } from '../../../components/agent/provider/types';
|
|
2
|
+
export interface AgentChatState {
|
|
3
|
+
messages: AgentMessage[];
|
|
4
|
+
threadId: string | null;
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
error: string | null;
|
|
7
|
+
pendingToolCalls: Map<string, ToolCallState>;
|
|
8
|
+
}
|
|
9
|
+
export interface StartChatPayload {
|
|
10
|
+
userMessageId: string;
|
|
11
|
+
text: string;
|
|
12
|
+
assistantMessageId: string;
|
|
13
|
+
}
|
|
14
|
+
export interface UpsertToolCallStatePayload {
|
|
15
|
+
toolCall: ToolCallData;
|
|
16
|
+
status: ToolCallStatus;
|
|
17
|
+
result?: unknown;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
export type AgentChatAction = {
|
|
21
|
+
type: 'START_CHAT';
|
|
22
|
+
payload: StartChatPayload;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'START_ASSISTANT_MESSAGE';
|
|
25
|
+
payload: {
|
|
26
|
+
assistantMessageId: string;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
type: 'UPDATE_ASSISTANT_PROGRESS';
|
|
30
|
+
payload: {
|
|
31
|
+
assistantMessageId: string;
|
|
32
|
+
text: string;
|
|
33
|
+
toolCalls: ToolCallData[];
|
|
34
|
+
};
|
|
35
|
+
} | {
|
|
36
|
+
type: 'FINISH_ASSISTANT_MESSAGE';
|
|
37
|
+
payload: {
|
|
38
|
+
assistantMessageId: string;
|
|
39
|
+
};
|
|
40
|
+
} | {
|
|
41
|
+
type: 'UPSERT_TOOL_CALL_STATE';
|
|
42
|
+
payload: UpsertToolCallStatePayload;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'SET_LOADING';
|
|
45
|
+
payload: {
|
|
46
|
+
isLoading: boolean;
|
|
47
|
+
};
|
|
48
|
+
} | {
|
|
49
|
+
type: 'SET_ERROR';
|
|
50
|
+
payload: {
|
|
51
|
+
error: string | null;
|
|
52
|
+
};
|
|
53
|
+
} | {
|
|
54
|
+
type: 'SET_THREAD_ID';
|
|
55
|
+
payload: {
|
|
56
|
+
threadId: string;
|
|
57
|
+
};
|
|
58
|
+
} | {
|
|
59
|
+
type: 'RESET_CHAT';
|
|
60
|
+
};
|
|
61
|
+
export declare function createInitialChatState(): AgentChatState;
|
|
62
|
+
export declare function appendIntentText(current: string, intentMessage: string): string;
|
|
63
|
+
export declare function selectPendingToolCalls(state: AgentChatState): ToolCallState[];
|
|
64
|
+
export declare function chatStateReducer(state: AgentChatState, action: AgentChatAction): AgentChatState;
|