indusagi-coding-agent 0.1.28 → 0.1.30
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/CHANGELOG.md +23 -0
- package/LICENSE.md +22 -0
- package/README.md +2 -0
- package/dist/core/messages.d.ts +1 -76
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +1 -122
- package/dist/core/messages.js.map +1 -1
- package/dist/core/session-manager.d.ts +1 -447
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1203
- package/dist/core/session-manager.js.map +1 -1
- package/package.json +2 -2
- package/docs/COMPLETE-GUIDE.md +0 -300
- package/docs/COMPREHENSIVE-CLI-SUMMARY.md +0 -900
- package/docs/MODES-ARCHITECTURE.md +0 -565
- package/docs/PRINT-MODE-GUIDE.md +0 -456
- package/docs/README.md +0 -78
- package/docs/RPC-GUIDE.md +0 -705
- package/docs/UTILS-IMPLEMENTATION-SUMMARY.md +0 -647
- package/docs/UTILS-MODULE-OVERVIEW.md +0 -1480
- package/docs/UTILS-QA-CHECKLIST.md +0 -1061
- package/docs/UTILS-USAGE-GUIDE.md +0 -1419
- package/docs/compaction.md +0 -390
- package/docs/custom-provider.md +0 -538
- package/docs/development.md +0 -69
- package/docs/extensions.md +0 -1733
- package/docs/hooks.md +0 -378
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +0 -79
- package/docs/keybindings.md +0 -162
- package/docs/models.md +0 -193
- package/docs/packages.md +0 -163
- package/docs/prompt-templates.md +0 -67
- package/docs/providers.md +0 -147
- package/docs/rpc.md +0 -1048
- package/docs/sdk.md +0 -969
- package/docs/session.md +0 -412
- package/docs/settings.md +0 -219
- package/docs/shell-aliases.md +0 -13
- package/docs/skills.md +0 -226
- package/docs/subagents.md +0 -225
- package/docs/terminal-setup.md +0 -65
- package/docs/themes.md +0 -295
- package/docs/tree.md +0 -219
- package/docs/tui.md +0 -887
- package/docs/web-tools.md +0 -304
- package/docs/windows.md +0 -17
- package/examples/README.md +0 -25
- package/examples/extensions/README.md +0 -192
- package/examples/extensions/antigravity-image-gen.ts +0 -414
- package/examples/extensions/auto-commit-on-exit.ts +0 -49
- package/examples/extensions/bookmark.ts +0 -50
- package/examples/extensions/claude-rules.ts +0 -86
- package/examples/extensions/confirm-destructive.ts +0 -59
- package/examples/extensions/custom-compaction.ts +0 -115
- package/examples/extensions/custom-footer.ts +0 -65
- package/examples/extensions/custom-header.ts +0 -73
- package/examples/extensions/custom-provider-anthropic/index.ts +0 -605
- package/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
- package/examples/extensions/custom-provider-anthropic/package.json +0 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -350
- package/examples/extensions/custom-provider-gitlab-duo/package.json +0 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +0 -83
- package/examples/extensions/dirty-repo-guard.ts +0 -56
- package/examples/extensions/doom-overlay/README.md +0 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +0 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +0 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +0 -72
- package/examples/extensions/doom-overlay/doom-component.ts +0 -133
- package/examples/extensions/doom-overlay/doom-engine.ts +0 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +0 -105
- package/examples/extensions/doom-overlay/index.ts +0 -74
- package/examples/extensions/doom-overlay/wad-finder.ts +0 -51
- package/examples/extensions/event-bus.ts +0 -43
- package/examples/extensions/file-trigger.ts +0 -41
- package/examples/extensions/git-checkpoint.ts +0 -53
- package/examples/extensions/handoff.ts +0 -151
- package/examples/extensions/hello.ts +0 -25
- package/examples/extensions/inline-bash.ts +0 -94
- package/examples/extensions/input-transform.ts +0 -43
- package/examples/extensions/interactive-shell.ts +0 -196
- package/examples/extensions/mac-system-theme.ts +0 -47
- package/examples/extensions/message-renderer.ts +0 -60
- package/examples/extensions/modal-editor.ts +0 -86
- package/examples/extensions/model-status.ts +0 -31
- package/examples/extensions/notify.ts +0 -25
- package/examples/extensions/overlay-qa-tests.ts +0 -882
- package/examples/extensions/overlay-test.ts +0 -151
- package/examples/extensions/permission-gate.ts +0 -34
- package/examples/extensions/pirate.ts +0 -47
- package/examples/extensions/plan-mode/README.md +0 -65
- package/examples/extensions/plan-mode/index.ts +0 -341
- package/examples/extensions/plan-mode/utils.ts +0 -168
- package/examples/extensions/preset.ts +0 -399
- package/examples/extensions/protected-paths.ts +0 -30
- package/examples/extensions/qna.ts +0 -120
- package/examples/extensions/question.ts +0 -265
- package/examples/extensions/questionnaire.ts +0 -428
- package/examples/extensions/rainbow-editor.ts +0 -88
- package/examples/extensions/sandbox/index.ts +0 -318
- package/examples/extensions/sandbox/package-lock.json +0 -92
- package/examples/extensions/sandbox/package.json +0 -19
- package/examples/extensions/send-user-message.ts +0 -97
- package/examples/extensions/session-name.ts +0 -27
- package/examples/extensions/shutdown-command.ts +0 -63
- package/examples/extensions/snake.ts +0 -344
- package/examples/extensions/space-invaders.ts +0 -561
- package/examples/extensions/ssh.ts +0 -220
- package/examples/extensions/status-line.ts +0 -40
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -127
- package/examples/extensions/subagent/index.ts +0 -964
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/examples/extensions/summarize.ts +0 -196
- package/examples/extensions/timed-confirm.ts +0 -70
- package/examples/extensions/todo.ts +0 -300
- package/examples/extensions/tool-override.ts +0 -144
- package/examples/extensions/tools.ts +0 -147
- package/examples/extensions/trigger-compact.ts +0 -40
- package/examples/extensions/truncated-tool.ts +0 -193
- package/examples/extensions/widget-placement.ts +0 -17
- package/examples/extensions/with-deps/index.ts +0 -36
- package/examples/extensions/with-deps/package-lock.json +0 -31
- package/examples/extensions/with-deps/package.json +0 -22
- package/examples/sdk/01-minimal.ts +0 -22
- package/examples/sdk/02-custom-model.ts +0 -50
- package/examples/sdk/03-custom-prompt.ts +0 -55
- package/examples/sdk/04-skills.ts +0 -46
- package/examples/sdk/05-tools.ts +0 -56
- package/examples/sdk/06-extensions.ts +0 -88
- package/examples/sdk/07-context-files.ts +0 -40
- package/examples/sdk/08-prompt-templates.ts +0 -47
- package/examples/sdk/09-api-keys-and-oauth.ts +0 -48
- package/examples/sdk/10-settings.ts +0 -38
- package/examples/sdk/11-sessions.ts +0 -48
- package/examples/sdk/12-full-control.ts +0 -82
- package/examples/sdk/13-codex-oauth.ts +0 -37
- package/examples/sdk/README.md +0 -144
package/docs/RPC-GUIDE.md
DELETED
|
@@ -1,705 +0,0 @@
|
|
|
1
|
-
# RPC Mode Complete Guide
|
|
2
|
-
|
|
3
|
-
Comprehensive documentation for programmatic access via JSON-RPC 2.0 protocol.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## RPC Authentication & Security
|
|
8
|
-
|
|
9
|
-
### Authentication Methods
|
|
10
|
-
|
|
11
|
-
**1. Token-Based Authentication**
|
|
12
|
-
```json
|
|
13
|
-
{
|
|
14
|
-
"id": "req-1",
|
|
15
|
-
"method": "prompt",
|
|
16
|
-
"params": {
|
|
17
|
-
"message": "What is 2+2?",
|
|
18
|
-
"token": "your_secret_token_here"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Token Generation:**
|
|
24
|
-
```bash
|
|
25
|
-
# Generate secure random token
|
|
26
|
-
openssl rand -hex 32
|
|
27
|
-
# Output: a1f2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
|
28
|
-
|
|
29
|
-
# Store in config
|
|
30
|
-
indusagi config set rpc-token a1f2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**2. Environment Variable**
|
|
34
|
-
```bash
|
|
35
|
-
export INDUSAGI_RPC_TOKEN="your_secret_token"
|
|
36
|
-
indusagi --mode=rpc
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**3. File-Based Token**
|
|
40
|
-
```bash
|
|
41
|
-
# Token stored in ~/.indusagi/rpc-token
|
|
42
|
-
mkdir -p ~/.indusagi
|
|
43
|
-
echo "your_secret_token" > ~/.indusagi/rpc-token
|
|
44
|
-
chmod 600 ~/.indusagi/rpc-token
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**4. Header-Based (HTTP)**
|
|
48
|
-
```
|
|
49
|
-
Authorization: Bearer your_secret_token
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Token Best Practices
|
|
53
|
-
|
|
54
|
-
✅ **Do:**
|
|
55
|
-
- Use 32+ byte tokens (minimum entropy)
|
|
56
|
-
- Rotate tokens every 90 days
|
|
57
|
-
- Store securely in environment/files
|
|
58
|
-
- Log token usage for audit trails
|
|
59
|
-
- Revoke compromised tokens immediately
|
|
60
|
-
|
|
61
|
-
❌ **Don't:**
|
|
62
|
-
- Commit tokens to version control
|
|
63
|
-
- Use weak/predictable tokens
|
|
64
|
-
- Share tokens via email/Slack
|
|
65
|
-
- Log full tokens (log only hash)
|
|
66
|
-
- Reuse same token across environments
|
|
67
|
-
|
|
68
|
-
### Security Considerations
|
|
69
|
-
|
|
70
|
-
**1. Connection Security**
|
|
71
|
-
```javascript
|
|
72
|
-
// Always use Unix sockets for local connections
|
|
73
|
-
const socket = '/var/run/indusagi.sock';
|
|
74
|
-
|
|
75
|
-
// Always use HTTPS for remote connections
|
|
76
|
-
const url = 'https://agent.example.com:3000';
|
|
77
|
-
|
|
78
|
-
// Never expose over HTTP publicly
|
|
79
|
-
// ❌ DON'T: indusagi --rpc-port 3000 --rpc-host 0.0.0.0
|
|
80
|
-
// ✅ DO: indusagi --rpc-socket /var/run/indusagi.sock
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**2. Rate Limiting**
|
|
84
|
-
```javascript
|
|
85
|
-
// Implement rate limiting on client side
|
|
86
|
-
class RateLimitedRpcClient {
|
|
87
|
-
constructor(client, maxRequests = 10, windowMs = 1000) {
|
|
88
|
-
this.client = client;
|
|
89
|
-
this.maxRequests = maxRequests;
|
|
90
|
-
this.windowMs = windowMs;
|
|
91
|
-
this.requests = [];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async send(method, params) {
|
|
95
|
-
const now = Date.now();
|
|
96
|
-
this.requests = this.requests.filter(t => now - t < this.windowMs);
|
|
97
|
-
|
|
98
|
-
if (this.requests.length >= this.maxRequests) {
|
|
99
|
-
throw new Error('Rate limit exceeded');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
this.requests.push(now);
|
|
103
|
-
return this.client.send(method, params);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**3. Input Validation**
|
|
109
|
-
```javascript
|
|
110
|
-
// Always validate input before sending to RPC
|
|
111
|
-
function validatePrompt(message) {
|
|
112
|
-
if (!message || typeof message !== 'string') {
|
|
113
|
-
throw new Error('Message must be a non-empty string');
|
|
114
|
-
}
|
|
115
|
-
if (message.length > 100000) {
|
|
116
|
-
throw new Error('Message exceeds maximum length');
|
|
117
|
-
}
|
|
118
|
-
return message;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const message = validatePrompt(userInput);
|
|
122
|
-
await rpcClient.prompt(message);
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## RPC Connection Handling
|
|
128
|
-
|
|
129
|
-
### Connection Lifecycle
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
┌──────────────────────────────────────────────────┐
|
|
133
|
-
│ RPC Connection Lifecycle │
|
|
134
|
-
├──────────────────────────────────────────────────┤
|
|
135
|
-
│ │
|
|
136
|
-
│ 1. CONNECTING │
|
|
137
|
-
│ ↓ │
|
|
138
|
-
│ 2. HANDSHAKE (authenticate, check version) │
|
|
139
|
-
│ ↓ │
|
|
140
|
-
│ 3. CONNECTED (ready to send commands) │
|
|
141
|
-
│ ↓ │
|
|
142
|
-
│ 4. REQUEST LOOP (send/receive) │
|
|
143
|
-
│ ↓ │
|
|
144
|
-
│ 5. DISCONNECTING │
|
|
145
|
-
│ ↓ │
|
|
146
|
-
│ 6. DISCONNECTED │
|
|
147
|
-
│ │
|
|
148
|
-
│ ERROR: Can reconnect and replay pending reqs │
|
|
149
|
-
│ │
|
|
150
|
-
└──────────────────────────────────────────────────┘
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Reconnection Strategy
|
|
154
|
-
|
|
155
|
-
**Exponential Backoff:**
|
|
156
|
-
```javascript
|
|
157
|
-
function calculateBackoff(attempt) {
|
|
158
|
-
const baseDelay = 100; // ms
|
|
159
|
-
const maxDelay = 30000; // 30 seconds
|
|
160
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
161
|
-
const jitter = Math.random() * delay * 0.1; // ±10% jitter
|
|
162
|
-
return delay + jitter;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Attempt sequence:
|
|
166
|
-
// 1st: ~100ms, 2nd: ~200ms, 3rd: ~400ms, 4th: ~800ms, ...
|
|
167
|
-
// Max: 30000ms (30s)
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
**Implementation:**
|
|
171
|
-
```javascript
|
|
172
|
-
class RobustRpcClient {
|
|
173
|
-
async connect() {
|
|
174
|
-
let attempt = 0;
|
|
175
|
-
const maxAttempts = 10;
|
|
176
|
-
|
|
177
|
-
while (attempt < maxAttempts) {
|
|
178
|
-
try {
|
|
179
|
-
await this._doConnect();
|
|
180
|
-
this.reconnectAttempt = 0; // Reset on success
|
|
181
|
-
return;
|
|
182
|
-
} catch (error) {
|
|
183
|
-
if (attempt < maxAttempts - 1) {
|
|
184
|
-
const delay = calculateBackoff(attempt);
|
|
185
|
-
console.log(`Reconnecting in ${delay}ms...`);
|
|
186
|
-
await new Promise(r => setTimeout(r, delay));
|
|
187
|
-
attempt++;
|
|
188
|
-
} else {
|
|
189
|
-
throw new Error(`Failed to connect after ${maxAttempts} attempts`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Event Subscription
|
|
198
|
-
|
|
199
|
-
**Subscribing to Events:**
|
|
200
|
-
```json
|
|
201
|
-
{
|
|
202
|
-
"id": "sub-1",
|
|
203
|
-
"method": "subscribe",
|
|
204
|
-
"params": {
|
|
205
|
-
"events": ["agent_start", "message_start", "message_end", "tool_*"]
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**Response:**
|
|
211
|
-
```json
|
|
212
|
-
{
|
|
213
|
-
"id": "sub-1",
|
|
214
|
-
"result": {
|
|
215
|
-
"subscription_id": "sub-abc-123",
|
|
216
|
-
"active": true
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Receiving Events:**
|
|
222
|
-
```json
|
|
223
|
-
{
|
|
224
|
-
"method": "event",
|
|
225
|
-
"params": {
|
|
226
|
-
"subscription_id": "sub-abc-123",
|
|
227
|
-
"type": "agent_start",
|
|
228
|
-
"timestamp": 1677000000000
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Handling Disconnections
|
|
234
|
-
|
|
235
|
-
```javascript
|
|
236
|
-
class ReconnectingClient {
|
|
237
|
-
constructor(options) {
|
|
238
|
-
this.options = options;
|
|
239
|
-
this.socket = null;
|
|
240
|
-
this.pendingRequests = new Map();
|
|
241
|
-
this.isConnected = false;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async send(method, params) {
|
|
245
|
-
// Queue request if not connected
|
|
246
|
-
if (!this.isConnected) {
|
|
247
|
-
return new Promise((resolve, reject) => {
|
|
248
|
-
const requestId = uuid();
|
|
249
|
-
this.pendingRequests.set(requestId, {
|
|
250
|
-
method,
|
|
251
|
-
params,
|
|
252
|
-
resolve,
|
|
253
|
-
reject,
|
|
254
|
-
timestamp: Date.now()
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Timeout after 30 seconds
|
|
258
|
-
setTimeout(() => {
|
|
259
|
-
this.pendingRequests.delete(requestId);
|
|
260
|
-
reject(new Error('Request timeout'));
|
|
261
|
-
}, 30000);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return this._sendRequest(method, params);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async onReconnect() {
|
|
269
|
-
// Replay pending requests
|
|
270
|
-
const pending = Array.from(this.pendingRequests.values());
|
|
271
|
-
this.pendingRequests.clear();
|
|
272
|
-
|
|
273
|
-
for (const req of pending) {
|
|
274
|
-
try {
|
|
275
|
-
const result = await this._sendRequest(req.method, req.params);
|
|
276
|
-
req.resolve(result);
|
|
277
|
-
} catch (error) {
|
|
278
|
-
req.reject(error);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
onDisconnect() {
|
|
284
|
-
this.isConnected = false;
|
|
285
|
-
// Pending requests will be replayed on reconnect
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
## RPC Client Examples
|
|
293
|
-
|
|
294
|
-
### Node.js Client Implementation
|
|
295
|
-
|
|
296
|
-
**Complete Example:**
|
|
297
|
-
```javascript
|
|
298
|
-
const net = require('net');
|
|
299
|
-
const { v4: uuid } = require('uuid');
|
|
300
|
-
|
|
301
|
-
class IndusagiRpcClient {
|
|
302
|
-
constructor(options = {}) {
|
|
303
|
-
this.socket = options.socket || '/var/run/indusagi.sock';
|
|
304
|
-
this.timeout = options.timeout || 30000;
|
|
305
|
-
this.connection = null;
|
|
306
|
-
this.requestMap = new Map();
|
|
307
|
-
this.eventHandlers = new Map();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async connect() {
|
|
311
|
-
return new Promise((resolve, reject) => {
|
|
312
|
-
this.connection = net.createConnection(this.socket, () => {
|
|
313
|
-
this.connection.on('data', (data) => this._handleData(data));
|
|
314
|
-
this.connection.on('error', (err) => this._handleError(err));
|
|
315
|
-
this.connection.on('close', () => this._handleClose());
|
|
316
|
-
resolve();
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
this.connection.on('error', reject);
|
|
320
|
-
|
|
321
|
-
setTimeout(() => {
|
|
322
|
-
if (!this.connection.connecting) return;
|
|
323
|
-
reject(new Error('Connection timeout'));
|
|
324
|
-
}, this.timeout);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async prompt(message, options = {}) {
|
|
329
|
-
return this._send('prompt', { message, ...options });
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async setModel(model) {
|
|
333
|
-
return this._send('model.set', { model });
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async compact() {
|
|
337
|
-
return this._send('compact', {});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async subscribe(events) {
|
|
341
|
-
return this._send('subscribe', { events });
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
on(eventType, handler) {
|
|
345
|
-
if (!this.eventHandlers.has(eventType)) {
|
|
346
|
-
this.eventHandlers.set(eventType, []);
|
|
347
|
-
}
|
|
348
|
-
this.eventHandlers.get(eventType).push(handler);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
_send(method, params) {
|
|
352
|
-
return new Promise((resolve, reject) => {
|
|
353
|
-
const id = uuid();
|
|
354
|
-
const request = { jsonrpc: '2.0', id, method, params };
|
|
355
|
-
|
|
356
|
-
const timeoutId = setTimeout(() => {
|
|
357
|
-
this.requestMap.delete(id);
|
|
358
|
-
reject(new Error(`Request timeout: ${method}`));
|
|
359
|
-
}, this.timeout);
|
|
360
|
-
|
|
361
|
-
this.requestMap.set(id, { resolve, reject, timeoutId });
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
this.connection.write(JSON.stringify(request) + '\n');
|
|
365
|
-
} catch (error) {
|
|
366
|
-
this.requestMap.delete(id);
|
|
367
|
-
clearTimeout(timeoutId);
|
|
368
|
-
reject(error);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
_handleData(data) {
|
|
374
|
-
const lines = data.toString().split('\n');
|
|
375
|
-
for (const line of lines) {
|
|
376
|
-
if (!line.trim()) continue;
|
|
377
|
-
try {
|
|
378
|
-
const msg = JSON.parse(line);
|
|
379
|
-
if (msg.id && this.requestMap.has(msg.id)) {
|
|
380
|
-
const { resolve, reject, timeoutId } = this.requestMap.get(msg.id);
|
|
381
|
-
this.requestMap.delete(msg.id);
|
|
382
|
-
clearTimeout(timeoutId);
|
|
383
|
-
|
|
384
|
-
if (msg.error) {
|
|
385
|
-
reject(new Error(msg.error.message));
|
|
386
|
-
} else {
|
|
387
|
-
resolve(msg.result);
|
|
388
|
-
}
|
|
389
|
-
} else if (msg.method === 'event') {
|
|
390
|
-
this._emitEvent(msg.params);
|
|
391
|
-
}
|
|
392
|
-
} catch (error) {
|
|
393
|
-
console.error('Failed to parse RPC response:', error);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
_emitEvent(event) {
|
|
399
|
-
const handlers = this.eventHandlers.get(event.type) || [];
|
|
400
|
-
for (const handler of handlers) {
|
|
401
|
-
try {
|
|
402
|
-
handler(event);
|
|
403
|
-
} catch (error) {
|
|
404
|
-
console.error('Error in event handler:', error);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
_handleError(error) {
|
|
410
|
-
console.error('RPC connection error:', error);
|
|
411
|
-
// Implement reconnection logic
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
_handleClose() {
|
|
415
|
-
console.log('RPC connection closed');
|
|
416
|
-
// Clean up pending requests
|
|
417
|
-
for (const { reject, timeoutId } of this.requestMap.values()) {
|
|
418
|
-
clearTimeout(timeoutId);
|
|
419
|
-
reject(new Error('Connection closed'));
|
|
420
|
-
}
|
|
421
|
-
this.requestMap.clear();
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
disconnect() {
|
|
425
|
-
return new Promise((resolve) => {
|
|
426
|
-
if (this.connection) {
|
|
427
|
-
this.connection.end(resolve);
|
|
428
|
-
} else {
|
|
429
|
-
resolve();
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
module.exports = IndusagiRpcClient;
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Usage Pattern
|
|
439
|
-
|
|
440
|
-
```javascript
|
|
441
|
-
const IndusagiRpcClient = require('./rpc-client');
|
|
442
|
-
|
|
443
|
-
async function main() {
|
|
444
|
-
const client = new IndusagiRpcClient({
|
|
445
|
-
socket: '/var/run/indusagi.sock'
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
try {
|
|
449
|
-
// Connect
|
|
450
|
-
await client.connect();
|
|
451
|
-
console.log('Connected to indusagi RPC server');
|
|
452
|
-
|
|
453
|
-
// Subscribe to events
|
|
454
|
-
await client.subscribe(['agent_start', 'message_*', 'tool_*']);
|
|
455
|
-
|
|
456
|
-
// Listen for events
|
|
457
|
-
client.on('agent_start', (event) => {
|
|
458
|
-
console.log('Agent started:', event);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
client.on('message_start', (event) => {
|
|
462
|
-
console.log('Message:', event.content?.slice(0, 50));
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// Send prompt
|
|
466
|
-
const result = await client.prompt('What is 2+2?');
|
|
467
|
-
console.log('Result:', result);
|
|
468
|
-
|
|
469
|
-
} finally {
|
|
470
|
-
await client.disconnect();
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
main().catch(console.error);
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
### Python Client Example
|
|
478
|
-
|
|
479
|
-
```python
|
|
480
|
-
import socket
|
|
481
|
-
import json
|
|
482
|
-
import uuid
|
|
483
|
-
from typing import Dict, Any, Optional
|
|
484
|
-
|
|
485
|
-
class IndusagiRpcClient:
|
|
486
|
-
def __init__(self, socket_path: str = '/var/run/indusagi.sock'):
|
|
487
|
-
self.socket_path = socket_path
|
|
488
|
-
self.socket = None
|
|
489
|
-
self.request_map = {}
|
|
490
|
-
self.event_handlers = {}
|
|
491
|
-
|
|
492
|
-
def connect(self):
|
|
493
|
-
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
494
|
-
self.socket.connect(self.socket_path)
|
|
495
|
-
|
|
496
|
-
def prompt(self, message: str, **options) -> Dict[str, Any]:
|
|
497
|
-
return self._send('prompt', {'message': message, **options})
|
|
498
|
-
|
|
499
|
-
def set_model(self, model: str) -> Dict[str, Any]:
|
|
500
|
-
return self._send('model.set', {'model': model})
|
|
501
|
-
|
|
502
|
-
def compact(self) -> Dict[str, Any]:
|
|
503
|
-
return self._send('compact', {})
|
|
504
|
-
|
|
505
|
-
def _send(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
506
|
-
request_id = str(uuid.uuid4())
|
|
507
|
-
request = {
|
|
508
|
-
'jsonrpc': '2.0',
|
|
509
|
-
'id': request_id,
|
|
510
|
-
'method': method,
|
|
511
|
-
'params': params
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
self.socket.sendall((json.dumps(request) + '\n').encode())
|
|
515
|
-
|
|
516
|
-
# Wait for response
|
|
517
|
-
response_data = b''
|
|
518
|
-
while True:
|
|
519
|
-
data = self.socket.recv(4096)
|
|
520
|
-
if not data:
|
|
521
|
-
break
|
|
522
|
-
response_data += data
|
|
523
|
-
if b'\n' in response_data:
|
|
524
|
-
break
|
|
525
|
-
|
|
526
|
-
response = json.loads(response_data.decode().strip())
|
|
527
|
-
|
|
528
|
-
if 'error' in response:
|
|
529
|
-
raise Exception(response['error']['message'])
|
|
530
|
-
|
|
531
|
-
return response.get('result')
|
|
532
|
-
|
|
533
|
-
def disconnect(self):
|
|
534
|
-
if self.socket:
|
|
535
|
-
self.socket.close()
|
|
536
|
-
|
|
537
|
-
# Usage
|
|
538
|
-
client = IndusagiRpcClient()
|
|
539
|
-
client.connect()
|
|
540
|
-
result = client.prompt('What is 2+2?')
|
|
541
|
-
print(result)
|
|
542
|
-
client.disconnect()
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
---
|
|
546
|
-
|
|
547
|
-
## RPC Error Handling
|
|
548
|
-
|
|
549
|
-
### Common Error Scenarios
|
|
550
|
-
|
|
551
|
-
**1. Authentication Failure**
|
|
552
|
-
```json
|
|
553
|
-
{
|
|
554
|
-
"jsonrpc": "2.0",
|
|
555
|
-
"error": {
|
|
556
|
-
"code": -32005,
|
|
557
|
-
"message": "Authentication required",
|
|
558
|
-
"data": {
|
|
559
|
-
"expected": "valid token",
|
|
560
|
-
"received": "missing or invalid"
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
**Handling:**
|
|
567
|
-
```javascript
|
|
568
|
-
try {
|
|
569
|
-
await client.prompt('test');
|
|
570
|
-
} catch (error) {
|
|
571
|
-
if (error.code === -32005) {
|
|
572
|
-
console.log('Auth failed, check RPC_TOKEN');
|
|
573
|
-
// Re-authenticate or refresh token
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
**2. Model Unavailable**
|
|
579
|
-
```json
|
|
580
|
-
{
|
|
581
|
-
"code": -32003,
|
|
582
|
-
"message": "Model gpt-4 is unavailable",
|
|
583
|
-
"data": {
|
|
584
|
-
"retriable": true,
|
|
585
|
-
"retry_after": 60
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
**Handling:**
|
|
591
|
-
```javascript
|
|
592
|
-
async function promptWithRetry(message, maxAttempts = 3) {
|
|
593
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
594
|
-
try {
|
|
595
|
-
return await client.prompt(message);
|
|
596
|
-
} catch (error) {
|
|
597
|
-
if (error.code === -32003 && error.data?.retriable) {
|
|
598
|
-
const delay = error.data.retry_after || (attempt * 10) * 1000;
|
|
599
|
-
console.log(`Retrying in ${delay}ms...`);
|
|
600
|
-
await new Promise(r => setTimeout(r, delay));
|
|
601
|
-
} else {
|
|
602
|
-
throw error;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
**3. Timeout**
|
|
610
|
-
```javascript
|
|
611
|
-
async function promptWithTimeout(message, timeoutMs = 30000) {
|
|
612
|
-
return Promise.race([
|
|
613
|
-
client.prompt(message),
|
|
614
|
-
new Promise((_, reject) =>
|
|
615
|
-
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
|
|
616
|
-
)
|
|
617
|
-
]);
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
---
|
|
622
|
-
|
|
623
|
-
## Advanced Patterns
|
|
624
|
-
|
|
625
|
-
### Pattern: Batch Processing
|
|
626
|
-
|
|
627
|
-
```javascript
|
|
628
|
-
async function batchProcess(items) {
|
|
629
|
-
const client = new IndusagiRpcClient();
|
|
630
|
-
await client.connect();
|
|
631
|
-
|
|
632
|
-
const results = [];
|
|
633
|
-
for (const item of items) {
|
|
634
|
-
const result = await client.prompt(`Process: ${item}`);
|
|
635
|
-
results.push(result);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
await client.disconnect();
|
|
639
|
-
return results;
|
|
640
|
-
}
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
### Pattern: Event-Driven Processing
|
|
644
|
-
|
|
645
|
-
```javascript
|
|
646
|
-
async function processWithEvents() {
|
|
647
|
-
const client = new IndusagiRpcClient();
|
|
648
|
-
await client.connect();
|
|
649
|
-
|
|
650
|
-
const messages = [];
|
|
651
|
-
client.on('message_start', (e) => {
|
|
652
|
-
messages.push({ role: e.role, content: '' });
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
client.on('message_chunk', (e) => {
|
|
656
|
-
if (messages.length > 0) {
|
|
657
|
-
messages[messages.length - 1].content += e.chunk;
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
client.on('message_end', (e) => {
|
|
662
|
-
console.log('Message complete');
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
await client.prompt('Generate a poem');
|
|
666
|
-
await client.disconnect();
|
|
667
|
-
|
|
668
|
-
return messages;
|
|
669
|
-
}
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### Pattern: Session Management
|
|
673
|
-
|
|
674
|
-
```javascript
|
|
675
|
-
async function persistentSession() {
|
|
676
|
-
const client = new IndusagiRpcClient();
|
|
677
|
-
await client.connect();
|
|
678
|
-
|
|
679
|
-
// Create session
|
|
680
|
-
const session = await client.send('session.create', {
|
|
681
|
-
name: 'my-session',
|
|
682
|
-
model: 'gpt-4'
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// Continue in session
|
|
686
|
-
await client.send('prompt', {
|
|
687
|
-
message: 'First question',
|
|
688
|
-
session_id: session.id
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
await client.send('prompt', {
|
|
692
|
-
message: 'Follow-up',
|
|
693
|
-
session_id: session.id
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
// Export session
|
|
697
|
-
const html = await client.send('session.export', {
|
|
698
|
-
session_id: session.id,
|
|
699
|
-
format: 'html'
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
fs.writeFileSync('session.html', html);
|
|
703
|
-
await client.disconnect();
|
|
704
|
-
}
|
|
705
|
-
```
|