@nextclaw/nextclaw-ncp-runtime-codex-sdk 0.1.29 → 0.1.30
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.d.ts +5 -6
- package/dist/index.js +54 -106
- package/dist/services/codex-live-output-event-merge.service.js +101 -0
- package/dist/services/codex-live-output-stream.service.d.ts +31 -0
- package/dist/services/codex-live-output-stream.service.js +81 -0
- package/dist/services/codex-ncp-run-event-emitter.service.js +84 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CodexLiveOutputChannel, CodexLiveOutputEvent, CodexLiveOutputStream } from "./services/codex-live-output-stream.service.js";
|
|
1
2
|
import { CodexAssetContentPathResolver, CodexThreadInput, buildCodexInputBuilder } from "./codex-input.utils.js";
|
|
2
3
|
import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpEndpointEvent } from "@nextclaw/ncp";
|
|
3
4
|
import { CodexOptions, ThreadOptions } from "@openai/codex-sdk";
|
|
@@ -16,12 +17,15 @@ type CodexSdkNcpAgentRuntimeConfig = {
|
|
|
16
17
|
sessionMetadata?: Record<string, unknown>;
|
|
17
18
|
setSessionMetadata?: (nextMetadata: Record<string, unknown>) => void;
|
|
18
19
|
inputBuilder?: (input: NcpAgentRunInput) => Promise<CodexThreadInput> | CodexThreadInput;
|
|
20
|
+
liveOutputStream?: CodexLiveOutputStream;
|
|
19
21
|
resolveAssetContentPath?: CodexAssetContentPathResolver;
|
|
20
22
|
stateManager?: NcpAgentConversationStateManager;
|
|
21
23
|
};
|
|
22
24
|
declare class CodexSdkNcpAgentRuntime implements NcpAgentRuntime {
|
|
23
25
|
private readonly config;
|
|
24
26
|
private codexPromise;
|
|
27
|
+
private readonly eventEmitter;
|
|
28
|
+
private readonly liveOutputEventMergeService;
|
|
25
29
|
private thread;
|
|
26
30
|
private threadId;
|
|
27
31
|
private readonly sessionMetadata;
|
|
@@ -30,14 +34,9 @@ declare class CodexSdkNcpAgentRuntime implements NcpAgentRuntime {
|
|
|
30
34
|
private getCodex;
|
|
31
35
|
private resolveThread;
|
|
32
36
|
private buildTurnInput;
|
|
33
|
-
private emitEvent;
|
|
34
|
-
private emitRunStarted;
|
|
35
|
-
private emitReadyMetadata;
|
|
36
|
-
private emitRunError;
|
|
37
|
-
private emitRunCompleted;
|
|
38
37
|
private streamTurnEvents;
|
|
39
38
|
private handleThreadEvent;
|
|
40
39
|
private updateThreadId;
|
|
41
40
|
}
|
|
42
41
|
//#endregion
|
|
43
|
-
export { type CodexAssetContentPathResolver, CodexSdkNcpAgentRuntime, CodexSdkNcpAgentRuntimeConfig, buildCodexInputBuilder };
|
|
42
|
+
export { type CodexAssetContentPathResolver, type CodexLiveOutputChannel, type CodexLiveOutputEvent, CodexLiveOutputStream, CodexSdkNcpAgentRuntime, CodexSdkNcpAgentRuntimeConfig, buildCodexInputBuilder };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodexLiveOutputEventMergeService } from "./services/codex-live-output-event-merge.service.js";
|
|
2
|
+
import { CodexNcpRunEventEmitter } from "./services/codex-ncp-run-event-emitter.service.js";
|
|
2
3
|
import { mapCodexItemEvent } from "./utils/codex-sdk-ncp-event-mapper.utils.js";
|
|
3
4
|
import { buildCodexCliEnv } from "./codex-cli-env.js";
|
|
4
5
|
import { buildCodexInputBuilder, buildCodexTurnInputFromRunInput } from "./codex-input.utils.js";
|
|
6
|
+
import { CodexLiveOutputStream } from "./services/codex-live-output-stream.service.js";
|
|
5
7
|
import { createRequire } from "node:module";
|
|
6
|
-
import
|
|
8
|
+
import "@nextclaw/ncp";
|
|
7
9
|
//#region src/index.ts
|
|
8
10
|
const codexLoader = createRequire(import.meta.url)("../codex-sdk-loader.cjs");
|
|
9
11
|
function createId(prefix) {
|
|
@@ -28,36 +30,41 @@ function isItemLifecycleEvent(event) {
|
|
|
28
30
|
}
|
|
29
31
|
var CodexSdkNcpAgentRuntime = class {
|
|
30
32
|
codexPromise = null;
|
|
33
|
+
eventEmitter;
|
|
34
|
+
liveOutputEventMergeService = new CodexLiveOutputEventMergeService();
|
|
31
35
|
thread = null;
|
|
32
36
|
threadId;
|
|
33
37
|
sessionMetadata;
|
|
34
38
|
constructor(config) {
|
|
35
39
|
this.config = config;
|
|
40
|
+
this.eventEmitter = new CodexNcpRunEventEmitter(config.stateManager);
|
|
36
41
|
this.threadId = config.threadId?.trim() || null;
|
|
37
42
|
this.sessionMetadata = { ...config.sessionMetadata ? structuredClone(config.sessionMetadata) : {} };
|
|
38
43
|
}
|
|
39
44
|
run = async function* (input, options) {
|
|
45
|
+
const signal = options?.signal;
|
|
40
46
|
const messageId = createId("codex-message");
|
|
41
47
|
const runId = createId("codex-run");
|
|
42
48
|
const itemTextById = /* @__PURE__ */ new Map();
|
|
43
49
|
const toolStateById = /* @__PURE__ */ new Map();
|
|
44
|
-
yield* this.emitRunStarted(input.sessionId, messageId, runId);
|
|
45
|
-
yield* this.emitReadyMetadata(input.sessionId, messageId, runId);
|
|
50
|
+
yield* this.eventEmitter.emitRunStarted(input.sessionId, messageId, runId);
|
|
51
|
+
yield* this.eventEmitter.emitReadyMetadata(input.sessionId, messageId, runId);
|
|
46
52
|
const thread = await this.resolveThread();
|
|
47
53
|
const turnInput = await this.buildTurnInput(input);
|
|
48
|
-
|
|
54
|
+
this.config.liveOutputStream?.reset();
|
|
55
|
+
const streamed = await thread.runStreamed(turnInput, { ...signal ? { signal } : {} });
|
|
49
56
|
try {
|
|
50
57
|
yield* this.streamTurnEvents({
|
|
51
58
|
sessionId: input.sessionId,
|
|
52
59
|
messageId,
|
|
53
60
|
runId,
|
|
54
61
|
streamed,
|
|
55
|
-
signal
|
|
62
|
+
signal,
|
|
56
63
|
itemTextById,
|
|
57
64
|
toolStateById
|
|
58
65
|
});
|
|
59
66
|
} catch (error) {
|
|
60
|
-
if (
|
|
67
|
+
if (signal?.aborted) throw toAbortError(signal.reason);
|
|
61
68
|
throw error;
|
|
62
69
|
}
|
|
63
70
|
};
|
|
@@ -85,122 +92,63 @@ var CodexSdkNcpAgentRuntime = class {
|
|
|
85
92
|
if (this.config.inputBuilder) return await this.config.inputBuilder(input);
|
|
86
93
|
return await buildCodexTurnInputFromRunInput(input, { resolveAssetContentPath: this.config.resolveAssetContentPath });
|
|
87
94
|
};
|
|
88
|
-
emitEvent = async function* (event) {
|
|
89
|
-
await this.config.stateManager?.dispatch(event);
|
|
90
|
-
yield event;
|
|
91
|
-
};
|
|
92
|
-
emitRunStarted = async function* (sessionId, messageId, runId) {
|
|
93
|
-
yield* this.emitEvent({
|
|
94
|
-
type: NcpEventType.RunStarted,
|
|
95
|
-
payload: {
|
|
96
|
-
sessionId,
|
|
97
|
-
messageId,
|
|
98
|
-
runId
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
};
|
|
102
|
-
emitReadyMetadata = async function* (sessionId, messageId, runId) {
|
|
103
|
-
yield* this.emitEvent({
|
|
104
|
-
type: NcpEventType.RunMetadata,
|
|
105
|
-
payload: {
|
|
106
|
-
sessionId,
|
|
107
|
-
messageId,
|
|
108
|
-
runId,
|
|
109
|
-
metadata: {
|
|
110
|
-
kind: "ready",
|
|
111
|
-
runId,
|
|
112
|
-
sessionId,
|
|
113
|
-
supportsAbort: true
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
emitRunError = async function* (sessionId, messageId, runId, error) {
|
|
119
|
-
yield* this.emitEvent({
|
|
120
|
-
type: NcpEventType.RunError,
|
|
121
|
-
payload: {
|
|
122
|
-
sessionId,
|
|
123
|
-
messageId,
|
|
124
|
-
runId,
|
|
125
|
-
error
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
};
|
|
129
|
-
emitRunCompleted = async function* (sessionId, messageId, runId) {
|
|
130
|
-
yield* this.emitEvent({
|
|
131
|
-
type: NcpEventType.RunMetadata,
|
|
132
|
-
payload: {
|
|
133
|
-
sessionId,
|
|
134
|
-
messageId,
|
|
135
|
-
runId,
|
|
136
|
-
metadata: {
|
|
137
|
-
kind: "final",
|
|
138
|
-
sessionId
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
yield* this.emitEvent({
|
|
143
|
-
type: NcpEventType.MessageCompleted,
|
|
144
|
-
payload: {
|
|
145
|
-
sessionId,
|
|
146
|
-
message: buildCompletedAssistantMessage({
|
|
147
|
-
stateManager: this.config.stateManager,
|
|
148
|
-
sessionId,
|
|
149
|
-
messageId
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
yield* this.emitEvent({
|
|
154
|
-
type: NcpEventType.RunFinished,
|
|
155
|
-
payload: {
|
|
156
|
-
sessionId,
|
|
157
|
-
messageId,
|
|
158
|
-
runId
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
95
|
streamTurnEvents = async function* (params) {
|
|
96
|
+
const { itemTextById, messageId, runId, sessionId, signal, streamed, toolStateById } = params;
|
|
97
|
+
const liveOutputStream = this.config.liveOutputStream;
|
|
98
|
+
if (liveOutputStream) {
|
|
99
|
+
yield* this.liveOutputEventMergeService.stream({
|
|
100
|
+
...params,
|
|
101
|
+
liveOutputStream,
|
|
102
|
+
emitEvent: (event) => this.eventEmitter.emitEvent(event),
|
|
103
|
+
emitRunCompleted: (sessionId, messageId, runId) => this.eventEmitter.emitRunCompleted(sessionId, messageId, runId),
|
|
104
|
+
handleThreadEvent: (handlerParams) => this.handleThreadEvent(handlerParams)
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
163
108
|
let finished = false;
|
|
164
|
-
for await (const event of
|
|
165
|
-
sessionId
|
|
166
|
-
messageId
|
|
167
|
-
runId
|
|
109
|
+
for await (const event of streamed.events) if (yield* this.handleThreadEvent({
|
|
110
|
+
sessionId,
|
|
111
|
+
messageId,
|
|
112
|
+
runId,
|
|
168
113
|
event,
|
|
169
|
-
signal
|
|
170
|
-
itemTextById
|
|
171
|
-
toolStateById
|
|
114
|
+
signal,
|
|
115
|
+
itemTextById,
|
|
116
|
+
toolStateById
|
|
172
117
|
})) {
|
|
173
118
|
finished = true;
|
|
174
119
|
return;
|
|
175
120
|
}
|
|
176
|
-
if (!finished) yield* this.emitRunCompleted(
|
|
121
|
+
if (!finished) yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);
|
|
177
122
|
};
|
|
178
123
|
handleThreadEvent = async function* (params) {
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
124
|
+
const { event, itemTextById, messageId, runId, sessionId, signal, suppressLiveChannels, toolStateById } = params;
|
|
125
|
+
if (signal?.aborted) throw toAbortError(signal.reason);
|
|
126
|
+
if (event.type === "thread.started") {
|
|
127
|
+
this.updateThreadId(event.thread_id);
|
|
182
128
|
return false;
|
|
183
129
|
}
|
|
184
|
-
if (
|
|
185
|
-
yield* this.emitRunError(
|
|
130
|
+
if (event.type === "turn.failed") {
|
|
131
|
+
yield* this.eventEmitter.emitRunError(sessionId, messageId, runId, event.error.message);
|
|
186
132
|
return true;
|
|
187
133
|
}
|
|
188
|
-
if (
|
|
189
|
-
yield* this.emitRunError(
|
|
134
|
+
if (event.type === "error") {
|
|
135
|
+
yield* this.eventEmitter.emitRunError(sessionId, messageId, runId, event.message);
|
|
190
136
|
return true;
|
|
191
137
|
}
|
|
192
|
-
if (isItemLifecycleEvent(
|
|
138
|
+
if (isItemLifecycleEvent(event)) {
|
|
139
|
+
const suppressedChannel = event.item.type === "agent_message" ? "text" : event.item.type === "reasoning" ? "reasoning" : null;
|
|
140
|
+
if (suppressedChannel && suppressLiveChannels?.has(suppressedChannel)) return false;
|
|
193
141
|
for await (const mappedEvent of mapCodexItemEvent({
|
|
194
|
-
sessionId
|
|
195
|
-
messageId
|
|
196
|
-
event
|
|
197
|
-
itemTextById
|
|
198
|
-
toolStateById
|
|
199
|
-
})) yield* this.emitEvent(mappedEvent);
|
|
142
|
+
sessionId,
|
|
143
|
+
messageId,
|
|
144
|
+
event,
|
|
145
|
+
itemTextById,
|
|
146
|
+
toolStateById
|
|
147
|
+
})) yield* this.eventEmitter.emitEvent(mappedEvent);
|
|
200
148
|
return false;
|
|
201
149
|
}
|
|
202
|
-
if (
|
|
203
|
-
yield* this.emitRunCompleted(
|
|
150
|
+
if (event.type === "turn.completed") {
|
|
151
|
+
yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);
|
|
204
152
|
return true;
|
|
205
153
|
}
|
|
206
154
|
return false;
|
|
@@ -220,4 +168,4 @@ var CodexSdkNcpAgentRuntime = class {
|
|
|
220
168
|
};
|
|
221
169
|
};
|
|
222
170
|
//#endregion
|
|
223
|
-
export { CodexSdkNcpAgentRuntime, buildCodexInputBuilder };
|
|
171
|
+
export { CodexLiveOutputStream, CodexSdkNcpAgentRuntime, buildCodexInputBuilder };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { NcpEventType } from "@nextclaw/ncp";
|
|
2
|
+
//#region src/services/codex-live-output-event-merge.service.ts
|
|
3
|
+
var CodexLiveOutputEventMergeService = class {
|
|
4
|
+
stream = async function* (params) {
|
|
5
|
+
const { emitEvent, emitRunCompleted, handleThreadEvent, itemTextById, liveOutputStream, messageId, runId, sessionId, signal, streamed, toolStateById } = params;
|
|
6
|
+
const liveChannels = /* @__PURE__ */ new Set();
|
|
7
|
+
const codexIterator = streamed.events[Symbol.asyncIterator]();
|
|
8
|
+
const liveIterator = this.streamLiveOutputEvents({
|
|
9
|
+
emitEvent,
|
|
10
|
+
liveChannels,
|
|
11
|
+
liveOutputStream,
|
|
12
|
+
messageId,
|
|
13
|
+
sessionId,
|
|
14
|
+
signal
|
|
15
|
+
})[Symbol.asyncIterator]();
|
|
16
|
+
let finished = false;
|
|
17
|
+
let codexDone = false;
|
|
18
|
+
let liveDone = false;
|
|
19
|
+
let codexNext = codexIterator.next();
|
|
20
|
+
let liveNext = liveIterator.next();
|
|
21
|
+
while (!codexDone || !liveDone) {
|
|
22
|
+
const pending = [...!codexDone ? [codexNext.then((next) => ({
|
|
23
|
+
source: "codex",
|
|
24
|
+
next
|
|
25
|
+
}))] : [], ...!liveDone ? [liveNext.then((next) => ({
|
|
26
|
+
source: "live",
|
|
27
|
+
next
|
|
28
|
+
}))] : []];
|
|
29
|
+
const result = await Promise.race(pending);
|
|
30
|
+
if (result.source === "live") {
|
|
31
|
+
if (result.next.done) liveDone = true;
|
|
32
|
+
else {
|
|
33
|
+
yield result.next.value;
|
|
34
|
+
liveNext = liveIterator.next();
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (result.next.done) {
|
|
39
|
+
codexDone = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (yield* handleThreadEvent({
|
|
43
|
+
event: result.next.value,
|
|
44
|
+
itemTextById,
|
|
45
|
+
messageId,
|
|
46
|
+
runId,
|
|
47
|
+
sessionId,
|
|
48
|
+
signal,
|
|
49
|
+
suppressLiveChannels: liveChannels,
|
|
50
|
+
toolStateById
|
|
51
|
+
})) {
|
|
52
|
+
finished = true;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
codexNext = codexIterator.next();
|
|
56
|
+
}
|
|
57
|
+
await liveIterator.return?.(void 0);
|
|
58
|
+
if (!finished) yield* emitRunCompleted(sessionId, messageId, runId);
|
|
59
|
+
};
|
|
60
|
+
streamLiveOutputEvents = async function* (params) {
|
|
61
|
+
const { emitEvent, liveChannels, liveOutputStream, messageId, sessionId, signal } = params;
|
|
62
|
+
const startedChannels = /* @__PURE__ */ new Set();
|
|
63
|
+
for await (const event of liveOutputStream.events(signal)) {
|
|
64
|
+
if (event.type === "delta") {
|
|
65
|
+
liveChannels.add(event.channel);
|
|
66
|
+
if (!startedChannels.has(event.channel)) {
|
|
67
|
+
startedChannels.add(event.channel);
|
|
68
|
+
yield* emitEvent({
|
|
69
|
+
type: event.channel === "reasoning" ? NcpEventType.MessageReasoningStart : NcpEventType.MessageTextStart,
|
|
70
|
+
payload: {
|
|
71
|
+
sessionId,
|
|
72
|
+
messageId
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
yield* emitEvent({
|
|
77
|
+
type: event.channel === "reasoning" ? NcpEventType.MessageReasoningDelta : NcpEventType.MessageTextDelta,
|
|
78
|
+
payload: {
|
|
79
|
+
sessionId,
|
|
80
|
+
messageId,
|
|
81
|
+
delta: event.delta
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (event.type === "end") {
|
|
87
|
+
if (startedChannels.has(event.channel)) yield* emitEvent({
|
|
88
|
+
type: event.channel === "reasoning" ? NcpEventType.MessageReasoningEnd : NcpEventType.MessageTextEnd,
|
|
89
|
+
payload: {
|
|
90
|
+
sessionId,
|
|
91
|
+
messageId
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
//#endregion
|
|
101
|
+
export { CodexLiveOutputEventMergeService };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/services/codex-live-output-stream.service.d.ts
|
|
2
|
+
type CodexLiveOutputChannel = "reasoning" | "text";
|
|
3
|
+
type CodexLiveOutputEvent = {
|
|
4
|
+
type: "delta";
|
|
5
|
+
channel: CodexLiveOutputChannel;
|
|
6
|
+
delta: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "end";
|
|
9
|
+
channel: CodexLiveOutputChannel;
|
|
10
|
+
} | {
|
|
11
|
+
type: "done";
|
|
12
|
+
};
|
|
13
|
+
declare class CodexLiveOutputStream {
|
|
14
|
+
private eventsQueue;
|
|
15
|
+
private waiters;
|
|
16
|
+
private done;
|
|
17
|
+
private reasoningEnded;
|
|
18
|
+
private textEnded;
|
|
19
|
+
reset: () => void;
|
|
20
|
+
onReasoningDelta: (delta: string) => void;
|
|
21
|
+
onReasoningDone: () => void;
|
|
22
|
+
onTextDelta: (delta: string) => void;
|
|
23
|
+
onTextDone: () => void;
|
|
24
|
+
onDone: () => void;
|
|
25
|
+
events: (this: CodexLiveOutputStream, signal?: AbortSignal) => AsyncGenerator<CodexLiveOutputEvent>;
|
|
26
|
+
private pushDelta;
|
|
27
|
+
private pushEnd;
|
|
28
|
+
private push;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { CodexLiveOutputChannel, CodexLiveOutputEvent, CodexLiveOutputStream };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/services/codex-live-output-stream.service.ts
|
|
2
|
+
var CodexLiveOutputStream = class {
|
|
3
|
+
eventsQueue = [];
|
|
4
|
+
waiters = [];
|
|
5
|
+
done = false;
|
|
6
|
+
reasoningEnded = false;
|
|
7
|
+
textEnded = false;
|
|
8
|
+
reset = () => {
|
|
9
|
+
this.eventsQueue = [];
|
|
10
|
+
this.done = false;
|
|
11
|
+
this.reasoningEnded = false;
|
|
12
|
+
this.textEnded = false;
|
|
13
|
+
};
|
|
14
|
+
onReasoningDelta = (delta) => {
|
|
15
|
+
this.pushDelta("reasoning", delta);
|
|
16
|
+
};
|
|
17
|
+
onReasoningDone = () => {
|
|
18
|
+
this.pushEnd("reasoning");
|
|
19
|
+
};
|
|
20
|
+
onTextDelta = (delta) => {
|
|
21
|
+
this.pushDelta("text", delta);
|
|
22
|
+
};
|
|
23
|
+
onTextDone = () => {
|
|
24
|
+
this.pushEnd("text");
|
|
25
|
+
};
|
|
26
|
+
onDone = () => {
|
|
27
|
+
if (this.done) return;
|
|
28
|
+
this.done = true;
|
|
29
|
+
this.push({ type: "done" });
|
|
30
|
+
};
|
|
31
|
+
events = async function* (signal) {
|
|
32
|
+
while (!signal?.aborted) {
|
|
33
|
+
const event = this.eventsQueue.shift();
|
|
34
|
+
if (event) {
|
|
35
|
+
yield event;
|
|
36
|
+
if (event.type === "done") return;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
await new Promise((resolve) => {
|
|
40
|
+
const abort = () => {
|
|
41
|
+
signal?.removeEventListener("abort", abort);
|
|
42
|
+
resolve();
|
|
43
|
+
};
|
|
44
|
+
this.waiters.push(() => {
|
|
45
|
+
signal?.removeEventListener("abort", abort);
|
|
46
|
+
resolve();
|
|
47
|
+
});
|
|
48
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
pushDelta = (channel, delta) => {
|
|
53
|
+
if (!delta || this.done) return;
|
|
54
|
+
this.push({
|
|
55
|
+
type: "delta",
|
|
56
|
+
channel,
|
|
57
|
+
delta
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
pushEnd = (channel) => {
|
|
61
|
+
if (this.done) return;
|
|
62
|
+
if (channel === "reasoning") {
|
|
63
|
+
if (this.reasoningEnded) return;
|
|
64
|
+
this.reasoningEnded = true;
|
|
65
|
+
} else {
|
|
66
|
+
if (this.textEnded) return;
|
|
67
|
+
this.textEnded = true;
|
|
68
|
+
}
|
|
69
|
+
this.push({
|
|
70
|
+
type: "end",
|
|
71
|
+
channel
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
push = (event) => {
|
|
75
|
+
this.eventsQueue.push(event);
|
|
76
|
+
const waiters = this.waiters.splice(0);
|
|
77
|
+
for (const waiter of waiters) waiter();
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { CodexLiveOutputStream };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { buildCompletedAssistantMessage } from "../completed-assistant-message.utils.js";
|
|
2
|
+
import { NcpEventType } from "@nextclaw/ncp";
|
|
3
|
+
//#region src/services/codex-ncp-run-event-emitter.service.ts
|
|
4
|
+
var CodexNcpRunEventEmitter = class {
|
|
5
|
+
constructor(stateManager) {
|
|
6
|
+
this.stateManager = stateManager;
|
|
7
|
+
}
|
|
8
|
+
emitEvent = async function* (event) {
|
|
9
|
+
await this.stateManager?.dispatch(event);
|
|
10
|
+
yield event;
|
|
11
|
+
};
|
|
12
|
+
emitRunStarted = async function* (sessionId, messageId, runId) {
|
|
13
|
+
yield* this.emitEvent({
|
|
14
|
+
type: NcpEventType.RunStarted,
|
|
15
|
+
payload: {
|
|
16
|
+
sessionId,
|
|
17
|
+
messageId,
|
|
18
|
+
runId
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
emitReadyMetadata = async function* (sessionId, messageId, runId) {
|
|
23
|
+
yield* this.emitEvent({
|
|
24
|
+
type: NcpEventType.RunMetadata,
|
|
25
|
+
payload: {
|
|
26
|
+
sessionId,
|
|
27
|
+
messageId,
|
|
28
|
+
runId,
|
|
29
|
+
metadata: {
|
|
30
|
+
kind: "ready",
|
|
31
|
+
runId,
|
|
32
|
+
sessionId,
|
|
33
|
+
supportsAbort: true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
emitRunError = async function* (sessionId, messageId, runId, error) {
|
|
39
|
+
yield* this.emitEvent({
|
|
40
|
+
type: NcpEventType.RunError,
|
|
41
|
+
payload: {
|
|
42
|
+
sessionId,
|
|
43
|
+
messageId,
|
|
44
|
+
runId,
|
|
45
|
+
error
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
emitRunCompleted = async function* (sessionId, messageId, runId) {
|
|
50
|
+
yield* this.emitEvent({
|
|
51
|
+
type: NcpEventType.RunMetadata,
|
|
52
|
+
payload: {
|
|
53
|
+
sessionId,
|
|
54
|
+
messageId,
|
|
55
|
+
runId,
|
|
56
|
+
metadata: {
|
|
57
|
+
kind: "final",
|
|
58
|
+
sessionId
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
yield* this.emitEvent({
|
|
63
|
+
type: NcpEventType.MessageCompleted,
|
|
64
|
+
payload: {
|
|
65
|
+
sessionId,
|
|
66
|
+
message: buildCompletedAssistantMessage({
|
|
67
|
+
stateManager: this.stateManager,
|
|
68
|
+
sessionId,
|
|
69
|
+
messageId
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
yield* this.emitEvent({
|
|
74
|
+
type: NcpEventType.RunFinished,
|
|
75
|
+
payload: {
|
|
76
|
+
sessionId,
|
|
77
|
+
messageId,
|
|
78
|
+
runId
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { CodexNcpRunEventEmitter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/nextclaw-ncp-runtime-codex-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Optional NCP runtime adapter backed by OpenAI Codex SDK.",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension --unbundle",
|
|
29
29
|
"lint": "eslint .",
|
|
30
|
-
"test": "vitest run src/utils/codex-sdk-ncp-event-mapper.utils.test.ts",
|
|
30
|
+
"test": "vitest run src/utils/codex-sdk-ncp-event-mapper.utils.test.ts src/services/codex-live-output-stream.service.test.ts",
|
|
31
31
|
"tsc": "tsc -p tsconfig.json"
|
|
32
32
|
}
|
|
33
33
|
}
|