pi-better-openai 0.1.4 → 0.1.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/README.md +11 -26
- package/extensions/image.ts +70 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,40 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
A pi extension for OpenAI subscription workflows: fast mode, usage visibility, footer polish, and image generation through `openai-codex` auth.
|
|
4
4
|
|
|
5
|
-
## Screenshots
|
|
6
|
-
|
|
7
|
-
<!-- Add screenshots here. -->
|
|
8
|
-
|
|
9
5
|
## Install
|
|
10
6
|
|
|
11
|
-
Install from
|
|
7
|
+
Install from GitHub:
|
|
12
8
|
|
|
13
9
|
```bash
|
|
14
|
-
pi install /
|
|
10
|
+
pi install github:mattleong/pi-better-openai
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
Or install from npm:
|
|
18
14
|
|
|
19
15
|
```bash
|
|
20
|
-
pi install
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Reload pi after installing:
|
|
24
|
-
|
|
25
|
-
```text
|
|
26
|
-
/reload
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Sign in to OpenAI Codex subscription auth if you want usage stats or image generation:
|
|
30
|
-
|
|
31
|
-
```text
|
|
32
|
-
/login openai-codex
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Start pi with fast mode enabled:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
pi --fast
|
|
16
|
+
pi install npm:pi-better-openai
|
|
39
17
|
```
|
|
40
18
|
|
|
41
19
|
## Features
|
|
@@ -50,3 +28,10 @@ pi --fast
|
|
|
50
28
|
- `/openai-image <prompt>` generates an image directly.
|
|
51
29
|
- `/openai-usage` shows current OpenAI subscription usage.
|
|
52
30
|
- `/openai-settings` opens settings, diagnostics, and config details.
|
|
31
|
+
|
|
32
|
+
## Screenshots
|
|
33
|
+
|
|
34
|
+
<!-- Add screenshots here. -->
|
|
35
|
+
|
|
36
|
+
<img width="983" height="851" alt="Screenshot 2026-04-29 at 11 53 23 PM" src="https://github.com/user-attachments/assets/07a2fb87-ef48-4396-8b12-124825c8d360" />
|
|
37
|
+
<img width="1327" height="102" alt="Screenshot 2026-04-29 at 11 34 49 PM" src="https://github.com/user-attachments/assets/22042782-c94e-491d-b5af-095f7f0810f9" />
|
package/extensions/image.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { extname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
6
6
|
import type { ResolvedConfig } from "./config.ts";
|
|
7
7
|
import { isRecord } from "./config.ts";
|
|
@@ -23,7 +23,7 @@ export type ImageOutputFormat = typeof IMAGE_OUTPUT_FORMATS[number];
|
|
|
23
23
|
const TOOL_PARAMS = {
|
|
24
24
|
type: "object",
|
|
25
25
|
properties: {
|
|
26
|
-
prompt: { type: "string", description: "Image generation/editing prompt." },
|
|
26
|
+
prompt: { type: "string", description: "Image generation/editing prompt. Pass the user's wording verbatim unless they explicitly ask you to refine or expand it." },
|
|
27
27
|
action: { type: "string", enum: IMAGE_ACTIONS, description: "Whether to generate a new image, edit/reference provided images, or let the model decide." },
|
|
28
28
|
images: {
|
|
29
29
|
type: "array",
|
|
@@ -338,6 +338,39 @@ async function requestCodexImage(params: ToolParams, ctx: ExtensionContext, cfg:
|
|
|
338
338
|
return { ...parsed, prompt: params.prompt, savedPath, model, action, outputFormat };
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
function displayPath(path: string): string {
|
|
342
|
+
const home = homedir();
|
|
343
|
+
if (!home) return path;
|
|
344
|
+
if (path === home) return "~";
|
|
345
|
+
const homePrefix = home.endsWith(sep) ? home : `${home}${sep}`;
|
|
346
|
+
return path.startsWith(homePrefix) ? `~/${path.slice(homePrefix.length)}` : path;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function textFromMessageContent(content: unknown): string | undefined {
|
|
350
|
+
if (typeof content === "string") return content.trim() || undefined;
|
|
351
|
+
if (!Array.isArray(content)) return undefined;
|
|
352
|
+
const text = content
|
|
353
|
+
.filter((part) => isRecord(part) && part.type === "text" && typeof part.text === "string")
|
|
354
|
+
.map((part) => (part as { text: string }).text)
|
|
355
|
+
.join("\n")
|
|
356
|
+
.trim();
|
|
357
|
+
return text || undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function latestUserPromptFromEntries(entries: unknown[]): string | undefined {
|
|
361
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
362
|
+
const entry = entries[i];
|
|
363
|
+
if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message) || entry.message.role !== "user") continue;
|
|
364
|
+
const text = textFromMessageContent(entry.message.content);
|
|
365
|
+
if (text) return text;
|
|
366
|
+
}
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function resolveToolPrompt(params: ToolParams, ctx: ExtensionContext): string {
|
|
371
|
+
return latestUserPromptFromEntries(ctx.sessionManager.getEntries()) ?? params.prompt;
|
|
372
|
+
}
|
|
373
|
+
|
|
341
374
|
function resultText(result: CodexImageResult): string {
|
|
342
375
|
const parts = [
|
|
343
376
|
`Generated image via openai-codex/${result.model}.`,
|
|
@@ -345,7 +378,7 @@ function resultText(result: CodexImageResult): string {
|
|
|
345
378
|
`Prompt: ${result.prompt}`
|
|
346
379
|
];
|
|
347
380
|
if (result.revisedPrompt) parts.push(`Revised prompt: ${result.revisedPrompt}`);
|
|
348
|
-
if (result.savedPath) parts.push(`Saved: ${result.savedPath}`);
|
|
381
|
+
if (result.savedPath) parts.push(`Saved: ${displayPath(result.savedPath)}`);
|
|
349
382
|
return parts.join("\n");
|
|
350
383
|
}
|
|
351
384
|
|
|
@@ -388,6 +421,35 @@ export function registerOpenAIImage(pi: ExtensionAPI, getConfig: (ctx: Extension
|
|
|
388
421
|
};
|
|
389
422
|
}
|
|
390
423
|
|
|
424
|
+
void import("@mariozechner/pi-tui").then(({ Box, Container, Image, Text }) => {
|
|
425
|
+
pi.registerMessageRenderer<CodexImageResult>("openai-image", (message, _options, theme) => {
|
|
426
|
+
const result = message.details;
|
|
427
|
+
const text = result && isRecord(result)
|
|
428
|
+
? resultText(result as CodexImageResult)
|
|
429
|
+
: typeof message.content === "string"
|
|
430
|
+
? message.content
|
|
431
|
+
: message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
432
|
+
const image = result && isRecord(result) && typeof result.data === "string" && typeof result.mimeType === "string"
|
|
433
|
+
? { data: result.data, mimeType: result.mimeType, savedPath: typeof result.savedPath === "string" ? result.savedPath : undefined }
|
|
434
|
+
: Array.isArray(message.content)
|
|
435
|
+
? message.content.find((part) => part.type === "image" && typeof part.data === "string" && typeof part.mimeType === "string")
|
|
436
|
+
: undefined;
|
|
437
|
+
|
|
438
|
+
const container = new Container();
|
|
439
|
+
const box = new Box(1, 1, (line) => theme.bg("customMessageBg", line));
|
|
440
|
+
box.addChild(new Text(`${theme.fg("accent", theme.bold("[openai-image]"))}\n\n${text}`, 0, 0));
|
|
441
|
+
if (image) {
|
|
442
|
+
box.addChild(new Image(image.data, image.mimeType, { fallbackColor: (line) => theme.fg("dim", line) }, {
|
|
443
|
+
maxWidthCells: 80,
|
|
444
|
+
maxHeightCells: 24,
|
|
445
|
+
filename: "savedPath" in image && typeof image.savedPath === "string" ? image.savedPath : undefined
|
|
446
|
+
}));
|
|
447
|
+
}
|
|
448
|
+
container.addChild(box);
|
|
449
|
+
return container;
|
|
450
|
+
});
|
|
451
|
+
}).catch(() => undefined);
|
|
452
|
+
|
|
391
453
|
pi.registerCommand(OPENAI_IMAGE_COMMAND, {
|
|
392
454
|
description: "Generate an image with OpenAI Codex image generation",
|
|
393
455
|
handler: async (args, ctx) => {
|
|
@@ -417,14 +479,16 @@ export function registerOpenAIImage(pi: ExtensionAPI, getConfig: (ctx: Extension
|
|
|
417
479
|
promptSnippet: "Generate or edit raster images via OpenAI Codex subscription auth.",
|
|
418
480
|
promptGuidelines: [
|
|
419
481
|
"Use openai_image when the user asks to generate or edit a raster image, photo, illustration, mockup, texture, sprite, or bitmap asset.",
|
|
482
|
+
"Pass the user's image prompt verbatim. Do not embellish, rewrite, add camera/style details, or add negative prompt terms unless the user explicitly asks you to refine the prompt.",
|
|
420
483
|
"Use openai_image with images for local reference images or edit targets; save project assets into the workspace when requested."
|
|
421
484
|
],
|
|
422
485
|
parameters: TOOL_PARAMS,
|
|
423
486
|
async execute(_toolCallId, params: ToolParams, signal, onUpdate, ctx) {
|
|
424
487
|
const cfg = getConfig(ctx);
|
|
425
488
|
const model = resolveModel(params, ctx, cfg);
|
|
489
|
+
const requestParams = { ...params, prompt: resolveToolPrompt(params, ctx) };
|
|
426
490
|
onUpdate?.({ content: [{ type: "text", text: `Requesting OpenAI image via openai-codex/${model}...` }] });
|
|
427
|
-
const result = await generate(
|
|
491
|
+
const result = await generate(requestParams, ctx, signal);
|
|
428
492
|
return {
|
|
429
493
|
content: [
|
|
430
494
|
{ type: "text", text: resultText(result) },
|
|
@@ -447,5 +511,7 @@ export const _imageTest = {
|
|
|
447
511
|
imageMimeType,
|
|
448
512
|
dataUrlParts,
|
|
449
513
|
extractImageFromEvent,
|
|
514
|
+
displayPath,
|
|
515
|
+
latestUserPromptFromEntries,
|
|
450
516
|
buildRequest
|
|
451
517
|
};
|