pi-openai-toolkit 0.14.5 → 0.14.6
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/docs/internals.md +42 -21
- package/package.json +1 -1
- package/src/auto-mode/classifier.ts +165 -165
- package/src/auto-mode/policy.ts +146 -146
- package/src/auto-mode/prompt.ts +279 -279
- package/src/auto-mode/reviewer.ts +325 -325
- package/src/auto-mode/transcript.ts +133 -133
- package/src/auto-mode/types.ts +137 -137
- package/src/codex-astra/effort-planner.ts +175 -175
- package/src/codex-astra/extension.ts +122 -122
- package/src/context-management/codex-provider.ts +20 -20
- package/src/context-management/history-notes.ts +32 -0
- package/src/context-management/messages.ts +112 -106
- package/src/context-management/namespace-tools.ts +426 -426
- package/src/context-management/tool-contract.ts +23 -23
- package/src/context-management/tools.ts +306 -311
- package/src/context-management/types.ts +7 -0
- package/src/context-management/window-budget.ts +83 -83
- package/src/context-management/window-manager.ts +60 -51
- package/src/context-management/window-request.ts +125 -125
- package/src/debug.ts +1 -1
- package/src/extension-runtime.ts +34 -9
- package/src/image-generation/artifacts.ts +237 -237
- package/src/image-generation/client.ts +248 -248
- package/src/image-generation/eligibility.ts +18 -18
- package/src/image-generation/references.ts +194 -194
- package/src/image-generation/render.ts +252 -252
- package/src/model-scope.ts +17 -17
- package/src/provider-headers.ts +23 -23
- package/src/responses-headers.ts +76 -11
- package/src/runtime.ts +37 -1
- package/src/web-search/extension.ts +80 -80
- package/src/web-search/payload.ts +96 -96
- package/src/web-search/prompt.ts +20 -20
- package/src/web-search/types.ts +45 -45
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { Api, Model, ProviderHeaders } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { mergeProviderHeaders } from "../provider-headers";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CODEX_AFFINITY_SCOPE,
|
|
6
|
+
CODEX_CLIENT_VERSION,
|
|
7
|
+
CODEX_GATEWAY_ORIGINATOR,
|
|
8
|
+
buildCodexCliUserAgent,
|
|
9
|
+
filterCodexGatewayHeaders,
|
|
10
|
+
} from "../responses-headers";
|
|
11
|
+
export { CODEX_AFFINITY_SCOPE } from "../responses-headers";
|
|
5
12
|
import { normalizeBaseUrl } from "../runtime";
|
|
6
13
|
import { isExactModelAllowed } from "../model-scope";
|
|
7
14
|
import {
|
|
@@ -13,13 +20,10 @@ import {
|
|
|
13
20
|
const CODEX_PROVIDER = "openai-codex";
|
|
14
21
|
const CODEX_API = "openai-codex-responses";
|
|
15
22
|
const GATEWAY_API = "openai-responses";
|
|
16
|
-
const CODEX_TOOL_ORIGINATOR =
|
|
17
|
-
const CODEX_GATEWAY_ORIGINATOR = "codex_cli_rs";
|
|
23
|
+
const CODEX_TOOL_ORIGINATOR = CODEX_GATEWAY_ORIGINATOR;
|
|
18
24
|
const CODEX_TOOL_VERSION = "0.0.0";
|
|
19
25
|
const CODEX_SESSION_ID_MAX_LENGTH = 64;
|
|
20
26
|
|
|
21
|
-
export const CODEX_AFFINITY_SCOPE = "codex-session-v1";
|
|
22
|
-
|
|
23
27
|
export const CODEX_CONTEXT_PROVIDER_ERROR =
|
|
24
28
|
"Remote Context management requires a configured Codex-compatible backend";
|
|
25
29
|
|
|
@@ -103,9 +107,7 @@ function accountIdFromToken(token: string): string | undefined {
|
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
function userAgent(version: string): string {
|
|
106
|
-
|
|
107
|
-
const arch = typeof process !== "undefined" ? process.arch : "unknown";
|
|
108
|
-
return `codex_cli_rs/${version} (${platform}; ${arch})`;
|
|
110
|
+
return buildCodexCliUserAgent(version);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
function clampSessionId(value: string | undefined): string | undefined {
|
|
@@ -117,17 +119,12 @@ function clampSessionId(value: string | undefined): string | undefined {
|
|
|
117
119
|
: chars.slice(0, CODEX_SESSION_ID_MAX_LENGTH).join("");
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (lower === "authorization" || lower === "cookie" || lower === "chatgpt-account-id" || lower === "x-api-key") {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
result[name] = value;
|
|
122
|
+
function resolveContextSessionId(ctx: ExtensionContext): string | undefined {
|
|
123
|
+
try {
|
|
124
|
+
return clampSessionId(ctx.sessionManager.getSessionId());
|
|
125
|
+
} catch {
|
|
126
|
+
return undefined;
|
|
129
127
|
}
|
|
130
|
-
return result;
|
|
131
128
|
}
|
|
132
129
|
|
|
133
130
|
export type CodexContextProviderHeaderOptions = {
|
|
@@ -164,7 +161,7 @@ export function codexContextProviderHeaders(
|
|
|
164
161
|
const sessionId = clampSessionId(options.sessionId);
|
|
165
162
|
if (sessionId) {
|
|
166
163
|
headers.set("Session-Id", sessionId);
|
|
167
|
-
headers.set("X-Client-Request-Id", options.clientRequestId
|
|
164
|
+
headers.set("X-Client-Request-Id", clampSessionId(options.clientRequestId) ?? sessionId);
|
|
168
165
|
}
|
|
169
166
|
headers.set("content-type", "application/json");
|
|
170
167
|
return headers;
|
|
@@ -208,6 +205,9 @@ export async function resolveCodexContextProvider(
|
|
|
208
205
|
if (!rawBaseUrl) return { ok: false, reason: "missing-base-url", ...descriptor };
|
|
209
206
|
|
|
210
207
|
if (isGateway) {
|
|
208
|
+
if (!resolveContextSessionId(ctx)) {
|
|
209
|
+
return { ok: false, reason: "missing-session-id", ...descriptor, baseUrl: rawBaseUrl };
|
|
210
|
+
}
|
|
211
211
|
const apiKey = bearerToken(auth.apiKey) ?? bearerToken(headerValue(auth.headers, "authorization"));
|
|
212
212
|
if (!apiKey) return { ok: false, reason: "missing-api-key", ...descriptor, baseUrl: rawBaseUrl };
|
|
213
213
|
return {
|
|
@@ -220,7 +220,7 @@ export async function resolveCodexContextProvider(
|
|
|
220
220
|
model: model.id,
|
|
221
221
|
baseUrl: rawBaseUrl,
|
|
222
222
|
apiKey,
|
|
223
|
-
headers:
|
|
223
|
+
headers: filterCodexGatewayHeaders(mergeProviderHeaders(model.headers, auth.headers)),
|
|
224
224
|
},
|
|
225
225
|
};
|
|
226
226
|
}
|
|
@@ -37,6 +37,11 @@ export const NOTES_ENDPOINTS = {
|
|
|
37
37
|
write_file: "alpha/notes/v2/write_file",
|
|
38
38
|
} as const satisfies Record<NotesAction, string>;
|
|
39
39
|
|
|
40
|
+
const NOTES_WRITE_ENDPOINTS = new Set<string>([
|
|
41
|
+
NOTES_ENDPOINTS.append_to_file,
|
|
42
|
+
NOTES_ENDPOINTS.write_file,
|
|
43
|
+
]);
|
|
44
|
+
|
|
40
45
|
export const ENCRYPTED_ARGUMENT_ENDPOINTS = new Set<string>([
|
|
41
46
|
HISTORY_ENDPOINTS.search_contents,
|
|
42
47
|
NOTES_ENDPOINTS.search_contents,
|
|
@@ -69,6 +74,28 @@ export interface CodexHistoryNotesDetails {
|
|
|
69
74
|
codexHistoryNotes: Record<string, unknown>;
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Optional backend status flags are accepted only when explicitly affirmative;
|
|
79
|
+
* an omitted flag is compatible with older response shapes.
|
|
80
|
+
*/
|
|
81
|
+
export function isSuccessfulHistoryNotesResponse(value: unknown): value is HistoryNotesResponse {
|
|
82
|
+
return isRecord(value) && isAffirmativeOptionalBoolean(value.ok) && isAffirmativeOptionalBoolean(value.success);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Validate persisted tool-result details before using them as a checkpoint. */
|
|
86
|
+
export function isSuccessfulHistoryNotesToolResult(details: unknown): details is CodexHistoryNotesDetails {
|
|
87
|
+
if (!isRecord(details) || !isRecord(details.codexHistoryNotes)) return false;
|
|
88
|
+
return isSuccessfulHistoryNotesResponse(details.codexHistoryNotes);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isAffirmativeOptionalBoolean(value: unknown): boolean {
|
|
92
|
+
return value === undefined || value === true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isRejectedNotesWriteResponse(value: HistoryNotesResponse): boolean {
|
|
96
|
+
return value.ok === false || value.success === false;
|
|
97
|
+
}
|
|
98
|
+
|
|
72
99
|
export async function callHistoryNotesBackend(
|
|
73
100
|
endpoint: string,
|
|
74
101
|
arguments_: Record<string, unknown>,
|
|
@@ -172,6 +199,7 @@ export async function loadHistoryNotesThreadHint(
|
|
|
172
199
|
if (result.reason === "aborted") throw new Error("Remote context hint request was aborted");
|
|
173
200
|
return undefined;
|
|
174
201
|
}
|
|
202
|
+
if (!isSuccessfulHistoryNotesResponse(result.value)) return undefined;
|
|
175
203
|
const text = result.value.text;
|
|
176
204
|
return typeof text === "string" && Buffer.byteLength(text, "utf8") <= THREAD_HINT_MAX_BYTES ? text : undefined;
|
|
177
205
|
}
|
|
@@ -198,6 +226,9 @@ export async function executeHistoryNotesTool(
|
|
|
198
226
|
gatewayModels,
|
|
199
227
|
);
|
|
200
228
|
if (!result.ok) throw new Error(formatHistoryNotesFailure(result.reason, result.status));
|
|
229
|
+
if (NOTES_WRITE_ENDPOINTS.has(endpoint) && isRejectedNotesWriteResponse(result.value)) {
|
|
230
|
+
throw new Error("Remote notes write was rejected");
|
|
231
|
+
}
|
|
201
232
|
const modelResult = { ...result.value };
|
|
202
233
|
delete modelResult.images;
|
|
203
234
|
const content: AgentToolResult<CodexHistoryNotesDetails>["content"] = [
|
|
@@ -227,6 +258,7 @@ export function formatHistoryNotesFailure(
|
|
|
227
258
|
case "missing-api-key": return "Remote history/notes gateway authentication is missing an API key";
|
|
228
259
|
case "missing-account-id": return "Remote history/notes authentication is missing an account id";
|
|
229
260
|
case "missing-base-url": return "Remote history/notes backend is missing a base URL";
|
|
261
|
+
case "missing-session-id": return "Remote history/notes request is missing a session id";
|
|
230
262
|
case "unsupported-backend": return "Remote history/notes require the native Codex backend";
|
|
231
263
|
case "protocol-error": return "Remote history/notes request failed";
|
|
232
264
|
case "invalid-account-token": return "Remote history/notes authentication token is invalid";
|
|
@@ -1,106 +1,112 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import {
|
|
5
|
-
CODEX_CONTEXT_WINDOW_MESSAGE_TYPE,
|
|
6
|
-
CONTEXT_MANAGEMENT_PROTOCOL,
|
|
7
|
-
CONTEXT_WINDOW_COMPACTION_STRATEGY,
|
|
8
|
-
CONTEXT_WINDOW_FALLBACK_BUFFER,
|
|
9
|
-
CONTEXT_WINDOW_REMINDER_THRESHOLD,
|
|
10
|
-
type CodexContextManagementMessageDetails,
|
|
11
|
-
type ContextManagementMessageKind,
|
|
12
|
-
type ContextWindowCompactionDetails,
|
|
13
|
-
type ContextWindowIdentity,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
lines.push(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
:
|
|
106
|
-
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import {
|
|
5
|
+
CODEX_CONTEXT_WINDOW_MESSAGE_TYPE,
|
|
6
|
+
CONTEXT_MANAGEMENT_PROTOCOL,
|
|
7
|
+
CONTEXT_WINDOW_COMPACTION_STRATEGY,
|
|
8
|
+
CONTEXT_WINDOW_FALLBACK_BUFFER,
|
|
9
|
+
CONTEXT_WINDOW_REMINDER_THRESHOLD,
|
|
10
|
+
type CodexContextManagementMessageDetails,
|
|
11
|
+
type ContextManagementMessageKind,
|
|
12
|
+
type ContextWindowCompactionDetails,
|
|
13
|
+
type ContextWindowIdentity,
|
|
14
|
+
type NotesCheckpointReceipt,
|
|
15
|
+
isCodexContextManagementMessageDetails,
|
|
16
|
+
isContextWindowCompactionDetails,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
CODEX_CONTEXT_WINDOW_MESSAGE_TYPE,
|
|
21
|
+
CONTEXT_WINDOW_COMPACTION_STRATEGY,
|
|
22
|
+
CONTEXT_WINDOW_FALLBACK_BUFFER,
|
|
23
|
+
CONTEXT_WINDOW_REMINDER_THRESHOLD,
|
|
24
|
+
isCodexContextManagementMessageDetails,
|
|
25
|
+
isContextWindowCompactionDetails,
|
|
26
|
+
};
|
|
27
|
+
export type {
|
|
28
|
+
CodexContextManagementMessageDetails,
|
|
29
|
+
ContextManagementMessageKind,
|
|
30
|
+
ContextWindowCompactionDetails,
|
|
31
|
+
ContextWindowIdentity,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const CONTEXT_WINDOW_COMPACTION_SUMMARY =
|
|
35
|
+
"[Pi Codex context-window boundary; no conversation summary was generated.]";
|
|
36
|
+
|
|
37
|
+
const CONTEXT_WINDOW_GUIDANCE = `<context_window_guidance>
|
|
38
|
+
Checkpoint the active request, known history IDs, decisions, progress, learnings and next steps in notes before new_context; no summary carries over. After rollover, read the checkpoint receipt note first when present; use a thread hint only as supplemental guidance. Use history only for a missing detail.
|
|
39
|
+
</context_window_guidance>`;
|
|
40
|
+
|
|
41
|
+
export function renderContextWindowMessage(
|
|
42
|
+
identity: ContextWindowIdentity,
|
|
43
|
+
threadHint?: string,
|
|
44
|
+
checkpoint?: NotesCheckpointReceipt,
|
|
45
|
+
): string {
|
|
46
|
+
const lines = [
|
|
47
|
+
"<context_window>",
|
|
48
|
+
"Agent name: /root",
|
|
49
|
+
`First context window id: ${identity.firstWindowId}`,
|
|
50
|
+
`Current context window id: ${identity.currentWindowId}`,
|
|
51
|
+
];
|
|
52
|
+
if (identity.previousWindowId) lines.push(`Previous context window id: ${identity.previousWindowId}`);
|
|
53
|
+
if (checkpoint) {
|
|
54
|
+
lines.push("Checkpoint successfully written:");
|
|
55
|
+
lines.push(` Read this note before doing anything else with notes action "read_file": ${JSON.stringify(checkpoint.path)}`);
|
|
56
|
+
}
|
|
57
|
+
if (threadHint) lines.push(threadHint);
|
|
58
|
+
lines.push("</context_window>");
|
|
59
|
+
return `${CONTEXT_WINDOW_GUIDANCE}\n\n${lines.join("\n")}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function renderContextWindowReminder(remainingTokens: number): string {
|
|
63
|
+
return `<context_window_reminder>
|
|
64
|
+
Only ${Math.max(0, Math.floor(remainingTokens))} context tokens remain. Checkpoint the active request, state and known history IDs in notes, then call new_context; no conversation summary carries over.
|
|
65
|
+
</context_window_reminder>`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const CONTEXT_WINDOW_FALLBACK_MESSAGE = `<context_window_reminder>
|
|
69
|
+
Context exhausted. Do not continue or answer. Make exactly one notes write or append call that checkpoints the active request, state and known history IDs, then call new_context. Use no other tools before rollover.
|
|
70
|
+
</context_window_reminder>`;
|
|
71
|
+
|
|
72
|
+
export function isContextWindowBoundary(
|
|
73
|
+
message: AgentMessage,
|
|
74
|
+
): message is Extract<AgentMessage, { role: "custom" }> & {
|
|
75
|
+
details: CodexContextManagementMessageDetails;
|
|
76
|
+
} {
|
|
77
|
+
return (
|
|
78
|
+
message.role === "custom" &&
|
|
79
|
+
message.customType === CODEX_CONTEXT_WINDOW_MESSAGE_TYPE &&
|
|
80
|
+
isCodexContextManagementMessageDetails(message.details) &&
|
|
81
|
+
message.details.contextManagement.kind === "window"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function sendContextWindowMessage(
|
|
86
|
+
pi: ExtensionAPI,
|
|
87
|
+
content: string,
|
|
88
|
+
kind: ContextManagementMessageKind,
|
|
89
|
+
identity: ContextWindowIdentity,
|
|
90
|
+
options: { triggerTurn: boolean; sessionId?: string },
|
|
91
|
+
trimPreviousWindow = false,
|
|
92
|
+
): void {
|
|
93
|
+
const details: CodexContextManagementMessageDetails = {
|
|
94
|
+
protocol: CONTEXT_MANAGEMENT_PROTOCOL,
|
|
95
|
+
id: randomUUID(),
|
|
96
|
+
...(options.sessionId ? { sessionId: options.sessionId } : {}),
|
|
97
|
+
contextManagement: {
|
|
98
|
+
protocol: CONTEXT_MANAGEMENT_PROTOCOL,
|
|
99
|
+
kind,
|
|
100
|
+
...identity,
|
|
101
|
+
...(trimPreviousWindow ? { trimPreviousWindow: true as const } : {}),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
pi.sendMessage({
|
|
105
|
+
customType: CODEX_CONTEXT_WINDOW_MESSAGE_TYPE,
|
|
106
|
+
content,
|
|
107
|
+
display: true,
|
|
108
|
+
details,
|
|
109
|
+
}, options.triggerTurn
|
|
110
|
+
? { deliverAs: "steer", triggerTurn: true }
|
|
111
|
+
: { triggerTurn: false });
|
|
112
|
+
}
|