replicas-engine 0.1.6 → 0.1.8
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/routes/codex.js +26 -23
- package/dist/services/codex-manager.js +53 -10
- package/package.json +1 -1
package/dist/routes/codex.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { stream } from 'hono/streaming';
|
|
3
2
|
import { CodexManager } from '../services/codex-manager.js';
|
|
4
3
|
const codex = new Hono();
|
|
5
4
|
const codexManager = new CodexManager();
|
|
6
5
|
/**
|
|
7
6
|
* POST /codex/send
|
|
8
|
-
* send a message to Codex
|
|
7
|
+
* send a message to Codex (non-blocking, writes to JSONL automatically)
|
|
9
8
|
*/
|
|
10
9
|
codex.post('/send', async (c) => {
|
|
11
10
|
try {
|
|
@@ -14,27 +13,10 @@ codex.post('/send', async (c) => {
|
|
|
14
13
|
if (!message || typeof message !== 'string') {
|
|
15
14
|
return c.json({ error: 'Message is required and must be a string' }, 400);
|
|
16
15
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
stream.onAbort(() => {
|
|
22
|
-
console.log('Client aborted SSE connection');
|
|
23
|
-
});
|
|
24
|
-
try {
|
|
25
|
-
for await (const event of codexManager.sendMessage(message)) {
|
|
26
|
-
const sseData = `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
|
|
27
|
-
await stream.write(sseData);
|
|
28
|
-
}
|
|
29
|
-
await stream.write('event: done\ndata: {}\n\n');
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
console.error('Error during Codex streaming:', error);
|
|
33
|
-
const errorData = `event: error\ndata: ${JSON.stringify({
|
|
34
|
-
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
35
|
-
})}\n\n`;
|
|
36
|
-
await stream.write(errorData);
|
|
37
|
-
}
|
|
16
|
+
await codexManager.sendMessage(message);
|
|
17
|
+
return c.json({
|
|
18
|
+
success: true,
|
|
19
|
+
message: 'Message sent successfully',
|
|
38
20
|
});
|
|
39
21
|
}
|
|
40
22
|
catch (error) {
|
|
@@ -69,6 +51,27 @@ codex.get('/history', async (c) => {
|
|
|
69
51
|
}, 500);
|
|
70
52
|
}
|
|
71
53
|
});
|
|
54
|
+
/**
|
|
55
|
+
* GET /codex/updates
|
|
56
|
+
* get new events since a given timestamp (for polling)
|
|
57
|
+
*/
|
|
58
|
+
codex.get('/updates', async (c) => {
|
|
59
|
+
try {
|
|
60
|
+
const since = c.req.query('since') || '';
|
|
61
|
+
if (!since) {
|
|
62
|
+
return c.json({ error: 'Missing "since" query parameter' }, 400);
|
|
63
|
+
}
|
|
64
|
+
const updates = await codexManager.getUpdates(since);
|
|
65
|
+
return c.json(updates);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('Error in /codex/updates:', error);
|
|
69
|
+
return c.json({
|
|
70
|
+
error: 'Failed to retrieve updates',
|
|
71
|
+
details: error instanceof Error ? error.message : 'Unknown error',
|
|
72
|
+
}, 500);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
72
75
|
/**
|
|
73
76
|
* GET /codex/status
|
|
74
77
|
* get current thread status and information
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Codex, Thread } from '@openai/codex-sdk';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
2
3
|
import { findSessionFile, readJSONL } from '../utils/jsonl-reader.js';
|
|
3
4
|
export class CodexManager {
|
|
4
5
|
codex;
|
|
@@ -22,7 +23,7 @@ export class CodexManager {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
async
|
|
26
|
+
async sendMessage(message) {
|
|
26
27
|
if (!this.currentThread) {
|
|
27
28
|
if (this.currentThreadId) {
|
|
28
29
|
console.log(`Resuming thread ${this.currentThreadId}`);
|
|
@@ -39,16 +40,26 @@ export class CodexManager {
|
|
|
39
40
|
skipGitRepoCheck: true,
|
|
40
41
|
sandboxMode: 'danger-full-access',
|
|
41
42
|
});
|
|
43
|
+
if (this.currentThread.id) {
|
|
44
|
+
this.currentThreadId = this.currentThread.id;
|
|
45
|
+
console.log(`Thread started: ${this.currentThreadId}`);
|
|
46
|
+
}
|
|
47
|
+
// prime the thread with system instructions if thread is new
|
|
48
|
+
const workspaceName = process.env.WORKSPACE_NAME || 'workspace';
|
|
49
|
+
const workspaceBranchSegment = workspaceName
|
|
50
|
+
.trim()
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
53
|
+
.replace(/^-+|-+$/g, '') || 'workspace';
|
|
54
|
+
const uuid = randomUUID().slice(0, 8);
|
|
55
|
+
const branchName = `replicas/${workspaceBranchSegment}-${uuid}`;
|
|
56
|
+
const systemMessage = `<replicas_important_instructions>When completing solutions, push your changes to branch ${branchName} and to origin. Greet the user.</replicas_important_instructions>`;
|
|
57
|
+
console.log('Priming thread with system instructions');
|
|
58
|
+
await this.currentThread.run(systemMessage);
|
|
59
|
+
console.log('Thread primed successfully');
|
|
42
60
|
}
|
|
43
61
|
}
|
|
44
|
-
|
|
45
|
-
for await (const event of events) {
|
|
46
|
-
if (event.type === 'thread.started') {
|
|
47
|
-
this.currentThreadId = event.thread_id;
|
|
48
|
-
console.log(`Thread started: ${this.currentThreadId}`);
|
|
49
|
-
}
|
|
50
|
-
yield event;
|
|
51
|
-
}
|
|
62
|
+
await this.currentThread.run(message);
|
|
52
63
|
}
|
|
53
64
|
async getHistory() {
|
|
54
65
|
if (!this.currentThreadId) {
|
|
@@ -67,9 +78,13 @@ export class CodexManager {
|
|
|
67
78
|
}
|
|
68
79
|
console.log(`Reading session file: ${sessionFile}`);
|
|
69
80
|
const events = await readJSONL(sessionFile);
|
|
81
|
+
const filteredEvents = events.filter((event) => {
|
|
82
|
+
const eventStr = JSON.stringify(event);
|
|
83
|
+
return !eventStr.includes('<replicas_important_instructions>');
|
|
84
|
+
});
|
|
70
85
|
return {
|
|
71
86
|
thread_id: this.currentThreadId,
|
|
72
|
-
events,
|
|
87
|
+
events: filteredEvents,
|
|
73
88
|
};
|
|
74
89
|
}
|
|
75
90
|
async getStatus() {
|
|
@@ -92,4 +107,32 @@ export class CodexManager {
|
|
|
92
107
|
getThreadId() {
|
|
93
108
|
return this.currentThreadId;
|
|
94
109
|
}
|
|
110
|
+
async getUpdates(since) {
|
|
111
|
+
if (!this.currentThreadId) {
|
|
112
|
+
return {
|
|
113
|
+
events: [],
|
|
114
|
+
isComplete: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const sessionFile = await findSessionFile(this.currentThreadId);
|
|
118
|
+
if (!sessionFile) {
|
|
119
|
+
return {
|
|
120
|
+
events: [],
|
|
121
|
+
isComplete: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const allEvents = await readJSONL(sessionFile);
|
|
125
|
+
// Filter events that occurred after the 'since' timestamp
|
|
126
|
+
const filteredEvents = allEvents.filter((event) => {
|
|
127
|
+
return event.timestamp > since;
|
|
128
|
+
});
|
|
129
|
+
// Check if thread is complete by looking for turn.completed or error events
|
|
130
|
+
const isComplete = this.currentThread === null ||
|
|
131
|
+
allEvents.some((event) => event.type === 'event_msg' &&
|
|
132
|
+
(event.payload?.type === 'turn.completed' || event.payload?.type === 'error'));
|
|
133
|
+
return {
|
|
134
|
+
events: filteredEvents,
|
|
135
|
+
isComplete,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
95
138
|
}
|