@jean2/sdk 1.0.1 → 1.0.3
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 +987 -0
- package/dist/namespaces/terminal.d.ts +1 -1
- package/dist/namespaces/terminal.d.ts.map +1 -1
- package/dist/namespaces/terminal.js +4 -2
- package/dist/namespaces/terminal.js.map +1 -1
- package/dist/rest/config.d.ts +5 -1
- package/dist/rest/config.d.ts.map +1 -1
- package/dist/rest/config.js +3 -0
- package/dist/rest/config.js.map +1 -1
- package/dist/shared-types/configuration.d.ts +7 -0
- package/dist/shared-types/configuration.d.ts.map +1 -1
- package/dist/shared-types/control.d.ts +1 -1
- package/dist/shared-types/control.d.ts.map +1 -1
- package/dist/shared-types/control.js +5 -0
- package/dist/shared-types/control.js.map +1 -1
- package/dist/shared-types/server.d.ts +1 -1
- package/dist/shared-types/server.d.ts.map +1 -1
- package/dist/transport/http.d.ts +2 -2
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +8 -4
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/websocket.d.ts +1 -1
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +4 -1
- package/dist/transport/websocket.js.map +1 -1
- package/dist/types/rest-responses.d.ts +11 -0
- package/dist/types/rest-responses.d.ts.map +1 -1
- package/dist/types/sdk-types.d.ts +1 -1
- package/dist/types/sdk-types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
# @jean2/sdk
|
|
2
|
+
|
|
3
|
+
The official TypeScript SDK for Jean2. Provides a typed client for connecting to a Jean2 server over WebSocket and REST — with zero production dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @jean2/sdk
|
|
9
|
+
# or
|
|
10
|
+
npm install @jean2/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Jean2Client } from '@jean2/sdk';
|
|
17
|
+
|
|
18
|
+
const client = new Jean2Client({
|
|
19
|
+
url: 'http://localhost:3000',
|
|
20
|
+
token: 'your-auth-token', // optional, required if server auth is enabled
|
|
21
|
+
clientDescriptor: {
|
|
22
|
+
clientId: 'my-app',
|
|
23
|
+
clientType: 'sdk', // 'desktop' | 'web' | 'extension' | 'sdk' | 'mobile'
|
|
24
|
+
displayName: 'My App',
|
|
25
|
+
interactionMode: 'headless', // 'human' | 'headless' | 'hybrid'
|
|
26
|
+
capabilities: ['chat_ui', 'ask_ui'],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Listen for events
|
|
31
|
+
client.on('connected', () => console.log('Connected!'));
|
|
32
|
+
client.on('session.created', (session) => console.log('Session:', session.id));
|
|
33
|
+
client.on('message.created', (message) => console.log('Message:', message));
|
|
34
|
+
|
|
35
|
+
// Connect
|
|
36
|
+
await client.connect();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Table of Contents
|
|
40
|
+
|
|
41
|
+
- [Client Configuration](#client-configuration)
|
|
42
|
+
- [Connection Lifecycle](#connection-lifecycle)
|
|
43
|
+
- [WebSocket Namespaces](#websocket-namespaces)
|
|
44
|
+
- [Sessions](#sessions)
|
|
45
|
+
- [Chat](#chat)
|
|
46
|
+
- [Permissions](#permissions)
|
|
47
|
+
- [Queue](#queue)
|
|
48
|
+
- [Providers](#providers)
|
|
49
|
+
- [Control](#control)
|
|
50
|
+
- [Terminal](#terminal)
|
|
51
|
+
- [REST API (http namespace)](#rest-api-http-namespace)
|
|
52
|
+
- [Sessions](#sessions-rest)
|
|
53
|
+
- [Workspaces](#workspaces)
|
|
54
|
+
- [Models](#models)
|
|
55
|
+
- [Tools](#tools)
|
|
56
|
+
- [Providers](#providers-rest)
|
|
57
|
+
- [Preconfigs](#preconfigs)
|
|
58
|
+
- [Prompts](#prompts)
|
|
59
|
+
- [Files](#files)
|
|
60
|
+
- [Attachments](#attachments)
|
|
61
|
+
- [Terminals](#terminals-rest)
|
|
62
|
+
- [MCP](#mcp)
|
|
63
|
+
- [Config](#config)
|
|
64
|
+
- [Loading All Data](#loading-all-data)
|
|
65
|
+
- [Events](#events)
|
|
66
|
+
- [Lifecycle Events](#lifecycle-events)
|
|
67
|
+
- [Session Events](#session-events)
|
|
68
|
+
- [Message Events](#message-events)
|
|
69
|
+
- [Ask Events](#ask-events)
|
|
70
|
+
- [Permission Events](#permission-events)
|
|
71
|
+
- [Queue Events](#queue-events)
|
|
72
|
+
- [Provider Events](#provider-events)
|
|
73
|
+
- [Error Events](#error-events)
|
|
74
|
+
- [Core Types](#core-types)
|
|
75
|
+
- [Session](#session-type)
|
|
76
|
+
- [Messages & Parts](#messages--parts)
|
|
77
|
+
- [Permissions](#permissions-type)
|
|
78
|
+
- [Ask Protocol](#ask-protocol-types)
|
|
79
|
+
- [Workspaces](#workspace-type)
|
|
80
|
+
- [Models & Providers](#models--providers)
|
|
81
|
+
- [Preconfigs](#preconfig-type)
|
|
82
|
+
- [MCP](#mcp-type)
|
|
83
|
+
- [Error Handling](#error-handling)
|
|
84
|
+
- [Transport Layer](#transport-layer)
|
|
85
|
+
- [HTTP Client](#http-client)
|
|
86
|
+
- [WebSocket Transport](#websocket-transport)
|
|
87
|
+
- [Utility Functions](#utility-functions)
|
|
88
|
+
- [Terminal Namespace](#terminal-namespace-detail)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Client Configuration
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface ClientConfig {
|
|
96
|
+
// Server URL (required)
|
|
97
|
+
url: string;
|
|
98
|
+
|
|
99
|
+
// Auth token for Bearer authentication (optional)
|
|
100
|
+
token?: string;
|
|
101
|
+
|
|
102
|
+
// Custom WebSocket constructor (optional, useful in Node.js)
|
|
103
|
+
wsConstructor?: typeof WebSocket;
|
|
104
|
+
|
|
105
|
+
// API base path (default: "/api")
|
|
106
|
+
apiBase?: string;
|
|
107
|
+
|
|
108
|
+
// Connection timeout in milliseconds (default: 10000)
|
|
109
|
+
connectionTimeout?: number;
|
|
110
|
+
|
|
111
|
+
// Client registration descriptor (optional)
|
|
112
|
+
clientDescriptor?: ClientDescriptor;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### ClientDescriptor
|
|
117
|
+
|
|
118
|
+
When provided, the client automatically sends a `client.register` message on connect. The server uses this for session control, ask routing, and capability checks.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
interface ClientDescriptor {
|
|
122
|
+
clientId: string;
|
|
123
|
+
clientType: 'desktop' | 'web' | 'extension' | 'sdk' | 'mobile';
|
|
124
|
+
displayName: string;
|
|
125
|
+
interactionMode: 'human' | 'headless' | 'hybrid';
|
|
126
|
+
capabilities: string[]; // e.g. ['chat_ui', 'ask_ui', 'terminal_ui']
|
|
127
|
+
instanceMetadata?: Record<string, unknown>;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Well-known capabilities:** `chat_ui`, `ask_ui`, `browser_automation`, `active_tab_read`, `tab_context`, `notifications`, `terminal_ui`, `file_picker`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Connection Lifecycle
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const client = new Jean2Client({ url: 'http://localhost:3000' });
|
|
139
|
+
|
|
140
|
+
// Connect to the server
|
|
141
|
+
await client.connect();
|
|
142
|
+
|
|
143
|
+
// Check state
|
|
144
|
+
client.state; // 'disconnected' | 'connecting' | 'connected' | 'disconnecting'
|
|
145
|
+
client.connected; // boolean
|
|
146
|
+
client.connectionId; // string | null (set after registration)
|
|
147
|
+
client.clientId; // string | null (from registered descriptor)
|
|
148
|
+
|
|
149
|
+
// Graceful disconnect
|
|
150
|
+
await client.disconnect();
|
|
151
|
+
|
|
152
|
+
// Disconnect and clean up all listeners
|
|
153
|
+
await client.dispose();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## WebSocket Namespaces
|
|
159
|
+
|
|
160
|
+
All WebSocket namespaces are accessed as properties on the `Jean2Client` instance. They send typed messages to the server and results arrive as [events](#events).
|
|
161
|
+
|
|
162
|
+
### Sessions
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
client.sessions.create({ workspaceId, preconfigId, title });
|
|
166
|
+
client.sessions.resume(sessionId);
|
|
167
|
+
client.sessions.close(sessionId);
|
|
168
|
+
client.sessions.reopen(sessionId);
|
|
169
|
+
client.sessions.delete(sessionId);
|
|
170
|
+
client.sessions.rename(sessionId, 'New Title');
|
|
171
|
+
client.sessions.update(sessionId, { preconfigId });
|
|
172
|
+
client.sessions.updateModel(sessionId, { modelId, providerId, variant });
|
|
173
|
+
client.sessions.compact(sessionId);
|
|
174
|
+
client.sessions.revert(sessionId, messageId);
|
|
175
|
+
client.sessions.fork(sessionId, messageId, title);
|
|
176
|
+
client.sessions.interrupt(sessionId, reason);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**`interrupt` reason:** `'user_request'` | `'timeout'` | `'error'`
|
|
180
|
+
|
|
181
|
+
### Chat
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
client.chat.send(sessionId, 'Hello, how are you?');
|
|
185
|
+
|
|
186
|
+
// With attachments
|
|
187
|
+
client.chat.send(sessionId, 'Check this image', {
|
|
188
|
+
attachments: [{ id: 'att-123', kind: 'image' }],
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Permissions
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
client.permissions.list(workspaceId, includeRevoked);
|
|
196
|
+
client.permissions.revoke(grantId);
|
|
197
|
+
client.permissions.revokeAll(workspaceId);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Queue
|
|
201
|
+
|
|
202
|
+
Queue messages for later sending (e.g., while a session is busy).
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
client.queue.add(sessionId, 'Follow-up message', attachments);
|
|
206
|
+
client.queue.remove(queueId);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Providers
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
client.providers.connect('anthropic');
|
|
213
|
+
client.providers.disconnect('openai');
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Control
|
|
217
|
+
|
|
218
|
+
Session control for multi-client scenarios (claim/release ownership, takeover).
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
client.control.claim(sessionId);
|
|
222
|
+
client.control.release(sessionId);
|
|
223
|
+
client.control.requestTakeover(sessionId);
|
|
224
|
+
client.control.respondTakeover(sessionId, requesterClientId, 'approve');
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## REST API (http namespace)
|
|
230
|
+
|
|
231
|
+
All REST methods return promises and are accessed via `client.http.<namespace>`.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const client = new Jean2Client({ url: 'http://localhost:3000' });
|
|
235
|
+
// No need to connect() — REST works without WebSocket
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Sessions (REST)
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// List all sessions
|
|
242
|
+
const { sessions } = await client.http.sessions.list({ status: 'active' });
|
|
243
|
+
|
|
244
|
+
// Create a session
|
|
245
|
+
const { session } = await client.http.sessions.create({
|
|
246
|
+
workspaceId: 'ws-1',
|
|
247
|
+
title: 'My Session',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Get a session with its messages
|
|
251
|
+
const { session, messages, usage, control } = await client.http.sessions.get(sessionId);
|
|
252
|
+
|
|
253
|
+
// Update session
|
|
254
|
+
await client.http.sessions.update(sessionId, { title: 'Renamed' });
|
|
255
|
+
|
|
256
|
+
// Delete session
|
|
257
|
+
await client.http.sessions.delete(sessionId);
|
|
258
|
+
|
|
259
|
+
// List messages for a session
|
|
260
|
+
const { messages } = await client.http.sessions.listMessages(sessionId);
|
|
261
|
+
|
|
262
|
+
// List sessions grouped by workspace
|
|
263
|
+
const grouped = await client.http.sessions.listGrouped({
|
|
264
|
+
workspaceIds: ['ws-1', 'ws-2'],
|
|
265
|
+
rootOnly: true,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// List sessions for a workspace
|
|
269
|
+
const { sessions } = await client.http.sessions.listByWorkspace({
|
|
270
|
+
workspaceId: 'ws-1',
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Workspaces
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const { workspaces } = await client.http.workspaces.list();
|
|
278
|
+
const { workspace } = await client.http.workspaces.create({ name: 'My Project', path: '/path' });
|
|
279
|
+
const { workspace } = await client.http.workspaces.get(workspaceId);
|
|
280
|
+
await client.http.workspaces.update(workspaceId, { name: 'Renamed' });
|
|
281
|
+
await client.http.workspaces.delete(workspaceId);
|
|
282
|
+
const { sessions } = await client.http.workspaces.listSessions(workspaceId);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Models
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const { models, defaultModel, defaultProvider } = await client.http.models.list();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Tools
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
const { tools } = await client.http.tools.list();
|
|
295
|
+
const { tool } = await client.http.tools.get('read-file');
|
|
296
|
+
const { envVars } = await client.http.tools.listEnvVars();
|
|
297
|
+
await client.http.tools.setEnvVar('API_KEY', { value: 'sk-...' });
|
|
298
|
+
await client.http.tools.clearEnvVar('API_KEY');
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Providers (REST)
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
const { providers } = await client.http.providers.list();
|
|
305
|
+
const { status } = await client.http.providers.getStatus('anthropic');
|
|
306
|
+
await client.http.providers.connect('anthropic');
|
|
307
|
+
await client.http.providers.disconnect('openai');
|
|
308
|
+
const { credentials } = await client.http.providers.listCredentials();
|
|
309
|
+
await client.http.providers.setCredential('anthropic', { apiKey: 'sk-...' });
|
|
310
|
+
await client.http.providers.clearCredential('anthropic');
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Preconfigs
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const { preconfigs } = await client.http.preconfigs.list();
|
|
317
|
+
const { preconfig } = await client.http.preconfigs.create({
|
|
318
|
+
name: 'Code Helper',
|
|
319
|
+
description: 'Helps with coding tasks',
|
|
320
|
+
systemPrompt: 'You are a helpful coding assistant.',
|
|
321
|
+
tools: ['read-file', 'write-file'],
|
|
322
|
+
model: 'claude-sonnet-4-20250514',
|
|
323
|
+
provider: 'anthropic',
|
|
324
|
+
mode: 'primary',
|
|
325
|
+
});
|
|
326
|
+
const { preconfig } = await client.http.preconfigs.get(preconfigId);
|
|
327
|
+
await client.http.preconfigs.update(preconfigId, { name: 'Updated Name' });
|
|
328
|
+
await client.http.preconfigs.delete(preconfigId);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Prompts
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const { prompts } = await client.http.prompts.list();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Files
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// Browse workspace directory
|
|
341
|
+
const { entries } = await client.http.files.browse(workspaceId, '/src', { showHidden: true });
|
|
342
|
+
|
|
343
|
+
// Search files by name
|
|
344
|
+
const { entries } = await client.http.files.search(workspaceId, '*.ts');
|
|
345
|
+
|
|
346
|
+
// Preview file content
|
|
347
|
+
const { content } = await client.http.files.preview(workspaceId, '/src/index.ts');
|
|
348
|
+
|
|
349
|
+
// Browse server filesystem
|
|
350
|
+
const { entries } = await client.http.files.browseFs('/');
|
|
351
|
+
|
|
352
|
+
// Get parent directory
|
|
353
|
+
const { parent } = await client.http.files.parent('/src/index.ts');
|
|
354
|
+
|
|
355
|
+
// List available drives (Windows)
|
|
356
|
+
const { drives } = await client.http.files.drives();
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Attachments
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const { attachments } = await client.http.attachments.list(sessionId);
|
|
363
|
+
const { attachment } = await client.http.attachments.upload(sessionId, fileBlob);
|
|
364
|
+
|
|
365
|
+
// Get attachment URL (no HTTP call — builds the URL string)
|
|
366
|
+
const url = client.http.attachments.getUrl(sessionId, attachmentId, 'thumbnail');
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Terminals (REST)
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const { sessions } = await client.http.terminals.list(workspaceId);
|
|
373
|
+
const { session } = await client.http.terminals.create(workspaceId, { cwd: '/project' });
|
|
374
|
+
const { session } = await client.http.terminals.get(workspaceId, sessionId);
|
|
375
|
+
await client.http.terminals.delete(workspaceId, sessionId);
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### MCP
|
|
379
|
+
|
|
380
|
+
Model Context Protocol integration.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const { status } = await client.http.mcp.getStatus(workspaceId);
|
|
384
|
+
await client.http.mcp.connect(workspaceId, 'my-mcp-server');
|
|
385
|
+
await client.http.mcp.disconnect(workspaceId, 'my-mcp-server');
|
|
386
|
+
await client.http.mcp.startAuth(workspaceId, 'remote-mcp');
|
|
387
|
+
await client.http.mcp.finishAuth(workspaceId, 'remote-mcp', 'auth-code');
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Config
|
|
391
|
+
|
|
392
|
+
Runtime configuration for models, providers, and prompts.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// Models config
|
|
396
|
+
const config = await client.http.config.models.get();
|
|
397
|
+
await client.http.config.models.createProvider({ id: 'my-provider', name: 'My Provider' });
|
|
398
|
+
await client.http.config.models.updateProvider('my-provider', { name: 'Updated' });
|
|
399
|
+
await client.http.config.models.deleteProvider('my-provider');
|
|
400
|
+
await client.http.config.models.createModel('my-provider', { id: 'my-model', name: 'My Model' });
|
|
401
|
+
await client.http.config.models.updateModel('my-provider', 'my-model', { name: 'Updated' });
|
|
402
|
+
await client.http.config.models.deleteModel('my-provider', 'my-model');
|
|
403
|
+
await client.http.config.models.setDefaults({ defaultModel: 'gpt-4o', defaultProvider: 'openai' });
|
|
404
|
+
|
|
405
|
+
// Prompts config
|
|
406
|
+
const { prompts } = await client.http.config.prompts.list();
|
|
407
|
+
const { prompt } = await client.http.config.prompts.get('system');
|
|
408
|
+
await client.http.config.prompts.create({ name: 'custom', content: '...' });
|
|
409
|
+
await client.http.config.prompts.update('custom', { content: 'updated' });
|
|
410
|
+
await client.http.config.prompts.delete('custom');
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Loading All Data
|
|
414
|
+
|
|
415
|
+
Fetch all initial data in a single parallel call.
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const data = await client.http.loadAll();
|
|
419
|
+
// data.workspaces, data.preconfigs, data.prompts, data.models,
|
|
420
|
+
// data.defaultModel, data.defaultProvider, data.providers
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Events
|
|
426
|
+
|
|
427
|
+
The SDK uses a typed event emitter. All event handlers receive typed arguments.
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// Add listener
|
|
431
|
+
client.on('session.created', (session) => { ... });
|
|
432
|
+
|
|
433
|
+
// One-time listener
|
|
434
|
+
client.once('connected', () => { ... });
|
|
435
|
+
|
|
436
|
+
// Remove listener
|
|
437
|
+
const handler = (session) => { ... };
|
|
438
|
+
client.on('session.created', handler);
|
|
439
|
+
client.off('session.created', handler);
|
|
440
|
+
|
|
441
|
+
// Wildcard — catches all events
|
|
442
|
+
client.on('*', (event) => {
|
|
443
|
+
console.log(event.source, event.type); // 'lifecycle' | 'server', event name
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Lifecycle Events
|
|
448
|
+
|
|
449
|
+
| Event | Args | Description |
|
|
450
|
+
|-------|------|-------------|
|
|
451
|
+
| `connected` | `()` | WebSocket connected |
|
|
452
|
+
| `disconnected` | `{ code, reason, wasClean }` | WebSocket disconnected |
|
|
453
|
+
| `error.connection` | `Error` | Connection error |
|
|
454
|
+
|
|
455
|
+
### Client Registration Events
|
|
456
|
+
|
|
457
|
+
| Event | Args | Description |
|
|
458
|
+
|-------|------|-------------|
|
|
459
|
+
| `client.registered` | `client, connectionId, serverTime` | Client registered with server |
|
|
460
|
+
| `client.rejected` | `code, message` | Registration rejected |
|
|
461
|
+
|
|
462
|
+
### Session Events
|
|
463
|
+
|
|
464
|
+
| Event | Args | Description |
|
|
465
|
+
|-------|------|-------------|
|
|
466
|
+
| `session.created` | `session` | New session created |
|
|
467
|
+
| `session.resumed` | `session, messages, usage, isRunning, control` | Session resumed with full state |
|
|
468
|
+
| `session.updated` | `session` | Session updated |
|
|
469
|
+
| `session.renamed` | `session` | Session renamed |
|
|
470
|
+
| `session.closed` | `sessionId` | Session closed |
|
|
471
|
+
| `session.reopened` | `session` | Session reopened |
|
|
472
|
+
| `session.deleted` | `sessionId` | Session deleted |
|
|
473
|
+
| `session.interrupted` | `sessionId, result` | Session interrupted |
|
|
474
|
+
| `session.reverted` | `sessionId, revertedTo, removed` | Session reverted to a message |
|
|
475
|
+
| `session.forked` | `originalSessionId, forkedSession, messages` | Session forked |
|
|
476
|
+
| `session.state` | `sessionId, messages` | Full session state snapshot |
|
|
477
|
+
| `session.control.updated` | `control, reason` | Session control changed |
|
|
478
|
+
| `session.action_rejected` | `sessionId, action, code, message, control` | Action rejected by server |
|
|
479
|
+
|
|
480
|
+
### Message Events
|
|
481
|
+
|
|
482
|
+
| Event | Args | Description |
|
|
483
|
+
|-------|------|-------------|
|
|
484
|
+
| `message.created` | `message` | New message |
|
|
485
|
+
| `message.updated` | `message` | Message updated |
|
|
486
|
+
| `part.created` | `sessionId, part` | New part (text, tool call, etc.) |
|
|
487
|
+
| `part.updated` | `sessionId, part` | Part updated (tool completed, etc.) |
|
|
488
|
+
| `part.append` | `sessionId, partId, field, delta` | Streaming text/reasoning delta |
|
|
489
|
+
|
|
490
|
+
### Chat Events
|
|
491
|
+
|
|
492
|
+
| Event | Args | Description |
|
|
493
|
+
|-------|------|-------------|
|
|
494
|
+
| `chat.usage` | `sessionId, usage, model, variant` | Token usage for a response |
|
|
495
|
+
| `compaction.complete` | `sessionId, tokensUsed` | Context compaction completed |
|
|
496
|
+
|
|
497
|
+
### Ask Events
|
|
498
|
+
|
|
499
|
+
Interactive prompts from tools (permissions, questions, forms).
|
|
500
|
+
|
|
501
|
+
| Event | Args | Description |
|
|
502
|
+
|-------|------|-------------|
|
|
503
|
+
| `ask.request` | `sessionId, toolCallId, toolName, ask, requestId, authority` | Tool is asking for user input or permission |
|
|
504
|
+
| `ask.response_rejected` | `sessionId, toolCallId, requestId, code, message` | Ask response was rejected |
|
|
505
|
+
| `ask.timeout` | `sessionId, toolCallId, requestId` | Ask request timed out |
|
|
506
|
+
| `ask.pending_sync` | `sessionId, requests` | Server synced pending ask requests |
|
|
507
|
+
|
|
508
|
+
**Responding to an ask:**
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
client.on('ask.request', (sessionId, toolCallId, toolName, ask, requestId) => {
|
|
512
|
+
if (ask.type === 'permission') {
|
|
513
|
+
// Show permission dialog
|
|
514
|
+
client.send({
|
|
515
|
+
type: 'ask.response',
|
|
516
|
+
toolCallId,
|
|
517
|
+
response: { type: 'permission', grant: 'session' }, // 'once' | 'session' | 'workspace' | 'deny'
|
|
518
|
+
requestId,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (ask.type === 'confirm') {
|
|
523
|
+
client.send({
|
|
524
|
+
type: 'ask.response',
|
|
525
|
+
toolCallId,
|
|
526
|
+
response: { type: 'confirm', value: true },
|
|
527
|
+
requestId,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Permission Events
|
|
534
|
+
|
|
535
|
+
| Event | Args | Description |
|
|
536
|
+
|-------|------|-------------|
|
|
537
|
+
| `permission.list` | `workspaceId, grants` | Permission list received |
|
|
538
|
+
| `permission.revoked` | `grantId` | A grant was revoked |
|
|
539
|
+
| `permission.all_revoked` | `workspaceId, count` | All grants revoked for workspace |
|
|
540
|
+
|
|
541
|
+
### Queue Events
|
|
542
|
+
|
|
543
|
+
| Event | Args | Description |
|
|
544
|
+
|-------|------|-------------|
|
|
545
|
+
| `queue.list` | `sessionId, messages` | Queue state received |
|
|
546
|
+
| `queue.added` | `sessionId, message` | Message added to queue |
|
|
547
|
+
| `queue.removed` | `sessionId, queueId` | Message removed from queue |
|
|
548
|
+
| `queue.sending` | `sessionId, queueId` | Queued message is being sent |
|
|
549
|
+
|
|
550
|
+
### Provider Events
|
|
551
|
+
|
|
552
|
+
| Event | Args | Description |
|
|
553
|
+
|-------|------|-------------|
|
|
554
|
+
| `provider.status` | `provider, connected, authorizationUrl, error` | Provider status update |
|
|
555
|
+
| `provider.connected` | `provider, connected, connectedAt, accountId` | Provider connection changed |
|
|
556
|
+
|
|
557
|
+
### Error Events
|
|
558
|
+
|
|
559
|
+
| Event | Args | Description |
|
|
560
|
+
|-------|------|-------------|
|
|
561
|
+
| `error` | `code, message` | Generic error |
|
|
562
|
+
| `error.rate_limit` | `code, message, retryAfterMs` | Rate limited |
|
|
563
|
+
| `error.server` | `code, message, retryAfterMs` | Server error |
|
|
564
|
+
| `error.timeout` | `code, message, retryAfterMs` | Timeout |
|
|
565
|
+
| `error.auth` | `code, message` | Authentication error |
|
|
566
|
+
| `error.invalid_request` | `code, message` | Invalid request |
|
|
567
|
+
| `error.context_overflow` | `code, message` | Context window exceeded |
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Core Types
|
|
572
|
+
|
|
573
|
+
### Session Type
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
interface Session {
|
|
577
|
+
id: string;
|
|
578
|
+
workspaceId: string;
|
|
579
|
+
preconfigId: string | null;
|
|
580
|
+
title: string | null;
|
|
581
|
+
status: 'active' | 'closed';
|
|
582
|
+
createdAt: string;
|
|
583
|
+
updatedAt: string;
|
|
584
|
+
metadata: Record<string, unknown> | null;
|
|
585
|
+
selectedModel?: string | null;
|
|
586
|
+
selectedProvider?: string | null;
|
|
587
|
+
selectedVariant?: string | null;
|
|
588
|
+
promptTokens?: number;
|
|
589
|
+
completionTokens?: number;
|
|
590
|
+
totalTokens?: number;
|
|
591
|
+
parentId: string | null;
|
|
592
|
+
agentName: string | null;
|
|
593
|
+
subagentStatus?: 'running' | 'completed' | 'error' | 'interrupted' | null;
|
|
594
|
+
runningAt?: string | null;
|
|
595
|
+
compacting?: boolean;
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Messages & Parts
|
|
600
|
+
|
|
601
|
+
Messages are composed of typed parts. A message's content arrives via streaming `part.created`, `part.updated`, and `part.append` events.
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
type MessageRole = 'user' | 'assistant' | 'system';
|
|
605
|
+
type AssistantStatus = 'streaming' | 'completed' | 'error' | 'interrupted';
|
|
606
|
+
|
|
607
|
+
interface Message {
|
|
608
|
+
id: string;
|
|
609
|
+
sessionId: string;
|
|
610
|
+
role: MessageRole;
|
|
611
|
+
createdAt: string;
|
|
612
|
+
// ...role-specific fields
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Parts
|
|
616
|
+
type Part =
|
|
617
|
+
| TextPart // { type: 'text', text: string }
|
|
618
|
+
| ReasoningPart // { type: 'reasoning', text: string }
|
|
619
|
+
| ToolPart // { type: 'tool', callId, name, state: ToolState, ... }
|
|
620
|
+
| FilePart // { type: 'file', mimeType, filename?, url }
|
|
621
|
+
| ImagePart // { type: 'image', url, mimeType? }
|
|
622
|
+
| StepPart // { type: 'step', number, status, finishReason?, tokens?, cost? }
|
|
623
|
+
| CompactionPart; // { type: 'compaction', auto, overflow? }
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Tool state** (`ToolState`) tracks the lifecycle: `pending` → `running` → `completed` | `error` | `interrupted`.
|
|
627
|
+
|
|
628
|
+
### Permissions Type
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
type PermissionRiskLevel = 'none' | 'low' | 'medium' | 'high' | 'critical';
|
|
632
|
+
type GrantScope = 'once' | 'session' | 'workspace';
|
|
633
|
+
type GrantMatcher = 'exact' | 'prefix' | 'glob' | 'shell-command';
|
|
634
|
+
type PermissionResource = 'file' | 'path' | 'directory' | 'shell-command' | 'network' | 'env' | 'clipboard' | string;
|
|
635
|
+
|
|
636
|
+
interface PermissionGrant {
|
|
637
|
+
id: string;
|
|
638
|
+
workspaceId: string;
|
|
639
|
+
toolName: string;
|
|
640
|
+
resource: PermissionResource;
|
|
641
|
+
action?: string;
|
|
642
|
+
scope: GrantScope;
|
|
643
|
+
matcher: GrantMatcher;
|
|
644
|
+
patterns: string[];
|
|
645
|
+
allowed: boolean;
|
|
646
|
+
grantedAt: string;
|
|
647
|
+
expiresAt: string | null;
|
|
648
|
+
// ...
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Ask Protocol Types
|
|
653
|
+
|
|
654
|
+
Tools interact with users through the Ask protocol — typed requests for permissions, questions, forms, and client capabilities.
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
type AskTarget = 'human' | 'client' | 'permission';
|
|
658
|
+
|
|
659
|
+
// Questions
|
|
660
|
+
interface SingleSelectQuestion { type: 'single_select'; question: string; options: Array<{ label, value, description? }>; }
|
|
661
|
+
interface MultiSelectQuestion { type: 'multi_select'; question: string; options: Array<{ label, value, description? }>; min?; max?; }
|
|
662
|
+
interface TextQuestion { type: 'text'; question: string; placeholder?; defaultValue?; }
|
|
663
|
+
interface ConfirmQuestion { type: 'confirm'; question: string; defaultValue?; }
|
|
664
|
+
|
|
665
|
+
// Ask = PermissionAsk | HumanQuestion | ClientCapabilityAsk
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Workspace Type
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
interface Workspace {
|
|
672
|
+
id: string;
|
|
673
|
+
name: string;
|
|
674
|
+
path: string;
|
|
675
|
+
isVirtual: boolean;
|
|
676
|
+
createdAt: string;
|
|
677
|
+
updatedAt: string;
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Models & Providers
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
type ModelTier = 'budget' | 'standard' | 'premium';
|
|
685
|
+
|
|
686
|
+
interface ModelDefinition {
|
|
687
|
+
id: string;
|
|
688
|
+
name: string;
|
|
689
|
+
contextWindow: number;
|
|
690
|
+
maxOutputTokens?: number;
|
|
691
|
+
tier: ModelTier;
|
|
692
|
+
variants?: Record<string, { providerOptions: Record<string, unknown> }>;
|
|
693
|
+
capabilities?: ModelCapabilities;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
interface ProviderDefinition {
|
|
697
|
+
id: string;
|
|
698
|
+
name: string;
|
|
699
|
+
models: ModelDefinition[];
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Preconfig Type
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
type PreconfigMode = 'primary' | 'subagent' | 'both';
|
|
707
|
+
|
|
708
|
+
interface Preconfig {
|
|
709
|
+
id: string;
|
|
710
|
+
name: string;
|
|
711
|
+
description: string;
|
|
712
|
+
systemPrompt: string;
|
|
713
|
+
tools: string[] | null;
|
|
714
|
+
model: string | null;
|
|
715
|
+
provider: string | null;
|
|
716
|
+
variant?: string | null;
|
|
717
|
+
settings: Record<string, unknown> | null;
|
|
718
|
+
isDefault: boolean;
|
|
719
|
+
mode?: PreconfigMode;
|
|
720
|
+
canSpawnSubagents?: boolean | string[] | null;
|
|
721
|
+
skills?: string[] | null;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### MCP Type
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
type McpServerType = 'local' | 'remote';
|
|
729
|
+
|
|
730
|
+
// Local: stdio-based MCP server
|
|
731
|
+
interface McpLocalServerConfig {
|
|
732
|
+
type: 'local';
|
|
733
|
+
command: string[];
|
|
734
|
+
env?: Record<string, string>;
|
|
735
|
+
timeout?: number;
|
|
736
|
+
enabled?: boolean;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Remote: HTTP-based MCP server with optional OAuth
|
|
740
|
+
interface McpRemoteServerConfig {
|
|
741
|
+
type: 'remote';
|
|
742
|
+
url: string;
|
|
743
|
+
oauth?: boolean | McpOAuthConfig;
|
|
744
|
+
headers?: Record<string, string>;
|
|
745
|
+
timeout?: number;
|
|
746
|
+
enabled?: boolean;
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## Error Handling
|
|
753
|
+
|
|
754
|
+
The SDK exports a hierarchy of error classes:
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
import { Jean2Error, ConnectionError, AuthError, RateLimitError, TimeoutError, ServerError, ValidationError } from '@jean2/sdk';
|
|
758
|
+
|
|
759
|
+
try {
|
|
760
|
+
await client.connect();
|
|
761
|
+
} catch (err) {
|
|
762
|
+
if (err instanceof ConnectionError) {
|
|
763
|
+
// WebSocket connection failed
|
|
764
|
+
} else if (err instanceof AuthError) {
|
|
765
|
+
// Authentication failed
|
|
766
|
+
} else if (err instanceof RateLimitError) {
|
|
767
|
+
// Rate limited — check err.retryAfterMs
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
| Error | Description |
|
|
773
|
+
|-------|-------------|
|
|
774
|
+
| `Jean2Error` | Base error class for all SDK errors |
|
|
775
|
+
| `ConnectionError` | WebSocket connection failures |
|
|
776
|
+
| `AuthError` | Authentication failures |
|
|
777
|
+
| `RateLimitError` | Rate limited (`retryAfterMs` property) |
|
|
778
|
+
| `TimeoutError` | Request timeouts |
|
|
779
|
+
| `ServerError` | Server-side errors (`statusCode` property) |
|
|
780
|
+
| `ValidationError` | Invalid input (`statusCode` property, default 400) |
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
## Transport Layer
|
|
785
|
+
|
|
786
|
+
The SDK uses two transport layers — you don't normally need to interact with them directly.
|
|
787
|
+
|
|
788
|
+
### HTTP Client
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
const httpClient = client.httpClient;
|
|
792
|
+
|
|
793
|
+
// Generic request
|
|
794
|
+
const data = await httpClient.request<MyType>('/sessions', { method: 'POST', body: { ... } });
|
|
795
|
+
|
|
796
|
+
// Convenience methods
|
|
797
|
+
await httpClient.get('/sessions');
|
|
798
|
+
await httpClient.post('/sessions', { title: 'New' });
|
|
799
|
+
await httpClient.put('/sessions/123', { title: 'Updated' });
|
|
800
|
+
await httpClient.patch('/sessions/123', { title: 'Updated' });
|
|
801
|
+
await httpClient.delete('/sessions/123');
|
|
802
|
+
|
|
803
|
+
// Verify token before connecting
|
|
804
|
+
const valid = await HttpClient.verifyToken('http://localhost:3000', 'my-token');
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
### WebSocket Transport
|
|
808
|
+
|
|
809
|
+
The WebSocket transport handles heartbeat (ping/pong every 30s) and automatic message serialization.
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
// Transport is internal — use client.connect() / client.disconnect() instead
|
|
813
|
+
// Access the raw WebSocket if needed:
|
|
814
|
+
client.ws; // WebSocket | null
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
---
|
|
818
|
+
|
|
819
|
+
## Utility Functions
|
|
820
|
+
|
|
821
|
+
### Type Guards
|
|
822
|
+
|
|
823
|
+
```typescript
|
|
824
|
+
import { isTextPart, isToolPart, isReasoningPart, isStepPart, isImagePart, isFilePart, isCompactionPart, isAssistantMessage, isUserMessage } from '@jean2/sdk';
|
|
825
|
+
|
|
826
|
+
if (isToolPart(part)) {
|
|
827
|
+
console.log(part.callId, part.name, part.state);
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
### Shell Permission Helpers
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
import {
|
|
835
|
+
SHELL_DANGEROUS_COMMANDS,
|
|
836
|
+
SHELL_FILESYSTEM_COMMANDS,
|
|
837
|
+
SENSITIVE_FILE_PATTERNS,
|
|
838
|
+
splitShellCommandSegments,
|
|
839
|
+
createShellPermissionAskStructured,
|
|
840
|
+
analyzeShellCommandEffects,
|
|
841
|
+
} from '@jean2/sdk';
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
## Terminal Namespace (Detail)
|
|
847
|
+
|
|
848
|
+
The terminal namespace manages interactive terminal sessions via dedicated WebSocket connections (separate from the main connection).
|
|
849
|
+
|
|
850
|
+
### Connecting to a Terminal
|
|
851
|
+
|
|
852
|
+
```typescript
|
|
853
|
+
const termConn = await client.terminal.connect({
|
|
854
|
+
workspaceId: 'ws-1',
|
|
855
|
+
cwd: '/project',
|
|
856
|
+
shell: '/bin/bash', // optional
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// TerminalConnection extends TypedEventEmitter
|
|
860
|
+
termConn.on('output', (data: Uint8Array) => {
|
|
861
|
+
process.stdout.write(data);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
termConn.on('exit', (exitCode: number) => {
|
|
865
|
+
console.log('Exited:', exitCode);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
termConn.on('title', (title: string) => {
|
|
869
|
+
console.log('Title:', title);
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// Write to terminal
|
|
873
|
+
termConn.write('ls -la\n');
|
|
874
|
+
termConn.write(new Uint8Array([0x03])); // Ctrl+C
|
|
875
|
+
|
|
876
|
+
// Resize
|
|
877
|
+
termConn.resize(120, 40);
|
|
878
|
+
|
|
879
|
+
// Close
|
|
880
|
+
termConn.close();
|
|
881
|
+
|
|
882
|
+
// Properties
|
|
883
|
+
termConn.sessionId; // string
|
|
884
|
+
termConn.pid; // number
|
|
885
|
+
termConn.cwd; // string
|
|
886
|
+
termConn.shell; // string
|
|
887
|
+
termConn.cols; // number
|
|
888
|
+
termConn.rows; // number
|
|
889
|
+
termConn.title; // string
|
|
890
|
+
termConn.status; // 'running' | 'exited'
|
|
891
|
+
termConn.exitCode; // number | null
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### Subscribing to Terminal Events
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
const { conn, initialSessions } = await client.terminal.subscribeEvents('ws-1');
|
|
898
|
+
|
|
899
|
+
conn.on('created', (session) => { ... });
|
|
900
|
+
conn.on('destroyed', (sessionId) => { ... });
|
|
901
|
+
conn.on('exited', (sessionId, exitCode) => { ... });
|
|
902
|
+
conn.on('title_changed', (sessionId, title) => { ... });
|
|
903
|
+
conn.on('status_changed', (sessionId, status) => { ... });
|
|
904
|
+
conn.on('snapshot', (sessions) => { ... });
|
|
905
|
+
|
|
906
|
+
conn.close();
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
---
|
|
910
|
+
|
|
911
|
+
## Full Example: Headless Chat Bot
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
import { Jean2Client } from '@jean2/sdk';
|
|
915
|
+
|
|
916
|
+
const client = new Jean2Client({
|
|
917
|
+
url: 'http://localhost:3000',
|
|
918
|
+
token: process.env.JEAN2_TOKEN,
|
|
919
|
+
clientDescriptor: {
|
|
920
|
+
clientId: 'bot',
|
|
921
|
+
clientType: 'sdk',
|
|
922
|
+
displayName: 'Headless Bot',
|
|
923
|
+
interactionMode: 'headless',
|
|
924
|
+
capabilities: [],
|
|
925
|
+
},
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
// Track assistant responses
|
|
929
|
+
let currentText = '';
|
|
930
|
+
|
|
931
|
+
client.on('connected', async () => {
|
|
932
|
+
console.log('Connected to Jean2 server');
|
|
933
|
+
|
|
934
|
+
// Create a session via REST
|
|
935
|
+
const { session } = await client.http.sessions.create({
|
|
936
|
+
workspaceId: 'default',
|
|
937
|
+
title: 'Bot Session',
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// Resume it over WebSocket to receive events
|
|
941
|
+
client.sessions.resume(session.id);
|
|
942
|
+
|
|
943
|
+
// Send a message
|
|
944
|
+
client.chat.send(session.id, 'Explain TypeScript generics in one paragraph.');
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
client.on('part.created', (sessionId, part) => {
|
|
948
|
+
if (part.type === 'text') {
|
|
949
|
+
currentText = part.text;
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
client.on('part.append', (sessionId, partId, field, delta) => {
|
|
954
|
+
process.stdout.write(delta);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
client.on('part.updated', (sessionId, part) => {
|
|
958
|
+
if (part.type === 'tool') {
|
|
959
|
+
console.log(`Tool ${part.name}: ${part.state}`);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
client.on('ask.request', (sessionId, toolCallId, toolName, ask, requestId) => {
|
|
964
|
+
// Auto-grant all permissions in headless mode
|
|
965
|
+
if (ask.type === 'permission') {
|
|
966
|
+
client.send({
|
|
967
|
+
type: 'ask.response',
|
|
968
|
+
toolCallId,
|
|
969
|
+
response: { type: 'permission', grant: 'session' },
|
|
970
|
+
requestId,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
client.on('error.connection', (err) => {
|
|
976
|
+
console.error('Connection error:', err);
|
|
977
|
+
process.exit(1);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
await client.connect();
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
---
|
|
984
|
+
|
|
985
|
+
## License
|
|
986
|
+
|
|
987
|
+
Private — part of the Jean2 monorepo.
|