@zakstam/codex-local-component 0.2.1 → 0.4.0
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/README.md +26 -4
- package/dist/app-server/client.d.ts +51 -1
- package/dist/app-server/client.d.ts.map +1 -1
- package/dist/app-server/client.js +77 -3
- package/dist/app-server/client.js.map +1 -1
- package/dist/app-server/index.d.ts +1 -1
- package/dist/app-server/index.d.ts.map +1 -1
- package/dist/app-server/index.js +1 -1
- package/dist/app-server/index.js.map +1 -1
- package/dist/client/index.d.ts +4 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/reasoning.d.ts +10 -0
- package/dist/client/reasoning.d.ts.map +1 -0
- package/dist/client/reasoning.js +4 -0
- package/dist/client/reasoning.js.map +1 -0
- package/dist/client/serverRequests.d.ts +14 -0
- package/dist/client/serverRequests.d.ts.map +1 -0
- package/dist/client/serverRequests.js +10 -0
- package/dist/client/serverRequests.js.map +1 -0
- package/dist/client/threads.d.ts +39 -3
- package/dist/client/threads.d.ts.map +1 -1
- package/dist/client/threads.js +18 -0
- package/dist/client/threads.js.map +1 -1
- package/dist/client/types.d.ts +23 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/approvals.d.ts +1 -1
- package/dist/component/index.d.ts +2 -0
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +2 -0
- package/dist/component/index.js.map +1 -1
- package/dist/component/ingest/applyApprovals.d.ts +5 -0
- package/dist/component/ingest/applyApprovals.d.ts.map +1 -0
- package/dist/component/ingest/applyApprovals.js +59 -0
- package/dist/component/ingest/applyApprovals.js.map +1 -0
- package/dist/component/ingest/applyMessages.d.ts +4 -0
- package/dist/component/ingest/applyMessages.d.ts.map +1 -0
- package/dist/component/ingest/applyMessages.js +149 -0
- package/dist/component/ingest/applyMessages.js.map +1 -0
- package/dist/component/ingest/applyStreams.d.ts +7 -0
- package/dist/component/ingest/applyStreams.d.ts.map +1 -0
- package/dist/component/ingest/applyStreams.js +189 -0
- package/dist/component/ingest/applyStreams.js.map +1 -0
- package/dist/component/ingest/applyTurns.d.ts +6 -0
- package/dist/component/ingest/applyTurns.d.ts.map +1 -0
- package/dist/component/ingest/applyTurns.js +94 -0
- package/dist/component/ingest/applyTurns.js.map +1 -0
- package/dist/component/ingest/checkpoints.d.ts +12 -0
- package/dist/component/ingest/checkpoints.d.ts.map +1 -0
- package/dist/component/ingest/checkpoints.js +63 -0
- package/dist/component/ingest/checkpoints.js.map +1 -0
- package/dist/component/ingest/index.d.ts +10 -0
- package/dist/component/ingest/index.d.ts.map +1 -0
- package/dist/component/ingest/index.js +76 -0
- package/dist/component/ingest/index.js.map +1 -0
- package/dist/component/ingest/normalize.d.ts +5 -0
- package/dist/component/ingest/normalize.d.ts.map +1 -0
- package/dist/component/ingest/normalize.js +35 -0
- package/dist/component/ingest/normalize.js.map +1 -0
- package/dist/component/ingest/postIngest.d.ts +4 -0
- package/dist/component/ingest/postIngest.d.ts.map +1 -0
- package/dist/component/ingest/postIngest.js +31 -0
- package/dist/component/ingest/postIngest.js.map +1 -0
- package/dist/component/ingest/sessionGuard.d.ts +9 -0
- package/dist/component/ingest/sessionGuard.d.ts.map +1 -0
- package/dist/component/ingest/sessionGuard.js +102 -0
- package/dist/component/ingest/sessionGuard.js.map +1 -0
- package/dist/component/ingest/stateCache.d.ts +19 -0
- package/dist/component/ingest/stateCache.d.ts.map +1 -0
- package/dist/component/ingest/stateCache.js +121 -0
- package/dist/component/ingest/stateCache.js.map +1 -0
- package/dist/component/ingest/types.d.ts +129 -0
- package/dist/component/ingest/types.d.ts.map +1 -0
- package/dist/component/ingest/types.js +2 -0
- package/dist/component/ingest/types.js.map +1 -0
- package/dist/component/reasoning.d.ts +37 -0
- package/dist/component/reasoning.d.ts.map +1 -0
- package/dist/component/reasoning.js +48 -0
- package/dist/component/reasoning.js.map +1 -0
- package/dist/component/schema.d.ts +87 -11
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +47 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/serverRequests.d.ts +53 -0
- package/dist/component/serverRequests.d.ts.map +1 -0
- package/dist/component/serverRequests.js +187 -0
- package/dist/component/serverRequests.js.map +1 -0
- package/dist/component/streamStats.d.ts +10 -0
- package/dist/component/streamStats.d.ts.map +1 -1
- package/dist/component/streamStats.js +34 -0
- package/dist/component/streamStats.js.map +1 -1
- package/dist/component/sync.d.ts +4 -67
- package/dist/component/sync.d.ts.map +1 -1
- package/dist/component/syncHelpers.d.ts +12 -35
- package/dist/component/syncHelpers.d.ts.map +1 -1
- package/dist/component/syncHelpers.js +11 -228
- package/dist/component/syncHelpers.js.map +1 -1
- package/dist/component/syncIngest.d.ts +2 -72
- package/dist/component/syncIngest.d.ts.map +1 -1
- package/dist/component/syncIngest.js +9 -726
- package/dist/component/syncIngest.js.map +1 -1
- package/dist/component/syncRuntime.d.ts +7 -1
- package/dist/component/syncRuntime.d.ts.map +1 -1
- package/dist/component/syncRuntime.js +8 -2
- package/dist/component/syncRuntime.js.map +1 -1
- package/dist/component/types.d.ts +5 -1
- package/dist/component/types.d.ts.map +1 -1
- package/dist/component/types.js +2 -0
- package/dist/component/types.js.map +1 -1
- package/dist/host/convex-entry.d.ts +3 -0
- package/dist/host/convex-entry.d.ts.map +1 -0
- package/dist/host/convex-entry.js +3 -0
- package/dist/host/convex-entry.js.map +1 -0
- package/dist/host/convex.d.ts +17 -0
- package/dist/host/convex.d.ts.map +1 -1
- package/dist/host/convex.js +9 -0
- package/dist/host/convex.js.map +1 -1
- package/dist/host/convexSlice.d.ts +504 -0
- package/dist/host/convexSlice.d.ts.map +1 -0
- package/dist/host/convexSlice.js +315 -0
- package/dist/host/convexSlice.js.map +1 -0
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -1
- package/dist/host/index.js.map +1 -1
- package/dist/host/runtime.d.ts +126 -2
- package/dist/host/runtime.d.ts.map +1 -1
- package/dist/host/runtime.js +444 -53
- package/dist/host/runtime.js.map +1 -1
- package/dist/local-adapter/bridge.d.ts +3 -2
- package/dist/local-adapter/bridge.d.ts.map +1 -1
- package/dist/local-adapter/bridge.js.map +1 -1
- package/dist/mapping.d.ts +29 -0
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +136 -46
- package/dist/mapping.js.map +1 -1
- package/dist/protocol/classifier.d.ts +2 -12
- package/dist/protocol/classifier.d.ts.map +1 -1
- package/dist/protocol/classifier.js +1 -104
- package/dist/protocol/classifier.js.map +1 -1
- package/dist/protocol/events.d.ts +72 -0
- package/dist/protocol/events.d.ts.map +1 -0
- package/dist/protocol/events.js +533 -0
- package/dist/protocol/events.js.map +1 -0
- package/dist/protocol/generated.d.ts +16 -2
- package/dist/protocol/generated.d.ts.map +1 -1
- package/dist/protocol/index.d.ts +3 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/outbound.d.ts +14 -0
- package/dist/protocol/outbound.d.ts.map +1 -0
- package/dist/protocol/outbound.js +2 -0
- package/dist/protocol/outbound.js.map +1 -0
- package/dist/protocol/parser.d.ts +3 -2
- package/dist/protocol/parser.d.ts.map +1 -1
- package/dist/protocol/parser.js +99 -3
- package/dist/protocol/parser.js.map +1 -1
- package/dist/protocol/schemas/CommandExecutionRequestApprovalResponse.json +72 -0
- package/dist/protocol/schemas/DynamicToolCallResponse.json +66 -0
- package/dist/protocol/schemas/FileChangeRequestApprovalResponse.json +47 -0
- package/dist/protocol/schemas/ToolRequestUserInputResponse.json +34 -0
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/types.d.ts +13 -1
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useCodexReasoning.d.ts +7 -0
- package/dist/react/useCodexReasoning.d.ts.map +1 -0
- package/dist/react/useCodexReasoning.js +16 -0
- package/dist/react/useCodexReasoning.js.map +1 -0
- package/dist/react/useCodexStreamOverlay.d.ts.map +1 -1
- package/dist/react/useCodexStreamOverlay.js +68 -23
- package/dist/react/useCodexStreamOverlay.js.map +1 -1
- package/dist/react/useCodexStreamingReasoning.d.ts +12 -0
- package/dist/react/useCodexStreamingReasoning.d.ts.map +1 -0
- package/dist/react/useCodexStreamingReasoning.js +21 -0
- package/dist/react/useCodexStreamingReasoning.js.map +1 -0
- package/package.json +5 -1
package/dist/host/runtime.js
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildAccountLoginCancelRequest, buildAccountLoginStartRequest, buildAccountLogoutRequest, buildAccountRateLimitsReadRequest, buildAccountReadRequest, buildChatgptAuthTokensRefreshResponse, buildCommandExecutionApprovalResponse, buildDynamicToolCallResponse, buildFileChangeApprovalResponse, buildThreadArchiveRequest, buildThreadForkRequest, buildThreadListRequest, buildThreadLoadedListRequest, buildThreadReadRequest, buildThreadResumeRequest, buildThreadRollbackRequest, buildInitializeRequestWithCapabilities, buildInitializedNotification, buildThreadStartRequest, buildThreadUnarchiveRequest, buildToolRequestUserInputResponse, buildTurnInterruptRequest, buildTurnStartTextRequest, isUuidLikeThreadId, } from "../app-server/client.js";
|
|
2
2
|
import { CodexLocalBridge } from "../local-adapter/bridge.js";
|
|
3
3
|
const MAX_BATCH_SIZE = 32;
|
|
4
|
+
const MANAGED_SERVER_REQUEST_METHODS = new Set([
|
|
5
|
+
"item/commandExecution/requestApproval",
|
|
6
|
+
"item/fileChange/requestApproval",
|
|
7
|
+
"item/tool/requestUserInput",
|
|
8
|
+
"item/tool/call",
|
|
9
|
+
]);
|
|
10
|
+
const TURN_SCOPED_EVENT_PREFIXES = ["turn/", "item/"];
|
|
11
|
+
function toRequestKey(requestId) {
|
|
12
|
+
return `${typeof requestId}:${String(requestId)}`;
|
|
13
|
+
}
|
|
14
|
+
function asObject(value) {
|
|
15
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
16
|
+
}
|
|
17
|
+
function parseManagedServerRequestFromEvent(event) {
|
|
18
|
+
if (!MANAGED_SERVER_REQUEST_METHODS.has(event.kind)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(event.payloadJson);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const message = asObject(parsed);
|
|
29
|
+
if (!message || typeof message.method !== "string") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!MANAGED_SERVER_REQUEST_METHODS.has(message.method)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (typeof message.id !== "number" && typeof message.id !== "string") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const params = asObject(message.params);
|
|
39
|
+
if (!params) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (typeof params.threadId !== "string" || typeof params.turnId !== "string") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const method = message.method;
|
|
46
|
+
const itemId = typeof params.itemId === "string"
|
|
47
|
+
? params.itemId
|
|
48
|
+
: method === "item/tool/call" && typeof params.callId === "string"
|
|
49
|
+
? params.callId
|
|
50
|
+
: null;
|
|
51
|
+
if (!itemId) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const reason = typeof params.reason === "string" ? params.reason : undefined;
|
|
55
|
+
const questionsRaw = params.questions;
|
|
56
|
+
const questions = method === "item/tool/requestUserInput" && Array.isArray(questionsRaw)
|
|
57
|
+
? questionsRaw
|
|
58
|
+
: undefined;
|
|
59
|
+
return {
|
|
60
|
+
requestId: message.id,
|
|
61
|
+
method,
|
|
62
|
+
threadId: params.threadId,
|
|
63
|
+
turnId: params.turnId,
|
|
64
|
+
itemId,
|
|
65
|
+
payloadJson: event.payloadJson,
|
|
66
|
+
createdAt: event.createdAt,
|
|
67
|
+
...(reason ? { reason } : {}),
|
|
68
|
+
...(questions ? { questions } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function isTurnScopedEvent(kind) {
|
|
72
|
+
return kind === "error" || TURN_SCOPED_EVENT_PREFIXES.some((prefix) => kind.startsWith(prefix));
|
|
73
|
+
}
|
|
4
74
|
function randomSessionId() {
|
|
5
75
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
6
76
|
return crypto.randomUUID();
|
|
@@ -10,18 +80,20 @@ function randomSessionId() {
|
|
|
10
80
|
function isServerNotification(message) {
|
|
11
81
|
return "method" in message;
|
|
12
82
|
}
|
|
83
|
+
function isChatgptAuthTokensRefreshRequest(message) {
|
|
84
|
+
if (!("method" in message) ||
|
|
85
|
+
message.method !== "account/chatgptAuthTokens/refresh" ||
|
|
86
|
+
!("id" in message) ||
|
|
87
|
+
(typeof message.id !== "number" && typeof message.id !== "string") ||
|
|
88
|
+
!("params" in message)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const params = asObject(message.params);
|
|
92
|
+
return !!params && typeof params.reason === "string";
|
|
93
|
+
}
|
|
13
94
|
function isResponse(message) {
|
|
14
95
|
return "id" in message && !isServerNotification(message);
|
|
15
96
|
}
|
|
16
|
-
function normalizeIngestKind(kind) {
|
|
17
|
-
if (kind === "codex/event/task_started") {
|
|
18
|
-
return "turn/started";
|
|
19
|
-
}
|
|
20
|
-
if (kind === "codex/event/task_complete") {
|
|
21
|
-
return "turn/completed";
|
|
22
|
-
}
|
|
23
|
-
return kind;
|
|
24
|
-
}
|
|
25
97
|
export function createCodexHostRuntime(args) {
|
|
26
98
|
let bridge = null;
|
|
27
99
|
let actor = null;
|
|
@@ -38,7 +110,25 @@ export function createCodexHostRuntime(args) {
|
|
|
38
110
|
let flushTimer = null;
|
|
39
111
|
let flushTail = Promise.resolve();
|
|
40
112
|
let ingestFlushMs = 250;
|
|
113
|
+
const pendingServerRequests = new Map();
|
|
114
|
+
const pendingAuthTokensRefreshRequests = new Map();
|
|
115
|
+
const enqueuedByKind = new Map();
|
|
116
|
+
const skippedByKind = new Map();
|
|
117
|
+
let enqueuedEventCount = 0;
|
|
118
|
+
let skippedEventCount = 0;
|
|
41
119
|
const pendingRequests = new Map();
|
|
120
|
+
const incrementCount = (counts, kind) => {
|
|
121
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
122
|
+
};
|
|
123
|
+
const snapshotKindCounts = (counts) => Array.from(counts.entries())
|
|
124
|
+
.sort((left, right) => left[0].localeCompare(right[0]))
|
|
125
|
+
.map(([kind, count]) => ({ kind, count }));
|
|
126
|
+
const resetIngestMetrics = () => {
|
|
127
|
+
enqueuedByKind.clear();
|
|
128
|
+
skippedByKind.clear();
|
|
129
|
+
enqueuedEventCount = 0;
|
|
130
|
+
skippedEventCount = 0;
|
|
131
|
+
};
|
|
42
132
|
const emitState = (lastError = null) => {
|
|
43
133
|
args.handlers?.onState?.({
|
|
44
134
|
running: !!bridge,
|
|
@@ -46,6 +136,13 @@ export function createCodexHostRuntime(args) {
|
|
|
46
136
|
externalThreadId,
|
|
47
137
|
turnId,
|
|
48
138
|
turnInFlight,
|
|
139
|
+
pendingServerRequestCount: pendingServerRequests.size,
|
|
140
|
+
ingestMetrics: {
|
|
141
|
+
enqueuedEventCount,
|
|
142
|
+
skippedEventCount,
|
|
143
|
+
enqueuedByKind: snapshotKindCounts(enqueuedByKind),
|
|
144
|
+
skippedByKind: snapshotKindCounts(skippedByKind),
|
|
145
|
+
},
|
|
49
146
|
lastError,
|
|
50
147
|
});
|
|
51
148
|
};
|
|
@@ -60,15 +157,123 @@ export function createCodexHostRuntime(args) {
|
|
|
60
157
|
nextRequestId += 1;
|
|
61
158
|
return id;
|
|
62
159
|
};
|
|
63
|
-
const
|
|
160
|
+
const assertRuntimeReady = () => {
|
|
64
161
|
if (!bridge) {
|
|
65
162
|
throw new Error("Bridge not started");
|
|
66
163
|
}
|
|
67
|
-
bridge
|
|
164
|
+
return bridge;
|
|
165
|
+
};
|
|
166
|
+
const methodForRequest = (message) => message.method;
|
|
167
|
+
const sendMessage = (message, trackedMethod) => {
|
|
168
|
+
const runtimeBridge = assertRuntimeReady();
|
|
169
|
+
runtimeBridge.send(message);
|
|
68
170
|
if ("id" in message && typeof message.id === "number" && trackedMethod) {
|
|
69
171
|
pendingRequests.set(message.id, { method: trackedMethod });
|
|
70
172
|
}
|
|
71
173
|
};
|
|
174
|
+
const sendRequest = (message) => {
|
|
175
|
+
const runtimeBridge = assertRuntimeReady();
|
|
176
|
+
if (typeof message.id !== "number") {
|
|
177
|
+
throw new Error("Runtime requires numeric request ids.");
|
|
178
|
+
}
|
|
179
|
+
const messageId = message.id;
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
pendingRequests.set(messageId, { method: methodForRequest(message), resolve, reject });
|
|
182
|
+
runtimeBridge.send(message);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
const registerPendingServerRequest = async (request) => {
|
|
186
|
+
pendingServerRequests.set(toRequestKey(request.requestId), request);
|
|
187
|
+
if (actor) {
|
|
188
|
+
await args.persistence.upsertPendingServerRequest({ actor, request });
|
|
189
|
+
}
|
|
190
|
+
emitState();
|
|
191
|
+
};
|
|
192
|
+
const resolvePendingServerRequest = async (argsForResolve) => {
|
|
193
|
+
const key = toRequestKey(argsForResolve.requestId);
|
|
194
|
+
const pending = pendingServerRequests.get(key);
|
|
195
|
+
if (!pending) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
pendingServerRequests.delete(key);
|
|
199
|
+
if (actor) {
|
|
200
|
+
await args.persistence.resolvePendingServerRequest({
|
|
201
|
+
actor,
|
|
202
|
+
threadId: pending.threadId,
|
|
203
|
+
requestId: pending.requestId,
|
|
204
|
+
status: argsForResolve.status,
|
|
205
|
+
resolvedAt: Date.now(),
|
|
206
|
+
...(argsForResolve.responseJson ? { responseJson: argsForResolve.responseJson } : {}),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
emitState();
|
|
210
|
+
};
|
|
211
|
+
const expireTurnServerRequests = async (turn) => {
|
|
212
|
+
if (!turn.turnId) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const idsToExpire = [];
|
|
216
|
+
for (const request of pendingServerRequests.values()) {
|
|
217
|
+
if (request.threadId === turn.threadId && request.turnId === turn.turnId) {
|
|
218
|
+
idsToExpire.push(request.requestId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const requestId of idsToExpire) {
|
|
222
|
+
await resolvePendingServerRequest({ requestId, status: "expired" });
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const getPendingServerRequest = (requestId) => {
|
|
226
|
+
const pending = pendingServerRequests.get(toRequestKey(requestId));
|
|
227
|
+
if (!pending) {
|
|
228
|
+
throw new Error(`No pending server request found for id ${String(requestId)}`);
|
|
229
|
+
}
|
|
230
|
+
return pending;
|
|
231
|
+
};
|
|
232
|
+
const sendServerRequestResponse = async (requestId, responseMessage) => {
|
|
233
|
+
getPendingServerRequest(requestId);
|
|
234
|
+
sendMessage(responseMessage);
|
|
235
|
+
await resolvePendingServerRequest({
|
|
236
|
+
requestId,
|
|
237
|
+
status: "answered",
|
|
238
|
+
responseJson: JSON.stringify(responseMessage),
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
const registerPendingAuthTokensRefreshRequest = (request) => {
|
|
242
|
+
pendingAuthTokensRefreshRequests.set(toRequestKey(request.requestId), request);
|
|
243
|
+
};
|
|
244
|
+
const getPendingAuthTokensRefreshRequest = (requestId) => {
|
|
245
|
+
const pending = pendingAuthTokensRefreshRequests.get(toRequestKey(requestId));
|
|
246
|
+
if (!pending) {
|
|
247
|
+
throw new Error(`No pending auth token refresh request found for id ${String(requestId)}`);
|
|
248
|
+
}
|
|
249
|
+
return pending;
|
|
250
|
+
};
|
|
251
|
+
const resolvePendingAuthTokensRefreshRequest = (requestId) => {
|
|
252
|
+
pendingAuthTokensRefreshRequests.delete(toRequestKey(requestId));
|
|
253
|
+
};
|
|
254
|
+
const throwIfTurnMutationLocked = () => {
|
|
255
|
+
if (turnInFlight && !turnSettled) {
|
|
256
|
+
throw new Error("Cannot change thread lifecycle while a turn is in flight.");
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const setRuntimeThreadFromResponse = (message, method) => {
|
|
260
|
+
if (message.error || !message.result || typeof message.result !== "object") {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!("thread" in message.result) || typeof message.result.thread !== "object" || message.result.thread === null) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!("id" in message.result.thread) || typeof message.result.thread.id !== "string") {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (method === "thread/start" || method === "thread/resume" || method === "thread/fork") {
|
|
270
|
+
runtimeThreadId = message.result.thread.id;
|
|
271
|
+
if (!externalThreadId) {
|
|
272
|
+
externalThreadId = message.result.thread.id;
|
|
273
|
+
}
|
|
274
|
+
emitState();
|
|
275
|
+
}
|
|
276
|
+
};
|
|
72
277
|
const flushQueue = async () => {
|
|
73
278
|
if (!actor || !sessionId || !threadId) {
|
|
74
279
|
return;
|
|
@@ -94,6 +299,28 @@ export function createCodexHostRuntime(args) {
|
|
|
94
299
|
flushTail = next.catch(() => undefined);
|
|
95
300
|
await next;
|
|
96
301
|
};
|
|
302
|
+
const toIngestDelta = (event, persistedThreadId) => {
|
|
303
|
+
const resolvedTurnId = event.turnId ?? turnId;
|
|
304
|
+
if (!resolvedTurnId || !isTurnScopedEvent(event.kind)) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const resolvedStreamId = event.streamId;
|
|
308
|
+
if (!resolvedStreamId) {
|
|
309
|
+
throw new Error(`Protocol event missing streamId for turn-scoped kind: ${event.kind}`);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
type: "stream_delta",
|
|
313
|
+
eventId: event.eventId,
|
|
314
|
+
kind: event.kind,
|
|
315
|
+
payloadJson: event.payloadJson,
|
|
316
|
+
cursorStart: event.cursorStart,
|
|
317
|
+
cursorEnd: event.cursorEnd,
|
|
318
|
+
createdAt: event.createdAt,
|
|
319
|
+
threadId: persistedThreadId,
|
|
320
|
+
turnId: resolvedTurnId,
|
|
321
|
+
streamId: resolvedStreamId,
|
|
322
|
+
};
|
|
323
|
+
};
|
|
97
324
|
const enqueueIngestDelta = async (delta, forceFlush) => {
|
|
98
325
|
ingestQueue.push(delta);
|
|
99
326
|
if (forceFlush || ingestQueue.length >= MAX_BATCH_SIZE) {
|
|
@@ -116,6 +343,7 @@ export function createCodexHostRuntime(args) {
|
|
|
116
343
|
sessionId = startArgs.sessionId ? `${startArgs.sessionId}-${randomSessionId()}` : randomSessionId();
|
|
117
344
|
externalThreadId = startArgs.externalThreadId ?? null;
|
|
118
345
|
ingestFlushMs = startArgs.ingestFlushMs ?? 250;
|
|
346
|
+
resetIngestMetrics();
|
|
119
347
|
const bridgeConfig = {};
|
|
120
348
|
if (args.bridge?.codexBin !== undefined) {
|
|
121
349
|
bridgeConfig.codexBin = args.bridge.codexBin;
|
|
@@ -124,7 +352,7 @@ export function createCodexHostRuntime(args) {
|
|
|
124
352
|
if (resolvedCwd !== undefined) {
|
|
125
353
|
bridgeConfig.cwd = resolvedCwd;
|
|
126
354
|
}
|
|
127
|
-
|
|
355
|
+
const bridgeHandlers = {
|
|
128
356
|
onEvent: async (event) => {
|
|
129
357
|
if (!actor || !sessionId) {
|
|
130
358
|
return;
|
|
@@ -167,11 +395,15 @@ export function createCodexHostRuntime(args) {
|
|
|
167
395
|
interruptRequested = false;
|
|
168
396
|
}
|
|
169
397
|
}
|
|
170
|
-
if (event.kind === "turn/completed"
|
|
398
|
+
if (event.kind === "turn/completed") {
|
|
171
399
|
turnId = null;
|
|
172
400
|
turnInFlight = false;
|
|
173
401
|
turnSettled = true;
|
|
174
402
|
emitState();
|
|
403
|
+
await expireTurnServerRequests({
|
|
404
|
+
threadId: threadId ?? event.threadId,
|
|
405
|
+
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
406
|
+
});
|
|
175
407
|
}
|
|
176
408
|
if (event.kind === "error") {
|
|
177
409
|
turnInFlight = false;
|
|
@@ -180,53 +412,51 @@ export function createCodexHostRuntime(args) {
|
|
|
180
412
|
turnId = null;
|
|
181
413
|
}
|
|
182
414
|
emitState();
|
|
415
|
+
await expireTurnServerRequests({
|
|
416
|
+
threadId: threadId ?? event.threadId,
|
|
417
|
+
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const pendingServerRequest = parseManagedServerRequestFromEvent(event);
|
|
421
|
+
if (pendingServerRequest) {
|
|
422
|
+
const persistedThreadId = threadId;
|
|
423
|
+
if (persistedThreadId) {
|
|
424
|
+
await registerPendingServerRequest({
|
|
425
|
+
...pendingServerRequest,
|
|
426
|
+
threadId: persistedThreadId,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
183
429
|
}
|
|
184
430
|
args.handlers?.onEvent?.(event);
|
|
185
431
|
const persistedThreadId = threadId;
|
|
186
432
|
if (!persistedThreadId) {
|
|
187
433
|
return;
|
|
188
434
|
}
|
|
189
|
-
const delta = event
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
createdAt: event.createdAt,
|
|
198
|
-
threadId: persistedThreadId,
|
|
199
|
-
turnId: event.turnId,
|
|
200
|
-
streamId: event.streamId,
|
|
201
|
-
}
|
|
202
|
-
: {
|
|
203
|
-
type: "lifecycle_event",
|
|
204
|
-
eventId: event.eventId,
|
|
205
|
-
kind: normalizeIngestKind(event.kind),
|
|
206
|
-
payloadJson: event.payloadJson,
|
|
207
|
-
createdAt: event.createdAt,
|
|
208
|
-
threadId: persistedThreadId,
|
|
209
|
-
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
210
|
-
};
|
|
435
|
+
const delta = toIngestDelta(event, persistedThreadId);
|
|
436
|
+
if (!delta) {
|
|
437
|
+
skippedEventCount += 1;
|
|
438
|
+
incrementCount(skippedByKind, event.kind);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
enqueuedEventCount += 1;
|
|
442
|
+
incrementCount(enqueuedByKind, event.kind);
|
|
211
443
|
const forceFlush = event.kind === "turn/completed" ||
|
|
212
|
-
event.kind === "codex/event/turn_aborted" ||
|
|
213
444
|
event.kind === "error";
|
|
214
445
|
await enqueueIngestDelta(delta, forceFlush);
|
|
215
446
|
},
|
|
216
447
|
onGlobalMessage: async (message) => {
|
|
448
|
+
if (isChatgptAuthTokensRefreshRequest(message)) {
|
|
449
|
+
registerPendingAuthTokensRefreshRequest({
|
|
450
|
+
requestId: message.id,
|
|
451
|
+
params: message.params,
|
|
452
|
+
createdAt: Date.now(),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
217
455
|
if (isResponse(message) && typeof message.id === "number") {
|
|
218
456
|
const pending = pendingRequests.get(message.id);
|
|
219
457
|
pendingRequests.delete(message.id);
|
|
220
|
-
if (pending
|
|
221
|
-
|
|
222
|
-
message.result &&
|
|
223
|
-
typeof message.result === "object" &&
|
|
224
|
-
"thread" in message.result &&
|
|
225
|
-
typeof message.result.thread === "object" &&
|
|
226
|
-
message.result.thread !== null &&
|
|
227
|
-
"id" in message.result.thread &&
|
|
228
|
-
typeof message.result.thread.id === "string") {
|
|
229
|
-
runtimeThreadId = message.result.thread.id;
|
|
458
|
+
if (pending) {
|
|
459
|
+
setRuntimeThreadFromResponse(message, pending.method);
|
|
230
460
|
}
|
|
231
461
|
if (message.error && pending?.method === "turn/start") {
|
|
232
462
|
turnInFlight = false;
|
|
@@ -234,6 +464,15 @@ export function createCodexHostRuntime(args) {
|
|
|
234
464
|
turnId = null;
|
|
235
465
|
emitState();
|
|
236
466
|
}
|
|
467
|
+
if (pending?.resolve) {
|
|
468
|
+
if (message.error) {
|
|
469
|
+
const code = typeof message.error.code === "number" ? String(message.error.code) : "UNKNOWN";
|
|
470
|
+
pending.reject?.(new Error(`[${code}] ${message.error.message}`));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
pending.resolve(message);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
237
476
|
}
|
|
238
477
|
args.handlers?.onGlobalMessage?.(message);
|
|
239
478
|
},
|
|
@@ -245,23 +484,53 @@ export function createCodexHostRuntime(args) {
|
|
|
245
484
|
onProcessExit: (code) => {
|
|
246
485
|
emitState(`codex exited with code ${String(code)}`);
|
|
247
486
|
},
|
|
248
|
-
}
|
|
487
|
+
};
|
|
488
|
+
bridge = args.bridgeFactory
|
|
489
|
+
? args.bridgeFactory(bridgeConfig, bridgeHandlers)
|
|
490
|
+
: new CodexLocalBridge(bridgeConfig, bridgeHandlers);
|
|
249
491
|
bridge.start();
|
|
250
|
-
sendMessage(
|
|
492
|
+
sendMessage(buildInitializeRequestWithCapabilities(requestId(), {
|
|
251
493
|
name: "codex_local_host_runtime",
|
|
252
494
|
title: "Codex Local Host Runtime",
|
|
253
495
|
version: "0.1.0",
|
|
496
|
+
}, {
|
|
497
|
+
experimentalApi: Array.isArray(startArgs.dynamicTools) && startArgs.dynamicTools.length > 0,
|
|
254
498
|
}), "initialize");
|
|
255
499
|
sendMessage(buildInitializedNotification());
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
500
|
+
const strategy = startArgs.threadStrategy ?? "start";
|
|
501
|
+
if ((strategy === "resume" || strategy === "fork") && !startArgs.runtimeThreadId) {
|
|
502
|
+
throw new Error(`runtimeThreadId is required when threadStrategy=\"${strategy}\".`);
|
|
503
|
+
}
|
|
504
|
+
if (strategy === "start") {
|
|
505
|
+
sendMessage(buildThreadStartRequest(requestId(), {
|
|
506
|
+
...(startArgs.model ? { model: startArgs.model } : {}),
|
|
507
|
+
...(startArgs.cwd ? { cwd: startArgs.cwd } : {}),
|
|
508
|
+
...(startArgs.dynamicTools ? { dynamicTools: startArgs.dynamicTools } : {}),
|
|
509
|
+
}), "thread/start");
|
|
510
|
+
}
|
|
511
|
+
else if (strategy === "resume") {
|
|
512
|
+
sendMessage(buildThreadResumeRequest(requestId(), {
|
|
513
|
+
threadId: startArgs.runtimeThreadId,
|
|
514
|
+
...(startArgs.model ? { model: startArgs.model } : {}),
|
|
515
|
+
...(startArgs.cwd ? { cwd: startArgs.cwd } : {}),
|
|
516
|
+
...(startArgs.dynamicTools ? { dynamicTools: startArgs.dynamicTools } : {}),
|
|
517
|
+
}), "thread/resume");
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
sendMessage(buildThreadForkRequest(requestId(), {
|
|
521
|
+
threadId: startArgs.runtimeThreadId,
|
|
522
|
+
...(startArgs.model ? { model: startArgs.model } : {}),
|
|
523
|
+
...(startArgs.cwd ? { cwd: startArgs.cwd } : {}),
|
|
524
|
+
}), "thread/fork");
|
|
525
|
+
}
|
|
260
526
|
emitState();
|
|
261
527
|
};
|
|
262
528
|
const stop = async () => {
|
|
263
529
|
clearFlushTimer();
|
|
264
530
|
await flushQueue();
|
|
531
|
+
for (const [, pending] of pendingRequests) {
|
|
532
|
+
pending.reject?.(new Error("Bridge stopped before request completed."));
|
|
533
|
+
}
|
|
265
534
|
bridge?.stop();
|
|
266
535
|
bridge = null;
|
|
267
536
|
actor = null;
|
|
@@ -274,6 +543,9 @@ export function createCodexHostRuntime(args) {
|
|
|
274
543
|
turnSettled = false;
|
|
275
544
|
interruptRequested = false;
|
|
276
545
|
pendingRequests.clear();
|
|
546
|
+
pendingServerRequests.clear();
|
|
547
|
+
pendingAuthTokensRefreshRequests.clear();
|
|
548
|
+
resetIngestMetrics();
|
|
277
549
|
emitState();
|
|
278
550
|
};
|
|
279
551
|
const sendTurn = (text) => {
|
|
@@ -301,12 +573,112 @@ export function createCodexHostRuntime(args) {
|
|
|
301
573
|
}
|
|
302
574
|
sendMessage(buildTurnInterruptRequest(requestId(), { threadId: runtimeThreadId, turnId }), "turn/interrupt");
|
|
303
575
|
};
|
|
576
|
+
const resumeThread = async (nextRuntimeThreadId, params) => {
|
|
577
|
+
throwIfTurnMutationLocked();
|
|
578
|
+
return sendRequest(buildThreadResumeRequest(requestId(), {
|
|
579
|
+
threadId: nextRuntimeThreadId,
|
|
580
|
+
...(params ?? {}),
|
|
581
|
+
}));
|
|
582
|
+
};
|
|
583
|
+
const forkThread = async (sourceRuntimeThreadId, params) => {
|
|
584
|
+
throwIfTurnMutationLocked();
|
|
585
|
+
return sendRequest(buildThreadForkRequest(requestId(), {
|
|
586
|
+
threadId: sourceRuntimeThreadId,
|
|
587
|
+
...(params ?? {}),
|
|
588
|
+
}));
|
|
589
|
+
};
|
|
590
|
+
const archiveThread = async (targetRuntimeThreadId) => {
|
|
591
|
+
throwIfTurnMutationLocked();
|
|
592
|
+
return sendRequest(buildThreadArchiveRequest(requestId(), {
|
|
593
|
+
threadId: targetRuntimeThreadId,
|
|
594
|
+
}));
|
|
595
|
+
};
|
|
596
|
+
const unarchiveThread = async (targetRuntimeThreadId) => {
|
|
597
|
+
throwIfTurnMutationLocked();
|
|
598
|
+
return sendRequest(buildThreadUnarchiveRequest(requestId(), {
|
|
599
|
+
threadId: targetRuntimeThreadId,
|
|
600
|
+
}));
|
|
601
|
+
};
|
|
602
|
+
const rollbackThread = async (targetRuntimeThreadId, numTurns) => {
|
|
603
|
+
throwIfTurnMutationLocked();
|
|
604
|
+
return sendRequest(buildThreadRollbackRequest(requestId(), {
|
|
605
|
+
threadId: targetRuntimeThreadId,
|
|
606
|
+
numTurns,
|
|
607
|
+
}));
|
|
608
|
+
};
|
|
609
|
+
const readThread = async (targetRuntimeThreadId, includeTurns = false) => sendRequest(buildThreadReadRequest(requestId(), {
|
|
610
|
+
threadId: targetRuntimeThreadId,
|
|
611
|
+
includeTurns,
|
|
612
|
+
}));
|
|
613
|
+
const readAccount = async (params) => sendRequest(buildAccountReadRequest(requestId(), params));
|
|
614
|
+
const loginAccount = async (params) => sendRequest(buildAccountLoginStartRequest(requestId(), params));
|
|
615
|
+
const cancelAccountLogin = async (params) => sendRequest(buildAccountLoginCancelRequest(requestId(), params));
|
|
616
|
+
const logoutAccount = async () => sendRequest(buildAccountLogoutRequest(requestId()));
|
|
617
|
+
const readAccountRateLimits = async () => sendRequest(buildAccountRateLimitsReadRequest(requestId()));
|
|
618
|
+
const listThreads = async (params) => sendRequest(buildThreadListRequest(requestId(), params));
|
|
619
|
+
const listLoadedThreads = async (params) => sendRequest(buildThreadLoadedListRequest(requestId(), params));
|
|
620
|
+
const listPendingServerRequests = async (threadIdFilter) => {
|
|
621
|
+
if (actor) {
|
|
622
|
+
return args.persistence.listPendingServerRequests({
|
|
623
|
+
actor,
|
|
624
|
+
...(threadIdFilter ? { threadId: threadIdFilter } : {}),
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return [];
|
|
628
|
+
};
|
|
629
|
+
const respondCommandApproval = async (argsForDecision) => {
|
|
630
|
+
const pending = getPendingServerRequest(argsForDecision.requestId);
|
|
631
|
+
if (pending.method !== "item/commandExecution/requestApproval") {
|
|
632
|
+
throw new Error(`Server request ${String(argsForDecision.requestId)} is ${pending.method}, expected item/commandExecution/requestApproval`);
|
|
633
|
+
}
|
|
634
|
+
await sendServerRequestResponse(argsForDecision.requestId, buildCommandExecutionApprovalResponse(argsForDecision.requestId, argsForDecision.decision));
|
|
635
|
+
};
|
|
636
|
+
const respondFileChangeApproval = async (argsForDecision) => {
|
|
637
|
+
const pending = getPendingServerRequest(argsForDecision.requestId);
|
|
638
|
+
if (pending.method !== "item/fileChange/requestApproval") {
|
|
639
|
+
throw new Error(`Server request ${String(argsForDecision.requestId)} is ${pending.method}, expected item/fileChange/requestApproval`);
|
|
640
|
+
}
|
|
641
|
+
await sendServerRequestResponse(argsForDecision.requestId, buildFileChangeApprovalResponse(argsForDecision.requestId, argsForDecision.decision));
|
|
642
|
+
};
|
|
643
|
+
const respondToolUserInput = async (argsForAnswer) => {
|
|
644
|
+
const pending = getPendingServerRequest(argsForAnswer.requestId);
|
|
645
|
+
if (pending.method !== "item/tool/requestUserInput") {
|
|
646
|
+
throw new Error(`Server request ${String(argsForAnswer.requestId)} is ${pending.method}, expected item/tool/requestUserInput`);
|
|
647
|
+
}
|
|
648
|
+
await sendServerRequestResponse(argsForAnswer.requestId, buildToolRequestUserInputResponse(argsForAnswer.requestId, argsForAnswer.answers));
|
|
649
|
+
};
|
|
650
|
+
const respondDynamicToolCall = async (argsForResult) => {
|
|
651
|
+
const pending = getPendingServerRequest(argsForResult.requestId);
|
|
652
|
+
if (pending.method !== "item/tool/call") {
|
|
653
|
+
throw new Error(`Server request ${String(argsForResult.requestId)} is ${pending.method}, expected item/tool/call`);
|
|
654
|
+
}
|
|
655
|
+
await sendServerRequestResponse(argsForResult.requestId, buildDynamicToolCallResponse(argsForResult.requestId, {
|
|
656
|
+
success: argsForResult.success,
|
|
657
|
+
contentItems: argsForResult.contentItems,
|
|
658
|
+
}));
|
|
659
|
+
};
|
|
660
|
+
const respondChatgptAuthTokensRefresh = async (argsForTokens) => {
|
|
661
|
+
getPendingAuthTokensRefreshRequest(argsForTokens.requestId);
|
|
662
|
+
const responseMessage = buildChatgptAuthTokensRefreshResponse(argsForTokens.requestId, {
|
|
663
|
+
idToken: argsForTokens.idToken,
|
|
664
|
+
accessToken: argsForTokens.accessToken,
|
|
665
|
+
});
|
|
666
|
+
sendMessage(responseMessage);
|
|
667
|
+
resolvePendingAuthTokensRefreshRequest(argsForTokens.requestId);
|
|
668
|
+
};
|
|
304
669
|
const getState = () => ({
|
|
305
670
|
running: !!bridge,
|
|
306
671
|
threadId,
|
|
307
672
|
externalThreadId,
|
|
308
673
|
turnId,
|
|
309
674
|
turnInFlight,
|
|
675
|
+
pendingServerRequestCount: pendingServerRequests.size,
|
|
676
|
+
ingestMetrics: {
|
|
677
|
+
enqueuedEventCount,
|
|
678
|
+
skippedEventCount,
|
|
679
|
+
enqueuedByKind: snapshotKindCounts(enqueuedByKind),
|
|
680
|
+
skippedByKind: snapshotKindCounts(skippedByKind),
|
|
681
|
+
},
|
|
310
682
|
lastError: null,
|
|
311
683
|
});
|
|
312
684
|
return {
|
|
@@ -314,6 +686,25 @@ export function createCodexHostRuntime(args) {
|
|
|
314
686
|
stop,
|
|
315
687
|
sendTurn,
|
|
316
688
|
interrupt,
|
|
689
|
+
resumeThread,
|
|
690
|
+
forkThread,
|
|
691
|
+
archiveThread,
|
|
692
|
+
unarchiveThread,
|
|
693
|
+
rollbackThread,
|
|
694
|
+
readThread,
|
|
695
|
+
readAccount,
|
|
696
|
+
loginAccount,
|
|
697
|
+
cancelAccountLogin,
|
|
698
|
+
logoutAccount,
|
|
699
|
+
readAccountRateLimits,
|
|
700
|
+
listThreads,
|
|
701
|
+
listLoadedThreads,
|
|
702
|
+
listPendingServerRequests,
|
|
703
|
+
respondCommandApproval,
|
|
704
|
+
respondFileChangeApproval,
|
|
705
|
+
respondToolUserInput,
|
|
706
|
+
respondDynamicToolCall,
|
|
707
|
+
respondChatgptAuthTokensRefresh,
|
|
317
708
|
getState,
|
|
318
709
|
};
|
|
319
710
|
}
|