axexec 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/bin/axexec +17 -0
- package/dist/agents/claude-code/adapter.d.ts +6 -0
- package/dist/agents/claude-code/adapter.js +71 -0
- package/dist/agents/claude-code/parse-event.d.ts +16 -0
- package/dist/agents/claude-code/parse-event.js +185 -0
- package/dist/agents/claude-code/types.d.ts +180 -0
- package/dist/agents/claude-code/types.js +112 -0
- package/dist/agents/codex/adapter.d.ts +6 -0
- package/dist/agents/codex/adapter.js +62 -0
- package/dist/agents/codex/item-parsers.d.ts +17 -0
- package/dist/agents/codex/item-parsers.js +126 -0
- package/dist/agents/codex/parse-event.d.ts +38 -0
- package/dist/agents/codex/parse-event.js +141 -0
- package/dist/agents/codex/types.d.ts +407 -0
- package/dist/agents/codex/types.js +188 -0
- package/dist/agents/copilot/adapter.d.ts +11 -0
- package/dist/agents/copilot/adapter.js +81 -0
- package/dist/agents/copilot/parse-event.d.ts +20 -0
- package/dist/agents/copilot/parse-event.js +137 -0
- package/dist/agents/copilot/stream-session.d.ts +10 -0
- package/dist/agents/copilot/stream-session.js +120 -0
- package/dist/agents/copilot/tail-file.d.ts +11 -0
- package/dist/agents/copilot/tail-file.js +52 -0
- package/dist/agents/copilot/transform-event.d.ts +19 -0
- package/dist/agents/copilot/transform-event.js +100 -0
- package/dist/agents/copilot/types.d.ts +320 -0
- package/dist/agents/copilot/types.js +220 -0
- package/dist/agents/copilot/watch-session.d.ts +26 -0
- package/dist/agents/copilot/watch-session.js +186 -0
- package/dist/agents/gemini/adapter.d.ts +6 -0
- package/dist/agents/gemini/adapter.js +78 -0
- package/dist/agents/gemini/parse-event.d.ts +18 -0
- package/dist/agents/gemini/parse-event.js +144 -0
- package/dist/agents/gemini/types.d.ts +162 -0
- package/dist/agents/gemini/types.js +103 -0
- package/dist/agents/opencode/adapter.d.ts +16 -0
- package/dist/agents/opencode/adapter.js +142 -0
- package/dist/agents/opencode/cleanup-session.d.ts +17 -0
- package/dist/agents/opencode/cleanup-session.js +41 -0
- package/dist/agents/opencode/create-session-start-event.d.ts +18 -0
- package/dist/agents/opencode/create-session-start-event.js +24 -0
- package/dist/agents/opencode/detect-empty-session.d.ts +25 -0
- package/dist/agents/opencode/detect-empty-session.js +42 -0
- package/dist/agents/opencode/map-error-to-event.d.ts +10 -0
- package/dist/agents/opencode/map-error-to-event.js +55 -0
- package/dist/agents/opencode/parse-message-part.d.ts +24 -0
- package/dist/agents/opencode/parse-message-part.js +112 -0
- package/dist/agents/opencode/parse-sse-event.d.ts +23 -0
- package/dist/agents/opencode/parse-sse-event.js +125 -0
- package/dist/agents/opencode/process-sse-events.d.ts +24 -0
- package/dist/agents/opencode/process-sse-events.js +66 -0
- package/dist/agents/opencode/server-types.d.ts +509 -0
- package/dist/agents/opencode/server-types.js +293 -0
- package/dist/agents/opencode/session-api.d.ts +56 -0
- package/dist/agents/opencode/session-api.js +151 -0
- package/dist/agents/opencode/spawn-server.d.ts +29 -0
- package/dist/agents/opencode/spawn-server.js +95 -0
- package/dist/agents/opencode/sse-client.d.ts +33 -0
- package/dist/agents/opencode/sse-client.js +145 -0
- package/dist/agents/registry.d.ts +15 -0
- package/dist/agents/registry.js +24 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +119 -0
- package/dist/credentials/get-credential-environment.d.ts +13 -0
- package/dist/credentials/get-credential-environment.js +46 -0
- package/dist/credentials/install-credentials.d.ts +27 -0
- package/dist/credentials/install-credentials.js +102 -0
- package/dist/credentials/save-json-file.d.ts +11 -0
- package/dist/credentials/save-json-file.js +21 -0
- package/dist/credentials/types.d.ts +24 -0
- package/dist/credentials/types.js +4 -0
- package/dist/credentials/write-agent-credentials.d.ts +36 -0
- package/dist/credentials/write-agent-credentials.js +91 -0
- package/dist/determine-session-success.d.ts +15 -0
- package/dist/determine-session-success.js +25 -0
- package/dist/format-event-tsv.d.ts +23 -0
- package/dist/format-event-tsv.js +136 -0
- package/dist/parse-credentials.d.ts +13 -0
- package/dist/parse-credentials.js +63 -0
- package/dist/parse-iso-timestamp.d.ts +21 -0
- package/dist/parse-iso-timestamp.js +37 -0
- package/dist/resolve-binary.d.ts +29 -0
- package/dist/resolve-binary.js +46 -0
- package/dist/resolve-output-mode.d.ts +39 -0
- package/dist/resolve-output-mode.js +39 -0
- package/dist/run-agent.d.ts +32 -0
- package/dist/run-agent.js +146 -0
- package/dist/stream-agent.d.ts +20 -0
- package/dist/stream-agent.js +207 -0
- package/dist/types/adapter.d.ts +101 -0
- package/dist/types/adapter.js +14 -0
- package/dist/types/events.d.ts +82 -0
- package/dist/types/events.js +13 -0
- package/dist/types/options.d.ts +41 -0
- package/dist/types/options.js +4 -0
- package/dist/validate-agent.d.ts +20 -0
- package/dist/validate-agent.js +50 -0
- package/dist/write-event.d.ts +23 -0
- package/dist/write-event.js +43 -0
- package/package.json +79 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent execution with credential isolation and config generation.
|
|
3
|
+
*/
|
|
4
|
+
import { buildAgentConfig } from "axconfig";
|
|
5
|
+
import { isStreamableAdapter } from "./types/adapter.js";
|
|
6
|
+
import { installCredentials, cleanupCredentials, } from "./credentials/install-credentials.js";
|
|
7
|
+
import { streamAgent } from "./stream-agent.js";
|
|
8
|
+
import { validateAgent } from "./validate-agent.js";
|
|
9
|
+
import { resolveOutputMode } from "./resolve-output-mode.js";
|
|
10
|
+
import { createEventWriter } from "./write-event.js";
|
|
11
|
+
import { parseCredentialsFromEnvironment } from "./parse-credentials.js";
|
|
12
|
+
import { DependencyError } from "./resolve-binary.js";
|
|
13
|
+
/**
|
|
14
|
+
* Runs an agent with full isolation.
|
|
15
|
+
*
|
|
16
|
+
* 1. Validates the agent ID
|
|
17
|
+
* 2. Parses credentials from environment
|
|
18
|
+
* 3. Installs credentials to temp directory
|
|
19
|
+
* 4. Builds config if permissions specified
|
|
20
|
+
* 5. Streams events from agent
|
|
21
|
+
* 6. Cleans up temp directory
|
|
22
|
+
*/
|
|
23
|
+
async function runAgent(agentId, options) {
|
|
24
|
+
// Validate agent
|
|
25
|
+
const validation = validateAgent(agentId);
|
|
26
|
+
if (!validation.ok) {
|
|
27
|
+
process.stderr.write(validation.message + "\n");
|
|
28
|
+
process.exitCode = validation.exitCode;
|
|
29
|
+
return { events: [], success: false };
|
|
30
|
+
}
|
|
31
|
+
const { adapter } = validation;
|
|
32
|
+
// Parse credentials from environment
|
|
33
|
+
const credentials = parseCredentialsFromEnvironment(agentId);
|
|
34
|
+
// Install credentials to temp directory
|
|
35
|
+
let credentialResult;
|
|
36
|
+
try {
|
|
37
|
+
credentialResult = await installCredentials(validation.agentId, credentials);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
process.stderr.write(`Error installing credentials: ${message}\n`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return { events: [], success: false };
|
|
44
|
+
}
|
|
45
|
+
// Build config if permissions specified
|
|
46
|
+
let configEnvironment = credentialResult.env;
|
|
47
|
+
if (options.allow || options.deny) {
|
|
48
|
+
const configResult = buildConfig(validation.agentId, options, credentialResult);
|
|
49
|
+
if (!configResult.ok) {
|
|
50
|
+
await cleanupCredentials(credentialResult.baseDirectory);
|
|
51
|
+
process.exitCode = configResult.exitCode;
|
|
52
|
+
return { events: [], success: false };
|
|
53
|
+
}
|
|
54
|
+
configEnvironment = configResult.env;
|
|
55
|
+
}
|
|
56
|
+
// Execute agent
|
|
57
|
+
return executeAgent(adapter, options, configEnvironment, credentialResult);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Builds agent config with permissions.
|
|
61
|
+
*/
|
|
62
|
+
function buildConfig(agentId, options, credentialResult) {
|
|
63
|
+
try {
|
|
64
|
+
const configResult = buildAgentConfig({
|
|
65
|
+
agentId,
|
|
66
|
+
allow: options.allow,
|
|
67
|
+
deny: options.deny,
|
|
68
|
+
output: credentialResult.configDirectory,
|
|
69
|
+
});
|
|
70
|
+
if (!configResult.ok) {
|
|
71
|
+
if ("message" in configResult) {
|
|
72
|
+
process.stderr.write(`Error: ${configResult.message}\n`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
process.stderr.write(`Error building config: permission errors\n`);
|
|
76
|
+
for (const error of configResult.errors) {
|
|
77
|
+
process.stderr.write(` - ${error.reason}\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { ok: false, exitCode: configResult.exitCode };
|
|
81
|
+
}
|
|
82
|
+
// Log warnings
|
|
83
|
+
for (const warning of configResult.warnings) {
|
|
84
|
+
process.stderr.write(`Warning: ${warning.reason}\n`);
|
|
85
|
+
}
|
|
86
|
+
// Merge config env with credential env (config env takes precedence)
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
env: { ...credentialResult.env, ...configResult.env },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
process.stderr.write(`Error parsing permissions: ${message}\n`);
|
|
95
|
+
return { ok: false, exitCode: 1 };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Executes agent and streams events.
|
|
100
|
+
*/
|
|
101
|
+
async function executeAgent(adapter, options, configEnvironment, credentialResult) {
|
|
102
|
+
const runOptions = {
|
|
103
|
+
prompt: options.prompt,
|
|
104
|
+
verbose: options.verbose ?? false,
|
|
105
|
+
model: options.model,
|
|
106
|
+
rawLogPath: options.rawLog,
|
|
107
|
+
debug: options.debug,
|
|
108
|
+
configEnv: configEnvironment,
|
|
109
|
+
preserveGithubSha: options.preserveGithubSha,
|
|
110
|
+
};
|
|
111
|
+
const outputMode = resolveOutputMode(options.format, process.stdout.isTTY);
|
|
112
|
+
const writeEvent = createEventWriter(outputMode);
|
|
113
|
+
const events = [];
|
|
114
|
+
try {
|
|
115
|
+
const eventStream = isStreamableAdapter(adapter)
|
|
116
|
+
? adapter.streamSession(runOptions)
|
|
117
|
+
: streamAgent(adapter, runOptions);
|
|
118
|
+
for await (const event of eventStream) {
|
|
119
|
+
events.push(event);
|
|
120
|
+
writeEvent(event);
|
|
121
|
+
}
|
|
122
|
+
const success = adapter.isSuccess(events);
|
|
123
|
+
return { events, success };
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof DependencyError) {
|
|
127
|
+
process.stderr.write(error.message + "\n");
|
|
128
|
+
return { events, success: false };
|
|
129
|
+
}
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
const errorEvent = {
|
|
132
|
+
type: "session.error",
|
|
133
|
+
code: "SPAWN_ERROR",
|
|
134
|
+
message,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
events.push(errorEvent);
|
|
138
|
+
writeEvent(errorEvent);
|
|
139
|
+
return { events, success: false };
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
// Safe to await - executeAgent is async so finally block can use await
|
|
143
|
+
await cleanupCredentials(credentialResult.baseDirectory);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export { runAgent };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core runner for executing agents.
|
|
3
|
+
*
|
|
4
|
+
* This module is the imperative shell that handles all I/O:
|
|
5
|
+
* process spawning, streaming, and event collection.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentAdapter } from "./types/adapter.js";
|
|
8
|
+
import type { AxexecEvent } from "./types/events.js";
|
|
9
|
+
import type { RunOptions } from "./types/options.js";
|
|
10
|
+
/**
|
|
11
|
+
* Runs an agent and streams normalized events.
|
|
12
|
+
*
|
|
13
|
+
* @param adapter - The agent adapter to use
|
|
14
|
+
* @param options - Run options including prompt and flags
|
|
15
|
+
* @yields Normalized events as they arrive from the agent
|
|
16
|
+
* @throws {AgentNotFoundError} If the agent binary is not found
|
|
17
|
+
* @throws {AgentSpawnError} If the process fails to start
|
|
18
|
+
*/
|
|
19
|
+
declare function streamAgent(adapter: AgentAdapter, options: RunOptions): AsyncGenerator<AxexecEvent>;
|
|
20
|
+
export { streamAgent };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core runner for executing agents.
|
|
3
|
+
*
|
|
4
|
+
* This module is the imperative shell that handles all I/O:
|
|
5
|
+
* process spawning, streaming, and event collection.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { createWriteStream } from "node:fs";
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
10
|
+
/**
|
|
11
|
+
* Environment variables to always exclude from agent subprocess.
|
|
12
|
+
*
|
|
13
|
+
* Vault credentials are excluded to prevent prompt injection attacks from
|
|
14
|
+
* exfiltrating secrets. The credentials are resolved before spawning and
|
|
15
|
+
* passed via agent-specific env vars (e.g., ANTHROPIC_API_KEY).
|
|
16
|
+
*/
|
|
17
|
+
const VAULT_ENV_EXCLUSIONS = ["AXVAULT", "AXVAULT_URL", "AXVAULT_API_KEY"];
|
|
18
|
+
/** Error thrown when agent binary is not found */
|
|
19
|
+
class AgentNotFoundError extends Error {
|
|
20
|
+
bin;
|
|
21
|
+
constructor(bin, cause) {
|
|
22
|
+
super(`Agent binary not found: ${bin}`);
|
|
23
|
+
this.name = "AgentNotFoundError";
|
|
24
|
+
this.bin = bin;
|
|
25
|
+
this.cause = cause;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Error thrown when agent process fails to start */
|
|
29
|
+
class AgentSpawnError extends Error {
|
|
30
|
+
bin;
|
|
31
|
+
constructor(bin, cause) {
|
|
32
|
+
super(`Failed to spawn agent: ${bin}`);
|
|
33
|
+
this.name = "AgentSpawnError";
|
|
34
|
+
this.bin = bin;
|
|
35
|
+
this.cause = cause;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Runs an agent and streams normalized events.
|
|
40
|
+
*
|
|
41
|
+
* @param adapter - The agent adapter to use
|
|
42
|
+
* @param options - Run options including prompt and flags
|
|
43
|
+
* @yields Normalized events as they arrive from the agent
|
|
44
|
+
* @throws {AgentNotFoundError} If the agent binary is not found
|
|
45
|
+
* @throws {AgentSpawnError} If the process fails to start
|
|
46
|
+
*/
|
|
47
|
+
async function* streamAgent(adapter, options) {
|
|
48
|
+
const { bin, args, env, envExclusions: environmentExclusions, } = adapter.prepareCommand(options);
|
|
49
|
+
const parseEvent = adapter.createParser(options);
|
|
50
|
+
// Open raw log file if requested.
|
|
51
|
+
// Errors are logged to stderr but don't interrupt the main stream.
|
|
52
|
+
let rawLogStream;
|
|
53
|
+
if (options.rawLogPath) {
|
|
54
|
+
rawLogStream = createWriteStream(options.rawLogPath, { flags: "w" });
|
|
55
|
+
rawLogStream.on("error", (error) => {
|
|
56
|
+
process.stderr.write(`Warning: raw log error: ${error.message}\n`);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Build environment: start with process.env, exclude vault vars and any
|
|
60
|
+
// adapter-specified vars, then merge adapter env and config env.
|
|
61
|
+
const allExclusions = new Set([
|
|
62
|
+
...VAULT_ENV_EXCLUSIONS,
|
|
63
|
+
...(environmentExclusions ?? []),
|
|
64
|
+
]);
|
|
65
|
+
const baseEnvironment = Object.fromEntries(Object.entries(process.env).filter(([key]) => !allExclusions.has(key)));
|
|
66
|
+
const child = spawn(bin, args, {
|
|
67
|
+
env: { ...baseEnvironment, ...env, ...options.configEnv },
|
|
68
|
+
cwd: options.cwd,
|
|
69
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
70
|
+
});
|
|
71
|
+
// Consume stderr to prevent pipe buffer deadlock.
|
|
72
|
+
// Capture output for error diagnostics when process fails.
|
|
73
|
+
// stderr is guaranteed non-null since stdio is set to "pipe".
|
|
74
|
+
let stderrBuffer = "";
|
|
75
|
+
const STDERR_LIMIT = 50 * 1024 * 1024; // 50 MB limit to prevent OOM
|
|
76
|
+
if (options.verbose) {
|
|
77
|
+
child.stderr.pipe(process.stderr);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
child.stderr.on("data", (chunk) => {
|
|
81
|
+
// Limit buffer size to prevent memory exhaustion from verbose agents
|
|
82
|
+
if (stderrBuffer.length < STDERR_LIMIT) {
|
|
83
|
+
stderrBuffer += chunk.toString();
|
|
84
|
+
if (stderrBuffer.length >= STDERR_LIMIT) {
|
|
85
|
+
stderrBuffer =
|
|
86
|
+
stderrBuffer.slice(0, STDERR_LIMIT) + "\n... (truncated)";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Wait for spawn or error
|
|
92
|
+
await new Promise((resolve, reject) => {
|
|
93
|
+
child.once("error", (error) => {
|
|
94
|
+
if (error.code === "ENOENT") {
|
|
95
|
+
reject(new AgentNotFoundError(bin, error));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
reject(new AgentSpawnError(bin, error));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
child.once("spawn", () => {
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
// Forward termination signals to the child process.
|
|
106
|
+
// Registered after spawn succeeds to prevent handler leak on spawn failure.
|
|
107
|
+
const forwardSignal = (signal) => {
|
|
108
|
+
child.kill(signal);
|
|
109
|
+
};
|
|
110
|
+
process.on("SIGINT", forwardSignal);
|
|
111
|
+
process.on("SIGTERM", forwardSignal);
|
|
112
|
+
try {
|
|
113
|
+
// Stream stdout line-by-line.
|
|
114
|
+
// stdout is guaranteed non-null since stdio is set to "pipe".
|
|
115
|
+
const stdout = child.stdout;
|
|
116
|
+
// Handle stream errors that readline's for-await-of doesn't forward
|
|
117
|
+
let streamError;
|
|
118
|
+
stdout.on("error", (error) => {
|
|
119
|
+
streamError = error;
|
|
120
|
+
});
|
|
121
|
+
const rl = createInterface({ input: stdout });
|
|
122
|
+
for await (const line of rl) {
|
|
123
|
+
// Write raw line to log file if enabled
|
|
124
|
+
if (rawLogStream) {
|
|
125
|
+
rawLogStream.write(line + "\n");
|
|
126
|
+
}
|
|
127
|
+
const event = parseEvent(line);
|
|
128
|
+
if (event) {
|
|
129
|
+
yield event;
|
|
130
|
+
}
|
|
131
|
+
else if (options.debug && line.trim() !== "") {
|
|
132
|
+
// Log unknown events in debug mode
|
|
133
|
+
// Try to extract event type for better diagnostics
|
|
134
|
+
let eventType = "unknown";
|
|
135
|
+
try {
|
|
136
|
+
const parsed = JSON.parse(line);
|
|
137
|
+
if (typeof parsed.type === "string") {
|
|
138
|
+
eventType = parsed.type;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Not valid JSON, use raw preview
|
|
143
|
+
eventType = `(invalid JSON: ${line.slice(0, 50)}...)`;
|
|
144
|
+
}
|
|
145
|
+
process.stderr.write(`[debug] Unhandled event type: ${eventType}\n`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Drain any buffered events from parsers that queue multiple events per line.
|
|
149
|
+
// Some parsers (e.g., OpenCode) produce multiple events from a single line
|
|
150
|
+
// but return them one at a time. Calling with empty string drains the queue.
|
|
151
|
+
let bufferedEvent;
|
|
152
|
+
while ((bufferedEvent = parseEvent("")) !== undefined) {
|
|
153
|
+
yield bufferedEvent;
|
|
154
|
+
}
|
|
155
|
+
// Check if we had a stream error during iteration
|
|
156
|
+
if (streamError) {
|
|
157
|
+
yield {
|
|
158
|
+
type: "session.error",
|
|
159
|
+
code: "STREAM_ERROR",
|
|
160
|
+
message: streamError.message,
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Wait for process to exit
|
|
165
|
+
const { exitCode, signal } = await new Promise((resolve) => {
|
|
166
|
+
child.once("close", (code, sig) => {
|
|
167
|
+
resolve({
|
|
168
|
+
exitCode: code ?? undefined,
|
|
169
|
+
signal: sig ?? undefined,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
// If process was terminated by a signal, yield a signal error event
|
|
174
|
+
if (signal) {
|
|
175
|
+
yield {
|
|
176
|
+
type: "session.error",
|
|
177
|
+
code: "SIGNAL",
|
|
178
|
+
message: `Agent terminated by ${signal}`,
|
|
179
|
+
timestamp: Date.now(),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
else if (exitCode !== 0 && exitCode !== undefined) {
|
|
183
|
+
// If process exited with non-zero, yield an exit-code error event.
|
|
184
|
+
// Note: This may emit after a stream error - consumers handle both.
|
|
185
|
+
const stderrTrimmed = stderrBuffer.trim();
|
|
186
|
+
const message = stderrTrimmed
|
|
187
|
+
? `Agent exited with code ${exitCode}: ${stderrTrimmed}`
|
|
188
|
+
: `Agent exited with code ${exitCode}`;
|
|
189
|
+
yield {
|
|
190
|
+
type: "session.error",
|
|
191
|
+
code: "EXIT_CODE",
|
|
192
|
+
message,
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
// Clean up signal handlers - guaranteed to run even if consumer breaks early
|
|
199
|
+
process.off("SIGINT", forwardSignal);
|
|
200
|
+
process.off("SIGTERM", forwardSignal);
|
|
201
|
+
// Close raw log stream if open
|
|
202
|
+
if (rawLogStream) {
|
|
203
|
+
rawLogStream.end();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export { streamAgent };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter interface and related types.
|
|
3
|
+
*
|
|
4
|
+
* Each agent implements the {@link AgentAdapter} interface to provide
|
|
5
|
+
* a consistent way to prepare commands and parse events.
|
|
6
|
+
*
|
|
7
|
+
* Adapters may optionally implement {@link StreamableAdapter} to provide
|
|
8
|
+
* custom streaming behavior (e.g., for server-mode agents).
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentCli, AxexecEvent } from "./events.js";
|
|
11
|
+
import type { RunOptions } from "./options.js";
|
|
12
|
+
/** Metadata about an agent */
|
|
13
|
+
interface AgentInfo {
|
|
14
|
+
/** Unique identifier for the agent */
|
|
15
|
+
id: AgentCli;
|
|
16
|
+
/** Human-readable name */
|
|
17
|
+
name: string;
|
|
18
|
+
/** CLI package name (e.g., "@anthropic-ai/claude-code") */
|
|
19
|
+
package: string;
|
|
20
|
+
/** Detected version of the installed CLI, if available */
|
|
21
|
+
version?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Command prepared for execution by spawn() */
|
|
24
|
+
interface PreparedCommand {
|
|
25
|
+
/** Binary to execute */
|
|
26
|
+
bin: string;
|
|
27
|
+
/** Arguments to pass to the binary */
|
|
28
|
+
args: string[];
|
|
29
|
+
/** Environment variables to set */
|
|
30
|
+
env: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Environment variables to exclude from process.env.
|
|
33
|
+
*
|
|
34
|
+
* These keys will be removed from the inherited environment before
|
|
35
|
+
* the adapter's env values are merged in. Useful for agents that need
|
|
36
|
+
* to work around CI environment variable conflicts.
|
|
37
|
+
*/
|
|
38
|
+
envExclusions?: string[];
|
|
39
|
+
}
|
|
40
|
+
/** Function that parses a single JSONL line into a normalized event */
|
|
41
|
+
type EventParser = (line: string) => AxexecEvent | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Adapter interface that each agent must implement.
|
|
44
|
+
*
|
|
45
|
+
* Adapters are responsible for:
|
|
46
|
+
* - Providing agent metadata
|
|
47
|
+
* - Preparing the spawn command with appropriate flags
|
|
48
|
+
* - Parsing raw JSONL lines into normalized events
|
|
49
|
+
* - Determining task success from the event stream
|
|
50
|
+
*/
|
|
51
|
+
interface AgentAdapter {
|
|
52
|
+
/** Returns agent metadata */
|
|
53
|
+
info(): AgentInfo;
|
|
54
|
+
/** Prepares the spawn command with environment and arguments */
|
|
55
|
+
prepareCommand(options: RunOptions): PreparedCommand;
|
|
56
|
+
/**
|
|
57
|
+
* Creates a parser for a single session.
|
|
58
|
+
*
|
|
59
|
+
* The returned function may maintain state across calls (e.g., to correlate
|
|
60
|
+
* tool calls with their results). A new parser should be created for each
|
|
61
|
+
* agent invocation.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Run options (optional, for agents that need model info)
|
|
64
|
+
* @returns A function that parses JSONL lines into normalized events
|
|
65
|
+
*/
|
|
66
|
+
createParser(options?: RunOptions): EventParser;
|
|
67
|
+
/**
|
|
68
|
+
* Determines if the task completed successfully from the event stream.
|
|
69
|
+
*
|
|
70
|
+
* Called after the process exits to interpret the final state.
|
|
71
|
+
*/
|
|
72
|
+
isSuccess(events: AxexecEvent[]): boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extended adapter interface for agents that provide custom streaming.
|
|
76
|
+
*
|
|
77
|
+
* Agents implementing this interface handle their own execution flow
|
|
78
|
+
* (e.g., server-mode agents that use HTTP/SSE instead of stdout).
|
|
79
|
+
*
|
|
80
|
+
* When an adapter implements `streamSession`, the CLI will use it
|
|
81
|
+
* instead of the default spawn+parse pattern.
|
|
82
|
+
*/
|
|
83
|
+
interface StreamableAdapter extends AgentAdapter {
|
|
84
|
+
/**
|
|
85
|
+
* Streams events from a custom execution flow.
|
|
86
|
+
*
|
|
87
|
+
* This method handles the full lifecycle:
|
|
88
|
+
* - Starting any required processes/servers
|
|
89
|
+
* - Connecting to event sources
|
|
90
|
+
* - Yielding normalized events
|
|
91
|
+
* - Cleanup on completion or error
|
|
92
|
+
*
|
|
93
|
+
* @param options - Run options including prompt and flags
|
|
94
|
+
* @yields Normalized events as they arrive
|
|
95
|
+
*/
|
|
96
|
+
streamSession(options: RunOptions): AsyncGenerator<AxexecEvent>;
|
|
97
|
+
}
|
|
98
|
+
/** Type guard to check if an adapter implements StreamableAdapter */
|
|
99
|
+
declare function isStreamableAdapter(adapter: AgentAdapter): adapter is StreamableAdapter;
|
|
100
|
+
export type { AgentAdapter, AgentInfo, EventParser, PreparedCommand, StreamableAdapter, };
|
|
101
|
+
export { isStreamableAdapter };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter interface and related types.
|
|
3
|
+
*
|
|
4
|
+
* Each agent implements the {@link AgentAdapter} interface to provide
|
|
5
|
+
* a consistent way to prepare commands and parse events.
|
|
6
|
+
*
|
|
7
|
+
* Adapters may optionally implement {@link StreamableAdapter} to provide
|
|
8
|
+
* custom streaming behavior (e.g., for server-mode agents).
|
|
9
|
+
*/
|
|
10
|
+
/** Type guard to check if an adapter implements StreamableAdapter */
|
|
11
|
+
function isStreamableAdapter(adapter) {
|
|
12
|
+
return ("streamSession" in adapter && typeof adapter.streamSession === "function");
|
|
13
|
+
}
|
|
14
|
+
export { isStreamableAdapter };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized event types for axexec.
|
|
3
|
+
*
|
|
4
|
+
* All agent adapters transform their raw JSONL events into these
|
|
5
|
+
* normalized types, providing a consistent interface regardless
|
|
6
|
+
* of which underlying agent is used.
|
|
7
|
+
*
|
|
8
|
+
* Events are grouped by semantic category:
|
|
9
|
+
* - session.*: Session lifecycle (start, complete, error)
|
|
10
|
+
* - agent.*: Agent content output (reasoning, message)
|
|
11
|
+
* - tool.*: Tool execution (call, result)
|
|
12
|
+
*/
|
|
13
|
+
export type { AgentCli } from "axshared";
|
|
14
|
+
type AgentCli = import("axshared").AgentCli;
|
|
15
|
+
/** Statistics collected during session execution */
|
|
16
|
+
interface SessionStats {
|
|
17
|
+
durationMs: number;
|
|
18
|
+
costUsd?: number;
|
|
19
|
+
inputTokens?: number;
|
|
20
|
+
outputTokens?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Emitted when a session begins */
|
|
23
|
+
interface SessionStartEvent {
|
|
24
|
+
type: "session.start";
|
|
25
|
+
/** Unique identifier for this session */
|
|
26
|
+
sessionId: string;
|
|
27
|
+
agent: AgentCli;
|
|
28
|
+
/** Model used for this session (e.g., "claude-sonnet-4-5-20250929", "gpt-4") */
|
|
29
|
+
model: string;
|
|
30
|
+
/** Provider of the model, if known (e.g., "anthropic", "openai", "google") */
|
|
31
|
+
provider?: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
34
|
+
/** Emitted when a session completes successfully */
|
|
35
|
+
interface SessionCompleteEvent {
|
|
36
|
+
type: "session.complete";
|
|
37
|
+
stats: SessionStats;
|
|
38
|
+
timestamp: number;
|
|
39
|
+
}
|
|
40
|
+
/** Emitted when a session ends with an error */
|
|
41
|
+
interface SessionErrorEvent {
|
|
42
|
+
type: "session.error";
|
|
43
|
+
code: string;
|
|
44
|
+
message: string;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
/** Emitted when the agent produces internal reasoning (chain-of-thought) */
|
|
48
|
+
interface AgentReasoningEvent {
|
|
49
|
+
type: "agent.reasoning";
|
|
50
|
+
content: string;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
}
|
|
53
|
+
/** Emitted when the agent produces a message/response to the user */
|
|
54
|
+
interface AgentMessageEvent {
|
|
55
|
+
type: "agent.message";
|
|
56
|
+
content: string;
|
|
57
|
+
timestamp: number;
|
|
58
|
+
}
|
|
59
|
+
/** Emitted when the agent invokes a tool */
|
|
60
|
+
interface ToolCallEvent {
|
|
61
|
+
type: "tool.call";
|
|
62
|
+
/** Unique identifier for this tool invocation, used to correlate with result */
|
|
63
|
+
callId: string;
|
|
64
|
+
/** Human-readable tool name */
|
|
65
|
+
tool: string;
|
|
66
|
+
input: unknown;
|
|
67
|
+
timestamp: number;
|
|
68
|
+
}
|
|
69
|
+
/** Emitted when a tool returns its result */
|
|
70
|
+
interface ToolResultEvent {
|
|
71
|
+
type: "tool.result";
|
|
72
|
+
/** Matches the callId from the corresponding tool.call event */
|
|
73
|
+
callId: string;
|
|
74
|
+
/** Human-readable tool name (resolved via correlation with tool.call) */
|
|
75
|
+
tool: string;
|
|
76
|
+
output: unknown;
|
|
77
|
+
success: boolean;
|
|
78
|
+
timestamp: number;
|
|
79
|
+
}
|
|
80
|
+
/** Discriminated union of all normalized event types */
|
|
81
|
+
type AxexecEvent = SessionStartEvent | SessionCompleteEvent | SessionErrorEvent | AgentReasoningEvent | AgentMessageEvent | ToolCallEvent | ToolResultEvent;
|
|
82
|
+
export type { AxexecEvent };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized event types for axexec.
|
|
3
|
+
*
|
|
4
|
+
* All agent adapters transform their raw JSONL events into these
|
|
5
|
+
* normalized types, providing a consistent interface regardless
|
|
6
|
+
* of which underlying agent is used.
|
|
7
|
+
*
|
|
8
|
+
* Events are grouped by semantic category:
|
|
9
|
+
* - session.*: Session lifecycle (start, complete, error)
|
|
10
|
+
* - agent.*: Agent content output (reasoning, message)
|
|
11
|
+
* - tool.*: Tool execution (call, result)
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options types for running agents.
|
|
3
|
+
*/
|
|
4
|
+
/** Options for running an agent */
|
|
5
|
+
interface RunOptions {
|
|
6
|
+
/** The prompt to send to the agent */
|
|
7
|
+
prompt: string;
|
|
8
|
+
/** Show verbose/debug output from the agent */
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
/** Working directory for the agent process */
|
|
11
|
+
cwd?: string;
|
|
12
|
+
/** Path to write raw agent JSONL output for debugging/fixtures */
|
|
13
|
+
rawLogPath?: string;
|
|
14
|
+
/** Enable debug mode (logs unknown events, additional diagnostics) */
|
|
15
|
+
debug?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Additional environment variables for agent configuration.
|
|
18
|
+
* Generated by ConfigBuilder to point to temp config files.
|
|
19
|
+
*/
|
|
20
|
+
configEnv?: Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Model to use. Format varies by agent:
|
|
23
|
+
* - Claude Code: aliases ('sonnet', 'opus', 'haiku') or full model names
|
|
24
|
+
* - Codex: model names ('o4-mini', 'gpt-5.1-codex')
|
|
25
|
+
* - Gemini: aliases ('pro', 'flash') or full names ('gemini-2.5-pro')
|
|
26
|
+
* - Copilot: model names ('gpt-5', 'claude-sonnet-4.5')
|
|
27
|
+
* - OpenCode: provider/model format ('anthropic/claude-sonnet-4')
|
|
28
|
+
*/
|
|
29
|
+
model?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Preserve GITHUB_SHA environment variable.
|
|
32
|
+
*
|
|
33
|
+
* When false (default), Gemini excludes GITHUB_SHA to work around a CLI bug
|
|
34
|
+
* where strict environment sanitization blocks GH_TOKEN from reaching shell
|
|
35
|
+
* commands when GITHUB_SHA is set.
|
|
36
|
+
*
|
|
37
|
+
* Set to true to preserve GITHUB_SHA in the child process environment.
|
|
38
|
+
*/
|
|
39
|
+
preserveGithubSha?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export type { RunOptions };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent validation utilities.
|
|
3
|
+
*/
|
|
4
|
+
import { type AgentCli } from "axshared";
|
|
5
|
+
import type { AgentAdapter } from "./types/adapter.js";
|
|
6
|
+
/** Result of validating an agent */
|
|
7
|
+
type ValidateAgentResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
agentId: AgentCli;
|
|
10
|
+
adapter: AgentAdapter;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
exitCode: number;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Validate that an agent ID is valid and the adapter is available.
|
|
18
|
+
*/
|
|
19
|
+
declare function validateAgent(agentId: string | undefined): ValidateAgentResult;
|
|
20
|
+
export { validateAgent };
|