@howaboua/pi-codex-conversion 1.0.25 → 1.0.26
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/package.json
CHANGED
|
@@ -105,6 +105,12 @@ interface SessionWebSocketCacheEntry {
|
|
|
105
105
|
idleTimer?: ReturnType<typeof setTimeout>;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
interface AcquiredWebSocket {
|
|
109
|
+
socket: WebSocketLike;
|
|
110
|
+
reused: boolean;
|
|
111
|
+
release: (options?: { keep?: boolean }) => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
108
114
|
let fsPromisesPromise: Promise<typeof import("node:fs/promises")> | undefined;
|
|
109
115
|
const workspaceRootCache = new Map<string, Promise<string>>();
|
|
110
116
|
|
|
@@ -753,12 +759,13 @@ async function acquireWebSocket(
|
|
|
753
759
|
headers: Headers,
|
|
754
760
|
sessionId: string | undefined,
|
|
755
761
|
signal: AbortSignal | undefined,
|
|
756
|
-
): Promise<
|
|
762
|
+
): Promise<AcquiredWebSocket> {
|
|
757
763
|
const cacheKey = buildWebSocketCacheKey(url, headers, sessionId);
|
|
758
764
|
if (!cacheKey) {
|
|
759
765
|
const socket = await connectWebSocket(url, headers, signal);
|
|
760
766
|
return {
|
|
761
767
|
socket,
|
|
768
|
+
reused: false,
|
|
762
769
|
release: ({ keep } = {}) => {
|
|
763
770
|
if (keep === false) {
|
|
764
771
|
closeWebSocketSilently(socket);
|
|
@@ -780,6 +787,7 @@ async function acquireWebSocket(
|
|
|
780
787
|
cached.busy = true;
|
|
781
788
|
return {
|
|
782
789
|
socket: cached.socket,
|
|
790
|
+
reused: true,
|
|
783
791
|
release: ({ keep } = {}) => {
|
|
784
792
|
if (!keep || !isWebSocketReusable(cached.socket)) {
|
|
785
793
|
closeWebSocketSilently(cached.socket);
|
|
@@ -796,6 +804,7 @@ async function acquireWebSocket(
|
|
|
796
804
|
const socket = await connectWebSocket(url, headers, signal);
|
|
797
805
|
return {
|
|
798
806
|
socket,
|
|
807
|
+
reused: false,
|
|
799
808
|
release: () => {
|
|
800
809
|
closeWebSocketSilently(socket);
|
|
801
810
|
},
|
|
@@ -813,6 +822,7 @@ async function acquireWebSocket(
|
|
|
813
822
|
websocketSessionCache.set(cacheKey, entry);
|
|
814
823
|
return {
|
|
815
824
|
socket,
|
|
825
|
+
reused: false,
|
|
816
826
|
release: ({ keep } = {}) => {
|
|
817
827
|
if (!keep || !isWebSocketReusable(entry.socket)) {
|
|
818
828
|
closeWebSocketSilently(entry.socket);
|
|
@@ -948,6 +958,21 @@ async function* parseWebSocket(socket: WebSocketLike, signal: AbortSignal | unde
|
|
|
948
958
|
}
|
|
949
959
|
}
|
|
950
960
|
|
|
961
|
+
async function* countWebSocketEvents(
|
|
962
|
+
events: AsyncIterable<StreamEventShape>,
|
|
963
|
+
onEvent: () => void,
|
|
964
|
+
): AsyncIterable<StreamEventShape> {
|
|
965
|
+
for await (const event of events) {
|
|
966
|
+
onEvent();
|
|
967
|
+
yield event;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function isRetryableEarlyWebSocketError(error: unknown): boolean {
|
|
972
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
973
|
+
return /^WebSocket (error|closed)(?:\s|$)/.test(message);
|
|
974
|
+
}
|
|
975
|
+
|
|
951
976
|
async function* mapCodexEvents(events: AsyncIterable<StreamEventShape>): AsyncIterable<StreamEventShape> {
|
|
952
977
|
let sawTerminalResponse = false;
|
|
953
978
|
for await (const event of events) {
|
|
@@ -1100,22 +1125,58 @@ async function processWebSocketStream<TApi extends Api>(
|
|
|
1100
1125
|
cwd: string,
|
|
1101
1126
|
requestPrompt: string | undefined,
|
|
1102
1127
|
): Promise<void> {
|
|
1103
|
-
|
|
1104
|
-
|
|
1128
|
+
let streamStarted = false;
|
|
1129
|
+
|
|
1130
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1131
|
+
const { socket, release, reused } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
|
|
1132
|
+
let keepConnection = true;
|
|
1133
|
+
let released = false;
|
|
1134
|
+
let eventCount = 0;
|
|
1135
|
+
|
|
1136
|
+
const releaseOnce = (releaseOptions?: { keep?: boolean }) => {
|
|
1137
|
+
if (released) return;
|
|
1138
|
+
released = true;
|
|
1139
|
+
release(releaseOptions);
|
|
1140
|
+
};
|
|
1105
1141
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1142
|
+
try {
|
|
1143
|
+
socket.send(JSON.stringify({ type: "response.create", ...body }));
|
|
1144
|
+
if (!streamStarted) {
|
|
1145
|
+
onStart();
|
|
1146
|
+
stream.push({ type: "start", partial: output });
|
|
1147
|
+
streamStarted = true;
|
|
1148
|
+
}
|
|
1149
|
+
await processCapturedResponsesStream(
|
|
1150
|
+
countWebSocketEvents(parseWebSocket(socket, options?.signal), () => {
|
|
1151
|
+
eventCount++;
|
|
1152
|
+
}),
|
|
1153
|
+
output,
|
|
1154
|
+
stream,
|
|
1155
|
+
model,
|
|
1156
|
+
options,
|
|
1157
|
+
deps,
|
|
1158
|
+
cwd,
|
|
1159
|
+
requestPrompt,
|
|
1160
|
+
);
|
|
1161
|
+
if (options?.signal?.aborted) {
|
|
1162
|
+
keepConnection = false;
|
|
1163
|
+
}
|
|
1164
|
+
releaseOnce({ keep: keepConnection });
|
|
1165
|
+
return;
|
|
1166
|
+
} catch (error) {
|
|
1112
1167
|
keepConnection = false;
|
|
1168
|
+
releaseOnce({ keep: false });
|
|
1169
|
+
// Pi's stock provider reuses session WebSockets. In practice the Codex
|
|
1170
|
+
// backend sometimes cleanly closes an idle cached socket between turns;
|
|
1171
|
+
// if that stale socket fails before any response event, retry once on a
|
|
1172
|
+
// fresh WebSocket without changing request shape or falling back transports.
|
|
1173
|
+
if (attempt === 0 && reused && eventCount === 0 && !options?.signal?.aborted && isRetryableEarlyWebSocketError(error)) {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
throw error;
|
|
1177
|
+
} finally {
|
|
1178
|
+
releaseOnce({ keep: keepConnection });
|
|
1113
1179
|
}
|
|
1114
|
-
} catch (error) {
|
|
1115
|
-
keepConnection = false;
|
|
1116
|
-
throw error;
|
|
1117
|
-
} finally {
|
|
1118
|
-
release({ keep: keepConnection });
|
|
1119
1180
|
}
|
|
1120
1181
|
}
|
|
1121
1182
|
|