@agentex/agent 0.0.23 → 0.0.26
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/CHANGELOG.md +338 -0
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/derived.d.ts +5 -3
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +11 -7
- package/dist/derived.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/acp/index.d.ts +1 -1
- package/dist/providers/acp/index.d.ts.map +1 -1
- package/dist/providers/acp/index.js +5 -97
- package/dist/providers/acp/index.js.map +1 -1
- package/dist/providers/acp/session.d.ts +8 -1
- package/dist/providers/acp/session.d.ts.map +1 -1
- package/dist/providers/acp/session.js +94 -0
- package/dist/providers/acp/session.js.map +1 -1
- package/dist/providers/claude/attach.d.ts +8 -0
- package/dist/providers/claude/attach.d.ts.map +1 -0
- package/dist/providers/claude/attach.js +113 -0
- package/dist/providers/claude/attach.js.map +1 -0
- package/dist/providers/claude/execute.d.ts.map +1 -1
- package/dist/providers/claude/execute.js +17 -2
- package/dist/providers/claude/execute.js.map +1 -1
- package/dist/providers/claude/goal-capability.d.ts +15 -0
- package/dist/providers/claude/goal-capability.d.ts.map +1 -0
- package/dist/providers/claude/goal-capability.js +20 -0
- package/dist/providers/claude/goal-capability.js.map +1 -0
- package/dist/providers/claude/index.d.ts.map +1 -1
- package/dist/providers/claude/index.js +8 -4
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/providers/claude/session.d.ts +11 -9
- package/dist/providers/claude/session.d.ts.map +1 -1
- package/dist/providers/claude/session.js +36 -14
- package/dist/providers/claude/session.js.map +1 -1
- package/dist/providers/codex/attach.d.ts +9 -0
- package/dist/providers/codex/attach.d.ts.map +1 -0
- package/dist/providers/codex/attach.js +93 -0
- package/dist/providers/codex/attach.js.map +1 -0
- package/dist/providers/codex/execute.d.ts.map +1 -1
- package/dist/providers/codex/execute.js +17 -3
- package/dist/providers/codex/execute.js.map +1 -1
- package/dist/providers/codex/goal-capability.d.ts +13 -0
- package/dist/providers/codex/goal-capability.d.ts.map +1 -0
- package/dist/providers/codex/goal-capability.js +18 -0
- package/dist/providers/codex/goal-capability.js.map +1 -0
- package/dist/providers/codex/index.d.ts +1 -0
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/index.js +9 -6
- package/dist/providers/codex/index.js.map +1 -1
- package/dist/providers/codex/session.d.ts +11 -7
- package/dist/providers/codex/session.d.ts.map +1 -1
- package/dist/providers/codex/session.js +37 -12
- package/dist/providers/codex/session.js.map +1 -1
- package/dist/providers/codex/transcript-normalize.d.ts +28 -0
- package/dist/providers/codex/transcript-normalize.d.ts.map +1 -0
- package/dist/providers/codex/transcript-normalize.js +191 -0
- package/dist/providers/codex/transcript-normalize.js.map +1 -0
- package/dist/providers/cursor/index.d.ts.map +1 -1
- package/dist/providers/cursor/index.js +2 -2
- package/dist/providers/cursor/index.js.map +1 -1
- package/dist/providers/openclaw/index.d.ts.map +1 -1
- package/dist/providers/openclaw/index.js +2 -2
- package/dist/providers/openclaw/index.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +3 -5
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/pi/index.d.ts.map +1 -1
- package/dist/providers/pi/index.js +3 -5
- package/dist/providers/pi/index.js.map +1 -1
- package/dist/providers/process/index.d.ts.map +1 -1
- package/dist/providers/process/index.js +2 -2
- package/dist/providers/process/index.js.map +1 -1
- package/dist/registry.d.ts +0 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +0 -4
- package/dist/registry.js.map +1 -1
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/record.d.ts +43 -0
- package/dist/sessions/record.d.ts.map +1 -0
- package/dist/sessions/record.js +85 -0
- package/dist/sessions/record.js.map +1 -0
- package/dist/types.d.ts +176 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/endpoint.d.ts +38 -0
- package/dist/utils/endpoint.d.ts.map +1 -0
- package/dist/utils/endpoint.js +151 -0
- package/dist/utils/endpoint.js.map +1 -0
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/env.js +5 -1
- package/dist/utils/env.js.map +1 -1
- package/dist/utils/uuid.d.ts +7 -1
- package/dist/utils/uuid.d.ts.map +1 -1
- package/dist/utils/uuid.js +21 -1
- package/dist/utils/uuid.js.map +1 -1
- package/package.json +64 -7
- package/src/derived.ts +311 -0
- package/src/goals/controller.ts +442 -0
- package/src/goals/index.ts +21 -0
- package/src/goals/normalize.ts +173 -0
- package/src/goals/sentinel.ts +90 -0
- package/src/index.ts +270 -0
- package/src/providers/_shared/http-agent.ts +304 -0
- package/src/providers/acp/index.ts +103 -0
- package/src/providers/acp/parse.ts +131 -0
- package/src/providers/acp/session.ts +744 -0
- package/src/providers/claude/attach.ts +147 -0
- package/src/providers/claude/codec.ts +43 -0
- package/src/providers/claude/execute.ts +300 -0
- package/src/providers/claude/goal-capability.ts +21 -0
- package/src/providers/claude/index.ts +72 -0
- package/src/providers/claude/mcp.ts +82 -0
- package/src/providers/claude/parse.ts +824 -0
- package/src/providers/claude/session.ts +1192 -0
- package/src/providers/claude/transcript.ts +555 -0
- package/src/providers/codex/attach.ts +123 -0
- package/src/providers/codex/codec.ts +50 -0
- package/src/providers/codex/execute.ts +337 -0
- package/src/providers/codex/goal-capability.ts +19 -0
- package/src/providers/codex/index.ts +57 -0
- package/src/providers/codex/modes.ts +159 -0
- package/src/providers/codex/parse.ts +691 -0
- package/src/providers/codex/plan-mode.ts +49 -0
- package/src/providers/codex/session.ts +1287 -0
- package/src/providers/codex/transcript-normalize.ts +197 -0
- package/src/providers/codex/transcript.ts +487 -0
- package/src/providers/codex/usage-scanner.ts +178 -0
- package/src/providers/copilot/index.ts +19 -0
- package/src/providers/cursor/codec.ts +44 -0
- package/src/providers/cursor/execute.ts +271 -0
- package/src/providers/cursor/index.ts +25 -0
- package/src/providers/cursor/parse.ts +288 -0
- package/src/providers/gemini/index.ts +21 -0
- package/src/providers/openclaw/codec.ts +40 -0
- package/src/providers/openclaw/execute.ts +19 -0
- package/src/providers/openclaw/index.ts +29 -0
- package/src/providers/opencode/codec.ts +50 -0
- package/src/providers/opencode/event-parse.ts +141 -0
- package/src/providers/opencode/execute.ts +251 -0
- package/src/providers/opencode/http-session.ts +427 -0
- package/src/providers/opencode/index.ts +30 -0
- package/src/providers/opencode/parse.ts +203 -0
- package/src/providers/opencode/server.ts +0 -0
- package/src/providers/pi/codec.ts +44 -0
- package/src/providers/pi/execute.ts +297 -0
- package/src/providers/pi/index.ts +30 -0
- package/src/providers/pi/parse.ts +231 -0
- package/src/providers/pi/session.ts +381 -0
- package/src/providers/process/execute.ts +148 -0
- package/src/providers/process/index.ts +52 -0
- package/src/registry.ts +40 -0
- package/src/sessions/index.ts +8 -0
- package/src/sessions/record.ts +108 -0
- package/src/types.ts +1638 -0
- package/src/utils/ask-user-question.ts +57 -0
- package/src/utils/auth.ts +661 -0
- package/src/utils/binary.ts +179 -0
- package/src/utils/endpoint.ts +172 -0
- package/src/utils/env.ts +63 -0
- package/src/utils/execute-all.ts +68 -0
- package/src/utils/exit-plan-mode.ts +40 -0
- package/src/utils/instructions.ts +427 -0
- package/src/utils/process.ts +223 -0
- package/src/utils/runtime-config.ts +100 -0
- package/src/utils/runtime-homes.ts +49 -0
- package/src/utils/skill-commands.ts +493 -0
- package/src/utils/skills.ts +500 -0
- package/src/utils/template.ts +16 -0
- package/src/utils/tool-names.ts +51 -0
- package/src/utils/uuid.ts +21 -0
- package/src/utils/workspace.ts +156 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as readline from "node:readline";
|
|
5
|
+
import { createReadStream } from "node:fs";
|
|
6
|
+
import type { TokenUsage } from "../../types.js";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Log path resolution
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
function getCodexHome(): string {
|
|
13
|
+
return process.env["CODEX_HOME"] || path.join(os.homedir(), ".codex");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find session log files that may contain usage data for a given time range.
|
|
18
|
+
* Codex stores logs at: `CODEX_HOME/sessions/YYYY/MM/DD/*.jsonl`
|
|
19
|
+
*/
|
|
20
|
+
async function findLogFiles(startedAfter: Date): Promise<string[]> {
|
|
21
|
+
const sessionsDir = path.join(getCodexHome(), "sessions");
|
|
22
|
+
const files: string[] = [];
|
|
23
|
+
|
|
24
|
+
// Walk date-partitioned directories starting from the startedAfter date
|
|
25
|
+
const startDate = new Date(startedAfter);
|
|
26
|
+
const now = new Date();
|
|
27
|
+
|
|
28
|
+
for (let d = new Date(startDate); d <= now; d.setDate(d.getDate() + 1)) {
|
|
29
|
+
const year = String(d.getFullYear());
|
|
30
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
31
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
32
|
+
const dayDir = path.join(sessionsDir, year, month, day);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const entries = await fs.readdir(dayDir);
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (entry.endsWith(".jsonl")) {
|
|
38
|
+
files.push(path.join(dayDir, entry));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Directory doesn't exist — skip
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// JSONL scanning
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
interface ScanOptions {
|
|
54
|
+
/** Only include events after this timestamp */
|
|
55
|
+
startedAfter: Date;
|
|
56
|
+
/** Optional thread ID to filter by */
|
|
57
|
+
threadId?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Scan Codex session log files for token usage events and aggregate them.
|
|
62
|
+
*
|
|
63
|
+
* Codex logs `token_count` events in session JSONL files with fields like
|
|
64
|
+
* `InputTokens`, `OutputTokens`, `CachedInputTokens`, `CacheReadInputTokens`.
|
|
65
|
+
*
|
|
66
|
+
* Returns aggregated usage keyed by model name, or null if no usage found.
|
|
67
|
+
*/
|
|
68
|
+
export async function scanCodexSessionUsage(
|
|
69
|
+
options: ScanOptions,
|
|
70
|
+
): Promise<Record<string, TokenUsage> | undefined> {
|
|
71
|
+
const logFiles = await findLogFiles(options.startedAfter);
|
|
72
|
+
if (logFiles.length === 0) return undefined;
|
|
73
|
+
|
|
74
|
+
const usageByModel = new Map<string, {
|
|
75
|
+
inputTokens: number;
|
|
76
|
+
outputTokens: number;
|
|
77
|
+
cachedInputTokens: number;
|
|
78
|
+
cacheCreationInputTokens: number;
|
|
79
|
+
}>();
|
|
80
|
+
|
|
81
|
+
for (const file of logFiles) {
|
|
82
|
+
await scanFile(file, options, usageByModel);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (usageByModel.size === 0) return undefined;
|
|
86
|
+
|
|
87
|
+
const result: Record<string, TokenUsage> = {};
|
|
88
|
+
for (const [model, usage] of usageByModel) {
|
|
89
|
+
const entry: TokenUsage = {
|
|
90
|
+
inputTokens: usage.inputTokens,
|
|
91
|
+
outputTokens: usage.outputTokens,
|
|
92
|
+
};
|
|
93
|
+
if (usage.cachedInputTokens > 0) entry.cachedInputTokens = usage.cachedInputTokens;
|
|
94
|
+
if (usage.cacheCreationInputTokens > 0) entry.cacheCreationInputTokens = usage.cacheCreationInputTokens;
|
|
95
|
+
result[model] = entry;
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function scanFile(
|
|
101
|
+
filePath: string,
|
|
102
|
+
options: ScanOptions,
|
|
103
|
+
usageByModel: Map<string, { inputTokens: number; outputTokens: number; cachedInputTokens: number; cacheCreationInputTokens: number }>,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
let stream: ReturnType<typeof createReadStream>;
|
|
106
|
+
try {
|
|
107
|
+
stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
108
|
+
} catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
for await (const line of rl) {
|
|
116
|
+
const trimmed = line.trim();
|
|
117
|
+
if (!trimmed) continue;
|
|
118
|
+
|
|
119
|
+
let parsed: Record<string, unknown>;
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(trimmed);
|
|
122
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
|
|
123
|
+
} catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const type = typeof parsed["type"] === "string" ? parsed["type"] : "";
|
|
128
|
+
if (type !== "token_count") continue;
|
|
129
|
+
|
|
130
|
+
// Check timestamp
|
|
131
|
+
const ts = typeof parsed["timestamp"] === "string" ? parsed["timestamp"]
|
|
132
|
+
: typeof parsed["created_at"] === "string" ? parsed["created_at"]
|
|
133
|
+
: null;
|
|
134
|
+
if (ts) {
|
|
135
|
+
const eventTime = new Date(ts);
|
|
136
|
+
if (eventTime < options.startedAfter) continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check thread ID filter
|
|
140
|
+
if (options.threadId) {
|
|
141
|
+
const eventThreadId = typeof parsed["thread_id"] === "string" ? parsed["thread_id"]
|
|
142
|
+
: typeof parsed["threadId"] === "string" ? parsed["threadId"]
|
|
143
|
+
: null;
|
|
144
|
+
if (eventThreadId && eventThreadId !== options.threadId) continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Extract usage
|
|
148
|
+
const model = typeof parsed["model"] === "string" ? parsed["model"] : "unknown";
|
|
149
|
+
const inputTokens = typeof parsed["InputTokens"] === "number" ? parsed["InputTokens"]
|
|
150
|
+
: typeof parsed["input_tokens"] === "number" ? parsed["input_tokens"]
|
|
151
|
+
: 0;
|
|
152
|
+
const outputTokens = typeof parsed["OutputTokens"] === "number" ? parsed["OutputTokens"]
|
|
153
|
+
: typeof parsed["output_tokens"] === "number" ? parsed["output_tokens"]
|
|
154
|
+
: 0;
|
|
155
|
+
const cachedInputTokens = typeof parsed["CacheReadInputTokens"] === "number" ? parsed["CacheReadInputTokens"]
|
|
156
|
+
: typeof parsed["cached_input_tokens"] === "number" ? parsed["cached_input_tokens"]
|
|
157
|
+
: 0;
|
|
158
|
+
const cacheCreationInputTokens = typeof parsed["CachedInputTokens"] === "number" ? parsed["CachedInputTokens"]
|
|
159
|
+
: typeof parsed["cache_creation_input_tokens"] === "number" ? parsed["cache_creation_input_tokens"]
|
|
160
|
+
: 0;
|
|
161
|
+
|
|
162
|
+
const existing = usageByModel.get(model) ?? {
|
|
163
|
+
inputTokens: 0,
|
|
164
|
+
outputTokens: 0,
|
|
165
|
+
cachedInputTokens: 0,
|
|
166
|
+
cacheCreationInputTokens: 0,
|
|
167
|
+
};
|
|
168
|
+
existing.inputTokens += inputTokens;
|
|
169
|
+
existing.outputTokens += outputTokens;
|
|
170
|
+
existing.cachedInputTokens += cachedInputTokens;
|
|
171
|
+
existing.cacheCreationInputTokens += cacheCreationInputTokens;
|
|
172
|
+
usageByModel.set(model, existing);
|
|
173
|
+
}
|
|
174
|
+
} finally {
|
|
175
|
+
rl.close();
|
|
176
|
+
stream.destroy();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { acpProvider } from "../acp/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GitHub Copilot CLI via the Agent Client Protocol (`copilot --acp`). Copilot
|
|
5
|
+
* handles its own GitHub auth.
|
|
6
|
+
*
|
|
7
|
+
* Adding Copilot is the whole point of the ACP tier — it's a handful of lines
|
|
8
|
+
* because the ACP base does the work (sessions, streaming, tool-call
|
|
9
|
+
* correlation, permission bridging, mode discovery).
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Copilot also exposes an "allow-all" approval mode through an ACP
|
|
12
|
+
* `setSessionConfigOption` (distinct from `setSessionMode`). Fully surfacing it
|
|
13
|
+
* would need a config-option transformer hook (future work); the standard
|
|
14
|
+
* mode + permission flow works today.
|
|
15
|
+
*/
|
|
16
|
+
export const copilotProvider = acpProvider({
|
|
17
|
+
id: "copilot",
|
|
18
|
+
command: ["copilot", "--acp"],
|
|
19
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SessionCodec } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
function readNonEmptyString(value: unknown): string | null {
|
|
4
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function parseAsObject(raw: unknown): Record<string, unknown> | null {
|
|
8
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
9
|
+
return raw as Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const cursorSessionCodec: SessionCodec = {
|
|
13
|
+
deserialize(raw: unknown): Record<string, unknown> | null {
|
|
14
|
+
const obj = parseAsObject(raw);
|
|
15
|
+
if (!obj) return null;
|
|
16
|
+
const sessionId =
|
|
17
|
+
readNonEmptyString(obj["sessionId"]) ??
|
|
18
|
+
readNonEmptyString(obj["session_id"]);
|
|
19
|
+
if (!sessionId) return null;
|
|
20
|
+
const cwd = readNonEmptyString(obj["cwd"]);
|
|
21
|
+
return {
|
|
22
|
+
sessionId,
|
|
23
|
+
...(cwd ? { cwd } : {}),
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
serialize(params: Record<string, unknown> | null): Record<string, unknown> | null {
|
|
28
|
+
if (!params) return null;
|
|
29
|
+
const sessionId =
|
|
30
|
+
readNonEmptyString(params["sessionId"]) ??
|
|
31
|
+
readNonEmptyString(params["session_id"]);
|
|
32
|
+
if (!sessionId) return null;
|
|
33
|
+
const cwd = readNonEmptyString(params["cwd"]);
|
|
34
|
+
return {
|
|
35
|
+
sessionId,
|
|
36
|
+
...(cwd ? { cwd } : {}),
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
getDisplayId(params: Record<string, unknown> | null): string | null {
|
|
41
|
+
if (!params) return null;
|
|
42
|
+
return readNonEmptyString(params["sessionId"]) ?? readNonEmptyString(params["session_id"]);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { ExecutionContext, ExecutionResult } from "../../types.js";
|
|
3
|
+
import { findBinary } from "../../utils/binary.js";
|
|
4
|
+
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
5
|
+
import { runChildProcess, deriveErrorCode } from "../../utils/process.js";
|
|
6
|
+
import { detectAuth } from "../../utils/auth.js";
|
|
7
|
+
import { injectHomeSkills } from "../../utils/skills.js";
|
|
8
|
+
import { resolveInstructions } from "../../utils/instructions.js";
|
|
9
|
+
import { prepareWorkspace } from "../../utils/workspace.js";
|
|
10
|
+
import type { PreparedWorkspace } from "../../utils/workspace.js";
|
|
11
|
+
import { uuidv7 } from "../../utils/uuid.js";
|
|
12
|
+
import {
|
|
13
|
+
parseCursorJsonl,
|
|
14
|
+
parseCursorStreamLine,
|
|
15
|
+
normalizeCursorStreamLine,
|
|
16
|
+
isCursorUnknownSessionError,
|
|
17
|
+
isCursorAuthRequired,
|
|
18
|
+
} from "./parse.js";
|
|
19
|
+
|
|
20
|
+
export async function executeCursorProvider(ctx: ExecutionContext): Promise<ExecutionResult> {
|
|
21
|
+
const runId = ctx.runId ?? uuidv7();
|
|
22
|
+
let cwd = ctx.cwd ?? process.cwd();
|
|
23
|
+
const model = ctx.model ?? ctx.config?.model ?? "";
|
|
24
|
+
const config = ctx.config ?? {};
|
|
25
|
+
const startedAt = new Date().toISOString();
|
|
26
|
+
|
|
27
|
+
// 1. Resolve binary
|
|
28
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "binary" });
|
|
29
|
+
let resolvedBinary;
|
|
30
|
+
try {
|
|
31
|
+
resolvedBinary = await findBinary("agent", config.command);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const errorMessage = err instanceof Error ? err.message : "Binary not found";
|
|
34
|
+
ctx.onLifecycle?.({ phase: "error", message: errorMessage });
|
|
35
|
+
return {
|
|
36
|
+
runId,
|
|
37
|
+
exitCode: null,
|
|
38
|
+
signal: null,
|
|
39
|
+
status: "failed" as const,
|
|
40
|
+
startedAt,
|
|
41
|
+
completedAt: new Date().toISOString(),
|
|
42
|
+
durationMs: Date.now() - new Date(startedAt).getTime(),
|
|
43
|
+
errorMessage,
|
|
44
|
+
errorCode: "binary_not_found",
|
|
45
|
+
costUsd: null,
|
|
46
|
+
model: null,
|
|
47
|
+
summary: null,
|
|
48
|
+
sessionParams: null,
|
|
49
|
+
sessionDisplayId: null,
|
|
50
|
+
clearSession: false,
|
|
51
|
+
billingType: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Workspace isolation
|
|
56
|
+
let workspace: PreparedWorkspace | undefined;
|
|
57
|
+
if (config.workspace) {
|
|
58
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "workspace" });
|
|
59
|
+
workspace = await prepareWorkspace(cwd, config.workspace);
|
|
60
|
+
cwd = workspace.cwd;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. Build env & detect auth/billing
|
|
64
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "auth" });
|
|
65
|
+
const env = buildEnv(ctx.env);
|
|
66
|
+
ensurePathInEnv(env);
|
|
67
|
+
const billingType = detectAuth("cursor", env).billingType;
|
|
68
|
+
|
|
69
|
+
// 3. Resolve instructions
|
|
70
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "instructions" });
|
|
71
|
+
const instructions = await resolveInstructions(config.instructionsFile);
|
|
72
|
+
const fullPrompt = instructions ? `${instructions}\n\n${ctx.prompt}` : ctx.prompt;
|
|
73
|
+
|
|
74
|
+
// 4. Inject skills into ~/.cursor/skills/
|
|
75
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "skills" });
|
|
76
|
+
if (config.skillDirs && config.skillDirs.length > 0) {
|
|
77
|
+
try {
|
|
78
|
+
await injectHomeSkills(config.skillDirs, "cursor");
|
|
79
|
+
} catch {
|
|
80
|
+
// Non-fatal
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 4. Determine session resume
|
|
85
|
+
const sessionParams = ctx.sessionParams ?? null;
|
|
86
|
+
const sessionId = (() => {
|
|
87
|
+
if (!sessionParams) return null;
|
|
88
|
+
const id =
|
|
89
|
+
(sessionParams["sessionId"] as string | undefined) ??
|
|
90
|
+
(sessionParams["session_id"] as string | undefined);
|
|
91
|
+
if (!id || typeof id !== "string") return null;
|
|
92
|
+
const sessionCwd = sessionParams["cwd"] as string | undefined;
|
|
93
|
+
if (sessionCwd && path.resolve(sessionCwd) !== path.resolve(cwd)) return null;
|
|
94
|
+
return id;
|
|
95
|
+
})();
|
|
96
|
+
|
|
97
|
+
// 5. Build args
|
|
98
|
+
const buildArgs = (resumeSessionId: string | null): string[] => {
|
|
99
|
+
const args = [...resolvedBinary.prefixArgs, "-p", "--output-format", "stream-json", "--workspace", cwd];
|
|
100
|
+
if (resumeSessionId) args.push("--resume", resumeSessionId);
|
|
101
|
+
if (model) args.push("--model", model);
|
|
102
|
+
if (config.mode) args.push("--mode", config.mode);
|
|
103
|
+
if (config.skipPermissions) args.push("--yolo");
|
|
104
|
+
if (config.extraArgs) args.push(...config.extraArgs);
|
|
105
|
+
return args;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 6. Run attempt
|
|
109
|
+
const runAttempt = async (resumeSessionId: string | null) => {
|
|
110
|
+
const args = buildArgs(resumeSessionId);
|
|
111
|
+
let stdoutLineBuffer = "";
|
|
112
|
+
|
|
113
|
+
ctx.onLifecycle?.({ phase: "spawning" });
|
|
114
|
+
const proc = await runChildProcess({
|
|
115
|
+
runId,
|
|
116
|
+
command: resolvedBinary.bin,
|
|
117
|
+
args,
|
|
118
|
+
cwd,
|
|
119
|
+
env,
|
|
120
|
+
stdin: fullPrompt,
|
|
121
|
+
timeoutSec: config.timeoutSec,
|
|
122
|
+
graceSec: config.graceSec,
|
|
123
|
+
onStart: (pid) => {
|
|
124
|
+
ctx.onLifecycle?.({ phase: "running", pid });
|
|
125
|
+
ctx.onStart?.(pid);
|
|
126
|
+
},
|
|
127
|
+
signal: ctx.signal,
|
|
128
|
+
onOutput: async (stream, chunk) => {
|
|
129
|
+
if (stream !== "stdout") {
|
|
130
|
+
if (ctx.onOutput) {
|
|
131
|
+
try { await ctx.onOutput(stream, chunk); } catch { /* swallow */ }
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Buffer stdout and normalize cursor stream lines
|
|
137
|
+
stdoutLineBuffer += chunk;
|
|
138
|
+
const lines = stdoutLineBuffer.split("\n");
|
|
139
|
+
stdoutLineBuffer = lines.pop() ?? "";
|
|
140
|
+
|
|
141
|
+
for (const rawLine of lines) {
|
|
142
|
+
const normalized = normalizeCursorStreamLine(rawLine);
|
|
143
|
+
if (!normalized.line) continue;
|
|
144
|
+
|
|
145
|
+
if (ctx.onOutput) {
|
|
146
|
+
try { await ctx.onOutput(normalized.stream ?? "stdout", `${normalized.line}\n`); } catch { /* swallow */ }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (ctx.onEvent) {
|
|
150
|
+
const event = parseCursorStreamLine(rawLine);
|
|
151
|
+
if (event) {
|
|
152
|
+
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Flush remaining buffer
|
|
160
|
+
if (stdoutLineBuffer.trim()) {
|
|
161
|
+
const normalized = normalizeCursorStreamLine(stdoutLineBuffer);
|
|
162
|
+
if (normalized.line && ctx.onOutput) {
|
|
163
|
+
try { await ctx.onOutput(normalized.stream ?? "stdout", `${normalized.line}\n`); } catch { /* swallow */ }
|
|
164
|
+
}
|
|
165
|
+
if (ctx.onEvent) {
|
|
166
|
+
const event = parseCursorStreamLine(stdoutLineBuffer);
|
|
167
|
+
if (event) {
|
|
168
|
+
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return proc;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// 7. Initial attempt
|
|
177
|
+
let proc = await runAttempt(sessionId);
|
|
178
|
+
let clearSession = false;
|
|
179
|
+
|
|
180
|
+
// 8. Check for unknown session — retry once
|
|
181
|
+
if (
|
|
182
|
+
sessionId &&
|
|
183
|
+
!proc.timedOut &&
|
|
184
|
+
(proc.exitCode ?? 0) !== 0 &&
|
|
185
|
+
isCursorUnknownSessionError(proc.stdout, proc.stderr)
|
|
186
|
+
) {
|
|
187
|
+
proc = await runAttempt(null);
|
|
188
|
+
clearSession = true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 9. Parse result
|
|
192
|
+
const parsed = parseCursorJsonl(proc.stdout);
|
|
193
|
+
const processErrorCode = deriveErrorCode(proc);
|
|
194
|
+
|
|
195
|
+
let errorCode = processErrorCode;
|
|
196
|
+
if (!errorCode && isCursorAuthRequired(proc.stdout, proc.stderr)) {
|
|
197
|
+
errorCode = "auth_required";
|
|
198
|
+
if (ctx.onEvent) {
|
|
199
|
+
try {
|
|
200
|
+
await ctx.onEvent({
|
|
201
|
+
type: "auth_required",
|
|
202
|
+
httpStatus: null,
|
|
203
|
+
reason: "missing",
|
|
204
|
+
loginCommand: "cursor-agent login",
|
|
205
|
+
message: parsed.errorMessage,
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
providerType: "cursor",
|
|
208
|
+
sessionId: parsed.sessionId,
|
|
209
|
+
messageId: null,
|
|
210
|
+
eventId: null,
|
|
211
|
+
turnId: null,
|
|
212
|
+
parentToolCallId: null,
|
|
213
|
+
raw: {},
|
|
214
|
+
});
|
|
215
|
+
} catch { /* swallow */ }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const errorMessage = (() => {
|
|
220
|
+
if (proc.timedOut) return `Timed out after ${config.timeoutSec ?? 0}s`;
|
|
221
|
+
if (errorCode === "auth_required") return "Cursor requires authentication.";
|
|
222
|
+
if (parsed.errorMessage) return parsed.errorMessage;
|
|
223
|
+
if ((proc.exitCode ?? 0) !== 0 && !parsed.summary) {
|
|
224
|
+
const stderrLine = proc.stderr.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
225
|
+
return stderrLine ?? `Cursor exited with code ${proc.exitCode ?? -1}`;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
})();
|
|
229
|
+
|
|
230
|
+
const resolvedSessionId = parsed.sessionId;
|
|
231
|
+
const resultSessionParams = resolvedSessionId ? { sessionId: resolvedSessionId, cwd } : null;
|
|
232
|
+
|
|
233
|
+
const completedAt = new Date().toISOString();
|
|
234
|
+
const resolvedModel = parsed.model ?? model ?? null;
|
|
235
|
+
const status = proc.aborted ? "aborted" as const
|
|
236
|
+
: proc.timedOut ? "timeout" as const
|
|
237
|
+
: (errorCode || errorMessage) ? "failed" as const
|
|
238
|
+
: "completed" as const;
|
|
239
|
+
|
|
240
|
+
if (status === "completed") {
|
|
241
|
+
ctx.onLifecycle?.({ phase: "completed" });
|
|
242
|
+
} else if (status === "aborted") {
|
|
243
|
+
ctx.onLifecycle?.({ phase: "cancelled" });
|
|
244
|
+
} else {
|
|
245
|
+
ctx.onLifecycle?.({ phase: "error", message: errorMessage ?? "Unknown error" });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
runId,
|
|
250
|
+
exitCode: proc.exitCode,
|
|
251
|
+
signal: proc.signal ?? null,
|
|
252
|
+
status,
|
|
253
|
+
startedAt,
|
|
254
|
+
completedAt,
|
|
255
|
+
durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
|
|
256
|
+
errorMessage,
|
|
257
|
+
errorCode,
|
|
258
|
+
usage: parsed.usage && resolvedModel
|
|
259
|
+
? { [resolvedModel]: { inputTokens: parsed.usage.inputTokens, outputTokens: parsed.usage.outputTokens, cachedInputTokens: parsed.usage.cachedInputTokens } }
|
|
260
|
+
: undefined,
|
|
261
|
+
costUsd: parsed.costUsd,
|
|
262
|
+
model: resolvedModel,
|
|
263
|
+
summary: parsed.summary,
|
|
264
|
+
sessionParams: resultSessionParams,
|
|
265
|
+
sessionDisplayId: resolvedSessionId,
|
|
266
|
+
clearSession,
|
|
267
|
+
billingType,
|
|
268
|
+
raw: null,
|
|
269
|
+
workspace,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ProviderModule } from "../../types.js";
|
|
2
|
+
import { cursorSessionCodec } from "./codec.js";
|
|
3
|
+
import { resolveAuthForProvider } from "../../utils/auth.js";
|
|
4
|
+
|
|
5
|
+
export const cursorProvider: ProviderModule = {
|
|
6
|
+
type: "cursor",
|
|
7
|
+
capabilities: {
|
|
8
|
+
sessions: false,
|
|
9
|
+
modelDiscovery: false,
|
|
10
|
+
quotaProbing: false,
|
|
11
|
+
mcp: false,
|
|
12
|
+
skills: true,
|
|
13
|
+
instructions: true,
|
|
14
|
+
workspace: true,
|
|
15
|
+
planMode: false,
|
|
16
|
+
concurrentSend: false,
|
|
17
|
+
cancelQueuedMessage: false,
|
|
18
|
+
stopTask: false,
|
|
19
|
+
modes: false,
|
|
20
|
+
},
|
|
21
|
+
// execute.ts loads lazily on first use.
|
|
22
|
+
execute: async (ctx) => (await import("./execute.js")).executeCursorProvider(ctx),
|
|
23
|
+
resolveAuth: (ctx) => resolveAuthForProvider("cursor", ctx),
|
|
24
|
+
sessionCodec: cursorSessionCodec,
|
|
25
|
+
};
|