@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,203 @@
|
|
|
1
|
+
import type { BaseStreamEventFields, StreamEvent } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
const PROVIDER_TYPE = "opencode";
|
|
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 OpenCodeParsedResult {
|
|
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
|
+
export function parseOpenCodeJsonl(stdout: string): OpenCodeParsedResult {
|
|
56
|
+
let sessionId: string | null = null;
|
|
57
|
+
const messages: string[] = [];
|
|
58
|
+
const errors: string[] = [];
|
|
59
|
+
const usage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
60
|
+
let costUsd = 0;
|
|
61
|
+
let hasUsage = false;
|
|
62
|
+
|
|
63
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
64
|
+
const line = rawLine.trim();
|
|
65
|
+
if (!line) continue;
|
|
66
|
+
|
|
67
|
+
const event = parseJson(line);
|
|
68
|
+
if (!event) continue;
|
|
69
|
+
|
|
70
|
+
const currentSessionId = asString(event["sessionID"], "").trim();
|
|
71
|
+
if (currentSessionId) sessionId = currentSessionId;
|
|
72
|
+
|
|
73
|
+
const type = asString(event["type"], "");
|
|
74
|
+
|
|
75
|
+
if (type === "text") {
|
|
76
|
+
const part = parseObject(event["part"]);
|
|
77
|
+
const text = asString(part["text"], "").trim();
|
|
78
|
+
if (text) messages.push(text);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (type === "step_finish") {
|
|
83
|
+
const part = parseObject(event["part"]);
|
|
84
|
+
const tokens = parseObject(part["tokens"]);
|
|
85
|
+
const cache = parseObject(tokens["cache"]);
|
|
86
|
+
usage.inputTokens += asNumber(tokens["input"], 0);
|
|
87
|
+
usage.cachedInputTokens += asNumber(cache["read"], 0);
|
|
88
|
+
usage.outputTokens += asNumber(tokens["output"], 0) + asNumber(tokens["reasoning"], 0);
|
|
89
|
+
costUsd += asNumber(part["cost"], 0);
|
|
90
|
+
hasUsage = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (type === "tool_use") {
|
|
95
|
+
const part = parseObject(event["part"]);
|
|
96
|
+
const state = parseObject(part["state"]);
|
|
97
|
+
if (asString(state["status"], "") === "error") {
|
|
98
|
+
const text = asString(state["error"], "").trim();
|
|
99
|
+
if (text) errors.push(text);
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (type === "error") {
|
|
105
|
+
const text = asString(event["message"] as string ?? event["error"] as string, "").trim();
|
|
106
|
+
if (text) errors.push(text);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
sessionId,
|
|
113
|
+
model: null,
|
|
114
|
+
usage: hasUsage ? usage : null,
|
|
115
|
+
costUsd: costUsd > 0 ? costUsd : null,
|
|
116
|
+
summary: messages.join("\n\n").trim() || null,
|
|
117
|
+
isError: errors.length > 0,
|
|
118
|
+
errorMessage: errors.length > 0 ? errors.join("\n") : null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function parseOpenCodeStreamLine(line: string): StreamEvent | null {
|
|
123
|
+
const event = parseJson(line);
|
|
124
|
+
if (!event) return null;
|
|
125
|
+
|
|
126
|
+
const type = asString(event["type"], "");
|
|
127
|
+
const sessionId = asString(event["sessionID"], "").trim() || null;
|
|
128
|
+
const base = stubBase(event, sessionId);
|
|
129
|
+
|
|
130
|
+
if (type === "text") {
|
|
131
|
+
const part = parseObject(event["part"]);
|
|
132
|
+
const text = asString(part["text"], "");
|
|
133
|
+
if (text) return { type: "assistant", text, ...base };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (type === "tool_use") {
|
|
137
|
+
const part = parseObject(event["part"]);
|
|
138
|
+
const name = asString(part["name"], "");
|
|
139
|
+
const state = parseObject(part["state"]);
|
|
140
|
+
const status = asString(state["status"], "");
|
|
141
|
+
if (status === "error" || status === "completed") {
|
|
142
|
+
return {
|
|
143
|
+
type: "tool_result",
|
|
144
|
+
toolCallId: asString(part["id"], "") || asString(part["tool_use_id"], "") || null,
|
|
145
|
+
toolName: name || null,
|
|
146
|
+
content: status === "error" ? asString(state["error"], "") : asString(state["result"], ""),
|
|
147
|
+
isError: status === "error",
|
|
148
|
+
exitCode: null,
|
|
149
|
+
...base,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
type: "tool_call",
|
|
154
|
+
toolCallId: asString(part["id"], "") || asString(part["tool_use_id"], "") || null,
|
|
155
|
+
name,
|
|
156
|
+
input: part["input"],
|
|
157
|
+
...base,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (type === "step_finish") {
|
|
162
|
+
return {
|
|
163
|
+
type: "result",
|
|
164
|
+
text: "",
|
|
165
|
+
costUsd: null,
|
|
166
|
+
isError: false,
|
|
167
|
+
stopReason: null,
|
|
168
|
+
terminalReason: null,
|
|
169
|
+
numTurns: null,
|
|
170
|
+
durationMs: null,
|
|
171
|
+
...base,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (type === "error") {
|
|
176
|
+
return {
|
|
177
|
+
type: "result",
|
|
178
|
+
text: asString(event["message"], ""),
|
|
179
|
+
costUsd: null,
|
|
180
|
+
isError: true,
|
|
181
|
+
stopReason: null,
|
|
182
|
+
terminalReason: null,
|
|
183
|
+
numTurns: null,
|
|
184
|
+
durationMs: null,
|
|
185
|
+
...base,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Forward-compat: surface unknown wire events rather than dropping them.
|
|
190
|
+
return { type: "unknown", subtype: type, ...base };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const OPENCODE_UNKNOWN_SESSION_RE = /unknown\s+session|session\b.*\bnot\s+found|no session/i;
|
|
194
|
+
|
|
195
|
+
export function isOpenCodeUnknownSessionError(stdout: string, stderr: string): boolean {
|
|
196
|
+
return OPENCODE_UNKNOWN_SESSION_RE.test(stdout) || OPENCODE_UNKNOWN_SESSION_RE.test(stderr);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const OPENCODE_AUTH_RE = /api[_ ]?key\s+(?:required|missing|not\s+set|invalid)|unauthorized|authentication.*required/i;
|
|
200
|
+
|
|
201
|
+
export function isOpenCodeAuthRequired(stdout: string, stderr: string): boolean {
|
|
202
|
+
return OPENCODE_AUTH_RE.test(stdout) || OPENCODE_AUTH_RE.test(stderr);
|
|
203
|
+
}
|
|
Binary file
|
|
@@ -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
|
+
/**
|
|
13
|
+
* Pi uses file-path-based sessions (e.g. ~/.pi/paperclips/2024-01-01T00-00-00-agentId.jsonl).
|
|
14
|
+
* The sessionId stored here is the file path to the session file.
|
|
15
|
+
*/
|
|
16
|
+
export const piSessionCodec: SessionCodec = {
|
|
17
|
+
deserialize(raw: unknown): Record<string, unknown> | null {
|
|
18
|
+
const obj = parseAsObject(raw);
|
|
19
|
+
if (!obj) return null;
|
|
20
|
+
const sessionId = readNonEmptyString(obj["sessionId"]) ?? readNonEmptyString(obj["session_id"]);
|
|
21
|
+
if (!sessionId) return null;
|
|
22
|
+
const cwd = readNonEmptyString(obj["cwd"]);
|
|
23
|
+
return {
|
|
24
|
+
sessionId,
|
|
25
|
+
...(cwd ? { cwd } : {}),
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
serialize(params: Record<string, unknown> | null): Record<string, unknown> | null {
|
|
30
|
+
if (!params) return null;
|
|
31
|
+
const sessionId = readNonEmptyString(params["sessionId"]) ?? 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,297 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import type { ExecutionContext, ExecutionResult } from "../../types.js";
|
|
5
|
+
import { findBinary } from "../../utils/binary.js";
|
|
6
|
+
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
7
|
+
import { runChildProcess, deriveErrorCode } from "../../utils/process.js";
|
|
8
|
+
import { injectHomeSkills } from "../../utils/skills.js";
|
|
9
|
+
import { resolveInstructions } from "../../utils/instructions.js";
|
|
10
|
+
import { prepareWorkspace } from "../../utils/workspace.js";
|
|
11
|
+
import type { PreparedWorkspace } from "../../utils/workspace.js";
|
|
12
|
+
import { uuidv7 } from "../../utils/uuid.js";
|
|
13
|
+
import { parsePiJsonl, parsePiStreamLine, isPiUnknownSessionError } from "./parse.js";
|
|
14
|
+
|
|
15
|
+
const PI_SESSIONS_DIR = path.join(os.homedir(), ".pi", "sessions");
|
|
16
|
+
|
|
17
|
+
function parseModelProvider(model: string | null): string | null {
|
|
18
|
+
if (!model) return null;
|
|
19
|
+
const trimmed = model.trim();
|
|
20
|
+
if (!trimmed.includes("/")) return null;
|
|
21
|
+
return trimmed.slice(0, trimmed.indexOf("/")).trim() || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseModelId(model: string | null): string | null {
|
|
25
|
+
if (!model) return null;
|
|
26
|
+
const trimmed = model.trim();
|
|
27
|
+
if (!trimmed.includes("/")) return trimmed || null;
|
|
28
|
+
return trimmed.slice(trimmed.indexOf("/") + 1).trim() || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildSessionPath(runId: string): string {
|
|
32
|
+
const safeTimestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
33
|
+
return path.join(PI_SESSIONS_DIR, `${safeTimestamp}-${runId}.jsonl`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function executePiProvider(ctx: ExecutionContext): Promise<ExecutionResult> {
|
|
37
|
+
const runId = ctx.runId ?? uuidv7();
|
|
38
|
+
let cwd = ctx.cwd ?? process.cwd();
|
|
39
|
+
const model = ctx.model ?? ctx.config?.model ?? "";
|
|
40
|
+
const config = ctx.config ?? {};
|
|
41
|
+
const startedAt = new Date().toISOString();
|
|
42
|
+
|
|
43
|
+
// 1. Resolve binary
|
|
44
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "binary" });
|
|
45
|
+
let resolvedBinary;
|
|
46
|
+
try {
|
|
47
|
+
resolvedBinary = await findBinary("pi", config.command);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const errorMessage = err instanceof Error ? err.message : "Binary not found";
|
|
50
|
+
ctx.onLifecycle?.({ phase: "error", message: errorMessage });
|
|
51
|
+
return {
|
|
52
|
+
runId,
|
|
53
|
+
exitCode: null,
|
|
54
|
+
signal: null,
|
|
55
|
+
status: "failed" as const,
|
|
56
|
+
startedAt,
|
|
57
|
+
completedAt: new Date().toISOString(),
|
|
58
|
+
durationMs: Date.now() - new Date(startedAt).getTime(),
|
|
59
|
+
errorMessage,
|
|
60
|
+
errorCode: "binary_not_found",
|
|
61
|
+
costUsd: null,
|
|
62
|
+
model: null,
|
|
63
|
+
summary: null,
|
|
64
|
+
sessionParams: null,
|
|
65
|
+
sessionDisplayId: null,
|
|
66
|
+
clearSession: false,
|
|
67
|
+
billingType: null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Workspace isolation
|
|
72
|
+
let workspace: PreparedWorkspace | undefined;
|
|
73
|
+
if (config.workspace) {
|
|
74
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "workspace" });
|
|
75
|
+
workspace = await prepareWorkspace(cwd, config.workspace);
|
|
76
|
+
cwd = workspace.cwd;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. Build env
|
|
80
|
+
const env = buildEnv(ctx.env);
|
|
81
|
+
ensurePathInEnv(env);
|
|
82
|
+
|
|
83
|
+
// 3. Resolve instructions — Pi uses native --append-system-prompt flag,
|
|
84
|
+
// but we still validate the file exists early so failures are clear.
|
|
85
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "instructions" });
|
|
86
|
+
if (config.instructionsFile) await resolveInstructions(config.instructionsFile);
|
|
87
|
+
|
|
88
|
+
// 4. Inject skills into ~/.pi/agent/skills/
|
|
89
|
+
ctx.onLifecycle?.({ phase: "preparing", step: "skills" });
|
|
90
|
+
let piSkillsDir: string | null = null;
|
|
91
|
+
if (config.skillDirs && config.skillDirs.length > 0) {
|
|
92
|
+
try {
|
|
93
|
+
piSkillsDir = await injectHomeSkills(config.skillDirs, "pi");
|
|
94
|
+
} catch {
|
|
95
|
+
// Non-fatal
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 4. Ensure sessions directory exists
|
|
100
|
+
await fs.mkdir(PI_SESSIONS_DIR, { recursive: true });
|
|
101
|
+
|
|
102
|
+
// 5. Determine session resume
|
|
103
|
+
const sessionParams = ctx.sessionParams ?? null;
|
|
104
|
+
const existingSessionId = (() => {
|
|
105
|
+
if (!sessionParams) return null;
|
|
106
|
+
const id =
|
|
107
|
+
(sessionParams["sessionId"] as string | undefined) ??
|
|
108
|
+
(sessionParams["session_id"] as string | undefined);
|
|
109
|
+
if (!id || typeof id !== "string") return null;
|
|
110
|
+
const sessionCwd = sessionParams["cwd"] as string | undefined;
|
|
111
|
+
if (sessionCwd && path.resolve(sessionCwd) !== path.resolve(cwd)) return null;
|
|
112
|
+
return id;
|
|
113
|
+
})();
|
|
114
|
+
const canResume = existingSessionId !== null;
|
|
115
|
+
const sessionPath = canResume ? existingSessionId! : buildSessionPath(runId);
|
|
116
|
+
|
|
117
|
+
// Create session file if new
|
|
118
|
+
if (!canResume) {
|
|
119
|
+
try {
|
|
120
|
+
await fs.writeFile(sessionPath, "", { flag: "wx" });
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if ((err as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
123
|
+
// Non-fatal, Pi may handle it
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 6. Parse model into provider/modelId
|
|
129
|
+
const provider = parseModelProvider(model || null);
|
|
130
|
+
const modelId = parseModelId(model || null);
|
|
131
|
+
|
|
132
|
+
// 7. Build args
|
|
133
|
+
const buildArgs = (sessionFile: string): string[] => {
|
|
134
|
+
const args = [...resolvedBinary.prefixArgs, "--mode", "rpc"];
|
|
135
|
+
if (provider) args.push("--provider", provider);
|
|
136
|
+
if (modelId) args.push("--model", modelId);
|
|
137
|
+
if (config.thinking) args.push("--thinking", config.thinking);
|
|
138
|
+
args.push("--tools", "read,bash,edit,write,grep,find,ls");
|
|
139
|
+
args.push("--session", sessionFile);
|
|
140
|
+
if (piSkillsDir) args.push("--skill", piSkillsDir);
|
|
141
|
+
if (config.instructionsFile) args.push("--append-system-prompt", config.instructionsFile);
|
|
142
|
+
if (config.extraArgs) args.push(...config.extraArgs);
|
|
143
|
+
return args;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// 8. Build RPC stdin
|
|
147
|
+
const rpcStdin = JSON.stringify({ type: "prompt", message: ctx.prompt }) + "\n";
|
|
148
|
+
|
|
149
|
+
// 9. Run attempt
|
|
150
|
+
const runAttempt = async (sessionFile: string) => {
|
|
151
|
+
const args = buildArgs(sessionFile);
|
|
152
|
+
let stdoutBuffer = "";
|
|
153
|
+
|
|
154
|
+
ctx.onLifecycle?.({ phase: "spawning" });
|
|
155
|
+
const proc = await runChildProcess({
|
|
156
|
+
runId,
|
|
157
|
+
command: resolvedBinary.bin,
|
|
158
|
+
args,
|
|
159
|
+
cwd,
|
|
160
|
+
env,
|
|
161
|
+
stdin: rpcStdin,
|
|
162
|
+
timeoutSec: config.timeoutSec,
|
|
163
|
+
graceSec: config.graceSec,
|
|
164
|
+
onStart: (pid) => {
|
|
165
|
+
ctx.onLifecycle?.({ phase: "running", pid });
|
|
166
|
+
ctx.onStart?.(pid);
|
|
167
|
+
},
|
|
168
|
+
signal: ctx.signal,
|
|
169
|
+
onOutput: async (stream, chunk) => {
|
|
170
|
+
if (stream === "stderr") {
|
|
171
|
+
if (ctx.onOutput) {
|
|
172
|
+
try { await ctx.onOutput(stream, chunk); } catch { /* swallow */ }
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Buffer stdout by lines for JSONL parsing
|
|
178
|
+
stdoutBuffer += chunk;
|
|
179
|
+
const lines = stdoutBuffer.split("\n");
|
|
180
|
+
stdoutBuffer = lines.pop() || "";
|
|
181
|
+
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
if (!line) continue;
|
|
184
|
+
if (ctx.onOutput) {
|
|
185
|
+
try { await ctx.onOutput(stream, line + "\n"); } catch { /* swallow */ }
|
|
186
|
+
}
|
|
187
|
+
if (ctx.onEvent) {
|
|
188
|
+
const event = parsePiStreamLine(line);
|
|
189
|
+
if (event) {
|
|
190
|
+
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Flush remaining buffer
|
|
198
|
+
if (stdoutBuffer) {
|
|
199
|
+
if (ctx.onOutput) {
|
|
200
|
+
try { await ctx.onOutput("stdout", stdoutBuffer); } catch { /* swallow */ }
|
|
201
|
+
}
|
|
202
|
+
if (ctx.onEvent) {
|
|
203
|
+
const event = parsePiStreamLine(stdoutBuffer);
|
|
204
|
+
if (event) {
|
|
205
|
+
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return proc;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// 10. Initial attempt
|
|
214
|
+
let proc = await runAttempt(sessionPath);
|
|
215
|
+
let clearSession = false;
|
|
216
|
+
let finalSessionPath = sessionPath;
|
|
217
|
+
|
|
218
|
+
// 11. Check for unknown session — retry once with new session
|
|
219
|
+
if (
|
|
220
|
+
canResume &&
|
|
221
|
+
!proc.timedOut &&
|
|
222
|
+
(proc.exitCode ?? 0) !== 0 &&
|
|
223
|
+
isPiUnknownSessionError(proc.stdout, proc.stderr)
|
|
224
|
+
) {
|
|
225
|
+
finalSessionPath = buildSessionPath(runId + "-retry");
|
|
226
|
+
try {
|
|
227
|
+
await fs.writeFile(finalSessionPath, "", { flag: "wx" });
|
|
228
|
+
} catch {
|
|
229
|
+
// Non-fatal
|
|
230
|
+
}
|
|
231
|
+
proc = await runAttempt(finalSessionPath);
|
|
232
|
+
clearSession = true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 12. Parse result
|
|
236
|
+
const parsed = parsePiJsonl(proc.stdout);
|
|
237
|
+
const processErrorCode = deriveErrorCode(proc);
|
|
238
|
+
|
|
239
|
+
let errorCode = processErrorCode;
|
|
240
|
+
if (!errorCode && parsed.errorMessage) {
|
|
241
|
+
errorCode = "execution_error";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const errorMessage = (() => {
|
|
245
|
+
if (proc.timedOut) return `Timed out after ${config.timeoutSec ?? 0}s`;
|
|
246
|
+
if (parsed.errorMessage) return parsed.errorMessage;
|
|
247
|
+
if ((proc.exitCode ?? 0) !== 0 && !parsed.summary) {
|
|
248
|
+
const stderrLine = proc.stderr.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
249
|
+
return stderrLine ?? `Pi exited with code ${proc.exitCode ?? -1}`;
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
})();
|
|
253
|
+
|
|
254
|
+
// Always return the current session path so callers can continue.
|
|
255
|
+
// clearSession tells callers the OLD session was invalid, but we still
|
|
256
|
+
// provide the NEW session handle from the retry.
|
|
257
|
+
const resultSessionParams = { sessionId: finalSessionPath, cwd };
|
|
258
|
+
|
|
259
|
+
const completedAt = new Date().toISOString();
|
|
260
|
+
const resolvedModel = model || null;
|
|
261
|
+
const status = proc.aborted ? "aborted" as const
|
|
262
|
+
: proc.timedOut ? "timeout" as const
|
|
263
|
+
: (errorCode || errorMessage) ? "failed" as const
|
|
264
|
+
: "completed" as const;
|
|
265
|
+
|
|
266
|
+
if (status === "completed") {
|
|
267
|
+
ctx.onLifecycle?.({ phase: "completed" });
|
|
268
|
+
} else if (status === "aborted") {
|
|
269
|
+
ctx.onLifecycle?.({ phase: "cancelled" });
|
|
270
|
+
} else {
|
|
271
|
+
ctx.onLifecycle?.({ phase: "error", message: errorMessage ?? "Unknown error" });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
runId,
|
|
276
|
+
exitCode: proc.exitCode,
|
|
277
|
+
signal: proc.signal ?? null,
|
|
278
|
+
status,
|
|
279
|
+
startedAt,
|
|
280
|
+
completedAt,
|
|
281
|
+
durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
|
|
282
|
+
errorMessage,
|
|
283
|
+
errorCode,
|
|
284
|
+
usage: parsed.usage && resolvedModel
|
|
285
|
+
? { [resolvedModel]: { inputTokens: parsed.usage.inputTokens, outputTokens: parsed.usage.outputTokens, cachedInputTokens: parsed.usage.cachedInputTokens } }
|
|
286
|
+
: undefined,
|
|
287
|
+
costUsd: parsed.usage?.costUsd ?? null,
|
|
288
|
+
model: resolvedModel,
|
|
289
|
+
summary: parsed.summary,
|
|
290
|
+
sessionParams: resultSessionParams,
|
|
291
|
+
sessionDisplayId: finalSessionPath,
|
|
292
|
+
clearSession,
|
|
293
|
+
billingType: "api",
|
|
294
|
+
raw: null,
|
|
295
|
+
workspace,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ProviderModule, SessionContext, AgentSession } from "../../types.js";
|
|
2
|
+
import { piSessionCodec } from "./codec.js";
|
|
3
|
+
import { resolveAuthForProvider } from "../../utils/auth.js";
|
|
4
|
+
import { EMULATED_GOAL_CAPABILITY } from "../../goals/index.js";
|
|
5
|
+
|
|
6
|
+
export const piProvider: ProviderModule = {
|
|
7
|
+
type: "pi",
|
|
8
|
+
capabilities: {
|
|
9
|
+
sessions: true,
|
|
10
|
+
modelDiscovery: false,
|
|
11
|
+
quotaProbing: false,
|
|
12
|
+
mcp: false,
|
|
13
|
+
skills: true,
|
|
14
|
+
instructions: true,
|
|
15
|
+
workspace: true,
|
|
16
|
+
planMode: false,
|
|
17
|
+
concurrentSend: false,
|
|
18
|
+
cancelQueuedMessage: false,
|
|
19
|
+
stopTask: false,
|
|
20
|
+
modes: false,
|
|
21
|
+
goals: EMULATED_GOAL_CAPABILITY,
|
|
22
|
+
},
|
|
23
|
+
// One-shot `pi --mode rpc` for execute(); a persistent `pi --mode rpc` process
|
|
24
|
+
// across turns for createSession(). Both load lazily on first use.
|
|
25
|
+
execute: async (ctx) => (await import("./execute.js")).executePiProvider(ctx),
|
|
26
|
+
createSession: async (ctx: SessionContext): Promise<AgentSession> =>
|
|
27
|
+
(await import("./session.js")).createPiSession(ctx),
|
|
28
|
+
resolveAuth: (ctx) => resolveAuthForProvider("pi", ctx),
|
|
29
|
+
sessionCodec: piSessionCodec,
|
|
30
|
+
};
|