ohmyvibe 0.1.4 → 0.1.5
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/dist/daemon/httpServer.js +21 -1
- package/dist/daemon/index.js +3 -1
- package/dist/daemon/jsonRpc.js +22 -6
- package/dist/daemon/managementBridge.js +77 -22
- package/dist/daemon/openAiAgentClient.js +378 -0
- package/dist/daemon/projectManager.js +1188 -0
- package/dist/daemon/projectStore.js +60 -0
- package/dist/daemon/sessionManager.js +82 -5
- package/dist/daemon/sessionRuntime.js +19 -5
- package/dist/daemon/settingsStore.js +71 -0
- package/package.json +3 -1
|
@@ -62,13 +62,33 @@ export function startHttpServer(sessionManager, port) {
|
|
|
62
62
|
}
|
|
63
63
|
});
|
|
64
64
|
app.get("/api/sessions/:sessionId", (req, res) => {
|
|
65
|
-
const
|
|
65
|
+
const limit = typeof req.query.limit === "string" && Number.isFinite(Number(req.query.limit))
|
|
66
|
+
? Number(req.query.limit)
|
|
67
|
+
: undefined;
|
|
68
|
+
const session = sessionManager.get(req.params.sessionId, { limit });
|
|
66
69
|
if (!session) {
|
|
67
70
|
res.status(404).json({ error: "Session not found" });
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
70
73
|
res.json(session);
|
|
71
74
|
});
|
|
75
|
+
app.get("/api/sessions/:sessionId/transcript", (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const beforeEntryId = typeof req.query.beforeEntryId === "string" ? req.query.beforeEntryId : undefined;
|
|
78
|
+
const limit = typeof req.query.limit === "string" && Number.isFinite(Number(req.query.limit))
|
|
79
|
+
? Number(req.query.limit)
|
|
80
|
+
: undefined;
|
|
81
|
+
const page = sessionManager.getTranscriptPage(req.params.sessionId, { beforeEntryId, limit });
|
|
82
|
+
if (!page) {
|
|
83
|
+
res.status(404).json({ error: "Session not found" });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
res.json(page);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
72
92
|
app.post("/api/sessions/:sessionId/messages", async (req, res) => {
|
|
73
93
|
try {
|
|
74
94
|
const body = req.body;
|
package/dist/daemon/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
2
|
import { ManagementBridge } from "./managementBridge.js";
|
|
3
|
+
import { ProjectManager } from "./projectManager.js";
|
|
3
4
|
import { SessionManager } from "./sessionManager.js";
|
|
4
5
|
const sessionManager = new SessionManager();
|
|
6
|
+
const projectManager = new ProjectManager(sessionManager);
|
|
5
7
|
const managementServerUrl = process.env.MANAGEMENT_SERVER_URL;
|
|
6
8
|
if (!managementServerUrl) {
|
|
7
9
|
throw new Error("MANAGEMENT_SERVER_URL is required");
|
|
8
10
|
}
|
|
9
|
-
const bridge = new ManagementBridge(
|
|
11
|
+
const bridge = new ManagementBridge(projectManager, {
|
|
10
12
|
serverUrl: managementServerUrl,
|
|
11
13
|
daemonId: process.env.DAEMON_ID,
|
|
12
14
|
daemonName: process.env.DAEMON_NAME,
|
package/dist/daemon/jsonRpc.js
CHANGED
|
@@ -6,6 +6,7 @@ export class JsonRpcProcess extends EventEmitter {
|
|
|
6
6
|
nextId = 1;
|
|
7
7
|
stdoutBuffer = "";
|
|
8
8
|
stderrBuffer = "";
|
|
9
|
+
finished = false;
|
|
9
10
|
constructor(command, args, cwd) {
|
|
10
11
|
super();
|
|
11
12
|
this.child = spawn(command, args, {
|
|
@@ -17,15 +18,17 @@ export class JsonRpcProcess extends EventEmitter {
|
|
|
17
18
|
this.child.stderr.setEncoding("utf8");
|
|
18
19
|
this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
19
20
|
this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
|
|
21
|
+
this.child.stdin.on("error", (error) => this.handleFailure(error));
|
|
22
|
+
this.child.stdout.on("error", (error) => this.handleFailure(error));
|
|
23
|
+
this.child.stderr.on("error", (error) => this.handleFailure(error));
|
|
20
24
|
this.child.on("error", (error) => {
|
|
21
|
-
|
|
22
|
-
pending.reject(error);
|
|
23
|
-
}
|
|
24
|
-
this.pending.clear();
|
|
25
|
-
this.emit("stderr", error.message);
|
|
26
|
-
this.emit("exit", { code: null, signal: null });
|
|
25
|
+
this.handleFailure(error, { code: null, signal: null });
|
|
27
26
|
});
|
|
28
27
|
this.child.on("exit", (code, signal) => {
|
|
28
|
+
if (this.finished) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.finished = true;
|
|
29
32
|
this.flushStderr();
|
|
30
33
|
const error = new Error(`JSON-RPC process exited (code=${code}, signal=${signal})`);
|
|
31
34
|
for (const pending of this.pending.values()) {
|
|
@@ -133,4 +136,17 @@ export class JsonRpcProcess extends EventEmitter {
|
|
|
133
136
|
this.emit("stderr", remaining);
|
|
134
137
|
}
|
|
135
138
|
}
|
|
139
|
+
handleFailure(error, exit = { code: null, signal: null }) {
|
|
140
|
+
if (this.finished) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.finished = true;
|
|
144
|
+
for (const pending of this.pending.values()) {
|
|
145
|
+
pending.reject(error);
|
|
146
|
+
}
|
|
147
|
+
this.pending.clear();
|
|
148
|
+
this.flushStderr();
|
|
149
|
+
this.emit("stderr", error.message);
|
|
150
|
+
this.emit("exit", exit);
|
|
151
|
+
}
|
|
136
152
|
}
|
|
@@ -6,20 +6,20 @@ export class ManagementBridge {
|
|
|
6
6
|
serverUrl;
|
|
7
7
|
daemonId;
|
|
8
8
|
daemonName;
|
|
9
|
-
|
|
9
|
+
projectManager;
|
|
10
10
|
socket;
|
|
11
11
|
reconnectTimer;
|
|
12
12
|
eventFlushTimer;
|
|
13
13
|
pendingEvents = [];
|
|
14
|
-
constructor(
|
|
15
|
-
this.
|
|
14
|
+
constructor(projectManager, options) {
|
|
15
|
+
this.projectManager = projectManager;
|
|
16
16
|
this.serverUrl = options.serverUrl;
|
|
17
17
|
this.daemonId = options.daemonId ?? randomUUID();
|
|
18
18
|
this.daemonName = options.daemonName ?? os.hostname();
|
|
19
19
|
}
|
|
20
20
|
start() {
|
|
21
21
|
this.connect();
|
|
22
|
-
this.
|
|
22
|
+
this.projectManager.on("event", (event) => {
|
|
23
23
|
this.enqueueEvent(event);
|
|
24
24
|
});
|
|
25
25
|
}
|
|
@@ -94,9 +94,11 @@ export class ManagementBridge {
|
|
|
94
94
|
connectedAt: new Date().toISOString(),
|
|
95
95
|
lastSeenAt: new Date().toISOString(),
|
|
96
96
|
online: true,
|
|
97
|
-
sessionCount: this.
|
|
97
|
+
sessionCount: this.projectManager.listSessionsCached().length,
|
|
98
98
|
},
|
|
99
|
-
sessions: this.
|
|
99
|
+
sessions: this.projectManager.listSessionsCached(),
|
|
100
|
+
projects: this.projectManager.listProjects(),
|
|
101
|
+
agents: this.projectManager.listAgents(),
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
enqueueEvent(event) {
|
|
@@ -123,23 +125,38 @@ export class ManagementBridge {
|
|
|
123
125
|
async handleRequest(method, params) {
|
|
124
126
|
switch (method) {
|
|
125
127
|
case "getConfig":
|
|
126
|
-
return this.
|
|
128
|
+
return this.projectManager.getConfig();
|
|
127
129
|
case "listSessions":
|
|
128
|
-
return this.
|
|
130
|
+
return this.projectManager.listSessions();
|
|
129
131
|
case "getSession":
|
|
130
|
-
return this.
|
|
132
|
+
return this.projectManager.getSession(String(params.sessionId), {
|
|
133
|
+
limit: typeof params.limit === "number" ? params.limit : undefined,
|
|
134
|
+
});
|
|
135
|
+
case "getSessionTranscript":
|
|
136
|
+
return this.projectManager.getTranscriptPage(String(params.sessionId), {
|
|
137
|
+
beforeEntryId: typeof params.beforeEntryId === "string" ? params.beforeEntryId : undefined,
|
|
138
|
+
limit: typeof params.limit === "number" ? params.limit : undefined,
|
|
139
|
+
});
|
|
131
140
|
case "listHistory":
|
|
132
|
-
return this.
|
|
141
|
+
return this.projectManager.listHistory();
|
|
133
142
|
case "browseDirectories":
|
|
134
|
-
return this.
|
|
143
|
+
return this.projectManager.browseDirectories(typeof params.path === "string" ? params.path : undefined);
|
|
135
144
|
case "browseSessionFiles":
|
|
136
|
-
return this.
|
|
145
|
+
return this.projectManager.browseSessionFiles(String(params.sessionId), typeof params.path === "string" ? params.path : undefined);
|
|
137
146
|
case "readSessionFile":
|
|
138
|
-
return this.
|
|
147
|
+
return this.projectManager.readSessionFile(String(params.sessionId), String(params.path));
|
|
139
148
|
case "writeSessionFile":
|
|
140
|
-
return this.
|
|
149
|
+
return this.projectManager.writeSessionFile(String(params.sessionId), String(params.path), String(params.content ?? ""));
|
|
141
150
|
case "createSession":
|
|
142
|
-
return this.
|
|
151
|
+
return this.projectManager.createSession({
|
|
152
|
+
cwd: String(params.cwd ?? process.cwd()),
|
|
153
|
+
model: typeof params.model === "string" ? params.model : undefined,
|
|
154
|
+
reasoningEffort: typeof params.reasoningEffort === "string" ? params.reasoningEffort : undefined,
|
|
155
|
+
sandbox: typeof params.sandbox === "string" ? params.sandbox : undefined,
|
|
156
|
+
approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
|
|
157
|
+
});
|
|
158
|
+
case "createProjectSession":
|
|
159
|
+
return this.projectManager.createProjectSession(String(params.projectId), {
|
|
143
160
|
cwd: String(params.cwd ?? process.cwd()),
|
|
144
161
|
model: typeof params.model === "string" ? params.model : undefined,
|
|
145
162
|
reasoningEffort: typeof params.reasoningEffort === "string" ? params.reasoningEffort : undefined,
|
|
@@ -147,7 +164,7 @@ export class ManagementBridge {
|
|
|
147
164
|
approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
|
|
148
165
|
});
|
|
149
166
|
case "restoreSession":
|
|
150
|
-
return this.
|
|
167
|
+
return this.projectManager.restoreSession({
|
|
151
168
|
threadId: String(params.threadId),
|
|
152
169
|
cwd: typeof params.cwd === "string" ? params.cwd : undefined,
|
|
153
170
|
model: typeof params.model === "string" ? params.model : undefined,
|
|
@@ -156,27 +173,65 @@ export class ManagementBridge {
|
|
|
156
173
|
approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
|
|
157
174
|
});
|
|
158
175
|
case "sendMessage":
|
|
159
|
-
await this.
|
|
176
|
+
await this.projectManager.sendMessage(String(params.sessionId), String(params.text ?? ""));
|
|
160
177
|
return { ok: true };
|
|
161
178
|
case "updateSessionConfig":
|
|
162
|
-
return this.
|
|
179
|
+
return this.projectManager.updateSessionConfig(String(params.sessionId), {
|
|
163
180
|
model: typeof params.model === "string" ? params.model : undefined,
|
|
164
181
|
reasoningEffort: typeof params.reasoningEffort === "string" ? params.reasoningEffort : undefined,
|
|
165
182
|
sandbox: typeof params.sandbox === "string" ? params.sandbox : undefined,
|
|
166
183
|
approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
|
|
167
184
|
});
|
|
168
185
|
case "renameSession":
|
|
169
|
-
return this.
|
|
186
|
+
return this.projectManager.renameSession(String(params.sessionId), {
|
|
170
187
|
title: String(params.title ?? ""),
|
|
171
188
|
});
|
|
172
189
|
case "interruptSession":
|
|
173
|
-
await this.
|
|
190
|
+
await this.projectManager.interruptSession(String(params.sessionId));
|
|
174
191
|
return { ok: true };
|
|
175
192
|
case "respondApproval":
|
|
176
|
-
return this.
|
|
193
|
+
return this.projectManager.respondApproval(String(params.sessionId), String(params.approvalRequestId), params.decision === "deny" ? "deny" : "approve");
|
|
177
194
|
case "closeSession":
|
|
178
|
-
await this.
|
|
195
|
+
await this.projectManager.closeSession(String(params.sessionId));
|
|
179
196
|
return { ok: true };
|
|
197
|
+
case "listProjects":
|
|
198
|
+
return this.projectManager.listProjects();
|
|
199
|
+
case "getProject":
|
|
200
|
+
return this.projectManager.getProject(String(params.projectId));
|
|
201
|
+
case "createProject":
|
|
202
|
+
return this.projectManager.createProject({
|
|
203
|
+
name: String(params.name ?? ""),
|
|
204
|
+
rootDir: String(params.rootDir ?? process.cwd()),
|
|
205
|
+
goal: typeof params.goal === "string" ? params.goal : undefined,
|
|
206
|
+
runPolicy: params.runPolicy,
|
|
207
|
+
});
|
|
208
|
+
case "updateProject":
|
|
209
|
+
return this.projectManager.updateProject(String(params.projectId), {
|
|
210
|
+
name: typeof params.name === "string" ? params.name : undefined,
|
|
211
|
+
goal: typeof params.goal === "string" ? params.goal : undefined,
|
|
212
|
+
status: typeof params.status === "string" ? params.status : undefined,
|
|
213
|
+
runPolicy: params.runPolicy,
|
|
214
|
+
});
|
|
215
|
+
case "listAgents":
|
|
216
|
+
return this.projectManager.listAgents(typeof params.projectId === "string" ? params.projectId : undefined);
|
|
217
|
+
case "getAgent":
|
|
218
|
+
return this.projectManager.getAgent(String(params.agentId));
|
|
219
|
+
case "sendAgentMessage":
|
|
220
|
+
return this.projectManager.sendAgentMessage(String(params.projectId), String(params.agentId), String(params.text ?? ""));
|
|
221
|
+
case "clearAgentLogs":
|
|
222
|
+
return this.projectManager.clearAgentLogs(String(params.projectId), String(params.agentId));
|
|
223
|
+
case "runProject":
|
|
224
|
+
return this.projectManager.runProject(String(params.projectId));
|
|
225
|
+
case "pauseProject":
|
|
226
|
+
return this.projectManager.pauseProject(String(params.projectId));
|
|
227
|
+
case "getSettings":
|
|
228
|
+
return this.projectManager.getSettings();
|
|
229
|
+
case "updateProviderConfig":
|
|
230
|
+
return this.projectManager.updateProviderConfig(params);
|
|
231
|
+
case "updateNotificationConfig":
|
|
232
|
+
return this.projectManager.updateNotificationConfig(params);
|
|
233
|
+
case "listNotifications":
|
|
234
|
+
return this.projectManager.listNotifications(typeof params.projectId === "string" ? params.projectId : undefined);
|
|
180
235
|
default:
|
|
181
236
|
throw new Error(`Unsupported bridge method: ${method}`);
|
|
182
237
|
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
export class OpenAiAgentClient {
|
|
2
|
+
config;
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
async decide(agent, messages) {
|
|
7
|
+
if (this.config.apiFormat === "chat_completions") {
|
|
8
|
+
return this.decideWithChatCompletions(agent, messages);
|
|
9
|
+
}
|
|
10
|
+
return this.decideWithResponses(agent, messages);
|
|
11
|
+
}
|
|
12
|
+
async decideWithResponses(agent, messages) {
|
|
13
|
+
const payload = await this.createResponsesRequest({
|
|
14
|
+
model: this.config.model || agent.model,
|
|
15
|
+
temperature: this.config.temperature ?? 0.2,
|
|
16
|
+
max_output_tokens: this.config.maxOutputTokens ?? 4000,
|
|
17
|
+
input: messages.map((message) => ({
|
|
18
|
+
role: message.role,
|
|
19
|
+
content: message.content,
|
|
20
|
+
})),
|
|
21
|
+
tool_choice: "required",
|
|
22
|
+
tools: buildResponsesDecisionTools(),
|
|
23
|
+
});
|
|
24
|
+
const decision = parseDecisionFromResponsesOutput(payload);
|
|
25
|
+
if (decision) {
|
|
26
|
+
return decision;
|
|
27
|
+
}
|
|
28
|
+
throw new Error("OpenAI responses output did not include a tool call");
|
|
29
|
+
}
|
|
30
|
+
async decideWithChatCompletions(agent, messages) {
|
|
31
|
+
const toolPayload = await this.createChatCompletion({
|
|
32
|
+
model: this.config.model || agent.model,
|
|
33
|
+
temperature: this.config.temperature ?? 0.2,
|
|
34
|
+
max_tokens: this.config.maxOutputTokens ?? 4000,
|
|
35
|
+
messages,
|
|
36
|
+
tool_choice: "required",
|
|
37
|
+
tools: buildChatDecisionTools(),
|
|
38
|
+
});
|
|
39
|
+
const toolDecision = parseDecisionFromToolResponse(toolPayload);
|
|
40
|
+
if (toolDecision) {
|
|
41
|
+
return toolDecision;
|
|
42
|
+
}
|
|
43
|
+
const legacyPayload = await this.createChatCompletion({
|
|
44
|
+
model: this.config.model || agent.model,
|
|
45
|
+
temperature: this.config.temperature ?? 0.2,
|
|
46
|
+
max_tokens: this.config.maxOutputTokens ?? 4000,
|
|
47
|
+
messages,
|
|
48
|
+
function_call: { name: "agent_decide" },
|
|
49
|
+
functions: [buildLegacyDecisionFunction()],
|
|
50
|
+
});
|
|
51
|
+
const legacyDecision = parseDecisionFromLegacyFunctionResponse(legacyPayload);
|
|
52
|
+
if (legacyDecision) {
|
|
53
|
+
return legacyDecision;
|
|
54
|
+
}
|
|
55
|
+
throw new Error("OpenAI chat completions output did not include a tool/function call");
|
|
56
|
+
}
|
|
57
|
+
async createResponsesRequest(body) {
|
|
58
|
+
const response = await fetch(`${this.config.baseUrl.replace(/\/$/, "")}/responses`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(`OpenAI responses request failed: ${response.status} ${response.statusText}`);
|
|
68
|
+
}
|
|
69
|
+
return (await response.json());
|
|
70
|
+
}
|
|
71
|
+
async createChatCompletion(body) {
|
|
72
|
+
const response = await fetch(`${this.config.baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify(body),
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`OpenAI chat completions request failed: ${response.status} ${response.statusText}`);
|
|
82
|
+
}
|
|
83
|
+
return (await response.json());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function isAgentToolName(value) {
|
|
87
|
+
return (value === "noop" ||
|
|
88
|
+
value === "create_session" ||
|
|
89
|
+
value === "send_session_instruction" ||
|
|
90
|
+
value === "message_agent" ||
|
|
91
|
+
value === "notify_user" ||
|
|
92
|
+
value === "mark_project_complete");
|
|
93
|
+
}
|
|
94
|
+
function parseToolArguments(value) {
|
|
95
|
+
if (!value?.trim()) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
return JSON.parse(value);
|
|
99
|
+
}
|
|
100
|
+
function readMessageText(content) {
|
|
101
|
+
if (typeof content === "string") {
|
|
102
|
+
return content.trim();
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(content)) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
return content
|
|
108
|
+
.map((item) => (typeof item?.text === "string" ? item.text : ""))
|
|
109
|
+
.join("")
|
|
110
|
+
.trim();
|
|
111
|
+
}
|
|
112
|
+
function readResponsesOutputText(payload) {
|
|
113
|
+
const outputParts = Array.isArray(payload.output)
|
|
114
|
+
? payload.output
|
|
115
|
+
.flatMap((item) => Array.isArray(item.content)
|
|
116
|
+
? item.content.map((content) => (typeof content?.text === "string" ? content.text : ""))
|
|
117
|
+
: [])
|
|
118
|
+
.filter(Boolean)
|
|
119
|
+
: [];
|
|
120
|
+
if (outputParts.length) {
|
|
121
|
+
return outputParts.join("").trim();
|
|
122
|
+
}
|
|
123
|
+
return typeof payload.output_text === "string" ? payload.output_text.trim() : "";
|
|
124
|
+
}
|
|
125
|
+
function omitDecisionMeta(input) {
|
|
126
|
+
const { action: _action, thought: _thought, stopReason: _stopReason, userFacingText: _userFacingText, ...rest } = input;
|
|
127
|
+
return rest;
|
|
128
|
+
}
|
|
129
|
+
function parseDecisionFromResponsesOutput(payload) {
|
|
130
|
+
const toolCall = payload.output?.find((item) => item?.type === "function_call" && typeof item?.name === "string");
|
|
131
|
+
if (!toolCall?.name) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (!isAgentToolName(toolCall.name)) {
|
|
135
|
+
throw new Error(`Unsupported agent tool: ${toolCall.name}`);
|
|
136
|
+
}
|
|
137
|
+
const parsedArgs = parseToolArguments(toolCall.arguments);
|
|
138
|
+
return buildDecision(toolCall.name, parsedArgs, readResponsesOutputText(payload));
|
|
139
|
+
}
|
|
140
|
+
function parseDecisionFromToolResponse(payload) {
|
|
141
|
+
const message = payload.choices?.[0]?.message;
|
|
142
|
+
const toolCall = message?.tool_calls?.[0];
|
|
143
|
+
if (!toolCall?.function?.name) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const action = toolCall.function.name;
|
|
147
|
+
if (!isAgentToolName(action)) {
|
|
148
|
+
throw new Error(`Unsupported agent tool: ${toolCall.function.name}`);
|
|
149
|
+
}
|
|
150
|
+
const parsedArgs = parseToolArguments(toolCall.function.arguments);
|
|
151
|
+
return buildDecision(action, parsedArgs, readMessageText(message?.content));
|
|
152
|
+
}
|
|
153
|
+
function parseDecisionFromLegacyFunctionResponse(payload) {
|
|
154
|
+
const message = payload.choices?.[0]?.message;
|
|
155
|
+
const functionCall = message?.function_call;
|
|
156
|
+
if (!functionCall?.name) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
if (functionCall.name === "agent_decide") {
|
|
160
|
+
const parsedArgs = parseToolArguments(functionCall.arguments);
|
|
161
|
+
const action = typeof parsedArgs.action === "string" ? parsedArgs.action : "";
|
|
162
|
+
if (!isAgentToolName(action)) {
|
|
163
|
+
throw new Error(`Unsupported legacy agent action: ${action || functionCall.name}`);
|
|
164
|
+
}
|
|
165
|
+
return buildDecision(action, parsedArgs, readMessageText(message?.content));
|
|
166
|
+
}
|
|
167
|
+
if (!isAgentToolName(functionCall.name)) {
|
|
168
|
+
throw new Error(`Unsupported legacy function call: ${functionCall.name}`);
|
|
169
|
+
}
|
|
170
|
+
const parsedArgs = parseToolArguments(functionCall.arguments);
|
|
171
|
+
return buildDecision(functionCall.name, parsedArgs, readMessageText(message?.content));
|
|
172
|
+
}
|
|
173
|
+
function buildDecision(action, parsedArgs, contentText) {
|
|
174
|
+
const thought = typeof parsedArgs.thought === "string" && parsedArgs.thought.trim()
|
|
175
|
+
? parsedArgs.thought.trim()
|
|
176
|
+
: contentText;
|
|
177
|
+
const actionInput = omitDecisionMeta(parsedArgs);
|
|
178
|
+
return {
|
|
179
|
+
thought,
|
|
180
|
+
action,
|
|
181
|
+
actionInput: Object.keys(actionInput).length ? actionInput : undefined,
|
|
182
|
+
stopReason: typeof parsedArgs.stopReason === "string" && parsedArgs.stopReason.trim()
|
|
183
|
+
? parsedArgs.stopReason.trim()
|
|
184
|
+
: undefined,
|
|
185
|
+
userFacingText: typeof parsedArgs.userFacingText === "string" && parsedArgs.userFacingText.trim()
|
|
186
|
+
? parsedArgs.userFacingText.trim()
|
|
187
|
+
: undefined,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function buildResponsesDecisionTools() {
|
|
191
|
+
return buildDecisionToolSpecs().map((tool) => ({
|
|
192
|
+
type: "function",
|
|
193
|
+
name: tool.name,
|
|
194
|
+
description: tool.description,
|
|
195
|
+
parameters: tool.parameters,
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
function buildChatDecisionTools() {
|
|
199
|
+
return buildDecisionToolSpecs().map((tool) => ({
|
|
200
|
+
type: "function",
|
|
201
|
+
function: tool,
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
function buildDecisionToolSpecs() {
|
|
205
|
+
return [
|
|
206
|
+
{
|
|
207
|
+
name: "noop",
|
|
208
|
+
description: "Use when no external action should be taken right now.",
|
|
209
|
+
parameters: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: commonDecisionProperties(),
|
|
212
|
+
additionalProperties: false,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "create_session",
|
|
217
|
+
description: "Create a new session for the project, optionally with an initial instruction.",
|
|
218
|
+
parameters: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {
|
|
221
|
+
...commonDecisionProperties(),
|
|
222
|
+
cwd: { type: "string", description: "Working directory for the new session." },
|
|
223
|
+
model: { type: "string", description: "Optional model override." },
|
|
224
|
+
reasoningEffort: {
|
|
225
|
+
type: "string",
|
|
226
|
+
enum: ["none", "minimal", "low", "medium", "high", "xhigh"],
|
|
227
|
+
},
|
|
228
|
+
sandbox: {
|
|
229
|
+
type: "string",
|
|
230
|
+
enum: ["read-only", "workspace-write", "danger-full-access"],
|
|
231
|
+
},
|
|
232
|
+
approvalPolicy: {
|
|
233
|
+
type: "string",
|
|
234
|
+
enum: ["untrusted", "on-failure", "on-request", "never"],
|
|
235
|
+
},
|
|
236
|
+
instruction: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Optional first instruction to send to the new session immediately after creation.",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
additionalProperties: false,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "send_session_instruction",
|
|
246
|
+
description: "Send an instruction to an existing session.",
|
|
247
|
+
parameters: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
...commonDecisionProperties(),
|
|
251
|
+
sessionId: {
|
|
252
|
+
type: "string",
|
|
253
|
+
description: "Target session id. Optional if this agent already has a bound session.",
|
|
254
|
+
},
|
|
255
|
+
instruction: {
|
|
256
|
+
type: "string",
|
|
257
|
+
description: "Instruction text to send to the target session.",
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
required: ["instruction"],
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "message_agent",
|
|
266
|
+
description: "Send a message to another agent in the same project.",
|
|
267
|
+
parameters: {
|
|
268
|
+
type: "object",
|
|
269
|
+
properties: {
|
|
270
|
+
...commonDecisionProperties(),
|
|
271
|
+
targetAgentId: { type: "string", description: "Target agent id." },
|
|
272
|
+
text: { type: "string", description: "Message content." },
|
|
273
|
+
},
|
|
274
|
+
required: ["targetAgentId", "text"],
|
|
275
|
+
additionalProperties: false,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "notify_user",
|
|
280
|
+
description: "Create a notification for the user, optionally via email.",
|
|
281
|
+
parameters: {
|
|
282
|
+
type: "object",
|
|
283
|
+
properties: {
|
|
284
|
+
...commonDecisionProperties(),
|
|
285
|
+
severity: {
|
|
286
|
+
type: "string",
|
|
287
|
+
enum: ["info", "warning", "critical"],
|
|
288
|
+
},
|
|
289
|
+
channel: {
|
|
290
|
+
type: "string",
|
|
291
|
+
enum: ["inbox", "email"],
|
|
292
|
+
},
|
|
293
|
+
subject: { type: "string" },
|
|
294
|
+
body: { type: "string" },
|
|
295
|
+
},
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "mark_project_complete",
|
|
301
|
+
description: "Mark the project as completed.",
|
|
302
|
+
parameters: {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: commonDecisionProperties(),
|
|
305
|
+
additionalProperties: false,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
function commonDecisionProperties() {
|
|
311
|
+
return {
|
|
312
|
+
thought: {
|
|
313
|
+
type: "string",
|
|
314
|
+
description: "Brief internal reasoning summary for the agent log.",
|
|
315
|
+
},
|
|
316
|
+
stopReason: {
|
|
317
|
+
type: "string",
|
|
318
|
+
description: "Why the agent is stopping or waiting after this action.",
|
|
319
|
+
},
|
|
320
|
+
userFacingText: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "Optional concise text that can be surfaced to the user.",
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function buildLegacyDecisionFunction() {
|
|
327
|
+
return {
|
|
328
|
+
name: "agent_decide",
|
|
329
|
+
description: "Choose the next agent action and provide its arguments.",
|
|
330
|
+
parameters: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
...commonDecisionProperties(),
|
|
334
|
+
action: {
|
|
335
|
+
type: "string",
|
|
336
|
+
enum: [
|
|
337
|
+
"noop",
|
|
338
|
+
"create_session",
|
|
339
|
+
"send_session_instruction",
|
|
340
|
+
"message_agent",
|
|
341
|
+
"notify_user",
|
|
342
|
+
"mark_project_complete",
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
cwd: { type: "string" },
|
|
346
|
+
model: { type: "string" },
|
|
347
|
+
reasoningEffort: {
|
|
348
|
+
type: "string",
|
|
349
|
+
enum: ["none", "minimal", "low", "medium", "high", "xhigh"],
|
|
350
|
+
},
|
|
351
|
+
sandbox: {
|
|
352
|
+
type: "string",
|
|
353
|
+
enum: ["read-only", "workspace-write", "danger-full-access"],
|
|
354
|
+
},
|
|
355
|
+
approvalPolicy: {
|
|
356
|
+
type: "string",
|
|
357
|
+
enum: ["untrusted", "on-failure", "on-request", "never"],
|
|
358
|
+
},
|
|
359
|
+
instruction: { type: "string" },
|
|
360
|
+
sessionId: { type: "string" },
|
|
361
|
+
text: { type: "string" },
|
|
362
|
+
targetAgentId: { type: "string" },
|
|
363
|
+
severity: {
|
|
364
|
+
type: "string",
|
|
365
|
+
enum: ["info", "warning", "critical"],
|
|
366
|
+
},
|
|
367
|
+
channel: {
|
|
368
|
+
type: "string",
|
|
369
|
+
enum: ["inbox", "email"],
|
|
370
|
+
},
|
|
371
|
+
subject: { type: "string" },
|
|
372
|
+
body: { type: "string" },
|
|
373
|
+
},
|
|
374
|
+
required: ["action"],
|
|
375
|
+
additionalProperties: false,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|