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
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Event parser for Copilot CLI.
3
+ *
4
+ * Parses JSONL events from Copilot CLI session files. The wrapper script
5
+ * (stream-session.ts) tails the session file and outputs events to stdout,
6
+ * which are then parsed by this module.
7
+ */
8
+ import type { EventParser } from "../../types/adapter.js";
9
+ /**
10
+ * Creates a parser for Copilot CLI JSONL events.
11
+ *
12
+ * Maintains state for:
13
+ * - Tool name correlation (toolCallId -> tool name)
14
+ * - Tool call deduplication (tracks emitted tool.call events)
15
+ * - Model tracking (updated from session.model_change)
16
+ * - Usage accumulation (for session.complete stats)
17
+ * - Turn end tracking (for session.complete emission)
18
+ */
19
+ declare function createCopilotParser(): EventParser;
20
+ export { createCopilotParser };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Event parser for Copilot CLI.
3
+ *
4
+ * Parses JSONL events from Copilot CLI session files. The wrapper script
5
+ * (stream-session.ts) tails the session file and outputs events to stdout,
6
+ * which are then parsed by this module.
7
+ */
8
+ import { createTimestampParser } from "../../parse-iso-timestamp.js";
9
+ import { CopilotEvent, isAbortEvent, isAssistantMessageEvent, isAssistantTurnEndEvent, isAssistantUsageEvent, isSessionErrorEvent, isSessionModelChangeEvent, isSessionStartEvent, isToolExecutionCompleteEvent, isToolExecutionStartEvent, } from "./types.js";
10
+ import { transformAssistantMessageEvent, transformSessionErrorEvent, transformSessionStartEvent, transformToolCompleteEvent, transformToolStartEvent, } from "./transform-event.js";
11
+ /**
12
+ * Creates a parser for Copilot CLI JSONL events.
13
+ *
14
+ * Maintains state for:
15
+ * - Tool name correlation (toolCallId -> tool name)
16
+ * - Tool call deduplication (tracks emitted tool.call events)
17
+ * - Model tracking (updated from session.model_change)
18
+ * - Usage accumulation (for session.complete stats)
19
+ * - Turn end tracking (for session.complete emission)
20
+ */
21
+ function createCopilotParser() {
22
+ const parseTimestamp = createTimestampParser();
23
+ const toolNames = new Map();
24
+ const emittedToolCalls = new Set();
25
+ let currentModel = "unknown";
26
+ let accumulatedUsage;
27
+ let sessionStartEmitted = false;
28
+ let lastTurnEndTimestamp;
29
+ let sessionCompleteEmitted = false;
30
+ return (line) => {
31
+ const trimmed = line.trim();
32
+ // Drain call - emit session.complete if we haven't already
33
+ // The adapter signals end-of-stream by calling the parser with an empty string
34
+ if (trimmed === "") {
35
+ return emitSessionComplete();
36
+ }
37
+ // Parse and validate JSON
38
+ const event = parseEventLine(trimmed);
39
+ if (!event) {
40
+ return undefined;
41
+ }
42
+ // Dispatch based on event type
43
+ return handleEvent(event);
44
+ };
45
+ function emitSessionComplete() {
46
+ if (!sessionCompleteEmitted && lastTurnEndTimestamp !== undefined) {
47
+ sessionCompleteEmitted = true;
48
+ return {
49
+ type: "session.complete",
50
+ stats: {
51
+ durationMs: accumulatedUsage?.durationMs ?? 0,
52
+ costUsd: accumulatedUsage?.costUsd,
53
+ inputTokens: accumulatedUsage?.inputTokens,
54
+ outputTokens: accumulatedUsage?.outputTokens,
55
+ },
56
+ timestamp: lastTurnEndTimestamp,
57
+ };
58
+ }
59
+ return undefined;
60
+ }
61
+ function handleEvent(event) {
62
+ if (isSessionStartEvent(event)) {
63
+ if (sessionStartEmitted)
64
+ return undefined;
65
+ sessionStartEmitted = true;
66
+ return transformSessionStartEvent(event, currentModel, parseTimestamp);
67
+ }
68
+ if (isSessionModelChangeEvent(event)) {
69
+ currentModel = event.data.newModel;
70
+ return undefined;
71
+ }
72
+ if (isSessionErrorEvent(event)) {
73
+ return transformSessionErrorEvent(event, parseTimestamp);
74
+ }
75
+ if (isAssistantMessageEvent(event)) {
76
+ return transformAssistantMessageEvent(event, toolNames, parseTimestamp);
77
+ }
78
+ if (isAssistantUsageEvent(event)) {
79
+ accumulateUsage(event);
80
+ return undefined;
81
+ }
82
+ if (isAssistantTurnEndEvent(event)) {
83
+ lastTurnEndTimestamp = parseTimestamp(event.timestamp);
84
+ return undefined;
85
+ }
86
+ if (isToolExecutionStartEvent(event)) {
87
+ return transformToolStartEvent(event, toolNames, emittedToolCalls, parseTimestamp);
88
+ }
89
+ if (isToolExecutionCompleteEvent(event)) {
90
+ return transformToolCompleteEvent(event, toolNames, parseTimestamp);
91
+ }
92
+ if (isAbortEvent(event)) {
93
+ return {
94
+ type: "session.error",
95
+ code: "ABORTED",
96
+ message: event.data.reason,
97
+ timestamp: parseTimestamp(event.timestamp),
98
+ };
99
+ }
100
+ return undefined;
101
+ }
102
+ function accumulateUsage(event) {
103
+ if (!accumulatedUsage) {
104
+ accumulatedUsage = {
105
+ inputTokens: 0,
106
+ outputTokens: 0,
107
+ costUsd: 0,
108
+ durationMs: 0,
109
+ };
110
+ }
111
+ if (event.data.inputTokens) {
112
+ accumulatedUsage.inputTokens += event.data.inputTokens;
113
+ }
114
+ if (event.data.outputTokens) {
115
+ accumulatedUsage.outputTokens += event.data.outputTokens;
116
+ }
117
+ if (event.data.cost) {
118
+ accumulatedUsage.costUsd += event.data.cost;
119
+ }
120
+ if (event.data.duration) {
121
+ accumulatedUsage.durationMs += event.data.duration;
122
+ }
123
+ }
124
+ }
125
+ /** Parse a JSONL line into a CopilotEvent */
126
+ function parseEventLine(line) {
127
+ let json;
128
+ try {
129
+ json = JSON.parse(line);
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
134
+ const result = CopilotEvent.safeParse(json);
135
+ return result.success ? result.data : undefined;
136
+ }
137
+ export { createCopilotParser };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copilot CLI session file streamer.
4
+ *
5
+ * This script runs Copilot CLI and streams session events from the JSONL
6
+ * session file to stdout. It's designed to be invoked as a wrapper by axexec.
7
+ *
8
+ * Usage: node stream-session.js <prompt> [copilot-args...]
9
+ */
10
+ export {};
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copilot CLI session file streamer.
4
+ *
5
+ * This script runs Copilot CLI and streams session events from the JSONL
6
+ * session file to stdout. It's designed to be invoked as a wrapper by axexec.
7
+ *
8
+ * Usage: node stream-session.js <prompt> [copilot-args...]
9
+ */
10
+ import { spawn } from "node:child_process";
11
+ import { tailFile } from "./tail-file.js";
12
+ import { getExistingSessionFiles, waitForSessionFile, } from "./watch-session.js";
13
+ /** Main entry point */
14
+ async function main() {
15
+ const cliArguments = process.argv.slice(2);
16
+ const prompt = cliArguments[0];
17
+ if (!prompt) {
18
+ process.stderr.write("Usage: stream-session.js <prompt> [args...]\n");
19
+ process.exit(2);
20
+ }
21
+ // Build Copilot CLI arguments
22
+ const extraArguments = cliArguments.slice(1);
23
+ const copilotArguments = [
24
+ "-p",
25
+ prompt,
26
+ "--allow-all-tools",
27
+ // Enable network access without confirmation prompts.
28
+ // Copilot requires user approval for URL access by default, but other
29
+ // agents (Gemini, OpenCode) allow it by default. For unified behavior,
30
+ // enable it. This matches how --allow-all-tools works for other operations.
31
+ // See: copilot-cli-decompiled/copilot-source/index.js line 418918
32
+ "--allow-all-urls",
33
+ ...extraArguments,
34
+ ];
35
+ // Record existing session files
36
+ const existingFiles = getExistingSessionFiles();
37
+ // Abort controller for cleanup
38
+ const abortController = new AbortController();
39
+ // Track resources for cleanup
40
+ let copilotProcess;
41
+ let sessionWatcherCleanup;
42
+ // Cleanup function
43
+ const cleanup = () => {
44
+ abortController.abort();
45
+ sessionWatcherCleanup?.();
46
+ if (copilotProcess && !copilotProcess.killed) {
47
+ copilotProcess.kill();
48
+ }
49
+ };
50
+ // Handle signals
51
+ process.on("SIGINT", cleanup);
52
+ process.on("SIGTERM", cleanup);
53
+ try {
54
+ // Start Copilot CLI
55
+ const copilotBin = process.env["AXEXEC_COPILOT_PATH"] ?? "copilot";
56
+ const child = spawn(copilotBin, copilotArguments, {
57
+ stdio: ["ignore", "pipe", "pipe"],
58
+ });
59
+ copilotProcess = child;
60
+ // Pipe Copilot's stdout/stderr to our stderr (for debugging)
61
+ // We use stdout for JSONL events only
62
+ child.stdout.pipe(process.stderr);
63
+ child.stderr.pipe(process.stderr);
64
+ // Handle spawn errors
65
+ child.on("error", (error) => {
66
+ process.stderr.write(`Error spawning Copilot CLI: ${error.message}\n`);
67
+ cleanup();
68
+ process.exit(1);
69
+ });
70
+ // Wait for new session file
71
+ const sessionWatcher = waitForSessionFile(existingFiles);
72
+ sessionWatcherCleanup = sessionWatcher.cleanup;
73
+ const sessionFilePath = await sessionWatcher.promise;
74
+ sessionWatcherCleanup = undefined;
75
+ // Start tailing the session file
76
+ const tailPromise = (async () => {
77
+ for await (const line of tailFile(sessionFilePath, abortController.signal)) {
78
+ if (line.trim()) {
79
+ process.stdout.write(line + "\n");
80
+ }
81
+ }
82
+ })();
83
+ // Wait for Copilot to exit
84
+ const exitCode = await new Promise((resolve) => {
85
+ child.on("close", (code) => {
86
+ resolve(code ?? 0);
87
+ });
88
+ });
89
+ // Give a moment for final events to be written
90
+ await new Promise((resolve) => setTimeout(resolve, 200));
91
+ // Stop tailing
92
+ abortController.abort();
93
+ // Wait for tail to finish processing
94
+ try {
95
+ await tailPromise;
96
+ }
97
+ catch {
98
+ // Ignore abort errors
99
+ }
100
+ process.exit(exitCode);
101
+ }
102
+ catch (error) {
103
+ const message = error instanceof Error ? error.message : String(error);
104
+ process.stderr.write(`Error: ${message}\n`);
105
+ // Don't call cleanup() here - finally block handles it
106
+ process.exit(1);
107
+ }
108
+ finally {
109
+ cleanup();
110
+ }
111
+ }
112
+ // Run main and handle errors
113
+ try {
114
+ await main();
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : String(error);
118
+ process.stderr.write(`Fatal error: ${message}\n`);
119
+ process.exit(1);
120
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * File tailing utility for streaming new lines from a file.
3
+ */
4
+ /**
5
+ * Tail a file and yield lines as they're written.
6
+ *
7
+ * Polls the file for new content and yields complete lines as they appear.
8
+ * Buffers partial lines across reads to avoid splitting JSONL records.
9
+ * Continues until the abort signal is triggered.
10
+ */
11
+ export declare function tailFile(filePath: string, signal: AbortSignal): AsyncGenerator<string>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * File tailing utility for streaming new lines from a file.
3
+ */
4
+ import { createReadStream, statSync } from "node:fs";
5
+ /**
6
+ * Tail a file and yield lines as they're written.
7
+ *
8
+ * Polls the file for new content and yields complete lines as they appear.
9
+ * Buffers partial lines across reads to avoid splitting JSONL records.
10
+ * Continues until the abort signal is triggered.
11
+ */
12
+ export async function* tailFile(filePath, signal) {
13
+ let position = 0;
14
+ let buffer = "";
15
+ // Keep reading until aborted
16
+ while (!signal.aborted) {
17
+ try {
18
+ const stats = statSync(filePath);
19
+ const fileSize = stats.size;
20
+ if (fileSize > position) {
21
+ // New data available
22
+ const stream = createReadStream(filePath, {
23
+ start: position,
24
+ end: fileSize - 1,
25
+ });
26
+ for await (const chunk of stream) {
27
+ // Check abort during iteration - signal can change while awaiting
28
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
29
+ if (signal.aborted)
30
+ break;
31
+ buffer += chunk.toString("utf8");
32
+ // Yield complete lines (terminated by newline)
33
+ if (buffer.includes("\n")) {
34
+ const lines = buffer.split("\n");
35
+ // Keep last element as buffer (partial line or empty string)
36
+ buffer = lines.pop() ?? "";
37
+ for (const line of lines) {
38
+ yield line;
39
+ }
40
+ }
41
+ }
42
+ position = fileSize;
43
+ }
44
+ // Wait a bit before checking again
45
+ await new Promise((resolve) => setTimeout(resolve, 50));
46
+ }
47
+ catch {
48
+ // File might be temporarily unavailable, wait and retry
49
+ await new Promise((resolve) => setTimeout(resolve, 100));
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Event transformation functions for Copilot CLI events.
3
+ *
4
+ * Transforms raw Copilot events into normalized AxexecEvent format.
5
+ */
6
+ import type { AxexecEvent } from "../../types/events.js";
7
+ import type { CopilotAssistantMessageEvent, CopilotSessionErrorEvent, CopilotSessionStartEvent, CopilotToolExecutionCompleteEvent, CopilotToolExecutionStartEvent } from "./types.js";
8
+ type TimestampParser = (isoString: string | undefined) => number;
9
+ /** Transforms a session.start event */
10
+ export declare function transformSessionStartEvent(event: CopilotSessionStartEvent, model: string, parseTimestamp: TimestampParser): AxexecEvent;
11
+ /** Transforms a session.error event */
12
+ export declare function transformSessionErrorEvent(event: CopilotSessionErrorEvent, parseTimestamp: TimestampParser): AxexecEvent;
13
+ /** Transforms an assistant.message event */
14
+ export declare function transformAssistantMessageEvent(event: CopilotAssistantMessageEvent, toolNames: Map<string, string>, parseTimestamp: TimestampParser): AxexecEvent | undefined;
15
+ /** Transforms a tool.execution_start event */
16
+ export declare function transformToolStartEvent(event: CopilotToolExecutionStartEvent, toolNames: Map<string, string>, emittedToolCalls: Set<string>, parseTimestamp: TimestampParser): AxexecEvent | undefined;
17
+ /** Transforms a tool.execution_complete event */
18
+ export declare function transformToolCompleteEvent(event: CopilotToolExecutionCompleteEvent, toolNames: Map<string, string>, parseTimestamp: TimestampParser): AxexecEvent;
19
+ export {};
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Event transformation functions for Copilot CLI events.
3
+ *
4
+ * Transforms raw Copilot events into normalized AxexecEvent format.
5
+ */
6
+ /**
7
+ * Infers the provider from a Copilot model name.
8
+ *
9
+ * Copilot supports models from multiple providers:
10
+ * - claude-* → anthropic
11
+ * - gpt-*, o1-*, o3-*, o4-* → openai
12
+ * - gemini-* → google
13
+ *
14
+ * Returns undefined for unrecognized models (field is optional).
15
+ */
16
+ function inferProviderFromModel(model) {
17
+ if (model.startsWith("claude-"))
18
+ return "anthropic";
19
+ if (model.startsWith("gpt-"))
20
+ return "openai";
21
+ if (model.startsWith("o1-") ||
22
+ model.startsWith("o3-") ||
23
+ model.startsWith("o4-"))
24
+ return "openai";
25
+ if (model.startsWith("gemini-"))
26
+ return "google";
27
+ return undefined;
28
+ }
29
+ /** Transforms a session.start event */
30
+ export function transformSessionStartEvent(event, model, parseTimestamp) {
31
+ const resolvedModel = event.data.selectedModel ?? model;
32
+ return {
33
+ type: "session.start",
34
+ sessionId: event.data.sessionId,
35
+ agent: "copilot",
36
+ model: resolvedModel,
37
+ provider: inferProviderFromModel(resolvedModel),
38
+ timestamp: parseTimestamp(event.timestamp),
39
+ };
40
+ }
41
+ /** Transforms a session.error event */
42
+ export function transformSessionErrorEvent(event, parseTimestamp) {
43
+ return {
44
+ type: "session.error",
45
+ code: event.data.errorType,
46
+ message: event.data.message,
47
+ timestamp: parseTimestamp(event.timestamp),
48
+ };
49
+ }
50
+ /** Transforms an assistant.message event */
51
+ export function transformAssistantMessageEvent(event, toolNames, parseTimestamp) {
52
+ const timestamp = parseTimestamp(event.timestamp);
53
+ // If there are tool requests, pre-register the tool names for correlation
54
+ // but DON'T emit tool.call here - we'll emit from tool.execution_start
55
+ for (const request of event.data.toolRequests ?? []) {
56
+ toolNames.set(request.toolCallId, request.name);
57
+ }
58
+ // Emit text content as agent message (only if there's actual content)
59
+ if (event.data.content) {
60
+ return {
61
+ type: "agent.message",
62
+ content: event.data.content,
63
+ timestamp,
64
+ };
65
+ }
66
+ return undefined;
67
+ }
68
+ /** Transforms a tool.execution_start event */
69
+ export function transformToolStartEvent(event, toolNames, emittedToolCalls, parseTimestamp) {
70
+ const callId = event.data.toolCallId;
71
+ // Check if we've already emitted this tool call
72
+ if (emittedToolCalls.has(callId)) {
73
+ return undefined;
74
+ }
75
+ emittedToolCalls.add(callId);
76
+ const tool = event.data.toolName;
77
+ toolNames.set(callId, tool);
78
+ return {
79
+ type: "tool.call",
80
+ callId,
81
+ tool,
82
+ input: event.data.arguments,
83
+ timestamp: parseTimestamp(event.timestamp),
84
+ };
85
+ }
86
+ /** Transforms a tool.execution_complete event */
87
+ export function transformToolCompleteEvent(event, toolNames, parseTimestamp) {
88
+ const callId = event.data.toolCallId;
89
+ const tool = toolNames.get(callId) ?? "unknown";
90
+ // Clean up to prevent memory leaks
91
+ toolNames.delete(callId);
92
+ return {
93
+ type: "tool.result",
94
+ callId,
95
+ tool,
96
+ output: event.data.result?.content ?? event.data.error?.message,
97
+ success: event.data.success,
98
+ timestamp: parseTimestamp(event.timestamp),
99
+ };
100
+ }