@surf-kit/agent 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2026 surf-kit contributors
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted.
5
+
6
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
8
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @surf-kit/agent
2
+
3
+ > AI agent interface components — the building blocks no other design system has
4
+
5
+ Part of the [surf-kit](https://github.com/barney-w/surf-kit) design system.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @surf-kit/agent @surf-kit/core @surf-kit/theme @surf-kit/tokens
11
+ ```
12
+
13
+ ## Quick Example
14
+
15
+ ```tsx
16
+ import { ThemeProvider } from '@surf-kit/theme';
17
+ import { AgentChat } from '@surf-kit/agent';
18
+ import '@surf-kit/tokens/css';
19
+
20
+ function App() {
21
+ return (
22
+ <ThemeProvider>
23
+ <AgentChat
24
+ agentName="Assistant"
25
+ messages={messages}
26
+ onSend={handleSend}
27
+ />
28
+ </ThemeProvider>
29
+ );
30
+ }
31
+ ```
32
+
33
+ ## What's Included
34
+
35
+ **Chat** — AgentChat, MessageThread, MessageBubble, MessageComposer, WelcomeScreen, ConversationList
36
+
37
+ **Streaming** — StreamingMessage, ThinkingIndicator, ToolExecution, RetrievalProgress, VerificationProgress, TypewriterText
38
+
39
+ **Trust & Confidence** — ConfidenceBadge, ConfidenceMeter, ConfidenceBreakdown, VerificationBadge, VerificationDetail
40
+
41
+ **Sources** — SourceCard, SourceList, SourceInline, SourceDrawer, SourceBadge
42
+
43
+ **Agent Identity** — AgentAvatar, AgentLabel, AgentHandoff, RoutingIndicator
44
+
45
+ **Feedback** — ThumbsFeedback, FeedbackDialog, FeedbackConfirmation
46
+
47
+ **Response** — AgentResponse, ResponseMessage, StructuredResponse, FollowUpChips, ErrorResponse
48
+
49
+ **Layouts** — AgentFullPage, AgentPanel, AgentWidget, AgentEmbed
50
+
51
+ **Hooks** (via `@surf-kit/agent/hooks`) — useAgentChat, useStreaming, useConversation, useFeedback, useAgentTheme, useCharacterDrain
52
+
53
+ ## Docs
54
+
55
+ - [Storybook](https://barney-w.github.io/surf-kit/storybook)
56
+ - [Contributing](https://github.com/barney-w/surf-kit/blob/main/CONTRIBUTING.md)
57
+
58
+ ## License
59
+
60
+ [0BSD](./LICENSE)
@@ -0,0 +1,232 @@
1
+ import React from 'react';
2
+
3
+ interface Source {
4
+ title: string;
5
+ section?: string;
6
+ document_id: string;
7
+ url: string;
8
+ confidence: number;
9
+ snippet: string;
10
+ }
11
+ interface ConfidenceBreakdown {
12
+ overall: 'high' | 'medium' | 'low';
13
+ retrieval_quality: number;
14
+ source_authority: number;
15
+ answer_groundedness: number;
16
+ recency: number;
17
+ reasoning: string;
18
+ }
19
+ interface VerificationResult {
20
+ status: 'passed' | 'flagged' | 'failed';
21
+ flags: string[];
22
+ claims_checked: number;
23
+ claims_verified: number;
24
+ }
25
+ interface AgentResponse {
26
+ message: string;
27
+ sources: Source[];
28
+ confidence: ConfidenceBreakdown;
29
+ verification: VerificationResult;
30
+ ui_hint: 'text' | 'table' | 'card' | 'list' | 'steps' | 'warning';
31
+ structured_data: Record<string, unknown> | null;
32
+ follow_up_suggestions: string[];
33
+ }
34
+ interface AgentInfo {
35
+ id: string;
36
+ label: string;
37
+ accent?: string;
38
+ icon?: React.ComponentType<{
39
+ size?: number;
40
+ className?: string;
41
+ }>;
42
+ }
43
+
44
+ interface ChatMessage {
45
+ id: string;
46
+ role: 'user' | 'assistant';
47
+ content: string;
48
+ response?: AgentResponse;
49
+ agent?: string;
50
+ timestamp: Date;
51
+ }
52
+ interface ConversationSummary {
53
+ id: string;
54
+ title: string;
55
+ lastMessage: string;
56
+ updatedAt: Date;
57
+ messageCount: number;
58
+ }
59
+ interface ChatError {
60
+ code: 'NETWORK_ERROR' | 'API_ERROR' | 'STREAM_ERROR' | 'TIMEOUT';
61
+ message: string;
62
+ retryable: boolean;
63
+ }
64
+
65
+ type StreamEvent = {
66
+ type: 'phase';
67
+ phase: 'thinking' | 'retrieving' | 'generating' | 'verifying' | 'waiting';
68
+ } | {
69
+ type: 'delta';
70
+ content: string;
71
+ } | {
72
+ type: 'source';
73
+ source: Source;
74
+ } | {
75
+ type: 'agent';
76
+ agent: string;
77
+ } | {
78
+ type: 'verification';
79
+ result: VerificationResult;
80
+ } | {
81
+ type: 'confidence';
82
+ breakdown: ConfidenceBreakdown;
83
+ } | {
84
+ type: 'done';
85
+ response: AgentResponse;
86
+ conversation_id?: string;
87
+ } | {
88
+ type: 'error';
89
+ error: ChatError;
90
+ };
91
+ interface StreamState {
92
+ active: boolean;
93
+ phase: 'idle' | 'thinking' | 'retrieving' | 'generating' | 'verifying' | 'waiting';
94
+ content: string;
95
+ sources: Source[];
96
+ agent: string | null;
97
+ agentLabel: string | null;
98
+ }
99
+
100
+ interface AgentChatConfig {
101
+ /** Base URL for the agent API */
102
+ apiUrl: string;
103
+ /** SSE endpoint path (appended to apiUrl) */
104
+ streamPath?: string;
105
+ /** Feedback endpoint path (appended to apiUrl) */
106
+ feedbackPath?: string;
107
+ /** Conversations endpoint path (appended to apiUrl) */
108
+ conversationsPath?: string;
109
+ /** Request headers (e.g. Authorization) */
110
+ headers?: Record<string, string>;
111
+ /** Request timeout in milliseconds */
112
+ timeout?: number;
113
+ /** Enable localStorage persistence for conversations */
114
+ persistConversations?: boolean;
115
+ /** Map of agent IDs to their display config */
116
+ agentThemes?: Record<string, AgentInfo>;
117
+ }
118
+
119
+ interface AgentChatState {
120
+ messages: ChatMessage[];
121
+ conversationId: string | null;
122
+ isLoading: boolean;
123
+ error: ChatError | null;
124
+ inputValue: string;
125
+ streamPhase: StreamState['phase'];
126
+ streamingContent: string;
127
+ }
128
+ interface AgentChatActions {
129
+ sendMessage: (content: string) => Promise<void>;
130
+ setInputValue: (value: string) => void;
131
+ loadConversation: (conversationId: string, messages: ChatMessage[]) => void;
132
+ submitFeedback: (messageId: string, rating: 'positive' | 'negative', comment?: string) => Promise<void>;
133
+ retry: () => Promise<void>;
134
+ reset: () => void;
135
+ }
136
+ declare function useAgentChat(config: AgentChatConfig): {
137
+ state: AgentChatState;
138
+ actions: AgentChatActions;
139
+ };
140
+
141
+ interface UseStreamingOptions {
142
+ /** SSE endpoint URL */
143
+ url: string;
144
+ /** Additional headers for fetch-based SSE (not used with native EventSource) */
145
+ headers?: Record<string, string>;
146
+ /** Called when a complete response is received */
147
+ onDone?: (event: Extract<StreamEvent, {
148
+ type: 'done';
149
+ }>) => void;
150
+ /** Called on error */
151
+ onError?: (event: Extract<StreamEvent, {
152
+ type: 'error';
153
+ }>) => void;
154
+ }
155
+ declare function useStreaming(options: UseStreamingOptions): {
156
+ state: StreamState;
157
+ start: (body: Record<string, unknown>) => Promise<void>;
158
+ stop: () => void;
159
+ };
160
+
161
+ interface Conversation {
162
+ id: string;
163
+ title: string;
164
+ messages: ChatMessage[];
165
+ createdAt: Date;
166
+ updatedAt: Date;
167
+ }
168
+ interface UseConversationOptions {
169
+ /** Enable localStorage persistence */
170
+ persist?: boolean;
171
+ /** localStorage key prefix */
172
+ storageKey?: string;
173
+ }
174
+ declare function useConversation(options?: UseConversationOptions): {
175
+ conversations: Conversation[];
176
+ current: Conversation | null;
177
+ create: (title?: string) => Conversation;
178
+ list: () => ConversationSummary[];
179
+ load: (id: string) => Conversation | null;
180
+ delete: (id: string) => void;
181
+ rename: (id: string, title: string) => void;
182
+ addMessage: (conversationId: string, message: ChatMessage) => void;
183
+ };
184
+
185
+ type FeedbackState = 'idle' | 'submitting' | 'submitted' | 'error';
186
+ interface UseFeedbackOptions {
187
+ /** API endpoint URL for feedback submission */
188
+ url: string;
189
+ /** Additional request headers */
190
+ headers?: Record<string, string>;
191
+ }
192
+ interface FeedbackPayload {
193
+ messageId: string;
194
+ rating: 'positive' | 'negative';
195
+ comment?: string;
196
+ }
197
+ declare function useFeedback(options: UseFeedbackOptions): {
198
+ state: FeedbackState;
199
+ error: string | null;
200
+ submit: (messageId: string, rating: "positive" | "negative", comment?: string) => Promise<void>;
201
+ reset: () => void;
202
+ };
203
+
204
+ interface AgentThemeResult {
205
+ accent: string;
206
+ icon: AgentInfo['icon'] | null;
207
+ label: string;
208
+ }
209
+ declare function useAgentTheme(agentId: string | null | undefined, agentThemes?: Record<string, AgentInfo>): AgentThemeResult;
210
+
211
+ interface CharacterDrainResult {
212
+ displayed: string;
213
+ isDraining: boolean;
214
+ }
215
+ /**
216
+ * Smoothly drains a growing `target` string character-by-character using
217
+ * `requestAnimationFrame`, decoupling visual rendering from network packet
218
+ * timing so text appears to type out instead of arriving in chunks.
219
+ *
220
+ * When `target` resets to empty (e.g. stream finished), the hook continues
221
+ * draining the previous content to completion before resetting, so the
222
+ * typing animation isn't cut short.
223
+ *
224
+ * Design: the RAF loop is long-lived — it does NOT restart on every delta.
225
+ * The tick function is stored in a ref (updated each render) so the loop
226
+ * always reads the latest drainTarget without being cancelled/restarted.
227
+ * A separate kick-start effect re-fires the loop when it was idle and new
228
+ * content arrives.
229
+ */
230
+ declare function useCharacterDrain(target: string, msPerChar?: number): CharacterDrainResult;
231
+
232
+ export { type AgentResponse as A, type ChatMessage as C, type FeedbackPayload as F, type Source as S, type UseConversationOptions as U, type VerificationResult as V, type ChatError as a, type ConfidenceBreakdown as b, type AgentInfo as c, type StreamState as d, type ConversationSummary as e, type AgentChatActions as f, type AgentChatConfig as g, type AgentChatState as h, type AgentThemeResult as i, type CharacterDrainResult as j, type Conversation as k, type FeedbackState as l, type StreamEvent as m, type UseFeedbackOptions as n, type UseStreamingOptions as o, useAgentTheme as p, useCharacterDrain as q, useConversation as r, useFeedback as s, useStreaming as t, useAgentChat as u };
@@ -0,0 +1,232 @@
1
+ import React from 'react';
2
+
3
+ interface Source {
4
+ title: string;
5
+ section?: string;
6
+ document_id: string;
7
+ url: string;
8
+ confidence: number;
9
+ snippet: string;
10
+ }
11
+ interface ConfidenceBreakdown {
12
+ overall: 'high' | 'medium' | 'low';
13
+ retrieval_quality: number;
14
+ source_authority: number;
15
+ answer_groundedness: number;
16
+ recency: number;
17
+ reasoning: string;
18
+ }
19
+ interface VerificationResult {
20
+ status: 'passed' | 'flagged' | 'failed';
21
+ flags: string[];
22
+ claims_checked: number;
23
+ claims_verified: number;
24
+ }
25
+ interface AgentResponse {
26
+ message: string;
27
+ sources: Source[];
28
+ confidence: ConfidenceBreakdown;
29
+ verification: VerificationResult;
30
+ ui_hint: 'text' | 'table' | 'card' | 'list' | 'steps' | 'warning';
31
+ structured_data: Record<string, unknown> | null;
32
+ follow_up_suggestions: string[];
33
+ }
34
+ interface AgentInfo {
35
+ id: string;
36
+ label: string;
37
+ accent?: string;
38
+ icon?: React.ComponentType<{
39
+ size?: number;
40
+ className?: string;
41
+ }>;
42
+ }
43
+
44
+ interface ChatMessage {
45
+ id: string;
46
+ role: 'user' | 'assistant';
47
+ content: string;
48
+ response?: AgentResponse;
49
+ agent?: string;
50
+ timestamp: Date;
51
+ }
52
+ interface ConversationSummary {
53
+ id: string;
54
+ title: string;
55
+ lastMessage: string;
56
+ updatedAt: Date;
57
+ messageCount: number;
58
+ }
59
+ interface ChatError {
60
+ code: 'NETWORK_ERROR' | 'API_ERROR' | 'STREAM_ERROR' | 'TIMEOUT';
61
+ message: string;
62
+ retryable: boolean;
63
+ }
64
+
65
+ type StreamEvent = {
66
+ type: 'phase';
67
+ phase: 'thinking' | 'retrieving' | 'generating' | 'verifying' | 'waiting';
68
+ } | {
69
+ type: 'delta';
70
+ content: string;
71
+ } | {
72
+ type: 'source';
73
+ source: Source;
74
+ } | {
75
+ type: 'agent';
76
+ agent: string;
77
+ } | {
78
+ type: 'verification';
79
+ result: VerificationResult;
80
+ } | {
81
+ type: 'confidence';
82
+ breakdown: ConfidenceBreakdown;
83
+ } | {
84
+ type: 'done';
85
+ response: AgentResponse;
86
+ conversation_id?: string;
87
+ } | {
88
+ type: 'error';
89
+ error: ChatError;
90
+ };
91
+ interface StreamState {
92
+ active: boolean;
93
+ phase: 'idle' | 'thinking' | 'retrieving' | 'generating' | 'verifying' | 'waiting';
94
+ content: string;
95
+ sources: Source[];
96
+ agent: string | null;
97
+ agentLabel: string | null;
98
+ }
99
+
100
+ interface AgentChatConfig {
101
+ /** Base URL for the agent API */
102
+ apiUrl: string;
103
+ /** SSE endpoint path (appended to apiUrl) */
104
+ streamPath?: string;
105
+ /** Feedback endpoint path (appended to apiUrl) */
106
+ feedbackPath?: string;
107
+ /** Conversations endpoint path (appended to apiUrl) */
108
+ conversationsPath?: string;
109
+ /** Request headers (e.g. Authorization) */
110
+ headers?: Record<string, string>;
111
+ /** Request timeout in milliseconds */
112
+ timeout?: number;
113
+ /** Enable localStorage persistence for conversations */
114
+ persistConversations?: boolean;
115
+ /** Map of agent IDs to their display config */
116
+ agentThemes?: Record<string, AgentInfo>;
117
+ }
118
+
119
+ interface AgentChatState {
120
+ messages: ChatMessage[];
121
+ conversationId: string | null;
122
+ isLoading: boolean;
123
+ error: ChatError | null;
124
+ inputValue: string;
125
+ streamPhase: StreamState['phase'];
126
+ streamingContent: string;
127
+ }
128
+ interface AgentChatActions {
129
+ sendMessage: (content: string) => Promise<void>;
130
+ setInputValue: (value: string) => void;
131
+ loadConversation: (conversationId: string, messages: ChatMessage[]) => void;
132
+ submitFeedback: (messageId: string, rating: 'positive' | 'negative', comment?: string) => Promise<void>;
133
+ retry: () => Promise<void>;
134
+ reset: () => void;
135
+ }
136
+ declare function useAgentChat(config: AgentChatConfig): {
137
+ state: AgentChatState;
138
+ actions: AgentChatActions;
139
+ };
140
+
141
+ interface UseStreamingOptions {
142
+ /** SSE endpoint URL */
143
+ url: string;
144
+ /** Additional headers for fetch-based SSE (not used with native EventSource) */
145
+ headers?: Record<string, string>;
146
+ /** Called when a complete response is received */
147
+ onDone?: (event: Extract<StreamEvent, {
148
+ type: 'done';
149
+ }>) => void;
150
+ /** Called on error */
151
+ onError?: (event: Extract<StreamEvent, {
152
+ type: 'error';
153
+ }>) => void;
154
+ }
155
+ declare function useStreaming(options: UseStreamingOptions): {
156
+ state: StreamState;
157
+ start: (body: Record<string, unknown>) => Promise<void>;
158
+ stop: () => void;
159
+ };
160
+
161
+ interface Conversation {
162
+ id: string;
163
+ title: string;
164
+ messages: ChatMessage[];
165
+ createdAt: Date;
166
+ updatedAt: Date;
167
+ }
168
+ interface UseConversationOptions {
169
+ /** Enable localStorage persistence */
170
+ persist?: boolean;
171
+ /** localStorage key prefix */
172
+ storageKey?: string;
173
+ }
174
+ declare function useConversation(options?: UseConversationOptions): {
175
+ conversations: Conversation[];
176
+ current: Conversation | null;
177
+ create: (title?: string) => Conversation;
178
+ list: () => ConversationSummary[];
179
+ load: (id: string) => Conversation | null;
180
+ delete: (id: string) => void;
181
+ rename: (id: string, title: string) => void;
182
+ addMessage: (conversationId: string, message: ChatMessage) => void;
183
+ };
184
+
185
+ type FeedbackState = 'idle' | 'submitting' | 'submitted' | 'error';
186
+ interface UseFeedbackOptions {
187
+ /** API endpoint URL for feedback submission */
188
+ url: string;
189
+ /** Additional request headers */
190
+ headers?: Record<string, string>;
191
+ }
192
+ interface FeedbackPayload {
193
+ messageId: string;
194
+ rating: 'positive' | 'negative';
195
+ comment?: string;
196
+ }
197
+ declare function useFeedback(options: UseFeedbackOptions): {
198
+ state: FeedbackState;
199
+ error: string | null;
200
+ submit: (messageId: string, rating: "positive" | "negative", comment?: string) => Promise<void>;
201
+ reset: () => void;
202
+ };
203
+
204
+ interface AgentThemeResult {
205
+ accent: string;
206
+ icon: AgentInfo['icon'] | null;
207
+ label: string;
208
+ }
209
+ declare function useAgentTheme(agentId: string | null | undefined, agentThemes?: Record<string, AgentInfo>): AgentThemeResult;
210
+
211
+ interface CharacterDrainResult {
212
+ displayed: string;
213
+ isDraining: boolean;
214
+ }
215
+ /**
216
+ * Smoothly drains a growing `target` string character-by-character using
217
+ * `requestAnimationFrame`, decoupling visual rendering from network packet
218
+ * timing so text appears to type out instead of arriving in chunks.
219
+ *
220
+ * When `target` resets to empty (e.g. stream finished), the hook continues
221
+ * draining the previous content to completion before resetting, so the
222
+ * typing animation isn't cut short.
223
+ *
224
+ * Design: the RAF loop is long-lived — it does NOT restart on every delta.
225
+ * The tick function is stored in a ref (updated each render) so the loop
226
+ * always reads the latest drainTarget without being cancelled/restarted.
227
+ * A separate kick-start effect re-fires the loop when it was idle and new
228
+ * content arrives.
229
+ */
230
+ declare function useCharacterDrain(target: string, msPerChar?: number): CharacterDrainResult;
231
+
232
+ export { type AgentResponse as A, type ChatMessage as C, type FeedbackPayload as F, type Source as S, type UseConversationOptions as U, type VerificationResult as V, type ChatError as a, type ConfidenceBreakdown as b, type AgentInfo as c, type StreamState as d, type ConversationSummary as e, type AgentChatActions as f, type AgentChatConfig as g, type AgentChatState as h, type AgentThemeResult as i, type CharacterDrainResult as j, type Conversation as k, type FeedbackState as l, type StreamEvent as m, type UseFeedbackOptions as n, type UseStreamingOptions as o, useAgentTheme as p, useCharacterDrain as q, useConversation as r, useFeedback as s, useStreaming as t, useAgentChat as u };