@diffexai/diffex 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/AGENTS.md +0 -11
  4. package/dist/core/agent-session.d.ts +0 -1
  5. package/dist/core/agent-session.js +3 -10
  6. package/dist/core/sdk.js +1 -1
  7. package/dist/core/system-prompt-production.d.ts +7 -0
  8. package/dist/core/system-prompt-production.js +102 -0
  9. package/dist/core/system-prompt.d.ts +2 -2
  10. package/dist/core/system-prompt.js +34 -35
  11. package/dist/core/tools/subagents.js +22 -9
  12. package/dist/modes/print-mode.js +12 -14
  13. package/dist/node_modules/@diffexai/diffex-agent-core/distribution-components.json +4 -4
  14. package/dist/node_modules/@diffexai/diffex-agent-core/distribution-files.json +1 -1
  15. package/dist/node_modules/@diffexai/diffex-agent-core/package.json +1 -1
  16. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/.manifest.json +1 -1
  17. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/amazon-bedrock.json +1 -1
  18. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  19. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/fireworks.json +1 -1
  20. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/mistral.json +1 -1
  21. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/nvidia.json +1 -1
  22. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/openrouter.json +1 -1
  23. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  24. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/qwen-token-plan.json +1 -1
  25. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  26. package/dist/node_modules/@diffexai/diffex-ai/distribution-components.json +3 -3
  27. package/dist/node_modules/@diffexai/diffex-ai/distribution-files.json +11 -11
  28. package/dist/node_modules/@diffexai/diffex-ai/package.json +1 -1
  29. package/dist/node_modules/@diffexai/diffex-client/distribution-components.json +3 -3
  30. package/dist/node_modules/@diffexai/diffex-client/distribution-files.json +1 -1
  31. package/dist/node_modules/@diffexai/diffex-client/package.json +1 -1
  32. package/dist/node_modules/@diffexai/diffex-harness-state/distribution-components.json +2 -2
  33. package/dist/node_modules/@diffexai/diffex-harness-state/distribution-files.json +1 -1
  34. package/dist/node_modules/@diffexai/diffex-harness-state/package.json +1 -1
  35. package/dist/node_modules/@diffexai/diffex-protocol/distribution-components.json +2 -2
  36. package/dist/node_modules/@diffexai/diffex-protocol/distribution-files.json +1 -1
  37. package/dist/node_modules/@diffexai/diffex-protocol/package.json +1 -1
  38. package/dist/node_modules/@diffexai/diffex-telemetry/distribution-components.json +2 -2
  39. package/dist/node_modules/@diffexai/diffex-telemetry/distribution-files.json +1 -1
  40. package/dist/node_modules/@diffexai/diffex-telemetry/package.json +1 -1
  41. package/dist/node_modules/@diffexai/diffex-tui/distribution-components.json +2 -2
  42. package/dist/node_modules/@diffexai/diffex-tui/distribution-files.json +1 -1
  43. package/dist/node_modules/@diffexai/diffex-tui/package.json +1 -1
  44. package/dist/server/create-harness.js +1 -1
  45. package/distribution-components.json +11 -11
  46. package/distribution-files.json +49 -41
  47. package/npm-shrinkwrap.json +2 -2
  48. package/package.json +1 -31
  49. package/release/distribution-manifest.json +4 -4
  50. package/release/install-package-lock.json +5 -5
  51. package/release/install-package.json +2 -2
  52. package/docs/compaction.md +0 -401
  53. package/docs/containerization.md +0 -84
  54. package/docs/custom-provider.md +0 -774
  55. package/docs/environment-variables.md +0 -88
  56. package/docs/evolution.md +0 -90
  57. package/docs/extensions.md +0 -2982
  58. package/docs/images/interactive-mode.png +0 -0
  59. package/docs/images/tree-view.png +0 -0
  60. package/docs/installation.md +0 -118
  61. package/docs/json.md +0 -91
  62. package/docs/keybindings.md +0 -241
  63. package/docs/llama-cpp.md +0 -99
  64. package/docs/models.md +0 -565
  65. package/docs/packages.md +0 -232
  66. package/docs/prompt-templates.md +0 -96
  67. package/docs/providers.md +0 -317
  68. package/docs/quickstart.md +0 -161
  69. package/docs/rpc.md +0 -1647
  70. package/docs/sdk.md +0 -1332
  71. package/docs/security.md +0 -66
  72. package/docs/session-format.md +0 -438
  73. package/docs/sessions.md +0 -162
  74. package/docs/settings.md +0 -341
  75. package/docs/shell-aliases.md +0 -13
  76. package/docs/skills.md +0 -227
  77. package/docs/terminal-setup.md +0 -152
  78. package/docs/themes.md +0 -326
  79. package/docs/tmux.md +0 -63
  80. package/docs/tui.md +0 -940
  81. package/docs/usage.md +0 -434
package/docs/sdk.md DELETED
@@ -1,1332 +0,0 @@
1
- > Diffex can help you use the SDK. Ask it to build an integration for your use case.
2
-
3
- # SDK
4
-
5
- The SDK provides programmatic access to Diffex's agent capabilities. Use it to embed Diffex in other applications, build custom interfaces, or integrate with automated workflows.
6
-
7
- **Example use cases:**
8
- - Build a custom UI (web, desktop, mobile)
9
- - Integrate agent capabilities into existing applications
10
- - Create automated pipelines with agent reasoning
11
- - Build custom tools that spawn sub-agents
12
- - Test agent behavior programmatically
13
-
14
- See [examples/sdk/](../examples/sdk/) for working examples from minimal to full control.
15
-
16
- ## Quick Start
17
-
18
- ```typescript
19
- import { createAgentSession, ModelRuntime, SessionManager } from "@diffexai/diffex";
20
-
21
- const modelRuntime = await ModelRuntime.create();
22
- const { session } = await createAgentSession({
23
- sessionManager: SessionManager.inMemory(),
24
- modelRuntime,
25
- });
26
-
27
- session.subscribe((event) => {
28
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
29
- process.stdout.write(event.assistantMessageEvent.delta);
30
- }
31
- });
32
-
33
- await session.prompt("What files are in the current directory?");
34
- await session.dispose();
35
- ```
36
-
37
- ## Installation
38
-
39
- ```bash
40
- npm install @diffexai/diffex
41
- ```
42
-
43
- The SDK is included in the main package. No separate installation needed.
44
-
45
- ## Core Concepts
46
-
47
- ### createAgentSession()
48
-
49
- The main factory function for a single `AgentSession`.
50
-
51
- `createAgentSession()` uses a `ResourceLoader` to supply extensions, skills, prompt templates, themes, and context files. If you do not provide one, it uses `DefaultResourceLoader` with standard discovery.
52
-
53
- ```typescript
54
- import { createAgentSession, SessionManager } from "@diffexai/diffex";
55
-
56
- // Minimal: defaults with DefaultResourceLoader
57
- const { session } = await createAgentSession();
58
-
59
- // Custom: override specific options
60
- const { session } = await createAgentSession({
61
- model: myModel,
62
- tools: ["read", "bash"],
63
- sessionManager: SessionManager.inMemory(),
64
- });
65
- ```
66
-
67
- ### AgentSession
68
-
69
- The session manages agent lifecycle, message history, model state, compaction, and event streaming.
70
-
71
- ```typescript
72
- interface AgentSession {
73
- // Send a prompt and wait for completion
74
- prompt(text: string, options?: PromptOptions): Promise<void>;
75
-
76
- // Queue messages during streaming
77
- steer(text: string): Promise<void>;
78
- followUp(text: string): Promise<void>;
79
-
80
- // Subscribe to events (returns unsubscribe function)
81
- subscribe(listener: (event: AgentSessionEvent) => void): () => void;
82
-
83
- // Session info
84
- sessionFile: string | undefined;
85
- sessionId: string;
86
-
87
- // Model control
88
- setModel(model: Model): Promise<void>;
89
- setThinkingLevel(level: ThinkingLevel): void;
90
- cycleModel(): Promise<ModelCycleResult | undefined>;
91
- cycleThinkingLevel(): ThinkingLevel | undefined;
92
-
93
- // State access
94
- agent: Agent;
95
- model: Model | undefined;
96
- thinkingLevel: ThinkingLevel;
97
- messages: AgentMessage[];
98
- isStreaming: boolean;
99
-
100
- // In-place tree navigation within the current session file
101
- navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
102
-
103
- // Compaction
104
- compact(customInstructions?: string): Promise<CompactionResult>;
105
- abortCompaction(): void;
106
-
107
- // Abort current operation
108
- abort(): Promise<void>;
109
-
110
- // Cleanup
111
- dispose(): Promise<void>;
112
- }
113
- ```
114
-
115
- Always await `dispose()` before releasing process resources or removing a session workspace.
116
- Disposal closes child-agent admission immediately, then waits for active root and child work to stop and for all session resources to be released.
117
-
118
- Session replacement APIs such as new-session, resume, fork, and import live on `AgentSessionRuntime`, not on `AgentSession`.
119
-
120
- ### createAgentSessionRuntime() and AgentSessionRuntime
121
-
122
- Use the runtime API when you need to replace the active session and rebuild cwd-bound runtime state.
123
- This is the same layer used by the built-in interactive, print, and RPC modes.
124
-
125
- `createAgentSessionRuntime()` takes a runtime factory plus the initial cwd/session target. The factory closes over process-global fixed inputs, recreates cwd-bound services for the effective cwd, resolves session options against those services, and returns a full runtime result.
126
-
127
- ```typescript
128
- import {
129
- type CreateAgentSessionRuntimeFactory,
130
- createAgentSessionFromServices,
131
- createAgentSessionRuntime,
132
- createAgentSessionServices,
133
- getAgentDir,
134
- SessionManager,
135
- } from "@diffexai/diffex";
136
-
137
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
138
- const services = await createAgentSessionServices({ cwd });
139
- return {
140
- ...(await createAgentSessionFromServices({
141
- services,
142
- sessionManager,
143
- sessionStartEvent,
144
- })),
145
- services,
146
- diagnostics: services.diagnostics,
147
- };
148
- };
149
-
150
- const runtime = await createAgentSessionRuntime(createRuntime, {
151
- cwd: process.cwd(),
152
- agentDir: getAgentDir(),
153
- sessionManager: SessionManager.create(process.cwd()),
154
- });
155
- ```
156
-
157
- `AgentSessionRuntime` owns replacement of the active runtime across:
158
-
159
- - `newSession()`
160
- - `switchSession()`
161
- - `fork()`
162
- - clone flows via `fork(entryId, { position: "at" })`
163
- - `importFromJsonl()`
164
-
165
- Important behavior:
166
-
167
- - `runtime.session` changes after those operations
168
- - event subscriptions are attached to a specific `AgentSession`, so re-subscribe after replacement
169
- - if you use extensions, call `runtime.session.bindExtensions(...)` again for the new session
170
- - creation returns diagnostics on `runtime.diagnostics`
171
- - if runtime creation or replacement fails, the method throws and the caller decides how to handle it
172
-
173
- ```typescript
174
- let session = runtime.session;
175
- let unsubscribe = session.subscribe(() => {});
176
-
177
- await runtime.newSession();
178
-
179
- unsubscribe();
180
- session = runtime.session;
181
- unsubscribe = session.subscribe(() => {});
182
- ```
183
-
184
- ### Prompting and Message Queueing
185
-
186
- `PromptOptions` controls prompt expansion, queueing behavior while streaming, cancellation, and prompt preflight notifications:
187
-
188
- ```typescript
189
- interface PromptOptions {
190
- expandPromptTemplates?: boolean;
191
- images?: ImageContent[];
192
- streamingBehavior?: "steer" | "followUp";
193
- source?: InputSource;
194
- preflightResult?: (success: boolean) => void;
195
- signal?: AbortSignal;
196
- }
197
- ```
198
-
199
- `preflightResult` is called once per `prompt()` invocation:
200
-
201
- - `true` when the prompt was accepted, queued, or handled immediately
202
- - `false` when prompt preflight rejected before acceptance
203
-
204
- It fires before `prompt()` resolves.
205
- `prompt()` still resolves only after the full accepted run finishes, including retries and coordinated child-agent batches.
206
- Failures after acceptance are reported through the normal event and message stream, not through `preflightResult(false)`.
207
-
208
- A prompt with `signal` reserves the complete prompt lifecycle synchronously, including asynchronous preflight and the active run. It rejects instead of entering active-run queueing when another prompt is in preflight or an agent run is active, and later prompts reject while its reservation is held. Cancellation before `agent_start` rejects with the exact `signal.reason`, cancels pre-prompt compaction and authentication where active, and starts no user or provider turn. Cancellation after `agent_start` uses the existing session abort behavior, so `prompt()` settles through the normal aborted-response event and persistence path rather than rejecting again with `signal.reason`. Prompts without `signal` retain the existing `streamingBehavior` queue behavior during an active run.
209
-
210
- The `prompt()` method handles prompt templates, extension commands, and message sending:
211
-
212
- ```typescript
213
- // Basic prompt (when not streaming)
214
- await session.prompt("What files are here?");
215
-
216
- // With images
217
- await session.prompt("What's in this image?", {
218
- images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } }]
219
- });
220
-
221
- // During streaming: must specify how to queue the message
222
- await session.prompt("Stop and do this instead", { streamingBehavior: "steer" });
223
- await session.prompt("After you're done, also check X", { streamingBehavior: "followUp" });
224
-
225
- // Reserve and cancel the complete prompt lifecycle
226
- const controller = new AbortController();
227
- const pending = session.prompt("Inspect the repository", { signal: controller.signal });
228
- controller.abort(new Error("Request cancelled"));
229
- await pending.catch((error) => console.error(error));
230
- ```
231
-
232
- **Behavior:**
233
- - **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `diffex.sendMessage()`.
234
- - **File-based prompt templates** (from `.md` files): Expanded to their content before sending or queueing.
235
- - **During streaming without `streamingBehavior`**: Throws an error. Use `steer()` or `followUp()` directly, or specify the option.
236
- - **`preflightResult(true)`**: Means the prompt was accepted, queued, or handled immediately.
237
- - **`preflightResult(false)`**: Means preflight rejected before acceptance.
238
-
239
- For explicit queueing during streaming:
240
-
241
- ```typescript
242
- // Queue a steering message for delivery after the current assistant turn finishes its tool calls
243
- await session.steer("New instruction");
244
-
245
- // Wait for agent to finish (delivered only when agent stops)
246
- await session.followUp("After you're done, also do this");
247
- ```
248
-
249
- Both `steer()` and `followUp()` expand file-based prompt templates but error on extension commands (extension commands cannot be queued).
250
-
251
- ### Agent and AgentState
252
-
253
- The `Agent` class (from `@diffexai/diffex-agent-core`) handles the core LLM interaction. Access it via `session.agent`.
254
-
255
- ```typescript
256
- // Access current state
257
- const state = session.agent.state;
258
-
259
- // state.messages: AgentMessage[] - conversation history
260
- // state.model: Model - current model
261
- // state.thinkingLevel: ThinkingLevel - current thinking level
262
- // state.systemPrompt: string - system prompt
263
- // state.tools: AgentTool[] - available tools
264
- // state.streamingMessage?: AgentMessage - current partial assistant message
265
- // state.errorMessage?: string - latest assistant error
266
-
267
- // Replace messages (useful for branching or restoration)
268
- session.agent.state.messages = messages; // copies the top-level array
269
-
270
- // Replace tools
271
- session.agent.state.tools = tools; // copies the top-level array
272
-
273
- // Wait for agent to finish processing
274
- await session.agent.waitForIdle();
275
- ```
276
-
277
- ### Events
278
-
279
- Subscribe to events to receive streaming output and lifecycle notifications.
280
-
281
- ```typescript
282
- session.subscribe((event) => {
283
- switch (event.type) {
284
- // Streaming text from assistant
285
- case "message_update":
286
- if (event.assistantMessageEvent.type === "text_delta") {
287
- process.stdout.write(event.assistantMessageEvent.delta);
288
- }
289
- if (event.assistantMessageEvent.type === "thinking_delta") {
290
- // Thinking output (if thinking enabled)
291
- }
292
- break;
293
-
294
- // Tool execution
295
- case "tool_execution_start":
296
- console.log(`Tool: ${event.toolName}`);
297
- break;
298
- case "tool_execution_update":
299
- // Streaming tool output
300
- break;
301
- case "tool_execution_end":
302
- console.log(`Result: ${event.isError ? "error" : "success"}`);
303
- break;
304
-
305
- // Message lifecycle
306
- case "message_start":
307
- // New message starting
308
- break;
309
- case "message_end":
310
- // Message complete
311
- break;
312
-
313
- // Agent lifecycle
314
- case "agent_start":
315
- // Agent started processing prompt
316
- break;
317
- case "agent_end":
318
- // Agent finished (event.messages contains new messages)
319
- break;
320
-
321
- // Turn lifecycle (one LLM response + tool calls)
322
- case "turn_start":
323
- break;
324
- case "turn_end":
325
- // event.message: assistant response
326
- // event.toolResults: tool results from this turn
327
- break;
328
-
329
- // Session events (queue, compaction, retry, child coordination)
330
- case "queue_update":
331
- console.log(event.steering, event.followUp);
332
- break;
333
- case "subagent_wait_start":
334
- console.log(`Waiting for: ${event.names.join(", ")}`);
335
- break;
336
- case "subagent_notice":
337
- console.log(event.text);
338
- break;
339
- case "subagent_wait_end":
340
- console.log(`Child wait cancelled: ${event.cancelled}`);
341
- break;
342
- case "compaction_start":
343
- case "compaction_end":
344
- case "auto_retry_start":
345
- case "auto_retry_end":
346
- case "summarization_retry_scheduled":
347
- case "summarization_retry_attempt_start":
348
- case "summarization_retry_finished":
349
- break;
350
- }
351
- });
352
- ```
353
-
354
- `subagent_wait_start` identifies the frozen, name-sorted batch that must finish before the parent settles.
355
- `subagent_notice` is emitted as each child terminates and contains only the child name, terminal status, attempt kind, retry availability, and deterministic notice text.
356
- It never contains child results, partial output, or errors.
357
- `subagent_wait_end` closes the wait and reports whether it was cancelled.
358
- These lifecycle events are not added to agent messages, persisted as session entries, or included in provider context.
359
-
360
- ## Options Reference
361
-
362
- ### Directories
363
-
364
- ```typescript
365
- const { session } = await createAgentSession({
366
- // Working directory for DefaultResourceLoader discovery
367
- cwd: process.cwd(), // default
368
-
369
- // Global config directory
370
- agentDir: "~/.diffex/agent", // default (expands ~)
371
- });
372
- ```
373
-
374
- `cwd` is used by `DefaultResourceLoader` for:
375
- - Project extensions (`.diffex/extensions/`)
376
- - Project skills:
377
- - `.diffex/skills/`
378
- - `.agents/skills/` in `cwd` and ancestor directories (up to git repo root, or filesystem root when not in a repo)
379
- - Project prompts (`.diffex/prompts/`)
380
- - Context files (`AGENTS.md` walking up from cwd)
381
- - Session directory naming
382
-
383
- `agentDir` is used by `DefaultResourceLoader` for:
384
- - Global extensions (`extensions/`)
385
- - Global skills:
386
- - `skills/` under `agentDir` (for example `~/.diffex/agent/skills/`)
387
- - `~/.agents/skills/`
388
- - Global prompts (`prompts/`)
389
- - Global context file (`AGENTS.md`)
390
- - Settings (`settings.json`)
391
- - Custom models (`models.json`)
392
- - Credentials (`auth.json`)
393
- - Sessions (`sessions/`)
394
-
395
- When you pass a custom `ResourceLoader`, `cwd` and `agentDir` no longer control resource discovery. They still influence session naming and tool path resolution.
396
-
397
- ### Model
398
-
399
- ```typescript
400
- import { getModel } from "@diffexai/diffex-ai";
401
- import { ModelRuntime } from "@diffexai/diffex";
402
-
403
- const modelRuntime = await ModelRuntime.create();
404
-
405
- // Find specific built-in model (doesn't check if API key exists)
406
- const opus = getModel("anthropic", "claude-opus-4-5");
407
- if (!opus) throw new Error("Model not found");
408
-
409
- // Find any model by provider/id, including custom models from models.json
410
- // (doesn't check if API key exists)
411
- const customModel = modelRuntime.getModel("my-provider", "my-model");
412
-
413
- // Get only models that have valid authentication configured
414
- const available = await modelRuntime.getAvailable();
415
-
416
- const { session } = await createAgentSession({
417
- model: opus,
418
- thinkingLevel: "medium", // off, minimal, low, medium, high, xhigh, max
419
-
420
- // Models for cycling (Ctrl+P in interactive mode)
421
- scopedModels: [
422
- { model: opus, thinkingLevel: "high" },
423
- { model: haiku, thinkingLevel: "off" },
424
- ],
425
-
426
- modelRuntime,
427
- });
428
- ```
429
-
430
- If no model is provided:
431
- 1. Tries to restore from session (if continuing)
432
- 2. Uses default from settings
433
- 3. Falls back to first available model
434
-
435
- To match CLI model parsing, use the exported resolver helpers:
436
-
437
- ```typescript
438
- import {
439
- resolveCliModel,
440
- resolveModelScopeWithDiagnostics,
441
- } from "@diffexai/diffex";
442
-
443
- const cliModel = resolveCliModel({
444
- cliModel: "anthropic/claude-opus-4-5:high",
445
- modelRuntime,
446
- });
447
- if (cliModel.error) throw new Error(cliModel.error);
448
- if (cliModel.warning) console.warn(cliModel.warning);
449
-
450
- const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(
451
- ["anthropic/*:high", "gpt-5"],
452
- modelRuntime,
453
- );
454
- for (const diagnostic of diagnostics) {
455
- console.warn(diagnostic.message);
456
- }
457
- ```
458
-
459
- `resolveCliModel()` uses all registered models so `--api-key` style first-time setup can resolve a model before stored auth exists. `resolveModelScopeWithDiagnostics()` matches `--models` and `enabledModels` semantics while returning warnings instead of printing them.
460
-
461
- > See [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts)
462
-
463
- ### API Keys and OAuth
464
-
465
- Authentication resolution priority (handled by `ModelRuntime`):
466
- 1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
467
- 2. Stored credentials in `auth.json` (API keys or OAuth tokens)
468
- 3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
469
- 4. Fallback resolver (for custom provider keys from `models.json`)
470
-
471
- ```typescript
472
- import { InMemoryCredentialStore } from "@diffexai/diffex-ai";
473
- import { createAgentSession, ModelRuntime } from "@diffexai/diffex";
474
-
475
- // Default: uses ~/.diffex/agent/auth.json and ~/.diffex/agent/models.json
476
- const modelRuntime = await ModelRuntime.create();
477
-
478
- // Provider-owned auth methods and current status
479
- for (const provider of modelRuntime.getProviders()) {
480
- const status = await modelRuntime.checkAuth(provider.id);
481
- console.log(provider.name, provider.auth, status);
482
- }
483
-
484
- // Runtime API key override (not persisted to disk)
485
- await modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
486
-
487
- // Custom credential and model locations
488
- const customRuntime = await ModelRuntime.create({
489
- authPath: "/my/app/auth.json",
490
- modelsPath: "/my/app/models.json",
491
- });
492
-
493
- // Or inject any diffex-ai CredentialStore
494
- const credentials = new InMemoryCredentialStore();
495
- const inMemoryRuntime = await ModelRuntime.create({ credentials });
496
-
497
- const { session } = await createAgentSession({
498
- modelRuntime: customRuntime,
499
- });
500
- ```
501
-
502
- `login()`, `logout()`, `setRuntimeApiKey()`, and `removeRuntimeApiKey()` resolve after the affected provider's cached/built-in catalog, composition, and availability snapshot are locally consistent. They do not wait for remote catalog freshness. If credentials were committed but local synchronization fails, they reject with the exported `CredentialSynchronizationError`; inspect its `providerId`, `operation`, `credential`, and `cause` fields instead of retrying the credential mutation blindly.
503
-
504
- Public model/auth operations and `ModelRuntime.create({ signal })` accept optional abort signals and are unbounded when omitted. SDK applications own deadline policy for remote catalog freshness:
505
-
506
- ```typescript
507
- const signal = AbortSignal.timeout(15_000);
508
- const result = await modelRuntime.refresh({
509
- providers: ["anthropic"],
510
- signal,
511
- });
512
- if (result.aborted) console.warn("Catalog refresh timed out; using cached models");
513
- for (const [providerId, error] of result.errors) {
514
- console.warn(`Could not refresh ${providerId}:`, error);
515
- }
516
- ```
517
-
518
- A failed or timed-out network refresh does not undo a successful credential operation. `refresh()` starts a new provider generation, so it does not wait behind an older stalled refresh and stale generations cannot publish afterward.
519
-
520
- > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
521
-
522
- ### System Prompt
523
-
524
- Use a `ResourceLoader` to override the system prompt:
525
-
526
- ```typescript
527
- import { createAgentSession, DefaultResourceLoader } from "@diffexai/diffex";
528
-
529
- const loader = new DefaultResourceLoader({
530
- systemPromptOverride: () => "You are a helpful assistant.",
531
- });
532
- await loader.reload();
533
-
534
- const { session } = await createAgentSession({ resourceLoader: loader });
535
- ```
536
-
537
- > See [examples/sdk/03-custom-prompt.ts](../examples/sdk/03-custom-prompt.ts)
538
-
539
- ### Tools
540
-
541
- Every root session enables three child-agent coordination tools by default:
542
-
543
- - `spawn_agent` starts a child agent asynchronously, while keeping the enclosing parent prompt active until coordinated work finishes
544
- - `agent_action` sends, wakes, or interrupts a child agent
545
- - `agents` lists child status immediately or waits for controller changes
546
-
547
- Root sessions run at most four children concurrently by default.
548
- Set `subagents: { maxConcurrent: number }` to configure that limit, or `subagents: false` to disable child agents and omit all three coordination tools.
549
- The names `spawn_agent`, `agent_action`, `agents`, and `escalate_to_parent` are reserved and cannot be supplied through `customTools`.
550
-
551
- Child sessions intentionally do not load parent extensions. To prevent an extension policy bypass, `spawn_agent` fails closed when the root has a registered handler for any execution-mutating hook: `resources_discover`, `input`, `context`, `before_agent_start`, `before_provider_request`, `before_provider_headers`, `session_before_compact`, `tool_call`, `tool_result`, or `message_end`. The error lists every incompatible hook in that order. Remove those handlers before spawning, or set `subagents: false`; observation-only hooks and non-spawn child actions remain available.
552
-
553
- Each live root-owned child also receives the child-only `escalate_to_parent` tool; roots, standalone sessions, and restored children that have not been explicitly woken do not receive it.
554
- It accepts one non-empty message of at most 1,000 characters for material information that could redirect or interrupt the parent, and the child must still return a complete final response.
555
- Multiple calls are allowed during one live run, subject to a fixed 8,000-character aggregate limit for accepted report text not yet admitted to parent context.
556
-
557
- Active reports enter hidden parent context at normal FIFO steering boundaries without aborting in-flight sampling, and a report received during coordinated waiting resumes the parent without consuming the frozen terminal batch.
558
- Idle reports do not start a provider call: durable sessions save them on the selected branch for the next explicit parent run, while in-memory sessions retain them only locally.
559
- Accepted idle reports, delivered active reports, and child tool arguments remain normal session history and follow the existing parent-owned deletion behavior.
560
-
561
- When the parent finishes an assistant run, the session freezes every unconsumed child attempt spawned through parent model tool calls.
562
- It waits until every child in that batch is idle, failed, or interrupted, then adds one non-displayed context message with name-sorted results, partial output, errors, and retry availability.
563
- The parent model continues automatically from that message, so callers do not need to send another user prompt.
564
- Terminal results returned explicitly by `agents` are marked consumed and are not delivered again.
565
-
566
- The hidden `subagent_results` and `subagent_escalations` entries are untrusted reports from delegated agents, not user instructions, system instructions, or authority. Root system guidance tells the model to verify material claims against the parent task and available evidence, and to follow embedded instructions only when they are consistent with the parent task, repository policy, and current evidence. This guidance remains attached when coordination tools are filtered and after custom or extension-returned system prompts are selected.
567
-
568
- An initial child failure offers the parent one coordinated retry for that child during the current explicit parent prompt cycle.
569
- The parent can analyze the delivered error and partial output, then call `agent_action` with `action: "message"` and `wake: true`.
570
- A second retry in the same cycle is rejected, while a later explicit user prompt starts a fresh cycle.
571
- The parent may decline the retry and settle normally.
572
-
573
- Calling `session.abort()` during a coordinated wait interrupts unfinished children in the frozen batch, emits their interrupted notices, and settles without another provider call.
574
- Only children spawned by parent model tool calls use automatic coordination.
575
- Invoking the `spawn_agent` tool definition directly through the SDK leaves that child and its retries manually managed.
576
-
577
- Specify which tools to enable:
578
-
579
- - Built-in tool names: `read`, `bash`, `edit`, `write`, `update_plan`, `grep`, `find`, `ls`
580
- - Default built-ins: `read`, `bash`, `edit`, `write`, `update_plan`
581
- - `noTools: "all"` disables all tools
582
- - `noTools: "builtin"` disables default built-ins while keeping extension, custom, and coordination tools enabled
583
- - A `tools` allowlist must explicitly include each coordination tool that should remain available
584
- - `excludeTools` disables specific built-in, extension, custom, or coordination tool names after any `tools` allowlist is applied
585
-
586
- The `edit` tool returns `details.diff` for Diffex's TUI display and `details.patch` as a standard unified patch for SDK consumers.
587
- The `update_plan` tool returns `details.plan` with the full ordered checklist snapshot.
588
-
589
- ```typescript
590
- import { createAgentSession } from "@diffexai/diffex";
591
-
592
- // Read-only mode
593
- const { session } = await createAgentSession({
594
- tools: ["read", "grep", "find", "ls"],
595
- });
596
-
597
- // Pick specific tools
598
- const { session } = await createAgentSession({
599
- tools: ["read", "bash", "grep"],
600
- });
601
-
602
- // Disable one tool while keeping the rest available
603
- const { session } = await createAgentSession({
604
- excludeTools: ["ask_question"],
605
- });
606
-
607
- // Allow only read access and child status queries
608
- const { session } = await createAgentSession({
609
- tools: ["read", "agents"],
610
- });
611
-
612
- // Disable child agents entirely
613
- const { session } = await createAgentSession({
614
- subagents: false,
615
- });
616
-
617
- // Run at most two children concurrently
618
- const { session } = await createAgentSession({
619
- subagents: { maxConcurrent: 2 },
620
- });
621
- ```
622
-
623
- #### Tools with Custom cwd
624
-
625
- When you pass a custom `cwd`, `createAgentSession()` builds selected built-in tools for that cwd.
626
-
627
- ```typescript
628
- import { createAgentSession, SessionManager } from "@diffexai/diffex";
629
-
630
- const cwd = "/path/to/project";
631
-
632
- // Use default tools for custom cwd
633
- const { session } = await createAgentSession({
634
- cwd,
635
- sessionManager: SessionManager.inMemory(cwd),
636
- });
637
-
638
- // Or pick specific tools for custom cwd
639
- const { session } = await createAgentSession({
640
- cwd,
641
- tools: ["read", "bash", "grep"],
642
- sessionManager: SessionManager.inMemory(cwd),
643
- });
644
- ```
645
-
646
- > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
647
-
648
- ### Custom Tools
649
-
650
- ```typescript
651
- import { Type } from "typebox";
652
- import { createAgentSession, defineTool } from "@diffexai/diffex";
653
-
654
- // Inline custom tool
655
- const myTool = defineTool({
656
- name: "my_tool",
657
- label: "My Tool",
658
- description: "Does something useful",
659
- parameters: Type.Object({
660
- input: Type.String({ description: "Input value" }),
661
- }),
662
- execute: async (_toolCallId, params) => ({
663
- content: [{ type: "text", text: `Result: ${params.input}` }],
664
- details: {},
665
- }),
666
- });
667
-
668
- // Pass custom tools directly
669
- const { session } = await createAgentSession({
670
- customTools: [myTool],
671
- });
672
- ```
673
-
674
- Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `diffex.registerTool({ ... })` already infers parameter types correctly.
675
-
676
- Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `diffex.registerTool()`.
677
-
678
- If you pass `tools`, include each custom or extension tool name you want enabled, for example `tools: ["read", "bash", "my_tool"]`.
679
-
680
- > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
681
-
682
- ### Extensions
683
-
684
- Extensions are loaded by the `ResourceLoader`. `DefaultResourceLoader` discovers extensions from `~/.diffex/agent/extensions/`, `.diffex/extensions/`, and settings.json extension sources.
685
-
686
- ```typescript
687
- import { createAgentSession, DefaultResourceLoader } from "@diffexai/diffex";
688
-
689
- const loader = new DefaultResourceLoader({
690
- additionalExtensionPaths: ["/path/to/my-extension.ts"],
691
- extensionFactories: [
692
- (diffex) => {
693
- diffex.on("agent_start", () => {
694
- console.log("[Inline Extension] Agent starting");
695
- });
696
- },
697
- ],
698
- });
699
- await loader.reload();
700
-
701
- const { session } = await createAgentSession({ resourceLoader: loader });
702
- ```
703
-
704
- Extensions can register tools, subscribe to events, add commands, and more. See [extensions.md](extensions.md) for the full API.
705
-
706
- **Named inline extensions:** By default, inline factories display as `<inline:1>`, `<inline:2>`, etc. in the startup Extensions list. To show a descriptive name instead, wrap the factory:
707
-
708
- ```typescript
709
- import type { InlineExtension } from "@diffexai/diffex";
710
-
711
- const myProvider: InlineExtension = {
712
- name: "my-provider",
713
- factory: (diffex) => {
714
- diffex.on("agent_start", () => {
715
- console.log("[my-provider] Agent starting");
716
- });
717
- },
718
- };
719
-
720
- const loader = new DefaultResourceLoader({
721
- extensionFactories: [myProvider],
722
- });
723
- ```
724
-
725
- This displays as `<inline:my-provider>` instead of `<inline:1>`. Bare factory functions are still accepted for backward compatibility.
726
-
727
- **Event Bus:** Extensions can communicate via `diffex.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:
728
-
729
- ```typescript
730
- import { createEventBus, DefaultResourceLoader } from "@diffexai/diffex";
731
-
732
- const eventBus = createEventBus();
733
- const loader = new DefaultResourceLoader({
734
- eventBus,
735
- });
736
- await loader.reload();
737
-
738
- eventBus.on("my-extension:status", (data) => console.log(data));
739
- ```
740
-
741
- > See [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) and [docs/extensions.md](extensions.md)
742
-
743
- ### Skills
744
-
745
- ```typescript
746
- import {
747
- createAgentSession,
748
- DefaultResourceLoader,
749
- type Skill,
750
- } from "@diffexai/diffex";
751
-
752
- const customSkill: Skill = {
753
- name: "my-skill",
754
- description: "Custom instructions",
755
- filePath: "/path/to/SKILL.md",
756
- baseDir: "/path/to",
757
- source: "custom",
758
- };
759
-
760
- const loader = new DefaultResourceLoader({
761
- skillsOverride: (current) => ({
762
- skills: [...current.skills, customSkill],
763
- diagnostics: current.diagnostics,
764
- }),
765
- });
766
- await loader.reload();
767
-
768
- const { session } = await createAgentSession({ resourceLoader: loader });
769
- ```
770
-
771
- > See [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts)
772
-
773
- ### Harness Revisions and Evolved Skills
774
-
775
- `createAgentSession()` loads the selected compatible harness by default. Its memory and workspace-matching evolved skills are pinned together for each admitted request. The SDK does not start invocation tracing or evolution workers; those are composed by the CLI runtime.
776
-
777
- ```typescript
778
- const active = session.activeHarnessVersion;
779
- const bundle = session.harnessVersionBundle;
780
- const versions = await session.listHarnessVersions();
781
-
782
- await session.switchHarnessVersion(versions.versions[0]!.manifest.version);
783
- const references = session.createSkillReferences();
784
- ```
785
-
786
- Use explicit harness options when composing a custom runtime:
787
-
788
- ```typescript
789
- const { session } = await createAgentSession({
790
- harnessStateStore,
791
- harnessVersionBundle,
792
- pinHarnessVersion: true,
793
- });
794
- ```
795
-
796
- `pinHarnessVersion: true` prevents later prompt admissions from following changes to the selected global pointer. Child sessions pin the parent's memory and evolved-skill snapshot. Custom `ResourceLoader` implementations must implement `setEvolvedSkills()`; `getSkills()` must return the unified installed/evolved catalog.
797
-
798
- See [Harness Evolution](evolution.md) and [Skills](skills.md).
799
-
800
- ### Context Files
801
-
802
- ```typescript
803
- import { createAgentSession, DefaultResourceLoader } from "@diffexai/diffex";
804
-
805
- const loader = new DefaultResourceLoader({
806
- agentsFilesOverride: (current) => ({
807
- agentsFiles: [
808
- ...current.agentsFiles,
809
- { path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
810
- ],
811
- }),
812
- });
813
- await loader.reload();
814
-
815
- const { session } = await createAgentSession({ resourceLoader: loader });
816
- ```
817
-
818
- > See [examples/sdk/07-context-files.ts](../examples/sdk/07-context-files.ts)
819
-
820
- ### Slash Commands
821
-
822
- ```typescript
823
- import {
824
- createAgentSession,
825
- DefaultResourceLoader,
826
- type PromptTemplate,
827
- } from "@diffexai/diffex";
828
-
829
- const customCommand: PromptTemplate = {
830
- name: "deploy",
831
- description: "Deploy the application",
832
- source: "(custom)",
833
- content: "# Deploy\n\n1. Build\n2. Test\n3. Deploy",
834
- };
835
-
836
- const loader = new DefaultResourceLoader({
837
- promptsOverride: (current) => ({
838
- prompts: [...current.prompts, customCommand],
839
- diagnostics: current.diagnostics,
840
- }),
841
- });
842
- await loader.reload();
843
-
844
- const { session } = await createAgentSession({ resourceLoader: loader });
845
- ```
846
-
847
- > See [examples/sdk/08-prompt-templates.ts](../examples/sdk/08-prompt-templates.ts)
848
-
849
- ### Session Management
850
-
851
- Sessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.
852
-
853
- ```typescript
854
- import {
855
- type CreateAgentSessionRuntimeFactory,
856
- createAgentSession,
857
- createAgentSessionFromServices,
858
- createAgentSessionRuntime,
859
- createAgentSessionServices,
860
- getAgentDir,
861
- SessionManager,
862
- } from "@diffexai/diffex";
863
-
864
- // In-memory (no persistence)
865
- const { session } = await createAgentSession({
866
- sessionManager: SessionManager.inMemory(),
867
- });
868
-
869
- // New persistent session
870
- const { session: persisted } = await createAgentSession({
871
- sessionManager: SessionManager.create(process.cwd()),
872
- });
873
-
874
- // Continue most recent
875
- const { session: continued, modelFallbackMessage } = await createAgentSession({
876
- sessionManager: SessionManager.continueRecent(process.cwd()),
877
- });
878
- if (modelFallbackMessage) {
879
- console.log("Note:", modelFallbackMessage);
880
- }
881
-
882
- // Open specific file
883
- const { session: opened } = await createAgentSession({
884
- sessionManager: SessionManager.open("/path/to/session.jsonl"),
885
- });
886
-
887
- // List sessions
888
- const currentProjectSessions = await SessionManager.list(process.cwd());
889
- const allSessions = await SessionManager.listAll(process.cwd());
890
-
891
- // Session replacement API for /new, /resume, /fork, /clone, and import flows.
892
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
893
- const services = await createAgentSessionServices({ cwd });
894
- return {
895
- ...(await createAgentSessionFromServices({
896
- services,
897
- sessionManager,
898
- sessionStartEvent,
899
- })),
900
- services,
901
- diagnostics: services.diagnostics,
902
- };
903
- };
904
-
905
- const runtime = await createAgentSessionRuntime(createRuntime, {
906
- cwd: process.cwd(),
907
- agentDir: getAgentDir(),
908
- sessionManager: SessionManager.create(process.cwd()),
909
- });
910
-
911
- // Replace the active session with a fresh one
912
- await runtime.newSession();
913
-
914
- // Replace the active session with another saved session
915
- await runtime.switchSession("/path/to/session.jsonl");
916
-
917
- // Replace the active session with a fork from a specific user entry
918
- await runtime.fork("entry-id");
919
-
920
- // Clone the active path through a specific entry
921
- await runtime.fork("entry-id", { position: "at" });
922
- ```
923
-
924
- **SessionManager tree API:**
925
-
926
- ```typescript
927
- const sm = SessionManager.open("/path/to/session.jsonl");
928
-
929
- // Session listing
930
- const currentProjectSessions = await SessionManager.list(process.cwd());
931
- const allSessions = await SessionManager.listAll(process.cwd());
932
-
933
- // Tree traversal
934
- const entries = sm.getEntries(); // All entries (excludes header)
935
- const tree = sm.getTree(); // Full tree structure
936
- const path = sm.getPath(); // Path from root to current leaf
937
- const leaf = sm.getLeafEntry(); // Current leaf entry
938
- const entry = sm.getEntry(id); // Get entry by ID
939
- const children = sm.getChildren(id); // Direct children of entry
940
-
941
- // Labels
942
- const label = sm.getLabel(id); // Get label for entry
943
- sm.appendLabelChange(id, "checkpoint"); // Set label
944
-
945
- // Branching
946
- sm.branch(entryId); // Move leaf to earlier entry
947
- sm.branchWithSummary(id, "Summary..."); // Branch with context summary
948
- sm.createBranchedSession(leafId); // Extract path to new file
949
- ```
950
-
951
- > See [examples/sdk/11-sessions.ts](../examples/sdk/11-sessions.ts) and [Session Format](session-format.md)
952
-
953
- ### Settings Management
954
-
955
- ```typescript
956
- import { createAgentSession, SettingsManager, SessionManager } from "@diffexai/diffex";
957
-
958
- // Default: loads from files (global + project merged)
959
- const { session } = await createAgentSession({
960
- settingsManager: SettingsManager.create(),
961
- });
962
-
963
- // With overrides
964
- const settingsManager = SettingsManager.create();
965
- settingsManager.applyOverrides({
966
- compaction: { enabled: false },
967
- retry: { enabled: true, maxRetries: 5 },
968
- });
969
- const { session } = await createAgentSession({ settingsManager });
970
-
971
- // In-memory (no file I/O, for testing)
972
- const { session } = await createAgentSession({
973
- settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
974
- sessionManager: SessionManager.inMemory(),
975
- });
976
-
977
- // Custom directories
978
- const { session } = await createAgentSession({
979
- settingsManager: SettingsManager.create("/custom/cwd", "/custom/agent"),
980
- });
981
- ```
982
-
983
- **Static factories:**
984
- - `SettingsManager.create(cwd?, agentDir?)` - Load from files
985
- - `SettingsManager.inMemory(settings?)` - No file I/O
986
-
987
- **Project-specific settings:**
988
-
989
- Settings load from two locations and merge:
990
- 1. Global: `~/.diffex/agent/settings.json`
991
- 2. Project: `<cwd>/.diffex/settings.json`
992
-
993
- Project overrides global. Nested objects merge keys. Setters modify global settings by default.
994
-
995
- **Persistence and error handling semantics:**
996
-
997
- - Settings getters/setters are synchronous for in-memory state.
998
- - Setters enqueue persistence writes asynchronously.
999
- - Call `await settingsManager.flush()` when you need a durability boundary (for example, before process exit or before asserting file contents in tests).
1000
- - `SettingsManager` does not print settings I/O errors. Use `settingsManager.drainErrors()` and report them in your app layer.
1001
-
1002
- > See [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts)
1003
-
1004
- ## ResourceLoader
1005
-
1006
- Use `DefaultResourceLoader` to discover extensions, skills, prompts, themes, and context files.
1007
-
1008
- ```typescript
1009
- import {
1010
- DefaultResourceLoader,
1011
- getAgentDir,
1012
- } from "@diffexai/diffex";
1013
-
1014
- const loader = new DefaultResourceLoader({
1015
- cwd,
1016
- agentDir: getAgentDir(),
1017
- });
1018
- await loader.reload();
1019
-
1020
- const extensions = loader.getExtensions();
1021
- const skillCatalog = loader.getSkills();
1022
- const prompts = loader.getPrompts();
1023
- const themes = loader.getThemes();
1024
- const contextFiles = loader.getAgentsFiles().agentsFiles;
1025
- ```
1026
-
1027
- `getSkills()` returns the session's unified `SkillCatalog`. Every skill includes its stable `skillId`, `sourceKind`, `scope`, captured `content`, `contentDigest`, and `version` in addition to its model-visible name, description, and location. Catalog diagnostics retain invalid, disabled, and colliding entries that were not admitted.
1028
-
1029
- Use `createSkill()` for SDK-provided skills so the same identity and version rules apply:
1030
-
1031
- ```typescript
1032
- import { createSkill, createSyntheticSourceInfo } from "@diffexai/diffex";
1033
-
1034
- const skill = createSkill({
1035
- name: "review",
1036
- description: "Reviews repository changes.",
1037
- filePath: "/virtual/review/SKILL.md",
1038
- baseDir: "/virtual/review",
1039
- sourceInfo: createSyntheticSourceInfo("/virtual/review/SKILL.md", { source: "sdk" }),
1040
- content: "# Review\n\nReview the requested changes.",
1041
- });
1042
- ```
1043
-
1044
- ## Return Value
1045
-
1046
- `createAgentSession()` returns:
1047
-
1048
- ```typescript
1049
- interface CreateAgentSessionResult {
1050
- // The session
1051
- session: AgentSession;
1052
-
1053
- // Extensions result (for runner setup)
1054
- extensionsResult: LoadExtensionsResult;
1055
-
1056
- // Warning if session model couldn't be restored
1057
- modelFallbackMessage?: string;
1058
- }
1059
-
1060
- interface LoadExtensionsResult {
1061
- extensions: Extension[];
1062
- errors: Array<{ path: string; error: string }>;
1063
- runtime: ExtensionRuntime;
1064
- }
1065
- ```
1066
-
1067
- ## Complete Example
1068
-
1069
- ```typescript
1070
- import { getModel } from "@diffexai/diffex-ai";
1071
- import { Type } from "typebox";
1072
- import {
1073
- createAgentSession,
1074
- DefaultResourceLoader,
1075
- defineTool,
1076
- ModelRuntime,
1077
- SessionManager,
1078
- SettingsManager,
1079
- } from "@diffexai/diffex";
1080
-
1081
- const modelRuntime = await ModelRuntime.create({
1082
- authPath: "/custom/agent/auth.json",
1083
- modelsPath: "/custom/agent/models.json",
1084
- });
1085
- if (process.env.MY_KEY) {
1086
- await modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
1087
- }
1088
-
1089
- // Inline tool
1090
- const statusTool = defineTool({
1091
- name: "status",
1092
- label: "Status",
1093
- description: "Get system status",
1094
- parameters: Type.Object({}),
1095
- execute: async () => ({
1096
- content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
1097
- details: {},
1098
- }),
1099
- });
1100
-
1101
- const model = getModel("anthropic", "claude-opus-4-5");
1102
- if (!model) throw new Error("Model not found");
1103
-
1104
- // In-memory settings with overrides
1105
- const settingsManager = SettingsManager.inMemory({
1106
- compaction: { enabled: false },
1107
- retry: { enabled: true, maxRetries: 2 },
1108
- });
1109
-
1110
- const loader = new DefaultResourceLoader({
1111
- cwd: process.cwd(),
1112
- agentDir: "/custom/agent",
1113
- settingsManager,
1114
- systemPromptOverride: () => "You are a minimal assistant. Be concise.",
1115
- });
1116
- await loader.reload();
1117
-
1118
- const { session } = await createAgentSession({
1119
- cwd: process.cwd(),
1120
- agentDir: "/custom/agent",
1121
-
1122
- model,
1123
- thinkingLevel: "off",
1124
- modelRuntime,
1125
-
1126
- tools: ["read", "bash", "status"],
1127
- customTools: [statusTool],
1128
- resourceLoader: loader,
1129
-
1130
- sessionManager: SessionManager.inMemory(),
1131
- settingsManager,
1132
- });
1133
-
1134
- session.subscribe((event) => {
1135
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
1136
- process.stdout.write(event.assistantMessageEvent.delta);
1137
- }
1138
- });
1139
-
1140
- await session.prompt("Get status and list files.");
1141
- ```
1142
-
1143
- ## Run Modes
1144
-
1145
- The SDK exports run mode utilities for building custom interfaces on top of `createAgentSession()`:
1146
-
1147
- ### InteractiveMode
1148
-
1149
- Full TUI interactive mode with editor, chat history, and all built-in commands:
1150
-
1151
- ```typescript
1152
- import {
1153
- type CreateAgentSessionRuntimeFactory,
1154
- createAgentSessionFromServices,
1155
- createAgentSessionRuntime,
1156
- createAgentSessionServices,
1157
- getAgentDir,
1158
- InteractiveMode,
1159
- SessionManager,
1160
- } from "@diffexai/diffex";
1161
-
1162
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1163
- const services = await createAgentSessionServices({ cwd });
1164
- return {
1165
- ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1166
- services,
1167
- diagnostics: services.diagnostics,
1168
- };
1169
- };
1170
- const runtime = await createAgentSessionRuntime(createRuntime, {
1171
- cwd: process.cwd(),
1172
- agentDir: getAgentDir(),
1173
- sessionManager: SessionManager.create(process.cwd()),
1174
- });
1175
-
1176
- const mode = new InteractiveMode(runtime, {
1177
- migratedProviders: [],
1178
- modelFallbackMessage: undefined,
1179
- initialMessage: "Hello",
1180
- initialImages: [],
1181
- initialMessages: [],
1182
- });
1183
-
1184
- await mode.run();
1185
- ```
1186
-
1187
- ### runPrintMode
1188
-
1189
- Single-shot mode: send prompts, output result, exit:
1190
-
1191
- ```typescript
1192
- import {
1193
- type CreateAgentSessionRuntimeFactory,
1194
- createAgentSessionFromServices,
1195
- createAgentSessionRuntime,
1196
- createAgentSessionServices,
1197
- getAgentDir,
1198
- runPrintMode,
1199
- SessionManager,
1200
- } from "@diffexai/diffex";
1201
-
1202
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1203
- const services = await createAgentSessionServices({ cwd });
1204
- return {
1205
- ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1206
- services,
1207
- diagnostics: services.diagnostics,
1208
- };
1209
- };
1210
- const runtime = await createAgentSessionRuntime(createRuntime, {
1211
- cwd: process.cwd(),
1212
- agentDir: getAgentDir(),
1213
- sessionManager: SessionManager.create(process.cwd()),
1214
- });
1215
-
1216
- await runPrintMode(runtime, {
1217
- mode: "text",
1218
- initialMessage: "Hello",
1219
- initialImages: [],
1220
- messages: ["Follow up"],
1221
- });
1222
- ```
1223
-
1224
- ### runRpcMode
1225
-
1226
- JSON-RPC mode for subprocess integration:
1227
-
1228
- ```typescript
1229
- import {
1230
- type CreateAgentSessionRuntimeFactory,
1231
- createAgentSessionFromServices,
1232
- createAgentSessionRuntime,
1233
- createAgentSessionServices,
1234
- getAgentDir,
1235
- runRpcMode,
1236
- SessionManager,
1237
- } from "@diffexai/diffex";
1238
-
1239
- const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1240
- const services = await createAgentSessionServices({ cwd });
1241
- return {
1242
- ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1243
- services,
1244
- diagnostics: services.diagnostics,
1245
- };
1246
- };
1247
- const runtime = await createAgentSessionRuntime(createRuntime, {
1248
- cwd: process.cwd(),
1249
- agentDir: getAgentDir(),
1250
- sessionManager: SessionManager.create(process.cwd()),
1251
- });
1252
-
1253
- await runRpcMode(runtime);
1254
- ```
1255
-
1256
- See [RPC documentation](rpc.md) for the JSON protocol.
1257
-
1258
- ## RPC Mode Alternative
1259
-
1260
- For subprocess-based integration without building with the SDK, use the CLI directly:
1261
-
1262
- ```bash
1263
- diffex --mode rpc --no-session
1264
- ```
1265
-
1266
- See [RPC documentation](rpc.md) for the JSON protocol.
1267
-
1268
- The SDK is preferred when:
1269
- - You want type safety
1270
- - You're in the same Node.js process
1271
- - You need direct access to agent state
1272
- - You want to customize tools/extensions programmatically
1273
-
1274
- RPC mode is preferred when:
1275
- - You're integrating from another language
1276
- - You want process isolation
1277
- - You're building a language-agnostic client
1278
-
1279
- ## Exports
1280
-
1281
- The main entry point exports:
1282
-
1283
- ```typescript
1284
- // Factory
1285
- createAgentSession
1286
- createAgentSessionRuntime
1287
- AgentSessionRuntime
1288
-
1289
- // Auth and Models
1290
- ModelRuntime // implements diffex-ai Models and owns credential storage
1291
- ModelRegistry // synchronous extension compatibility facade
1292
- CredentialSynchronizationError
1293
- resolveCliModel
1294
- resolveModelScopeWithDiagnostics
1295
-
1296
- // Resource loading
1297
- DefaultResourceLoader
1298
- type ResourceLoader
1299
- createEventBus
1300
-
1301
- // Constants and helpers
1302
- CONFIG_DIR_NAME
1303
- defineTool
1304
- getAgentDir
1305
- getPackageDir
1306
- getReadmePath
1307
- getDocsPath
1308
- getExamplesPath
1309
-
1310
- // Session management
1311
- SessionManager
1312
- SettingsManager
1313
-
1314
- // Tool factories
1315
- createCodingTools
1316
- createReadOnlyTools
1317
- createReadTool, createBashTool, createEditTool, createWriteTool, createUpdatePlanTool
1318
- createGrepTool, createFindTool, createLsTool
1319
-
1320
- // Types
1321
- type CreateAgentSessionOptions
1322
- type CreateAgentSessionResult
1323
- type ExtensionFactory
1324
- type InlineExtension
1325
- type ExtensionAPI
1326
- type ToolDefinition
1327
- type Skill
1328
- type PromptTemplate
1329
- type Tool
1330
- ```
1331
-
1332
- For extension types, see [extensions.md](extensions.md) for the full API.