replicas-engine 0.1.5 → 0.1.7

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/index.js CHANGED
@@ -3,9 +3,12 @@ import 'dotenv/config';
3
3
  import { serve } from '@hono/node-server';
4
4
  import { Hono } from 'hono';
5
5
  import { readFile } from 'fs/promises';
6
- import { READY_MESSAGE, COMPLETION_MESSAGE } from '@replicas/shared';
7
6
  import { authMiddleware } from './middleware/auth.js';
8
7
  import codex from './routes/codex.js';
8
+ // NOTE: These constants are duplicated in @replicas/shared/src/workspaces/WorkspaceInitializer.ts
9
+ // If you change these values, you MUST update both locations to keep them in sync.
10
+ const READY_MESSAGE = '========= REPLICAS WORKSPACE READY ==========';
11
+ const COMPLETION_MESSAGE = '========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========';
9
12
  const app = new Hono();
10
13
  app.get('/health', async (c) => {
11
14
  try {
@@ -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 and stream events back via Server-Sent Events (SSE)
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
- return stream(c, async (stream) => {
18
- c.header('Content-Type', 'text/event-stream');
19
- c.header('Cache-Control', 'no-cache');
20
- c.header('Connection', 'keep-alive');
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
@@ -22,7 +22,7 @@ export class CodexManager {
22
22
  }
23
23
  }
24
24
  }
25
- async *sendMessage(message) {
25
+ async sendMessage(message) {
26
26
  if (!this.currentThread) {
27
27
  if (this.currentThreadId) {
28
28
  console.log(`Resuming thread ${this.currentThreadId}`);
@@ -41,13 +41,10 @@ export class CodexManager {
41
41
  });
42
42
  }
43
43
  }
44
- const { events } = await this.currentThread.runStreamed(message);
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;
44
+ await this.currentThread.run(message);
45
+ if (!this.currentThreadId && this.currentThread.id) {
46
+ this.currentThreadId = this.currentThread.id;
47
+ console.log(`Thread started: ${this.currentThreadId}`);
51
48
  }
52
49
  }
53
50
  async getHistory() {
@@ -92,4 +89,32 @@ export class CodexManager {
92
89
  getThreadId() {
93
90
  return this.currentThreadId;
94
91
  }
92
+ async getUpdates(since) {
93
+ if (!this.currentThreadId) {
94
+ return {
95
+ events: [],
96
+ isComplete: true,
97
+ };
98
+ }
99
+ const sessionFile = await findSessionFile(this.currentThreadId);
100
+ if (!sessionFile) {
101
+ return {
102
+ events: [],
103
+ isComplete: true,
104
+ };
105
+ }
106
+ const allEvents = await readJSONL(sessionFile);
107
+ // Filter events that occurred after the 'since' timestamp
108
+ const filteredEvents = allEvents.filter((event) => {
109
+ return event.timestamp > since;
110
+ });
111
+ // Check if thread is complete by looking for turn.completed or error events
112
+ const isComplete = this.currentThread === null ||
113
+ allEvents.some((event) => event.type === 'event_msg' &&
114
+ (event.payload?.type === 'turn.completed' || event.payload?.type === 'error'));
115
+ return {
116
+ events: filteredEvents,
117
+ isComplete,
118
+ };
119
+ }
95
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,6 @@
25
25
  "author": "Replicas",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "@replicas/shared": "0.0.1",
29
28
  "@hono/node-server": "^1.19.5",
30
29
  "@openai/codex-sdk": "^0.50.0",
31
30
  "dotenv": "^17.2.3",