axexec 1.0.0 → 1.1.1
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 +21 -0
- package/dist/agents/claude-code/types.d.ts +2 -2
- package/dist/agents/codex/adapter.js +8 -13
- package/dist/agents/gemini/types.d.ts +4 -4
- package/dist/build-agent-environment.d.ts +2 -0
- package/dist/build-agent-environment.js +19 -0
- package/dist/build-execution-metadata.d.ts +10 -0
- package/dist/build-execution-metadata.js +27 -0
- package/dist/build-permissions-config.d.ts +24 -0
- package/dist/build-permissions-config.js +48 -0
- package/dist/credentials/credentials.d.ts +39 -0
- package/dist/credentials/credentials.js +73 -0
- package/dist/credentials/get-credential-environment.d.ts +7 -5
- package/dist/credentials/get-credential-environment.js +44 -14
- package/dist/credentials/get-environment-trimmed.d.ts +5 -0
- package/dist/credentials/get-environment-trimmed.js +8 -0
- package/dist/credentials/install-credentials.d.ts +7 -6
- package/dist/credentials/install-credentials.js +26 -16
- package/dist/credentials/resolve-string-field.d.ts +7 -0
- package/dist/credentials/resolve-string-field.js +21 -0
- package/dist/credentials/types.d.ts +1 -10
- package/dist/credentials/write-agent-credentials.d.ts +28 -6
- package/dist/credentials/write-agent-credentials.js +88 -43
- package/dist/execute-agent.d.ts +12 -0
- package/dist/execute-agent.js +68 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/parse-credentials.d.ts +17 -3
- package/dist/parse-credentials.js +146 -27
- package/dist/resolve-run-diagnostics.d.ts +9 -0
- package/dist/resolve-run-diagnostics.js +35 -0
- package/dist/run-agent.d.ts +4 -20
- package/dist/run-agent.js +67 -109
- package/dist/stream-agent.js +5 -15
- package/dist/types/run-result.d.ts +81 -0
- package/dist/types/run-result.js +4 -0
- package/package.json +10 -2
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential types for axexec.
|
|
3
3
|
*/
|
|
4
|
-
/** Raw credentials that can be installed for any agent */
|
|
5
|
-
interface RawCredentials {
|
|
6
|
-
/** OAuth credentials (access token, refresh token, etc.) */
|
|
7
|
-
oauth?: Record<string, unknown>;
|
|
8
|
-
/** API key for direct API access */
|
|
9
|
-
apiKey?: string;
|
|
10
|
-
/** GitHub token for Copilot */
|
|
11
|
-
githubToken?: string;
|
|
12
|
-
}
|
|
13
4
|
/** Result of credential installation */
|
|
14
5
|
interface InstallResult {
|
|
15
6
|
/** Environment variables to pass to the agent subprocess */
|
|
@@ -21,4 +12,4 @@ interface InstallResult {
|
|
|
21
12
|
/** Base temporary directory (for cleanup) */
|
|
22
13
|
baseDirectory: string;
|
|
23
14
|
}
|
|
24
|
-
export type { InstallResult
|
|
15
|
+
export type { InstallResult };
|
|
@@ -2,35 +2,57 @@
|
|
|
2
2
|
* Per-agent credential file writers.
|
|
3
3
|
*
|
|
4
4
|
* Each agent has its own credential file format. These functions
|
|
5
|
-
* write credentials in the agent-specific format
|
|
5
|
+
* write credentials in the agent-specific format, extracting what
|
|
6
|
+
* they need from the canonical Credentials type.
|
|
6
7
|
*/
|
|
7
|
-
import type {
|
|
8
|
+
import type { Credentials } from "./credentials.js";
|
|
9
|
+
type WarningWriter = (message: string) => void;
|
|
8
10
|
/**
|
|
9
11
|
* Installs Claude credentials to a config directory.
|
|
10
12
|
*
|
|
11
13
|
* File: .credentials.json
|
|
12
14
|
* Format: { claudeAiOauth: { ... } }
|
|
15
|
+
*
|
|
16
|
+
* Handles:
|
|
17
|
+
* - oauth-credentials: Full OAuth data written to file
|
|
18
|
+
* - oauth-token: Not written (env var only)
|
|
19
|
+
* - api-key: Not written (env var only)
|
|
13
20
|
*/
|
|
14
|
-
declare function installClaudeCredentials(configDirectory: string, credentials:
|
|
21
|
+
declare function installClaudeCredentials(configDirectory: string, credentials: Credentials): void;
|
|
15
22
|
/**
|
|
16
23
|
* Installs Codex credentials to a config directory.
|
|
17
24
|
*
|
|
18
25
|
* File: auth.json
|
|
19
26
|
* Format: { OPENAI_API_KEY: "..." } or { tokens: { ... }, last_refresh: "..." }
|
|
27
|
+
*
|
|
28
|
+
* Handles:
|
|
29
|
+
* - api-key: Written as { OPENAI_API_KEY: "..." }
|
|
30
|
+
* - oauth-credentials: Written as { tokens: { ... }, last_refresh: "..." }
|
|
20
31
|
*/
|
|
21
|
-
declare function installCodexCredentials(configDirectory: string, credentials:
|
|
32
|
+
declare function installCodexCredentials(configDirectory: string, credentials: Credentials, warn?: WarningWriter): void;
|
|
22
33
|
/**
|
|
23
34
|
* Installs Gemini credentials to a config directory.
|
|
24
35
|
*
|
|
25
36
|
* File: oauth_creds.json
|
|
26
37
|
* Format: { access_token: "...", refresh_token: "...", ... }
|
|
38
|
+
*
|
|
39
|
+
* Handles:
|
|
40
|
+
* - oauth-credentials: Full OAuth data written to file
|
|
41
|
+
* - api-key: Not written (env var only)
|
|
27
42
|
*/
|
|
28
|
-
declare function installGeminiCredentials(configDirectory: string, credentials:
|
|
43
|
+
declare function installGeminiCredentials(configDirectory: string, credentials: Credentials): void;
|
|
29
44
|
/**
|
|
30
45
|
* Installs OpenCode credentials to a data directory.
|
|
31
46
|
*
|
|
32
47
|
* File: auth.json
|
|
33
48
|
* Format: { anthropic: { type: "oauth", ... }, openai: { type: "api", ... } }
|
|
49
|
+
*
|
|
50
|
+
* OpenCode is a multi-provider agent. The Credentials.provider field
|
|
51
|
+
* specifies which provider (anthropic, openai, etc.) the credential is for.
|
|
52
|
+
*
|
|
53
|
+
* Handles:
|
|
54
|
+
* - oauth-credentials: Written as { [provider]: { type: "oauth", ...data } }
|
|
55
|
+
* - api-key: Written as { [provider]: { type: "api", key: "..." } }
|
|
34
56
|
*/
|
|
35
|
-
declare function installOpenCodeCredentials(dataDirectory: string, credentials:
|
|
57
|
+
declare function installOpenCodeCredentials(dataDirectory: string, credentials: Credentials, warn?: WarningWriter): void;
|
|
36
58
|
export { installClaudeCredentials, installCodexCredentials, installGeminiCredentials, installOpenCodeCredentials, };
|
|
@@ -2,45 +2,74 @@
|
|
|
2
2
|
* Per-agent credential file writers.
|
|
3
3
|
*
|
|
4
4
|
* Each agent has its own credential file format. These functions
|
|
5
|
-
* write credentials in the agent-specific format
|
|
5
|
+
* write credentials in the agent-specific format, extracting what
|
|
6
|
+
* they need from the canonical Credentials type.
|
|
6
7
|
*/
|
|
7
8
|
import path from "node:path";
|
|
9
|
+
import { resolveStringField } from "./resolve-string-field.js";
|
|
8
10
|
import { saveJsonFile } from "./save-json-file.js";
|
|
11
|
+
function normalizeOpenCodeProvider(provider) {
|
|
12
|
+
if (!provider)
|
|
13
|
+
return "anthropic";
|
|
14
|
+
return provider === "gemini" ? "google" : provider;
|
|
15
|
+
}
|
|
16
|
+
function defaultWarningWriter(message) {
|
|
17
|
+
const formatted = message.endsWith("\n") ? message : `${message}\n`;
|
|
18
|
+
process.stderr.write(formatted);
|
|
19
|
+
}
|
|
9
20
|
/**
|
|
10
21
|
* Installs Claude credentials to a config directory.
|
|
11
22
|
*
|
|
12
23
|
* File: .credentials.json
|
|
13
24
|
* Format: { claudeAiOauth: { ... } }
|
|
25
|
+
*
|
|
26
|
+
* Handles:
|
|
27
|
+
* - oauth-credentials: Full OAuth data written to file
|
|
28
|
+
* - oauth-token: Not written (env var only)
|
|
29
|
+
* - api-key: Not written (env var only)
|
|
14
30
|
*/
|
|
15
31
|
function installClaudeCredentials(configDirectory, credentials) {
|
|
16
|
-
|
|
32
|
+
// Only oauth-credentials need file installation
|
|
33
|
+
if (credentials.type !== "oauth-credentials") {
|
|
17
34
|
return;
|
|
18
35
|
}
|
|
19
36
|
const credentialsPath = path.join(configDirectory, ".credentials.json");
|
|
20
|
-
|
|
21
|
-
saveJsonFile(credentialsPath, { claudeAiOauth: credentials.oauth });
|
|
22
|
-
}
|
|
23
|
-
// API key is passed via environment variable, not file
|
|
37
|
+
saveJsonFile(credentialsPath, { claudeAiOauth: credentials.data });
|
|
24
38
|
}
|
|
25
39
|
/**
|
|
26
40
|
* Installs Codex credentials to a config directory.
|
|
27
41
|
*
|
|
28
42
|
* File: auth.json
|
|
29
43
|
* Format: { OPENAI_API_KEY: "..." } or { tokens: { ... }, last_refresh: "..." }
|
|
44
|
+
*
|
|
45
|
+
* Handles:
|
|
46
|
+
* - api-key: Written as { OPENAI_API_KEY: "..." }
|
|
47
|
+
* - oauth-credentials: Written as { tokens: { ... }, last_refresh: "..." }
|
|
30
48
|
*/
|
|
31
|
-
function installCodexCredentials(configDirectory, credentials) {
|
|
32
|
-
if (!credentials.oauth && !credentials.apiKey) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
49
|
+
function installCodexCredentials(configDirectory, credentials, warn = defaultWarningWriter) {
|
|
35
50
|
const authPath = path.join(configDirectory, "auth.json");
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
switch (credentials.type) {
|
|
52
|
+
case "api-key": {
|
|
53
|
+
const apiKey = resolveStringField(credentials.data, "apiKey", "key");
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
warn("Warning: installCodexCredentials: no API key found in credentials.data. auth.json was not written.");
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
saveJsonFile(authPath, { OPENAI_API_KEY: apiKey });
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "oauth-credentials": {
|
|
62
|
+
saveJsonFile(authPath, {
|
|
63
|
+
tokens: credentials.data,
|
|
64
|
+
last_refresh: new Date().toISOString(),
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "oauth-token": {
|
|
69
|
+
// OAuth tokens are not applicable for Codex (uses API key only)
|
|
70
|
+
warn("Warning: installCodexCredentials: oauth-token credentials are not applicable for Codex. No authentication was configured.");
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
44
73
|
}
|
|
45
74
|
}
|
|
46
75
|
/**
|
|
@@ -48,43 +77,59 @@ function installCodexCredentials(configDirectory, credentials) {
|
|
|
48
77
|
*
|
|
49
78
|
* File: oauth_creds.json
|
|
50
79
|
* Format: { access_token: "...", refresh_token: "...", ... }
|
|
80
|
+
*
|
|
81
|
+
* Handles:
|
|
82
|
+
* - oauth-credentials: Full OAuth data written to file
|
|
83
|
+
* - api-key: Not written (env var only)
|
|
51
84
|
*/
|
|
52
85
|
function installGeminiCredentials(configDirectory, credentials) {
|
|
53
|
-
|
|
86
|
+
// Only oauth-credentials need file installation
|
|
87
|
+
if (credentials.type !== "oauth-credentials") {
|
|
54
88
|
return;
|
|
55
89
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
saveJsonFile(credentialsPath, credentials.oauth);
|
|
59
|
-
}
|
|
60
|
-
// API key is passed via environment variable, not file
|
|
90
|
+
const credentialsPath = path.join(configDirectory, "oauth_creds.json");
|
|
91
|
+
saveJsonFile(credentialsPath, credentials.data);
|
|
61
92
|
}
|
|
62
93
|
/**
|
|
63
94
|
* Installs OpenCode credentials to a data directory.
|
|
64
95
|
*
|
|
65
96
|
* File: auth.json
|
|
66
97
|
* Format: { anthropic: { type: "oauth", ... }, openai: { type: "api", ... } }
|
|
98
|
+
*
|
|
99
|
+
* OpenCode is a multi-provider agent. The Credentials.provider field
|
|
100
|
+
* specifies which provider (anthropic, openai, etc.) the credential is for.
|
|
101
|
+
*
|
|
102
|
+
* Handles:
|
|
103
|
+
* - oauth-credentials: Written as { [provider]: { type: "oauth", ...data } }
|
|
104
|
+
* - api-key: Written as { [provider]: { type: "api", key: "..." } }
|
|
67
105
|
*/
|
|
68
|
-
function installOpenCodeCredentials(dataDirectory, credentials) {
|
|
69
|
-
if (!credentials.oauth && !credentials.apiKey) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
106
|
+
function installOpenCodeCredentials(dataDirectory, credentials, warn = defaultWarningWriter) {
|
|
72
107
|
const authPath = path.join(dataDirectory, "auth.json");
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
const provider = normalizeOpenCodeProvider(credentials.provider);
|
|
109
|
+
switch (credentials.type) {
|
|
110
|
+
case "oauth-credentials": {
|
|
111
|
+
saveJsonFile(authPath, {
|
|
112
|
+
[provider]: {
|
|
113
|
+
type: "oauth",
|
|
114
|
+
...credentials.data,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "api-key": {
|
|
120
|
+
const apiKey = resolveStringField(credentials.data, "apiKey", "key");
|
|
121
|
+
if (!apiKey) {
|
|
122
|
+
warn(`Warning: installOpenCodeCredentials: no API key found in credentials.data for provider "${provider}". auth.json was not written.`);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
saveJsonFile(authPath, {
|
|
126
|
+
[provider]: {
|
|
127
|
+
type: "api",
|
|
128
|
+
key: apiKey,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
88
133
|
}
|
|
89
134
|
}
|
|
90
135
|
// Note: Copilot uses environment variables (GITHUB_TOKEN), not file-based credentials.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentAdapter } from "./types/adapter.js";
|
|
2
|
+
import type { AgentCli } from "./types/events.js";
|
|
3
|
+
import type { RunAgentOptions, RunResult } from "./types/run-result.js";
|
|
4
|
+
import type { Credentials } from "./credentials/credentials.js";
|
|
5
|
+
import type { installCredentials } from "./credentials/install-credentials.js";
|
|
6
|
+
import type { DiagnosticWriter } from "./resolve-run-diagnostics.js";
|
|
7
|
+
type CredentialInstallResult = Awaited<ReturnType<typeof installCredentials>>;
|
|
8
|
+
/**
|
|
9
|
+
* Executes agent and streams events.
|
|
10
|
+
*/
|
|
11
|
+
declare function executeAgent(agentId: AgentCli, adapter: AgentAdapter, options: RunAgentOptions, configEnvironment: Record<string, string>, credentialResult: CredentialInstallResult | undefined, credentials: Credentials | undefined, preserveConfigDirectory: boolean, emitError: DiagnosticWriter): Promise<RunResult>;
|
|
12
|
+
export { executeAgent };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isStreamableAdapter } from "./types/adapter.js";
|
|
2
|
+
import { cleanupCredentials } from "./credentials/install-credentials.js";
|
|
3
|
+
import { streamAgent } from "./stream-agent.js";
|
|
4
|
+
import { resolveOutputMode } from "./resolve-output-mode.js";
|
|
5
|
+
import { createEventWriter } from "./write-event.js";
|
|
6
|
+
import { DependencyError } from "./resolve-binary.js";
|
|
7
|
+
import { buildExecutionMetadata } from "./build-execution-metadata.js";
|
|
8
|
+
/**
|
|
9
|
+
* Executes agent and streams events.
|
|
10
|
+
*/
|
|
11
|
+
async function executeAgent(agentId, adapter, options, configEnvironment, credentialResult, credentials, preserveConfigDirectory, emitError) {
|
|
12
|
+
const runOptions = {
|
|
13
|
+
prompt: options.prompt,
|
|
14
|
+
verbose: options.verbose ?? false,
|
|
15
|
+
model: options.model,
|
|
16
|
+
rawLogPath: options.rawLog,
|
|
17
|
+
debug: options.debug,
|
|
18
|
+
configEnv: configEnvironment,
|
|
19
|
+
preserveGithubSha: options.preserveGithubSha,
|
|
20
|
+
};
|
|
21
|
+
const outputMode = resolveOutputMode(options.format, process.stdout.isTTY);
|
|
22
|
+
const writeEvent = createEventWriter(outputMode);
|
|
23
|
+
const events = [];
|
|
24
|
+
// Build execution metadata (same for success and error cases)
|
|
25
|
+
const execution = buildExecutionMetadata(agentId, options, credentialResult, credentials, preserveConfigDirectory);
|
|
26
|
+
try {
|
|
27
|
+
const eventStream = isStreamableAdapter(adapter)
|
|
28
|
+
? adapter.streamSession(runOptions)
|
|
29
|
+
: streamAgent(adapter, runOptions);
|
|
30
|
+
for await (const event of eventStream) {
|
|
31
|
+
events.push(event);
|
|
32
|
+
writeEvent(event);
|
|
33
|
+
}
|
|
34
|
+
const success = adapter.isSuccess(events);
|
|
35
|
+
return { events, success, execution };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof DependencyError) {
|
|
39
|
+
emitError(error.message);
|
|
40
|
+
const errorEvent = {
|
|
41
|
+
type: "session.error",
|
|
42
|
+
code: "DEPENDENCY_ERROR",
|
|
43
|
+
message: error.message,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
};
|
|
46
|
+
events.push(errorEvent);
|
|
47
|
+
writeEvent(errorEvent);
|
|
48
|
+
return { events, success: false, execution };
|
|
49
|
+
}
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
const errorEvent = {
|
|
52
|
+
type: "session.error",
|
|
53
|
+
code: "SPAWN_ERROR",
|
|
54
|
+
message,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
};
|
|
57
|
+
events.push(errorEvent);
|
|
58
|
+
writeEvent(errorEvent);
|
|
59
|
+
return { events, success: false, execution };
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
// Clean up temp directory unless caller wants to preserve it
|
|
63
|
+
if (credentialResult && !preserveConfigDirectory) {
|
|
64
|
+
await cleanupCredentials(credentialResult.baseDirectory);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export { executeAgent };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axexec - Unified execution engine for AI coding agents.
|
|
3
|
+
*
|
|
4
|
+
* Programmatic API for running agents with credential isolation,
|
|
5
|
+
* config generation, and normalized event streaming.
|
|
6
|
+
*/
|
|
7
|
+
import "./agents/claude-code/adapter.js";
|
|
8
|
+
import "./agents/codex/adapter.js";
|
|
9
|
+
import "./agents/gemini/adapter.js";
|
|
10
|
+
import "./agents/opencode/adapter.js";
|
|
11
|
+
import "./agents/copilot/adapter.js";
|
|
12
|
+
export { runAgent } from "./run-agent.js";
|
|
13
|
+
export { cleanupCredentials } from "./credentials/install-credentials.js";
|
|
14
|
+
export type { ExecutionCredentials, ExecutionDirectories, ExecutionMetadata, RunAgentDiagnostics, RunAgentOptions, RunResult, } from "./types/run-result.js";
|
|
15
|
+
export type { AxexecEvent, AgentCli } from "./types/events.js";
|
|
16
|
+
export type { Credentials } from "./credentials/credentials.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axexec - Unified execution engine for AI coding agents.
|
|
3
|
+
*
|
|
4
|
+
* Programmatic API for running agents with credential isolation,
|
|
5
|
+
* config generation, and normalized event streaming.
|
|
6
|
+
*/
|
|
7
|
+
// Import all adapters to trigger self-registration
|
|
8
|
+
import "./agents/claude-code/adapter.js";
|
|
9
|
+
import "./agents/codex/adapter.js";
|
|
10
|
+
import "./agents/gemini/adapter.js";
|
|
11
|
+
import "./agents/opencode/adapter.js";
|
|
12
|
+
import "./agents/copilot/adapter.js";
|
|
13
|
+
// Main entry point
|
|
14
|
+
export { runAgent } from "./run-agent.js";
|
|
15
|
+
// Cleanup utility for use with preserveConfigDirectory option
|
|
16
|
+
export { cleanupCredentials } from "./credentials/install-credentials.js";
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential parsing from environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Used by axexec CLI to read credentials from environment when
|
|
5
|
+
* not provided via the programmatic API.
|
|
3
6
|
*/
|
|
4
|
-
import type {
|
|
7
|
+
import type { AgentCli } from "axshared";
|
|
8
|
+
import { type Credentials } from "./credentials/credentials.js";
|
|
9
|
+
type ParseCredentialsResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
credentials: Credentials | undefined;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
message: string;
|
|
15
|
+
exitCode: number;
|
|
16
|
+
};
|
|
5
17
|
/**
|
|
6
18
|
* Parses credentials from environment variables.
|
|
7
19
|
*
|
|
8
20
|
* Environment variables checked (in order):
|
|
9
|
-
* - AX_<AGENT>_CREDENTIALS: JSON with
|
|
21
|
+
* - AX_<AGENT>_CREDENTIALS: JSON with full Credentials object
|
|
10
22
|
* - Standard env vars: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
|
|
23
|
+
*
|
|
24
|
+
* Returns ok with undefined credentials if none are found.
|
|
11
25
|
*/
|
|
12
|
-
declare function parseCredentialsFromEnvironment(agentId: string):
|
|
26
|
+
declare function parseCredentialsFromEnvironment(agentId: AgentCli, model?: string): ParseCredentialsResult;
|
|
13
27
|
export { parseCredentialsFromEnvironment };
|
|
@@ -1,63 +1,182 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential parsing from environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Used by axexec CLI to read credentials from environment when
|
|
5
|
+
* not provided via the programmatic API.
|
|
3
6
|
*/
|
|
7
|
+
import { parseCredentials, } from "./credentials/credentials.js";
|
|
8
|
+
import { getEnvironmentTrimmed } from "./credentials/get-environment-trimmed.js";
|
|
4
9
|
/**
|
|
5
10
|
* Parses credentials from environment variables.
|
|
6
11
|
*
|
|
7
12
|
* Environment variables checked (in order):
|
|
8
|
-
* - AX_<AGENT>_CREDENTIALS: JSON with
|
|
13
|
+
* - AX_<AGENT>_CREDENTIALS: JSON with full Credentials object
|
|
9
14
|
* - Standard env vars: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
|
|
15
|
+
*
|
|
16
|
+
* Returns ok with undefined credentials if none are found.
|
|
10
17
|
*/
|
|
11
|
-
function parseCredentialsFromEnvironment(agentId) {
|
|
12
|
-
|
|
13
|
-
// Check for axexec credentials env var
|
|
18
|
+
function parseCredentialsFromEnvironment(agentId, model) {
|
|
19
|
+
// Check for axexec credentials env var (full Credentials JSON)
|
|
14
20
|
const credEnvironmentVariable = `AX_${agentId.toUpperCase()}_CREDENTIALS`;
|
|
15
21
|
const credEnvironmentValue = process.env[credEnvironmentVariable];
|
|
16
22
|
if (credEnvironmentValue) {
|
|
17
23
|
try {
|
|
18
24
|
const parsed = JSON.parse(credEnvironmentValue);
|
|
19
|
-
|
|
25
|
+
const parseResult = parseCredentials(parsed);
|
|
26
|
+
if (parseResult.ok) {
|
|
27
|
+
if (parseResult.credentials.agent !== agentId) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
exitCode: 2,
|
|
31
|
+
message: `Error: ${credEnvironmentVariable} agent mismatch. ` +
|
|
32
|
+
`Expected ${agentId}, got ${parseResult.credentials.agent}.`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, credentials: parseResult.credentials };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
exitCode: 2,
|
|
40
|
+
message: `Error: Invalid credentials format in ${credEnvironmentVariable}: ` +
|
|
41
|
+
parseResult.error,
|
|
42
|
+
};
|
|
20
43
|
}
|
|
21
|
-
catch {
|
|
22
|
-
|
|
44
|
+
catch (error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
exitCode: 2,
|
|
49
|
+
message: `Error: Failed to parse ${credEnvironmentVariable}: ${message}`,
|
|
50
|
+
};
|
|
23
51
|
}
|
|
24
52
|
}
|
|
25
|
-
// Fall back to standard env vars
|
|
53
|
+
// Fall back to standard API key env vars
|
|
54
|
+
return parseApiKeyFromEnvironment(agentId, model);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parses API key from standard environment variables.
|
|
58
|
+
*/
|
|
59
|
+
function parseApiKeyFromEnvironment(agentId, model) {
|
|
60
|
+
let apiKey;
|
|
26
61
|
switch (agentId) {
|
|
27
62
|
case "claude": {
|
|
28
|
-
|
|
29
|
-
|
|
63
|
+
// Check for OAuth token first (higher priority)
|
|
64
|
+
const oauthToken = getEnvironmentTrimmed("CLAUDE_CODE_OAUTH_TOKEN");
|
|
65
|
+
if (oauthToken) {
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
credentials: {
|
|
69
|
+
agent: agentId,
|
|
70
|
+
type: "oauth-token",
|
|
71
|
+
data: { oauthToken },
|
|
72
|
+
},
|
|
73
|
+
};
|
|
30
74
|
}
|
|
75
|
+
apiKey = getEnvironmentTrimmed("ANTHROPIC_API_KEY");
|
|
31
76
|
break;
|
|
32
77
|
}
|
|
33
78
|
case "codex": {
|
|
34
|
-
|
|
35
|
-
credentials.apiKey = process.env["OPENAI_API_KEY"];
|
|
36
|
-
}
|
|
79
|
+
apiKey = getEnvironmentTrimmed("OPENAI_API_KEY");
|
|
37
80
|
break;
|
|
38
81
|
}
|
|
39
82
|
case "gemini": {
|
|
40
|
-
|
|
41
|
-
credentials.apiKey = process.env["GEMINI_API_KEY"];
|
|
42
|
-
}
|
|
83
|
+
apiKey = getEnvironmentTrimmed("GEMINI_API_KEY");
|
|
43
84
|
break;
|
|
44
85
|
}
|
|
45
86
|
case "opencode": {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
break;
|
|
87
|
+
// OpenCode can use multiple providers
|
|
88
|
+
return parseOpenCodeApiKeyFromEnvironment(model);
|
|
50
89
|
}
|
|
51
90
|
case "copilot": {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
credentials.githubToken = token;
|
|
57
|
-
}
|
|
91
|
+
apiKey =
|
|
92
|
+
getEnvironmentTrimmed("COPILOT_GITHUB_TOKEN") ??
|
|
93
|
+
getEnvironmentTrimmed("GH_TOKEN") ??
|
|
94
|
+
getEnvironmentTrimmed("GITHUB_TOKEN");
|
|
58
95
|
break;
|
|
59
96
|
}
|
|
60
97
|
}
|
|
61
|
-
|
|
98
|
+
if (!apiKey) {
|
|
99
|
+
return { ok: true, credentials: undefined };
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
credentials: {
|
|
104
|
+
agent: agentId,
|
|
105
|
+
type: "api-key",
|
|
106
|
+
data: { apiKey },
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function normalizeOpenCodeProvider(provider) {
|
|
111
|
+
if (!provider)
|
|
112
|
+
return undefined;
|
|
113
|
+
return provider === "gemini" ? "google" : provider;
|
|
114
|
+
}
|
|
115
|
+
function parseOpenCodeApiKeyFromEnvironment(model) {
|
|
116
|
+
const providers = [
|
|
117
|
+
{
|
|
118
|
+
provider: "anthropic",
|
|
119
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
120
|
+
value: getEnvironmentTrimmed("ANTHROPIC_API_KEY"),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
provider: "openai",
|
|
124
|
+
envVar: "OPENAI_API_KEY",
|
|
125
|
+
value: getEnvironmentTrimmed("OPENAI_API_KEY"),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
provider: "google",
|
|
129
|
+
envVar: "GEMINI_API_KEY",
|
|
130
|
+
value: getEnvironmentTrimmed("GEMINI_API_KEY"),
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
const available = providers.filter((entry) => entry.value !== undefined);
|
|
134
|
+
const modelProvider = normalizeOpenCodeProvider(model?.split("/")[0]);
|
|
135
|
+
if (modelProvider &&
|
|
136
|
+
["anthropic", "openai", "google"].includes(modelProvider)) {
|
|
137
|
+
const match = providers.find((entry) => entry.provider === modelProvider);
|
|
138
|
+
if (match?.value) {
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
credentials: {
|
|
142
|
+
agent: "opencode",
|
|
143
|
+
type: "api-key",
|
|
144
|
+
provider: modelProvider,
|
|
145
|
+
data: { apiKey: match.value },
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const requiredEnvironmentVariable = match?.envVar ?? "AX_OPENCODE_CREDENTIALS";
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
exitCode: 2,
|
|
153
|
+
message: `Error: ${requiredEnvironmentVariable} is required for OpenCode ` +
|
|
154
|
+
`model provider "${modelProvider}".`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (available.length === 0) {
|
|
158
|
+
return { ok: true, credentials: undefined };
|
|
159
|
+
}
|
|
160
|
+
if (available.length === 1) {
|
|
161
|
+
const match = available[0];
|
|
162
|
+
if (!match)
|
|
163
|
+
return { ok: true, credentials: undefined };
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
credentials: {
|
|
167
|
+
agent: "opencode",
|
|
168
|
+
type: "api-key",
|
|
169
|
+
provider: match.provider,
|
|
170
|
+
data: { apiKey: match.value },
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const environmentList = available.map((entry) => entry.envVar).join(", ");
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
exitCode: 2,
|
|
178
|
+
message: "Error: Multiple OpenCode API keys detected. " +
|
|
179
|
+
`Set AX_OPENCODE_CREDENTIALS to disambiguate (${environmentList}).`,
|
|
180
|
+
};
|
|
62
181
|
}
|
|
63
182
|
export { parseCredentialsFromEnvironment };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RunAgentOptions } from "./types/run-result.js";
|
|
2
|
+
type DiagnosticWriter = (message: string) => void;
|
|
3
|
+
declare function resolveDiagnostics(options: RunAgentOptions): {
|
|
4
|
+
error: DiagnosticWriter;
|
|
5
|
+
warn: DiagnosticWriter;
|
|
6
|
+
};
|
|
7
|
+
declare function resolveExitCodeSetter(options: RunAgentOptions): (code: number) => void;
|
|
8
|
+
export type { DiagnosticWriter };
|
|
9
|
+
export { resolveDiagnostics, resolveExitCodeSetter };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function formatDiagnosticMessage(message) {
|
|
2
|
+
return message.endsWith("\n") ? message : `${message}\n`;
|
|
3
|
+
}
|
|
4
|
+
function resolveDiagnostics(options) {
|
|
5
|
+
if (options.diagnostics) {
|
|
6
|
+
const diagnostics = options.diagnostics;
|
|
7
|
+
return {
|
|
8
|
+
error: (message) => {
|
|
9
|
+
diagnostics.error(formatDiagnosticMessage(message));
|
|
10
|
+
},
|
|
11
|
+
warn: (message) => {
|
|
12
|
+
diagnostics.warn(formatDiagnosticMessage(message));
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
error: (message) => {
|
|
18
|
+
process.stderr.write(formatDiagnosticMessage(message));
|
|
19
|
+
},
|
|
20
|
+
warn: (message) => {
|
|
21
|
+
process.stderr.write(formatDiagnosticMessage(message));
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function noopExitCodeSetter() { }
|
|
26
|
+
function setProcessExitCode(code) {
|
|
27
|
+
process.exitCode = code;
|
|
28
|
+
}
|
|
29
|
+
function resolveExitCodeSetter(options) {
|
|
30
|
+
if (options.setExitCode === false) {
|
|
31
|
+
return noopExitCodeSetter;
|
|
32
|
+
}
|
|
33
|
+
return setProcessExitCode;
|
|
34
|
+
}
|
|
35
|
+
export { resolveDiagnostics, resolveExitCodeSetter };
|