pi-openai-toolkit 0.14.4 → 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.
Files changed (43) hide show
  1. package/README.md +119 -147
  2. package/README.zh.md +119 -151
  3. package/docs/internals.md +42 -21
  4. package/package.json +127 -127
  5. package/src/auto-mode/classifier.ts +165 -165
  6. package/src/auto-mode/policy.ts +146 -146
  7. package/src/auto-mode/prompt.ts +279 -279
  8. package/src/auto-mode/reviewer.ts +325 -325
  9. package/src/auto-mode/transcript.ts +133 -133
  10. package/src/auto-mode/types.ts +137 -137
  11. package/src/codex-astra/effort-planner.ts +175 -175
  12. package/src/codex-astra/extension.ts +122 -122
  13. package/src/config.ts +621 -592
  14. package/src/context-management/codex-provider.ts +260 -260
  15. package/src/context-management/history-notes.ts +357 -325
  16. package/src/context-management/messages.ts +112 -106
  17. package/src/context-management/namespace-tools.ts +426 -426
  18. package/src/context-management/tool-contract.ts +23 -23
  19. package/src/context-management/tools.ts +306 -311
  20. package/src/context-management/types.ts +168 -161
  21. package/src/context-management/window-budget.ts +83 -83
  22. package/src/context-management/window-manager.ts +60 -51
  23. package/src/context-management/window-request.ts +125 -125
  24. package/src/debug.ts +1 -1
  25. package/src/extension-runtime.ts +34 -9
  26. package/src/image-generation/artifacts.ts +237 -237
  27. package/src/image-generation/client.ts +248 -248
  28. package/src/image-generation/eligibility.ts +18 -18
  29. package/src/image-generation/extension.ts +19 -2
  30. package/src/image-generation/protocol.ts +473 -405
  31. package/src/image-generation/references.ts +194 -194
  32. package/src/image-generation/render.ts +252 -252
  33. package/src/image-generation/service.ts +249 -243
  34. package/src/image-generation/types.ts +189 -184
  35. package/src/model-scope.ts +17 -17
  36. package/src/provider-headers.ts +23 -23
  37. package/src/responses-headers.ts +173 -108
  38. package/src/runtime.ts +513 -477
  39. package/src/types.ts +11 -0
  40. package/src/web-search/extension.ts +80 -80
  41. package/src/web-search/payload.ts +96 -96
  42. package/src/web-search/prompt.ts +20 -20
  43. package/src/web-search/types.ts +45 -45
package/src/types.ts CHANGED
@@ -107,6 +107,12 @@ export type WebSearchConfig = {
107
107
 
108
108
  export type ImageGenerationConfig = {
109
109
  enabled: boolean;
110
+ /**
111
+ * Bare `image_generation` tool model ids this toolkit may send, without a `provider/` prefix.
112
+ * The first entry is the default; `openai_generate_image`'s optional `model` argument may
113
+ * select any other entry in this order-preserving list.
114
+ */
115
+ models: string[];
110
116
  };
111
117
 
112
118
  /** `side-effect` reviews bash/write/edit plus extras; `all` reviews every tool call. */
@@ -527,8 +533,13 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchConfig = {
527
533
  models: [],
528
534
  };
529
535
 
536
+ /** Single source of truth for the hosted image model used when nothing is configured. */
537
+ export const DEFAULT_IMAGE_GENERATION_MODEL = "gpt-image-2.5";
538
+ export const DEFAULT_IMAGE_GENERATION_MODELS: readonly string[] = [DEFAULT_IMAGE_GENERATION_MODEL];
539
+
530
540
  export const DEFAULT_IMAGE_GENERATION_CONFIG: ImageGenerationConfig = {
531
541
  enabled: false,
542
+ models: [...DEFAULT_IMAGE_GENERATION_MODELS],
532
543
  };
533
544
 
534
545
  export const DEFAULT_AUTO_MODE_CONFIG: AutoModeConfig = {
@@ -1,80 +1,80 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { loadToolkitConfig } from "../config";
3
- import { transformWebSearchPayload } from "./payload";
4
- import { appendWebSearchPrompt } from "./prompt";
5
- import { isWebSearchEnabledForModel, type WebSearchModel } from "./types";
6
-
7
- function syncLocalWebSearchTool(
8
- pi: ExtensionAPI,
9
- model: WebSearchModel | undefined,
10
- config: Parameters<typeof isWebSearchEnabledForModel>[1],
11
- state: { wasActiveBeforeOwnership?: boolean; toolkitOwnsTool: boolean },
12
- ): void {
13
- const eligible = isWebSearchEnabledForModel(model, config);
14
- const activeTools = pi.getActiveTools();
15
-
16
- if (eligible) {
17
- if (!state.toolkitOwnsTool) {
18
- state.wasActiveBeforeOwnership = activeTools.includes("web_search");
19
- state.toolkitOwnsTool = true;
20
- }
21
-
22
- if (activeTools.includes("web_search")) {
23
- pi.setActiveTools(activeTools.filter((name) => name !== "web_search"));
24
- }
25
- return;
26
- }
27
-
28
- if (!state.toolkitOwnsTool) return;
29
-
30
- if (state.wasActiveBeforeOwnership && !activeTools.includes("web_search")) {
31
- pi.setActiveTools([...activeTools, "web_search"]);
32
- }
33
- state.wasActiveBeforeOwnership = undefined;
34
- state.toolkitOwnsTool = false;
35
- }
36
-
37
- export function registerWebSearchExtension(
38
- pi: ExtensionAPI,
39
- loadConfig: typeof loadToolkitConfig = loadToolkitConfig,
40
- ): void {
41
- const toolState = { wasActiveBeforeOwnership: undefined as boolean | undefined, toolkitOwnsTool: false };
42
-
43
- pi.on("session_start", (_event, ctx) => {
44
- const { config } = loadConfig();
45
- syncLocalWebSearchTool(pi, ctx.model, config.webSearch, toolState);
46
- });
47
-
48
- pi.on("model_select", (event) => {
49
- const { config } = loadConfig();
50
- syncLocalWebSearchTool(pi, event.model, config.webSearch, toolState);
51
- });
52
-
53
- pi.on("before_agent_start", (event, ctx) => {
54
- const { config } = loadConfig();
55
- syncLocalWebSearchTool(pi, ctx.model, config.webSearch, toolState);
56
- const systemPrompt = appendWebSearchPrompt({
57
- model: ctx.model,
58
- config: config.webSearch,
59
- systemPrompt: event.systemPrompt,
60
- });
61
-
62
- return systemPrompt === event.systemPrompt ? undefined : { systemPrompt };
63
- });
64
-
65
- pi.on("before_provider_request", (event, ctx) => {
66
- const { config } = loadConfig();
67
- syncLocalWebSearchTool(pi, ctx.model, config.webSearch, toolState);
68
- const transformed = transformWebSearchPayload({
69
- model: ctx.model,
70
- config: config.webSearch,
71
- payload: event.payload,
72
- });
73
-
74
- return transformed.changed ? transformed.payload : undefined;
75
- });
76
- }
77
-
78
- export default function webSearchExtension(pi: ExtensionAPI): void {
79
- registerWebSearchExtension(pi);
80
- }
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { loadToolkitConfig } from "../config";
3
+ import { transformWebSearchPayload } from "./payload";
4
+ import { appendWebSearchPrompt } from "./prompt";
5
+ import { isWebSearchEnabledForModel, type WebSearchModel } from "./types";
6
+
7
+ function syncLocalWebSearchTool(
8
+ pi: ExtensionAPI,
9
+ model: WebSearchModel | undefined,
10
+ config: Parameters<typeof isWebSearchEnabledForModel>[1],
11
+ state: { wasActiveBeforeOwnership?: boolean; toolkitOwnsTool: boolean },
12
+ ): void {
13
+ const eligible = isWebSearchEnabledForModel(model, config);
14
+ const activeTools = pi.getActiveTools();
15
+
16
+ if (eligible) {
17
+ if (!state.toolkitOwnsTool) {
18
+ state.wasActiveBeforeOwnership = activeTools.includes("web_search");
19
+ state.toolkitOwnsTool = true;
20
+ }
21
+
22
+ if (activeTools.includes("web_search")) {
23
+ pi.setActiveTools(activeTools.filter((name) => name !== "web_search"));
24
+ }
25
+ return;
26
+ }
27
+
28
+ if (!state.toolkitOwnsTool) return;
29
+
30
+ if (state.wasActiveBeforeOwnership && !activeTools.includes("web_search")) {
31
+ pi.setActiveTools([...activeTools, "web_search"]);
32
+ }
33
+ state.wasActiveBeforeOwnership = undefined;
34
+ state.toolkitOwnsTool = false;
35
+ }
36
+
37
+ export function registerWebSearchExtension(
38
+ pi: ExtensionAPI,
39
+ loadConfig: typeof loadToolkitConfig = loadToolkitConfig,
40
+ ): void {
41
+ const toolState = { wasActiveBeforeOwnership: undefined as boolean | undefined, toolkitOwnsTool: false };
42
+
43
+ pi.on("session_start", (_event, ctx) => {
44
+ const { config } = loadConfig();
45
+ syncLocalWebSearchTool(pi, ctx.model, config.webSearch, toolState);
46
+ });
47
+
48
+ pi.on("model_select", (event) => {
49
+ const { config } = loadConfig();
50
+ syncLocalWebSearchTool(pi, event.model, config.webSearch, toolState);
51
+ });
52
+
53
+ pi.on("before_agent_start", (event, ctx) => {
54
+ const { config } = loadConfig();
55
+ syncLocalWebSearchTool(pi, ctx.model, config.webSearch, toolState);
56
+ const systemPrompt = appendWebSearchPrompt({
57
+ model: ctx.model,
58
+ config: config.webSearch,
59
+ systemPrompt: event.systemPrompt,
60
+ });
61
+
62
+ return systemPrompt === event.systemPrompt ? undefined : { systemPrompt };
63
+ });
64
+
65
+ pi.on("before_provider_request", (event, ctx) => {
66
+ const { config } = loadConfig();
67
+ syncLocalWebSearchTool(pi, ctx.model, config.webSearch, toolState);
68
+ const transformed = transformWebSearchPayload({
69
+ model: ctx.model,
70
+ config: config.webSearch,
71
+ payload: event.payload,
72
+ });
73
+
74
+ return transformed.changed ? transformed.payload : undefined;
75
+ });
76
+ }
77
+
78
+ export default function webSearchExtension(pi: ExtensionAPI): void {
79
+ registerWebSearchExtension(pi);
80
+ }
@@ -1,96 +1,96 @@
1
- import type { WebSearchConfig } from "../types";
2
- import {
3
- WEB_SEARCH_SOURCE_INCLUDE,
4
- isWebSearchEnabledForModel,
5
- type WebSearchModel,
6
- type WebSearchPayloadTransform,
7
- } from "./types";
8
-
9
- type JsonObject = Record<string, unknown>;
10
-
11
- function isRecord(value: unknown): value is JsonObject {
12
- return !!value && typeof value === "object" && !Array.isArray(value);
13
- }
14
-
15
- function isLocalWebSearchFunction(tool: unknown): boolean {
16
- return isRecord(tool) && tool.type === "function" && tool.name === "web_search";
17
- }
18
-
19
- function isNativeWebSearchTool(tool: unknown): boolean {
20
- return isRecord(tool) && (tool.type === "web_search" || tool.type === "web_search_preview");
21
- }
22
-
23
- export function transformWebSearchPayload(args: {
24
- model: WebSearchModel | undefined;
25
- config: WebSearchConfig;
26
- payload: unknown;
27
- }): WebSearchPayloadTransform {
28
- const { model, config, payload } = args;
29
- if (!config.enabled) {
30
- return { payload, outcome: "disabled", changed: false };
31
- }
32
- if (!isWebSearchEnabledForModel(model, config)) {
33
- return { payload, outcome: "unsupported-model", changed: false };
34
- }
35
- if (!isRecord(payload)) {
36
- return { payload, outcome: "non-object-payload", changed: false };
37
- }
38
-
39
- const toolsValue = payload.tools;
40
- if (toolsValue !== undefined && !Array.isArray(toolsValue)) {
41
- return { payload, outcome: "invalid-tools", changed: false };
42
- }
43
- const tools = toolsValue ?? [];
44
- const includeValue = payload.include;
45
- if (includeValue !== undefined && !Array.isArray(includeValue)) {
46
- return { payload, outcome: "invalid-include", changed: false };
47
- }
48
- const include = includeValue ?? [];
49
- const normalizedTools: unknown[] = [];
50
- let nativeToolCount = 0;
51
- let localToolRemoved = false;
52
- for (const tool of tools) {
53
- if (isLocalWebSearchFunction(tool)) {
54
- localToolRemoved = true;
55
- continue;
56
- }
57
- if (isNativeWebSearchTool(tool)) {
58
- nativeToolCount += 1;
59
- if (nativeToolCount > 1) continue;
60
- }
61
- normalizedTools.push(tool);
62
- }
63
- const hasNativeTool = nativeToolCount > 0;
64
- if (!hasNativeTool) {
65
- normalizedTools.push({ type: "web_search" });
66
- }
67
-
68
- const normalizedInclude: unknown[] = [];
69
- let sourceIncludeCount = 0;
70
- for (const item of include) {
71
- if (item === WEB_SEARCH_SOURCE_INCLUDE) {
72
- sourceIncludeCount += 1;
73
- if (sourceIncludeCount > 1) continue;
74
- }
75
- normalizedInclude.push(item);
76
- }
77
- if (sourceIncludeCount === 0) {
78
- normalizedInclude.push(WEB_SEARCH_SOURCE_INCLUDE);
79
- }
80
-
81
- const toolsChanged = localToolRemoved || !hasNativeTool || nativeToolCount > 1;
82
- const includeChanged = sourceIncludeCount !== 1;
83
- if (!toolsChanged && !includeChanged) {
84
- return { payload, outcome: "existing-native-tool", changed: false };
85
- }
86
-
87
- return {
88
- payload: {
89
- ...payload,
90
- ...(toolsChanged ? { tools: normalizedTools } : {}),
91
- ...(includeChanged ? { include: normalizedInclude } : {}),
92
- },
93
- outcome: hasNativeTool ? "existing-native-tool" : "injected-native-tool",
94
- changed: true,
95
- };
96
- }
1
+ import type { WebSearchConfig } from "../types";
2
+ import {
3
+ WEB_SEARCH_SOURCE_INCLUDE,
4
+ isWebSearchEnabledForModel,
5
+ type WebSearchModel,
6
+ type WebSearchPayloadTransform,
7
+ } from "./types";
8
+
9
+ type JsonObject = Record<string, unknown>;
10
+
11
+ function isRecord(value: unknown): value is JsonObject {
12
+ return !!value && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+
15
+ function isLocalWebSearchFunction(tool: unknown): boolean {
16
+ return isRecord(tool) && tool.type === "function" && tool.name === "web_search";
17
+ }
18
+
19
+ function isNativeWebSearchTool(tool: unknown): boolean {
20
+ return isRecord(tool) && (tool.type === "web_search" || tool.type === "web_search_preview");
21
+ }
22
+
23
+ export function transformWebSearchPayload(args: {
24
+ model: WebSearchModel | undefined;
25
+ config: WebSearchConfig;
26
+ payload: unknown;
27
+ }): WebSearchPayloadTransform {
28
+ const { model, config, payload } = args;
29
+ if (!config.enabled) {
30
+ return { payload, outcome: "disabled", changed: false };
31
+ }
32
+ if (!isWebSearchEnabledForModel(model, config)) {
33
+ return { payload, outcome: "unsupported-model", changed: false };
34
+ }
35
+ if (!isRecord(payload)) {
36
+ return { payload, outcome: "non-object-payload", changed: false };
37
+ }
38
+
39
+ const toolsValue = payload.tools;
40
+ if (toolsValue !== undefined && !Array.isArray(toolsValue)) {
41
+ return { payload, outcome: "invalid-tools", changed: false };
42
+ }
43
+ const tools = toolsValue ?? [];
44
+ const includeValue = payload.include;
45
+ if (includeValue !== undefined && !Array.isArray(includeValue)) {
46
+ return { payload, outcome: "invalid-include", changed: false };
47
+ }
48
+ const include = includeValue ?? [];
49
+ const normalizedTools: unknown[] = [];
50
+ let nativeToolCount = 0;
51
+ let localToolRemoved = false;
52
+ for (const tool of tools) {
53
+ if (isLocalWebSearchFunction(tool)) {
54
+ localToolRemoved = true;
55
+ continue;
56
+ }
57
+ if (isNativeWebSearchTool(tool)) {
58
+ nativeToolCount += 1;
59
+ if (nativeToolCount > 1) continue;
60
+ }
61
+ normalizedTools.push(tool);
62
+ }
63
+ const hasNativeTool = nativeToolCount > 0;
64
+ if (!hasNativeTool) {
65
+ normalizedTools.push({ type: "web_search" });
66
+ }
67
+
68
+ const normalizedInclude: unknown[] = [];
69
+ let sourceIncludeCount = 0;
70
+ for (const item of include) {
71
+ if (item === WEB_SEARCH_SOURCE_INCLUDE) {
72
+ sourceIncludeCount += 1;
73
+ if (sourceIncludeCount > 1) continue;
74
+ }
75
+ normalizedInclude.push(item);
76
+ }
77
+ if (sourceIncludeCount === 0) {
78
+ normalizedInclude.push(WEB_SEARCH_SOURCE_INCLUDE);
79
+ }
80
+
81
+ const toolsChanged = localToolRemoved || !hasNativeTool || nativeToolCount > 1;
82
+ const includeChanged = sourceIncludeCount !== 1;
83
+ if (!toolsChanged && !includeChanged) {
84
+ return { payload, outcome: "existing-native-tool", changed: false };
85
+ }
86
+
87
+ return {
88
+ payload: {
89
+ ...payload,
90
+ ...(toolsChanged ? { tools: normalizedTools } : {}),
91
+ ...(includeChanged ? { include: normalizedInclude } : {}),
92
+ },
93
+ outcome: hasNativeTool ? "existing-native-tool" : "injected-native-tool",
94
+ changed: true,
95
+ };
96
+ }
@@ -1,20 +1,20 @@
1
- import type { WebSearchConfig } from "../types";
2
- import { isWebSearchEnabledForModel, WEB_SEARCH_PROMPT_MARKER, type WebSearchModel } from "./types";
3
-
4
- export const WEB_SEARCH_PROMPT_SECTION = `${WEB_SEARCH_PROMPT_MARKER}
5
- ## Web Search
6
-
7
- The hosted \`web_search\` tool is part of your tool list for this model. The API executes it server-side: it is a real callable tool even though no client-side function schema or description accompanies it. Call it whenever current or online information matters instead of guessing, cite the returned sources in the answer, and never claim that web search or internet access is unavailable while this section is present.`;
8
-
9
- export function appendWebSearchPrompt(args: {
10
- model: WebSearchModel | undefined;
11
- config: WebSearchConfig;
12
- systemPrompt: string;
13
- }): string {
14
- const { model, config, systemPrompt } = args;
15
- if (!isWebSearchEnabledForModel(model, config) || systemPrompt.includes(WEB_SEARCH_PROMPT_MARKER)) {
16
- return systemPrompt;
17
- }
18
-
19
- return `${systemPrompt.trimEnd()}\n\n${WEB_SEARCH_PROMPT_SECTION}`;
20
- }
1
+ import type { WebSearchConfig } from "../types";
2
+ import { isWebSearchEnabledForModel, WEB_SEARCH_PROMPT_MARKER, type WebSearchModel } from "./types";
3
+
4
+ export const WEB_SEARCH_PROMPT_SECTION = `${WEB_SEARCH_PROMPT_MARKER}
5
+ ## Web Search
6
+
7
+ The hosted \`web_search\` tool is part of your tool list for this model. The API executes it server-side: it is a real callable tool even though no client-side function schema or description accompanies it. Call it whenever current or online information matters instead of guessing, cite the returned sources in the answer, and never claim that web search or internet access is unavailable while this section is present.`;
8
+
9
+ export function appendWebSearchPrompt(args: {
10
+ model: WebSearchModel | undefined;
11
+ config: WebSearchConfig;
12
+ systemPrompt: string;
13
+ }): string {
14
+ const { model, config, systemPrompt } = args;
15
+ if (!isWebSearchEnabledForModel(model, config) || systemPrompt.includes(WEB_SEARCH_PROMPT_MARKER)) {
16
+ return systemPrompt;
17
+ }
18
+
19
+ return `${systemPrompt.trimEnd()}\n\n${WEB_SEARCH_PROMPT_SECTION}`;
20
+ }
@@ -1,45 +1,45 @@
1
- import { getExactModelKey, isExactModelAllowed } from "../model-scope";
2
- import type { WebSearchConfig } from "../types";
3
-
4
- export const WEB_SEARCH_CAPABLE_APIS = ["openai-responses", "openai-codex-responses"] as const;
5
- export const WEB_SEARCH_SOURCE_INCLUDE = "web_search_call.action.sources";
6
- export const WEB_SEARCH_PROMPT_MARKER = "<!-- pi-openai-toolkit:web-search -->";
7
-
8
- export type WebSearchCapableApi = (typeof WEB_SEARCH_CAPABLE_APIS)[number];
9
-
10
- export type WebSearchModel = {
11
- api?: string;
12
- provider?: string;
13
- id?: string;
14
- };
15
-
16
- export type WebSearchPayloadOutcome =
17
- | "disabled"
18
- | "unsupported-model"
19
- | "non-object-payload"
20
- | "invalid-tools"
21
- | "invalid-include"
22
- | "existing-native-tool"
23
- | "injected-native-tool";
24
-
25
- export type WebSearchPayloadTransform = {
26
- payload: unknown;
27
- outcome: WebSearchPayloadOutcome;
28
- changed: boolean;
29
- };
30
-
31
- export function getWebSearchModelKey(model: WebSearchModel | undefined): string | undefined {
32
- return getExactModelKey(model);
33
- }
34
-
35
- export function isWebSearchEnabledForModel(
36
- model: WebSearchModel | undefined,
37
- config: WebSearchConfig,
38
- ): boolean {
39
- return (
40
- config.enabled &&
41
- typeof model?.api === "string" &&
42
- (WEB_SEARCH_CAPABLE_APIS as readonly string[]).includes(model.api) &&
43
- isExactModelAllowed(model, config.models)
44
- );
45
- }
1
+ import { getExactModelKey, isExactModelAllowed } from "../model-scope";
2
+ import type { WebSearchConfig } from "../types";
3
+
4
+ export const WEB_SEARCH_CAPABLE_APIS = ["openai-responses", "openai-codex-responses"] as const;
5
+ export const WEB_SEARCH_SOURCE_INCLUDE = "web_search_call.action.sources";
6
+ export const WEB_SEARCH_PROMPT_MARKER = "<!-- pi-openai-toolkit:web-search -->";
7
+
8
+ export type WebSearchCapableApi = (typeof WEB_SEARCH_CAPABLE_APIS)[number];
9
+
10
+ export type WebSearchModel = {
11
+ api?: string;
12
+ provider?: string;
13
+ id?: string;
14
+ };
15
+
16
+ export type WebSearchPayloadOutcome =
17
+ | "disabled"
18
+ | "unsupported-model"
19
+ | "non-object-payload"
20
+ | "invalid-tools"
21
+ | "invalid-include"
22
+ | "existing-native-tool"
23
+ | "injected-native-tool";
24
+
25
+ export type WebSearchPayloadTransform = {
26
+ payload: unknown;
27
+ outcome: WebSearchPayloadOutcome;
28
+ changed: boolean;
29
+ };
30
+
31
+ export function getWebSearchModelKey(model: WebSearchModel | undefined): string | undefined {
32
+ return getExactModelKey(model);
33
+ }
34
+
35
+ export function isWebSearchEnabledForModel(
36
+ model: WebSearchModel | undefined,
37
+ config: WebSearchConfig,
38
+ ): boolean {
39
+ return (
40
+ config.enabled &&
41
+ typeof model?.api === "string" &&
42
+ (WEB_SEARCH_CAPABLE_APIS as readonly string[]).includes(model.api) &&
43
+ isExactModelAllowed(model, config.models)
44
+ );
45
+ }