pi-openai-toolkit 0.14.13 → 0.15.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 +250 -241
- package/README.zh.md +250 -241
- package/config.schema.json +352 -0
- package/docs/configuration.md +176 -0
- package/docs/internals.md +139 -128
- package/package.json +135 -127
- package/src/auto-mode/extension.ts +35 -13
- package/src/config/legacy.ts +717 -0
- package/src/config/migration.ts +100 -0
- package/src/config/notifications.ts +28 -0
- package/src/config/policy.ts +122 -0
- package/src/config/v2.ts +1347 -0
- package/src/config-command.ts +73 -0
- package/src/config.ts +318 -739
- package/src/context-management/tools.ts +353 -347
- package/src/context-management/window-manager.ts +696 -693
- package/src/debug.ts +5 -5
- package/src/deferred-tool-carryover.ts +3 -1
- package/src/extension-runtime.ts +1375 -1329
- package/src/image-generation/extension.ts +14 -18
- package/src/image-generation/protocol.ts +10 -4
- package/src/image-generation/service.ts +9 -3
- package/src/request-context-cache.ts +2 -1
- package/src/types.ts +641 -628
- package/src/web-search/alpha-client.ts +876 -875
- package/src/web-search/extension.ts +518 -523
- package/src/web-search/payload.ts +295 -295
- package/src/web-search/prompt.ts +60 -59
- package/src/web-search/types.ts +144 -144
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StringEnum, Type, type Static } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { loadToolkitConfig } from "../config";
|
|
3
|
+
import { loadToolkitConfig, resolveToolkitConfig } from "../config";
|
|
4
|
+
import { notifyConfigIssues } from "../config/notifications";
|
|
4
5
|
import { executeImageGeneration } from "./service";
|
|
5
6
|
import { isImageGenerationEnabledForModel } from "./eligibility";
|
|
6
7
|
import { renderImageGenerationResult } from "./render";
|
|
@@ -77,7 +78,7 @@ const GenerateImageParameters = Type.Object(
|
|
|
77
78
|
],
|
|
78
79
|
{
|
|
79
80
|
description:
|
|
80
|
-
"Use null to accept the default image model,
|
|
81
|
+
"Use null to accept the default image model, configured by imageGeneration.defaultModel; otherwise provide another model id exactly as it appears in that configured list.",
|
|
81
82
|
},
|
|
82
83
|
),
|
|
83
84
|
),
|
|
@@ -115,14 +116,14 @@ export function registerImageGenerationExtension(
|
|
|
115
116
|
label: "OpenAI Generate Image",
|
|
116
117
|
description:
|
|
117
118
|
"Generate a PNG image, or edit from one to five user-approved local reference images, through the current Responses-capable model and a configured hosted image_generation model. This is a paid provider operation.",
|
|
118
|
-
promptSnippet: "Generate or edit PNG images through the current Responses-capable model and a configured imageGeneration.
|
|
119
|
+
promptSnippet: "Generate or edit PNG images through the current Responses-capable model and a configured imageGeneration.allowedModels entry.",
|
|
119
120
|
promptGuidelines: [
|
|
120
121
|
"Use openai_generate_image when the user explicitly asks to create, draw, render, or edit a raster image and the active model speaks a Responses API.",
|
|
121
122
|
"Do not call openai_generate_image speculatively: it consumes the user's provider or gateway image quota.",
|
|
122
123
|
"Keep the image prompt faithful to the user's requested subject and constraints; do not invent unrequested style details.",
|
|
123
124
|
"Set referenceImagePaths to null unless the user explicitly identified local files; never invent paths or placeholder strings, and remember upload requires user approval.",
|
|
124
125
|
"Set outputPath to null unless the user explicitly asks for a destination; never invent a destination or placeholder string, and otherwise use the default Pi agent artifact.",
|
|
125
|
-
"Set model to null unless the user named an image model configured in imageGeneration.
|
|
126
|
+
"Set model to null unless the user named an image model configured in imageGeneration.allowedModels; never invent or guess a model id, and remember an unlisted model fails before the paid request.",
|
|
126
127
|
"Do not substitute Python, browser automation, shell scripts, or unrelated image tools for an eligible image request.",
|
|
127
128
|
],
|
|
128
129
|
parameters: GenerateImageParameters,
|
|
@@ -151,20 +152,15 @@ export function registerImageGenerationExtension(
|
|
|
151
152
|
renderResult: renderImageGenerationResult,
|
|
152
153
|
});
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
pi.on("before_agent_start", (_event, ctx) => {
|
|
165
|
-
const { config } = loadConfig();
|
|
166
|
-
syncImageGenerationTool(pi, ctx.model, config.imageGeneration);
|
|
167
|
-
});
|
|
155
|
+
const synchronize = (ctx: Parameters<typeof executeImageGeneration>[0]["ctx"], model = ctx.model) => {
|
|
156
|
+
const resolved = resolveToolkitConfig(loadConfig(), model);
|
|
157
|
+
notifyConfigIssues(ctx, resolved);
|
|
158
|
+
const valid = !resolved.invalidFeatures.some((feature) => feature === "imageGeneration" || feature === "compatibility");
|
|
159
|
+
syncImageGenerationTool(pi, model, { ...resolved.config.imageGeneration, enabled: valid && resolved.policy.imageGeneration.enabled });
|
|
160
|
+
};
|
|
161
|
+
pi.on("session_start", (_event, ctx) => synchronize(ctx));
|
|
162
|
+
pi.on("model_select", (event, ctx) => synchronize(ctx, event.model));
|
|
163
|
+
pi.on("before_agent_start", (_event, ctx) => synchronize(ctx));
|
|
168
164
|
}
|
|
169
165
|
|
|
170
166
|
export default function imageGenerationExtension(pi: ExtensionAPI): void {
|
|
@@ -154,8 +154,8 @@ function usableImageModels(models: readonly string[] | undefined): string[] {
|
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
156
|
* Resolve the nested `image_generation` model for one call. An omitted request selects the
|
|
157
|
-
*
|
|
158
|
-
* match a configured id exactly
|
|
157
|
+
* explicit default when supplied (v2), or the first configured model for legacy callers.
|
|
158
|
+
* An explicit request must match a configured id exactly before any paid dispatch.
|
|
159
159
|
*/
|
|
160
160
|
function formatAvailableImageModels(models: readonly string[]): string {
|
|
161
161
|
const visible = models.slice(0, MAX_DISPLAYED_IMAGE_MODELS_IN_ERROR);
|
|
@@ -166,15 +166,21 @@ function formatAvailableImageModels(models: readonly string[]): string {
|
|
|
166
166
|
export function selectImageGenerationModel(args: {
|
|
167
167
|
requestedModel?: string;
|
|
168
168
|
configuredModels?: readonly string[];
|
|
169
|
+
defaultModel?: string;
|
|
169
170
|
}): string {
|
|
170
171
|
const models = usableImageModels(args.configuredModels);
|
|
172
|
+
if (args.defaultModel !== undefined && (!args.configuredModels?.length ||
|
|
173
|
+
!args.configuredModels.every((model) => typeof model === "string" && model.trim().length > 0 && model.trim().length <= MAX_IMAGE_MODEL_ID_CHARS) ||
|
|
174
|
+
!models.includes(args.defaultModel))) {
|
|
175
|
+
throw new ImageGenerationError("invalid-parameters", "The configured default image model must be in the nonempty allowedModels list.");
|
|
176
|
+
}
|
|
171
177
|
const requested = typeof args.requestedModel === "string" ? args.requestedModel.trim() : "";
|
|
172
|
-
if (!requested) return models[0]!;
|
|
178
|
+
if (!requested) return args.defaultModel ?? models[0]!;
|
|
173
179
|
if (models.includes(requested)) return requested;
|
|
174
180
|
throw new ImageGenerationError(
|
|
175
181
|
"invalid-parameters",
|
|
176
182
|
`Unknown image generation model "${requested.slice(0, MAX_IMAGE_MODEL_ID_CHARS)}". ` +
|
|
177
|
-
`Configure it in imageGeneration.models first; available models: ${formatAvailableImageModels(models)}.`,
|
|
183
|
+
`Configure it in imageGeneration.allowedModels (legacy: imageGeneration.models) first; available models: ${formatAvailableImageModels(models)}.`,
|
|
178
184
|
);
|
|
179
185
|
}
|
|
180
186
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getAgentDir, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { loadToolkitConfig } from "../config";
|
|
2
|
+
import { assertConfigValid, loadToolkitConfig, resolveToolkitConfig } from "../config";
|
|
3
|
+
import { notifyConfigIssues } from "../config/notifications";
|
|
3
4
|
import { resolveResponsesEnvironment } from "../runtime";
|
|
4
5
|
import {
|
|
5
6
|
copyImageToExplicitPath,
|
|
@@ -134,7 +135,10 @@ export function createImageGenerationExecutor(
|
|
|
134
135
|
return async (args) => {
|
|
135
136
|
const params = normalizeGenerateImageParams(args.params);
|
|
136
137
|
throwIfAborted(args.signal);
|
|
137
|
-
const
|
|
138
|
+
const resolved = resolveToolkitConfig(deps.loadConfig(), args.ctx.model);
|
|
139
|
+
notifyConfigIssues(args.ctx, resolved);
|
|
140
|
+
assertConfigValid(resolved, "imageGeneration", "compatibility");
|
|
141
|
+
const { config } = resolved;
|
|
138
142
|
if (!isImageGenerationEnabledForModel(args.ctx.model, config.imageGeneration)) {
|
|
139
143
|
throw new ImageGenerationError(
|
|
140
144
|
"unsupported-model",
|
|
@@ -144,12 +148,14 @@ export function createImageGenerationExecutor(
|
|
|
144
148
|
|
|
145
149
|
const imageModel = selectImageGenerationModel({
|
|
146
150
|
requestedModel: params.model,
|
|
147
|
-
configuredModels:
|
|
151
|
+
configuredModels: resolved.policy.imageGeneration.allowedModels,
|
|
152
|
+
defaultModel: resolved.policy.imageGeneration.defaultModel,
|
|
148
153
|
});
|
|
149
154
|
|
|
150
155
|
const runtimeResolution = await deps.resolveRuntime(args.ctx, {
|
|
151
156
|
enabled: config.imageGeneration.enabled,
|
|
152
157
|
responsesApis: IMAGE_GENERATION_CAPABLE_APIS,
|
|
158
|
+
...(resolved.format === "v2" ? { codexGatewayModels: resolved.gatewayModelKeys } : {}),
|
|
153
159
|
});
|
|
154
160
|
if (!runtimeResolution.ok) {
|
|
155
161
|
throw runtimeFailure(runtimeResolution.reason, runtimeResolution.errorMessage);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResponsesCompatibleRequestPayload } from "./runtime";
|
|
2
|
+
import { LOCAL_WEB_SEARCH_TOOL_NAME, WEB_RUN_TOOL_NAME } from "./web-search/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Explicit allowlist of fields mirrored from the latest live Responses request.
|
|
@@ -44,7 +45,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
44
45
|
function isWebSearchTool(value: unknown): boolean {
|
|
45
46
|
if (!isRecord(value)) return false;
|
|
46
47
|
if (value.type === "web_search" || value.type === "web_search_preview") return true;
|
|
47
|
-
return value.type === "function" && (value.name ===
|
|
48
|
+
return value.type === "function" && (value.name === LOCAL_WEB_SEARCH_TOOL_NAME || value.name === WEB_RUN_TOOL_NAME);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function sameIdentity(cachedIdentity: RequestContextIdentity, current: RequestContextIdentity): boolean {
|