@yushaw/sanqian-chat 0.1.1 → 0.2.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/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # @yushaw/sanqian-chat
2
+
3
+ Sanqian chat UI components and hooks for third-party Electron apps.
4
+
5
+ ## Phase 1 (Core Chat)
6
+ - Streaming chat messages
7
+ - Thinking section
8
+ - Tool call timeline
9
+ - HITL prompts
10
+ - Simplified input (text + send + stop)
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @yushaw/sanqian-chat
16
+ ```
17
+
18
+ ## Publish
19
+
20
+ From the repo root:
21
+
22
+ ```bash
23
+ npm publish --workspace=@yushaw/sanqian-chat
24
+ ```
25
+
26
+ Or from the package directory:
27
+
28
+ ```bash
29
+ cd packages/chat
30
+ npm publish
31
+ ```
32
+
33
+ ## Usage (Renderer)
34
+
35
+ ```tsx
36
+ import { SanqianChat, createSdkAdapter } from '@yushaw/sanqian-chat/renderer';
37
+ import { SanqianSDK } from '@yushaw/sanqian-sdk';
38
+
39
+ const sdk = new SanqianSDK({
40
+ appName: 'my-app',
41
+ appVersion: '0.1.0',
42
+ tools: [],
43
+ });
44
+
45
+ const adapter = createSdkAdapter({
46
+ getSdk: () => sdk,
47
+ getAgentId: () => 'assistant',
48
+ });
49
+
50
+ export function App() {
51
+ return (
52
+ <div className="h-full">
53
+ <SanqianChat
54
+ adapter={adapter}
55
+ config={{
56
+ theme: 'auto',
57
+ accentColor: '#e85d75',
58
+ locale: 'zh',
59
+ }}
60
+ />
61
+ </div>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ## API Reference
67
+
68
+ See `packages/chat/API.md` and `packages/chat/API.zh-CN.md`.
69
+
70
+ ## Notes
71
+ - **Style modes**: Default is `'safe'` (no Tailwind preflight, safe for embedding). Use `'full'` for standalone windows.
72
+ - Set `window.__SANQIAN_CHAT_STYLE_MODE__ = 'full'` for full styles including preflight
73
+ - Set `window.__SANQIAN_CHAT_DISABLE_STYLES__ = true` to disable auto-injection
74
+ - **CSS files**:
75
+ - `@yushaw/sanqian-chat/renderer/styles/safe.css` - Safe (no preflight, includes utilities)
76
+ - `@yushaw/sanqian-chat/renderer/styles/chat.css` - Full (includes Tailwind preflight)
77
+ - `@yushaw/sanqian-chat/renderer/styles/variables.css` - Variables only
78
+ - Sanqian should be running when the SDK connects.
79
+ - Additional capabilities (history, agents, files, settings) will be added in later phases.
80
+
81
+ ## Developing Styles
82
+
83
+ CSS uses a modular structure with a single source of truth:
84
+
85
+ ```
86
+ src/renderer/styles/
87
+ ├── coreCss.ts # ← EDIT THIS (CSS variables + utilities)
88
+ ├── preflightCss.ts # Tailwind preflight (don't edit)
89
+ ├── safeCss.ts # Auto-generated (imports coreCss)
90
+ ├── chatCss.ts # Auto-generated (imports coreCss + preflight)
91
+ ├── safe.css # Auto-generated
92
+ └── chat.css # Auto-generated
93
+ ```
94
+
95
+ **To modify styles:**
96
+ 1. Edit `coreCss.ts`
97
+ 2. Run `npm run build:css` to regenerate all files
98
+ 3. Or just run `npm run build` (includes CSS generation)
99
+
100
+ ## Changelog
101
+
102
+ ### 2025-01-03
103
+ - **Added**: Focus persistence - input keeps focus when clicking empty areas (unless selecting text)
104
+ - **Added**: `Cmd+N` (macOS) / `Ctrl+N` (Windows) keyboard shortcut for new chat
105
+ - **Added**: Hover tooltip with shortcut hint on new chat button
106
+
107
+ ### 2025-01-02
108
+ - **Changed**: CSS modular structure - single source of truth (`coreCss.ts`)
109
+ - **Changed**: `.prose` uses `font-size: inherit` for external control
110
+ - **Changed**: IntermediateSteps uses relative font sizes (rem instead of px)
111
+ - **Fixed**: `position` default value conflict with `rememberWindowState`
112
+ - **Fixed**: IPC now forwards `start` event (with `run_id`) to renderer
113
+ - **Fixed**: Center position calculation uses `workArea` (supports multi-monitor/dock)
114
+ - **Fixed**: `console.log` only outputs in `devMode`
115
+ - **Fixed**: Global style pollution - default to `'safe'` mode (no Tailwind preflight)
116
+ - **Fixed**: `cancelStream` now calls `sdk.cancelRun()` for real cancellation
@@ -7,16 +7,80 @@ export { ChatStreamEvent, HitlInterruptPayload, HitlInterruptType, HitlResponse,
7
7
  * Re-exports SDK types + chat-specific types
8
8
  */
9
9
 
10
- type MessageRole = 'user' | 'assistant' | 'system';
10
+ type Locale = 'en' | 'zh';
11
+ interface ChatUiStrings {
12
+ inputPlaceholder: string;
13
+ inputSend: string;
14
+ inputStop: string;
15
+ chat: string;
16
+ recentChats: string;
17
+ newChat: string;
18
+ selectConversation: string;
19
+ noHistory: string;
20
+ loadMore: string;
21
+ today: string;
22
+ yesterday: string;
23
+ delete: string;
24
+ dismiss: string;
25
+ close: string;
26
+ pin: string;
27
+ unpin: string;
28
+ conversationUntitled: string;
29
+ conversationDeleteConfirm: string;
30
+ messageThinking: string;
31
+ messageError: string;
32
+ messageLoading: string;
33
+ connectionConnecting: string;
34
+ connectionConnected: string;
35
+ connectionDisconnected: string;
36
+ connectionReconnecting: string;
37
+ connectionError: string;
38
+ steps: string;
39
+ executing: string;
40
+ thinking: string;
41
+ thinkingStreaming: string;
42
+ thinkingPaused: string;
43
+ hitlApprove: string;
44
+ hitlReject: string;
45
+ hitlSubmit: string;
46
+ hitlCancel: string;
47
+ hitlRememberChoice: string;
48
+ hitlRequiredField: string;
49
+ hitlTimeoutIn: string;
50
+ hitlSeconds: string;
51
+ hitlExecuteTool: string;
52
+ hitlToolLabel: string;
53
+ hitlArgsLabel: string;
54
+ hitlDefaultPrefix: string;
55
+ hitlEnterResponse: string;
56
+ hitlApprovalRequest: string;
57
+ hitlInputRequest: string;
58
+ hitlApprovalRequired: string;
59
+ hitlInputRequired: string;
60
+ }
61
+ type ChatThemeMode = 'light' | 'dark' | 'auto';
62
+ type ChatFontSize = 'small' | 'normal' | 'large' | 'extra-large';
63
+ interface ChatUiConfigSerializable {
64
+ logo?: string;
65
+ theme?: ChatThemeMode;
66
+ /** Font size scale: small (13px), normal (14px), large (16px), extra-large (18px) */
67
+ fontSize?: ChatFontSize;
68
+ accentColor?: string;
69
+ locale?: Locale;
70
+ strings?: Partial<ChatUiStrings>;
71
+ alwaysOnTop?: boolean;
72
+ }
73
+ type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
11
74
  type ToolCallStatus = 'pending' | 'running' | 'completed' | 'error' | 'cancelled';
12
75
  type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
13
76
  type ConnectionErrorCode = 'NOT_FOUND' | 'CONNECTION_FAILED' | 'WEBSOCKET_ERROR' | 'AUTH_ERROR' | 'TIMEOUT' | 'UNKNOWN';
14
77
  /** Tool call for UI rendering (extended from SDK) */
15
78
  interface ToolCall {
16
- id: string;
79
+ id?: string;
17
80
  name: string;
18
- arguments: Record<string, unknown>;
19
- status: ToolCallStatus;
81
+ args?: Record<string, unknown>;
82
+ argsRaw?: string;
83
+ status?: ToolCallStatus;
20
84
  result?: unknown;
21
85
  error?: string;
22
86
  }
@@ -27,9 +91,11 @@ interface MessageBlock {
27
91
  timestamp: number;
28
92
  toolName?: string;
29
93
  toolArgs?: Record<string, unknown>;
94
+ toolArgsRaw?: string;
30
95
  toolCallId?: string;
31
96
  toolStatus?: ToolCallStatus;
32
97
  isIntermediate?: boolean;
98
+ fromSubagent?: boolean;
33
99
  }
34
100
  /** Chat message for UI rendering */
35
101
  interface ChatMessage {
@@ -42,8 +108,12 @@ interface ChatMessage {
42
108
  thinking?: string;
43
109
  currentThinking?: string;
44
110
  isThinkingStreaming?: boolean;
111
+ isThinkingPaused?: boolean;
112
+ isToolCallsStreaming?: boolean;
45
113
  blocks?: MessageBlock[];
114
+ finalContent?: string;
46
115
  isComplete?: boolean;
116
+ filePaths?: string[];
47
117
  }
48
118
  /** Conversation info for UI */
49
119
  interface ConversationInfo {
@@ -52,6 +122,7 @@ interface ConversationInfo {
52
122
  createdAt: string;
53
123
  updatedAt: string;
54
124
  messageCount: number;
125
+ agentId?: string | null;
55
126
  }
56
127
  interface ConversationDetail extends ConversationInfo {
57
128
  messages: ChatMessage[];
@@ -67,9 +138,21 @@ interface FloatingWindowConfig {
67
138
  position?: WindowPosition;
68
139
  width?: number;
69
140
  height?: number;
141
+ /** Minimum window width (default: 320) */
142
+ minWidth?: number;
143
+ /** Minimum window height (default: 420) */
144
+ minHeight?: number;
70
145
  alwaysOnTop?: boolean;
71
146
  showInTaskbar?: boolean;
72
147
  theme?: 'light' | 'dark' | 'system';
148
+ /** Optional UI config for renderer (serializable) */
149
+ uiConfig?: ChatUiConfigSerializable;
150
+ /** Persist size/position across sessions (stored under ~/.sanqian-chat) */
151
+ rememberWindowState?: boolean;
152
+ /** Optional storage key to avoid conflicts between apps */
153
+ windowStateKey?: string;
154
+ /** Optional full path for window state file */
155
+ windowStatePath?: string;
73
156
  }
74
157
 
75
158
  /**
@@ -81,6 +164,10 @@ interface FloatingWindowConfig {
81
164
 
82
165
  /** Stream event callback */
83
166
  type StreamEvent = {
167
+ type: 'start';
168
+ run_id: string;
169
+ conversationId?: string;
170
+ } | {
84
171
  type: 'text';
85
172
  content: string;
86
173
  } | {
@@ -95,6 +182,16 @@ type StreamEvent = {
95
182
  arguments: string;
96
183
  };
97
184
  };
185
+ } | {
186
+ type: 'tool_args_chunk';
187
+ tool_call_id: string;
188
+ tool_name?: string;
189
+ chunk: string;
190
+ } | {
191
+ type: 'tool_args';
192
+ tool_call_id: string;
193
+ tool_name?: string;
194
+ args: Record<string, unknown>;
98
195
  } | {
99
196
  type: 'tool_result';
100
197
  tool_call_id: string;
@@ -111,6 +208,9 @@ type StreamEvent = {
111
208
  interrupt_type: string;
112
209
  interrupt_payload: HitlInterruptData;
113
210
  run_id?: string;
211
+ } | {
212
+ type: 'cancelled';
213
+ run_id?: string;
114
214
  };
115
215
  /** Message to send */
116
216
  interface SendMessage {
@@ -135,7 +235,9 @@ interface ChatAdapter {
135
235
  messageLimit?: number;
136
236
  }): Promise<ConversationDetail>;
137
237
  deleteConversation(id: string): Promise<void>;
138
- chatStream(messages: SendMessage[], conversationId: string | undefined, onEvent: (event: StreamEvent) => void): Promise<{
238
+ chatStream(messages: SendMessage[], conversationId: string | undefined, onEvent: (event: StreamEvent) => void, options?: {
239
+ agentId?: string | null;
240
+ }): Promise<{
139
241
  cancel: () => void;
140
242
  }>;
141
243
  sendHitlResponse?(response: HitlResponse, runId?: string): void;
@@ -148,9 +250,81 @@ interface SdkAdapterConfig {
148
250
  /** Agent ID getter */
149
251
  getAgentId: () => string | null;
150
252
  }
253
+ /** Simple chat adapter config (no SDK knowledge required) */
254
+ interface ChatAdapterConfig {
255
+ /** App name for registration */
256
+ appName: string;
257
+ /** App version */
258
+ appVersion: string;
259
+ /** Agent ID to chat with */
260
+ agentId: string;
261
+ /** Whether to persist conversation history (default: false) */
262
+ persistHistory?: boolean;
263
+ /** Tools to expose to the agent */
264
+ tools?: Array<{
265
+ name: string;
266
+ description: string;
267
+ parameters?: Record<string, unknown>;
268
+ handler?: (args: Record<string, unknown>) => Promise<unknown>;
269
+ }>;
270
+ }
271
+ /**
272
+ * Create a chat adapter with simple config (recommended for most use cases)
273
+ *
274
+ * This is the easiest way to integrate with Sanqian. Just provide your app info
275
+ * and agent ID, and the adapter handles SDK lifecycle automatically.
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const adapter = createChatAdapter({
280
+ * appName: 'my-app',
281
+ * appVersion: '1.0.0',
282
+ * agentId: 'assistant',
283
+ * });
284
+ *
285
+ * <CompactChat adapter={adapter} />
286
+ * ```
287
+ */
288
+ declare function createChatAdapter(config: ChatAdapterConfig): ChatAdapter;
151
289
  /**
152
290
  * Create adapter that wraps @yushaw/sanqian-sdk
291
+ *
292
+ * Use this when you need more control over the SDK instance.
293
+ * For simpler usage, prefer `createChatAdapter()`.
153
294
  */
154
295
  declare function createSdkAdapter(config: SdkAdapterConfig): ChatAdapter;
155
296
 
156
- export { type ChatAdapter, type ChatMessage, type ConnectionErrorCode, type ConnectionStatus, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type MessageBlock, type MessageRole, type SdkAdapterConfig, type SendMessage, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createSdkAdapter };
297
+ /**
298
+ * Conversation history helpers
299
+ *
300
+ * Normalizes SDK conversation messages into ChatMessage[] with
301
+ * tool call blocks and merged assistant/tool rounds.
302
+ *
303
+ * NOTE: This logic is intentionally kept in sync with
304
+ * src/renderer/src/utils/chat-helpers.ts in the main app.
305
+ */
306
+
307
+ interface ApiMessage {
308
+ id?: string;
309
+ role: string;
310
+ content: string;
311
+ created_at?: string;
312
+ timestamp?: string;
313
+ tool_calls?: unknown;
314
+ toolCalls?: unknown;
315
+ tool_call_id?: string | null;
316
+ thinking?: string | null;
317
+ filePaths?: string[];
318
+ message_id?: number;
319
+ }
320
+ /**
321
+ * Parse tool_calls from backend format (OpenAI or simplified).
322
+ */
323
+ declare function parseToolCalls(toolCalls: unknown): ToolCall[] | undefined;
324
+ /**
325
+ * Merge consecutive assistant + tool messages into single assistant messages,
326
+ * preserving blocks for intermediate steps.
327
+ */
328
+ declare function mergeConsecutiveAssistantMessages(rawMessages: ApiMessage[]): ChatMessage[];
329
+
330
+ export { type ApiMessage, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, type ChatMessage, type ChatThemeMode, type ChatUiConfigSerializable, type ChatUiStrings, type ConnectionErrorCode, type ConnectionStatus, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type Locale, type MessageBlock, type MessageRole, type SdkAdapterConfig, type SendMessage, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createChatAdapter, createSdkAdapter, mergeConsecutiveAssistantMessages, parseToolCalls };
@@ -7,16 +7,80 @@ export { ChatStreamEvent, HitlInterruptPayload, HitlInterruptType, HitlResponse,
7
7
  * Re-exports SDK types + chat-specific types
8
8
  */
9
9
 
10
- type MessageRole = 'user' | 'assistant' | 'system';
10
+ type Locale = 'en' | 'zh';
11
+ interface ChatUiStrings {
12
+ inputPlaceholder: string;
13
+ inputSend: string;
14
+ inputStop: string;
15
+ chat: string;
16
+ recentChats: string;
17
+ newChat: string;
18
+ selectConversation: string;
19
+ noHistory: string;
20
+ loadMore: string;
21
+ today: string;
22
+ yesterday: string;
23
+ delete: string;
24
+ dismiss: string;
25
+ close: string;
26
+ pin: string;
27
+ unpin: string;
28
+ conversationUntitled: string;
29
+ conversationDeleteConfirm: string;
30
+ messageThinking: string;
31
+ messageError: string;
32
+ messageLoading: string;
33
+ connectionConnecting: string;
34
+ connectionConnected: string;
35
+ connectionDisconnected: string;
36
+ connectionReconnecting: string;
37
+ connectionError: string;
38
+ steps: string;
39
+ executing: string;
40
+ thinking: string;
41
+ thinkingStreaming: string;
42
+ thinkingPaused: string;
43
+ hitlApprove: string;
44
+ hitlReject: string;
45
+ hitlSubmit: string;
46
+ hitlCancel: string;
47
+ hitlRememberChoice: string;
48
+ hitlRequiredField: string;
49
+ hitlTimeoutIn: string;
50
+ hitlSeconds: string;
51
+ hitlExecuteTool: string;
52
+ hitlToolLabel: string;
53
+ hitlArgsLabel: string;
54
+ hitlDefaultPrefix: string;
55
+ hitlEnterResponse: string;
56
+ hitlApprovalRequest: string;
57
+ hitlInputRequest: string;
58
+ hitlApprovalRequired: string;
59
+ hitlInputRequired: string;
60
+ }
61
+ type ChatThemeMode = 'light' | 'dark' | 'auto';
62
+ type ChatFontSize = 'small' | 'normal' | 'large' | 'extra-large';
63
+ interface ChatUiConfigSerializable {
64
+ logo?: string;
65
+ theme?: ChatThemeMode;
66
+ /** Font size scale: small (13px), normal (14px), large (16px), extra-large (18px) */
67
+ fontSize?: ChatFontSize;
68
+ accentColor?: string;
69
+ locale?: Locale;
70
+ strings?: Partial<ChatUiStrings>;
71
+ alwaysOnTop?: boolean;
72
+ }
73
+ type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
11
74
  type ToolCallStatus = 'pending' | 'running' | 'completed' | 'error' | 'cancelled';
12
75
  type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
13
76
  type ConnectionErrorCode = 'NOT_FOUND' | 'CONNECTION_FAILED' | 'WEBSOCKET_ERROR' | 'AUTH_ERROR' | 'TIMEOUT' | 'UNKNOWN';
14
77
  /** Tool call for UI rendering (extended from SDK) */
15
78
  interface ToolCall {
16
- id: string;
79
+ id?: string;
17
80
  name: string;
18
- arguments: Record<string, unknown>;
19
- status: ToolCallStatus;
81
+ args?: Record<string, unknown>;
82
+ argsRaw?: string;
83
+ status?: ToolCallStatus;
20
84
  result?: unknown;
21
85
  error?: string;
22
86
  }
@@ -27,9 +91,11 @@ interface MessageBlock {
27
91
  timestamp: number;
28
92
  toolName?: string;
29
93
  toolArgs?: Record<string, unknown>;
94
+ toolArgsRaw?: string;
30
95
  toolCallId?: string;
31
96
  toolStatus?: ToolCallStatus;
32
97
  isIntermediate?: boolean;
98
+ fromSubagent?: boolean;
33
99
  }
34
100
  /** Chat message for UI rendering */
35
101
  interface ChatMessage {
@@ -42,8 +108,12 @@ interface ChatMessage {
42
108
  thinking?: string;
43
109
  currentThinking?: string;
44
110
  isThinkingStreaming?: boolean;
111
+ isThinkingPaused?: boolean;
112
+ isToolCallsStreaming?: boolean;
45
113
  blocks?: MessageBlock[];
114
+ finalContent?: string;
46
115
  isComplete?: boolean;
116
+ filePaths?: string[];
47
117
  }
48
118
  /** Conversation info for UI */
49
119
  interface ConversationInfo {
@@ -52,6 +122,7 @@ interface ConversationInfo {
52
122
  createdAt: string;
53
123
  updatedAt: string;
54
124
  messageCount: number;
125
+ agentId?: string | null;
55
126
  }
56
127
  interface ConversationDetail extends ConversationInfo {
57
128
  messages: ChatMessage[];
@@ -67,9 +138,21 @@ interface FloatingWindowConfig {
67
138
  position?: WindowPosition;
68
139
  width?: number;
69
140
  height?: number;
141
+ /** Minimum window width (default: 320) */
142
+ minWidth?: number;
143
+ /** Minimum window height (default: 420) */
144
+ minHeight?: number;
70
145
  alwaysOnTop?: boolean;
71
146
  showInTaskbar?: boolean;
72
147
  theme?: 'light' | 'dark' | 'system';
148
+ /** Optional UI config for renderer (serializable) */
149
+ uiConfig?: ChatUiConfigSerializable;
150
+ /** Persist size/position across sessions (stored under ~/.sanqian-chat) */
151
+ rememberWindowState?: boolean;
152
+ /** Optional storage key to avoid conflicts between apps */
153
+ windowStateKey?: string;
154
+ /** Optional full path for window state file */
155
+ windowStatePath?: string;
73
156
  }
74
157
 
75
158
  /**
@@ -81,6 +164,10 @@ interface FloatingWindowConfig {
81
164
 
82
165
  /** Stream event callback */
83
166
  type StreamEvent = {
167
+ type: 'start';
168
+ run_id: string;
169
+ conversationId?: string;
170
+ } | {
84
171
  type: 'text';
85
172
  content: string;
86
173
  } | {
@@ -95,6 +182,16 @@ type StreamEvent = {
95
182
  arguments: string;
96
183
  };
97
184
  };
185
+ } | {
186
+ type: 'tool_args_chunk';
187
+ tool_call_id: string;
188
+ tool_name?: string;
189
+ chunk: string;
190
+ } | {
191
+ type: 'tool_args';
192
+ tool_call_id: string;
193
+ tool_name?: string;
194
+ args: Record<string, unknown>;
98
195
  } | {
99
196
  type: 'tool_result';
100
197
  tool_call_id: string;
@@ -111,6 +208,9 @@ type StreamEvent = {
111
208
  interrupt_type: string;
112
209
  interrupt_payload: HitlInterruptData;
113
210
  run_id?: string;
211
+ } | {
212
+ type: 'cancelled';
213
+ run_id?: string;
114
214
  };
115
215
  /** Message to send */
116
216
  interface SendMessage {
@@ -135,7 +235,9 @@ interface ChatAdapter {
135
235
  messageLimit?: number;
136
236
  }): Promise<ConversationDetail>;
137
237
  deleteConversation(id: string): Promise<void>;
138
- chatStream(messages: SendMessage[], conversationId: string | undefined, onEvent: (event: StreamEvent) => void): Promise<{
238
+ chatStream(messages: SendMessage[], conversationId: string | undefined, onEvent: (event: StreamEvent) => void, options?: {
239
+ agentId?: string | null;
240
+ }): Promise<{
139
241
  cancel: () => void;
140
242
  }>;
141
243
  sendHitlResponse?(response: HitlResponse, runId?: string): void;
@@ -148,9 +250,81 @@ interface SdkAdapterConfig {
148
250
  /** Agent ID getter */
149
251
  getAgentId: () => string | null;
150
252
  }
253
+ /** Simple chat adapter config (no SDK knowledge required) */
254
+ interface ChatAdapterConfig {
255
+ /** App name for registration */
256
+ appName: string;
257
+ /** App version */
258
+ appVersion: string;
259
+ /** Agent ID to chat with */
260
+ agentId: string;
261
+ /** Whether to persist conversation history (default: false) */
262
+ persistHistory?: boolean;
263
+ /** Tools to expose to the agent */
264
+ tools?: Array<{
265
+ name: string;
266
+ description: string;
267
+ parameters?: Record<string, unknown>;
268
+ handler?: (args: Record<string, unknown>) => Promise<unknown>;
269
+ }>;
270
+ }
271
+ /**
272
+ * Create a chat adapter with simple config (recommended for most use cases)
273
+ *
274
+ * This is the easiest way to integrate with Sanqian. Just provide your app info
275
+ * and agent ID, and the adapter handles SDK lifecycle automatically.
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const adapter = createChatAdapter({
280
+ * appName: 'my-app',
281
+ * appVersion: '1.0.0',
282
+ * agentId: 'assistant',
283
+ * });
284
+ *
285
+ * <CompactChat adapter={adapter} />
286
+ * ```
287
+ */
288
+ declare function createChatAdapter(config: ChatAdapterConfig): ChatAdapter;
151
289
  /**
152
290
  * Create adapter that wraps @yushaw/sanqian-sdk
291
+ *
292
+ * Use this when you need more control over the SDK instance.
293
+ * For simpler usage, prefer `createChatAdapter()`.
153
294
  */
154
295
  declare function createSdkAdapter(config: SdkAdapterConfig): ChatAdapter;
155
296
 
156
- export { type ChatAdapter, type ChatMessage, type ConnectionErrorCode, type ConnectionStatus, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type MessageBlock, type MessageRole, type SdkAdapterConfig, type SendMessage, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createSdkAdapter };
297
+ /**
298
+ * Conversation history helpers
299
+ *
300
+ * Normalizes SDK conversation messages into ChatMessage[] with
301
+ * tool call blocks and merged assistant/tool rounds.
302
+ *
303
+ * NOTE: This logic is intentionally kept in sync with
304
+ * src/renderer/src/utils/chat-helpers.ts in the main app.
305
+ */
306
+
307
+ interface ApiMessage {
308
+ id?: string;
309
+ role: string;
310
+ content: string;
311
+ created_at?: string;
312
+ timestamp?: string;
313
+ tool_calls?: unknown;
314
+ toolCalls?: unknown;
315
+ tool_call_id?: string | null;
316
+ thinking?: string | null;
317
+ filePaths?: string[];
318
+ message_id?: number;
319
+ }
320
+ /**
321
+ * Parse tool_calls from backend format (OpenAI or simplified).
322
+ */
323
+ declare function parseToolCalls(toolCalls: unknown): ToolCall[] | undefined;
324
+ /**
325
+ * Merge consecutive assistant + tool messages into single assistant messages,
326
+ * preserving blocks for intermediate steps.
327
+ */
328
+ declare function mergeConsecutiveAssistantMessages(rawMessages: ApiMessage[]): ChatMessage[];
329
+
330
+ export { type ApiMessage, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, type ChatMessage, type ChatThemeMode, type ChatUiConfigSerializable, type ChatUiStrings, type ConnectionErrorCode, type ConnectionStatus, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type Locale, type MessageBlock, type MessageRole, type SdkAdapterConfig, type SendMessage, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createChatAdapter, createSdkAdapter, mergeConsecutiveAssistantMessages, parseToolCalls };