@howaboua/pi-codex-conversion 1.0.30 → 1.0.31-dev.4.26e07fb
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 +1 -1
- package/bin/apply_patch +38 -0
- package/bin/apply_patch.cmd +2 -0
- package/package.json +18 -13
- package/src/adapter/codex-model.ts +1 -1
- package/src/index.ts +4 -21
- package/src/providers/openai-codex-custom-provider.ts +74 -26
- package/src/providers/openai-responses-shared.ts +2 -2
- package/src/tools/apply-patch-binary.ts +21 -0
- package/src/tools/apply-patch-rendering.ts +1 -1
- package/src/tools/apply-patch-tool.ts +2 -2
- package/src/tools/exec-command-tool.ts +2 -2
- package/src/tools/image-generation-tool.ts +2 -2
- package/src/tools/view-image-tool.ts +2 -2
- package/src/tools/web-search-tool.ts +2 -21
- package/src/tools/write-stdin-tool.ts +2 -2
- package/vendor/apply-patch/darwin-arm64/apply_patch +0 -0
- package/vendor/apply-patch/darwin-x64/apply_patch +0 -0
- package/vendor/apply-patch/linux-arm64/apply_patch +0 -0
- package/vendor/apply-patch/linux-x64/apply_patch +0 -0
- package/vendor/apply-patch/win32-arm64/apply_patch.exe +0 -0
- package/vendor/apply-patch/win32-x64/apply_patch.exe +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ This package replaces Pi's default Codex/GPT experience with a narrower Codex-li
|
|
|
9
9
|
- preserves Pi's composed system prompt and applies a narrow Codex-oriented delta on top
|
|
10
10
|
- renders exec activity with Codex-style command and background-terminal labels
|
|
11
11
|
- renders `apply_patch` calls with Codex-style `Added` / `Edited` / `Deleted` diff blocks and Pi-style colored diff lines
|
|
12
|
-
- targets modern Pi tool/rendering APIs and is aligned with Pi `0.
|
|
12
|
+
- targets modern Pi tool/rendering APIs and is aligned with Pi `0.74.x` / the `@earendil-works/*` package scope
|
|
13
13
|
|
|
14
14
|

|
|
15
15
|
|
package/bin/apply_patch
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { chmodSync, existsSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const platform = process.platform;
|
|
9
|
+
const arch = process.arch;
|
|
10
|
+
const exe = platform === "win32" ? "apply_patch.exe" : "apply_patch";
|
|
11
|
+
const platformArch = `${platform}-${arch}`;
|
|
12
|
+
const binary = join(root, "vendor", "apply-patch", platformArch, exe);
|
|
13
|
+
|
|
14
|
+
if (!existsSync(binary)) {
|
|
15
|
+
console.error(`apply_patch binary is not bundled for ${platformArch}.`);
|
|
16
|
+
console.error("Expected bundled binaries under vendor/apply-patch/<platform>-<arch>/.");
|
|
17
|
+
process.exit(127);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (platform !== "win32") {
|
|
21
|
+
try {
|
|
22
|
+
chmodSync(binary, 0o755);
|
|
23
|
+
} catch {
|
|
24
|
+
// Best effort. If chmod fails, spawn below will report the real error.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = spawnSync(binary, process.argv.slice(2), {
|
|
29
|
+
stdio: "inherit",
|
|
30
|
+
env: process.env,
|
|
31
|
+
cwd: process.cwd(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (result.error) {
|
|
35
|
+
console.error(result.error.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
process.exit(result.status ?? 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howaboua/pi-codex-conversion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31-dev.4.26e07fb",
|
|
4
4
|
"description": "Codex-oriented tool and prompt adapter for pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -21,10 +21,6 @@
|
|
|
21
21
|
"apply-patch"
|
|
22
22
|
],
|
|
23
23
|
"license": "MIT",
|
|
24
|
-
"os": [
|
|
25
|
-
"darwin",
|
|
26
|
-
"linux"
|
|
27
|
-
],
|
|
28
24
|
"pi": {
|
|
29
25
|
"extensions": [
|
|
30
26
|
"./src/index.ts"
|
|
@@ -33,6 +29,9 @@
|
|
|
33
29
|
"files": [
|
|
34
30
|
"src/**/*.ts",
|
|
35
31
|
"src/**/*.md",
|
|
32
|
+
"bin/apply_patch",
|
|
33
|
+
"bin/apply_patch.cmd",
|
|
34
|
+
"vendor/apply-patch/**",
|
|
36
35
|
"available-tools.png",
|
|
37
36
|
"README.md",
|
|
38
37
|
"LICENSE"
|
|
@@ -45,21 +44,24 @@
|
|
|
45
44
|
"publish:dev": "npm publish --tag dev",
|
|
46
45
|
"release:dev": "npm version prerelease --preid dev && npm publish --tag dev",
|
|
47
46
|
"prepack": "npm run check",
|
|
48
|
-
"prepublishOnly": "npm run check"
|
|
47
|
+
"prepublishOnly": "npm run verify:apply-patch-binaries && npm run check",
|
|
48
|
+
"build:apply-patch": "node scripts/build-apply-patch-binary.mjs",
|
|
49
|
+
"sync:apply-patch-source": "node scripts/sync-apply-patch-source.mjs",
|
|
50
|
+
"verify:apply-patch-binaries": "node scripts/verify-apply-patch-binaries.mjs"
|
|
49
51
|
},
|
|
50
52
|
"publishConfig": {
|
|
51
53
|
"access": "public"
|
|
52
54
|
},
|
|
53
55
|
"peerDependencies": {
|
|
54
|
-
"@
|
|
55
|
-
"@
|
|
56
|
-
"@
|
|
57
|
-
"typebox": "
|
|
56
|
+
"@earendil-works/pi-ai": "*",
|
|
57
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
58
|
+
"@earendil-works/pi-tui": "*",
|
|
59
|
+
"typebox": "*"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
|
-
"@
|
|
61
|
-
"@
|
|
62
|
-
"@
|
|
62
|
+
"@earendil-works/pi-ai": "^0.74.0",
|
|
63
|
+
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
64
|
+
"@earendil-works/pi-tui": "^0.74.0",
|
|
63
65
|
"tsx": "^4.20.5",
|
|
64
66
|
"typebox": "^1.1.24",
|
|
65
67
|
"typescript": "^5.9.3"
|
|
@@ -69,5 +71,8 @@
|
|
|
69
71
|
"partial-json": "^0.1.7",
|
|
70
72
|
"tree-sitter-bash": "^0.25.1",
|
|
71
73
|
"web-tree-sitter": "^0.26.7"
|
|
74
|
+
},
|
|
75
|
+
"bin": {
|
|
76
|
+
"apply_patch": "./bin/apply_patch"
|
|
72
77
|
}
|
|
73
78
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { getCodexRuntimeShell } from "./adapter/runtime-shell.ts";
|
|
3
3
|
import {
|
|
4
4
|
CORE_ADAPTER_TOOL_NAMES,
|
|
@@ -24,21 +24,18 @@ import { buildCodexSystemPrompt, extractPiPromptSkills, type PromptSkill } from
|
|
|
24
24
|
import { registerViewImageTool, supportsOriginalImageDetail } from "./tools/view-image-tool.ts";
|
|
25
25
|
import {
|
|
26
26
|
registerWebSearchTool,
|
|
27
|
-
registerWebSearchSessionNoteRenderer,
|
|
28
27
|
rewriteNativeWebSearchTool,
|
|
29
|
-
shouldShowWebSearchSessionNote,
|
|
30
28
|
supportsNativeWebSearch,
|
|
31
|
-
WEB_SEARCH_SESSION_NOTE_TEXT,
|
|
32
29
|
WEB_SEARCH_SESSION_NOTE_TYPE,
|
|
33
30
|
} from "./tools/web-search-tool.ts";
|
|
34
31
|
import { registerWriteStdinTool } from "./tools/write-stdin-tool.ts";
|
|
32
|
+
import { ensureBundledApplyPatchOnPath } from "./tools/apply-patch-binary.ts";
|
|
35
33
|
|
|
36
34
|
interface AdapterState {
|
|
37
35
|
enabled: boolean;
|
|
38
36
|
cwd: string;
|
|
39
37
|
previousToolNames?: string[];
|
|
40
38
|
promptSkills: PromptSkill[];
|
|
41
|
-
webSearchNoticeShown: boolean;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
const ADAPTER_TOOL_NAMES = [...CORE_ADAPTER_TOOL_NAMES, WEB_SEARCH_TOOL_NAME, IMAGE_GENERATION_TOOL_NAME, VIEW_IMAGE_TOOL_NAME];
|
|
@@ -61,8 +58,9 @@ function isToolCallOnlyAssistantMessage(message: unknown): boolean {
|
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
export default function codexConversion(pi: ExtensionAPI) {
|
|
61
|
+
ensureBundledApplyPatchOnPath();
|
|
64
62
|
const tracker = createExecCommandTracker();
|
|
65
|
-
const state: AdapterState = { enabled: false, cwd: process.cwd(), promptSkills: []
|
|
63
|
+
const state: AdapterState = { enabled: false, cwd: process.cwd(), promptSkills: [] };
|
|
66
64
|
const sessions = createExecSessionManager();
|
|
67
65
|
|
|
68
66
|
registerOpenAICodexCustomProvider(pi, {
|
|
@@ -73,7 +71,6 @@ export default function codexConversion(pi: ExtensionAPI) {
|
|
|
73
71
|
registerWriteStdinTool(pi, sessions);
|
|
74
72
|
registerImageGenerationTool(pi);
|
|
75
73
|
registerWebSearchTool(pi);
|
|
76
|
-
registerWebSearchSessionNoteRenderer(pi);
|
|
77
74
|
|
|
78
75
|
sessions.onSessionExit((sessionId) => {
|
|
79
76
|
tracker.recordSessionFinished(sessionId);
|
|
@@ -81,7 +78,6 @@ export default function codexConversion(pi: ExtensionAPI) {
|
|
|
81
78
|
|
|
82
79
|
pi.on("session_start", async (_event, ctx) => {
|
|
83
80
|
state.cwd = ctx.cwd;
|
|
84
|
-
state.webSearchNoticeShown = false;
|
|
85
81
|
clearApplyPatchRenderState();
|
|
86
82
|
tracker.clear();
|
|
87
83
|
syncAdapter(pi, ctx, state);
|
|
@@ -157,7 +153,6 @@ function syncAdapter(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterStat
|
|
|
157
153
|
state.promptSkills = extractPiPromptSkills(ctx.getSystemPrompt());
|
|
158
154
|
|
|
159
155
|
registerViewImageTool(pi, { allowOriginalDetail: supportsOriginalImageDetail(ctx.model) });
|
|
160
|
-
maybeShowWebSearchSessionNote(pi, ctx, state);
|
|
161
156
|
|
|
162
157
|
if (isCodexLikeContext(ctx)) {
|
|
163
158
|
enableAdapter(pi, ctx, state);
|
|
@@ -227,15 +222,3 @@ export function restoreTools(previousTools: string[], activeTools: string[]): st
|
|
|
227
222
|
function hasAdapterTools(activeTools: string[]): boolean {
|
|
228
223
|
return activeTools.some((toolName) => ADAPTER_TOOL_NAMES.includes(toolName));
|
|
229
224
|
}
|
|
230
|
-
|
|
231
|
-
function maybeShowWebSearchSessionNote(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterState): void {
|
|
232
|
-
if (!shouldShowWebSearchSessionNote(ctx.model, ctx.hasUI, state.webSearchNoticeShown)) {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
pi.sendMessage({
|
|
236
|
-
customType: WEB_SEARCH_SESSION_NOTE_TYPE,
|
|
237
|
-
content: WEB_SEARCH_SESSION_NOTE_TEXT,
|
|
238
|
-
display: true,
|
|
239
|
-
});
|
|
240
|
-
state.webSearchNoticeShown = true;
|
|
241
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
2
|
-
import { Box, Image, Spacer, Text } from "@
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Box, Image, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import {
|
|
4
4
|
createAssistantMessageEventStream,
|
|
5
5
|
appendAssistantMessageDiagnostic,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type Context,
|
|
13
13
|
type Model,
|
|
14
14
|
type SimpleStreamOptions,
|
|
15
|
-
} from "@
|
|
15
|
+
} from "@earendil-works/pi-ai";
|
|
16
16
|
import type { ResponseCreateParamsStreaming } from "openai/resources/responses/responses.js";
|
|
17
17
|
import {
|
|
18
18
|
convertResponsesMessages,
|
|
@@ -31,6 +31,7 @@ const BASE_DELAY_MS = 1000;
|
|
|
31
31
|
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
32
32
|
const CODEX_RESPONSE_STATUSES = new Set(["completed", "incomplete", "failed", "cancelled", "queued", "in_progress"]);
|
|
33
33
|
const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
|
|
34
|
+
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
|
34
35
|
const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
35
36
|
const dynamicImport = (specifier: string) => import(specifier);
|
|
36
37
|
let _os: { platform(): string; release(): string; arch(): string } | null = null;
|
|
@@ -515,7 +516,7 @@ function resolveCodexServiceTier(responseServiceTier: ServiceTier, requestServic
|
|
|
515
516
|
return responseServiceTier ?? requestServiceTier;
|
|
516
517
|
}
|
|
517
518
|
|
|
518
|
-
function buildRequestBody<TApi extends Api>(model: Model<TApi>, context: Context, options?: SimpleStreamOptions): ResponsesBody {
|
|
519
|
+
export function buildRequestBody<TApi extends Api>(model: Model<TApi>, context: Context, options?: SimpleStreamOptions): ResponsesBody {
|
|
519
520
|
const messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {
|
|
520
521
|
includeSystemPrompt: false,
|
|
521
522
|
});
|
|
@@ -524,7 +525,7 @@ function buildRequestBody<TApi extends Api>(model: Model<TApi>, context: Context
|
|
|
524
525
|
model: model.id,
|
|
525
526
|
store: false,
|
|
526
527
|
stream: true,
|
|
527
|
-
instructions: context.systemPrompt,
|
|
528
|
+
instructions: context.systemPrompt || "You are a helpful assistant.",
|
|
528
529
|
input: messages,
|
|
529
530
|
text: { verbosity: ((options as { textVerbosity?: string } | undefined)?.textVerbosity ?? "low") as string },
|
|
530
531
|
include: ["reasoning.encrypted_content"],
|
|
@@ -595,7 +596,7 @@ function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {
|
|
|
595
596
|
});
|
|
596
597
|
}
|
|
597
598
|
|
|
598
|
-
async function* parseSSE(response: Response): AsyncIterable<StreamEventShape> {
|
|
599
|
+
export async function* parseSSE(response: Response): AsyncIterable<StreamEventShape> {
|
|
599
600
|
if (!response.body) return;
|
|
600
601
|
|
|
601
602
|
const reader = response.body.getReader();
|
|
@@ -622,8 +623,8 @@ async function* parseSSE(response: Response): AsyncIterable<StreamEventShape> {
|
|
|
622
623
|
if (data && data !== "[DONE]") {
|
|
623
624
|
try {
|
|
624
625
|
yield JSON.parse(data) as StreamEventShape;
|
|
625
|
-
} catch {
|
|
626
|
-
|
|
626
|
+
} catch (error) {
|
|
627
|
+
throw new Error(`Invalid Codex SSE JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
627
628
|
}
|
|
628
629
|
}
|
|
629
630
|
}
|
|
@@ -666,6 +667,28 @@ function closeWebSocketSilently(socket: WebSocketLike, code = 1000, reason = "do
|
|
|
666
667
|
}
|
|
667
668
|
}
|
|
668
669
|
|
|
670
|
+
export function closeOpenAICodexWebSocketSessions(sessionId?: string): void {
|
|
671
|
+
const closeEntry = (entry: SessionWebSocketCacheEntry) => {
|
|
672
|
+
if (entry.idleTimer) {
|
|
673
|
+
clearTimeout(entry.idleTimer);
|
|
674
|
+
entry.idleTimer = undefined;
|
|
675
|
+
}
|
|
676
|
+
closeWebSocketSilently(entry.socket, 1000, "session_shutdown");
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
if (sessionId) {
|
|
680
|
+
const entry = websocketSessionCache.get(sessionId);
|
|
681
|
+
if (entry) closeEntry(entry);
|
|
682
|
+
websocketSessionCache.delete(sessionId);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
for (const entry of websocketSessionCache.values()) {
|
|
687
|
+
closeEntry(entry);
|
|
688
|
+
}
|
|
689
|
+
websocketSessionCache.clear();
|
|
690
|
+
}
|
|
691
|
+
|
|
669
692
|
|
|
670
693
|
function scheduleSessionWebSocketExpiry(cacheKey: string, entry: SessionWebSocketCacheEntry): void {
|
|
671
694
|
if (entry.idleTimer) {
|
|
@@ -693,7 +716,10 @@ function extractWebSocketCloseError(event: unknown): Error {
|
|
|
693
716
|
const code = "code" in event ? (event as { code?: unknown }).code : undefined;
|
|
694
717
|
const reason = "reason" in event ? (event as { reason?: unknown }).reason : undefined;
|
|
695
718
|
const codeText = typeof code === "number" ? ` ${code}` : "";
|
|
696
|
-
|
|
719
|
+
let reasonText = typeof reason === "string" && reason.length > 0 ? ` ${reason}` : "";
|
|
720
|
+
if (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {
|
|
721
|
+
reasonText = " message too big";
|
|
722
|
+
}
|
|
697
723
|
return new Error(`WebSocket closed${codeText}${reasonText}`.trim());
|
|
698
724
|
}
|
|
699
725
|
return new Error("WebSocket closed");
|
|
@@ -943,8 +969,9 @@ async function* parseWebSocket(socket: WebSocketLike, signal: AbortSignal | unde
|
|
|
943
969
|
done = true;
|
|
944
970
|
}
|
|
945
971
|
queue.push(parsed);
|
|
946
|
-
} catch {
|
|
947
|
-
|
|
972
|
+
} catch (error) {
|
|
973
|
+
failed = new Error(`Invalid Codex WebSocket JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
974
|
+
done = true;
|
|
948
975
|
}
|
|
949
976
|
})
|
|
950
977
|
.catch((error: unknown) => {
|
|
@@ -1025,9 +1052,27 @@ async function* countWebSocketEvents(
|
|
|
1025
1052
|
}
|
|
1026
1053
|
}
|
|
1027
1054
|
|
|
1055
|
+
async function* startWebSocketOutputOnFirstEvent(
|
|
1056
|
+
events: AsyncIterable<StreamEventShape>,
|
|
1057
|
+
output: AssistantMessage,
|
|
1058
|
+
stream: AssistantMessageEventStream,
|
|
1059
|
+
onStart: () => void,
|
|
1060
|
+
): AsyncIterable<StreamEventShape> {
|
|
1061
|
+
let started = false;
|
|
1062
|
+
for await (const event of events) {
|
|
1063
|
+
if (!started) {
|
|
1064
|
+
started = true;
|
|
1065
|
+
onStart();
|
|
1066
|
+
stream.push({ type: "start", partial: output });
|
|
1067
|
+
}
|
|
1068
|
+
yield event;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1028
1072
|
function isRetryableEarlyWebSocketError(error: unknown): boolean {
|
|
1029
1073
|
const message = error instanceof Error ? error.message : String(error);
|
|
1030
|
-
|
|
1074
|
+
if (/message too big/i.test(message)) return false;
|
|
1075
|
+
return /^(?:WebSocket (?:error|closed)(?:\s|$)|Invalid Codex WebSocket JSON)/.test(message);
|
|
1031
1076
|
}
|
|
1032
1077
|
|
|
1033
1078
|
async function* mapCodexEvents(events: AsyncIterable<StreamEventShape>): AsyncIterable<StreamEventShape> {
|
|
@@ -1185,7 +1230,7 @@ async function processWebSocketStream<TApi extends Api>(
|
|
|
1185
1230
|
let streamStarted = false;
|
|
1186
1231
|
|
|
1187
1232
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1188
|
-
const { socket, entry, release
|
|
1233
|
+
const { socket, entry, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
|
|
1189
1234
|
let keepConnection = true;
|
|
1190
1235
|
let released = false;
|
|
1191
1236
|
let eventCount = 0;
|
|
@@ -1204,15 +1249,18 @@ async function processWebSocketStream<TApi extends Api>(
|
|
|
1204
1249
|
|
|
1205
1250
|
try {
|
|
1206
1251
|
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
|
|
1207
|
-
if (!streamStarted) {
|
|
1208
|
-
onStart();
|
|
1209
|
-
stream.push({ type: "start", partial: output });
|
|
1210
|
-
streamStarted = true;
|
|
1211
|
-
}
|
|
1212
1252
|
await processCapturedResponsesStream(
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1253
|
+
startWebSocketOutputOnFirstEvent(
|
|
1254
|
+
countWebSocketEvents(parseWebSocket(socket, options?.signal), () => {
|
|
1255
|
+
eventCount++;
|
|
1256
|
+
}),
|
|
1257
|
+
output,
|
|
1258
|
+
stream,
|
|
1259
|
+
() => {
|
|
1260
|
+
streamStarted = true;
|
|
1261
|
+
onStart();
|
|
1262
|
+
},
|
|
1263
|
+
),
|
|
1216
1264
|
output,
|
|
1217
1265
|
stream,
|
|
1218
1266
|
model,
|
|
@@ -1241,11 +1289,10 @@ async function processWebSocketStream<TApi extends Api>(
|
|
|
1241
1289
|
}
|
|
1242
1290
|
keepConnection = false;
|
|
1243
1291
|
releaseOnce({ keep: false });
|
|
1244
|
-
//
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
|
|
1248
|
-
if (attempt === 0 && reused && eventCount === 0 && !options?.signal?.aborted && isRetryableEarlyWebSocketError(error)) {
|
|
1292
|
+
// If WebSocket fails before the first response event, nothing has been
|
|
1293
|
+
// emitted to the UI/history yet. Retry once on a fresh WebSocket; if that
|
|
1294
|
+
// also fails, the caller can fall back to SSE for `auto` transport.
|
|
1295
|
+
if (attempt === 0 && eventCount === 0 && !streamStarted && !options?.signal?.aborted && isRetryableEarlyWebSocketError(error)) {
|
|
1249
1296
|
continue;
|
|
1250
1297
|
}
|
|
1251
1298
|
throw error;
|
|
@@ -1659,6 +1706,7 @@ export function registerOpenAICodexCustomProvider(pi: ExtensionAPI, options: { g
|
|
|
1659
1706
|
flushPendingMessages();
|
|
1660
1707
|
}
|
|
1661
1708
|
clearPendingMessages();
|
|
1709
|
+
closeOpenAICodexWebSocketSessions();
|
|
1662
1710
|
});
|
|
1663
1711
|
|
|
1664
1712
|
pi.on("agent_end", async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { calculateCost, type Api, type AssistantMessage, type Context, type Model, type Tool, type Usage } from "@
|
|
1
|
+
import { calculateCost, type Api, type AssistantMessage, type Context, type Model, type Tool, type Usage } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ResponseCreateParamsStreaming, ResponseInput, ResponseStreamEvent, Tool as OpenAITool } from "openai/resources/responses/responses.js";
|
|
3
3
|
import { parse as partialParse } from "partial-json";
|
|
4
|
-
import type { AssistantMessageEventStream } from "@
|
|
4
|
+
import type { AssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
5
5
|
|
|
6
6
|
type MessageRole = Context["messages"][number]["role"];
|
|
7
7
|
type Message = Context["messages"][number];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, delimiter, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export function getBundledApplyPatchBinDir(): string {
|
|
6
|
+
return join(dirname(dirname(dirname(fileURLToPath(import.meta.url)))), "bin");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ensureBundledApplyPatchOnPath(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
10
|
+
const binDir = getBundledApplyPatchBinDir();
|
|
11
|
+
const wrapperPath = join(binDir, process.platform === "win32" ? "apply_patch.cmd" : "apply_patch");
|
|
12
|
+
if (!existsSync(wrapperPath)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const currentPath = env.PATH ?? "";
|
|
16
|
+
const entries = currentPath.split(delimiter).filter(Boolean);
|
|
17
|
+
if (!entries.includes(binDir)) {
|
|
18
|
+
env.PATH = [binDir, ...entries].join(delimiter);
|
|
19
|
+
}
|
|
20
|
+
return binDir;
|
|
21
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, relative } from "node:path";
|
|
2
|
-
import { renderDiff } from "@
|
|
2
|
+
import { renderDiff } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { openFileAtPath } from "../patch/paths.ts";
|
|
4
4
|
import { parsePatchActions } from "../patch/parser.ts";
|
|
5
5
|
import type { ParsedPatchAction } from "../patch/types.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
import type { ExtensionAPI } from "@
|
|
3
|
-
import { Container, Text } from "@
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { executePatch } from "../patch/core.ts";
|
|
5
5
|
import { ExecutePatchError, type ExecutePatchResult } from "../patch/types.ts";
|
|
6
6
|
import { formatApplyPatchSummary, formatPatchTarget, renderApplyPatchCall } from "./apply-patch-rendering.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { Container, Text } from "@
|
|
3
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { renderExecCommandCall, renderGroupedExecCommandCall } from "./codex-rendering.ts";
|
|
5
5
|
import type { ExecCommandTracker } from "./exec-command-state.ts";
|
|
6
6
|
import type { ExecSessionManager, UnifiedExecResult } from "./exec-session-manager.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { Container, Text } from "@
|
|
3
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { isOpenAICodexModel } from "../adapter/codex-model.ts";
|
|
5
5
|
|
|
6
6
|
export const IMAGE_GENERATION_UNSUPPORTED_MESSAGE =
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
type ExtensionAPI,
|
|
7
7
|
type ExtensionContext,
|
|
8
8
|
type ToolDefinition,
|
|
9
|
-
} from "@
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { Type, type TSchema } from "typebox";
|
|
11
|
-
import { Text } from "@
|
|
11
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
12
12
|
|
|
13
13
|
const VIEW_IMAGE_UNSUPPORTED_MESSAGE = "view_image is not allowed because you do not support image inputs";
|
|
14
14
|
const DETAIL_DESCRIPTION =
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
3
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { isOpenAICodexModel } from "../adapter/codex-model.ts";
|
|
5
5
|
|
|
6
6
|
export const WEB_SEARCH_UNSUPPORTED_MESSAGE = "web_search is only available with the openai-codex provider";
|
|
7
7
|
const WEB_SEARCH_LOCAL_EXECUTION_MESSAGE =
|
|
8
8
|
"web_search is a native openai-codex provider tool and should not execute locally";
|
|
9
9
|
export const WEB_SEARCH_SESSION_NOTE_TYPE = "codex-web-search-session-note";
|
|
10
|
-
export const WEB_SEARCH_SESSION_NOTE_TEXT =
|
|
11
|
-
"Native OpenAI Codex web search is enabled for this session. Search activity is surfaced as merged foldable status messages instead of native tool-call rows.";
|
|
12
10
|
const WEB_SEARCH_MULTIMODAL_CONTENT_TYPES = ["text", "image"] as const;
|
|
13
11
|
|
|
14
12
|
const WEB_SEARCH_PARAMETERS = Type.Unsafe<Record<string, never>>({
|
|
@@ -36,14 +34,6 @@ export function supportsNativeWebSearch(model: ExtensionContext["model"]): boole
|
|
|
36
34
|
return isOpenAICodexModel(model);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
export function shouldShowWebSearchSessionNote(
|
|
40
|
-
model: ExtensionContext["model"],
|
|
41
|
-
hasUI: boolean,
|
|
42
|
-
alreadyShown: boolean,
|
|
43
|
-
): boolean {
|
|
44
|
-
return hasUI && !alreadyShown && supportsNativeWebSearch(model);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
export function supportsMultimodalNativeWebSearch(model: ExtensionContext["model"]): boolean {
|
|
48
38
|
if (!supportsNativeWebSearch(model)) {
|
|
49
39
|
return false;
|
|
@@ -130,12 +120,3 @@ export function createWebSearchTool(): ToolDefinition<typeof WEB_SEARCH_PARAMETE
|
|
|
130
120
|
export function registerWebSearchTool(pi: ExtensionAPI): void {
|
|
131
121
|
pi.registerTool(createWebSearchTool());
|
|
132
122
|
}
|
|
133
|
-
|
|
134
|
-
export function registerWebSearchSessionNoteRenderer(pi: ExtensionAPI): void {
|
|
135
|
-
pi.registerMessageRenderer(WEB_SEARCH_SESSION_NOTE_TYPE, (_message, _options, theme) => {
|
|
136
|
-
const box = new Box(1, 1, (text) => theme.bg("toolSuccessBg", text));
|
|
137
|
-
box.addChild(new Text(theme.bold("Web search enabled"), 0, 0));
|
|
138
|
-
box.addChild(new Text(`\n${theme.fg("dim", WEB_SEARCH_SESSION_NOTE_TEXT)}`, 0, 0));
|
|
139
|
-
return box;
|
|
140
|
-
});
|
|
141
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { Container, Text } from "@
|
|
3
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { renderWriteStdinCall } from "./codex-rendering.ts";
|
|
5
5
|
import type { ExecSessionManager, UnifiedExecResult } from "./exec-session-manager.ts";
|
|
6
6
|
import { formatUnifiedExecResult } from "./unified-exec-format.ts";
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|