@thinkwell/conductor 0.2.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.
Files changed (54) hide show
  1. package/README.md +140 -0
  2. package/dist/conductor.d.ts +219 -0
  3. package/dist/conductor.d.ts.map +1 -0
  4. package/dist/conductor.js +960 -0
  5. package/dist/conductor.js.map +1 -0
  6. package/dist/connectors/channel.d.ts +60 -0
  7. package/dist/connectors/channel.d.ts.map +1 -0
  8. package/dist/connectors/channel.js +155 -0
  9. package/dist/connectors/channel.js.map +1 -0
  10. package/dist/connectors/index.d.ts +6 -0
  11. package/dist/connectors/index.d.ts.map +1 -0
  12. package/dist/connectors/index.js +6 -0
  13. package/dist/connectors/index.js.map +1 -0
  14. package/dist/connectors/stdio.d.ts +36 -0
  15. package/dist/connectors/stdio.d.ts.map +1 -0
  16. package/dist/connectors/stdio.js +198 -0
  17. package/dist/connectors/stdio.js.map +1 -0
  18. package/dist/index.d.ts +72 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +76 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/instantiators.d.ts +129 -0
  23. package/dist/instantiators.d.ts.map +1 -0
  24. package/dist/instantiators.js +183 -0
  25. package/dist/instantiators.js.map +1 -0
  26. package/dist/logger.d.ts +121 -0
  27. package/dist/logger.d.ts.map +1 -0
  28. package/dist/logger.js +162 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/mcp-bridge/http-listener.d.ts +35 -0
  31. package/dist/mcp-bridge/http-listener.d.ts.map +1 -0
  32. package/dist/mcp-bridge/http-listener.js +204 -0
  33. package/dist/mcp-bridge/http-listener.js.map +1 -0
  34. package/dist/mcp-bridge/index.d.ts +9 -0
  35. package/dist/mcp-bridge/index.d.ts.map +1 -0
  36. package/dist/mcp-bridge/index.js +8 -0
  37. package/dist/mcp-bridge/index.js.map +1 -0
  38. package/dist/mcp-bridge/mcp-bridge.d.ts +80 -0
  39. package/dist/mcp-bridge/mcp-bridge.d.ts.map +1 -0
  40. package/dist/mcp-bridge/mcp-bridge.js +170 -0
  41. package/dist/mcp-bridge/mcp-bridge.js.map +1 -0
  42. package/dist/mcp-bridge/types.d.ts +69 -0
  43. package/dist/mcp-bridge/types.d.ts.map +1 -0
  44. package/dist/mcp-bridge/types.js +8 -0
  45. package/dist/mcp-bridge/types.js.map +1 -0
  46. package/dist/message-queue.d.ts +46 -0
  47. package/dist/message-queue.d.ts.map +1 -0
  48. package/dist/message-queue.js +90 -0
  49. package/dist/message-queue.js.map +1 -0
  50. package/dist/types.d.ts +129 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +13 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # @thinkwell/conductor
2
+
3
+ TypeScript conductor for ACP proxy chains.
4
+
5
+ ## Overview
6
+
7
+ The conductor orchestrates message routing between clients, proxies, and agents in the Agent Client Protocol (ACP). It sits between every component, managing process lifecycle and message flow.
8
+
9
+ ```
10
+ Client <-> Conductor <-> [Proxy 0] <-> [Proxy 1] <-> ... <-> Agent
11
+ ```
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @thinkwell/conductor
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Basic Usage
22
+
23
+ ```typescript
24
+ import { Conductor, fromCommands, ChannelConnector } from '@thinkwell/conductor';
25
+
26
+ // Create a conductor that spawns an agent subprocess
27
+ const conductor = new Conductor({
28
+ instantiator: fromCommands(['my-agent']),
29
+ });
30
+
31
+ // Connect a client
32
+ const clientConnector = new ChannelConnector(/* your channel */);
33
+ await conductor.connect(clientConnector);
34
+ ```
35
+
36
+ ### With Proxies
37
+
38
+ ```typescript
39
+ const conductor = new Conductor({
40
+ // Last command is the agent, earlier commands are proxies
41
+ instantiator: fromCommands(['my-proxy', 'my-agent']),
42
+ });
43
+ ```
44
+
45
+ ### With Logging
46
+
47
+ ```typescript
48
+ const conductor = new Conductor({
49
+ instantiator: fromCommands(['my-agent']),
50
+ logging: {
51
+ level: 'debug', // 'error' | 'warn' | 'info' | 'debug' | 'trace'
52
+ name: 'my-app',
53
+ json: true, // Output as JSON for machine parsing
54
+ },
55
+ });
56
+ ```
57
+
58
+ ### With JSONL Tracing
59
+
60
+ Write all messages to a JSONL file for debugging:
61
+
62
+ ```typescript
63
+ const conductor = new Conductor({
64
+ instantiator: fromCommands(['my-agent']),
65
+ trace: {
66
+ path: '/tmp/conductor-trace.jsonl',
67
+ },
68
+ });
69
+ ```
70
+
71
+ Each line in the trace file is a JSON object with:
72
+ - `timestamp`: ISO 8601 timestamp
73
+ - `direction`: `'left-to-right'` | `'right-to-left'` | `'internal'`
74
+ - `source`: Source component identifier
75
+ - `target`: Target component identifier
76
+ - `message`: The full `ConductorMessage` object
77
+
78
+ ### Dynamic Component Selection
79
+
80
+ ```typescript
81
+ import { Conductor, dynamic, StdioConnector } from '@thinkwell/conductor';
82
+
83
+ const conductor = new Conductor({
84
+ instantiator: dynamic(async (initRequest) => {
85
+ // Inspect the initialize request to decide what to spawn
86
+ const needsProxy = initRequest.params?.mcpServers?.length > 0;
87
+
88
+ return {
89
+ proxies: needsProxy ? [new StdioConnector('my-proxy')] : [],
90
+ agent: new StdioConnector('my-agent'),
91
+ };
92
+ }),
93
+ });
94
+ ```
95
+
96
+ ## API Reference
97
+
98
+ ### Conductor
99
+
100
+ Main class that orchestrates the proxy chain.
101
+
102
+ #### Constructor Options
103
+
104
+ | Option | Type | Description |
105
+ |--------|------|-------------|
106
+ | `name` | `string` | Optional name for debugging |
107
+ | `instantiator` | `ComponentInstantiator` | Creates components on initialization |
108
+ | `mcpBridgeMode` | `'http' \| 'disabled'` | MCP bridge mode (default: `'http'`) |
109
+ | `logging` | `LoggerOptions` | Logging configuration |
110
+ | `trace` | `TraceOptions` | JSONL trace output configuration |
111
+
112
+ #### Methods
113
+
114
+ - `connect(clientConnector)`: Connect to a client and run the message loop
115
+ - `shutdown()`: Shut down the conductor and all components
116
+
117
+ ### Instantiators
118
+
119
+ Factory functions for creating component configurations:
120
+
121
+ - `fromCommands(commands)`: Create from a list of command strings (last is agent)
122
+ - `fromConnectors({ proxies, agent })`: Create from explicit connectors
123
+ - `staticInstantiator(config)`: Create with detailed options
124
+ - `dynamic(factory)`: Create dynamically based on initialize request
125
+
126
+ ### Connectors
127
+
128
+ - `StdioConnector`: Spawns a subprocess and communicates via stdin/stdout
129
+ - `ChannelConnector`: In-memory channel for testing
130
+
131
+ ### Logging
132
+
133
+ - `createLogger(options)`: Create a logger instance
134
+ - `createNoopLogger()`: Create a logger that discards all output
135
+ - `getLogger()`: Get the default logger
136
+ - `setLogger(logger)`: Set the default logger
137
+
138
+ ## License
139
+
140
+ See repository root for license information.
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Conductor - orchestrates ACP proxy chains
3
+ *
4
+ * The conductor sits between a client and an agent, managing message routing
5
+ * through a chain of proxy components. It:
6
+ *
7
+ * 1. Manages the message event loop
8
+ * 2. Routes messages left-to-right (client → proxies → agent)
9
+ * 3. Routes messages right-to-left (agent → proxies → client)
10
+ * 4. Handles `_proxy/successor/*` message wrapping/unwrapping
11
+ * 5. Manages proxy capability handshake during initialization
12
+ * 6. Correlates requests with responses via a pending request map
13
+ * 7. Bridges MCP-over-ACP for agents without native ACP transport support
14
+ */
15
+ import type { ComponentConnector, ComponentInstantiator } from "./types.js";
16
+ import { type LoggerOptions, type TraceOptions } from "./logger.js";
17
+ /**
18
+ * Configuration for the Conductor
19
+ */
20
+ export interface ConductorConfig {
21
+ /** Optional name for this conductor (for debugging) */
22
+ name?: string;
23
+ /** The instantiator that creates components when initialization arrives */
24
+ instantiator: ComponentInstantiator;
25
+ /** MCP bridge mode (disabled by default for Phase 2) */
26
+ mcpBridgeMode?: "http" | "disabled";
27
+ /** Logging configuration */
28
+ logging?: LoggerOptions;
29
+ /** JSONL trace output configuration */
30
+ trace?: TraceOptions;
31
+ }
32
+ /**
33
+ * The Conductor orchestrates ACP proxy chains.
34
+ *
35
+ * It sits between a client and an agent, routing all messages through
36
+ * a central event loop to preserve message ordering.
37
+ *
38
+ * ## Message Flow with Proxies
39
+ *
40
+ * ### Left-to-Right (client → agent):
41
+ * 1. Client sends request to conductor
42
+ * 2. Conductor forwards to proxy[0] (normal ACP)
43
+ * 3. Proxy[0] sends `_proxy/successor/request` to conductor
44
+ * 4. Conductor unwraps and forwards to proxy[1] (normal ACP)
45
+ * 5. ... until agent receives normal ACP
46
+ *
47
+ * ### Right-to-Left (agent → client):
48
+ * 1. Agent sends notification/request to conductor
49
+ * 2. Conductor wraps in `_proxy/successor/request` and sends to proxy[n-1]
50
+ * 3. Proxy[n-1] processes and forwards to conductor
51
+ * 4. ... until client receives normal ACP
52
+ */
53
+ export declare class Conductor {
54
+ private readonly config;
55
+ private readonly messageQueue;
56
+ private readonly logger;
57
+ private state;
58
+ private clientConnection;
59
+ private proxies;
60
+ private agentConnection;
61
+ private pendingRequests;
62
+ private nextRequestId;
63
+ private mcpBridge;
64
+ private agentSupportsMcpAcpTransport;
65
+ private pendingSessionRequests;
66
+ private mcpConnectResponders;
67
+ constructor(config: ConductorConfig);
68
+ /**
69
+ * Connect to a client and run the conductor's message loop.
70
+ *
71
+ * This method blocks until the conductor shuts down.
72
+ */
73
+ connect(clientConnector: ComponentConnector): Promise<void>;
74
+ /**
75
+ * Shut down the conductor
76
+ */
77
+ shutdown(): Promise<void>;
78
+ /**
79
+ * Pump messages from the client into the message queue
80
+ */
81
+ private pumpClientMessages;
82
+ /**
83
+ * Pump messages from a proxy into the message queue
84
+ */
85
+ private pumpProxyMessages;
86
+ /**
87
+ * Pump messages from the agent into the message queue
88
+ */
89
+ private pumpAgentMessages;
90
+ /**
91
+ * Handle a `_proxy/successor/request` from a proxy
92
+ *
93
+ * The proxy is forwarding a request to its successor (next proxy or agent).
94
+ * We unwrap the inner request and forward it.
95
+ */
96
+ private handleProxySuccessorRequest;
97
+ /**
98
+ * Handle a `_proxy/successor/notification` from a proxy
99
+ *
100
+ * The proxy is forwarding a notification to its successor.
101
+ */
102
+ private handleProxySuccessorNotification;
103
+ /**
104
+ * Convert a JSON-RPC message to a Dispatch
105
+ */
106
+ private messageToDispatch;
107
+ /**
108
+ * Create a responder that routes the response back to the appropriate destination
109
+ */
110
+ private createResponderForSource;
111
+ /**
112
+ * Run the main event loop, processing messages from the queue
113
+ */
114
+ private runEventLoop;
115
+ /**
116
+ * Handle a message from the queue
117
+ */
118
+ private handleMessage;
119
+ /**
120
+ * Get the source identifier for a message (for tracing)
121
+ */
122
+ private getMessageSource;
123
+ /**
124
+ * Get the target identifier for a message (for tracing)
125
+ */
126
+ private getMessageTarget;
127
+ /**
128
+ * Handle a left-to-right message (client → agent direction)
129
+ */
130
+ private handleLeftToRight;
131
+ /**
132
+ * Handle a right-to-left message (agent/proxy → client direction)
133
+ */
134
+ private handleRightToLeft;
135
+ /**
136
+ * Forward a dispatch to a proxy, wrapped in `_proxy/successor/*`
137
+ *
138
+ * This is used when routing messages FROM a successor (agent or later proxy)
139
+ * TO an earlier proxy in the chain.
140
+ */
141
+ private forwardWrappedToProxy;
142
+ /**
143
+ * Handle the initialize request - instantiate components and perform initialization sequence
144
+ *
145
+ * The initialization follows this sequence:
146
+ * 1. Instantiate all components (connect to proxies and agent)
147
+ * 2. Send `initialize` with `_meta.proxy: true` to proxy[0]
148
+ * 3. Proxy[0] will use `_proxy/successor/request` to forward to proxy[1], etc.
149
+ * 4. Agent receives `initialize` without proxy capability
150
+ * 5. Responses flow back up the chain
151
+ * 6. Conductor verifies each proxy accepted the proxy capability
152
+ */
153
+ private handleInitialize;
154
+ /**
155
+ * Add proxy capability to initialize params
156
+ */
157
+ private addProxyCapability;
158
+ /**
159
+ * Remove proxy capability from initialize params (for forwarding to agent)
160
+ */
161
+ private removeProxyCapability;
162
+ /**
163
+ * Create a responder that verifies the proxy accepted the capability
164
+ */
165
+ private createProxyInitializeResponder;
166
+ /**
167
+ * Create a responder that captures the agent's mcp_acp_transport capability
168
+ */
169
+ private createAgentInitializeResponder;
170
+ /**
171
+ * Handle a response by routing it back to the original requester
172
+ */
173
+ private handleResponse;
174
+ /**
175
+ * Forward a dispatch to a connection, handling request ID rewriting
176
+ */
177
+ private forwardToConnection;
178
+ /**
179
+ * Get the target connection for a given index
180
+ * Index 0..n-1 are proxies, index n is the agent
181
+ */
182
+ private getTargetConnection;
183
+ /**
184
+ * Handle a session/new request - transform acp: URLs to http: URLs
185
+ *
186
+ * If the request contains MCP servers with acp: URLs and the agent doesn't
187
+ * support mcp_acp_transport, we:
188
+ * 1. Spawn HTTP listeners for each acp: URL
189
+ * 2. Transform the URLs to http://localhost:$PORT
190
+ * 3. Forward the modified request to the agent
191
+ * 4. When the response comes back with session_id, deliver it to the listeners
192
+ */
193
+ private handleSessionNew;
194
+ /**
195
+ * Handle an MCP connection received from the HTTP bridge
196
+ *
197
+ * When an agent connects to our HTTP listener, we need to:
198
+ * 1. Send _mcp/connect to the proxy that owns this acp: URL
199
+ * 2. Wait for the connection_id in the response
200
+ */
201
+ private handleMcpConnectionReceived;
202
+ /**
203
+ * Handle an MCP message from a client (through the HTTP bridge)
204
+ *
205
+ * This routes MCP tool calls and other messages through the ACP chain.
206
+ */
207
+ private handleMcpClientToServer;
208
+ /**
209
+ * Handle an MCP connection being disconnected
210
+ */
211
+ private handleMcpConnectionDisconnected;
212
+ /**
213
+ * Generate a unique request ID for outgoing requests
214
+ */
215
+ private generateRequestId;
216
+ }
217
+ export { fromCommands, fromConnectors, dynamic, staticInstantiator } from "./instantiators.js";
218
+ export type { CommandSpec, CommandOptions, StaticInstantiatorConfig, DynamicInstantiatorFactory } from "./instantiators.js";
219
+ //# sourceMappingURL=conductor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conductor.d.ts","sourceRoot":"","sources":["../src/conductor.ts"],"names":[],"mappings":"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"}