@oai404iao/pi-codex-minimal-tools 1.3.0 → 1.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 +21 -0
- package/package.json +6 -2
- package/src/codex-identity-extension.ts +339 -0
- package/src/codex-wire-identity.ts +532 -118
- package/src/index.ts +2 -0
- package/src/provider-shim.ts +270 -97
- package/src/subagent-inline.ts +8 -0
- package/src/tools/web-search.ts +37 -7
|
@@ -1,182 +1,596 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//! requests in the same session can replay it.
|
|
11
|
-
|
|
12
|
-
const WIRE_IDENTITY_CACHE_LIMIT = 1024;
|
|
1
|
+
import { uuidv7 } from "@earendil-works/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Persistent Codex identity written by pi-codex-minimal-tools.
|
|
13
|
+
*
|
|
14
|
+
* `piSessionId` is only the lookup/binding key for Pi's session file. It is
|
|
15
|
+
* never emitted on the Codex wire.
|
|
16
|
+
*/
|
|
17
|
+
export interface CodexThreadIdentity {
|
|
18
|
+
version: 1;
|
|
19
|
+
piSessionId: string;
|
|
16
20
|
sessionId: string;
|
|
17
|
-
/** Stable UUID v7 for the thread (defaults to a per-thread value). */
|
|
18
21
|
threadId: string;
|
|
19
|
-
/** UUID v7 for the current auto-compact window; rotated on compaction. */
|
|
20
22
|
windowId: string;
|
|
23
|
+
firstWindowId: string;
|
|
24
|
+
previousWindowId?: string;
|
|
25
|
+
windowNumber: number;
|
|
26
|
+
parentThreadId?: string;
|
|
27
|
+
forkedFromThreadId?: string;
|
|
28
|
+
agentName?: string;
|
|
29
|
+
subagentKind?: string;
|
|
30
|
+
lastCompactionEntryId?: string;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
interface
|
|
33
|
+
export interface CodexWireIdentity {
|
|
34
|
+
sessionId: string;
|
|
24
35
|
threadId: string;
|
|
25
36
|
windowId: string;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
interface
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
export interface CodexTurnIdentity {
|
|
40
|
+
turnId: string;
|
|
41
|
+
startedAtMs: number;
|
|
42
|
+
parentTurnId?: string;
|
|
43
|
+
rootTurnId?: string;
|
|
44
|
+
turnState?: string;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
export interface CodexRequestIdentity extends CodexWireIdentity {
|
|
48
|
+
installationId: string;
|
|
49
|
+
turnId: string;
|
|
50
|
+
turnStartedAtMs?: number;
|
|
51
|
+
requestKind: "turn" | "compaction" | "prewarm";
|
|
52
|
+
parentThreadId?: string;
|
|
53
|
+
forkedFromThreadId?: string;
|
|
54
|
+
parentTurnId?: string;
|
|
55
|
+
rootTurnId?: string;
|
|
56
|
+
agentName?: string;
|
|
57
|
+
subagentKind?: string;
|
|
58
|
+
turnState?: string;
|
|
59
|
+
}
|
|
36
60
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
const bytes = new Uint8Array(count);
|
|
44
|
-
for (let index = 0; index < count; index++) {
|
|
45
|
-
bytes[index] = Math.floor(Math.random() * 256);
|
|
46
|
-
}
|
|
47
|
-
return bytes;
|
|
61
|
+
interface RuntimeSession {
|
|
62
|
+
identity: CodexThreadIdentity;
|
|
63
|
+
activeTurn?: CodexTurnIdentity;
|
|
64
|
+
pendingTurnState?: string;
|
|
65
|
+
extraThreads: Map<string, CodexThreadIdentity>;
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
interface CodexIdentityRuntime {
|
|
69
|
+
sessions: Map<string, RuntimeSession>;
|
|
70
|
+
installationId?: string;
|
|
71
|
+
installationPath?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const RUNTIME_SYMBOL = Symbol.for("@oai404iao/pi-codex/identity-runtime/v1");
|
|
75
|
+
const UUID_PATTERN =
|
|
76
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
77
|
+
const UUID_V7_PATTERN =
|
|
78
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
79
|
+
const INSTALLATION_UUID_V4_PATTERN =
|
|
80
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
81
|
+
|
|
82
|
+
function runtime(): CodexIdentityRuntime {
|
|
83
|
+
const globals = globalThis as typeof globalThis & {
|
|
84
|
+
[RUNTIME_SYMBOL]?: CodexIdentityRuntime;
|
|
85
|
+
};
|
|
86
|
+
if (!globals[RUNTIME_SYMBOL]) {
|
|
87
|
+
globals[RUNTIME_SYMBOL] = {
|
|
88
|
+
sessions: new Map(),
|
|
89
|
+
};
|
|
54
90
|
}
|
|
55
|
-
return [
|
|
56
|
-
hex.slice(0, 4).join(""),
|
|
57
|
-
hex.slice(4, 6).join(""),
|
|
58
|
-
hex.slice(6, 8).join(""),
|
|
59
|
-
hex.slice(8, 10).join(""),
|
|
60
|
-
hex.slice(10, 16).join(""),
|
|
61
|
-
].join("-");
|
|
91
|
+
return globals[RUNTIME_SYMBOL];
|
|
62
92
|
}
|
|
63
93
|
|
|
64
|
-
/**
|
|
94
|
+
/** Pi-owned identities are generated as RFC 9562 UUIDv7 values. */
|
|
65
95
|
export function uuidV7(): string {
|
|
66
|
-
|
|
67
|
-
const timestamp = Date.now();
|
|
68
|
-
bytes[0] = (timestamp / 0x10000000000) & 0xff;
|
|
69
|
-
bytes[1] = (timestamp / 0x100000000) & 0xff;
|
|
70
|
-
bytes[2] = (timestamp / 0x1000000) & 0xff;
|
|
71
|
-
bytes[3] = (timestamp / 0x10000) & 0xff;
|
|
72
|
-
bytes[4] = (timestamp / 0x100) & 0xff;
|
|
73
|
-
bytes[5] = timestamp & 0xff;
|
|
74
|
-
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x70;
|
|
75
|
-
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
|
|
76
|
-
return uuidBytesToHex(bytes);
|
|
96
|
+
return uuidv7();
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
function uuidV4(): string {
|
|
80
|
-
|
|
100
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
101
|
+
return globalThis.crypto.randomUUID();
|
|
102
|
+
}
|
|
103
|
+
const bytes = new Uint8Array(16);
|
|
104
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
105
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
106
|
+
} else {
|
|
107
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
108
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
81
111
|
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;
|
|
82
112
|
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
|
|
83
|
-
|
|
113
|
+
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
114
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isUuid(value: unknown): value is string {
|
|
118
|
+
return typeof value === "string" && UUID_PATTERN.test(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isUuidV7(value: unknown): value is string {
|
|
122
|
+
return typeof value === "string" && UUID_V7_PATTERN.test(value);
|
|
84
123
|
}
|
|
85
124
|
|
|
86
125
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
126
|
+
* Codex has one installation id for the installation, not one per Pi session.
|
|
127
|
+
*
|
|
128
|
+
* It is loaded and repaired synchronously because request payload assembly is
|
|
129
|
+
* synchronous at this boundary.
|
|
90
130
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
131
|
+
function installationIdPath(): string {
|
|
132
|
+
return process.env.PI_CODEX_INSTALLATION_ID_PATH
|
|
133
|
+
?? join(
|
|
134
|
+
getAgentDir(),
|
|
135
|
+
"pi-codex-minimal-tools",
|
|
136
|
+
"installation_id",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function codexInstallationIdFor(_sessionKey?: string): string {
|
|
141
|
+
const state = runtime();
|
|
142
|
+
const path = installationIdPath();
|
|
143
|
+
if (state.installationId && state.installationPath === path) {
|
|
144
|
+
return state.installationId;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const persisted = readFileSync(path, "utf8").trim();
|
|
148
|
+
if (INSTALLATION_UUID_V4_PATTERN.test(persisted)) {
|
|
149
|
+
state.installationId = persisted;
|
|
150
|
+
state.installationPath = path;
|
|
151
|
+
return persisted;
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// Missing/unreadable state is repaired below.
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const generated = uuidV4();
|
|
158
|
+
try {
|
|
159
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
160
|
+
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
161
|
+
writeFileSync(temporary, `${generated}\n`, {
|
|
162
|
+
encoding: "utf8",
|
|
163
|
+
mode: 0o600,
|
|
164
|
+
});
|
|
165
|
+
renameSync(temporary, path);
|
|
166
|
+
} catch {
|
|
167
|
+
// Read-only environments retain the installation id for this process.
|
|
168
|
+
}
|
|
169
|
+
state.installationId = generated;
|
|
170
|
+
state.installationPath = path;
|
|
171
|
+
return state.installationId;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function setCodexInstallationId(value: string): void {
|
|
175
|
+
if (!INSTALLATION_UUID_V4_PATTERN.test(value)) {
|
|
176
|
+
throw new Error("Codex installation id must be a UUIDv4");
|
|
98
177
|
}
|
|
99
|
-
|
|
178
|
+
runtime().installationId = value;
|
|
179
|
+
runtime().installationPath = installationIdPath();
|
|
100
180
|
}
|
|
101
181
|
|
|
102
|
-
function
|
|
182
|
+
function newRootIdentity(piSessionId: string, forkedFromThreadId?: string): CodexThreadIdentity {
|
|
183
|
+
const threadId = uuidV7();
|
|
184
|
+
const windowId = uuidV7();
|
|
103
185
|
return {
|
|
104
|
-
|
|
105
|
-
|
|
186
|
+
version: 1,
|
|
187
|
+
piSessionId,
|
|
188
|
+
sessionId: threadId,
|
|
189
|
+
threadId,
|
|
190
|
+
windowId,
|
|
191
|
+
firstWindowId: windowId,
|
|
192
|
+
windowNumber: 0,
|
|
193
|
+
...(forkedFromThreadId ? { forkedFromThreadId } : {}),
|
|
194
|
+
agentName: "root",
|
|
106
195
|
};
|
|
107
196
|
}
|
|
108
197
|
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
turnStateCache.clear();
|
|
115
|
-
}
|
|
116
|
-
session = newCachedSession();
|
|
117
|
-
sessionCache.set(sessionKey, session);
|
|
118
|
-
}
|
|
119
|
-
return session;
|
|
198
|
+
export function createCodexRootIdentity(
|
|
199
|
+
piSessionId: string,
|
|
200
|
+
options: { forkedFromThreadId?: string } = {},
|
|
201
|
+
): CodexThreadIdentity {
|
|
202
|
+
return newRootIdentity(piSessionId, options.forkedFromThreadId);
|
|
120
203
|
}
|
|
121
204
|
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
205
|
+
export function createCodexChildIdentity(
|
|
206
|
+
piSessionId: string,
|
|
207
|
+
parent: CodexThreadIdentity,
|
|
208
|
+
options: {
|
|
209
|
+
relation: "spawn" | "fork";
|
|
210
|
+
agentName?: string;
|
|
211
|
+
subagentKind?: string;
|
|
212
|
+
},
|
|
213
|
+
): CodexThreadIdentity {
|
|
214
|
+
const threadId = uuidV7();
|
|
215
|
+
const windowId = uuidV7();
|
|
216
|
+
return {
|
|
217
|
+
version: 1,
|
|
218
|
+
piSessionId,
|
|
219
|
+
sessionId: parent.sessionId,
|
|
220
|
+
threadId,
|
|
221
|
+
windowId,
|
|
222
|
+
firstWindowId: windowId,
|
|
223
|
+
windowNumber: 0,
|
|
224
|
+
parentThreadId: parent.threadId,
|
|
225
|
+
...(options.relation === "fork"
|
|
226
|
+
? { forkedFromThreadId: parent.threadId }
|
|
227
|
+
: {}),
|
|
228
|
+
...(options.agentName ? { agentName: options.agentName } : {}),
|
|
229
|
+
subagentKind: options.subagentKind ?? "collab_spawn",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function cloneIdentity(identity: CodexThreadIdentity): CodexThreadIdentity {
|
|
234
|
+
return structuredClone(identity);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function registerCodexThreadIdentity(identity: CodexThreadIdentity): CodexThreadIdentity {
|
|
238
|
+
const parsed = parseCodexThreadIdentity(identity);
|
|
239
|
+
const existing = runtime().sessions.get(parsed.piSessionId);
|
|
240
|
+
runtime().sessions.set(parsed.piSessionId, {
|
|
241
|
+
identity: cloneIdentity(parsed),
|
|
242
|
+
activeTurn: existing?.activeTurn,
|
|
243
|
+
pendingTurnState: existing?.pendingTurnState,
|
|
244
|
+
extraThreads: existing?.extraThreads ?? new Map(),
|
|
245
|
+
});
|
|
246
|
+
return cloneIdentity(parsed);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function codexThreadIdentityFor(piSessionId: string | undefined): CodexThreadIdentity | undefined {
|
|
250
|
+
if (!piSessionId) return undefined;
|
|
251
|
+
const value = runtime().sessions.get(piSessionId)?.identity;
|
|
252
|
+
return value ? cloneIdentity(value) : undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function ensureFallbackSession(piSessionId: string): RuntimeSession {
|
|
256
|
+
let state = runtime().sessions.get(piSessionId);
|
|
257
|
+
if (!state) {
|
|
258
|
+
state = {
|
|
259
|
+
identity: newRootIdentity(piSessionId),
|
|
260
|
+
extraThreads: new Map(),
|
|
261
|
+
};
|
|
262
|
+
runtime().sessions.set(piSessionId, state);
|
|
127
263
|
}
|
|
128
|
-
return
|
|
264
|
+
return state;
|
|
129
265
|
}
|
|
130
266
|
|
|
131
267
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
268
|
+
* Compatibility resolver used by tests and legacy call sites.
|
|
269
|
+
*
|
|
270
|
+
* A root Codex thread has `sessionId === threadId`. Additional explicit
|
|
271
|
+
* thread keys share that root session while receiving their own thread/window.
|
|
136
272
|
*/
|
|
137
273
|
export function resolveCodexWireIdentity(
|
|
138
|
-
|
|
274
|
+
piSessionId: string,
|
|
139
275
|
threadKey?: string,
|
|
140
276
|
): CodexWireIdentity {
|
|
141
|
-
const session =
|
|
142
|
-
|
|
277
|
+
const session = ensureFallbackSession(piSessionId);
|
|
278
|
+
if (
|
|
279
|
+
!threadKey
|
|
280
|
+
|| threadKey === piSessionId
|
|
281
|
+
|| threadKey === session.identity.threadId
|
|
282
|
+
) {
|
|
283
|
+
return {
|
|
284
|
+
sessionId: session.identity.sessionId,
|
|
285
|
+
threadId: session.identity.threadId,
|
|
286
|
+
windowId: session.identity.windowId,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
let thread = session.extraThreads.get(threadKey);
|
|
290
|
+
if (!thread) {
|
|
291
|
+
const threadId = uuidV7();
|
|
292
|
+
const windowId = uuidV7();
|
|
293
|
+
thread = {
|
|
294
|
+
version: 1,
|
|
295
|
+
piSessionId,
|
|
296
|
+
sessionId: session.identity.sessionId,
|
|
297
|
+
threadId,
|
|
298
|
+
windowId,
|
|
299
|
+
firstWindowId: windowId,
|
|
300
|
+
windowNumber: 0,
|
|
301
|
+
};
|
|
302
|
+
session.extraThreads.set(threadKey, thread);
|
|
303
|
+
}
|
|
143
304
|
return {
|
|
144
|
-
sessionId:
|
|
305
|
+
sessionId: thread.sessionId,
|
|
145
306
|
threadId: thread.threadId,
|
|
146
307
|
windowId: thread.windowId,
|
|
147
308
|
};
|
|
148
309
|
}
|
|
149
310
|
|
|
311
|
+
export function rotateCodexWindowId(piSessionId: string, threadKey?: string): void {
|
|
312
|
+
const session = ensureFallbackSession(piSessionId);
|
|
313
|
+
const identity =
|
|
314
|
+
threadKey && threadKey !== piSessionId && threadKey !== session.identity.threadId
|
|
315
|
+
? session.extraThreads.get(threadKey)
|
|
316
|
+
: session.identity;
|
|
317
|
+
if (!identity) {
|
|
318
|
+
resolveCodexWireIdentity(piSessionId, threadKey);
|
|
319
|
+
return rotateCodexWindowId(piSessionId, threadKey);
|
|
320
|
+
}
|
|
321
|
+
identity.previousWindowId = identity.windowId;
|
|
322
|
+
identity.windowId = uuidV7();
|
|
323
|
+
identity.windowNumber += 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function advanceCodexWindow(
|
|
327
|
+
piSessionId: string,
|
|
328
|
+
compactionEntryId?: string,
|
|
329
|
+
): CodexThreadIdentity {
|
|
330
|
+
const state = ensureFallbackSession(piSessionId);
|
|
331
|
+
if (
|
|
332
|
+
compactionEntryId
|
|
333
|
+
&& state.identity.lastCompactionEntryId === compactionEntryId
|
|
334
|
+
) {
|
|
335
|
+
return cloneIdentity(state.identity);
|
|
336
|
+
}
|
|
337
|
+
state.identity.previousWindowId = state.identity.windowId;
|
|
338
|
+
state.identity.windowId = uuidV7();
|
|
339
|
+
state.identity.windowNumber += 1;
|
|
340
|
+
if (compactionEntryId) {
|
|
341
|
+
state.identity.lastCompactionEntryId = compactionEntryId;
|
|
342
|
+
}
|
|
343
|
+
return cloneIdentity(state.identity);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function beginCodexTurn(
|
|
347
|
+
piSessionId: string,
|
|
348
|
+
options: {
|
|
349
|
+
parentPiSessionId?: string;
|
|
350
|
+
turnId?: string;
|
|
351
|
+
startedAtMs?: number;
|
|
352
|
+
} = {},
|
|
353
|
+
): CodexTurnIdentity {
|
|
354
|
+
const state = ensureFallbackSession(piSessionId);
|
|
355
|
+
if (state.activeTurn) return structuredClone(state.activeTurn);
|
|
356
|
+
|
|
357
|
+
const parentTurn = options.parentPiSessionId
|
|
358
|
+
? runtime().sessions.get(options.parentPiSessionId)?.activeTurn
|
|
359
|
+
: undefined;
|
|
360
|
+
const turnId = isUuidV7(options.turnId) ? options.turnId : uuidV7();
|
|
361
|
+
const turn: CodexTurnIdentity = {
|
|
362
|
+
turnId,
|
|
363
|
+
startedAtMs: options.startedAtMs ?? Date.now(),
|
|
364
|
+
...(parentTurn ? { parentTurnId: parentTurn.turnId } : {}),
|
|
365
|
+
rootTurnId: parentTurn?.rootTurnId ?? parentTurn?.turnId ?? turnId,
|
|
366
|
+
...(state.pendingTurnState
|
|
367
|
+
? { turnState: state.pendingTurnState }
|
|
368
|
+
: {}),
|
|
369
|
+
};
|
|
370
|
+
delete state.pendingTurnState;
|
|
371
|
+
state.activeTurn = turn;
|
|
372
|
+
return structuredClone(turn);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function currentCodexTurn(piSessionId: string | undefined): CodexTurnIdentity | undefined {
|
|
376
|
+
if (!piSessionId) return undefined;
|
|
377
|
+
const turn = runtime().sessions.get(piSessionId)?.activeTurn;
|
|
378
|
+
return turn ? structuredClone(turn) : undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function endCodexTurn(piSessionId: string, turnId?: string): void {
|
|
382
|
+
const state = runtime().sessions.get(piSessionId);
|
|
383
|
+
if (!state?.activeTurn) return;
|
|
384
|
+
if (turnId && state.activeTurn.turnId !== turnId) return;
|
|
385
|
+
delete state.activeTurn;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function codexTurnStateFor(piSessionId: string): string | undefined {
|
|
389
|
+
return runtime().sessions.get(piSessionId)?.activeTurn?.turnState;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Store the first sticky-routing token for the active logical turn only. */
|
|
393
|
+
export function captureCodexTurnState(
|
|
394
|
+
piSessionId: string,
|
|
395
|
+
token: string | undefined,
|
|
396
|
+
): void {
|
|
397
|
+
if (typeof token !== "string" || !token.trim()) return;
|
|
398
|
+
const state = ensureFallbackSession(piSessionId);
|
|
399
|
+
if (!state.activeTurn) {
|
|
400
|
+
if (!state.pendingTurnState) state.pendingTurnState = token.trim();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (state.activeTurn.turnState) return;
|
|
404
|
+
state.activeTurn.turnState = token.trim();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function metadataString(
|
|
408
|
+
metadata: Record<string, unknown> | undefined,
|
|
409
|
+
key: string,
|
|
410
|
+
): string | undefined {
|
|
411
|
+
const value = metadata?.[key];
|
|
412
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
413
|
+
}
|
|
414
|
+
|
|
150
415
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
416
|
+
* Resolve one immutable identity snapshot for an actual provider request.
|
|
417
|
+
* Valid explicit UUID values are consumed directly; they are never used as
|
|
418
|
+
* derivation keys.
|
|
154
419
|
*/
|
|
155
|
-
export function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
420
|
+
export function resolveCodexRequestIdentity(
|
|
421
|
+
piSessionId: string | undefined,
|
|
422
|
+
metadata: Record<string, unknown> | undefined,
|
|
423
|
+
requestKind: CodexRequestIdentity["requestKind"] = "turn",
|
|
424
|
+
): CodexRequestIdentity | undefined {
|
|
425
|
+
const explicitSessionId = metadataString(metadata, "session_id");
|
|
426
|
+
const explicitThreadId = metadataString(metadata, "thread_id");
|
|
427
|
+
const explicitWindowId = metadataString(metadata, "window_id")
|
|
428
|
+
?? metadataString(metadata, "x-codex-window-id");
|
|
429
|
+
const state = piSessionId ? ensureFallbackSession(piSessionId) : undefined;
|
|
430
|
+
if (
|
|
431
|
+
!state
|
|
432
|
+
&& (!isUuidV7(explicitSessionId) || !isUuidV7(explicitThreadId))
|
|
433
|
+
) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const identity = state?.identity;
|
|
437
|
+
const sessionId = isUuidV7(explicitSessionId)
|
|
438
|
+
? explicitSessionId
|
|
439
|
+
: identity?.sessionId;
|
|
440
|
+
const threadId = isUuidV7(explicitThreadId)
|
|
441
|
+
? explicitThreadId
|
|
442
|
+
: identity?.threadId;
|
|
443
|
+
const windowId = isUuidV7(explicitWindowId)
|
|
444
|
+
? explicitWindowId
|
|
445
|
+
: identity?.windowId;
|
|
446
|
+
if (!sessionId || !threadId || !windowId) return undefined;
|
|
447
|
+
|
|
448
|
+
let turn = state?.activeTurn;
|
|
449
|
+
const explicitTurnId = metadataString(metadata, "turn_id");
|
|
450
|
+
if (isUuidV7(explicitTurnId)) {
|
|
451
|
+
if (!turn || turn.turnId !== explicitTurnId) {
|
|
452
|
+
turn = {
|
|
453
|
+
turnId: explicitTurnId,
|
|
454
|
+
startedAtMs: Date.now(),
|
|
455
|
+
rootTurnId: explicitTurnId,
|
|
456
|
+
...(state?.pendingTurnState
|
|
457
|
+
? { turnState: state.pendingTurnState }
|
|
458
|
+
: {}),
|
|
459
|
+
};
|
|
460
|
+
if (state) {
|
|
461
|
+
state.activeTurn = turn;
|
|
462
|
+
delete state.pendingTurnState;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} else if (!turn && requestKind !== "prewarm" && piSessionId) {
|
|
466
|
+
turn = beginCodexTurn(piSessionId);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const explicitParentThreadId = metadataString(metadata, "parent_thread_id");
|
|
470
|
+
const explicitForkedFromThreadId = metadataString(metadata, "forked_from_thread_id");
|
|
471
|
+
const explicitParentTurnId = metadataString(metadata, "parent_turn_id");
|
|
472
|
+
const explicitRootTurnId = metadataString(metadata, "root_turn_id");
|
|
473
|
+
return {
|
|
474
|
+
installationId: codexInstallationIdFor(),
|
|
475
|
+
sessionId,
|
|
476
|
+
threadId,
|
|
477
|
+
windowId,
|
|
478
|
+
turnId: turn?.turnId ?? "",
|
|
479
|
+
...(turn ? { turnStartedAtMs: turn.startedAtMs } : {}),
|
|
480
|
+
requestKind,
|
|
481
|
+
...(isUuidV7(explicitParentThreadId)
|
|
482
|
+
? { parentThreadId: explicitParentThreadId }
|
|
483
|
+
: identity?.parentThreadId
|
|
484
|
+
? { parentThreadId: identity.parentThreadId }
|
|
485
|
+
: {}),
|
|
486
|
+
...(isUuidV7(explicitForkedFromThreadId)
|
|
487
|
+
? { forkedFromThreadId: explicitForkedFromThreadId }
|
|
488
|
+
: identity?.forkedFromThreadId
|
|
489
|
+
? { forkedFromThreadId: identity.forkedFromThreadId }
|
|
490
|
+
: {}),
|
|
491
|
+
...(isUuidV7(explicitParentTurnId)
|
|
492
|
+
? { parentTurnId: explicitParentTurnId }
|
|
493
|
+
: turn?.parentTurnId
|
|
494
|
+
? { parentTurnId: turn.parentTurnId }
|
|
495
|
+
: {}),
|
|
496
|
+
...(isUuidV7(explicitRootTurnId)
|
|
497
|
+
? { rootTurnId: explicitRootTurnId }
|
|
498
|
+
: turn?.rootTurnId
|
|
499
|
+
? { rootTurnId: turn.rootTurnId }
|
|
500
|
+
: {}),
|
|
501
|
+
...(identity?.agentName ? { agentName: identity.agentName } : {}),
|
|
502
|
+
...(identity?.subagentKind ? { subagentKind: identity.subagentKind } : {}),
|
|
503
|
+
...(turn?.turnState ? { turnState: turn.turnState } : {}),
|
|
504
|
+
};
|
|
159
505
|
}
|
|
160
506
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
507
|
+
type UnknownRecord = Record<string, unknown>;
|
|
508
|
+
|
|
509
|
+
function asRecord(value: unknown): UnknownRecord | undefined {
|
|
510
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
511
|
+
? value as UnknownRecord
|
|
512
|
+
: undefined;
|
|
164
513
|
}
|
|
165
514
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!
|
|
169
|
-
|
|
515
|
+
export function parseCodexThreadIdentity(value: unknown): CodexThreadIdentity {
|
|
516
|
+
const input = asRecord(value);
|
|
517
|
+
if (!input || input.version !== 1) {
|
|
518
|
+
throw new Error("unsupported Codex thread identity version");
|
|
519
|
+
}
|
|
520
|
+
for (const field of [
|
|
521
|
+
"piSessionId",
|
|
522
|
+
"sessionId",
|
|
523
|
+
"threadId",
|
|
524
|
+
"windowId",
|
|
525
|
+
"firstWindowId",
|
|
526
|
+
] as const) {
|
|
527
|
+
if (typeof input[field] !== "string" || !input[field]) {
|
|
528
|
+
throw new Error(`Codex thread identity ${field} must be a non-empty string`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
for (const field of [
|
|
532
|
+
"sessionId",
|
|
533
|
+
"threadId",
|
|
534
|
+
"windowId",
|
|
535
|
+
"firstWindowId",
|
|
536
|
+
"previousWindowId",
|
|
537
|
+
"parentThreadId",
|
|
538
|
+
"forkedFromThreadId",
|
|
539
|
+
] as const) {
|
|
540
|
+
if (input[field] !== undefined && !isUuidV7(input[field])) {
|
|
541
|
+
throw new Error(`Codex thread identity ${field} must be a UUIDv7`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (
|
|
545
|
+
typeof input.windowNumber !== "number"
|
|
546
|
+
|| !Number.isSafeInteger(input.windowNumber)
|
|
547
|
+
|| input.windowNumber < 0
|
|
548
|
+
) {
|
|
549
|
+
throw new Error("Codex thread identity windowNumber must be a non-negative integer");
|
|
550
|
+
}
|
|
551
|
+
for (const field of [
|
|
552
|
+
"agentName",
|
|
553
|
+
"subagentKind",
|
|
554
|
+
"lastCompactionEntryId",
|
|
555
|
+
] as const) {
|
|
556
|
+
if (input[field] !== undefined && typeof input[field] !== "string") {
|
|
557
|
+
throw new Error(`Codex thread identity ${field} must be a string`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
version: 1,
|
|
562
|
+
piSessionId: input.piSessionId as string,
|
|
563
|
+
sessionId: input.sessionId as string,
|
|
564
|
+
threadId: input.threadId as string,
|
|
565
|
+
windowId: input.windowId as string,
|
|
566
|
+
firstWindowId: input.firstWindowId as string,
|
|
567
|
+
windowNumber: input.windowNumber,
|
|
568
|
+
...(input.previousWindowId
|
|
569
|
+
? { previousWindowId: input.previousWindowId as string }
|
|
570
|
+
: {}),
|
|
571
|
+
...(input.parentThreadId
|
|
572
|
+
? { parentThreadId: input.parentThreadId as string }
|
|
573
|
+
: {}),
|
|
574
|
+
...(input.forkedFromThreadId
|
|
575
|
+
? { forkedFromThreadId: input.forkedFromThreadId as string }
|
|
576
|
+
: {}),
|
|
577
|
+
...(input.agentName ? { agentName: input.agentName as string } : {}),
|
|
578
|
+
...(input.subagentKind
|
|
579
|
+
? { subagentKind: input.subagentKind as string }
|
|
580
|
+
: {}),
|
|
581
|
+
...(input.lastCompactionEntryId
|
|
582
|
+
? { lastCompactionEntryId: input.lastCompactionEntryId as string }
|
|
583
|
+
: {}),
|
|
584
|
+
};
|
|
170
585
|
}
|
|
171
586
|
|
|
172
|
-
/** Forget all derived identity and turn-state state (tests, session teardown). */
|
|
173
587
|
export function resetCodexWireState(): void {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
588
|
+
const state = runtime();
|
|
589
|
+
state.sessions.clear();
|
|
590
|
+
delete state.installationId;
|
|
591
|
+
delete state.installationPath;
|
|
177
592
|
}
|
|
178
593
|
|
|
179
|
-
/** Current number of cached identities (diagnostics/tests). */
|
|
180
594
|
export function codexWireIdentityCount(): number {
|
|
181
|
-
return
|
|
595
|
+
return runtime().sessions.size;
|
|
182
596
|
}
|