clay-server 2.31.0 → 2.32.0-beta.1
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/lib/browser-mcp-server.js +32 -44
- package/lib/debate-mcp-server.js +14 -31
- package/lib/mcp-local.js +31 -1
- package/lib/project-connection.js +4 -2
- package/lib/project-filesystem.js +47 -1
- package/lib/project-http.js +75 -8
- package/lib/project-mcp.js +4 -0
- package/lib/project-sessions.js +88 -51
- package/lib/project-user-message.js +12 -7
- package/lib/project.js +204 -90
- package/lib/public/app.js +123 -448
- package/lib/public/codex-avatar.png +0 -0
- package/lib/public/css/debate.css +3 -2
- package/lib/public/css/filebrowser.css +91 -1
- package/lib/public/css/icon-strip.css +21 -5
- package/lib/public/css/input.css +181 -100
- package/lib/public/css/mates.css +43 -0
- package/lib/public/css/mention.css +48 -4
- package/lib/public/css/menus.css +1 -1
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/notifications-center.css +19 -0
- package/lib/public/index.html +46 -24
- package/lib/public/modules/app-connection.js +138 -37
- package/lib/public/modules/app-cursors.js +18 -17
- package/lib/public/modules/app-debate-ui.js +9 -9
- package/lib/public/modules/app-dm.js +170 -131
- package/lib/public/modules/app-favicon.js +28 -26
- package/lib/public/modules/app-header.js +79 -68
- package/lib/public/modules/app-home-hub.js +55 -47
- package/lib/public/modules/app-loop-ui.js +34 -18
- package/lib/public/modules/app-loop-wizard.js +6 -6
- package/lib/public/modules/app-messages.js +195 -152
- package/lib/public/modules/app-misc.js +23 -12
- package/lib/public/modules/app-notifications.js +91 -3
- package/lib/public/modules/app-panels.js +203 -49
- package/lib/public/modules/app-projects.js +159 -150
- package/lib/public/modules/app-rate-limit.js +5 -4
- package/lib/public/modules/app-rendering.js +149 -101
- package/lib/public/modules/app-skills-install.js +4 -4
- package/lib/public/modules/context-sources.js +12 -41
- package/lib/public/modules/dom-refs.js +21 -0
- package/lib/public/modules/filebrowser.js +173 -2
- package/lib/public/modules/input.js +86 -0
- package/lib/public/modules/mate-sidebar.js +38 -0
- package/lib/public/modules/mention.js +24 -6
- package/lib/public/modules/scheduler.js +1 -1
- package/lib/public/modules/sidebar-mates.js +66 -34
- package/lib/public/modules/sidebar-mobile.js +34 -30
- package/lib/public/modules/sidebar-projects.js +60 -57
- package/lib/public/modules/sidebar-sessions.js +75 -69
- package/lib/public/modules/sidebar.js +12 -20
- package/lib/public/modules/skills.js +8 -9
- package/lib/public/modules/sticky-notes.js +1 -2
- package/lib/public/modules/store.js +9 -2
- package/lib/public/modules/stt.js +4 -1
- package/lib/public/modules/tools.js +14 -9
- package/lib/sdk-bridge.js +511 -1113
- package/lib/sdk-message-processor.js +123 -134
- package/lib/sdk-worker.js +4 -0
- package/lib/server-dm.js +1 -0
- package/lib/server.js +86 -1
- package/lib/sessions.js +47 -36
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker.js +559 -0
- package/lib/yoke/adapters/claude.js +1418 -0
- package/lib/yoke/adapters/codex.js +968 -0
- package/lib/yoke/adapters/gemini.js +668 -0
- package/lib/yoke/codex-app-server.js +307 -0
- package/lib/yoke/index.js +199 -0
- package/lib/yoke/instructions.js +62 -0
- package/lib/yoke/interface.js +92 -0
- package/lib/yoke/mcp-bridge-server.js +294 -0
- package/lib/yoke/package.json +7 -0
- package/package.json +3 -1
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
// YOKE Codex Adapter
|
|
2
|
+
// -------------------
|
|
3
|
+
// Implements the YOKE interface using codex app-server protocol.
|
|
4
|
+
// Bidirectional JSON-RPC over stdin/stdout enables interactive approval flows.
|
|
5
|
+
|
|
6
|
+
var { CodexAppServer } = require("../codex-app-server");
|
|
7
|
+
|
|
8
|
+
// --- Event flattening ---
|
|
9
|
+
// Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
|
|
10
|
+
//
|
|
11
|
+
// App-server events use slash notation (item/started) and camelCase item types.
|
|
12
|
+
// We normalize to the same YOKE event format used by the rest of the system.
|
|
13
|
+
//
|
|
14
|
+
// Server -> Client notifications:
|
|
15
|
+
// thread/started -> { params: { thread } }
|
|
16
|
+
// turn/started -> { params: {} }
|
|
17
|
+
// turn/completed -> { params: { usage } }
|
|
18
|
+
// turn/failed -> { params: { error } }
|
|
19
|
+
// item/started -> { params: { item } }
|
|
20
|
+
// item/updated -> { params: { item } }
|
|
21
|
+
// item/completed -> { params: { item } }
|
|
22
|
+
// item/agentMessage/delta -> { params: { itemId, delta } }
|
|
23
|
+
//
|
|
24
|
+
// Item types (camelCase in app-server):
|
|
25
|
+
// agentMessage -> text response
|
|
26
|
+
// reasoning -> thinking
|
|
27
|
+
// commandExecution -> bash/shell
|
|
28
|
+
// fileChange -> file edits
|
|
29
|
+
// mcpToolCall -> MCP tool
|
|
30
|
+
// webSearch -> web search
|
|
31
|
+
// error -> error
|
|
32
|
+
|
|
33
|
+
var _uuidCounter = 0;
|
|
34
|
+
function generateUuid() {
|
|
35
|
+
var ts = Date.now().toString(36);
|
|
36
|
+
var cnt = (++_uuidCounter).toString(36);
|
|
37
|
+
var rnd = Math.random().toString(36).substring(2, 8);
|
|
38
|
+
return "codex-" + ts + "-" + cnt + "-" + rnd;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function flattenEvent(notification, state) {
|
|
42
|
+
var events = [];
|
|
43
|
+
var method = notification.method;
|
|
44
|
+
var params = notification.params || {};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if (method === "thread/started") {
|
|
48
|
+
state.threadId = params.thread ? params.thread.id : (params.threadId || null);
|
|
49
|
+
return events;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (method === "turn/started") {
|
|
53
|
+
state.turnStarted = true;
|
|
54
|
+
var userUuid = generateUuid();
|
|
55
|
+
events.push({ yokeType: "turn_start", uuid: userUuid, messageType: "user" });
|
|
56
|
+
return events;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (method === "turn/completed") {
|
|
60
|
+
var usage = params.usage || null;
|
|
61
|
+
var turnStatus = params.status || (params.turn && params.turn.status) || null;
|
|
62
|
+
state.lastUsage = usage;
|
|
63
|
+
// Emit interrupted status so UI shows "stopped" message
|
|
64
|
+
if (turnStatus === "interrupted" || state.aborted) {
|
|
65
|
+
events.push({ yokeType: "interrupted" });
|
|
66
|
+
}
|
|
67
|
+
var inputTokens = state.lastInputTokens || (usage ? (usage.input_tokens || 0) + (usage.cached_input_tokens || 0) : 0);
|
|
68
|
+
var outputTokens = usage ? (usage.output_tokens || 0) : 0;
|
|
69
|
+
var cachedTokens = usage ? (usage.cached_input_tokens || 0) : 0;
|
|
70
|
+
var hasTokenData = inputTokens > 0 || outputTokens > 0;
|
|
71
|
+
var resultModelUsage = {};
|
|
72
|
+
resultModelUsage[state.model] = { contextWindow: null };
|
|
73
|
+
var assistantUuid = generateUuid();
|
|
74
|
+
events.push({
|
|
75
|
+
yokeType: "result",
|
|
76
|
+
uuid: assistantUuid,
|
|
77
|
+
messageType: "assistant",
|
|
78
|
+
cost: null,
|
|
79
|
+
duration: null,
|
|
80
|
+
usage: hasTokenData ? {
|
|
81
|
+
input_tokens: inputTokens,
|
|
82
|
+
output_tokens: outputTokens,
|
|
83
|
+
cache_read_input_tokens: cachedTokens,
|
|
84
|
+
cache_creation_input_tokens: 0,
|
|
85
|
+
} : null,
|
|
86
|
+
modelUsage: resultModelUsage,
|
|
87
|
+
sessionId: state.threadId || null,
|
|
88
|
+
lastStreamInputTokens: state.lastInputTokens || null,
|
|
89
|
+
});
|
|
90
|
+
state.lastInputTokens = null;
|
|
91
|
+
return events;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (method === "turn/failed") {
|
|
95
|
+
events.push({
|
|
96
|
+
yokeType: "error",
|
|
97
|
+
text: params.error ? params.error.message : "Turn failed",
|
|
98
|
+
});
|
|
99
|
+
return events;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Rate limits from Codex account
|
|
103
|
+
if (method === "account/rateLimits/updated") {
|
|
104
|
+
var rl = params.rateLimits;
|
|
105
|
+
if (rl) {
|
|
106
|
+
var windows = [
|
|
107
|
+
{ key: "primary", type: "five_hour" },
|
|
108
|
+
{ key: "secondary", type: "seven_day" },
|
|
109
|
+
];
|
|
110
|
+
for (var wi = 0; wi < windows.length; wi++) {
|
|
111
|
+
var w = rl[windows[wi].key];
|
|
112
|
+
if (!w) continue;
|
|
113
|
+
var utilization = (w.usedPercent || 0) / 100;
|
|
114
|
+
var status = "allowed";
|
|
115
|
+
if (w.usedPercent >= 100) status = "rejected";
|
|
116
|
+
else if (w.usedPercent >= 80) status = "allowed_warning";
|
|
117
|
+
events.push({
|
|
118
|
+
yokeType: "rate_limit",
|
|
119
|
+
rateLimitInfo: {
|
|
120
|
+
status: status,
|
|
121
|
+
resetsAt: w.resetsAt || null,
|
|
122
|
+
rateLimitType: windows[wi].type,
|
|
123
|
+
utilization: utilization,
|
|
124
|
+
isUsingOverage: false,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return events;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Streaming text delta (app-server specific, not present in exec mode)
|
|
133
|
+
if (method === "item/agentMessage/delta") {
|
|
134
|
+
var deltaItemId = params.itemId || params.id;
|
|
135
|
+
if (deltaItemId && !state.textBlocks[deltaItemId]) {
|
|
136
|
+
state.textBlocks[deltaItemId] = true;
|
|
137
|
+
state.blockCounter++;
|
|
138
|
+
events.push({ yokeType: "text_start", blockId: "blk_" + state.blockCounter });
|
|
139
|
+
}
|
|
140
|
+
if (params.delta) {
|
|
141
|
+
events.push({
|
|
142
|
+
yokeType: "text_delta",
|
|
143
|
+
blockId: "blk_" + state.blockCounter,
|
|
144
|
+
text: params.delta,
|
|
145
|
+
});
|
|
146
|
+
// Track cumulative streamed length so item/completed doesn't re-send the full text
|
|
147
|
+
if (deltaItemId) {
|
|
148
|
+
state.textLengths[deltaItemId] = (state.textLengths[deltaItemId] || 0) + params.delta.length;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return events;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// serverRequest/resolved - confirmation that an approval was processed
|
|
155
|
+
if (method === "serverRequest/resolved") {
|
|
156
|
+
return events; // no-op, approval already handled
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Item events
|
|
160
|
+
if (method === "item/started" || method === "item/updated" || method === "item/completed") {
|
|
161
|
+
var item = params.item;
|
|
162
|
+
if (!item) return events;
|
|
163
|
+
|
|
164
|
+
var evtPhase = method.split("/")[1]; // "started", "updated", "completed"
|
|
165
|
+
|
|
166
|
+
// Agent message (text response)
|
|
167
|
+
if (item.type === "agentMessage" || item.type === "agent_message") {
|
|
168
|
+
if (!state.textBlocks[item.id]) {
|
|
169
|
+
state.textBlocks[item.id] = true;
|
|
170
|
+
state.blockCounter++;
|
|
171
|
+
events.push({ yokeType: "text_start", blockId: "blk_" + state.blockCounter });
|
|
172
|
+
}
|
|
173
|
+
if (item.text) {
|
|
174
|
+
var prevLen = state.textLengths[item.id] || 0;
|
|
175
|
+
if (item.text.length > prevLen) {
|
|
176
|
+
events.push({
|
|
177
|
+
yokeType: "text_delta",
|
|
178
|
+
blockId: "blk_" + state.blockCounter,
|
|
179
|
+
text: item.text.substring(prevLen),
|
|
180
|
+
});
|
|
181
|
+
state.textLengths[item.id] = item.text.length;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return events;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Reasoning (thinking)
|
|
188
|
+
if (item.type === "reasoning") {
|
|
189
|
+
if (!state.thinkingBlocks[item.id]) {
|
|
190
|
+
state.blockCounter++;
|
|
191
|
+
state.thinkingBlocks[item.id] = "blk_" + state.blockCounter;
|
|
192
|
+
events.push({ yokeType: "thinking_start", blockId: "blk_" + state.blockCounter });
|
|
193
|
+
}
|
|
194
|
+
if (item.text) {
|
|
195
|
+
var thinkBlockId = state.thinkingBlocks[item.id];
|
|
196
|
+
var prevThinkLen = state.thinkingLengths[item.id] || 0;
|
|
197
|
+
if (item.text.length > prevThinkLen) {
|
|
198
|
+
events.push({
|
|
199
|
+
yokeType: "thinking_delta",
|
|
200
|
+
blockId: thinkBlockId,
|
|
201
|
+
text: item.text.substring(prevThinkLen),
|
|
202
|
+
});
|
|
203
|
+
state.thinkingLengths[item.id] = item.text.length;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (evtPhase === "completed") {
|
|
207
|
+
events.push({ yokeType: "thinking_stop", blockId: state.thinkingBlocks[item.id] });
|
|
208
|
+
}
|
|
209
|
+
return events;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Command execution (bash/shell)
|
|
213
|
+
if (item.type === "commandExecution" || item.type === "command_execution") {
|
|
214
|
+
if (!state.toolBlocks[item.id]) {
|
|
215
|
+
state.blockCounter++;
|
|
216
|
+
state.toolBlocks[item.id] = "blk_" + state.blockCounter;
|
|
217
|
+
var toolBlockId = state.toolBlocks[item.id];
|
|
218
|
+
events.push({
|
|
219
|
+
yokeType: "tool_start",
|
|
220
|
+
blockId: toolBlockId,
|
|
221
|
+
toolId: item.id,
|
|
222
|
+
toolName: "Bash",
|
|
223
|
+
});
|
|
224
|
+
events.push({
|
|
225
|
+
yokeType: "tool_executing",
|
|
226
|
+
blockId: toolBlockId,
|
|
227
|
+
toolId: item.id,
|
|
228
|
+
toolName: "Bash",
|
|
229
|
+
input: { command: item.command },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (evtPhase === "completed") {
|
|
233
|
+
events.push({
|
|
234
|
+
yokeType: "tool_result",
|
|
235
|
+
toolId: item.id,
|
|
236
|
+
blockId: state.toolBlocks[item.id],
|
|
237
|
+
content: item.aggregated_output || item.output || "",
|
|
238
|
+
isError: item.status === "failed",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return events;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// File change
|
|
245
|
+
if (item.type === "fileChange" || item.type === "file_change") {
|
|
246
|
+
if (!state.toolBlocks[item.id]) {
|
|
247
|
+
state.blockCounter++;
|
|
248
|
+
state.toolBlocks[item.id] = "blk_" + state.blockCounter;
|
|
249
|
+
var fcBlockId = state.toolBlocks[item.id];
|
|
250
|
+
events.push({
|
|
251
|
+
yokeType: "tool_start",
|
|
252
|
+
blockId: fcBlockId,
|
|
253
|
+
toolId: item.id,
|
|
254
|
+
toolName: "Edit",
|
|
255
|
+
});
|
|
256
|
+
var changeDesc = (item.changes || []).map(function(c) { return c.kind + " " + c.path; }).join(", ");
|
|
257
|
+
events.push({
|
|
258
|
+
yokeType: "tool_executing",
|
|
259
|
+
blockId: fcBlockId,
|
|
260
|
+
toolId: item.id,
|
|
261
|
+
toolName: "Edit",
|
|
262
|
+
input: { changes: changeDesc },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (evtPhase === "completed") {
|
|
266
|
+
events.push({
|
|
267
|
+
yokeType: "tool_result",
|
|
268
|
+
toolId: item.id,
|
|
269
|
+
blockId: state.toolBlocks[item.id],
|
|
270
|
+
content: item.status === "completed" ? "Changes applied" : "Changes failed",
|
|
271
|
+
isError: item.status === "failed",
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return events;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// MCP tool call
|
|
278
|
+
if (item.type === "mcpToolCall" || item.type === "mcp_tool_call") {
|
|
279
|
+
console.log("[yoke/codex] MCP event:", method, "tool=" + (item.tool || "?"), "status=" + (item.status || "?"), "error=" + (item.error ? JSON.stringify(item.error) : "none"));
|
|
280
|
+
if (!state.toolBlocks[item.id]) {
|
|
281
|
+
state.blockCounter++;
|
|
282
|
+
state.toolBlocks[item.id] = "blk_" + state.blockCounter;
|
|
283
|
+
var mcpBlockId = state.toolBlocks[item.id];
|
|
284
|
+
events.push({
|
|
285
|
+
yokeType: "tool_start",
|
|
286
|
+
blockId: mcpBlockId,
|
|
287
|
+
toolId: item.id,
|
|
288
|
+
toolName: item.tool || "mcp_tool",
|
|
289
|
+
});
|
|
290
|
+
events.push({
|
|
291
|
+
yokeType: "tool_executing",
|
|
292
|
+
blockId: mcpBlockId,
|
|
293
|
+
toolId: item.id,
|
|
294
|
+
toolName: item.tool || "mcp_tool",
|
|
295
|
+
input: item.arguments || {},
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (evtPhase === "completed") {
|
|
299
|
+
var resultText = "";
|
|
300
|
+
if (item.result && item.result.content) {
|
|
301
|
+
resultText = item.result.content.map(function(c) { return c.text || ""; }).join("\n");
|
|
302
|
+
}
|
|
303
|
+
if (item.error) resultText = item.error.message;
|
|
304
|
+
events.push({
|
|
305
|
+
yokeType: "tool_result",
|
|
306
|
+
toolId: item.id,
|
|
307
|
+
blockId: state.toolBlocks[item.id],
|
|
308
|
+
content: resultText,
|
|
309
|
+
isError: !!item.error,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return events;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Web search
|
|
316
|
+
if (item.type === "webSearch" || item.type === "web_search") {
|
|
317
|
+
if (!state.toolBlocks[item.id]) {
|
|
318
|
+
state.blockCounter++;
|
|
319
|
+
state.toolBlocks[item.id] = "blk_" + state.blockCounter;
|
|
320
|
+
events.push({
|
|
321
|
+
yokeType: "tool_start",
|
|
322
|
+
blockId: state.toolBlocks[item.id],
|
|
323
|
+
toolId: item.id,
|
|
324
|
+
toolName: "WebSearch",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return events;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Error item
|
|
331
|
+
if (item.type === "error") {
|
|
332
|
+
events.push({
|
|
333
|
+
yokeType: "error",
|
|
334
|
+
text: item.message || "Unknown error",
|
|
335
|
+
});
|
|
336
|
+
return events;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Token usage update - track input tokens for context bar
|
|
341
|
+
if (method === "thread/tokenUsage/updated") {
|
|
342
|
+
var tu = params.tokenUsage;
|
|
343
|
+
if (tu && tu.total) {
|
|
344
|
+
state.lastInputTokens = tu.total.inputTokens || 0;
|
|
345
|
+
}
|
|
346
|
+
return events;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Unknown event type - pass through
|
|
350
|
+
console.log("[yoke/codex] UNHANDLED event:", method, JSON.stringify(params).substring(0, 200));
|
|
351
|
+
events.push({
|
|
352
|
+
yokeType: "runtime_specific",
|
|
353
|
+
vendor: "codex",
|
|
354
|
+
eventType: method,
|
|
355
|
+
raw: params,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return events;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// --- QueryHandle ---
|
|
362
|
+
|
|
363
|
+
function createCodexQueryHandle(appServer, queryOpts) {
|
|
364
|
+
var abortController = queryOpts.abortController;
|
|
365
|
+
var systemPrompt = queryOpts.systemPrompt || "";
|
|
366
|
+
var canUseTool = queryOpts.canUseTool || null;
|
|
367
|
+
|
|
368
|
+
// Check if the query was cancelled (either via handle.abort() or direct signal abort)
|
|
369
|
+
function isCancelled() {
|
|
370
|
+
return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
var state = {
|
|
374
|
+
blockCounter: 0,
|
|
375
|
+
threadId: null,
|
|
376
|
+
turnStarted: false,
|
|
377
|
+
lastUsage: null,
|
|
378
|
+
lastInputTokens: null, // from thread/tokenUsage/updated
|
|
379
|
+
done: false,
|
|
380
|
+
aborted: false,
|
|
381
|
+
loopStarted: false,
|
|
382
|
+
model: queryOpts.model || "gpt-5.4",
|
|
383
|
+
// Track incremental text deltas
|
|
384
|
+
textBlocks: {}, // itemId -> true (text_start sent)
|
|
385
|
+
textLengths: {}, // itemId -> last sent length
|
|
386
|
+
thinkingBlocks: {}, // itemId -> blockId
|
|
387
|
+
thinkingLengths: {}, // itemId -> last sent length
|
|
388
|
+
toolBlocks: {}, // itemId -> blockId (for tool_start dedup)
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Internal event buffer for async iterator
|
|
392
|
+
var eventBuffer = [];
|
|
393
|
+
var eventWaiting = null;
|
|
394
|
+
var iteratorDone = false;
|
|
395
|
+
|
|
396
|
+
function pushEvent(evt) {
|
|
397
|
+
if (iteratorDone) return;
|
|
398
|
+
if (eventWaiting) {
|
|
399
|
+
var resolve = eventWaiting;
|
|
400
|
+
eventWaiting = null;
|
|
401
|
+
resolve({ value: evt, done: false });
|
|
402
|
+
} else {
|
|
403
|
+
eventBuffer.push(evt);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function endIterator() {
|
|
408
|
+
iteratorDone = true;
|
|
409
|
+
if (eventWaiting) {
|
|
410
|
+
var resolve = eventWaiting;
|
|
411
|
+
eventWaiting = null;
|
|
412
|
+
resolve({ value: undefined, done: true });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Message queue for multi-turn
|
|
417
|
+
var messageQueue = [];
|
|
418
|
+
var messageWaiting = null;
|
|
419
|
+
var messageQueueEnded = false;
|
|
420
|
+
|
|
421
|
+
function pushMessageToQueue(msg) {
|
|
422
|
+
if (messageQueueEnded) return;
|
|
423
|
+
if (messageWaiting) {
|
|
424
|
+
var resolve = messageWaiting;
|
|
425
|
+
messageWaiting = null;
|
|
426
|
+
resolve(msg);
|
|
427
|
+
} else {
|
|
428
|
+
messageQueue.push(msg);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function waitForMessage() {
|
|
433
|
+
if (messageQueue.length > 0) return Promise.resolve(messageQueue.shift());
|
|
434
|
+
if (messageQueueEnded) return Promise.resolve(null);
|
|
435
|
+
return new Promise(function(resolve) { messageWaiting = resolve; });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Track whether this turn is still active (waiting for turn/completed or turn/failed)
|
|
439
|
+
var turnResolve = null;
|
|
440
|
+
|
|
441
|
+
// --- App-server event handler ---
|
|
442
|
+
function handleServerEvent(msg) {
|
|
443
|
+
var method = msg.method;
|
|
444
|
+
var params = msg.params || {};
|
|
445
|
+
|
|
446
|
+
// Ignore events from other threads (app-server is shared across sessions)
|
|
447
|
+
if (params.threadId && state.threadId && params.threadId !== state.threadId) return;
|
|
448
|
+
|
|
449
|
+
// After abort, only let turn-ending events through
|
|
450
|
+
if (isCancelled() && method !== "turn/completed" && method !== "turn/failed" && method !== "serverRequest/resolved" && method !== "thread/status/changed") return;
|
|
451
|
+
|
|
452
|
+
// --- Approval helper ---
|
|
453
|
+
// canUseTool returns { behavior: "allow"|"deny", updatedInput } or truthy/falsy
|
|
454
|
+
function isApproved(decision) {
|
|
455
|
+
if (!decision) return false;
|
|
456
|
+
if (decision === true) return true;
|
|
457
|
+
if (decision.behavior === "allow") return true;
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Command approval request
|
|
462
|
+
if (method === "item/commandExecution/requestApproval") {
|
|
463
|
+
var cmdParams = msg.params || {};
|
|
464
|
+
if (canUseTool) {
|
|
465
|
+
canUseTool("Bash", { command: cmdParams.command }, {}).then(function(decision) {
|
|
466
|
+
appServer.respond(msg.id, isApproved(decision) ? "accept" : "decline");
|
|
467
|
+
}).catch(function(err) {
|
|
468
|
+
console.error("[yoke/codex] canUseTool error:", err.message);
|
|
469
|
+
appServer.respond(msg.id, "decline");
|
|
470
|
+
});
|
|
471
|
+
} else {
|
|
472
|
+
appServer.respond(msg.id, "accept");
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// File change approval request
|
|
478
|
+
if (method === "item/fileChange/requestApproval") {
|
|
479
|
+
var fcParams = msg.params || {};
|
|
480
|
+
if (canUseTool) {
|
|
481
|
+
var changeInfo = (fcParams.changes || []).map(function(c) { return c.kind + " " + c.path; }).join(", ");
|
|
482
|
+
canUseTool("Edit", { changes: changeInfo, path: fcParams.path }, {}).then(function(decision) {
|
|
483
|
+
appServer.respond(msg.id, isApproved(decision) ? "accept" : "decline");
|
|
484
|
+
}).catch(function(err) {
|
|
485
|
+
console.error("[yoke/codex] canUseTool error:", err.message);
|
|
486
|
+
appServer.respond(msg.id, "decline");
|
|
487
|
+
});
|
|
488
|
+
} else {
|
|
489
|
+
appServer.respond(msg.id, "accept");
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// MCP tool approval / elicitation (app-server uses mcpServer/elicitation/request)
|
|
495
|
+
if (method === "item/tool/requestUserInput" || method === "mcpServer/elicitation/request") {
|
|
496
|
+
var mcpParams = msg.params || {};
|
|
497
|
+
var mcpMeta = mcpParams._meta || {};
|
|
498
|
+
console.log("[yoke/codex] MCP approval request:", (mcpMeta.tool || "?"), "server=" + (mcpParams.serverName || "?"));
|
|
499
|
+
if (canUseTool) {
|
|
500
|
+
canUseTool("mcp__" + (mcpParams.serverName || "unknown") + "__" + (mcpMeta.tool || "call"), mcpParams, {}).then(function(decision) {
|
|
501
|
+
appServer.respond(msg.id, { action: isApproved(decision) ? "accept" : "decline" });
|
|
502
|
+
}).catch(function(err) {
|
|
503
|
+
console.error("[yoke/codex] MCP canUseTool error:", err.message);
|
|
504
|
+
appServer.respond(msg.id, { action: "decline" });
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
appServer.respond(msg.id, { action: "accept" });
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Regular events: flatten and push to iterator
|
|
513
|
+
var yokeEvents = flattenEvent(msg, state);
|
|
514
|
+
for (var i = 0; i < yokeEvents.length; i++) {
|
|
515
|
+
pushEvent(yokeEvents[i]);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Resolve turn promise when turn ends
|
|
519
|
+
if (method === "turn/completed" || method === "turn/failed") {
|
|
520
|
+
if (turnResolve) {
|
|
521
|
+
var resolve = turnResolve;
|
|
522
|
+
turnResolve = null;
|
|
523
|
+
resolve();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// --- Main query loop ---
|
|
529
|
+
async function runQueryLoop(initialMessage) {
|
|
530
|
+
// Prepend system prompt (project instructions from YOKE layer) to first message
|
|
531
|
+
var currentMessage = systemPrompt
|
|
532
|
+
? systemPrompt + "\n\n" + initialMessage
|
|
533
|
+
: initialMessage;
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
// Set event handler on app-server
|
|
537
|
+
appServer.eventHandler = handleServerEvent;
|
|
538
|
+
|
|
539
|
+
// Start or resume thread
|
|
540
|
+
var threadParams = {
|
|
541
|
+
model: queryOpts.model || "gpt-5.4",
|
|
542
|
+
sandboxMode: queryOpts.sandboxMode || "workspace-write",
|
|
543
|
+
approvalPolicy: queryOpts.approvalPolicy || "on-failure",
|
|
544
|
+
workingDirectory: queryOpts.cwd,
|
|
545
|
+
skipGitRepoCheck: true,
|
|
546
|
+
};
|
|
547
|
+
if (queryOpts.modelReasoningEffort) {
|
|
548
|
+
threadParams.modelReasoningEffort = queryOpts.modelReasoningEffort;
|
|
549
|
+
}
|
|
550
|
+
if (queryOpts.webSearchMode) {
|
|
551
|
+
threadParams.webSearchMode = queryOpts.webSearchMode;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
var threadResult;
|
|
555
|
+
if (queryOpts.resumeSessionId) {
|
|
556
|
+
threadResult = await appServer.send("thread/resume", {
|
|
557
|
+
threadId: queryOpts.resumeSessionId,
|
|
558
|
+
model: threadParams.model,
|
|
559
|
+
}, 60000);
|
|
560
|
+
} else {
|
|
561
|
+
threadResult = await appServer.send("thread/start", threadParams, 60000);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (threadResult && threadResult.thread) {
|
|
565
|
+
state.threadId = threadResult.thread.id;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
while (!isCancelled()) {
|
|
569
|
+
// Reset per-turn state
|
|
570
|
+
state.turnStarted = false;
|
|
571
|
+
state.textBlocks = {};
|
|
572
|
+
state.textLengths = {};
|
|
573
|
+
state.thinkingBlocks = {};
|
|
574
|
+
state.thinkingLengths = {};
|
|
575
|
+
state.toolBlocks = {};
|
|
576
|
+
|
|
577
|
+
// Start turn
|
|
578
|
+
var turnPromise = new Promise(function(resolve) { turnResolve = resolve; });
|
|
579
|
+
|
|
580
|
+
var input;
|
|
581
|
+
if (typeof currentMessage === "string") {
|
|
582
|
+
input = [{ type: "text", text: currentMessage }];
|
|
583
|
+
} else {
|
|
584
|
+
input = currentMessage;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
await appServer.send("turn/start", {
|
|
588
|
+
threadId: state.threadId,
|
|
589
|
+
input: input,
|
|
590
|
+
}, 60000);
|
|
591
|
+
|
|
592
|
+
// Wait for turn to complete
|
|
593
|
+
await turnPromise;
|
|
594
|
+
|
|
595
|
+
if (isCancelled()) break;
|
|
596
|
+
|
|
597
|
+
// Wait for next message (multi-turn)
|
|
598
|
+
var nextMsg = await waitForMessage();
|
|
599
|
+
if (nextMsg === null) break;
|
|
600
|
+
currentMessage = nextMsg;
|
|
601
|
+
}
|
|
602
|
+
} catch (e) {
|
|
603
|
+
// Suppress AbortError when the user stopped the query.
|
|
604
|
+
if (!isCancelled() && e.name !== "AbortError") {
|
|
605
|
+
console.error("[yoke/codex] runQueryLoop error:", e.message || e);
|
|
606
|
+
console.error("[yoke/codex] stack:", e.stack || "(no stack)");
|
|
607
|
+
pushEvent({
|
|
608
|
+
yokeType: "error",
|
|
609
|
+
text: e.message || String(e),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
state.done = true;
|
|
615
|
+
endIterator();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
var handle = {
|
|
619
|
+
[Symbol.asyncIterator]: function() {
|
|
620
|
+
return {
|
|
621
|
+
next: function() {
|
|
622
|
+
if (eventBuffer.length > 0) {
|
|
623
|
+
return Promise.resolve({ value: eventBuffer.shift(), done: false });
|
|
624
|
+
}
|
|
625
|
+
if (iteratorDone) {
|
|
626
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
627
|
+
}
|
|
628
|
+
return new Promise(function(resolve) { eventWaiting = resolve; });
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
pushMessage: function(text, images) {
|
|
634
|
+
var input;
|
|
635
|
+
if (images && images.length > 0) {
|
|
636
|
+
input = [];
|
|
637
|
+
for (var i = 0; i < images.length; i++) {
|
|
638
|
+
// Codex supports local_image with path, not base64
|
|
639
|
+
// For now, text-only
|
|
640
|
+
}
|
|
641
|
+
input.push({ type: "text", text: text || "" });
|
|
642
|
+
} else {
|
|
643
|
+
input = text || "";
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (!state.loopStarted) {
|
|
647
|
+
state.loopStarted = true;
|
|
648
|
+
runQueryLoop(input);
|
|
649
|
+
} else {
|
|
650
|
+
pushMessageToQueue(input);
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
setModel: function(model) {
|
|
655
|
+
// Model is set at thread creation. Cannot change mid-thread.
|
|
656
|
+
return Promise.resolve();
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
setEffort: function(effort) {
|
|
660
|
+
// Stored for next thread
|
|
661
|
+
return Promise.resolve();
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
setToolPolicy: function(policy) {
|
|
665
|
+
// Codex uses approvalPolicy at thread creation
|
|
666
|
+
return Promise.resolve();
|
|
667
|
+
},
|
|
668
|
+
|
|
669
|
+
stopTask: function(taskId) {
|
|
670
|
+
// Codex doesn't expose sub-task stopping
|
|
671
|
+
return Promise.resolve();
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
getContextUsage: function() {
|
|
675
|
+
return Promise.resolve(state.lastUsage);
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
abort: function() {
|
|
679
|
+
console.log("[yoke/codex] handle.abort() called, threadId=" + state.threadId + " already aborted=" + state.aborted);
|
|
680
|
+
state.aborted = true;
|
|
681
|
+
// Send turn/interrupt to stop the server-side turn
|
|
682
|
+
if (state.threadId && appServer.started) {
|
|
683
|
+
appServer.send("turn/interrupt", { threadId: state.threadId }, 5000).catch(function() {});
|
|
684
|
+
}
|
|
685
|
+
// End iterator immediately. sdk-bridge's post-loop code checks
|
|
686
|
+
// session.taskStopRequested and sends the interrupted message + done.
|
|
687
|
+
// This matches Claude's abort pattern.
|
|
688
|
+
if (turnResolve) {
|
|
689
|
+
var resolve = turnResolve;
|
|
690
|
+
turnResolve = null;
|
|
691
|
+
resolve();
|
|
692
|
+
}
|
|
693
|
+
endIterator();
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
close: function() {
|
|
697
|
+
messageQueueEnded = true;
|
|
698
|
+
if (messageWaiting) {
|
|
699
|
+
var resolve = messageWaiting;
|
|
700
|
+
messageWaiting = null;
|
|
701
|
+
resolve(null);
|
|
702
|
+
}
|
|
703
|
+
endIterator();
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
endInput: function() {
|
|
707
|
+
messageQueueEnded = true;
|
|
708
|
+
if (messageWaiting) {
|
|
709
|
+
var resolve = messageWaiting;
|
|
710
|
+
messageWaiting = null;
|
|
711
|
+
resolve(null);
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// Listen for external abort (sdk-bridge's stopTask calls session.abortController.abort())
|
|
717
|
+
if (abortController && abortController.signal) {
|
|
718
|
+
abortController.signal.addEventListener("abort", function() {
|
|
719
|
+
if (!state.aborted) handle.abort();
|
|
720
|
+
}, { once: true });
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return handle;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// --- Adapter factory ---
|
|
727
|
+
|
|
728
|
+
function createCodexAdapter(opts) {
|
|
729
|
+
var _cwd = (opts && opts.cwd) || process.cwd();
|
|
730
|
+
var _cachedModels = [];
|
|
731
|
+
var _appServer = null;
|
|
732
|
+
var _initPromise = null;
|
|
733
|
+
var _initOpts = null; // stored for query-time access
|
|
734
|
+
|
|
735
|
+
var adapter = {
|
|
736
|
+
vendor: "codex",
|
|
737
|
+
|
|
738
|
+
init: function(initOpts) {
|
|
739
|
+
// Already initialized - return cached result
|
|
740
|
+
if (_appServer && _appServer.started && _cachedModels.length > 0) {
|
|
741
|
+
return Promise.resolve({
|
|
742
|
+
models: _cachedModels,
|
|
743
|
+
defaultModel: "gpt-5.4",
|
|
744
|
+
skills: [],
|
|
745
|
+
slashCommands: [],
|
|
746
|
+
fastModeState: null,
|
|
747
|
+
capabilities: {
|
|
748
|
+
thinking: true,
|
|
749
|
+
betas: false,
|
|
750
|
+
rewind: false,
|
|
751
|
+
sessionResume: true,
|
|
752
|
+
promptSuggestions: false,
|
|
753
|
+
elicitation: true,
|
|
754
|
+
fileCheckpointing: false,
|
|
755
|
+
contextCompacting: false,
|
|
756
|
+
toolPolicy: ["ask", "allow-all"],
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Deduplicate concurrent init calls
|
|
762
|
+
if (_initPromise) return _initPromise;
|
|
763
|
+
|
|
764
|
+
_initPromise = (async function() {
|
|
765
|
+
_initOpts = initOpts;
|
|
766
|
+
|
|
767
|
+
var serverOpts = { cwd: _cwd };
|
|
768
|
+
|
|
769
|
+
// Extract adapter options
|
|
770
|
+
if (initOpts && initOpts.adapterOptions && initOpts.adapterOptions.CODEX) {
|
|
771
|
+
var co = initOpts.adapterOptions.CODEX;
|
|
772
|
+
if (co.apiKey) serverOpts.env = Object.assign({}, serverOpts.env || {}, { OPENAI_API_KEY: co.apiKey });
|
|
773
|
+
if (co.baseUrl) serverOpts.env = Object.assign({}, serverOpts.env || {}, { OPENAI_BASE_URL: co.baseUrl });
|
|
774
|
+
if (co.config) serverOpts.config = co.config;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Track 1: Read local MCP server definitions from ~/.clay/mcp.json
|
|
778
|
+
// and inject into Codex config so Codex manages them natively.
|
|
779
|
+
var mcpServerConfig = {};
|
|
780
|
+
try {
|
|
781
|
+
var mcpLocal = require("../../mcp-local");
|
|
782
|
+
var localMcpServers = mcpLocal.readMergedServers();
|
|
783
|
+
var mcpNames = Object.keys(localMcpServers);
|
|
784
|
+
for (var mi = 0; mi < mcpNames.length; mi++) {
|
|
785
|
+
var ms = localMcpServers[mcpNames[mi]];
|
|
786
|
+
if (ms.command) {
|
|
787
|
+
mcpServerConfig[mcpNames[mi]] = { command: ms.command, args: ms.args || [] };
|
|
788
|
+
if (ms.env && Object.keys(ms.env).length > 0) {
|
|
789
|
+
mcpServerConfig[mcpNames[mi]].env = ms.env;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
} catch (e) {
|
|
794
|
+
console.error("[codex] Failed to read local MCP config:", e.message);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Track 2: Add clay-tools bridge server for in-app + remote MCP tools.
|
|
798
|
+
var bridgePath = require("path").join(__dirname, "..", "mcp-bridge-server.js");
|
|
799
|
+
var clayPort = (initOpts && initOpts.clayPort) || process.env.CLAY_PORT || 2633;
|
|
800
|
+
var clayTls = (initOpts && initOpts.clayTls) || false;
|
|
801
|
+
var clayAuthToken = (initOpts && initOpts.clayAuthToken) || "";
|
|
802
|
+
var claySlug = (initOpts && initOpts.slug) || "";
|
|
803
|
+
try {
|
|
804
|
+
if (require("fs").existsSync(bridgePath)) {
|
|
805
|
+
var bridgeArgs = [bridgePath, "--port", String(clayPort), "--slug", claySlug];
|
|
806
|
+
if (clayTls) bridgeArgs.push("--tls");
|
|
807
|
+
var bridgeEnv = {};
|
|
808
|
+
if (clayAuthToken) bridgeEnv.CLAY_AUTH_TOKEN = clayAuthToken;
|
|
809
|
+
mcpServerConfig["clay-tools"] = {
|
|
810
|
+
command: process.execPath,
|
|
811
|
+
args: bridgeArgs,
|
|
812
|
+
env: Object.keys(bridgeEnv).length > 0 ? bridgeEnv : undefined,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
} catch (e) {
|
|
816
|
+
console.error("[codex] Failed to configure clay-tools bridge:", e.message);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (Object.keys(mcpServerConfig).length > 0) {
|
|
820
|
+
serverOpts.config = Object.assign({}, serverOpts.config || {}, {
|
|
821
|
+
mcp_servers: mcpServerConfig,
|
|
822
|
+
});
|
|
823
|
+
console.log("[codex] MCP servers configured:", Object.keys(mcpServerConfig).join(", "));
|
|
824
|
+
try {
|
|
825
|
+
var names = Object.keys(mcpServerConfig);
|
|
826
|
+
for (var di = 0; di < names.length; di++) {
|
|
827
|
+
var sc = mcpServerConfig[names[di]];
|
|
828
|
+
console.log("[codex] MCP server '" + names[di] + "': command=" + sc.command + " args=" + JSON.stringify(sc.args));
|
|
829
|
+
}
|
|
830
|
+
} catch (e) {}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Spawn and initialize app-server
|
|
834
|
+
_appServer = new CodexAppServer(null, serverOpts);
|
|
835
|
+
await _appServer.start();
|
|
836
|
+
|
|
837
|
+
await _appServer.send("initialize", {
|
|
838
|
+
clientInfo: { name: "clay", title: "Clay", version: "1.0.0" },
|
|
839
|
+
capabilities: { experimentalApi: true },
|
|
840
|
+
});
|
|
841
|
+
_appServer.notify("initialized", {});
|
|
842
|
+
|
|
843
|
+
console.log("[codex] App-server initialized, models: gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.2");
|
|
844
|
+
|
|
845
|
+
_cachedModels = [
|
|
846
|
+
"gpt-5.4",
|
|
847
|
+
"gpt-5.4-mini",
|
|
848
|
+
"gpt-5.3-codex",
|
|
849
|
+
"gpt-5.3-codex-spark",
|
|
850
|
+
"gpt-5.2",
|
|
851
|
+
];
|
|
852
|
+
|
|
853
|
+
_initPromise = null;
|
|
854
|
+
|
|
855
|
+
return {
|
|
856
|
+
models: _cachedModels,
|
|
857
|
+
defaultModel: "gpt-5.4",
|
|
858
|
+
skills: [],
|
|
859
|
+
slashCommands: [],
|
|
860
|
+
fastModeState: null,
|
|
861
|
+
capabilities: {
|
|
862
|
+
thinking: true,
|
|
863
|
+
betas: false,
|
|
864
|
+
rewind: false,
|
|
865
|
+
sessionResume: true,
|
|
866
|
+
promptSuggestions: false,
|
|
867
|
+
elicitation: true,
|
|
868
|
+
fileCheckpointing: false,
|
|
869
|
+
contextCompacting: false,
|
|
870
|
+
toolPolicy: ["ask", "allow-all"],
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
})();
|
|
874
|
+
|
|
875
|
+
return _initPromise;
|
|
876
|
+
},
|
|
877
|
+
|
|
878
|
+
supportedModels: function() {
|
|
879
|
+
return Promise.resolve(_cachedModels.slice());
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
createToolServer: function(def) {
|
|
883
|
+
// Codex handles tools internally (file ops, bash, etc.)
|
|
884
|
+
// MCP tools are configured via Codex config, not SDK.
|
|
885
|
+
console.log("[yoke/codex] createToolServer skipped: Codex handles tools internally");
|
|
886
|
+
return null;
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
createQuery: async function(queryOpts) {
|
|
890
|
+
if (!_appServer || !_appServer.started) {
|
|
891
|
+
throw new Error("[yoke/codex] Adapter not initialized. Call init() first.");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
var model = queryOpts.model || "gpt-5.4";
|
|
895
|
+
var ac = queryOpts.abortController || new AbortController();
|
|
896
|
+
|
|
897
|
+
// Map YOKE options to Codex thread options
|
|
898
|
+
var codexOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.CODEX) || {};
|
|
899
|
+
|
|
900
|
+
var handleOpts = {
|
|
901
|
+
model: model,
|
|
902
|
+
cwd: queryOpts.cwd || _cwd,
|
|
903
|
+
systemPrompt: queryOpts.systemPrompt || "",
|
|
904
|
+
abortController: ac,
|
|
905
|
+
canUseTool: queryOpts.canUseTool || null,
|
|
906
|
+
resumeSessionId: queryOpts.resumeSessionId || null,
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// Reasoning effort
|
|
910
|
+
if (queryOpts.effort || codexOpts.modelReasoningEffort) {
|
|
911
|
+
handleOpts.modelReasoningEffort = codexOpts.modelReasoningEffort || queryOpts.effort || "medium";
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Tool policy -> approval mode
|
|
915
|
+
if (queryOpts.toolPolicy === "allow-all") {
|
|
916
|
+
handleOpts.approvalPolicy = "never";
|
|
917
|
+
} else {
|
|
918
|
+
handleOpts.approvalPolicy = codexOpts.approvalPolicy || "on-failure";
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Sandbox mode
|
|
922
|
+
handleOpts.sandboxMode = codexOpts.sandboxMode || "workspace-write";
|
|
923
|
+
|
|
924
|
+
// Web search
|
|
925
|
+
if (codexOpts.webSearchMode && codexOpts.webSearchMode !== "disabled") {
|
|
926
|
+
handleOpts.webSearchMode = codexOpts.webSearchMode;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
console.log("[yoke/codex] createQuery: model=" + model + " approval=" + handleOpts.approvalPolicy + " sandbox=" + handleOpts.sandboxMode);
|
|
930
|
+
|
|
931
|
+
var handle = createCodexQueryHandle(_appServer, handleOpts);
|
|
932
|
+
return handle;
|
|
933
|
+
},
|
|
934
|
+
|
|
935
|
+
// Codex has session persistence via thread IDs
|
|
936
|
+
getSessionInfo: function(sessionId) {
|
|
937
|
+
return Promise.resolve(null);
|
|
938
|
+
},
|
|
939
|
+
listSessions: function() { return Promise.resolve([]); },
|
|
940
|
+
renameSession: function() { return Promise.resolve(); },
|
|
941
|
+
forkSession: function(threadId, opts) {
|
|
942
|
+
if (!_appServer || !_appServer.started) return Promise.resolve(null);
|
|
943
|
+
return _appServer.send("thread/fork", { threadId: threadId }, 30000).then(function(result) {
|
|
944
|
+
var newThreadId = (result && result.thread) ? result.thread.id : null;
|
|
945
|
+
if (!newThreadId) throw new Error("thread/fork did not return a new thread id");
|
|
946
|
+
return { sessionId: newThreadId };
|
|
947
|
+
});
|
|
948
|
+
},
|
|
949
|
+
rollbackThread: function(threadId, numTurns) {
|
|
950
|
+
if (!_appServer || !_appServer.started) return Promise.resolve(null);
|
|
951
|
+
return _appServer.send("thread/rollback", { threadId: threadId, numTurns: numTurns }, 30000);
|
|
952
|
+
},
|
|
953
|
+
|
|
954
|
+
// Shutdown the app-server process
|
|
955
|
+
shutdown: function() {
|
|
956
|
+
if (_appServer) {
|
|
957
|
+
_appServer.stop();
|
|
958
|
+
_appServer = null;
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
return adapter;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
module.exports = {
|
|
967
|
+
createCodexAdapter: createCodexAdapter,
|
|
968
|
+
};
|