@bytespell/amux-client 0.0.19

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 (43) hide show
  1. package/dist/accumulator.d.ts +77 -0
  2. package/dist/accumulator.d.ts.map +1 -0
  3. package/dist/accumulator.js +285 -0
  4. package/dist/accumulator.js.map +1 -0
  5. package/dist/client.d.ts +155 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +364 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/connection.d.ts +81 -0
  10. package/dist/connection.d.ts.map +1 -0
  11. package/dist/connection.js +143 -0
  12. package/dist/connection.js.map +1 -0
  13. package/dist/index.d.ts +35 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +34 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/react/context.d.ts +60 -0
  18. package/dist/react/context.d.ts.map +1 -0
  19. package/dist/react/context.js +138 -0
  20. package/dist/react/context.js.map +1 -0
  21. package/dist/react/hooks.d.ts +153 -0
  22. package/dist/react/hooks.d.ts.map +1 -0
  23. package/dist/react/hooks.js +156 -0
  24. package/dist/react/hooks.js.map +1 -0
  25. package/dist/react/index.d.ts +29 -0
  26. package/dist/react/index.d.ts.map +1 -0
  27. package/dist/react/index.js +27 -0
  28. package/dist/react/index.js.map +1 -0
  29. package/dist/types.d.ts +166 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +2 -0
  32. package/dist/types.js.map +1 -0
  33. package/package.json +48 -0
  34. package/src/accumulator.ts +307 -0
  35. package/src/client.ts +445 -0
  36. package/src/connection.ts +194 -0
  37. package/src/index.ts +59 -0
  38. package/src/react/context.tsx +200 -0
  39. package/src/react/hooks.ts +208 -0
  40. package/src/react/index.ts +37 -0
  41. package/src/types.ts +120 -0
  42. package/tsconfig.json +14 -0
  43. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,166 @@
1
+ import type { SessionUpdate } from '@bytespell/amux-types';
2
+ export type { SessionUpdate };
3
+ /**
4
+ * Content block types for accumulated messages
5
+ */
6
+ export type ContentBlock = {
7
+ type: 'text';
8
+ text: string;
9
+ } | {
10
+ type: 'thinking';
11
+ text: string;
12
+ } | {
13
+ type: 'tool_use';
14
+ id: string;
15
+ name: string;
16
+ input: unknown;
17
+ } | {
18
+ type: 'tool_result';
19
+ toolUseId: string;
20
+ content: unknown;
21
+ isError?: boolean;
22
+ } | {
23
+ type: 'diff';
24
+ path: string;
25
+ oldText: string;
26
+ newText: string;
27
+ } | {
28
+ type: 'image';
29
+ source: {
30
+ type: 'base64';
31
+ mediaType: string;
32
+ data: string;
33
+ };
34
+ };
35
+ /**
36
+ * Accumulated message
37
+ */
38
+ export interface Message {
39
+ id: string;
40
+ role: 'user' | 'assistant';
41
+ content: ContentBlock[];
42
+ status: 'streaming' | 'complete';
43
+ timestamp: number;
44
+ }
45
+ /**
46
+ * Tool call state tracking
47
+ */
48
+ export interface ToolCallState {
49
+ id: string;
50
+ name: string;
51
+ input: unknown;
52
+ status: 'pending' | 'running' | 'complete' | 'error';
53
+ result?: unknown;
54
+ error?: string;
55
+ }
56
+ /**
57
+ * Turn state - groups user message and assistant response
58
+ */
59
+ export interface Turn {
60
+ id: string;
61
+ userMessage: Message | null;
62
+ assistantMessage: Message | null;
63
+ toolCalls: ToolCallState[];
64
+ status: 'idle' | 'streaming' | 'awaiting_permission' | 'complete';
65
+ }
66
+ /**
67
+ * Connection status
68
+ */
69
+ export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'ready';
70
+ /**
71
+ * Permission option (from server)
72
+ */
73
+ export interface PermissionOptionData {
74
+ id?: string;
75
+ optionId?: string;
76
+ label?: string;
77
+ title?: string;
78
+ description?: string;
79
+ }
80
+ /**
81
+ * Client event types
82
+ */
83
+ export interface AmuxClientEvents {
84
+ ready: {
85
+ cwd: string;
86
+ sessionId: string | null;
87
+ capabilities: unknown;
88
+ agent: {
89
+ type: string;
90
+ name: string;
91
+ };
92
+ availableAgents: Array<{
93
+ id: string;
94
+ name: string;
95
+ }>;
96
+ availableModels?: Array<{
97
+ id: string;
98
+ name: string;
99
+ provider?: string;
100
+ supportsThinking?: boolean;
101
+ }>;
102
+ currentModelId?: string;
103
+ };
104
+ connecting: Record<string, never>;
105
+ update: SessionUpdate;
106
+ turn_start: Record<string, never>;
107
+ turn_end: Record<string, never>;
108
+ permission_request: {
109
+ requestId: string;
110
+ toolCall?: unknown;
111
+ options: PermissionOptionData[];
112
+ };
113
+ prompt_complete: {
114
+ stopReason?: string;
115
+ };
116
+ session_created: {
117
+ sessionId: string | null;
118
+ };
119
+ session_switched: {
120
+ sessionId: string;
121
+ };
122
+ history_replay: {
123
+ previousSessionId: string;
124
+ events: unknown[];
125
+ eventCount: number;
126
+ };
127
+ history: {
128
+ events: unknown[];
129
+ sessionId: string | null;
130
+ };
131
+ sessions: {
132
+ sessions: unknown[];
133
+ };
134
+ error: {
135
+ message: string;
136
+ };
137
+ agent_exit: {
138
+ code: number | null;
139
+ signal: string | null;
140
+ };
141
+ connection_status: {
142
+ status: ConnectionStatus;
143
+ };
144
+ messages_updated: {
145
+ messages: Message[];
146
+ };
147
+ turns_updated: {
148
+ turns: Turn[];
149
+ };
150
+ }
151
+ /**
152
+ * Options for creating an AmuxClient
153
+ */
154
+ export interface AmuxClientOptions {
155
+ /** WebSocket URL to connect to */
156
+ url: string;
157
+ /** Auto-connect on creation (default: true) */
158
+ autoConnect?: boolean;
159
+ /** Auto-reconnect on disconnect (default: true) */
160
+ reconnect?: boolean;
161
+ /** Reconnect interval in ms (default: 1000) */
162
+ reconnectInterval?: number;
163
+ /** Max reconnect attempts (default: Infinity) */
164
+ maxReconnectAttempts?: number;
165
+ }
166
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@bytespell/amux-client",
3
+ "version": "0.0.19",
4
+ "description": "Client library for consuming amux over WebSocket",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./react": {
14
+ "types": "./dist/react/index.d.ts",
15
+ "import": "./dist/react/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "amux",
25
+ "client",
26
+ "websocket",
27
+ "acp",
28
+ "react"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@bytespell/amux-types": "*"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "^18.0.0 || ^19.0.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "react": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@types/react": "^18.0.0",
45
+ "react": "^18.0.0",
46
+ "typescript": "~5.7.0"
47
+ }
48
+ }
@@ -0,0 +1,307 @@
1
+ import type { SessionUpdate } from '@bytespell/amux-types';
2
+ import type { Message, Turn, ToolCallState } from './types.js';
3
+
4
+ /**
5
+ * Generate a unique ID
6
+ */
7
+ function generateId(): string {
8
+ return Math.random().toString(36).substring(2, 15);
9
+ }
10
+
11
+ /**
12
+ * Message and Turn accumulator
13
+ *
14
+ * Processes streaming session updates and builds structured conversation data.
15
+ */
16
+ export class Accumulator {
17
+ private _messages: Message[] = [];
18
+ private _turns: Turn[] = [];
19
+ private _currentTurn: Turn | null = null;
20
+
21
+ /**
22
+ * All accumulated messages
23
+ */
24
+ get messages(): Message[] {
25
+ return this._messages;
26
+ }
27
+
28
+ /**
29
+ * All turns (grouped user/assistant pairs)
30
+ */
31
+ get turns(): Turn[] {
32
+ return this._turns;
33
+ }
34
+
35
+ /**
36
+ * Current active turn (if streaming)
37
+ */
38
+ get currentTurn(): Turn | null {
39
+ return this._currentTurn;
40
+ }
41
+
42
+ /**
43
+ * Last user message
44
+ */
45
+ get lastUserMessage(): Message | null {
46
+ for (let i = this._messages.length - 1; i >= 0; i--) {
47
+ if (this._messages[i]?.role === 'user') {
48
+ return this._messages[i] ?? null;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Last assistant message
56
+ */
57
+ get lastAssistantMessage(): Message | null {
58
+ for (let i = this._messages.length - 1; i >= 0; i--) {
59
+ if (this._messages[i]?.role === 'assistant') {
60
+ return this._messages[i] ?? null;
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Start a new turn
68
+ */
69
+ startTurn(): void {
70
+ this._currentTurn = {
71
+ id: generateId(),
72
+ userMessage: null,
73
+ assistantMessage: null,
74
+ toolCalls: [],
75
+ status: 'streaming',
76
+ };
77
+ this._turns.push(this._currentTurn);
78
+ }
79
+
80
+ /**
81
+ * End the current turn
82
+ */
83
+ endTurn(): void {
84
+ if (this._currentTurn) {
85
+ this._currentTurn.status = 'complete';
86
+ if (this._currentTurn.userMessage) {
87
+ this._currentTurn.userMessage.status = 'complete';
88
+ }
89
+ if (this._currentTurn.assistantMessage) {
90
+ this._currentTurn.assistantMessage.status = 'complete';
91
+ }
92
+ this._currentTurn = null;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Set turn status to awaiting permission
98
+ */
99
+ setAwaitingPermission(): void {
100
+ if (this._currentTurn) {
101
+ this._currentTurn.status = 'awaiting_permission';
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Resume streaming after permission
107
+ */
108
+ resumeStreaming(): void {
109
+ if (this._currentTurn && this._currentTurn.status === 'awaiting_permission') {
110
+ this._currentTurn.status = 'streaming';
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Process a session update
116
+ */
117
+ processUpdate(update: SessionUpdate): void {
118
+ const updateType = update.sessionUpdate;
119
+
120
+ switch (updateType) {
121
+ case 'user_message_chunk':
122
+ this.processUserChunk(update);
123
+ break;
124
+ case 'agent_message_chunk':
125
+ this.processAssistantChunk(update);
126
+ break;
127
+ case 'tool_call':
128
+ this.processToolCall(update);
129
+ break;
130
+ case 'tool_call_update':
131
+ this.processToolCallUpdate(update);
132
+ break;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Process user message chunk
138
+ */
139
+ private processUserChunk(update: SessionUpdate): void {
140
+ if (update.sessionUpdate !== 'user_message_chunk') return;
141
+
142
+ // Create or get current turn
143
+ if (!this._currentTurn) {
144
+ this.startTurn();
145
+ }
146
+
147
+ // Create user message if needed
148
+ if (!this._currentTurn!.userMessage) {
149
+ const userMessage: Message = {
150
+ id: generateId(),
151
+ role: 'user',
152
+ content: [],
153
+ status: 'streaming',
154
+ timestamp: Date.now(),
155
+ };
156
+ this._currentTurn!.userMessage = userMessage;
157
+ this._messages.push(userMessage);
158
+ }
159
+
160
+ // Add content
161
+ const content = update.content as { type: string; text?: string };
162
+ if (content?.type === 'text' && content.text) {
163
+ const lastBlock = this._currentTurn!.userMessage!.content[
164
+ this._currentTurn!.userMessage!.content.length - 1
165
+ ];
166
+ if (lastBlock?.type === 'text') {
167
+ lastBlock.text += content.text;
168
+ } else {
169
+ this._currentTurn!.userMessage!.content.push({
170
+ type: 'text',
171
+ text: content.text,
172
+ });
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Process assistant message chunk
179
+ */
180
+ private processAssistantChunk(update: SessionUpdate): void {
181
+ if (update.sessionUpdate !== 'agent_message_chunk') return;
182
+
183
+ // Create turn if needed
184
+ if (!this._currentTurn) {
185
+ this.startTurn();
186
+ }
187
+
188
+ // Create assistant message if needed
189
+ if (!this._currentTurn!.assistantMessage) {
190
+ const assistantMessage: Message = {
191
+ id: generateId(),
192
+ role: 'assistant',
193
+ content: [],
194
+ status: 'streaming',
195
+ timestamp: Date.now(),
196
+ };
197
+ this._currentTurn!.assistantMessage = assistantMessage;
198
+ this._messages.push(assistantMessage);
199
+ }
200
+
201
+ // Add content based on type
202
+ const content = update.content as { type: string; text?: string; [key: string]: unknown };
203
+ if (!content) return;
204
+
205
+ const assistantContent = this._currentTurn!.assistantMessage!.content;
206
+
207
+ if (content.type === 'text' && content.text) {
208
+ const lastBlock = assistantContent[assistantContent.length - 1];
209
+ if (lastBlock?.type === 'text') {
210
+ lastBlock.text += content.text;
211
+ } else {
212
+ assistantContent.push({ type: 'text', text: content.text });
213
+ }
214
+ } else if (content.type === 'thinking' && content.text) {
215
+ const lastBlock = assistantContent[assistantContent.length - 1];
216
+ if (lastBlock?.type === 'thinking') {
217
+ lastBlock.text += content.text;
218
+ } else {
219
+ assistantContent.push({ type: 'thinking', text: content.text });
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Process tool call
226
+ */
227
+ private processToolCall(update: SessionUpdate): void {
228
+ if (update.sessionUpdate !== 'tool_call') return;
229
+
230
+ if (!this._currentTurn) return;
231
+
232
+ const toolCall: ToolCallState = {
233
+ id: (update as { toolCallId?: string }).toolCallId ?? generateId(),
234
+ name: (update as { title?: string }).title ?? 'unknown',
235
+ input: (update as { input?: unknown }).input,
236
+ status: 'pending',
237
+ };
238
+
239
+ this._currentTurn.toolCalls.push(toolCall);
240
+
241
+ // Also add to assistant message content
242
+ if (this._currentTurn.assistantMessage) {
243
+ this._currentTurn.assistantMessage.content.push({
244
+ type: 'tool_use',
245
+ id: toolCall.id,
246
+ name: toolCall.name,
247
+ input: toolCall.input,
248
+ });
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Process tool call update
254
+ */
255
+ private processToolCallUpdate(update: SessionUpdate): void {
256
+ if (update.sessionUpdate !== 'tool_call_update') return;
257
+
258
+ if (!this._currentTurn) return;
259
+
260
+ const toolCallId = (update as { toolCallId?: string }).toolCallId;
261
+ if (!toolCallId) return;
262
+
263
+ const toolCall = this._currentTurn.toolCalls.find((tc) => tc.id === toolCallId);
264
+ if (!toolCall) return;
265
+
266
+ const status = (update as { status?: string }).status;
267
+ if (status === 'running') {
268
+ toolCall.status = 'running';
269
+ } else if (status === 'completed') {
270
+ toolCall.status = 'complete';
271
+ toolCall.result = (update as { output?: unknown }).output;
272
+ } else if (status === 'error') {
273
+ toolCall.status = 'error';
274
+ toolCall.error = (update as { error?: string }).error;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Replay history events
280
+ */
281
+ replayHistory(events: unknown[]): void {
282
+ // Clear current state
283
+ this._messages = [];
284
+ this._turns = [];
285
+ this._currentTurn = null;
286
+
287
+ for (const event of events) {
288
+ const e = event as { type: string; update?: SessionUpdate };
289
+ if (e.type === 'turn_start') {
290
+ this.startTurn();
291
+ } else if (e.type === 'turn_end') {
292
+ this.endTurn();
293
+ } else if (e.type === 'session_update' && e.update) {
294
+ this.processUpdate(e.update);
295
+ }
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Clear all accumulated data
301
+ */
302
+ clear(): void {
303
+ this._messages = [];
304
+ this._turns = [];
305
+ this._currentTurn = null;
306
+ }
307
+ }