@oh-my-pi/pi-coding-agent 4.8.3 → 5.0.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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.0.0] - 2026-01-12
6
+
7
+ ### Added
8
+
9
+ - Implemented `xhigh` thinking level for Anthropic models with increased reasoning limits
10
+
11
+ ## [4.9.0] - 2026-01-12
12
+
5
13
  ## [4.8.3] - 2026-01-12
6
14
 
7
15
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "4.8.3",
3
+ "version": "5.0.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -39,10 +39,10 @@
39
39
  "prepublishOnly": "bun run generate-template && bun run clean && bun run build"
40
40
  },
41
41
  "dependencies": {
42
- "@oh-my-pi/pi-agent-core": "4.8.3",
43
- "@oh-my-pi/pi-ai": "4.8.3",
44
- "@oh-my-pi/pi-git-tool": "4.8.3",
45
- "@oh-my-pi/pi-tui": "4.8.3",
42
+ "@oh-my-pi/pi-agent-core": "5.0.0",
43
+ "@oh-my-pi/pi-ai": "5.0.0",
44
+ "@oh-my-pi/pi-git-tool": "5.0.0",
45
+ "@oh-my-pi/pi-tui": "5.0.0",
46
46
  "@openai/agents": "^0.3.7",
47
47
  "@sinclair/typebox": "^0.34.46",
48
48
  "ajv": "^8.17.1",
@@ -1,24 +1,6 @@
1
+ import { logger } from "../core/logger";
1
2
  import { convertToPngWithImageMagick } from "./image-magick";
2
-
3
- // Cached vips instance
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- let vipsInstance: any;
6
- let vipsLoadFailed = false;
7
-
8
- async function getVips() {
9
- if (vipsLoadFailed) return undefined;
10
- if (vipsInstance) return vipsInstance;
11
-
12
- try {
13
- const wasmVips = await import("wasm-vips");
14
- const Vips = wasmVips.default ?? wasmVips;
15
- vipsInstance = await Vips();
16
- return vipsInstance;
17
- } catch {
18
- vipsLoadFailed = true;
19
- return undefined;
20
- }
21
- }
3
+ import { Vips } from "./vips";
22
4
 
23
5
  /**
24
6
  * Convert image to PNG format for terminal display.
@@ -34,23 +16,23 @@ export async function convertToPng(
34
16
  return { data: base64Data, mimeType };
35
17
  }
36
18
 
37
- // Try wasm-vips first
38
- const vips = await getVips();
39
- if (vips) {
40
- let image: ReturnType<typeof vips.Image.newFromBuffer> | undefined;
19
+ try {
20
+ const { Image } = await Vips();
21
+ const image = Image.newFromBuffer(Buffer.from(base64Data, "base64"));
41
22
  try {
42
- const buffer = Buffer.from(base64Data, "base64");
43
- image = vips.Image.newFromBuffer(buffer);
44
23
  const pngBuffer = image.writeToBuffer(".png");
45
24
  return {
46
25
  data: Buffer.from(pngBuffer).toString("base64"),
47
26
  mimeType: "image/png",
48
27
  };
49
- } catch {
50
- // wasm-vips failed, try ImageMagick fallback
51
28
  } finally {
52
- image?.delete();
29
+ image.delete();
53
30
  }
31
+ } catch (error) {
32
+ // wasm-vips failed, try ImageMagick fallback
33
+ logger.error("Failed to convert image to PNG with wasm-vips", {
34
+ error: error instanceof Error ? error.message : String(error),
35
+ });
54
36
  }
55
37
 
56
38
  // Fall back to ImageMagick
@@ -1,5 +1,7 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+ import { logger } from "../core/logger";
2
3
  import { getImageDimensionsWithImageMagick, resizeWithImageMagick } from "./image-magick";
4
+ import { Vips } from "./vips";
3
5
 
4
6
  export interface ImageResizeOptions {
5
7
  maxWidth?: number; // Default: 2000
@@ -79,26 +81,6 @@ function pickSmaller(
79
81
  return a.buffer.length <= b.buffer.length ? a : b;
80
82
  }
81
83
 
82
- // Cached vips instance
83
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
- let vipsInstance: any;
85
- let vipsLoadFailed = false;
86
-
87
- async function getVips() {
88
- if (vipsLoadFailed) return undefined;
89
- if (vipsInstance) return vipsInstance;
90
-
91
- try {
92
- const wasmVips = await import("wasm-vips");
93
- const Vips = wasmVips.default ?? wasmVips;
94
- vipsInstance = await Vips();
95
- return vipsInstance;
96
- } catch {
97
- vipsLoadFailed = true;
98
- return undefined;
99
- }
100
- }
101
-
102
84
  /**
103
85
  * Resize an image to fit within the specified max dimensions and file size.
104
86
  * Returns the original image if it already fits within the limits.
@@ -115,91 +97,71 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
115
97
  const opts = { ...DEFAULT_OPTIONS, ...options };
116
98
  const buffer = Buffer.from(img.data, "base64");
117
99
 
118
- const vips = await getVips();
119
- if (!vips) {
120
- return resizeImageWithImageMagick(img, opts);
121
- }
122
-
123
- let image: ReturnType<typeof vips.Image.newFromBuffer> | undefined;
124
100
  try {
125
- image = vips.Image.newFromBuffer(buffer);
126
- const originalWidth = image.width;
127
- const originalHeight = image.height;
128
- const format = img.mimeType?.split("/")[1] ?? "png";
129
-
130
- // Check if already within all limits (dimensions AND size)
131
- const originalSize = buffer.length;
132
- if (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && originalSize <= opts.maxBytes) {
133
- return {
134
- data: img.data,
135
- mimeType: img.mimeType ?? `image/${format}`,
136
- originalWidth,
137
- originalHeight,
138
- width: originalWidth,
139
- height: originalHeight,
140
- wasResized: false,
141
- };
142
- }
101
+ const { Image } = await Vips();
102
+ const image = Image.newFromBuffer(buffer);
103
+ try {
104
+ const originalWidth = image.width;
105
+ const originalHeight = image.height;
106
+ const format = img.mimeType?.split("/")[1] ?? "png";
107
+
108
+ // Check if already within all limits (dimensions AND size)
109
+ const originalSize = buffer.length;
110
+ if (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && originalSize <= opts.maxBytes) {
111
+ return {
112
+ data: img.data,
113
+ mimeType: img.mimeType ?? `image/${format}`,
114
+ originalWidth,
115
+ originalHeight,
116
+ width: originalWidth,
117
+ height: originalHeight,
118
+ wasResized: false,
119
+ };
120
+ }
143
121
 
144
- // Calculate initial dimensions respecting max limits
145
- let targetWidth = originalWidth;
146
- let targetHeight = originalHeight;
122
+ // Calculate initial dimensions respecting max limits
123
+ let targetWidth = originalWidth;
124
+ let targetHeight = originalHeight;
147
125
 
148
- if (targetWidth > opts.maxWidth) {
149
- targetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);
150
- targetWidth = opts.maxWidth;
151
- }
152
- if (targetHeight > opts.maxHeight) {
153
- targetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);
154
- targetHeight = opts.maxHeight;
155
- }
126
+ if (targetWidth > opts.maxWidth) {
127
+ targetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);
128
+ targetWidth = opts.maxWidth;
129
+ }
130
+ if (targetHeight > opts.maxHeight) {
131
+ targetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);
132
+ targetHeight = opts.maxHeight;
133
+ }
156
134
 
157
- // Helper to resize and encode in both formats, returning the smaller one
158
- function tryBothFormats(
159
- width: number,
160
- height: number,
161
- jpegQuality: number,
162
- ): { buffer: Uint8Array; mimeType: string } {
163
- const scale = Math.min(width / originalWidth, height / originalHeight);
164
- const resized = image!.resize(scale);
135
+ // Helper to resize and encode in both formats, returning the smaller one
136
+ function tryBothFormats(
137
+ width: number,
138
+ height: number,
139
+ jpegQuality: number,
140
+ ): { buffer: Uint8Array; mimeType: string } {
141
+ const scale = Math.min(width / originalWidth, height / originalHeight);
142
+ const resized = image!.resize(scale);
165
143
 
166
- const pngBuffer = resized.writeToBuffer(".png");
167
- const jpegBuffer = resized.writeToBuffer(".jpg", { Q: jpegQuality });
144
+ const pngBuffer = resized.writeToBuffer(".png");
145
+ const jpegBuffer = resized.writeToBuffer(".jpg", { Q: jpegQuality });
168
146
 
169
- resized.delete();
147
+ resized.delete();
170
148
 
171
- return pickSmaller(
172
- { buffer: pngBuffer, mimeType: "image/png" },
173
- { buffer: jpegBuffer, mimeType: "image/jpeg" },
174
- );
175
- }
149
+ return pickSmaller(
150
+ { buffer: pngBuffer, mimeType: "image/png" },
151
+ { buffer: jpegBuffer, mimeType: "image/jpeg" },
152
+ );
153
+ }
176
154
 
177
- // Try to produce an image under maxBytes
178
- const qualitySteps = [85, 70, 55, 40];
179
- const scaleSteps = [1.0, 0.75, 0.5, 0.35, 0.25];
155
+ // Try to produce an image under maxBytes
156
+ const qualitySteps = [85, 70, 55, 40];
157
+ const scaleSteps = [1.0, 0.75, 0.5, 0.35, 0.25];
180
158
 
181
- let best: { buffer: Uint8Array; mimeType: string };
182
- let finalWidth = targetWidth;
183
- let finalHeight = targetHeight;
159
+ let best: { buffer: Uint8Array; mimeType: string };
160
+ let finalWidth = targetWidth;
161
+ let finalHeight = targetHeight;
184
162
 
185
- // First attempt: resize to target dimensions, try both formats
186
- best = tryBothFormats(targetWidth, targetHeight, opts.jpegQuality);
187
-
188
- if (best.buffer.length <= opts.maxBytes) {
189
- return {
190
- data: Buffer.from(best.buffer).toString("base64"),
191
- mimeType: best.mimeType,
192
- originalWidth,
193
- originalHeight,
194
- width: finalWidth,
195
- height: finalHeight,
196
- wasResized: true,
197
- };
198
- }
199
-
200
- // Still too large - try JPEG with decreasing quality
201
- for (const quality of qualitySteps) {
202
- best = tryBothFormats(targetWidth, targetHeight, quality);
163
+ // First attempt: resize to target dimensions, try both formats
164
+ best = tryBothFormats(targetWidth, targetHeight, opts.jpegQuality);
203
165
 
204
166
  if (best.buffer.length <= opts.maxBytes) {
205
167
  return {
@@ -212,19 +174,10 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
212
174
  wasResized: true,
213
175
  };
214
176
  }
215
- }
216
-
217
- // Still too large - reduce dimensions progressively
218
- for (const scale of scaleSteps) {
219
- finalWidth = Math.round(targetWidth * scale);
220
- finalHeight = Math.round(targetHeight * scale);
221
-
222
- if (finalWidth < 100 || finalHeight < 100) {
223
- break;
224
- }
225
177
 
178
+ // Still too large - try JPEG with decreasing quality
226
179
  for (const quality of qualitySteps) {
227
- best = tryBothFormats(finalWidth, finalHeight, quality);
180
+ best = tryBothFormats(targetWidth, targetHeight, quality);
228
181
 
229
182
  if (best.buffer.length <= opts.maxBytes) {
230
183
  return {
@@ -238,23 +191,51 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
238
191
  };
239
192
  }
240
193
  }
241
- }
242
194
 
243
- // Last resort: return smallest version we produced
244
- return {
245
- data: Buffer.from(best.buffer).toString("base64"),
246
- mimeType: best.mimeType,
247
- originalWidth,
248
- originalHeight,
249
- width: finalWidth,
250
- height: finalHeight,
251
- wasResized: true,
252
- };
253
- } catch {
254
- // wasm-vips failed - try ImageMagick fallback
195
+ // Still too large - reduce dimensions progressively
196
+ for (const scale of scaleSteps) {
197
+ finalWidth = Math.round(targetWidth * scale);
198
+ finalHeight = Math.round(targetHeight * scale);
199
+
200
+ if (finalWidth < 100 || finalHeight < 100) {
201
+ break;
202
+ }
203
+
204
+ for (const quality of qualitySteps) {
205
+ best = tryBothFormats(finalWidth, finalHeight, quality);
206
+
207
+ if (best.buffer.length <= opts.maxBytes) {
208
+ return {
209
+ data: Buffer.from(best.buffer).toString("base64"),
210
+ mimeType: best.mimeType,
211
+ originalWidth,
212
+ originalHeight,
213
+ width: finalWidth,
214
+ height: finalHeight,
215
+ wasResized: true,
216
+ };
217
+ }
218
+ }
219
+ }
220
+
221
+ // Last resort: return smallest version we produced
222
+ return {
223
+ data: Buffer.from(best.buffer).toString("base64"),
224
+ mimeType: best.mimeType,
225
+ originalWidth,
226
+ originalHeight,
227
+ width: finalWidth,
228
+ height: finalHeight,
229
+ wasResized: true,
230
+ };
231
+ } finally {
232
+ image.delete();
233
+ }
234
+ } catch (error) {
235
+ logger.error("Failed to resize image with wasm-vips", {
236
+ error: error instanceof Error ? error.message : String(error),
237
+ });
255
238
  return resizeImageWithImageMagick(img, opts);
256
- } finally {
257
- image?.delete();
258
239
  }
259
240
  }
260
241
 
@@ -0,0 +1,23 @@
1
+ import type realVips from "wasm-vips";
2
+ import { logger } from "../core/logger";
3
+
4
+ // Cached vips instance
5
+ let _vips: Promise<typeof realVips> | undefined;
6
+
7
+ /**
8
+ * Get the vips instance.
9
+ * @returns The vips instance.
10
+ */
11
+ export function Vips(): Promise<typeof realVips> {
12
+ if (_vips) return _vips;
13
+
14
+ let instance: Promise<typeof realVips> | undefined;
15
+ try {
16
+ instance = import("wasm-vips").then((mod) => (mod.default ?? mod)());
17
+ } catch (error) {
18
+ logger.error("Failed to import wasm-vips", { error: error instanceof Error ? error.message : String(error) });
19
+ instance = Promise.reject(error);
20
+ }
21
+ _vips = instance;
22
+ return instance;
23
+ }