@agentex/agent 0.0.24 → 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 +52 -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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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/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 +29 -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/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 +24 -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 +119 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.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,288 @@
|
|
|
1
|
+
import type { BaseStreamEventFields, StreamEvent } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
const PROVIDER_TYPE = "cursor";
|
|
4
|
+
|
|
5
|
+
function stubBase(event: Record<string, unknown>, sessionId: string | null = null): BaseStreamEventFields {
|
|
6
|
+
return {
|
|
7
|
+
timestamp: new Date().toISOString(),
|
|
8
|
+
providerType: PROVIDER_TYPE,
|
|
9
|
+
sessionId,
|
|
10
|
+
messageId: null,
|
|
11
|
+
eventId: null,
|
|
12
|
+
turnId: null,
|
|
13
|
+
parentToolCallId: null,
|
|
14
|
+
raw: event,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CursorParsedResult {
|
|
19
|
+
sessionId: string | null;
|
|
20
|
+
model: string | null;
|
|
21
|
+
usage: { inputTokens: number; outputTokens: number; cachedInputTokens: number } | null;
|
|
22
|
+
costUsd: number | null;
|
|
23
|
+
summary: string | null;
|
|
24
|
+
isError: boolean;
|
|
25
|
+
errorMessage: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseJson(line: string): Record<string, unknown> | null {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(line);
|
|
31
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
32
|
+
return parsed as Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Skip malformed lines
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function asString(value: unknown, fallback: string): string {
|
|
41
|
+
return typeof value === "string" ? value : fallback;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function asNumber(value: unknown, fallback: number): number {
|
|
45
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseObject(value: unknown): Record<string, unknown> {
|
|
49
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
50
|
+
return value as Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readSessionId(event: Record<string, unknown>): string | null {
|
|
56
|
+
return (
|
|
57
|
+
asString(event["session_id"], "").trim() ||
|
|
58
|
+
asString(event["sessionId"], "").trim() ||
|
|
59
|
+
asString(event["sessionID"], "").trim() ||
|
|
60
|
+
null
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function collectAssistantText(message: unknown): string[] {
|
|
65
|
+
if (typeof message === "string") {
|
|
66
|
+
const trimmed = message.trim();
|
|
67
|
+
return trimmed ? [trimmed] : [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rec = parseObject(message);
|
|
71
|
+
const direct = asString(rec["text"], "").trim();
|
|
72
|
+
const lines: string[] = direct ? [direct] : [];
|
|
73
|
+
const content = Array.isArray(rec["content"]) ? rec["content"] : [];
|
|
74
|
+
|
|
75
|
+
for (const partRaw of content) {
|
|
76
|
+
const part = parseObject(partRaw);
|
|
77
|
+
const type = asString(part["type"], "").trim();
|
|
78
|
+
if (type === "output_text" || type === "text") {
|
|
79
|
+
const text = asString(part["text"], "").trim();
|
|
80
|
+
if (text) lines.push(text);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Normalize Cursor stream lines which may have a "stdout: {...}" or "stderr: {...}" prefix.
|
|
89
|
+
*/
|
|
90
|
+
export function normalizeCursorStreamLine(rawLine: string): {
|
|
91
|
+
stream: "stdout" | "stderr" | null;
|
|
92
|
+
line: string;
|
|
93
|
+
} {
|
|
94
|
+
const trimmed = rawLine.trim();
|
|
95
|
+
if (!trimmed) return { stream: null, line: "" };
|
|
96
|
+
|
|
97
|
+
const prefixed = trimmed.match(/^(stdout|stderr)\s*[:=]?\s*([\[{].*)$/i);
|
|
98
|
+
if (!prefixed) {
|
|
99
|
+
return { stream: null, line: trimmed };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const stream = prefixed[1]?.toLowerCase() === "stderr" ? "stderr" : "stdout";
|
|
103
|
+
const line = (prefixed[2] ?? "").trim();
|
|
104
|
+
return { stream, line };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseCursorJsonl(stdout: string): CursorParsedResult {
|
|
108
|
+
let sessionId: string | null = null;
|
|
109
|
+
let model: string | null = null;
|
|
110
|
+
const messages: string[] = [];
|
|
111
|
+
let errorMessage: string | null = null;
|
|
112
|
+
let totalCostUsd = 0;
|
|
113
|
+
const usage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
114
|
+
let hasUsage = false;
|
|
115
|
+
|
|
116
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
117
|
+
const line = normalizeCursorStreamLine(rawLine).line;
|
|
118
|
+
if (!line) continue;
|
|
119
|
+
|
|
120
|
+
const event = parseJson(line);
|
|
121
|
+
if (!event) continue;
|
|
122
|
+
|
|
123
|
+
const foundSession = readSessionId(event);
|
|
124
|
+
if (foundSession) sessionId = foundSession;
|
|
125
|
+
|
|
126
|
+
const type = asString(event["type"], "").trim();
|
|
127
|
+
|
|
128
|
+
if (type === "system" && asString(event["subtype"], "").trim() === "init") {
|
|
129
|
+
model = asString(event["model"], "").trim() || model;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (type === "assistant") {
|
|
134
|
+
messages.push(...collectAssistantText(event["message"]));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (type === "result") {
|
|
139
|
+
const usageObj = parseObject(event["usage"]);
|
|
140
|
+
usage.inputTokens += asNumber(usageObj["input_tokens"], asNumber(usageObj["inputTokens"], 0));
|
|
141
|
+
usage.cachedInputTokens += asNumber(usageObj["cached_input_tokens"], asNumber(usageObj["cachedInputTokens"], asNumber(usageObj["cache_read_input_tokens"], 0)));
|
|
142
|
+
usage.outputTokens += asNumber(usageObj["output_tokens"], asNumber(usageObj["outputTokens"], 0));
|
|
143
|
+
hasUsage = true;
|
|
144
|
+
totalCostUsd += asNumber(event["total_cost_usd"], asNumber(event["cost_usd"], asNumber(event["cost"], 0)));
|
|
145
|
+
model = asString(event["model"], "").trim() || model;
|
|
146
|
+
|
|
147
|
+
const isError = event["is_error"] === true || asString(event["subtype"], "").toLowerCase() === "error";
|
|
148
|
+
const resultText = asString(event["result"], "").trim();
|
|
149
|
+
if (resultText && messages.length === 0) messages.push(resultText);
|
|
150
|
+
if (isError) {
|
|
151
|
+
const text = asString(event["error"] as string ?? event["message"] as string ?? event["result"] as string, "").trim();
|
|
152
|
+
if (text) errorMessage = text;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (type === "error") {
|
|
158
|
+
const msg = asString(event["message"] as string ?? event["error"] as string, "").trim();
|
|
159
|
+
if (msg) errorMessage = msg;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (type === "system") {
|
|
164
|
+
const subtype = asString(event["subtype"], "").trim().toLowerCase();
|
|
165
|
+
if (subtype === "error") {
|
|
166
|
+
const msg = asString(event["message"] as string ?? event["error"] as string, "").trim();
|
|
167
|
+
if (msg) errorMessage = msg;
|
|
168
|
+
}
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (type === "text") {
|
|
173
|
+
const part = parseObject(event["part"]);
|
|
174
|
+
const text = asString(part["text"], "").trim();
|
|
175
|
+
if (text) messages.push(text);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (type === "step_finish") {
|
|
180
|
+
const part = parseObject(event["part"]);
|
|
181
|
+
const tokens = parseObject(part["tokens"]);
|
|
182
|
+
const cache = parseObject(tokens["cache"]);
|
|
183
|
+
usage.inputTokens += asNumber(tokens["input"], 0);
|
|
184
|
+
usage.cachedInputTokens += asNumber(cache["read"], 0);
|
|
185
|
+
usage.outputTokens += asNumber(tokens["output"], 0);
|
|
186
|
+
hasUsage = true;
|
|
187
|
+
totalCostUsd += asNumber(part["cost"], 0);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
sessionId,
|
|
194
|
+
model,
|
|
195
|
+
usage: hasUsage ? usage : null,
|
|
196
|
+
costUsd: totalCostUsd > 0 ? totalCostUsd : null,
|
|
197
|
+
summary: messages.join("\n\n").trim() || null,
|
|
198
|
+
isError: errorMessage !== null,
|
|
199
|
+
errorMessage,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function parseCursorStreamLine(line: string): StreamEvent | null {
|
|
204
|
+
const normalized = normalizeCursorStreamLine(line);
|
|
205
|
+
if (!normalized.line) return null;
|
|
206
|
+
|
|
207
|
+
const event = parseJson(normalized.line);
|
|
208
|
+
if (!event) return null;
|
|
209
|
+
|
|
210
|
+
const type = asString(event["type"], "").trim();
|
|
211
|
+
const sessionId = readSessionId(event);
|
|
212
|
+
const base = stubBase(event, sessionId);
|
|
213
|
+
|
|
214
|
+
if (type === "system" && asString(event["subtype"], "").trim() === "init") {
|
|
215
|
+
return {
|
|
216
|
+
type: "system",
|
|
217
|
+
subtype: "init",
|
|
218
|
+
model: asString(event["model"], "") || null,
|
|
219
|
+
cwd: null,
|
|
220
|
+
tools: null,
|
|
221
|
+
permissionMode: null,
|
|
222
|
+
...base,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (type === "assistant") {
|
|
227
|
+
const message = parseObject(event["message"]);
|
|
228
|
+
const content = Array.isArray(message["content"]) ? message["content"] : [];
|
|
229
|
+
for (const partRaw of content) {
|
|
230
|
+
const part = parseObject(partRaw);
|
|
231
|
+
const blockType = asString(part["type"], "").trim();
|
|
232
|
+
if (blockType === "tool_use") {
|
|
233
|
+
return {
|
|
234
|
+
type: "tool_call",
|
|
235
|
+
toolCallId: asString(part["id"], "") || null,
|
|
236
|
+
name: asString(part["name"], ""),
|
|
237
|
+
input: part["input"],
|
|
238
|
+
...base,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (blockType === "tool_result") {
|
|
242
|
+
return {
|
|
243
|
+
type: "tool_result",
|
|
244
|
+
toolCallId: asString(part["tool_use_id"], "") || null,
|
|
245
|
+
toolName: null,
|
|
246
|
+
content: asString(part["content"], ""),
|
|
247
|
+
isError: part["is_error"] === true,
|
|
248
|
+
exitCode: null,
|
|
249
|
+
...base,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const texts = collectAssistantText(event["message"]);
|
|
255
|
+
if (texts.length > 0) {
|
|
256
|
+
return { type: "assistant", text: texts.join("\n"), ...base };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (type === "result") {
|
|
261
|
+
return {
|
|
262
|
+
type: "result",
|
|
263
|
+
text: asString(event["result"], ""),
|
|
264
|
+
costUsd: typeof event["total_cost_usd"] === "number" ? event["total_cost_usd"] : null,
|
|
265
|
+
isError: event["is_error"] === true,
|
|
266
|
+
stopReason: null,
|
|
267
|
+
terminalReason: null,
|
|
268
|
+
numTurns: null,
|
|
269
|
+
durationMs: null,
|
|
270
|
+
...base,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Forward-compat: surface unknown wire events rather than dropping them.
|
|
275
|
+
return { type: "unknown", subtype: type, ...base };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const CURSOR_UNKNOWN_SESSION_RE = /unknown\s+(session|chat)|session\s+.*\s+not\s+found|chat\s+.*\s+not\s+found|resume\s+.*\s+not\s+found|could\s+not\s+resume/i;
|
|
279
|
+
|
|
280
|
+
export function isCursorUnknownSessionError(stdout: string, stderr: string): boolean {
|
|
281
|
+
return CURSOR_UNKNOWN_SESSION_RE.test(stdout) || CURSOR_UNKNOWN_SESSION_RE.test(stderr);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const CURSOR_AUTH_RE = /CURSOR_API_KEY\s+is\s+not\s+set|OPENAI_API_KEY\s+is\s+not\s+set|unauthorized|authentication.*required/i;
|
|
285
|
+
|
|
286
|
+
export function isCursorAuthRequired(stdout: string, stderr: string): boolean {
|
|
287
|
+
return CURSOR_AUTH_RE.test(stdout) || CURSOR_AUTH_RE.test(stderr);
|
|
288
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProviderModule } from "../../types.js";
|
|
2
|
+
import { acpProvider } from "../acp/index.js";
|
|
3
|
+
import { resolveAuthForProvider } from "../../utils/auth.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gemini via the Agent Client Protocol (`gemini --acp`). Requires a recent
|
|
7
|
+
* `@google/gemini-cli` on PATH; Gemini handles its own Google auth (GEMINI_API_KEY,
|
|
8
|
+
* GOOGLE_API_KEY, or an OAuth login).
|
|
9
|
+
*
|
|
10
|
+
* Replaces the previous one-shot `--output-format stream-json` adapter: the ACP
|
|
11
|
+
* base gives gemini real sessions, streaming, tool-call correlation, permission
|
|
12
|
+
* bridging (via `onUserInputRequest`), and mode discovery — none of which the
|
|
13
|
+
* stub parser had.
|
|
14
|
+
*/
|
|
15
|
+
const gemini: ProviderModule = acpProvider({ id: "gemini", command: ["gemini", "--acp"] });
|
|
16
|
+
|
|
17
|
+
// Keep gemini's richer auth reporting (API key / OAuth presence) rather than the
|
|
18
|
+
// generic ACP binary-only check.
|
|
19
|
+
gemini.resolveAuth = (ctx) => resolveAuthForProvider("gemini", ctx);
|
|
20
|
+
|
|
21
|
+
export const geminiProvider = gemini;
|
|
@@ -0,0 +1,40 @@
|
|
|
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 openclawSessionCodec: SessionCodec = {
|
|
13
|
+
deserialize(raw: unknown): Record<string, unknown> | null {
|
|
14
|
+
const obj = parseAsObject(raw);
|
|
15
|
+
if (!obj) return null;
|
|
16
|
+
const sessionKey = readNonEmptyString(obj["sessionKey"]) ?? readNonEmptyString(obj["session_key"]);
|
|
17
|
+
if (!sessionKey) return null;
|
|
18
|
+
const gatewayUrl = readNonEmptyString(obj["gatewayUrl"]) ?? readNonEmptyString(obj["gateway_url"]);
|
|
19
|
+
return {
|
|
20
|
+
sessionKey,
|
|
21
|
+
...(gatewayUrl ? { gatewayUrl } : {}),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
serialize(params: Record<string, unknown> | null): Record<string, unknown> | null {
|
|
26
|
+
if (!params) return null;
|
|
27
|
+
const sessionKey = readNonEmptyString(params["sessionKey"]) ?? readNonEmptyString(params["session_key"]);
|
|
28
|
+
if (!sessionKey) return null;
|
|
29
|
+
const gatewayUrl = readNonEmptyString(params["gatewayUrl"]);
|
|
30
|
+
return {
|
|
31
|
+
sessionKey,
|
|
32
|
+
...(gatewayUrl ? { gatewayUrl } : {}),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
getDisplayId(params: Record<string, unknown> | null): string | null {
|
|
37
|
+
if (!params) return null;
|
|
38
|
+
return readNonEmptyString(params["sessionKey"]) ?? readNonEmptyString(params["session_key"]);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ExecutionContext, ExecutionResult } from "../../types.js";
|
|
2
|
+
import { runHttpAgent } from "../_shared/http-agent.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OpenClaw is a remote HTTP-gateway agent — its behavior is the canonical
|
|
6
|
+
* `httpAgent` pattern (URL resolution, sessionKey round-trip, 401/403 →
|
|
7
|
+
* auth_required, AbortController timeout), so it's a thin call into the shared base.
|
|
8
|
+
*/
|
|
9
|
+
export function executeOpenclawProvider(ctx: ExecutionContext): Promise<ExecutionResult> {
|
|
10
|
+
return runHttpAgent(
|
|
11
|
+
{
|
|
12
|
+
providerType: "openclaw",
|
|
13
|
+
defaultBaseUrl: "http://localhost:3001",
|
|
14
|
+
runPath: "/api/agent/run",
|
|
15
|
+
loginCommand: "openclaw login",
|
|
16
|
+
},
|
|
17
|
+
ctx,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ProviderModule } from "../../types.js";
|
|
2
|
+
import { openclawSessionCodec } from "./codec.js";
|
|
3
|
+
|
|
4
|
+
export const openclawProvider: ProviderModule = {
|
|
5
|
+
type: "openclaw",
|
|
6
|
+
capabilities: {
|
|
7
|
+
sessions: false,
|
|
8
|
+
modelDiscovery: false,
|
|
9
|
+
quotaProbing: false,
|
|
10
|
+
mcp: false,
|
|
11
|
+
skills: false,
|
|
12
|
+
instructions: false,
|
|
13
|
+
workspace: false,
|
|
14
|
+
planMode: false,
|
|
15
|
+
concurrentSend: false,
|
|
16
|
+
cancelQueuedMessage: false,
|
|
17
|
+
stopTask: false,
|
|
18
|
+
modes: false,
|
|
19
|
+
},
|
|
20
|
+
// execute.ts loads lazily on first use.
|
|
21
|
+
execute: async (ctx) => (await import("./execute.js")).executeOpenclawProvider(ctx),
|
|
22
|
+
resolveAuth: async () => ({
|
|
23
|
+
providerType: "openclaw",
|
|
24
|
+
binary: { installed: true }, // openclaw is a gateway URL, not a local binary
|
|
25
|
+
options: [],
|
|
26
|
+
source: "filesystem",
|
|
27
|
+
}),
|
|
28
|
+
sessionCodec: openclawSessionCodec,
|
|
29
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
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 opencodeSessionCodec: 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
|
+
readNonEmptyString(obj["sessionID"]);
|
|
20
|
+
if (!sessionId) return null;
|
|
21
|
+
const cwd = readNonEmptyString(obj["cwd"]);
|
|
22
|
+
return {
|
|
23
|
+
sessionId,
|
|
24
|
+
...(cwd ? { cwd } : {}),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
serialize(params: Record<string, unknown> | null): Record<string, unknown> | null {
|
|
29
|
+
if (!params) return null;
|
|
30
|
+
const sessionId =
|
|
31
|
+
readNonEmptyString(params["sessionId"]) ??
|
|
32
|
+
readNonEmptyString(params["session_id"]) ??
|
|
33
|
+
readNonEmptyString(params["sessionID"]);
|
|
34
|
+
if (!sessionId) return null;
|
|
35
|
+
const cwd = readNonEmptyString(params["cwd"]);
|
|
36
|
+
return {
|
|
37
|
+
sessionId,
|
|
38
|
+
...(cwd ? { cwd } : {}),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
getDisplayId(params: Record<string, unknown> | null): string | null {
|
|
43
|
+
if (!params) return null;
|
|
44
|
+
return (
|
|
45
|
+
readNonEmptyString(params["sessionId"]) ??
|
|
46
|
+
readNonEmptyString(params["session_id"]) ??
|
|
47
|
+
readNonEmptyString(params["sessionID"])
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { BaseStreamEventFields, StreamEvent, TokenUsage } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
function str(v: unknown): string | null {
|
|
4
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
5
|
+
}
|
|
6
|
+
function num(v: unknown): number | null {
|
|
7
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
8
|
+
}
|
|
9
|
+
function rec(v: unknown): Record<string, unknown> | null {
|
|
10
|
+
return v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface OcBaseInfo {
|
|
14
|
+
provider: string;
|
|
15
|
+
sessionId: string | null;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeBase(raw: Record<string, unknown>, info: OcBaseInfo): BaseStreamEventFields {
|
|
20
|
+
return {
|
|
21
|
+
timestamp: info.timestamp,
|
|
22
|
+
providerType: info.provider,
|
|
23
|
+
sessionId: info.sessionId,
|
|
24
|
+
messageId: str(raw["messageID"]),
|
|
25
|
+
eventId: str(raw["id"]),
|
|
26
|
+
turnId: null,
|
|
27
|
+
parentToolCallId: null,
|
|
28
|
+
raw,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** True if a text part should be surfaced (skip synthetic / ignored parts). */
|
|
33
|
+
function isVisibleText(part: Record<string, unknown>): boolean {
|
|
34
|
+
return part["synthetic"] !== true && part["ignored"] !== true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Concatenate the visible assistant text across a message's parts. */
|
|
38
|
+
export function assistantTextFromParts(parts: unknown): string {
|
|
39
|
+
if (!Array.isArray(parts)) return "";
|
|
40
|
+
const out: string[] = [];
|
|
41
|
+
for (const p of parts) {
|
|
42
|
+
const part = rec(p);
|
|
43
|
+
if (!part) continue;
|
|
44
|
+
if (part["type"] === "text" && isVisibleText(part) && typeof part["text"] === "string") {
|
|
45
|
+
out.push(part["text"]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out.join("");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Status of an opencode AssistantMessage → TurnResult status. */
|
|
52
|
+
export function turnStatusFromMessage(info: unknown): "completed" | "failed" {
|
|
53
|
+
const m = rec(info);
|
|
54
|
+
return m && m["error"] ? "failed" : "completed";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Map opencode's `tokens` (+ model identity) to agentex usage. */
|
|
58
|
+
export function usageFromMessage(info: unknown): Record<string, TokenUsage> | undefined {
|
|
59
|
+
const m = rec(info);
|
|
60
|
+
if (!m) return undefined;
|
|
61
|
+
const tokens = rec(m["tokens"]);
|
|
62
|
+
if (!tokens) return undefined;
|
|
63
|
+
const input = num(tokens["input"]);
|
|
64
|
+
const output = num(tokens["output"]);
|
|
65
|
+
if (input == null && output == null) return undefined;
|
|
66
|
+
const cache = rec(tokens["cache"]);
|
|
67
|
+
const model =
|
|
68
|
+
[str(m["providerID"]), str(m["modelID"])].filter(Boolean).join("/") || "opencode";
|
|
69
|
+
const usage: TokenUsage = { inputTokens: input ?? 0, outputTokens: output ?? 0 };
|
|
70
|
+
const cacheRead = cache ? num(cache["read"]) : null;
|
|
71
|
+
if (cacheRead != null) usage.cachedInputTokens = cacheRead;
|
|
72
|
+
return { [model]: usage };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Build a `tool_call` StreamEvent for an opencode ToolPart (the announcement,
|
|
76
|
+
* emitted on first sight regardless of state). */
|
|
77
|
+
export function mapOpenCodeToolCall(part: Record<string, unknown>, info: OcBaseInfo): StreamEvent {
|
|
78
|
+
const state = rec(part["state"]);
|
|
79
|
+
return {
|
|
80
|
+
type: "tool_call",
|
|
81
|
+
toolCallId: str(part["callID"]),
|
|
82
|
+
name: str(part["tool"]) ?? "tool",
|
|
83
|
+
input: state ? (state["input"] ?? null) : null,
|
|
84
|
+
...makeBase(part, info),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Map an opencode `Part` to a StreamEvent. Text/reasoning parts carry the text
|
|
90
|
+
* to emit in `textOverride` (the session passes a computed delta so streaming
|
|
91
|
+
* isn't re-emitted in full). Tool parts map by their state status. Returns null
|
|
92
|
+
* for parts with no agentex equivalent.
|
|
93
|
+
*/
|
|
94
|
+
export function mapOpenCodePart(
|
|
95
|
+
part: Record<string, unknown>,
|
|
96
|
+
info: OcBaseInfo,
|
|
97
|
+
textOverride?: string,
|
|
98
|
+
): StreamEvent | null {
|
|
99
|
+
const base = makeBase(part, info);
|
|
100
|
+
const type = part["type"];
|
|
101
|
+
|
|
102
|
+
if (type === "text") {
|
|
103
|
+
if (!isVisibleText(part)) return null;
|
|
104
|
+
const text = textOverride ?? (typeof part["text"] === "string" ? part["text"] : "");
|
|
105
|
+
return text ? { type: "assistant", text, ...base } : null;
|
|
106
|
+
}
|
|
107
|
+
if (type === "reasoning") {
|
|
108
|
+
const text = textOverride ?? (typeof part["text"] === "string" ? part["text"] : "");
|
|
109
|
+
return text ? { type: "thinking", text, ...base } : null;
|
|
110
|
+
}
|
|
111
|
+
if (type === "tool") {
|
|
112
|
+
const state = rec(part["state"]);
|
|
113
|
+
const status = state ? str(state["status"]) : null;
|
|
114
|
+
const callId = str(part["callID"]);
|
|
115
|
+
const name = str(part["tool"]) ?? "tool";
|
|
116
|
+
if (status === "completed" || status === "error") {
|
|
117
|
+
// OpenCode's ToolState carries the result in `output` on completion and the
|
|
118
|
+
// message in `error` on failure (verified against the 1.3.x OpenAPI).
|
|
119
|
+
const raw = status === "error" ? state?.["error"] : state?.["output"];
|
|
120
|
+
const content = typeof raw === "string" ? raw : raw != null ? JSON.stringify(raw) : "";
|
|
121
|
+
return {
|
|
122
|
+
type: "tool_result",
|
|
123
|
+
toolCallId: callId,
|
|
124
|
+
toolName: name,
|
|
125
|
+
content,
|
|
126
|
+
isError: status === "error",
|
|
127
|
+
exitCode: null,
|
|
128
|
+
...base,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// pending / running → the tool_call announcement
|
|
132
|
+
return {
|
|
133
|
+
type: "tool_call",
|
|
134
|
+
toolCallId: callId,
|
|
135
|
+
name,
|
|
136
|
+
input: state ? (state["input"] ?? null) : null,
|
|
137
|
+
...base,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|