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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Łukasz Jerciński
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# axexec
|
|
2
|
+
|
|
3
|
+
Execution engine for AI coding agents. Translates abstract inputs to agent-specific formats, runs agents in complete isolation, and normalizes output to a standard event stream.
|
|
4
|
+
|
|
5
|
+
## What axexec Does
|
|
6
|
+
|
|
7
|
+
axexec is the **translation layer** between high-level tooling and agent-specific details:
|
|
8
|
+
|
|
9
|
+
- **Input translation**: Credentials → agent-specific files, permissions → config files, model → CLI flags
|
|
10
|
+
- **Output normalization**: Claude JSONL, Codex items, Gemini events, OpenCode SSE → unified `AxexecEvent` stream
|
|
11
|
+
- **Complete isolation**: Agents run in temp directories with no access to user config or global credentials
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g axexec
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
Install the agent CLIs you plan to use:
|
|
22
|
+
|
|
23
|
+
- Claude Code: `npm install -g @anthropic-ai/claude-code` (bin: `claude`)
|
|
24
|
+
- Codex: `npm install -g @openai/codex` (bin: `codex`)
|
|
25
|
+
- Gemini: `npm install -g @google/gemini-cli` (bin: `gemini`)
|
|
26
|
+
- OpenCode: `npm install -g opencode-ai` (bin: `opencode`)
|
|
27
|
+
- Copilot CLI: `npm install -g @github/copilot` (bin: `copilot`)
|
|
28
|
+
|
|
29
|
+
### Custom Paths
|
|
30
|
+
|
|
31
|
+
If a binary is not on your PATH, set an override:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export AXEXEC_CLAUDE_PATH=/opt/claude/bin/claude
|
|
35
|
+
export AXEXEC_CODEX_PATH=/opt/codex/bin/codex
|
|
36
|
+
export AXEXEC_GEMINI_PATH=/opt/gemini/bin/gemini
|
|
37
|
+
export AXEXEC_OPENCODE_PATH=/opt/opencode/bin/opencode
|
|
38
|
+
export AXEXEC_COPILOT_PATH=/opt/copilot/bin/copilot
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Run a prompt with an agent
|
|
45
|
+
axexec --agent claude "Add error handling to auth.ts"
|
|
46
|
+
axexec -a codex "Fix the bug in main.ts"
|
|
47
|
+
axexec -a gemini "Refactor the utils module"
|
|
48
|
+
axexec -a opencode "Add logging"
|
|
49
|
+
axexec -a copilot "Write tests"
|
|
50
|
+
|
|
51
|
+
# Specify a model
|
|
52
|
+
axexec -a claude --model opus "Review this PR"
|
|
53
|
+
axexec -a gemini --model gemini-2.5-pro "Refactor utils"
|
|
54
|
+
|
|
55
|
+
# Set permissions
|
|
56
|
+
axexec -a claude --allow 'read,glob,bash:git *' "Check git history"
|
|
57
|
+
|
|
58
|
+
# Output normalized JSONL event stream
|
|
59
|
+
axexec -a claude -f jsonl "Add tests" | jq 'select(.type == "tool.call")'
|
|
60
|
+
|
|
61
|
+
# List available agents
|
|
62
|
+
axexec --list-agents
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Pipeline Examples
|
|
66
|
+
|
|
67
|
+
### Count event types
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
axexec -a claude "Summarize the change" | cut -f2 | sort | uniq -c | sort -rn
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Filter tool calls
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
axexec -a claude -f jsonl "Audit dependencies" | jq 'select(.type == "tool.call")'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Filter available agents by package
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
axexec --list-agents | tail -n +2 | awk -F'\t' '$3 ~ /openai/ {print $1}'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Options
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
-a, --agent <id> Agent to use (claude, codex, gemini, opencode, copilot)
|
|
89
|
+
-p, --prompt <text> Prompt text (alternative to positional argument)
|
|
90
|
+
-m, --model <model> Model to use (agent-specific)
|
|
91
|
+
--allow <perms> Permission rules to allow (comma-separated)
|
|
92
|
+
--deny <perms> Permission rules to deny (comma-separated)
|
|
93
|
+
-f, --format <fmt> Output format: jsonl, tsv (default: tsv, truncated on TTY)
|
|
94
|
+
--raw-log <file> Write raw agent output to file
|
|
95
|
+
--debug Enable debug mode (logs unknown events)
|
|
96
|
+
--verbose Show agent stderr output
|
|
97
|
+
--preserve-github-sha Keep GITHUB_SHA env var (Gemini excludes by default)
|
|
98
|
+
--list-agents List available agents
|
|
99
|
+
-V, --version Show version number
|
|
100
|
+
--help Show help
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Supported Agents
|
|
104
|
+
|
|
105
|
+
| Agent | Package | API Key Env Var |
|
|
106
|
+
| -------- | ------------------------- | --------------------- |
|
|
107
|
+
| claude | @anthropic-ai/claude-code | ANTHROPIC_API_KEY |
|
|
108
|
+
| codex | @openai/codex | OPENAI_API_KEY |
|
|
109
|
+
| gemini | @google/gemini-cli | GEMINI_API_KEY |
|
|
110
|
+
| opencode | opencode-ai | ANTHROPIC_API_KEY (†) |
|
|
111
|
+
| copilot | @github/copilot | GITHUB_TOKEN |
|
|
112
|
+
|
|
113
|
+
(†) OpenCode supports multiple providers via `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `GEMINI_API_KEY`.
|
|
114
|
+
|
|
115
|
+
## Event Stream
|
|
116
|
+
|
|
117
|
+
axexec normalizes all agent output to a standard event stream:
|
|
118
|
+
|
|
119
|
+
| Event Type | Description |
|
|
120
|
+
| ------------------ | ------------------------------------ |
|
|
121
|
+
| `session.start` | Agent session started |
|
|
122
|
+
| `session.complete` | Agent session completed successfully |
|
|
123
|
+
| `session.error` | Agent session failed |
|
|
124
|
+
| `agent.message` | Text output from the agent |
|
|
125
|
+
| `agent.reasoning` | Reasoning/thinking from the agent |
|
|
126
|
+
| `tool.call` | Agent invoked a tool |
|
|
127
|
+
| `tool.result` | Tool execution result |
|
|
128
|
+
|
|
129
|
+
## CI/CD Usage
|
|
130
|
+
|
|
131
|
+
### Using Credential Environment Variables
|
|
132
|
+
|
|
133
|
+
For CI/CD pipelines, credentials can be passed via environment variables:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Export credentials locally (one-time setup)
|
|
137
|
+
axauth export --agent claude --output creds.json --no-password
|
|
138
|
+
|
|
139
|
+
# Store as repository secret (e.g., AX_CLAUDE_CREDENTIALS)
|
|
140
|
+
|
|
141
|
+
# axexec auto-detects and installs credentials to temp directory
|
|
142
|
+
axexec --agent claude --prompt "Review this PR"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Agent | Credential Env Var |
|
|
146
|
+
| -------- | ----------------------- |
|
|
147
|
+
| claude | AX_CLAUDE_CREDENTIALS |
|
|
148
|
+
| codex | AX_CODEX_CREDENTIALS |
|
|
149
|
+
| gemini | AX_GEMINI_CREDENTIALS |
|
|
150
|
+
| opencode | AX_OPENCODE_CREDENTIALS |
|
|
151
|
+
| copilot | AX_COPILOT_CREDENTIALS |
|
|
152
|
+
|
|
153
|
+
### Using Standard API Keys
|
|
154
|
+
|
|
155
|
+
You can also use standard environment variables directly:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
ANTHROPIC_API_KEY=sk-... axexec -a claude "Review code"
|
|
159
|
+
OPENAI_API_KEY=sk-... axexec -a codex "Fix bug"
|
|
160
|
+
GEMINI_API_KEY=... axexec -a gemini "Refactor"
|
|
161
|
+
GITHUB_TOKEN=ghp_... axexec -a copilot "Write tests"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
For Claude, `CLAUDE_CODE_OAUTH_TOKEN` (generated via `claude setup-token`) also works.
|
|
165
|
+
|
|
166
|
+
## Isolation
|
|
167
|
+
|
|
168
|
+
axexec provides complete environment isolation:
|
|
169
|
+
|
|
170
|
+
1. Creates a temp directory for each session
|
|
171
|
+
2. Installs credentials to the temp directory
|
|
172
|
+
3. Generates agent-specific config files in the temp directory
|
|
173
|
+
4. Sets environment variables to point agents at the temp directory
|
|
174
|
+
5. Cleans up the temp directory after execution
|
|
175
|
+
|
|
176
|
+
Agents **never** access:
|
|
177
|
+
|
|
178
|
+
- User's home directory config (`~/.claude`, `~/.codex`, `~/.gemini`)
|
|
179
|
+
- System keychain or credential managers
|
|
180
|
+
- Global configuration files
|
|
181
|
+
|
|
182
|
+
## Agent Rule
|
|
183
|
+
|
|
184
|
+
Add to your `CLAUDE.md` or `AGENTS.md`:
|
|
185
|
+
|
|
186
|
+
```markdown
|
|
187
|
+
# Rule: `axexec` Usage
|
|
188
|
+
|
|
189
|
+
Run `npx -y axexec --help` to learn available options.
|
|
190
|
+
|
|
191
|
+
Use `axexec` when you need to run a supported agent in a fully isolated environment and consume a normalized event stream. It translates credentials and permissions to agent-specific formats so your automation stays consistent across providers.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
package/bin/axexec
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point that dynamically imports the compiled TypeScript.
|
|
4
|
+
*
|
|
5
|
+
* Uses top-level await to ensure module evaluation errors are handled
|
|
6
|
+
* properly. Without await, errors during import would surface as unhandled
|
|
7
|
+
* rejections instead of clean CLI failures with appropriate exit codes.
|
|
8
|
+
*/
|
|
9
|
+
try {
|
|
10
|
+
await import("../dist/cli.js");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(
|
|
13
|
+
"Failed to start axexec:",
|
|
14
|
+
error instanceof Error ? error.message : error,
|
|
15
|
+
);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code adapter implementation.
|
|
3
|
+
*
|
|
4
|
+
* Implements the {@link AgentAdapter} interface for Claude Code CLI.
|
|
5
|
+
*/
|
|
6
|
+
import { determineSessionSuccess } from "../../determine-session-success.js";
|
|
7
|
+
import { resolveBinary } from "../../resolve-binary.js";
|
|
8
|
+
import { registerAdapter } from "../registry.js";
|
|
9
|
+
import { createClaudeParser } from "./parse-event.js";
|
|
10
|
+
const CLAUDE_CODE_INFO = {
|
|
11
|
+
id: "claude",
|
|
12
|
+
name: "Claude Code",
|
|
13
|
+
package: "@anthropic-ai/claude-code",
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Prepares the command to spawn Claude Code.
|
|
17
|
+
*
|
|
18
|
+
* @see https://docs.anthropic.com/en/docs/claude-code
|
|
19
|
+
*/
|
|
20
|
+
function prepareCommand(options) {
|
|
21
|
+
const cliArguments = [
|
|
22
|
+
"-p",
|
|
23
|
+
options.prompt,
|
|
24
|
+
"--output-format",
|
|
25
|
+
"stream-json",
|
|
26
|
+
// Claude Code's --verbose is required for stream-json to emit all event
|
|
27
|
+
// types. This is different from axexec's --verbose flag which controls
|
|
28
|
+
// whether stderr is shown to the user.
|
|
29
|
+
"--verbose",
|
|
30
|
+
// Always skip permissions since stdin is ignored (non-interactive mode).
|
|
31
|
+
// Users who need interactive approval should use claude directly.
|
|
32
|
+
"--dangerously-skip-permissions",
|
|
33
|
+
];
|
|
34
|
+
// Add model flag if specified
|
|
35
|
+
// Claude Code accepts aliases (sonnet, opus, haiku) or full model names
|
|
36
|
+
if (options.model) {
|
|
37
|
+
cliArguments.push("--model", options.model);
|
|
38
|
+
}
|
|
39
|
+
const environment = {};
|
|
40
|
+
// Pass through authentication environment variables.
|
|
41
|
+
// If neither is set, Claude CLI will prompt interactively or fail with its
|
|
42
|
+
// own error message - we intentionally let the agent handle auth errors
|
|
43
|
+
// rather than duplicating that validation here.
|
|
44
|
+
if (process.env["ANTHROPIC_API_KEY"]) {
|
|
45
|
+
environment["ANTHROPIC_API_KEY"] = process.env["ANTHROPIC_API_KEY"];
|
|
46
|
+
}
|
|
47
|
+
if (process.env["CLAUDE_CODE_OAUTH_TOKEN"]) {
|
|
48
|
+
environment["CLAUDE_CODE_OAUTH_TOKEN"] =
|
|
49
|
+
process.env["CLAUDE_CODE_OAUTH_TOKEN"];
|
|
50
|
+
}
|
|
51
|
+
const bin = resolveBinary({
|
|
52
|
+
name: "claude",
|
|
53
|
+
command: "claude",
|
|
54
|
+
environmentVariable: "AXEXEC_CLAUDE_PATH",
|
|
55
|
+
installHint: "npm install -g @anthropic-ai/claude-code",
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
bin,
|
|
59
|
+
args: cliArguments,
|
|
60
|
+
env: environment,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Claude Code adapter */
|
|
64
|
+
const claudeCodeAdapter = {
|
|
65
|
+
info: () => CLAUDE_CODE_INFO,
|
|
66
|
+
prepareCommand,
|
|
67
|
+
createParser: createClaudeParser,
|
|
68
|
+
isSuccess: determineSessionSuccess,
|
|
69
|
+
};
|
|
70
|
+
// Self-register on import
|
|
71
|
+
registerAdapter(claudeCodeAdapter);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event parser for Claude Code.
|
|
3
|
+
*
|
|
4
|
+
* Transforms raw Claude Code JSONL events into normalized {@link AxexecEvent} types.
|
|
5
|
+
*/
|
|
6
|
+
import type { EventParser } from "../../types/adapter.js";
|
|
7
|
+
/**
|
|
8
|
+
* Creates a parser for Claude Code events.
|
|
9
|
+
*
|
|
10
|
+
* Maintains a mapping from callId to tool name to resolve tool names
|
|
11
|
+
* in tool.result events (which only contain the callId).
|
|
12
|
+
* Uses a stateful timestamp parser to preserve event ordering when
|
|
13
|
+
* timestamps are missing.
|
|
14
|
+
*/
|
|
15
|
+
declare function createClaudeParser(): EventParser;
|
|
16
|
+
export { createClaudeParser };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event parser for Claude Code.
|
|
3
|
+
*
|
|
4
|
+
* Transforms raw Claude Code JSONL events into normalized {@link AxexecEvent} types.
|
|
5
|
+
*/
|
|
6
|
+
import { createTimestampParser } from "../../parse-iso-timestamp.js";
|
|
7
|
+
import { ClaudeEvent, isAssistantEvent, isResultEvent, isSystemEvent, isUserEvent, } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates a parser for Claude Code events.
|
|
10
|
+
*
|
|
11
|
+
* Maintains a mapping from callId to tool name to resolve tool names
|
|
12
|
+
* in tool.result events (which only contain the callId).
|
|
13
|
+
* Uses a stateful timestamp parser to preserve event ordering when
|
|
14
|
+
* timestamps are missing.
|
|
15
|
+
*/
|
|
16
|
+
function createClaudeParser() {
|
|
17
|
+
const parseTimestamp = createTimestampParser();
|
|
18
|
+
// Map from callId to tool name for correlation
|
|
19
|
+
const toolNames = new Map();
|
|
20
|
+
return (line) => {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (trimmed === "") {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
let json;
|
|
26
|
+
try {
|
|
27
|
+
json = JSON.parse(trimmed);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Not valid JSON - skip
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
// Validate against schema - skip malformed events
|
|
34
|
+
const result = ClaudeEvent.safeParse(json);
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const raw = result.data;
|
|
39
|
+
// Dispatch based on event type
|
|
40
|
+
if (isSystemEvent(raw)) {
|
|
41
|
+
return parseSystemEvent(raw, parseTimestamp);
|
|
42
|
+
}
|
|
43
|
+
if (isAssistantEvent(raw)) {
|
|
44
|
+
return parseAssistantEvent(raw, toolNames, parseTimestamp);
|
|
45
|
+
}
|
|
46
|
+
if (isUserEvent(raw)) {
|
|
47
|
+
return parseUserEvent(raw, toolNames, parseTimestamp);
|
|
48
|
+
}
|
|
49
|
+
if (isResultEvent(raw)) {
|
|
50
|
+
return parseResultEvent(raw, parseTimestamp);
|
|
51
|
+
}
|
|
52
|
+
// Unknown event type - skip
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Parses a system event (task start) */
|
|
57
|
+
function parseSystemEvent(event, parseTimestamp) {
|
|
58
|
+
if (event.subtype !== "init") {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
type: "session.start",
|
|
63
|
+
sessionId: event.session_id ?? "unknown",
|
|
64
|
+
agent: "claude",
|
|
65
|
+
model: event.model ?? "unknown",
|
|
66
|
+
provider: "anthropic",
|
|
67
|
+
timestamp: parseTimestamp(event.timestamp),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Parses an assistant event (progress, tool calls) */
|
|
71
|
+
function parseAssistantEvent(event, toolNames, parseTimestamp) {
|
|
72
|
+
const timestamp = parseTimestamp(event.timestamp);
|
|
73
|
+
// Handle tool use from content blocks
|
|
74
|
+
if (event.content_block?.type === "tool_use") {
|
|
75
|
+
const callId = event.content_block.id ?? "unknown";
|
|
76
|
+
const tool = event.content_block.name ?? "unknown";
|
|
77
|
+
// Store for later correlation with tool.result
|
|
78
|
+
toolNames.set(callId, tool);
|
|
79
|
+
return {
|
|
80
|
+
type: "tool.call",
|
|
81
|
+
callId,
|
|
82
|
+
tool,
|
|
83
|
+
input: event.content_block.input,
|
|
84
|
+
timestamp,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Handle text content as agent message
|
|
88
|
+
if (event.content_block?.type === "text" && event.content_block.text) {
|
|
89
|
+
return {
|
|
90
|
+
type: "agent.message",
|
|
91
|
+
content: event.content_block.text,
|
|
92
|
+
timestamp,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Handle message-level content.
|
|
96
|
+
// Claude Code emits separate events per content block, so we only need to
|
|
97
|
+
// process the first matching block. Multiple blocks in a single message
|
|
98
|
+
// event would indicate a change in Claude Code's event format.
|
|
99
|
+
if (event.message?.content) {
|
|
100
|
+
for (const block of event.message.content) {
|
|
101
|
+
if (block.type === "tool_use") {
|
|
102
|
+
const callId = block.id ?? "unknown";
|
|
103
|
+
const tool = block.name ?? "unknown";
|
|
104
|
+
// Store for later correlation with tool.result
|
|
105
|
+
toolNames.set(callId, tool);
|
|
106
|
+
return {
|
|
107
|
+
type: "tool.call",
|
|
108
|
+
callId,
|
|
109
|
+
tool,
|
|
110
|
+
input: block.input,
|
|
111
|
+
timestamp,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (block.type === "text" && block.text) {
|
|
115
|
+
return {
|
|
116
|
+
type: "agent.message",
|
|
117
|
+
content: block.text,
|
|
118
|
+
timestamp,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Skip other assistant events (thinking, etc.)
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
/** Parses a user event (tool results) */
|
|
127
|
+
function parseUserEvent(event, toolNames, parseTimestamp) {
|
|
128
|
+
// Look for tool_result content blocks in message.content
|
|
129
|
+
if (!event.message?.content) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const timestamp = parseTimestamp(event.timestamp);
|
|
133
|
+
for (const block of event.message.content) {
|
|
134
|
+
if (block.type === "tool_result" && block.tool_use_id) {
|
|
135
|
+
// Determine success from tool_use_result metadata if present
|
|
136
|
+
const isError = event.tool_use_result?.type === "error" ||
|
|
137
|
+
event.tool_use_result?.status === "error";
|
|
138
|
+
// Look up tool name from previous tool.call event
|
|
139
|
+
const tool = toolNames.get(block.tool_use_id) ?? "unknown";
|
|
140
|
+
// Clean up to prevent memory leaks
|
|
141
|
+
toolNames.delete(block.tool_use_id);
|
|
142
|
+
return {
|
|
143
|
+
type: "tool.result",
|
|
144
|
+
callId: block.tool_use_id,
|
|
145
|
+
tool,
|
|
146
|
+
output: block.content,
|
|
147
|
+
success: !isError,
|
|
148
|
+
timestamp,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
/** Parses a result event (task completion) */
|
|
155
|
+
function parseResultEvent(event, parseTimestamp) {
|
|
156
|
+
const timestamp = parseTimestamp(event.timestamp);
|
|
157
|
+
if (event.subtype === "success") {
|
|
158
|
+
return {
|
|
159
|
+
type: "session.complete",
|
|
160
|
+
stats: {
|
|
161
|
+
durationMs: event.duration_ms ?? 0,
|
|
162
|
+
costUsd: event.total_cost_usd,
|
|
163
|
+
inputTokens: event.usage?.input_tokens,
|
|
164
|
+
outputTokens: event.usage?.output_tokens,
|
|
165
|
+
},
|
|
166
|
+
timestamp,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (event.subtype === "error") {
|
|
170
|
+
return {
|
|
171
|
+
type: "session.error",
|
|
172
|
+
code: "AGENT_ERROR",
|
|
173
|
+
message: event.error ?? event.message ?? "Unknown error",
|
|
174
|
+
timestamp,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Cancelled or other - treat as error
|
|
178
|
+
return {
|
|
179
|
+
type: "session.error",
|
|
180
|
+
code: "CANCELLED",
|
|
181
|
+
message: event.message ?? "Session was cancelled",
|
|
182
|
+
timestamp,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export { createClaudeParser };
|