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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent credential file writers.
|
|
3
|
+
*
|
|
4
|
+
* Each agent has its own credential file format. These functions
|
|
5
|
+
* write credentials in the agent-specific format.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { saveJsonFile } from "./save-json-file.js";
|
|
9
|
+
/**
|
|
10
|
+
* Installs Claude credentials to a config directory.
|
|
11
|
+
*
|
|
12
|
+
* File: .credentials.json
|
|
13
|
+
* Format: { claudeAiOauth: { ... } }
|
|
14
|
+
*/
|
|
15
|
+
function installClaudeCredentials(configDirectory, credentials) {
|
|
16
|
+
if (!credentials.oauth && !credentials.apiKey) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const credentialsPath = path.join(configDirectory, ".credentials.json");
|
|
20
|
+
if (credentials.oauth) {
|
|
21
|
+
saveJsonFile(credentialsPath, { claudeAiOauth: credentials.oauth });
|
|
22
|
+
}
|
|
23
|
+
// API key is passed via environment variable, not file
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Installs Codex credentials to a config directory.
|
|
27
|
+
*
|
|
28
|
+
* File: auth.json
|
|
29
|
+
* Format: { OPENAI_API_KEY: "..." } or { tokens: { ... }, last_refresh: "..." }
|
|
30
|
+
*/
|
|
31
|
+
function installCodexCredentials(configDirectory, credentials) {
|
|
32
|
+
if (!credentials.oauth && !credentials.apiKey) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const authPath = path.join(configDirectory, "auth.json");
|
|
36
|
+
if (credentials.apiKey) {
|
|
37
|
+
saveJsonFile(authPath, { OPENAI_API_KEY: credentials.apiKey });
|
|
38
|
+
}
|
|
39
|
+
else if (credentials.oauth) {
|
|
40
|
+
saveJsonFile(authPath, {
|
|
41
|
+
tokens: credentials.oauth,
|
|
42
|
+
last_refresh: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Installs Gemini credentials to a config directory.
|
|
48
|
+
*
|
|
49
|
+
* File: oauth_creds.json
|
|
50
|
+
* Format: { access_token: "...", refresh_token: "...", ... }
|
|
51
|
+
*/
|
|
52
|
+
function installGeminiCredentials(configDirectory, credentials) {
|
|
53
|
+
if (!credentials.oauth && !credentials.apiKey) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (credentials.oauth) {
|
|
57
|
+
const credentialsPath = path.join(configDirectory, "oauth_creds.json");
|
|
58
|
+
saveJsonFile(credentialsPath, credentials.oauth);
|
|
59
|
+
}
|
|
60
|
+
// API key is passed via environment variable, not file
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Installs OpenCode credentials to a data directory.
|
|
64
|
+
*
|
|
65
|
+
* File: auth.json
|
|
66
|
+
* Format: { anthropic: { type: "oauth", ... }, openai: { type: "api", ... } }
|
|
67
|
+
*/
|
|
68
|
+
function installOpenCodeCredentials(dataDirectory, credentials) {
|
|
69
|
+
if (!credentials.oauth && !credentials.apiKey) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const authPath = path.join(dataDirectory, "auth.json");
|
|
73
|
+
const authData = {};
|
|
74
|
+
if (credentials.oauth) {
|
|
75
|
+
authData.anthropic = {
|
|
76
|
+
type: "oauth",
|
|
77
|
+
...credentials.oauth,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (credentials.apiKey) {
|
|
81
|
+
authData.anthropic = {
|
|
82
|
+
type: "api",
|
|
83
|
+
key: credentials.apiKey,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (Object.keys(authData).length > 0) {
|
|
87
|
+
saveJsonFile(authPath, authData);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Note: Copilot uses environment variables (GITHUB_TOKEN), not file-based credentials.
|
|
91
|
+
export { installClaudeCredentials, installCodexCredentials, installGeminiCredentials, installOpenCodeCredentials, };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines session success from an event stream.
|
|
3
|
+
*
|
|
4
|
+
* Shared implementation used by all adapters to check if a session
|
|
5
|
+
* completed successfully based on the normalized event stream.
|
|
6
|
+
*/
|
|
7
|
+
import type { AxexecEvent } from "./types/events.js";
|
|
8
|
+
/**
|
|
9
|
+
* Determines if the session completed successfully based on the event stream.
|
|
10
|
+
*
|
|
11
|
+
* Scans backwards for terminal events: returns true if session.complete
|
|
12
|
+
* is found before any session.error event.
|
|
13
|
+
*/
|
|
14
|
+
declare function determineSessionSuccess(events: AxexecEvent[]): boolean;
|
|
15
|
+
export { determineSessionSuccess };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines session success from an event stream.
|
|
3
|
+
*
|
|
4
|
+
* Shared implementation used by all adapters to check if a session
|
|
5
|
+
* completed successfully based on the normalized event stream.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Determines if the session completed successfully based on the event stream.
|
|
9
|
+
*
|
|
10
|
+
* Scans backwards for terminal events: returns true if session.complete
|
|
11
|
+
* is found before any session.error event.
|
|
12
|
+
*/
|
|
13
|
+
function determineSessionSuccess(events) {
|
|
14
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
15
|
+
const event = events[index];
|
|
16
|
+
if (event?.type === "session.complete") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (event?.type === "session.error") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export { determineSessionSuccess };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TSV (Tab-Separated Values) formatter for axexec events.
|
|
3
|
+
*
|
|
4
|
+
* Formats events as human-readable TSV lines with proper escaping
|
|
5
|
+
* for newlines and tabs. Supports optional truncation for interactive
|
|
6
|
+
* display.
|
|
7
|
+
*/
|
|
8
|
+
import type { AxexecEvent } from "./types/events.js";
|
|
9
|
+
interface FormatOptions {
|
|
10
|
+
/** Whether to truncate long content (default: false) */
|
|
11
|
+
truncate?: boolean;
|
|
12
|
+
/** Maximum length before truncation (default: 80) */
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Formats an AxexecEvent as a TSV line.
|
|
17
|
+
*
|
|
18
|
+
* @param event - The event to format
|
|
19
|
+
* @param options - Formatting options
|
|
20
|
+
* @returns A single TSV line (without trailing newline)
|
|
21
|
+
*/
|
|
22
|
+
declare function formatEventTsv(event: AxexecEvent, options?: FormatOptions): string;
|
|
23
|
+
export { formatEventTsv };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TSV (Tab-Separated Values) formatter for axexec events.
|
|
3
|
+
*
|
|
4
|
+
* Formats events as human-readable TSV lines with proper escaping
|
|
5
|
+
* for newlines and tabs. Supports optional truncation for interactive
|
|
6
|
+
* display.
|
|
7
|
+
*/
|
|
8
|
+
/** Default maximum length for content before truncation */
|
|
9
|
+
const DEFAULT_MAX_LENGTH = 80;
|
|
10
|
+
/**
|
|
11
|
+
* Escapes special characters in a string for TSV output.
|
|
12
|
+
* - Newlines → \n (literal backslash-n)
|
|
13
|
+
* - Tabs → \t (literal backslash-t)
|
|
14
|
+
* - Backslashes → \\ (escaped)
|
|
15
|
+
*/
|
|
16
|
+
function escapeTsv(value) {
|
|
17
|
+
return value
|
|
18
|
+
.replaceAll("\\", String.raw `\\`)
|
|
19
|
+
.replaceAll("\n", String.raw `\n`)
|
|
20
|
+
.replaceAll("\t", String.raw `\t`);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Truncates a string with a [+N] marker indicating remaining characters.
|
|
24
|
+
* Returns the original string if within maxLength.
|
|
25
|
+
*/
|
|
26
|
+
function truncateWithMarker(value, maxLength) {
|
|
27
|
+
if (value.length <= maxLength) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
const remaining = value.length - maxLength;
|
|
31
|
+
const marker = `[+${remaining}]`;
|
|
32
|
+
// Ensure we have room for the marker
|
|
33
|
+
const truncateAt = maxLength - marker.length;
|
|
34
|
+
if (truncateAt <= 0) {
|
|
35
|
+
return marker;
|
|
36
|
+
}
|
|
37
|
+
return value.slice(0, truncateAt) + marker;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Formats a timestamp (milliseconds since epoch) as HH:MM:SS local time.
|
|
41
|
+
*/
|
|
42
|
+
function formatTime(timestamp) {
|
|
43
|
+
const date = new Date(timestamp);
|
|
44
|
+
return date.toLocaleTimeString("en-US", {
|
|
45
|
+
hour12: false,
|
|
46
|
+
hour: "2-digit",
|
|
47
|
+
minute: "2-digit",
|
|
48
|
+
second: "2-digit",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Formats a duration in milliseconds as a human-readable string.
|
|
53
|
+
*/
|
|
54
|
+
function formatDuration(ms) {
|
|
55
|
+
if (ms < 1000) {
|
|
56
|
+
return `${ms}ms`;
|
|
57
|
+
}
|
|
58
|
+
const seconds = ms / 1000;
|
|
59
|
+
if (seconds < 60) {
|
|
60
|
+
return `${seconds.toFixed(1)}s`;
|
|
61
|
+
}
|
|
62
|
+
const minutes = Math.floor(seconds / 60);
|
|
63
|
+
const remainingSeconds = seconds % 60;
|
|
64
|
+
return `${minutes}m${remainingSeconds.toFixed(0)}s`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Summarizes an unknown value for TSV display.
|
|
68
|
+
* Objects are JSON-stringified, primitives are converted to strings.
|
|
69
|
+
*/
|
|
70
|
+
function summarizeValue(value) {
|
|
71
|
+
if (value === null || value === undefined) {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
return JSON.stringify(value);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Formats an AxexecEvent as a TSV line.
|
|
84
|
+
*
|
|
85
|
+
* @param event - The event to format
|
|
86
|
+
* @param options - Formatting options
|
|
87
|
+
* @returns A single TSV line (without trailing newline)
|
|
88
|
+
*/
|
|
89
|
+
function formatEventTsv(event, options = {}) {
|
|
90
|
+
const { truncate = false, maxLength = DEFAULT_MAX_LENGTH } = options;
|
|
91
|
+
const time = formatTime(event.timestamp);
|
|
92
|
+
const processContent = (content) => {
|
|
93
|
+
const escaped = escapeTsv(content);
|
|
94
|
+
return truncate ? truncateWithMarker(escaped, maxLength) : escaped;
|
|
95
|
+
};
|
|
96
|
+
switch (event.type) {
|
|
97
|
+
case "session.start": {
|
|
98
|
+
return `${time}\t${event.type}\tagent=${event.agent}\tmodel=${event.model}`;
|
|
99
|
+
}
|
|
100
|
+
case "session.complete": {
|
|
101
|
+
const parts = [
|
|
102
|
+
time,
|
|
103
|
+
event.type,
|
|
104
|
+
`duration=${formatDuration(event.stats.durationMs)}`,
|
|
105
|
+
];
|
|
106
|
+
if (event.stats.inputTokens !== undefined ||
|
|
107
|
+
event.stats.outputTokens !== undefined) {
|
|
108
|
+
const input = event.stats.inputTokens ?? 0;
|
|
109
|
+
const output = event.stats.outputTokens ?? 0;
|
|
110
|
+
parts.push(`tokens=${input}+${output}`);
|
|
111
|
+
}
|
|
112
|
+
if (event.stats.costUsd !== undefined) {
|
|
113
|
+
parts.push(`cost=$${event.stats.costUsd.toFixed(4)}`);
|
|
114
|
+
}
|
|
115
|
+
return parts.join("\t");
|
|
116
|
+
}
|
|
117
|
+
case "session.error": {
|
|
118
|
+
const message = processContent(event.message);
|
|
119
|
+
return `${time}\t${event.type}\tcode=${event.code}\t${message}`;
|
|
120
|
+
}
|
|
121
|
+
case "agent.reasoning":
|
|
122
|
+
case "agent.message": {
|
|
123
|
+
return `${time}\t${event.type}\t${processContent(event.content)}`;
|
|
124
|
+
}
|
|
125
|
+
case "tool.call": {
|
|
126
|
+
const inputSummary = processContent(summarizeValue(event.input));
|
|
127
|
+
return `${time}\t${event.type}\ttool=${event.tool}\t${inputSummary}`;
|
|
128
|
+
}
|
|
129
|
+
case "tool.result": {
|
|
130
|
+
const outputSummary = processContent(summarizeValue(event.output));
|
|
131
|
+
const status = event.success ? "success" : "failed";
|
|
132
|
+
return `${time}\t${event.type}\ttool=${event.tool}\t${status}\t${outputSummary}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export { formatEventTsv };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential parsing from environment variables.
|
|
3
|
+
*/
|
|
4
|
+
import type { RawCredentials } from "./credentials/install-credentials.js";
|
|
5
|
+
/**
|
|
6
|
+
* Parses credentials from environment variables.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables checked (in order):
|
|
9
|
+
* - AX_<AGENT>_CREDENTIALS: JSON with credentials
|
|
10
|
+
* - Standard env vars: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
|
|
11
|
+
*/
|
|
12
|
+
declare function parseCredentialsFromEnvironment(agentId: string): RawCredentials;
|
|
13
|
+
export { parseCredentialsFromEnvironment };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential parsing from environment variables.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parses credentials from environment variables.
|
|
6
|
+
*
|
|
7
|
+
* Environment variables checked (in order):
|
|
8
|
+
* - AX_<AGENT>_CREDENTIALS: JSON with credentials
|
|
9
|
+
* - Standard env vars: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
|
|
10
|
+
*/
|
|
11
|
+
function parseCredentialsFromEnvironment(agentId) {
|
|
12
|
+
const credentials = {};
|
|
13
|
+
// Check for axexec credentials env var
|
|
14
|
+
const credEnvironmentVariable = `AX_${agentId.toUpperCase()}_CREDENTIALS`;
|
|
15
|
+
const credEnvironmentValue = process.env[credEnvironmentVariable];
|
|
16
|
+
if (credEnvironmentValue) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(credEnvironmentValue);
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
process.stderr.write(`Warning: Failed to parse ${credEnvironmentVariable}\n`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Fall back to standard env vars
|
|
26
|
+
switch (agentId) {
|
|
27
|
+
case "claude": {
|
|
28
|
+
if (process.env["ANTHROPIC_API_KEY"]) {
|
|
29
|
+
credentials.apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "codex": {
|
|
34
|
+
if (process.env["OPENAI_API_KEY"]) {
|
|
35
|
+
credentials.apiKey = process.env["OPENAI_API_KEY"];
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "gemini": {
|
|
40
|
+
if (process.env["GEMINI_API_KEY"]) {
|
|
41
|
+
credentials.apiKey = process.env["GEMINI_API_KEY"];
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "opencode": {
|
|
46
|
+
if (process.env["ANTHROPIC_API_KEY"]) {
|
|
47
|
+
credentials.apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "copilot": {
|
|
52
|
+
const token = process.env["GITHUB_TOKEN"] ??
|
|
53
|
+
process.env["GH_TOKEN"] ??
|
|
54
|
+
process.env["COPILOT_GITHUB_TOKEN"];
|
|
55
|
+
if (token) {
|
|
56
|
+
credentials.githubToken = token;
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return credentials;
|
|
62
|
+
}
|
|
63
|
+
export { parseCredentialsFromEnvironment };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO timestamp parsing utility.
|
|
3
|
+
*
|
|
4
|
+
* Converts ISO 8601 timestamp strings to epoch milliseconds,
|
|
5
|
+
* with stateful fallback to preserve event ordering.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Creates a stateful timestamp parser that tracks the last valid timestamp.
|
|
9
|
+
*
|
|
10
|
+
* When a timestamp is missing or invalid, falls back to the previous event's
|
|
11
|
+
* timestamp to preserve ordering. Only uses `Date.now()` if no previous
|
|
12
|
+
* timestamp exists (i.e., the very first event has no timestamp).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const parseTimestamp = createTimestampParser();
|
|
16
|
+
* parseTimestamp("2025-01-15T14:30:45.123Z") // Returns 1736951445123
|
|
17
|
+
* parseTimestamp(undefined) // Returns 1736951445123 (previous value)
|
|
18
|
+
* parseTimestamp("2025-01-15T14:30:46.000Z") // Returns 1736951446000
|
|
19
|
+
*/
|
|
20
|
+
declare function createTimestampParser(): (isoString: string | undefined) => number;
|
|
21
|
+
export { createTimestampParser };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO timestamp parsing utility.
|
|
3
|
+
*
|
|
4
|
+
* Converts ISO 8601 timestamp strings to epoch milliseconds,
|
|
5
|
+
* with stateful fallback to preserve event ordering.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Creates a stateful timestamp parser that tracks the last valid timestamp.
|
|
9
|
+
*
|
|
10
|
+
* When a timestamp is missing or invalid, falls back to the previous event's
|
|
11
|
+
* timestamp to preserve ordering. Only uses `Date.now()` if no previous
|
|
12
|
+
* timestamp exists (i.e., the very first event has no timestamp).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const parseTimestamp = createTimestampParser();
|
|
16
|
+
* parseTimestamp("2025-01-15T14:30:45.123Z") // Returns 1736951445123
|
|
17
|
+
* parseTimestamp(undefined) // Returns 1736951445123 (previous value)
|
|
18
|
+
* parseTimestamp("2025-01-15T14:30:46.000Z") // Returns 1736951446000
|
|
19
|
+
*/
|
|
20
|
+
function createTimestampParser() {
|
|
21
|
+
let lastTimestamp;
|
|
22
|
+
return (isoString) => {
|
|
23
|
+
if (isoString) {
|
|
24
|
+
const parsed = Date.parse(isoString);
|
|
25
|
+
if (!Number.isNaN(parsed)) {
|
|
26
|
+
lastTimestamp = parsed;
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Fall back to previous timestamp, or Date.now() for very first event
|
|
31
|
+
if (lastTimestamp === undefined) {
|
|
32
|
+
lastTimestamp = Date.now();
|
|
33
|
+
}
|
|
34
|
+
return lastTimestamp;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export { createTimestampParser };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve and validate external CLI binaries used by axexec.
|
|
3
|
+
*/
|
|
4
|
+
interface ResolveBinaryOptions {
|
|
5
|
+
/** Friendly dependency name for error messages */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Default command name on PATH */
|
|
8
|
+
command: string;
|
|
9
|
+
/** Environment variable override for the binary path */
|
|
10
|
+
environmentVariable: string;
|
|
11
|
+
/** Install hint shown in error messages */
|
|
12
|
+
installHint: string;
|
|
13
|
+
}
|
|
14
|
+
declare class DependencyError extends Error {
|
|
15
|
+
name: string;
|
|
16
|
+
readonly dependency: string;
|
|
17
|
+
readonly environmentVariable: string;
|
|
18
|
+
readonly command: string;
|
|
19
|
+
constructor(message: string, dependency: string, environmentVariable: string, command: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolves a binary path and verifies it can be spawned.
|
|
23
|
+
*
|
|
24
|
+
* Checks only that the binary exists and can be spawned - does not validate
|
|
25
|
+
* exit code since we're checking availability, not correctness. Uses a short
|
|
26
|
+
* timeout to prevent hangs in CI environments.
|
|
27
|
+
*/
|
|
28
|
+
declare function resolveBinary(options: ResolveBinaryOptions): string;
|
|
29
|
+
export { DependencyError, resolveBinary };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve and validate external CLI binaries used by axexec.
|
|
3
|
+
*/
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
class DependencyError extends Error {
|
|
6
|
+
name = "DependencyError";
|
|
7
|
+
dependency;
|
|
8
|
+
environmentVariable;
|
|
9
|
+
command;
|
|
10
|
+
constructor(message, dependency, environmentVariable, command) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.dependency = dependency;
|
|
13
|
+
this.environmentVariable = environmentVariable;
|
|
14
|
+
this.command = command;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolves a binary path and verifies it can be spawned.
|
|
19
|
+
*
|
|
20
|
+
* Checks only that the binary exists and can be spawned - does not validate
|
|
21
|
+
* exit code since we're checking availability, not correctness. Uses a short
|
|
22
|
+
* timeout to prevent hangs in CI environments.
|
|
23
|
+
*/
|
|
24
|
+
function resolveBinary(options) {
|
|
25
|
+
const override = process.env[options.environmentVariable];
|
|
26
|
+
const candidate = override && override.trim() !== "" ? override : options.command;
|
|
27
|
+
const result = spawnSync(candidate, ["--version"], {
|
|
28
|
+
stdio: "ignore",
|
|
29
|
+
timeout: 5000,
|
|
30
|
+
});
|
|
31
|
+
if (result.error) {
|
|
32
|
+
const lines = [
|
|
33
|
+
`Error: Required dependency '${options.name}' not found.`,
|
|
34
|
+
`Looked for: ${candidate}`,
|
|
35
|
+
`Reason: ${result.error.message}`,
|
|
36
|
+
"",
|
|
37
|
+
"To fix:",
|
|
38
|
+
` 1. Install it: ${options.installHint}`,
|
|
39
|
+
` 2. Set ${options.environmentVariable}=/path/to/${options.command}`,
|
|
40
|
+
"See: axexec --help for requirements.",
|
|
41
|
+
];
|
|
42
|
+
throw new DependencyError(lines.join("\n"), options.name, options.environmentVariable, options.command);
|
|
43
|
+
}
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
export { DependencyError, resolveBinary };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the output mode based on CLI flags and TTY status.
|
|
3
|
+
*
|
|
4
|
+
* The output mode determines how events are formatted:
|
|
5
|
+
* - jsonl: JSON Lines format (one JSON object per line)
|
|
6
|
+
* - tsv: Tab-separated values (no truncation)
|
|
7
|
+
* - tsv-truncated: Tab-separated values with truncation for readability
|
|
8
|
+
*/
|
|
9
|
+
/** User-specified output format from CLI flag */
|
|
10
|
+
type OutputFormat = "jsonl" | "tsv";
|
|
11
|
+
/**
|
|
12
|
+
* Resolved output mode including auto-detected variants.
|
|
13
|
+
* - jsonl: Full JSON Lines output
|
|
14
|
+
* - tsv: Full TSV output (no truncation)
|
|
15
|
+
* - tsv-truncated: TSV with truncation for interactive display
|
|
16
|
+
*/
|
|
17
|
+
type OutputMode = "jsonl" | "tsv" | "tsv-truncated";
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the output mode based on format flag and TTY status.
|
|
20
|
+
*
|
|
21
|
+
* Pure function for testability - isTTY is passed as argument.
|
|
22
|
+
*
|
|
23
|
+
* @param format - User-specified format from --format flag, or undefined for auto-detect
|
|
24
|
+
* @param isTTY - Whether stdout is a TTY (interactive terminal)
|
|
25
|
+
* @returns The resolved output mode
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Explicit format overrides TTY detection
|
|
29
|
+
* resolveOutputMode("jsonl", true) // → "jsonl"
|
|
30
|
+
* resolveOutputMode("tsv", false) // → "tsv"
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Auto-detect based on TTY
|
|
34
|
+
* resolveOutputMode(undefined, true) // → "tsv-truncated"
|
|
35
|
+
* resolveOutputMode(undefined, false) // → "tsv"
|
|
36
|
+
*/
|
|
37
|
+
declare function resolveOutputMode(format: OutputFormat | undefined, isTTY: boolean): OutputMode;
|
|
38
|
+
export { resolveOutputMode };
|
|
39
|
+
export type { OutputFormat, OutputMode };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the output mode based on CLI flags and TTY status.
|
|
3
|
+
*
|
|
4
|
+
* The output mode determines how events are formatted:
|
|
5
|
+
* - jsonl: JSON Lines format (one JSON object per line)
|
|
6
|
+
* - tsv: Tab-separated values (no truncation)
|
|
7
|
+
* - tsv-truncated: Tab-separated values with truncation for readability
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the output mode based on format flag and TTY status.
|
|
11
|
+
*
|
|
12
|
+
* Pure function for testability - isTTY is passed as argument.
|
|
13
|
+
*
|
|
14
|
+
* @param format - User-specified format from --format flag, or undefined for auto-detect
|
|
15
|
+
* @param isTTY - Whether stdout is a TTY (interactive terminal)
|
|
16
|
+
* @returns The resolved output mode
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Explicit format overrides TTY detection
|
|
20
|
+
* resolveOutputMode("jsonl", true) // → "jsonl"
|
|
21
|
+
* resolveOutputMode("tsv", false) // → "tsv"
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Auto-detect based on TTY
|
|
25
|
+
* resolveOutputMode(undefined, true) // → "tsv-truncated"
|
|
26
|
+
* resolveOutputMode(undefined, false) // → "tsv"
|
|
27
|
+
*/
|
|
28
|
+
function resolveOutputMode(format, isTTY) {
|
|
29
|
+
// Explicit format from flag takes precedence
|
|
30
|
+
if (format === "jsonl") {
|
|
31
|
+
return "jsonl";
|
|
32
|
+
}
|
|
33
|
+
if (format === "tsv") {
|
|
34
|
+
return "tsv";
|
|
35
|
+
}
|
|
36
|
+
// Auto-detect based on TTY
|
|
37
|
+
return isTTY ? "tsv-truncated" : "tsv";
|
|
38
|
+
}
|
|
39
|
+
export { resolveOutputMode };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent execution with credential isolation and config generation.
|
|
3
|
+
*/
|
|
4
|
+
import type { AxexecEvent } from "./types/events.js";
|
|
5
|
+
import { type OutputFormat } from "./resolve-output-mode.js";
|
|
6
|
+
interface RunAgentOptions {
|
|
7
|
+
prompt: string;
|
|
8
|
+
model?: string;
|
|
9
|
+
allow?: string;
|
|
10
|
+
deny?: string;
|
|
11
|
+
format?: OutputFormat;
|
|
12
|
+
rawLog?: string;
|
|
13
|
+
debug?: boolean;
|
|
14
|
+
verbose?: boolean;
|
|
15
|
+
preserveGithubSha?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface RunResult {
|
|
18
|
+
events: AxexecEvent[];
|
|
19
|
+
success: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs an agent with full isolation.
|
|
23
|
+
*
|
|
24
|
+
* 1. Validates the agent ID
|
|
25
|
+
* 2. Parses credentials from environment
|
|
26
|
+
* 3. Installs credentials to temp directory
|
|
27
|
+
* 4. Builds config if permissions specified
|
|
28
|
+
* 5. Streams events from agent
|
|
29
|
+
* 6. Cleans up temp directory
|
|
30
|
+
*/
|
|
31
|
+
declare function runAgent(agentId: string, options: RunAgentOptions): Promise<RunResult>;
|
|
32
|
+
export { runAgent };
|