@draht/coding-agent 2026.3.25 → 2026.4.5

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 (111) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +6 -2
  3. package/dist/core/agent-session-runtime.d.ts +136 -0
  4. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  5. package/dist/core/agent-session-runtime.js +267 -0
  6. package/dist/core/agent-session-runtime.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +22 -44
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +44 -248
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts +3 -1
  12. package/dist/core/auth-storage.d.ts.map +1 -1
  13. package/dist/core/auth-storage.js +5 -2
  14. package/dist/core/auth-storage.js.map +1 -1
  15. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  16. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  17. package/dist/core/compaction/branch-summarization.js +2 -2
  18. package/dist/core/compaction/branch-summarization.js.map +1 -1
  19. package/dist/core/compaction/compaction.d.ts +2 -2
  20. package/dist/core/compaction/compaction.d.ts.map +1 -1
  21. package/dist/core/compaction/compaction.js +9 -9
  22. package/dist/core/compaction/compaction.js.map +1 -1
  23. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  24. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  25. package/dist/core/export-html/tool-renderer.js +2 -2
  26. package/dist/core/export-html/tool-renderer.js.map +1 -1
  27. package/dist/core/extensions/index.d.ts +2 -2
  28. package/dist/core/extensions/index.d.ts.map +1 -1
  29. package/dist/core/extensions/index.js +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/types.d.ts +16 -16
  32. package/dist/core/extensions/types.d.ts.map +1 -1
  33. package/dist/core/extensions/types.js +10 -0
  34. package/dist/core/extensions/types.js.map +1 -1
  35. package/dist/core/footer-data-provider.d.ts +5 -1
  36. package/dist/core/footer-data-provider.d.ts.map +1 -1
  37. package/dist/core/footer-data-provider.js +70 -8
  38. package/dist/core/footer-data-provider.js.map +1 -1
  39. package/dist/core/index.d.ts +2 -1
  40. package/dist/core/index.d.ts.map +1 -1
  41. package/dist/core/index.js +2 -1
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/model-registry.d.ts +21 -3
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +90 -70
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts.map +1 -1
  48. package/dist/core/model-resolver.js +4 -4
  49. package/dist/core/model-resolver.js.map +1 -1
  50. package/dist/core/resolve-config-value.d.ts +6 -0
  51. package/dist/core/resolve-config-value.d.ts.map +1 -1
  52. package/dist/core/resolve-config-value.js +37 -5
  53. package/dist/core/resolve-config-value.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts +2 -0
  55. package/dist/core/resource-loader.d.ts.map +1 -1
  56. package/dist/core/resource-loader.js +5 -1
  57. package/dist/core/resource-loader.js.map +1 -1
  58. package/dist/core/sdk.d.ts +6 -3
  59. package/dist/core/sdk.d.ts.map +1 -1
  60. package/dist/core/sdk.js +17 -23
  61. package/dist/core/sdk.js.map +1 -1
  62. package/dist/index.d.ts +3 -3
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +3 -3
  65. package/dist/index.js.map +1 -1
  66. package/dist/main.d.ts.map +1 -1
  67. package/dist/main.js +49 -10
  68. package/dist/main.js.map +1 -1
  69. package/dist/modes/interactive/components/footer.d.ts +1 -0
  70. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/footer.js +4 -1
  72. package/dist/modes/interactive/components/footer.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.d.ts +8 -4
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +90 -87
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  78. package/dist/modes/interactive/theme/theme.js +6 -11
  79. package/dist/modes/interactive/theme/theme.js.map +1 -1
  80. package/dist/modes/print-mode.d.ts +4 -4
  81. package/dist/modes/print-mode.d.ts.map +1 -1
  82. package/dist/modes/print-mode.js +87 -74
  83. package/dist/modes/print-mode.js.map +1 -1
  84. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  85. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.js +69 -49
  87. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  88. package/docs/development.md +2 -2
  89. package/docs/extensions.md +78 -22
  90. package/docs/models.md +6 -0
  91. package/docs/packages.md +3 -3
  92. package/docs/rpc.md +2 -2
  93. package/docs/sdk.md +170 -82
  94. package/docs/tree.md +1 -1
  95. package/examples/extensions/custom-compaction.ts +17 -4
  96. package/examples/extensions/handoff.ts +5 -2
  97. package/examples/extensions/hello.ts +18 -17
  98. package/examples/extensions/qna.ts +5 -2
  99. package/examples/extensions/rpc-demo.ts +3 -9
  100. package/examples/extensions/status-line.ts +0 -8
  101. package/examples/extensions/subagent/index.ts +1 -1
  102. package/examples/extensions/summarize.ts +15 -4
  103. package/examples/extensions/todo.ts +0 -2
  104. package/examples/extensions/tools.ts +0 -5
  105. package/examples/extensions/widget-placement.ts +4 -12
  106. package/examples/sdk/02-custom-model.ts +1 -1
  107. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  108. package/examples/sdk/12-full-control.ts +1 -1
  109. package/examples/sdk/13-session-runtime.ts +49 -0
  110. package/examples/sdk/README.md +5 -4
  111. package/package.json +4 -4
package/docs/sdk.md CHANGED
@@ -1,8 +1,8 @@
1
- > pi can help you use the SDK. Ask it to build an integration for your use case.
1
+ > draht can help you use the SDK. Ask it to build an integration for your use case.
2
2
 
3
3
  # SDK
4
4
 
5
- The SDK provides programmatic access to pi's agent capabilities. Use it to embed pi in other applications, build custom interfaces, or integrate with automated workflows.
5
+ The SDK provides programmatic access to draht's agent capabilities. Use it to embed draht in other applications, build custom interfaces, or integrate with automated workflows.
6
6
 
7
7
  **Example use cases:**
8
8
  - Build a custom UI (web, desktop, mobile)
@@ -20,7 +20,7 @@ import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "
20
20
 
21
21
  // Set up credential storage and model registry
22
22
  const authStorage = AuthStorage.create();
23
- const modelRegistry = new ModelRegistry(authStorage);
23
+ const modelRegistry = ModelRegistry.create(authStorage);
24
24
 
25
25
  const { session } = await createAgentSession({
26
26
  sessionManager: SessionManager.inMemory(),
@@ -49,7 +49,7 @@ The SDK is included in the main package. No separate installation needed.
49
49
 
50
50
  ### createAgentSession()
51
51
 
52
- The main factory function. Creates an `AgentSession` with configurable options.
52
+ The main factory function for a single `AgentSession`.
53
53
 
54
54
  `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.
55
55
 
@@ -69,61 +69,102 @@ const { session } = await createAgentSession({
69
69
 
70
70
  ### AgentSession
71
71
 
72
- The session manages the agent lifecycle, message history, and event streaming.
72
+ The session manages agent lifecycle, message history, model state, compaction, and event streaming.
73
73
 
74
74
  ```typescript
75
75
  interface AgentSession {
76
76
  // Send a prompt and wait for completion
77
- // If streaming, requires streamingBehavior option to queue the message
78
77
  prompt(text: string, options?: PromptOptions): Promise<void>;
79
-
78
+
80
79
  // Queue messages during streaming
81
- steer(text: string): Promise<void>; // Queue for delivery after the current assistant turn finishes its tool calls
82
- followUp(text: string): Promise<void>; // Wait: delivered only when agent finishes
83
-
80
+ steer(text: string): Promise<void>;
81
+ followUp(text: string): Promise<void>;
82
+
84
83
  // Subscribe to events (returns unsubscribe function)
85
84
  subscribe(listener: (event: AgentSessionEvent) => void): () => void;
86
-
85
+
87
86
  // Session info
88
- sessionFile: string | undefined; // undefined for in-memory
87
+ sessionFile: string | undefined;
89
88
  sessionId: string;
90
-
89
+
91
90
  // Model control
92
91
  setModel(model: Model): Promise<void>;
93
92
  setThinkingLevel(level: ThinkingLevel): void;
94
93
  cycleModel(): Promise<ModelCycleResult | undefined>;
95
94
  cycleThinkingLevel(): ThinkingLevel | undefined;
96
-
95
+
97
96
  // State access
98
97
  agent: Agent;
99
98
  model: Model | undefined;
100
99
  thinkingLevel: ThinkingLevel;
101
100
  messages: AgentMessage[];
102
101
  isStreaming: boolean;
103
-
104
- // Session management
105
- newSession(options?: { parentSession?: string }): Promise<boolean>; // Returns false if cancelled by hook
106
- switchSession(sessionPath: string): Promise<boolean>;
107
-
108
- // Forking
109
- fork(entryId: string): Promise<{ selectedText: string; cancelled: boolean }>; // Creates new session file
110
- navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>; // In-place navigation
111
-
112
- // Hook message injection
113
- sendHookMessage(message: HookMessage, triggerTurn?: boolean): Promise<void>;
114
-
102
+
103
+ // In-place tree navigation within the current session file
104
+ navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
105
+
115
106
  // Compaction
116
107
  compact(customInstructions?: string): Promise<CompactionResult>;
117
108
  abortCompaction(): void;
118
-
109
+
119
110
  // Abort current operation
120
111
  abort(): Promise<void>;
121
-
112
+
122
113
  // Cleanup
123
114
  dispose(): void;
124
115
  }
125
116
  ```
126
117
 
118
+ Session replacement APIs such as new-session, resume, fork, and import live on `AgentSessionRuntimeHost`, not on `AgentSession`.
119
+
120
+ ### createAgentSessionRuntime() and AgentSessionRuntimeHost
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
+ ```typescript
126
+ import {
127
+ AgentSessionRuntimeHost,
128
+ createAgentSessionRuntime,
129
+ SessionManager,
130
+ } from "@draht/coding-agent";
131
+
132
+ const bootstrap = {
133
+ // Optional: authStorage, model, thinkingLevel, tools, customTools, resourceLoader
134
+ };
135
+
136
+ const runtime = await createAgentSessionRuntime(bootstrap, {
137
+ cwd: process.cwd(),
138
+ sessionManager: SessionManager.create(process.cwd()),
139
+ });
140
+
141
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
142
+ ```
143
+
144
+ `createAgentSessionRuntime()` returns an internal runtime bundle. `AgentSessionRuntimeHost` owns replacement of that bundle across:
145
+
146
+ - `newSession()`
147
+ - `switchSession()`
148
+ - `fork()`
149
+ - `importFromJsonl()`
150
+
151
+ Important behavior:
152
+
153
+ - `runtimeHost.session` changes after those operations
154
+ - event subscriptions are attached to a specific `AgentSession`, so re-subscribe after replacement
155
+ - if you use extensions, call `runtimeHost.session.bindExtensions(...)` again for the new session
156
+
157
+ ```typescript
158
+ let session = runtimeHost.session;
159
+ let unsubscribe = session.subscribe(() => {});
160
+
161
+ await runtimeHost.newSession();
162
+
163
+ unsubscribe();
164
+ session = runtimeHost.session;
165
+ unsubscribe = session.subscribe(() => {});
166
+ ```
167
+
127
168
  ### Prompting and Message Queueing
128
169
 
129
170
  The `prompt()` method handles prompt templates, extension commands, and message sending:
@@ -286,7 +327,7 @@ import { getModel } from "@draht/ai";
286
327
  import { AuthStorage, ModelRegistry } from "@draht/coding-agent";
287
328
 
288
329
  const authStorage = AuthStorage.create();
289
- const modelRegistry = new ModelRegistry(authStorage);
330
+ const modelRegistry = ModelRegistry.create(authStorage);
290
331
 
291
332
  // Find specific built-in model (doesn't check if API key exists)
292
333
  const opus = getModel("anthropic", "claude-opus-4-5");
@@ -334,7 +375,7 @@ import { AuthStorage, ModelRegistry } from "@draht/coding-agent";
334
375
 
335
376
  // Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
336
377
  const authStorage = AuthStorage.create();
337
- const modelRegistry = new ModelRegistry(authStorage);
378
+ const modelRegistry = ModelRegistry.create(authStorage);
338
379
 
339
380
  const { session } = await createAgentSession({
340
381
  sessionManager: SessionManager.inMemory(),
@@ -347,7 +388,7 @@ authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
347
388
 
348
389
  // Custom auth storage location
349
390
  const customAuth = AuthStorage.create("/my/app/auth.json");
350
- const customRegistry = new ModelRegistry(customAuth, "/my/app/models.json");
391
+ const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
351
392
 
352
393
  const { session } = await createAgentSession({
353
394
  sessionManager: SessionManager.inMemory(),
@@ -356,7 +397,7 @@ const { session } = await createAgentSession({
356
397
  });
357
398
 
358
399
  // No custom models.json (built-in models only)
359
- const simpleRegistry = new ModelRegistry(authStorage);
400
+ const simpleRegistry = ModelRegistry.inMemory(authStorage);
360
401
  ```
361
402
 
362
403
  > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
@@ -432,7 +473,7 @@ const { session } = await createAgentSession({
432
473
  ```
433
474
 
434
475
  **When you don't need factories:**
435
- - If you omit `tools`, pi automatically creates them with the correct `cwd`
476
+ - If you omit `tools`, draht automatically creates them with the correct `cwd`
436
477
  - If you use `process.cwd()` as your `cwd`, the pre-built instances work fine
437
478
 
438
479
  **When you must use factories:**
@@ -444,21 +485,21 @@ const { session } = await createAgentSession({
444
485
 
445
486
  ```typescript
446
487
  import { Type } from "@sinclair/typebox";
447
- import { createAgentSession, type ToolDefinition } from "@draht/coding-agent";
488
+ import { createAgentSession, defineTool } from "@draht/coding-agent";
448
489
 
449
490
  // Inline custom tool
450
- const myTool: ToolDefinition = {
491
+ const myTool = defineTool({
451
492
  name: "my_tool",
452
493
  label: "My Tool",
453
494
  description: "Does something useful",
454
495
  parameters: Type.Object({
455
496
  input: Type.String({ description: "Input value" }),
456
497
  }),
457
- execute: async (toolCallId, params, onUpdate, ctx, signal) => ({
498
+ execute: async (_toolCallId, params) => ({
458
499
  content: [{ type: "text", text: `Result: ${params.input}` }],
459
500
  details: {},
460
501
  }),
461
- };
502
+ });
462
503
 
463
504
  // Pass custom tools directly
464
505
  const { session } = await createAgentSession({
@@ -466,6 +507,8 @@ const { session } = await createAgentSession({
466
507
  });
467
508
  ```
468
509
 
510
+ Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.
511
+
469
512
  Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.
470
513
 
471
514
  > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
@@ -594,7 +637,12 @@ const { session } = await createAgentSession({ resourceLoader: loader });
594
637
  Sessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.
595
638
 
596
639
  ```typescript
597
- import { createAgentSession, SessionManager } from "@draht/coding-agent";
640
+ import {
641
+ AgentSessionRuntimeHost,
642
+ createAgentSession,
643
+ createAgentSessionRuntime,
644
+ SessionManager,
645
+ } from "@draht/coding-agent";
598
646
 
599
647
  // In-memory (no persistence)
600
648
  const { session } = await createAgentSession({
@@ -602,12 +650,12 @@ const { session } = await createAgentSession({
602
650
  });
603
651
 
604
652
  // New persistent session
605
- const { session } = await createAgentSession({
653
+ const { session: persisted } = await createAgentSession({
606
654
  sessionManager: SessionManager.create(process.cwd()),
607
655
  });
608
656
 
609
657
  // Continue most recent
610
- const { session, modelFallbackMessage } = await createAgentSession({
658
+ const { session: continued, modelFallbackMessage } = await createAgentSession({
611
659
  sessionManager: SessionManager.continueRecent(process.cwd()),
612
660
  });
613
661
  if (modelFallbackMessage) {
@@ -615,26 +663,30 @@ if (modelFallbackMessage) {
615
663
  }
616
664
 
617
665
  // Open specific file
618
- const { session } = await createAgentSession({
666
+ const { session: opened } = await createAgentSession({
619
667
  sessionManager: SessionManager.open("/path/to/session.jsonl"),
620
668
  });
621
669
 
622
- // List available sessions (async with optional progress callback)
623
- const sessions = await SessionManager.list(process.cwd());
624
- for (const info of sessions) {
625
- console.log(`${info.id}: ${info.firstMessage} (${info.messageCount} messages, cwd: ${info.cwd})`);
626
- }
670
+ // List sessions
671
+ const currentProjectSessions = await SessionManager.list(process.cwd());
672
+ const allSessions = await SessionManager.listAll(process.cwd());
627
673
 
628
- // List all sessions across all projects
629
- const allSessions = await SessionManager.listAll((loaded, total) => {
630
- console.log(`Loading ${loaded}/${total}...`);
674
+ // Session replacement API for /new, /resume, /fork, and import flows.
675
+ const bootstrap = {};
676
+ const runtime = await createAgentSessionRuntime(bootstrap, {
677
+ cwd: process.cwd(),
678
+ sessionManager: SessionManager.create(process.cwd()),
631
679
  });
680
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
632
681
 
633
- // Custom session directory (no cwd encoding)
634
- const customDir = "/path/to/my-sessions";
635
- const { session } = await createAgentSession({
636
- sessionManager: SessionManager.create(process.cwd(), customDir),
637
- });
682
+ // Replace the active session with a fresh one
683
+ await runtimeHost.newSession();
684
+
685
+ // Replace the active session with another saved session
686
+ await runtimeHost.switchSession("/path/to/session.jsonl");
687
+
688
+ // Replace the active session with a fork from a specific entry
689
+ await runtimeHost.fork("entry-id");
638
690
  ```
639
691
 
640
692
  **SessionManager tree API:**
@@ -642,6 +694,10 @@ const { session } = await createAgentSession({
642
694
  ```typescript
643
695
  const sm = SessionManager.open("/path/to/session.jsonl");
644
696
 
697
+ // Session listing
698
+ const currentProjectSessions = await SessionManager.list(process.cwd());
699
+ const allSessions = await SessionManager.listAll(process.cwd());
700
+
645
701
  // Tree traversal
646
702
  const entries = sm.getEntries(); // All entries (excludes header)
647
703
  const tree = sm.getTree(); // Full tree structure
@@ -766,14 +822,14 @@ import { getModel } from "@draht/ai";
766
822
  import { Type } from "@sinclair/typebox";
767
823
  import {
768
824
  AuthStorage,
825
+ bashTool,
769
826
  createAgentSession,
770
827
  DefaultResourceLoader,
828
+ defineTool,
771
829
  ModelRegistry,
830
+ readTool,
772
831
  SessionManager,
773
832
  SettingsManager,
774
- readTool,
775
- bashTool,
776
- type ToolDefinition,
777
833
  } from "@draht/coding-agent";
778
834
 
779
835
  // Set up auth storage (custom location)
@@ -785,10 +841,10 @@ if (process.env.MY_KEY) {
785
841
  }
786
842
 
787
843
  // Model registry (no custom models.json)
788
- const modelRegistry = new ModelRegistry(authStorage);
844
+ const modelRegistry = ModelRegistry.create(authStorage);
789
845
 
790
846
  // Inline tool
791
- const statusTool: ToolDefinition = {
847
+ const statusTool = defineTool({
792
848
  name: "status",
793
849
  label: "Status",
794
850
  description: "Get system status",
@@ -797,7 +853,7 @@ const statusTool: ToolDefinition = {
797
853
  content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
798
854
  details: {},
799
855
  }),
800
- };
856
+ });
801
857
 
802
858
  const model = getModel("anthropic", "claude-opus-4-5");
803
859
  if (!model) throw new Error("Model not found");
@@ -851,20 +907,29 @@ The SDK exports run mode utilities for building custom interfaces on top of `cre
851
907
  Full TUI interactive mode with editor, chat history, and all built-in commands:
852
908
 
853
909
  ```typescript
854
- import { createAgentSession, InteractiveMode } from "@draht/coding-agent";
855
-
856
- const { session } = await createAgentSession({ /* ... */ });
910
+ import {
911
+ AgentSessionRuntimeHost,
912
+ createAgentSessionRuntime,
913
+ InteractiveMode,
914
+ SessionManager,
915
+ } from "@draht/coding-agent";
857
916
 
858
- const mode = new InteractiveMode(session, {
859
- // All optional
860
- migratedProviders: [], // Show migration warnings
861
- modelFallbackMessage: undefined, // Show model restore warning
862
- initialMessage: "Hello", // Send on startup
863
- initialImages: [], // Images with initial message
864
- initialMessages: [], // Additional startup prompts
917
+ const bootstrap = {};
918
+ const runtime = await createAgentSessionRuntime(bootstrap, {
919
+ cwd: process.cwd(),
920
+ sessionManager: SessionManager.create(process.cwd()),
921
+ });
922
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
923
+
924
+ const mode = new InteractiveMode(runtimeHost, {
925
+ migratedProviders: [],
926
+ modelFallbackMessage: undefined,
927
+ initialMessage: "Hello",
928
+ initialImages: [],
929
+ initialMessages: [],
865
930
  });
866
931
 
867
- await mode.run(); // Blocks until exit
932
+ await mode.run();
868
933
  ```
869
934
 
870
935
  ### runPrintMode
@@ -872,15 +937,25 @@ await mode.run(); // Blocks until exit
872
937
  Single-shot mode: send prompts, output result, exit:
873
938
 
874
939
  ```typescript
875
- import { createAgentSession, runPrintMode } from "@draht/coding-agent";
940
+ import {
941
+ AgentSessionRuntimeHost,
942
+ createAgentSessionRuntime,
943
+ runPrintMode,
944
+ SessionManager,
945
+ } from "@draht/coding-agent";
876
946
 
877
- const { session } = await createAgentSession({ /* ... */ });
947
+ const bootstrap = {};
948
+ const runtime = await createAgentSessionRuntime(bootstrap, {
949
+ cwd: process.cwd(),
950
+ sessionManager: SessionManager.create(process.cwd()),
951
+ });
952
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
878
953
 
879
- await runPrintMode(session, {
880
- mode: "text", // "text" for final response, "json" for all events
881
- initialMessage: "Hello", // First message (can include @file content)
882
- initialImages: [], // Images with initial message
883
- messages: ["Follow up"], // Additional prompts
954
+ await runPrintMode(runtimeHost, {
955
+ mode: "text",
956
+ initialMessage: "Hello",
957
+ initialImages: [],
958
+ messages: ["Follow up"],
884
959
  });
885
960
  ```
886
961
 
@@ -889,11 +964,21 @@ await runPrintMode(session, {
889
964
  JSON-RPC mode for subprocess integration:
890
965
 
891
966
  ```typescript
892
- import { createAgentSession, runRpcMode } from "@draht/coding-agent";
967
+ import {
968
+ AgentSessionRuntimeHost,
969
+ createAgentSessionRuntime,
970
+ runRpcMode,
971
+ SessionManager,
972
+ } from "@draht/coding-agent";
893
973
 
894
- const { session } = await createAgentSession({ /* ... */ });
974
+ const bootstrap = {};
975
+ const runtime = await createAgentSessionRuntime(bootstrap, {
976
+ cwd: process.cwd(),
977
+ sessionManager: SessionManager.create(process.cwd()),
978
+ });
979
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
895
980
 
896
- await runRpcMode(session); // Reads JSON commands from stdin, writes to stdout
981
+ await runRpcMode(runtimeHost);
897
982
  ```
898
983
 
899
984
  See [RPC documentation](rpc.md) for the JSON protocol.
@@ -903,7 +988,7 @@ See [RPC documentation](rpc.md) for the JSON protocol.
903
988
  For subprocess-based integration without building with the SDK, use the CLI directly:
904
989
 
905
990
  ```bash
906
- pi --mode rpc --no-session
991
+ draht --mode rpc --no-session
907
992
  ```
908
993
 
909
994
  See [RPC documentation](rpc.md) for the JSON protocol.
@@ -926,6 +1011,8 @@ The main entry point exports:
926
1011
  ```typescript
927
1012
  // Factory
928
1013
  createAgentSession
1014
+ createAgentSessionRuntime
1015
+ AgentSessionRuntimeHost
929
1016
 
930
1017
  // Auth and Models
931
1018
  AuthStorage
@@ -937,6 +1024,7 @@ type ResourceLoader
937
1024
  createEventBus
938
1025
 
939
1026
  // Helpers
1027
+ defineTool
940
1028
 
941
1029
  // Session management
942
1030
  SessionManager
package/docs/tree.md CHANGED
@@ -13,7 +13,7 @@ Sessions are stored as trees where each entry has an `id` and `parentId`. The "l
13
13
  | View | Flat list of user messages | Full tree structure |
14
14
  | Action | Extracts path to **new session file** | Changes leaf in **same session** |
15
15
  | Summary | Never | Optional (user prompted) |
16
- | Events | `session_before_fork` / `session_fork` | `session_before_tree` / `session_tree` |
16
+ | Events | `session_before_fork` / `session_start` (`reason: "fork"`) | `session_before_tree` / `session_tree` |
17
17
 
18
18
  ## Tree UI
19
19
 
@@ -31,9 +31,13 @@ export default function (pi: ExtensionAPI) {
31
31
  return;
32
32
  }
33
33
 
34
- // Resolve API key for the summarization model
35
- const apiKey = await ctx.modelRegistry.getApiKey(model);
36
- if (!apiKey) {
34
+ // Resolve request auth for the summarization model
35
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
36
+ if (!auth.ok) {
37
+ ctx.ui.notify(`Compaction auth failed: ${auth.error}`, "warning");
38
+ return;
39
+ }
40
+ if (!auth.apiKey) {
37
41
  ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
38
42
  return;
39
43
  }
@@ -83,7 +87,16 @@ ${conversationText}
83
87
 
84
88
  try {
85
89
  // Pass signal to honor abort requests (e.g., user cancels compaction)
86
- const response = await complete(model, { messages: summaryMessages }, { apiKey, maxTokens: 8192, signal });
90
+ const response = await complete(
91
+ model,
92
+ { messages: summaryMessages },
93
+ {
94
+ apiKey: auth.apiKey,
95
+ headers: auth.headers,
96
+ maxTokens: 8192,
97
+ signal,
98
+ },
99
+ );
87
100
 
88
101
  const summary = response.content
89
102
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -80,7 +80,10 @@ export default function (pi: ExtensionAPI) {
80
80
  loader.onAbort = () => done(null);
81
81
 
82
82
  const doGenerate = async () => {
83
- const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
83
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
84
+ if (!auth.ok || !auth.apiKey) {
85
+ throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
86
+ }
84
87
 
85
88
  const userMessage: Message = {
86
89
  role: "user",
@@ -96,7 +99,7 @@ export default function (pi: ExtensionAPI) {
96
99
  const response = await complete(
97
100
  ctx.model!,
98
101
  { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
99
- { apiKey, signal: loader.signal },
102
+ { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
100
103
  );
101
104
 
102
105
  if (response.stopReason === "aborted") {
@@ -3,23 +3,24 @@
3
3
  */
4
4
 
5
5
  import { Type } from "@draht/ai";
6
- import type { ExtensionAPI } from "@draht/coding-agent";
6
+ import { defineTool, type ExtensionAPI } from "@draht/coding-agent";
7
7
 
8
- export default function (pi: ExtensionAPI) {
9
- pi.registerTool({
10
- name: "hello",
11
- label: "Hello",
12
- description: "A simple greeting tool",
13
- parameters: Type.Object({
14
- name: Type.String({ description: "Name to greet" }),
15
- }),
8
+ const helloTool = defineTool({
9
+ name: "hello",
10
+ label: "Hello",
11
+ description: "A simple greeting tool",
12
+ parameters: Type.Object({
13
+ name: Type.String({ description: "Name to greet" }),
14
+ }),
15
+
16
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
17
+ return {
18
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
19
+ details: { greeted: params.name },
20
+ };
21
+ },
22
+ });
16
23
 
17
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
18
- const { name } = params as { name: string };
19
- return {
20
- content: [{ type: "text", text: `Hello, ${name}!` }],
21
- details: { greeted: name },
22
- };
23
- },
24
- });
24
+ export default function (pi: ExtensionAPI) {
25
+ pi.registerTool(helloTool);
25
26
  }
@@ -77,7 +77,10 @@ export default function (pi: ExtensionAPI) {
77
77
 
78
78
  // Do the work
79
79
  const doExtract = async () => {
80
- const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
80
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
81
+ if (!auth.ok || !auth.apiKey) {
82
+ throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
83
+ }
81
84
  const userMessage: UserMessage = {
82
85
  role: "user",
83
86
  content: [{ type: "text", text: lastAssistantText! }],
@@ -87,7 +90,7 @@ export default function (pi: ExtensionAPI) {
87
90
  const response = await complete(
88
91
  ctx.model!,
89
92
  { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
90
- { apiKey, signal: loader.signal },
93
+ { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
91
94
  );
92
95
 
93
96
  if (response.stopReason === "aborted") {
@@ -13,7 +13,7 @@
13
13
  * - notify() - after each dialog completes
14
14
  * - setStatus() - on turn_start/turn_end
15
15
  * - setWidget() - on session_start
16
- * - setTitle() - on session_start and session_switch
16
+ * - setTitle() - on session_start
17
17
  * - setEditorText() - via /rpc-prefill command
18
18
  */
19
19
 
@@ -24,18 +24,12 @@ export default function (pi: ExtensionAPI) {
24
24
 
25
25
  // -- setTitle, setWidget, setStatus on session lifecycle --
26
26
 
27
- pi.on("session_start", async (_event, ctx) => {
28
- ctx.ui.setTitle("pi RPC Demo");
27
+ pi.on("session_start", async (event, ctx) => {
28
+ ctx.ui.setTitle(event.reason === "new" ? "pi RPC Demo (new session)" : "pi RPC Demo");
29
29
  ctx.ui.setWidget("rpc-demo", ["--- RPC Extension UI Demo ---", "Loaded and ready."]);
30
30
  ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
31
31
  });
32
32
 
33
- pi.on("session_switch", async (_event, ctx) => {
34
- turnCount = 0;
35
- ctx.ui.setTitle("pi RPC Demo (new session)");
36
- ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
37
- });
38
-
39
33
  // -- setStatus on turn lifecycle --
40
34
 
41
35
  pi.on("turn_start", async (_event, ctx) => {
@@ -29,12 +29,4 @@ export default function (pi: ExtensionAPI) {
29
29
  const text = theme.fg("dim", ` Turn ${turnCount} complete`);
30
30
  ctx.ui.setStatus("status-demo", check + text);
31
31
  });
32
-
33
- pi.on("session_switch", async (event, ctx) => {
34
- if (event.reason === "new") {
35
- turnCount = 0;
36
- const theme = ctx.ui.theme;
37
- ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
38
- }
39
- });
40
32
  }
@@ -229,7 +229,7 @@ function getPiInvocation(args: string[]): { command: string; args: string[] } {
229
229
  return { command: process.execPath, args };
230
230
  }
231
231
 
232
- return { command: "pi", args };
232
+ return { command: "draht", args };
233
233
  }
234
234
 
235
235
  type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;