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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/bin/axexec +17 -0
  4. package/dist/agents/claude-code/adapter.d.ts +6 -0
  5. package/dist/agents/claude-code/adapter.js +71 -0
  6. package/dist/agents/claude-code/parse-event.d.ts +16 -0
  7. package/dist/agents/claude-code/parse-event.js +185 -0
  8. package/dist/agents/claude-code/types.d.ts +180 -0
  9. package/dist/agents/claude-code/types.js +112 -0
  10. package/dist/agents/codex/adapter.d.ts +6 -0
  11. package/dist/agents/codex/adapter.js +62 -0
  12. package/dist/agents/codex/item-parsers.d.ts +17 -0
  13. package/dist/agents/codex/item-parsers.js +126 -0
  14. package/dist/agents/codex/parse-event.d.ts +38 -0
  15. package/dist/agents/codex/parse-event.js +141 -0
  16. package/dist/agents/codex/types.d.ts +407 -0
  17. package/dist/agents/codex/types.js +188 -0
  18. package/dist/agents/copilot/adapter.d.ts +11 -0
  19. package/dist/agents/copilot/adapter.js +81 -0
  20. package/dist/agents/copilot/parse-event.d.ts +20 -0
  21. package/dist/agents/copilot/parse-event.js +137 -0
  22. package/dist/agents/copilot/stream-session.d.ts +10 -0
  23. package/dist/agents/copilot/stream-session.js +120 -0
  24. package/dist/agents/copilot/tail-file.d.ts +11 -0
  25. package/dist/agents/copilot/tail-file.js +52 -0
  26. package/dist/agents/copilot/transform-event.d.ts +19 -0
  27. package/dist/agents/copilot/transform-event.js +100 -0
  28. package/dist/agents/copilot/types.d.ts +320 -0
  29. package/dist/agents/copilot/types.js +220 -0
  30. package/dist/agents/copilot/watch-session.d.ts +26 -0
  31. package/dist/agents/copilot/watch-session.js +186 -0
  32. package/dist/agents/gemini/adapter.d.ts +6 -0
  33. package/dist/agents/gemini/adapter.js +78 -0
  34. package/dist/agents/gemini/parse-event.d.ts +18 -0
  35. package/dist/agents/gemini/parse-event.js +144 -0
  36. package/dist/agents/gemini/types.d.ts +162 -0
  37. package/dist/agents/gemini/types.js +103 -0
  38. package/dist/agents/opencode/adapter.d.ts +16 -0
  39. package/dist/agents/opencode/adapter.js +142 -0
  40. package/dist/agents/opencode/cleanup-session.d.ts +17 -0
  41. package/dist/agents/opencode/cleanup-session.js +41 -0
  42. package/dist/agents/opencode/create-session-start-event.d.ts +18 -0
  43. package/dist/agents/opencode/create-session-start-event.js +24 -0
  44. package/dist/agents/opencode/detect-empty-session.d.ts +25 -0
  45. package/dist/agents/opencode/detect-empty-session.js +42 -0
  46. package/dist/agents/opencode/map-error-to-event.d.ts +10 -0
  47. package/dist/agents/opencode/map-error-to-event.js +55 -0
  48. package/dist/agents/opencode/parse-message-part.d.ts +24 -0
  49. package/dist/agents/opencode/parse-message-part.js +112 -0
  50. package/dist/agents/opencode/parse-sse-event.d.ts +23 -0
  51. package/dist/agents/opencode/parse-sse-event.js +125 -0
  52. package/dist/agents/opencode/process-sse-events.d.ts +24 -0
  53. package/dist/agents/opencode/process-sse-events.js +66 -0
  54. package/dist/agents/opencode/server-types.d.ts +509 -0
  55. package/dist/agents/opencode/server-types.js +293 -0
  56. package/dist/agents/opencode/session-api.d.ts +56 -0
  57. package/dist/agents/opencode/session-api.js +151 -0
  58. package/dist/agents/opencode/spawn-server.d.ts +29 -0
  59. package/dist/agents/opencode/spawn-server.js +95 -0
  60. package/dist/agents/opencode/sse-client.d.ts +33 -0
  61. package/dist/agents/opencode/sse-client.js +145 -0
  62. package/dist/agents/registry.d.ts +15 -0
  63. package/dist/agents/registry.js +24 -0
  64. package/dist/cli.d.ts +15 -0
  65. package/dist/cli.js +119 -0
  66. package/dist/credentials/get-credential-environment.d.ts +13 -0
  67. package/dist/credentials/get-credential-environment.js +46 -0
  68. package/dist/credentials/install-credentials.d.ts +27 -0
  69. package/dist/credentials/install-credentials.js +102 -0
  70. package/dist/credentials/save-json-file.d.ts +11 -0
  71. package/dist/credentials/save-json-file.js +21 -0
  72. package/dist/credentials/types.d.ts +24 -0
  73. package/dist/credentials/types.js +4 -0
  74. package/dist/credentials/write-agent-credentials.d.ts +36 -0
  75. package/dist/credentials/write-agent-credentials.js +91 -0
  76. package/dist/determine-session-success.d.ts +15 -0
  77. package/dist/determine-session-success.js +25 -0
  78. package/dist/format-event-tsv.d.ts +23 -0
  79. package/dist/format-event-tsv.js +136 -0
  80. package/dist/parse-credentials.d.ts +13 -0
  81. package/dist/parse-credentials.js +63 -0
  82. package/dist/parse-iso-timestamp.d.ts +21 -0
  83. package/dist/parse-iso-timestamp.js +37 -0
  84. package/dist/resolve-binary.d.ts +29 -0
  85. package/dist/resolve-binary.js +46 -0
  86. package/dist/resolve-output-mode.d.ts +39 -0
  87. package/dist/resolve-output-mode.js +39 -0
  88. package/dist/run-agent.d.ts +32 -0
  89. package/dist/run-agent.js +146 -0
  90. package/dist/stream-agent.d.ts +20 -0
  91. package/dist/stream-agent.js +207 -0
  92. package/dist/types/adapter.d.ts +101 -0
  93. package/dist/types/adapter.js +14 -0
  94. package/dist/types/events.d.ts +82 -0
  95. package/dist/types/events.js +13 -0
  96. package/dist/types/options.d.ts +41 -0
  97. package/dist/types/options.js +4 -0
  98. package/dist/validate-agent.d.ts +20 -0
  99. package/dist/validate-agent.js +50 -0
  100. package/dist/write-event.d.ts +23 -0
  101. package/dist/write-event.js +43 -0
  102. 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,6 @@
1
+ /**
2
+ * Claude Code adapter implementation.
3
+ *
4
+ * Implements the {@link AgentAdapter} interface for Claude Code CLI.
5
+ */
6
+ export {};
@@ -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 };