@deepstrike/sdk 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -51
- package/dist/collaboration/harness.js +6 -1
- package/dist/collaboration/modes/creator-verifier.d.ts +2 -2
- package/dist/collaboration/modes/creator-verifier.js +2 -2
- package/dist/collaboration/pool.d.ts +4 -35
- package/dist/collaboration/pool.js +18 -44
- package/dist/harness/harness.d.ts +9 -7
- package/dist/harness/harness.js +19 -22
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -2
- package/dist/kernel.d.ts +1 -0
- package/dist/kernel.js +10 -1
- package/dist/memory/protocols.d.ts +0 -4
- package/dist/providers/anthropic.d.ts +2 -1
- package/dist/providers/anthropic.js +19 -1
- package/dist/providers/deepseek.d.ts +3 -2
- package/dist/providers/deepseek.js +9 -0
- package/dist/providers/gemini.d.ts +2 -1
- package/dist/providers/gemini.js +11 -0
- package/dist/providers/kimi.d.ts +3 -1
- package/dist/providers/kimi.js +10 -0
- package/dist/providers/minimax.d.ts +3 -1
- package/dist/providers/minimax.js +8 -0
- package/dist/providers/ollama.d.ts +2 -1
- package/dist/providers/ollama.js +22 -0
- package/dist/providers/openai-responses.d.ts +1 -0
- package/dist/providers/openai-responses.js +13 -0
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.js +17 -0
- package/dist/providers/profiles.d.ts +502 -6
- package/dist/providers/profiles.js +225 -82
- package/dist/providers/qwen.d.ts +2 -1
- package/dist/providers/qwen.js +15 -0
- package/dist/runtime/credential-vault.d.ts +18 -0
- package/dist/runtime/credential-vault.js +33 -0
- package/dist/runtime/execution-plane.d.ts +38 -0
- package/dist/runtime/execution-plane.js +146 -0
- package/dist/runtime/mcp-proxy-plane.d.ts +51 -0
- package/dist/runtime/mcp-proxy-plane.js +204 -0
- package/dist/runtime/process-sandbox-plane.d.ts +32 -0
- package/dist/runtime/process-sandbox-plane.js +109 -0
- package/dist/runtime/remote-vpc-plane.d.ts +48 -0
- package/dist/runtime/remote-vpc-plane.js +82 -0
- package/dist/runtime/runner.d.ts +49 -0
- package/dist/runtime/runner.js +364 -0
- package/dist/runtime/session-log.d.ts +67 -0
- package/dist/runtime/session-log.js +84 -0
- package/dist/types.d.ts +16 -0
- package/package.json +3 -2
- package/dist/agent.d.ts +0 -90
- package/dist/agent.js +0 -499
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ToolCall, ToolSchema, StreamEvent } from "../types.js";
|
|
2
|
+
import type { RegisteredTool } from "../tools/index.js";
|
|
3
|
+
import type { ExecutionPlane, RunContext } from "./execution-plane.js";
|
|
4
|
+
import type { CredentialVault } from "./credential-vault.js";
|
|
5
|
+
export interface McpServerConfig {
|
|
6
|
+
/** Executable to run (e.g. "npx", "python3", "/usr/local/bin/my-mcp-server"). */
|
|
7
|
+
command: string;
|
|
8
|
+
/** Command-line arguments. */
|
|
9
|
+
args?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Keys to look up in the vault and inject as env vars with the same name.
|
|
12
|
+
* The credential value is never exposed to the model — only to the subprocess.
|
|
13
|
+
*/
|
|
14
|
+
credentialKeys?: string[];
|
|
15
|
+
/** Additional static env vars forwarded to the subprocess. */
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* ExecutionPlane that proxies tool calls to MCP servers.
|
|
20
|
+
*
|
|
21
|
+
* Credentials live in a CredentialVault and are injected into each server's
|
|
22
|
+
* subprocess environment — the model never sees the credential values.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* const plane = new McpProxyPlane({
|
|
26
|
+
* servers: { brave: { command: "npx", args: ["-y", "@modelcontextprotocol/server-brave-search"], credentialKeys: ["BRAVE_API_KEY"] } },
|
|
27
|
+
* vault: new EnvCredentialVault(),
|
|
28
|
+
* })
|
|
29
|
+
* await plane.connect()
|
|
30
|
+
* // ... use with RuntimeRunner ...
|
|
31
|
+
* await plane.disconnect()
|
|
32
|
+
*/
|
|
33
|
+
export declare class McpProxyPlane implements ExecutionPlane {
|
|
34
|
+
private readonly opts;
|
|
35
|
+
private connections;
|
|
36
|
+
private toolToConn;
|
|
37
|
+
private localPlane;
|
|
38
|
+
private localNames;
|
|
39
|
+
constructor(opts: {
|
|
40
|
+
servers: Record<string, McpServerConfig>;
|
|
41
|
+
vault: CredentialVault;
|
|
42
|
+
});
|
|
43
|
+
/** Start all configured MCP server processes and discover their tool schemas. */
|
|
44
|
+
connect(): Promise<void>;
|
|
45
|
+
/** Gracefully stop all MCP server processes. */
|
|
46
|
+
disconnect(): Promise<void>;
|
|
47
|
+
register(...tools: RegisteredTool[]): this;
|
|
48
|
+
unregister(name: string): this;
|
|
49
|
+
schemas(): ToolSchema[];
|
|
50
|
+
executeAll(calls: ToolCall[], ctx: RunContext): AsyncIterable<StreamEvent>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { LocalExecutionPlane } from "./execution-plane.js";
|
|
4
|
+
class McpConnection {
|
|
5
|
+
serverName;
|
|
6
|
+
config;
|
|
7
|
+
vault;
|
|
8
|
+
child = null;
|
|
9
|
+
pending = new Map();
|
|
10
|
+
nextId = 1;
|
|
11
|
+
_schemas = [];
|
|
12
|
+
schemaNames = new Set();
|
|
13
|
+
constructor(serverName, config, vault) {
|
|
14
|
+
this.serverName = serverName;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.vault = vault;
|
|
17
|
+
}
|
|
18
|
+
async start() {
|
|
19
|
+
const env = { ...process.env, ...(this.config.env ?? {}) };
|
|
20
|
+
for (const key of this.config.credentialKeys ?? []) {
|
|
21
|
+
const val = await this.vault.get(key);
|
|
22
|
+
if (val !== undefined)
|
|
23
|
+
env[key] = val;
|
|
24
|
+
}
|
|
25
|
+
this.child = spawn(this.config.command, this.config.args ?? [], {
|
|
26
|
+
env,
|
|
27
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
28
|
+
});
|
|
29
|
+
const rl = createInterface({ input: this.child.stdout, crlfDelay: Infinity });
|
|
30
|
+
rl.on("line", line => {
|
|
31
|
+
if (!line.trim())
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
const msg = JSON.parse(line);
|
|
35
|
+
const cb = this.pending.get(msg.id);
|
|
36
|
+
if (!cb)
|
|
37
|
+
return;
|
|
38
|
+
this.pending.delete(msg.id);
|
|
39
|
+
if (msg.error)
|
|
40
|
+
cb.reject(new Error(`MCP(${this.serverName}) ${msg.error.code}: ${msg.error.message}`));
|
|
41
|
+
else
|
|
42
|
+
cb.resolve(msg.result);
|
|
43
|
+
}
|
|
44
|
+
catch { /* ignore malformed lines */ }
|
|
45
|
+
});
|
|
46
|
+
// MCP handshake
|
|
47
|
+
await this.request("initialize", {
|
|
48
|
+
protocolVersion: "2024-11-05",
|
|
49
|
+
capabilities: { tools: {} },
|
|
50
|
+
clientInfo: { name: "deepstrike", version: "0.1.0" },
|
|
51
|
+
});
|
|
52
|
+
// initialized is a notification (no id, no response expected)
|
|
53
|
+
this.notify("notifications/initialized");
|
|
54
|
+
const listResult = await this.request("tools/list");
|
|
55
|
+
for (const t of listResult.tools ?? []) {
|
|
56
|
+
const schema = {
|
|
57
|
+
name: t.name,
|
|
58
|
+
description: t.description ?? t.name,
|
|
59
|
+
parameters: JSON.stringify(t.inputSchema ?? { type: "object", properties: {} }),
|
|
60
|
+
};
|
|
61
|
+
this._schemas.push(schema);
|
|
62
|
+
this.schemaNames.add(t.name);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
request(method, params) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
if (!this.child?.stdin) {
|
|
68
|
+
reject(new Error(`MCP server "${this.serverName}" not running`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const id = this.nextId++;
|
|
72
|
+
this.pending.set(id, { resolve, reject });
|
|
73
|
+
const msg = { jsonrpc: "2.0", method, id };
|
|
74
|
+
if (params !== undefined)
|
|
75
|
+
msg.params = params;
|
|
76
|
+
this.child.stdin.write(JSON.stringify(msg) + "\n");
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
notify(method, params) {
|
|
80
|
+
if (!this.child?.stdin)
|
|
81
|
+
return;
|
|
82
|
+
const msg = { jsonrpc: "2.0", method };
|
|
83
|
+
if (params !== undefined)
|
|
84
|
+
msg.params = params;
|
|
85
|
+
this.child.stdin.write(JSON.stringify(msg) + "\n");
|
|
86
|
+
}
|
|
87
|
+
schemas() { return this._schemas; }
|
|
88
|
+
hasSchema(name) { return this.schemaNames.has(name); }
|
|
89
|
+
async execute(call) {
|
|
90
|
+
try {
|
|
91
|
+
const args = JSON.parse(call.arguments || "{}");
|
|
92
|
+
const result = await this.request("tools/call", { name: call.name, arguments: args });
|
|
93
|
+
const text = (result.content ?? [])
|
|
94
|
+
.filter(c => c.type === "text")
|
|
95
|
+
.map(c => c.text ?? "")
|
|
96
|
+
.join("\n");
|
|
97
|
+
return { output: text || JSON.stringify(result), isError: result.isError ?? false };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return { output: String(err), isError: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async stop() {
|
|
104
|
+
this.child?.kill();
|
|
105
|
+
this.child = null;
|
|
106
|
+
for (const cb of this.pending.values())
|
|
107
|
+
cb.reject(new Error(`MCP server "${this.serverName}" stopped`));
|
|
108
|
+
this.pending.clear();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ── Public plane ──────────────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* ExecutionPlane that proxies tool calls to MCP servers.
|
|
114
|
+
*
|
|
115
|
+
* Credentials live in a CredentialVault and are injected into each server's
|
|
116
|
+
* subprocess environment — the model never sees the credential values.
|
|
117
|
+
*
|
|
118
|
+
* Usage:
|
|
119
|
+
* const plane = new McpProxyPlane({
|
|
120
|
+
* servers: { brave: { command: "npx", args: ["-y", "@modelcontextprotocol/server-brave-search"], credentialKeys: ["BRAVE_API_KEY"] } },
|
|
121
|
+
* vault: new EnvCredentialVault(),
|
|
122
|
+
* })
|
|
123
|
+
* await plane.connect()
|
|
124
|
+
* // ... use with RuntimeRunner ...
|
|
125
|
+
* await plane.disconnect()
|
|
126
|
+
*/
|
|
127
|
+
export class McpProxyPlane {
|
|
128
|
+
opts;
|
|
129
|
+
connections = new Map();
|
|
130
|
+
toolToConn = new Map();
|
|
131
|
+
localPlane = new LocalExecutionPlane();
|
|
132
|
+
localNames = new Set();
|
|
133
|
+
constructor(opts) {
|
|
134
|
+
this.opts = opts;
|
|
135
|
+
}
|
|
136
|
+
/** Start all configured MCP server processes and discover their tool schemas. */
|
|
137
|
+
async connect() {
|
|
138
|
+
for (const [name, config] of Object.entries(this.opts.servers)) {
|
|
139
|
+
const conn = new McpConnection(name, config, this.opts.vault);
|
|
140
|
+
await conn.start();
|
|
141
|
+
this.connections.set(name, conn);
|
|
142
|
+
for (const schema of conn.schemas())
|
|
143
|
+
this.toolToConn.set(schema.name, conn);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Gracefully stop all MCP server processes. */
|
|
147
|
+
async disconnect() {
|
|
148
|
+
for (const conn of this.connections.values())
|
|
149
|
+
await conn.stop();
|
|
150
|
+
this.connections.clear();
|
|
151
|
+
this.toolToConn.clear();
|
|
152
|
+
}
|
|
153
|
+
register(...tools) {
|
|
154
|
+
this.localPlane.register(...tools);
|
|
155
|
+
for (const t of tools)
|
|
156
|
+
this.localNames.add(t.schema.name);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
unregister(name) {
|
|
160
|
+
this.localPlane.unregister(name);
|
|
161
|
+
this.localNames.delete(name);
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
schemas() {
|
|
165
|
+
const mcp = [];
|
|
166
|
+
for (const conn of this.connections.values())
|
|
167
|
+
mcp.push(...conn.schemas());
|
|
168
|
+
return [...this.localPlane.schemas(), ...mcp];
|
|
169
|
+
}
|
|
170
|
+
async *executeAll(calls, ctx) {
|
|
171
|
+
const localCalls = calls.filter(c => this.localNames.has(c.name));
|
|
172
|
+
const mcpCalls = calls.filter(c => !this.localNames.has(c.name));
|
|
173
|
+
if (localCalls.length > 0)
|
|
174
|
+
yield* this.localPlane.executeAll(localCalls, ctx);
|
|
175
|
+
// MCP calls are inherently sequential per server (JSON-RPC request/response)
|
|
176
|
+
// but different servers can be called concurrently.
|
|
177
|
+
const groups = new Map();
|
|
178
|
+
const unknown = [];
|
|
179
|
+
for (const call of mcpCalls) {
|
|
180
|
+
const conn = this.toolToConn.get(call.name);
|
|
181
|
+
if (!conn) {
|
|
182
|
+
unknown.push(call);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (!groups.has(conn))
|
|
186
|
+
groups.set(conn, []);
|
|
187
|
+
groups.get(conn).push(call);
|
|
188
|
+
}
|
|
189
|
+
for (const call of unknown) {
|
|
190
|
+
yield { type: "tool_result", callId: call.id, name: call.name, content: `unknown MCP tool: ${call.name}`, isError: true };
|
|
191
|
+
}
|
|
192
|
+
const tasks = Array.from(groups.entries()).map(async ([conn, serverCalls]) => {
|
|
193
|
+
const results = [];
|
|
194
|
+
for (const call of serverCalls)
|
|
195
|
+
results.push({ call, result: await conn.execute(call) });
|
|
196
|
+
return results;
|
|
197
|
+
});
|
|
198
|
+
for (const settled of await Promise.all(tasks)) {
|
|
199
|
+
for (const { call, result } of settled) {
|
|
200
|
+
yield { type: "tool_result", callId: call.id, name: call.name, content: result.output, isError: result.isError };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { LocalExecutionPlane } from "./execution-plane.js";
|
|
2
|
+
export interface SandboxOptions {
|
|
3
|
+
/** Working directory for all subprocesses. Isolated from the rest of the file system by convention. */
|
|
4
|
+
sandboxDir: string;
|
|
5
|
+
/** Env var names from the host environment to forward into subprocesses. Default: none. */
|
|
6
|
+
allowedEnvKeys?: string[];
|
|
7
|
+
/** Per-call hard timeout in ms. Defaults to 30 000. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Truncate stdout+stderr after this many bytes. Defaults to 1 MiB. */
|
|
10
|
+
maxOutputBytes?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* ExecutionPlane that confines subprocess execution to a sandbox directory.
|
|
14
|
+
* Extends LocalExecutionPlane with two built-in tools:
|
|
15
|
+
* - `run_bash` — executes a bash command inside sandboxDir.
|
|
16
|
+
* - `run_node` — evaluates a Node.js script inside sandboxDir.
|
|
17
|
+
*
|
|
18
|
+
* All registered JS tools continue to run in-process (identical to LocalExecutionPlane).
|
|
19
|
+
* The sandboxing guarantee is: subprocesses spawned by the built-in tools cannot reach
|
|
20
|
+
* files outside sandboxDir unless the host OS allows it.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ProcessSandboxPlane extends LocalExecutionPlane {
|
|
23
|
+
private readonly sandboxDir;
|
|
24
|
+
private readonly allowedEnvKeys;
|
|
25
|
+
private readonly timeoutMs;
|
|
26
|
+
private readonly maxOutputBytes;
|
|
27
|
+
constructor(opts: SandboxOptions);
|
|
28
|
+
private buildEnv;
|
|
29
|
+
private runSubprocess;
|
|
30
|
+
private makeBashTool;
|
|
31
|
+
private makeNodeTool;
|
|
32
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { tool } from "../tools/index.js";
|
|
4
|
+
import { LocalExecutionPlane } from "./execution-plane.js";
|
|
5
|
+
/**
|
|
6
|
+
* ExecutionPlane that confines subprocess execution to a sandbox directory.
|
|
7
|
+
* Extends LocalExecutionPlane with two built-in tools:
|
|
8
|
+
* - `run_bash` — executes a bash command inside sandboxDir.
|
|
9
|
+
* - `run_node` — evaluates a Node.js script inside sandboxDir.
|
|
10
|
+
*
|
|
11
|
+
* All registered JS tools continue to run in-process (identical to LocalExecutionPlane).
|
|
12
|
+
* The sandboxing guarantee is: subprocesses spawned by the built-in tools cannot reach
|
|
13
|
+
* files outside sandboxDir unless the host OS allows it.
|
|
14
|
+
*/
|
|
15
|
+
export class ProcessSandboxPlane extends LocalExecutionPlane {
|
|
16
|
+
sandboxDir;
|
|
17
|
+
allowedEnvKeys;
|
|
18
|
+
timeoutMs;
|
|
19
|
+
maxOutputBytes;
|
|
20
|
+
constructor(opts) {
|
|
21
|
+
super();
|
|
22
|
+
this.sandboxDir = opts.sandboxDir;
|
|
23
|
+
this.allowedEnvKeys = opts.allowedEnvKeys ?? [];
|
|
24
|
+
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
25
|
+
this.maxOutputBytes = opts.maxOutputBytes ?? 1_048_576;
|
|
26
|
+
super.register(this.makeBashTool(), this.makeNodeTool());
|
|
27
|
+
}
|
|
28
|
+
buildEnv() {
|
|
29
|
+
const env = {
|
|
30
|
+
HOME: this.sandboxDir,
|
|
31
|
+
TMPDIR: this.sandboxDir,
|
|
32
|
+
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
33
|
+
};
|
|
34
|
+
for (const key of this.allowedEnvKeys) {
|
|
35
|
+
const val = process.env[key];
|
|
36
|
+
if (val !== undefined)
|
|
37
|
+
env[key] = val;
|
|
38
|
+
}
|
|
39
|
+
return env;
|
|
40
|
+
}
|
|
41
|
+
runSubprocess(cmd, argv) {
|
|
42
|
+
return new Promise(resolve => {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
let totalBytes = 0;
|
|
45
|
+
let settled = false;
|
|
46
|
+
const settle = (output, isError) => {
|
|
47
|
+
if (settled)
|
|
48
|
+
return;
|
|
49
|
+
settled = true;
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
resolve({ output, isError });
|
|
52
|
+
};
|
|
53
|
+
const child = spawn(cmd, argv, {
|
|
54
|
+
cwd: this.sandboxDir,
|
|
55
|
+
env: this.buildEnv(),
|
|
56
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
child.kill("SIGKILL");
|
|
60
|
+
settle(`timed out after ${this.timeoutMs}ms`, true);
|
|
61
|
+
}, this.timeoutMs);
|
|
62
|
+
const capture = (chunk) => {
|
|
63
|
+
if (settled)
|
|
64
|
+
return;
|
|
65
|
+
totalBytes += chunk.length;
|
|
66
|
+
if (totalBytes > this.maxOutputBytes) {
|
|
67
|
+
chunks.push(Buffer.from("\n[output truncated]"));
|
|
68
|
+
child.kill("SIGKILL");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
chunks.push(chunk);
|
|
72
|
+
};
|
|
73
|
+
child.stdout.on("data", capture);
|
|
74
|
+
child.stderr.on("data", capture);
|
|
75
|
+
child.on("close", code => settle(Buffer.concat(chunks).toString("utf8"), code !== 0));
|
|
76
|
+
child.on("error", err => settle(String(err), true));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
makeBashTool() {
|
|
80
|
+
return tool("run_bash", "Run a bash command confined to the sandbox directory. Paths outside the sandbox are not accessible.", {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
command: { type: "string", description: "The bash command to execute." },
|
|
84
|
+
},
|
|
85
|
+
required: ["command"],
|
|
86
|
+
}, async (args) => {
|
|
87
|
+
await mkdir(this.sandboxDir, { recursive: true });
|
|
88
|
+
const { output, isError } = await this.runSubprocess("bash", ["-c", String(args.command)]);
|
|
89
|
+
if (isError && !output.trim())
|
|
90
|
+
return "Process exited with non-zero status and produced no output.";
|
|
91
|
+
return output || "(no output)";
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
makeNodeTool() {
|
|
95
|
+
return tool("run_node", "Evaluate a Node.js script confined to the sandbox directory.", {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
code: { type: "string", description: "The JavaScript code to evaluate." },
|
|
99
|
+
},
|
|
100
|
+
required: ["code"],
|
|
101
|
+
}, async (args) => {
|
|
102
|
+
await mkdir(this.sandboxDir, { recursive: true });
|
|
103
|
+
const { output, isError } = await this.runSubprocess("node", ["-e", String(args.code)]);
|
|
104
|
+
if (isError && !output.trim())
|
|
105
|
+
return "Script exited with non-zero status and produced no output.";
|
|
106
|
+
return output || "(no output)";
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ToolCall, ToolSchema, StreamEvent } from "../types.js";
|
|
2
|
+
import type { RegisteredTool } from "../tools/index.js";
|
|
3
|
+
import type { ExecutionPlane, RunContext } from "./execution-plane.js";
|
|
4
|
+
import type { CredentialVault } from "./credential-vault.js";
|
|
5
|
+
export interface RemoteVpcOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Base URL of the remote worker endpoint inside the customer VPC.
|
|
8
|
+
* Expected routes:
|
|
9
|
+
* POST {baseUrl}/execute body: { name, arguments } response: { output, isError }
|
|
10
|
+
* GET {baseUrl}/schemas response: ToolSchema[] (optional — or provide `schemas` statically)
|
|
11
|
+
*/
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
vault: CredentialVault;
|
|
14
|
+
/**
|
|
15
|
+
* Vault key whose value is sent verbatim as the Authorization header.
|
|
16
|
+
* The credential is fetched once per executeAll call (not cached across calls).
|
|
17
|
+
*/
|
|
18
|
+
authCredentialKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Static tool schemas served by this VPC worker.
|
|
21
|
+
* Provide these when the remote /schemas route is unavailable or schema drift
|
|
22
|
+
* is undesirable at runtime.
|
|
23
|
+
*/
|
|
24
|
+
schemas: ToolSchema[];
|
|
25
|
+
/** Per-call HTTP timeout in ms. Defaults to 30 000. */
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* ExecutionPlane that forwards tool calls over HTTP to a worker inside a customer VPC.
|
|
30
|
+
*
|
|
31
|
+
* Credentials are stored in a CredentialVault and injected into HTTP headers at call
|
|
32
|
+
* time — they are never forwarded to the model or stored in the session log.
|
|
33
|
+
*
|
|
34
|
+
* Local tools (registered via `register()`) run in-process and take priority over
|
|
35
|
+
* any remote schema with the same name.
|
|
36
|
+
*/
|
|
37
|
+
export declare class RemoteVpcPlane implements ExecutionPlane {
|
|
38
|
+
private readonly opts;
|
|
39
|
+
private readonly remoteSchemas;
|
|
40
|
+
private readonly remoteNames;
|
|
41
|
+
private localPlane;
|
|
42
|
+
constructor(opts: RemoteVpcOptions);
|
|
43
|
+
register(...tools: RegisteredTool[]): this;
|
|
44
|
+
unregister(name: string): this;
|
|
45
|
+
schemas(): ToolSchema[];
|
|
46
|
+
executeAll(calls: ToolCall[], ctx: RunContext): AsyncIterable<StreamEvent>;
|
|
47
|
+
private callRemote;
|
|
48
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { LocalExecutionPlane } from "./execution-plane.js";
|
|
2
|
+
/**
|
|
3
|
+
* ExecutionPlane that forwards tool calls over HTTP to a worker inside a customer VPC.
|
|
4
|
+
*
|
|
5
|
+
* Credentials are stored in a CredentialVault and injected into HTTP headers at call
|
|
6
|
+
* time — they are never forwarded to the model or stored in the session log.
|
|
7
|
+
*
|
|
8
|
+
* Local tools (registered via `register()`) run in-process and take priority over
|
|
9
|
+
* any remote schema with the same name.
|
|
10
|
+
*/
|
|
11
|
+
export class RemoteVpcPlane {
|
|
12
|
+
opts;
|
|
13
|
+
remoteSchemas;
|
|
14
|
+
remoteNames;
|
|
15
|
+
localPlane = new LocalExecutionPlane();
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.opts = opts;
|
|
18
|
+
this.remoteSchemas = opts.schemas;
|
|
19
|
+
this.remoteNames = new Set(opts.schemas.map(s => s.name));
|
|
20
|
+
}
|
|
21
|
+
register(...tools) {
|
|
22
|
+
this.localPlane.register(...tools);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
unregister(name) {
|
|
26
|
+
this.localPlane.unregister(name);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
schemas() {
|
|
30
|
+
// Local tools shadow remote tools with the same name
|
|
31
|
+
const localNames = new Set(this.localPlane.schemas().map(s => s.name));
|
|
32
|
+
const remoteVisible = this.remoteSchemas.filter(s => !localNames.has(s.name));
|
|
33
|
+
return [...this.localPlane.schemas(), ...remoteVisible];
|
|
34
|
+
}
|
|
35
|
+
async *executeAll(calls, ctx) {
|
|
36
|
+
const localSchemaNames = new Set(this.localPlane.schemas().map(s => s.name));
|
|
37
|
+
const localCalls = calls.filter(c => localSchemaNames.has(c.name));
|
|
38
|
+
const remoteCalls = calls.filter(c => !localSchemaNames.has(c.name));
|
|
39
|
+
if (localCalls.length > 0)
|
|
40
|
+
yield* this.localPlane.executeAll(localCalls, ctx);
|
|
41
|
+
if (remoteCalls.length === 0)
|
|
42
|
+
return;
|
|
43
|
+
const auth = this.opts.authCredentialKey
|
|
44
|
+
? await this.opts.vault.get(this.opts.authCredentialKey)
|
|
45
|
+
: undefined;
|
|
46
|
+
const headers = { "Content-Type": "application/json" };
|
|
47
|
+
if (auth)
|
|
48
|
+
headers["Authorization"] = auth;
|
|
49
|
+
// Fire all remote calls concurrently; yield results in dispatch order
|
|
50
|
+
const pending = remoteCalls.map(call => this.callRemote(call, headers).then(result => ({ call, result })));
|
|
51
|
+
for (const p of pending) {
|
|
52
|
+
const { call, result } = await p;
|
|
53
|
+
yield {
|
|
54
|
+
type: "tool_result",
|
|
55
|
+
callId: call.id,
|
|
56
|
+
name: call.name,
|
|
57
|
+
content: result.output,
|
|
58
|
+
isError: result.isError,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async callRemote(call, headers) {
|
|
63
|
+
try {
|
|
64
|
+
const args = JSON.parse(call.arguments || "{}");
|
|
65
|
+
const response = await fetch(`${this.opts.baseUrl}/execute`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers,
|
|
68
|
+
body: JSON.stringify({ name: call.name, arguments: args }),
|
|
69
|
+
signal: AbortSignal.timeout(this.opts.timeoutMs ?? 30_000),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const body = await response.text().catch(() => "");
|
|
73
|
+
return { output: `HTTP ${response.status}${body ? `: ${body}` : ""}`, isError: true };
|
|
74
|
+
}
|
|
75
|
+
const result = await response.json();
|
|
76
|
+
return { output: result.output, isError: result.isError ?? false };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return { output: String(err), isError: true };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { LLMProvider, StreamEvent, ToolSuspendEvent } from "../types.js";
|
|
2
|
+
import type { DreamStore, DreamResult } from "../memory/protocols.js";
|
|
3
|
+
import type { KnowledgeSource } from "../knowledge/source.js";
|
|
4
|
+
import type { SignalSource } from "../signals/types.js";
|
|
5
|
+
import type { SessionLog } from "./session-log.js";
|
|
6
|
+
import type { ExecutionPlane } from "./execution-plane.js";
|
|
7
|
+
export interface RuntimeOptions {
|
|
8
|
+
provider: LLMProvider;
|
|
9
|
+
sessionLog: SessionLog;
|
|
10
|
+
executionPlane: ExecutionPlane;
|
|
11
|
+
maxTokens: number;
|
|
12
|
+
maxTurns?: number;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
agentId?: string;
|
|
15
|
+
systemPrompt?: string;
|
|
16
|
+
initialMemory?: string[];
|
|
17
|
+
skillDir?: string;
|
|
18
|
+
dreamStore?: DreamStore;
|
|
19
|
+
knowledgeSource?: KnowledgeSource;
|
|
20
|
+
signalSource?: SignalSource;
|
|
21
|
+
extensions?: Record<string, unknown>;
|
|
22
|
+
governance?: {
|
|
23
|
+
setTime?(nowMs: bigint): void;
|
|
24
|
+
evaluate(name: string, argsJson: string): {
|
|
25
|
+
kind: string;
|
|
26
|
+
reason?: string;
|
|
27
|
+
retryAfterMs?: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
onToolSuspend?: (event: ToolSuspendEvent) => Promise<unknown> | unknown;
|
|
31
|
+
}
|
|
32
|
+
export declare class RuntimeRunner {
|
|
33
|
+
private readonly opts;
|
|
34
|
+
private interrupted;
|
|
35
|
+
constructor(opts: RuntimeOptions);
|
|
36
|
+
interrupt(): void;
|
|
37
|
+
run(req: {
|
|
38
|
+
sessionId: string;
|
|
39
|
+
goal: string;
|
|
40
|
+
criteria?: string[];
|
|
41
|
+
extensions?: Record<string, unknown>;
|
|
42
|
+
}): AsyncIterable<StreamEvent>;
|
|
43
|
+
wake(sessionId: string, extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
44
|
+
dream(agentId: string, nowMs?: number): Promise<DreamResult>;
|
|
45
|
+
private execute;
|
|
46
|
+
private appendObservations;
|
|
47
|
+
}
|
|
48
|
+
/** Collect all text_delta events from a run into a single string. */
|
|
49
|
+
export declare function collectText(stream: AsyncIterable<StreamEvent>): Promise<string>;
|