zidane 1.2.0 → 1.4.0
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 +71 -9
- package/dist/{agent-DvZm8U14.d.ts → agent-B4wguzkU.d.ts} +36 -84
- package/dist/{chunk-LMSOIIAT.js → chunk-IC2WAUBZ.js} +153 -11
- package/dist/chunk-PRNQ7DXE.js +430 -0
- package/dist/chunk-QPYZR2QM.js +21 -0
- package/dist/{chunk-27EP7HB3.js → chunk-YCH7G7YC.js} +406 -283
- package/dist/harnesses.d.ts +3 -2
- package/dist/harnesses.js +8 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +34 -6
- package/dist/mcp.d.ts +3 -2
- package/dist/providers.d.ts +20 -5
- package/dist/providers.js +26 -24
- package/dist/session.d.ts +46 -20
- package/dist/session.js +1 -1
- package/dist/skills.d.ts +124 -0
- package/dist/skills.js +31 -0
- package/dist/{spawn-pP2grsVp.d.ts → spawn-vZAQfDkd.d.ts} +10 -10
- package/dist/tools.d.ts +4 -3
- package/dist/tools.js +4 -1
- package/dist/{types-4CFQ-6Qu.d.ts → types-CLRMCak3.d.ts} +11 -1
- package/dist/types-D8fzooXc.d.ts +141 -0
- package/package.json +5 -1
- package/dist/chunk-34KXKPNN.js +0 -45
package/README.md
CHANGED
|
@@ -290,24 +290,40 @@ MCP connections are made lazily on the first `run()` call and reused across subs
|
|
|
290
290
|
|
|
291
291
|
## Sessions
|
|
292
292
|
|
|
293
|
-
Sessions give an agent persistent identity,
|
|
293
|
+
Sessions give an agent persistent identity, turn history, and run metadata across multiple calls or restarts. Each message exchange is a `SessionTurn` with its own UUID, enabling real-time multiplayer streaming.
|
|
294
|
+
|
|
295
|
+
### SessionTurn
|
|
296
|
+
|
|
297
|
+
Every message in a session is a turn:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
interface SessionTurn {
|
|
301
|
+
id: string // UUID — generated by store or crypto.randomUUID()
|
|
302
|
+
role: 'user' | 'assistant' | 'system'
|
|
303
|
+
content: SessionContentBlock[] // same format used by providers
|
|
304
|
+
usage?: TurnUsage // token usage (assistant turns only)
|
|
305
|
+
createdAt: number // timestamp
|
|
306
|
+
}
|
|
307
|
+
```
|
|
294
308
|
|
|
295
309
|
### Creating a session
|
|
296
310
|
|
|
311
|
+
`createSession` is async — stores can generate IDs server-side (e.g. Supabase).
|
|
312
|
+
|
|
297
313
|
```ts
|
|
298
314
|
import { createSession, createMemoryStore } from 'zidane/session'
|
|
299
315
|
|
|
300
316
|
// In-memory (default, no persistence)
|
|
301
|
-
const session = createSession({ id: 'my-session', agentId: 'my-agent' })
|
|
317
|
+
const session = await createSession({ id: 'my-session', agentId: 'my-agent' })
|
|
302
318
|
|
|
303
319
|
// With a store for persistence
|
|
304
320
|
const store = createMemoryStore()
|
|
305
|
-
const session = createSession({ id: 'my-session', store })
|
|
321
|
+
const session = await createSession({ id: 'my-session', store })
|
|
306
322
|
```
|
|
307
323
|
|
|
308
324
|
### Storage backends
|
|
309
325
|
|
|
310
|
-
Three built-in stores are available
|
|
326
|
+
Three built-in stores are available. All implement the full `SessionStore` interface including incremental operations.
|
|
311
327
|
|
|
312
328
|
```ts
|
|
313
329
|
import { createMemoryStore, createSqliteStore, createRemoteStore } from 'zidane/session'
|
|
@@ -322,6 +338,38 @@ const sqliteStore = createSqliteStore({ path: './sessions.db' })
|
|
|
322
338
|
const remoteStore = createRemoteStore({ url: 'https://api.example.com/sessions' })
|
|
323
339
|
```
|
|
324
340
|
|
|
341
|
+
### SessionStore interface
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
interface SessionStore {
|
|
345
|
+
// Optional: server-side ID generation
|
|
346
|
+
generateSessionId?: () => string | Promise<string>
|
|
347
|
+
generateTurnId?: () => string | Promise<string>
|
|
348
|
+
|
|
349
|
+
// Core CRUD
|
|
350
|
+
load: (sessionId: string) => Promise<SessionData | null>
|
|
351
|
+
save: (session: SessionData) => Promise<void>
|
|
352
|
+
delete: (sessionId: string) => Promise<void>
|
|
353
|
+
list: (filter?) => Promise<string[]>
|
|
354
|
+
|
|
355
|
+
// Incremental operations (avoids full re-save)
|
|
356
|
+
appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>
|
|
357
|
+
getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>
|
|
358
|
+
updateRun: (sessionId: string, run: SessionRun) => Promise<void>
|
|
359
|
+
updateStatus: (sessionId: string, status: SessionStatus) => Promise<void>
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Custom ID generation lets external databases (e.g. Supabase) provide UUIDs server-side, keeping IDs in sync:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const store = createRemoteStore({ url: '...' })
|
|
367
|
+
store.generateTurnId = async () => {
|
|
368
|
+
const { data } = await supabase.rpc('gen_random_uuid')
|
|
369
|
+
return data
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
325
373
|
### Agent integration
|
|
326
374
|
|
|
327
375
|
```ts
|
|
@@ -335,6 +383,18 @@ await agent.run({ prompt: 'hello' })
|
|
|
335
383
|
await session.save() // persist to store
|
|
336
384
|
```
|
|
337
385
|
|
|
386
|
+
Turns are persisted incrementally after each agent turn via `appendTurns` — not as a full document save. If the agent crashes mid-run, you still have turns up to the last completed turn.
|
|
387
|
+
|
|
388
|
+
### Session status
|
|
389
|
+
|
|
390
|
+
Sessions track their status: `'idle' | 'running' | 'completed' | 'error'`. The agent updates it automatically during runs.
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
session.status // 'idle'
|
|
394
|
+
await agent.run({ prompt: 'go' })
|
|
395
|
+
// idle → running → completed (or error)
|
|
396
|
+
```
|
|
397
|
+
|
|
338
398
|
### Session hooks
|
|
339
399
|
|
|
340
400
|
```ts
|
|
@@ -347,9 +407,9 @@ agent.hooks.hook('session:end', (ctx) => {
|
|
|
347
407
|
// ctx.status: 'completed' | 'aborted' | 'error'
|
|
348
408
|
})
|
|
349
409
|
|
|
350
|
-
agent.hooks.hook('session:
|
|
410
|
+
agent.hooks.hook('session:turns', (ctx) => {
|
|
351
411
|
// ctx.sessionId, ctx.count
|
|
352
|
-
// fired after each turn (
|
|
412
|
+
// fired after each turn (incremental sync)
|
|
353
413
|
})
|
|
354
414
|
|
|
355
415
|
agent.hooks.hook('session:save', (ctx) => {
|
|
@@ -363,8 +423,6 @@ agent.hooks.hook('session:meta', (ctx) => {
|
|
|
363
423
|
})
|
|
364
424
|
```
|
|
365
425
|
|
|
366
|
-
Messages are synced to the session after every turn, not just at run start/end. If the agent crashes mid-run, you still have messages up to the last completed turn.
|
|
367
|
-
|
|
368
426
|
### Restoring a session
|
|
369
427
|
|
|
370
428
|
```ts
|
|
@@ -390,11 +448,12 @@ agent.hooks.hook('system:before', (ctx) => {
|
|
|
390
448
|
|
|
391
449
|
agent.hooks.hook('turn:before', (ctx) => {
|
|
392
450
|
// ctx.turn: turn number
|
|
451
|
+
// ctx.turnId: UUID for this turn (generated before LLM call)
|
|
393
452
|
// ctx.options: StreamOptions being sent to provider
|
|
394
453
|
})
|
|
395
454
|
|
|
396
455
|
agent.hooks.hook('turn:after', (ctx) => {
|
|
397
|
-
// ctx.turn, ctx.usage { input, output }
|
|
456
|
+
// ctx.turn, ctx.turnId, ctx.usage { input, output }
|
|
398
457
|
})
|
|
399
458
|
|
|
400
459
|
agent.hooks.hook('agent:done', (ctx) => {
|
|
@@ -412,10 +471,13 @@ agent.hooks.hook('agent:abort', () => {
|
|
|
412
471
|
agent.hooks.hook('stream:text', (ctx) => {
|
|
413
472
|
// ctx.delta: new text chunk
|
|
414
473
|
// ctx.text: accumulated text so far
|
|
474
|
+
// ctx.turnId: UUID of the turn being streamed
|
|
475
|
+
// ctx.blockIndex: content block index within the turn
|
|
415
476
|
})
|
|
416
477
|
|
|
417
478
|
agent.hooks.hook('stream:end', (ctx) => {
|
|
418
479
|
// ctx.text: final complete text
|
|
480
|
+
// ctx.turnId, ctx.blockIndex
|
|
419
481
|
})
|
|
420
482
|
```
|
|
421
483
|
|
|
@@ -1,87 +1,11 @@
|
|
|
1
1
|
import { Hookable } from 'hookable';
|
|
2
|
+
import { E as ExecutionContext, c as ExecutionHandle, f as SkillsConfig, d as SkillConfig } from './types-D8fzooXc.js';
|
|
2
3
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
-
import { M as McpServerConfig,
|
|
4
|
+
import { M as McpServerConfig, e as TurnUsage, b as SessionMessage, C as ChildRunStats, a as AgentStats, A as AgentRunOptions, c as SessionTurn, d as ToolExecutionMode } from './types-CLRMCak3.js';
|
|
4
5
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
5
6
|
import { Provider, StreamOptions } from './providers.js';
|
|
6
7
|
import { Session } from './session.js';
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
* Execution context types.
|
|
10
|
-
*
|
|
11
|
-
* An execution context defines *where* and *how* an agent's tools run.
|
|
12
|
-
* The agent loop and tools interact through this interface without knowing
|
|
13
|
-
* whether they're running in-process, in a Docker container, or in a
|
|
14
|
-
* remote sandbox.
|
|
15
|
-
*/
|
|
16
|
-
interface ContextCapabilities {
|
|
17
|
-
/** Can execute shell commands */
|
|
18
|
-
shell: boolean;
|
|
19
|
-
/** Can read/write files in a workspace */
|
|
20
|
-
filesystem: boolean;
|
|
21
|
-
/** Can make outbound network requests */
|
|
22
|
-
network: boolean;
|
|
23
|
-
/** Has GPU access */
|
|
24
|
-
gpu: boolean;
|
|
25
|
-
}
|
|
26
|
-
/** Opaque handle to a running execution context instance */
|
|
27
|
-
interface ExecutionHandle {
|
|
28
|
-
id: string;
|
|
29
|
-
type: ContextType;
|
|
30
|
-
/** Working directory within the context */
|
|
31
|
-
cwd: string;
|
|
32
|
-
}
|
|
33
|
-
interface ExecResult {
|
|
34
|
-
stdout: string;
|
|
35
|
-
stderr: string;
|
|
36
|
-
exitCode: number;
|
|
37
|
-
}
|
|
38
|
-
interface SpawnConfig {
|
|
39
|
-
/** Working directory (created if it doesn't exist) */
|
|
40
|
-
cwd?: string;
|
|
41
|
-
/** Environment variables */
|
|
42
|
-
env?: Record<string, string>;
|
|
43
|
-
/** Docker image (only for 'docker' context) */
|
|
44
|
-
image?: string;
|
|
45
|
-
/** Resource limits */
|
|
46
|
-
limits?: {
|
|
47
|
-
/** Memory limit in MB */
|
|
48
|
-
memory?: number;
|
|
49
|
-
/** CPU limit (e.g. '1.0' = 1 core) */
|
|
50
|
-
cpu?: string;
|
|
51
|
-
/** Timeout in seconds for the entire context lifetime */
|
|
52
|
-
timeout?: number;
|
|
53
|
-
};
|
|
54
|
-
/** Sandbox provider config (only for 'sandbox' context) */
|
|
55
|
-
sandbox?: {
|
|
56
|
-
provider: string;
|
|
57
|
-
apiKey?: string;
|
|
58
|
-
[key: string]: unknown;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
type ContextType = 'process' | 'docker' | 'sandbox';
|
|
62
|
-
interface ExecutionContext {
|
|
63
|
-
/** Context type identifier */
|
|
64
|
-
readonly type: ContextType;
|
|
65
|
-
/** What this context supports */
|
|
66
|
-
readonly capabilities: ContextCapabilities;
|
|
67
|
-
/** Spawn a new execution environment */
|
|
68
|
-
spawn: (config?: SpawnConfig) => Promise<ExecutionHandle>;
|
|
69
|
-
/** Execute a shell command in the context */
|
|
70
|
-
exec: (handle: ExecutionHandle, command: string, options?: {
|
|
71
|
-
cwd?: string;
|
|
72
|
-
env?: Record<string, string>;
|
|
73
|
-
timeout?: number;
|
|
74
|
-
}) => Promise<ExecResult>;
|
|
75
|
-
/** Read a file from the context's filesystem */
|
|
76
|
-
readFile: (handle: ExecutionHandle, path: string) => Promise<string>;
|
|
77
|
-
/** Write a file to the context's filesystem */
|
|
78
|
-
writeFile: (handle: ExecutionHandle, path: string, content: string) => Promise<void>;
|
|
79
|
-
/** List files in a directory */
|
|
80
|
-
listFiles: (handle: ExecutionHandle, path: string) => Promise<string[]>;
|
|
81
|
-
/** Destroy the execution environment and clean up resources */
|
|
82
|
-
destroy: (handle: ExecutionHandle) => Promise<void>;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
9
|
/** Core tools available in every basic harness (without spawn) */
|
|
86
10
|
declare const basicTools: {
|
|
87
11
|
shell: ToolDef;
|
|
@@ -123,12 +47,19 @@ interface HarnessConfig {
|
|
|
123
47
|
tools: Record<string, ToolDef>;
|
|
124
48
|
/** MCP servers to connect and expose as tools */
|
|
125
49
|
mcpServers?: McpServerConfig[];
|
|
50
|
+
/** Skills configuration at the harness level */
|
|
51
|
+
skills?: SkillsConfig;
|
|
126
52
|
}
|
|
127
53
|
/**
|
|
128
54
|
* Define a harness with a name, optional system prompt, and tools.
|
|
129
55
|
*/
|
|
130
56
|
declare function defineHarness(config: HarnessConfig): HarnessConfig;
|
|
131
57
|
type Harness = HarnessConfig;
|
|
58
|
+
/**
|
|
59
|
+
* A harness with no tools — for pure chat mode.
|
|
60
|
+
* Use with `enableTools: false` or when no tool access is needed.
|
|
61
|
+
*/
|
|
62
|
+
declare const noTools: HarnessConfig;
|
|
132
63
|
|
|
133
64
|
/**
|
|
134
65
|
* MCP (Model Context Protocol) server support.
|
|
@@ -168,18 +99,24 @@ interface AgentHooks {
|
|
|
168
99
|
}) => void;
|
|
169
100
|
'turn:before': (ctx: {
|
|
170
101
|
turn: number;
|
|
102
|
+
turnId: string;
|
|
171
103
|
options: StreamOptions;
|
|
172
104
|
}) => void;
|
|
173
105
|
'turn:after': (ctx: {
|
|
174
106
|
turn: number;
|
|
107
|
+
turnId: string;
|
|
175
108
|
usage: TurnUsage;
|
|
176
109
|
}) => void;
|
|
177
110
|
'stream:text': (ctx: {
|
|
178
111
|
delta: string;
|
|
179
112
|
text: string;
|
|
113
|
+
turnId: string;
|
|
114
|
+
blockIndex: number;
|
|
180
115
|
}) => void;
|
|
181
116
|
'stream:end': (ctx: {
|
|
182
117
|
text: string;
|
|
118
|
+
turnId: string;
|
|
119
|
+
blockIndex: number;
|
|
183
120
|
}) => void;
|
|
184
121
|
'tool:before': (ctx: {
|
|
185
122
|
name: string;
|
|
@@ -252,6 +189,16 @@ interface AgentHooks {
|
|
|
252
189
|
input: Record<string, unknown>;
|
|
253
190
|
error: Error;
|
|
254
191
|
}) => void;
|
|
192
|
+
'skills:resolve': (ctx: {
|
|
193
|
+
skills: SkillConfig[];
|
|
194
|
+
}) => void;
|
|
195
|
+
'skills:catalog': (ctx: {
|
|
196
|
+
catalog: string;
|
|
197
|
+
skills: SkillConfig[];
|
|
198
|
+
}) => void;
|
|
199
|
+
'skills:activate': (ctx: {
|
|
200
|
+
skill: SkillConfig;
|
|
201
|
+
}) => void;
|
|
255
202
|
'agent:abort': (ctx: object) => void;
|
|
256
203
|
'agent:done': (ctx: AgentStats) => void;
|
|
257
204
|
'session:start': (ctx: {
|
|
@@ -264,7 +211,7 @@ interface AgentHooks {
|
|
|
264
211
|
runId: string;
|
|
265
212
|
status: 'completed' | 'aborted' | 'error';
|
|
266
213
|
}) => void;
|
|
267
|
-
'session:
|
|
214
|
+
'session:turns': (ctx: {
|
|
268
215
|
sessionId: string;
|
|
269
216
|
count: number;
|
|
270
217
|
}) => void;
|
|
@@ -278,16 +225,21 @@ interface AgentHooks {
|
|
|
278
225
|
}) => void;
|
|
279
226
|
}
|
|
280
227
|
interface AgentOptions {
|
|
281
|
-
harness
|
|
228
|
+
/** Harness (tools + system prompt). Defaults to a no-tools harness if omitted. */
|
|
229
|
+
harness?: HarnessConfig;
|
|
282
230
|
provider: Provider;
|
|
283
231
|
/** Tool execution mode: 'sequential' (default) or 'parallel' */
|
|
284
232
|
toolExecution?: ToolExecutionMode;
|
|
233
|
+
/** Enable tool use. When false, no tools are sent to the provider (pure chat mode). Default: true. */
|
|
234
|
+
enableTools?: boolean;
|
|
285
235
|
/** Execution context: where tools run. Defaults to in-process. */
|
|
286
236
|
execution?: ExecutionContext;
|
|
287
237
|
/** MCP servers to connect and expose as tools */
|
|
288
238
|
mcpServers?: McpServerConfig[];
|
|
289
|
-
/** Session for identity,
|
|
239
|
+
/** Session for identity, turn persistence, and run tracking */
|
|
290
240
|
session?: Session;
|
|
241
|
+
/** Skills configuration (merged with harness-level skills, agent takes precedence) */
|
|
242
|
+
skills?: SkillsConfig;
|
|
291
243
|
/** @internal */
|
|
292
244
|
_mcpConnector?: (configs: McpServerConfig[]) => Promise<McpConnection>;
|
|
293
245
|
}
|
|
@@ -302,12 +254,12 @@ interface Agent {
|
|
|
302
254
|
/** Destroy the execution context and clean up resources */
|
|
303
255
|
destroy: () => Promise<void>;
|
|
304
256
|
readonly isRunning: boolean;
|
|
305
|
-
readonly
|
|
257
|
+
readonly turns: SessionTurn[];
|
|
306
258
|
readonly execution: ExecutionContext;
|
|
307
259
|
readonly handle: ExecutionHandle | null;
|
|
308
260
|
readonly session: Session | null;
|
|
309
261
|
meta: Record<string, unknown>;
|
|
310
262
|
}
|
|
311
|
-
declare function createAgent({ harness, provider, toolExecution, execution, mcpServers, session, _mcpConnector }: AgentOptions): Agent;
|
|
263
|
+
declare function createAgent({ harness: harnessOption, provider, toolExecution, enableTools, execution, mcpServers, session, skills: agentSkills, _mcpConnector }: AgentOptions): Agent;
|
|
312
264
|
|
|
313
|
-
export { type Agent as A, type
|
|
265
|
+
export { type Agent as A, type Harness as H, type McpConnection as M, type ToolContext as T, _default as _, type AgentHooks as a, type AgentOptions as b, type HarnessConfig as c, type ToolDef as d, type ToolMap as e, connectMcpServers as f, createAgent as g, defineHarness as h, basicTools as i, noTools as n, resultToString as r };
|
|
@@ -21,6 +21,37 @@ function createMemoryStore() {
|
|
|
21
21
|
ids = ids.slice(0, filter.limit);
|
|
22
22
|
}
|
|
23
23
|
return ids;
|
|
24
|
+
},
|
|
25
|
+
async appendTurns(sessionId, turns) {
|
|
26
|
+
const data = sessions.get(sessionId);
|
|
27
|
+
if (data) {
|
|
28
|
+
data.turns.push(...JSON.parse(JSON.stringify(turns)));
|
|
29
|
+
data.updatedAt = Date.now();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
async getTurns(sessionId, from = 0, limit) {
|
|
33
|
+
const data = sessions.get(sessionId);
|
|
34
|
+
if (!data)
|
|
35
|
+
return [];
|
|
36
|
+
const sliced = data.turns.slice(from, limit !== void 0 ? from + limit : void 0);
|
|
37
|
+
return JSON.parse(JSON.stringify(sliced));
|
|
38
|
+
},
|
|
39
|
+
async updateRun(sessionId, run) {
|
|
40
|
+
const data = sessions.get(sessionId);
|
|
41
|
+
if (data) {
|
|
42
|
+
const idx = data.runs.findIndex((r) => r.id === run.id);
|
|
43
|
+
if (idx >= 0) {
|
|
44
|
+
data.runs[idx] = JSON.parse(JSON.stringify(run));
|
|
45
|
+
}
|
|
46
|
+
data.updatedAt = Date.now();
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async updateStatus(sessionId, status) {
|
|
50
|
+
const data = sessions.get(sessionId);
|
|
51
|
+
if (data) {
|
|
52
|
+
data.status = status;
|
|
53
|
+
data.updatedAt = Date.now();
|
|
54
|
+
}
|
|
24
55
|
}
|
|
25
56
|
};
|
|
26
57
|
}
|
|
@@ -82,6 +113,50 @@ function createRemoteStore(options) {
|
|
|
82
113
|
}
|
|
83
114
|
const body = await res.json();
|
|
84
115
|
return body.ids;
|
|
116
|
+
},
|
|
117
|
+
async appendTurns(sessionId, turns) {
|
|
118
|
+
const res = await request(`/sessions/${encodeURIComponent(sessionId)}/turns`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: JSON.stringify(turns)
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok) {
|
|
123
|
+
throw new Error(`Remote appendTurns failed: ${res.status} ${res.statusText}`);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async getTurns(sessionId, from = 0, limit) {
|
|
127
|
+
const params = new URLSearchParams();
|
|
128
|
+
if (from)
|
|
129
|
+
params.set("from", String(from));
|
|
130
|
+
if (limit !== void 0)
|
|
131
|
+
params.set("limit", String(limit));
|
|
132
|
+
const query = params.toString();
|
|
133
|
+
const path = `/sessions/${encodeURIComponent(sessionId)}/turns${query ? `?${query}` : ""}`;
|
|
134
|
+
const res = await request(path);
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
throw new Error(`Remote getTurns failed: ${res.status} ${res.statusText}`);
|
|
137
|
+
}
|
|
138
|
+
return await res.json();
|
|
139
|
+
},
|
|
140
|
+
async updateRun(sessionId, run) {
|
|
141
|
+
const res = await request(
|
|
142
|
+
`/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(run.id)}`,
|
|
143
|
+
{
|
|
144
|
+
method: "PUT",
|
|
145
|
+
body: JSON.stringify(run)
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
throw new Error(`Remote updateRun failed: ${res.status} ${res.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
async updateStatus(sessionId, status) {
|
|
153
|
+
const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {
|
|
154
|
+
method: "PATCH",
|
|
155
|
+
body: JSON.stringify({ status })
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
throw new Error(`Remote updateStatus failed: ${res.status} ${res.statusText}`);
|
|
159
|
+
}
|
|
85
160
|
}
|
|
86
161
|
};
|
|
87
162
|
}
|
|
@@ -113,7 +188,7 @@ function createSqliteStore(options) {
|
|
|
113
188
|
const stmtDelete = db.prepare("DELETE FROM sessions WHERE id = ?");
|
|
114
189
|
const stmtList = db.prepare("SELECT id FROM sessions ORDER BY updated_at DESC");
|
|
115
190
|
const stmtListByAgent = db.prepare("SELECT id FROM sessions WHERE agent_id = ? ORDER BY updated_at DESC");
|
|
116
|
-
|
|
191
|
+
const store = {
|
|
117
192
|
async load(sessionId) {
|
|
118
193
|
const row = stmtLoad.get(sessionId);
|
|
119
194
|
if (!row)
|
|
@@ -144,19 +219,61 @@ function createSqliteStore(options) {
|
|
|
144
219
|
return ids.slice(0, filter.limit);
|
|
145
220
|
}
|
|
146
221
|
return ids;
|
|
222
|
+
},
|
|
223
|
+
async appendTurns(sessionId, turns) {
|
|
224
|
+
const data = await store.load(sessionId);
|
|
225
|
+
if (data) {
|
|
226
|
+
data.turns.push(...turns);
|
|
227
|
+
data.updatedAt = Date.now();
|
|
228
|
+
await store.save(data);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
async getTurns(sessionId, from = 0, limit) {
|
|
232
|
+
const data = await store.load(sessionId);
|
|
233
|
+
if (!data)
|
|
234
|
+
return [];
|
|
235
|
+
return data.turns.slice(from, limit !== void 0 ? from + limit : void 0);
|
|
236
|
+
},
|
|
237
|
+
async updateRun(sessionId, run) {
|
|
238
|
+
const data = await store.load(sessionId);
|
|
239
|
+
if (data) {
|
|
240
|
+
const idx = data.runs.findIndex((r) => r.id === run.id);
|
|
241
|
+
if (idx >= 0) {
|
|
242
|
+
data.runs[idx] = run;
|
|
243
|
+
}
|
|
244
|
+
data.updatedAt = Date.now();
|
|
245
|
+
await store.save(data);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
async updateStatus(sessionId, status) {
|
|
249
|
+
const data = await store.load(sessionId);
|
|
250
|
+
if (data) {
|
|
251
|
+
data.status = status;
|
|
252
|
+
data.updatedAt = Date.now();
|
|
253
|
+
await store.save(data);
|
|
254
|
+
}
|
|
147
255
|
}
|
|
148
256
|
};
|
|
257
|
+
return store;
|
|
149
258
|
}
|
|
150
259
|
|
|
151
260
|
// src/session/index.ts
|
|
152
|
-
function createSession(options = {}) {
|
|
261
|
+
async function createSession(options = {}) {
|
|
153
262
|
const store = options.store;
|
|
154
263
|
const now = Date.now();
|
|
264
|
+
let sessionId = options.id;
|
|
265
|
+
if (!sessionId && store?.generateSessionId) {
|
|
266
|
+
sessionId = await store.generateSessionId();
|
|
267
|
+
}
|
|
268
|
+
if (!sessionId) {
|
|
269
|
+
sessionId = generateId();
|
|
270
|
+
}
|
|
155
271
|
const data = options._data ?? {
|
|
156
|
-
id:
|
|
272
|
+
id: sessionId,
|
|
157
273
|
agentId: options.agentId,
|
|
158
|
-
|
|
274
|
+
turns: [],
|
|
159
275
|
runs: [],
|
|
276
|
+
status: "idle",
|
|
160
277
|
metadata: options.metadata ?? {},
|
|
161
278
|
createdAt: now,
|
|
162
279
|
updatedAt: now
|
|
@@ -167,15 +284,18 @@ function createSession(options = {}) {
|
|
|
167
284
|
function findRun(runId) {
|
|
168
285
|
return data.runs.find((r) => r.id === runId);
|
|
169
286
|
}
|
|
170
|
-
|
|
287
|
+
const session = {
|
|
171
288
|
get id() {
|
|
172
289
|
return data.id;
|
|
173
290
|
},
|
|
174
291
|
get agentId() {
|
|
175
292
|
return data.agentId;
|
|
176
293
|
},
|
|
177
|
-
get
|
|
178
|
-
return data.
|
|
294
|
+
get turns() {
|
|
295
|
+
return data.turns;
|
|
296
|
+
},
|
|
297
|
+
get status() {
|
|
298
|
+
return data.status;
|
|
179
299
|
},
|
|
180
300
|
get runs() {
|
|
181
301
|
return data.runs;
|
|
@@ -232,14 +352,35 @@ function createSession(options = {}) {
|
|
|
232
352
|
}
|
|
233
353
|
touch();
|
|
234
354
|
},
|
|
235
|
-
|
|
236
|
-
data.
|
|
355
|
+
async appendTurns(turns) {
|
|
356
|
+
data.turns.push(...turns);
|
|
237
357
|
touch();
|
|
358
|
+
if (store) {
|
|
359
|
+
await store.appendTurns(data.id, turns);
|
|
360
|
+
}
|
|
238
361
|
},
|
|
239
|
-
|
|
240
|
-
data.
|
|
362
|
+
setTurns(turns) {
|
|
363
|
+
data.turns = turns;
|
|
241
364
|
touch();
|
|
242
365
|
},
|
|
366
|
+
async updateStatus(status) {
|
|
367
|
+
data.status = status;
|
|
368
|
+
touch();
|
|
369
|
+
if (store) {
|
|
370
|
+
await store.updateStatus(data.id, status);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
async updateRun(run) {
|
|
374
|
+
if (store) {
|
|
375
|
+
await store.updateRun(data.id, run);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
generateTurnId() {
|
|
379
|
+
if (store?.generateTurnId) {
|
|
380
|
+
return store.generateTurnId();
|
|
381
|
+
}
|
|
382
|
+
return crypto.randomUUID();
|
|
383
|
+
},
|
|
243
384
|
setMeta(key, value) {
|
|
244
385
|
data.metadata[key] = value;
|
|
245
386
|
touch();
|
|
@@ -254,6 +395,7 @@ function createSession(options = {}) {
|
|
|
254
395
|
return JSON.parse(JSON.stringify(data));
|
|
255
396
|
}
|
|
256
397
|
};
|
|
398
|
+
return session;
|
|
257
399
|
}
|
|
258
400
|
async function loadSession(store, sessionId) {
|
|
259
401
|
const loaded = await store.load(sessionId);
|