@howaboua/pi-codex-conversion 1.5.10-dev.41.7e2d8d2 → 1.5.10-dev.44.ec03884

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 CHANGED
@@ -57,6 +57,8 @@ The settings UI also has **Usage**, **Overrides**, and **About** tabs. **Usage**
57
57
 
58
58
  The **Compaction** tab can enable native OpenAI Responses compaction and choose the compaction model/reasoning. If native compaction fails, the extension falls back to Pi's normal compaction flow; when an older native compacted window exists, it is included in that Pi fallback summarization request so OpenAI can still use the prior opaque context server-side.
59
59
 
60
+ For OpenAI Codex subscription models, the extension also adjusts Pi's registered model context windows so Pi's fixed reserve-token compaction heuristic trips at roughly Codex's native auto-compact budget: 90% of Pi's resolved model window. This is calculated from Pi's current model metadata instead of hardcoded per-model limits.
61
+
60
62
  When `all` is on, non-Codex providers get the shell, patch, skill, and prompt-adapter behavior, but keep their normal Pi provider path. Native web search, native image generation, and priority service tier stay limited to the OpenAI Codex provider. Verbosity is applied to Responses API providers.
61
63
 
62
64
  The footer shows the active state, for example:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/pi-codex-conversion",
3
- "version": "1.5.10-dev.41.7e2d8d2",
3
+ "version": "1.5.10-dev.44.ec03884",
4
4
  "description": "Codex-oriented tool and prompt adapter for pi coding agent",
5
5
  "type": "module",
6
6
  "repository": {
@@ -60,9 +60,10 @@
60
60
  "typebox": "*"
61
61
  },
62
62
  "devDependencies": {
63
- "@earendil-works/pi-ai": "^0.75.3",
64
- "@earendil-works/pi-coding-agent": "^0.75.3",
65
- "@earendil-works/pi-tui": "^0.75.3",
63
+ "@earendil-works/pi-agent-core": "^0.75.4",
64
+ "@earendil-works/pi-ai": "^0.75.4",
65
+ "@earendil-works/pi-coding-agent": "^0.75.4",
66
+ "@earendil-works/pi-tui": "^0.75.4",
66
67
  "tsx": "^4.20.5",
67
68
  "typebox": "^1.1.24",
68
69
  "typescript": "^5.9.3"
@@ -0,0 +1,93 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
+ import type { AdapterState } from "./state.ts";
6
+
7
+ const OPENAI_CODEX_PROVIDER = "openai-codex";
8
+ const OPENAI_CODEX_API = "openai-codex-responses";
9
+ const CODEX_AUTO_COMPACT_NUMERATOR = 9;
10
+ const CODEX_AUTO_COMPACT_DENOMINATOR = 10;
11
+ const PI_DEFAULT_COMPACTION_RESERVE_TOKENS = 16_384;
12
+
13
+ export function getCodexAutoCompactBudget(contextWindow: number): number {
14
+ return Math.floor((contextWindow * CODEX_AUTO_COMPACT_NUMERATOR) / CODEX_AUTO_COMPACT_DENOMINATOR);
15
+ }
16
+
17
+ export function getPiContextWindowForCodexAutoCompact(contextWindow: number, reserveTokens = PI_DEFAULT_COMPACTION_RESERVE_TOKENS): number {
18
+ return Math.min(contextWindow, getCodexAutoCompactBudget(contextWindow) + reserveTokens);
19
+ }
20
+
21
+ function readSettings(path: string): Record<string, unknown> | undefined {
22
+ if (!existsSync(path)) return undefined;
23
+ try {
24
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
25
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : undefined;
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
30
+
31
+ function getReserveTokens(settings: Record<string, unknown> | undefined): number | undefined {
32
+ const compaction = settings?.compaction;
33
+ if (!compaction || typeof compaction !== "object" || Array.isArray(compaction)) return undefined;
34
+ const reserveTokens = (compaction as { reserveTokens?: unknown }).reserveTokens;
35
+ return typeof reserveTokens === "number" && Number.isFinite(reserveTokens) && reserveTokens >= 0 ? reserveTokens : undefined;
36
+ }
37
+
38
+ export function readPiCompactionReserveTokens(cwd: string): number {
39
+ return (
40
+ getReserveTokens(readSettings(join(cwd, ".pi", "settings.json"))) ??
41
+ getReserveTokens(readSettings(join(getAgentDir(), "settings.json"))) ??
42
+ PI_DEFAULT_COMPACTION_RESERVE_TOKENS
43
+ );
44
+ }
45
+
46
+ export function shouldUseCodexContextBudgetModel(model: Model<Api> | undefined): model is Model<Api> {
47
+ return model?.provider === OPENAI_CODEX_PROVIDER && model.api === OPENAI_CODEX_API;
48
+ }
49
+
50
+ function getModelKey(model: Model<Api>): string {
51
+ return `${model.provider}:${model.api}:${model.id}`;
52
+ }
53
+
54
+ function resolveRawContextWindow<TApi extends Api>(model: Model<TApi>, state: AdapterState | undefined): number {
55
+ if (!state) return model.contextWindow;
56
+ const key = getModelKey(model);
57
+ const cachedRaw = state.codexContextBudgetRawWindows?.[key];
58
+ if (cachedRaw === undefined) {
59
+ state.codexContextBudgetRawWindows ??= {};
60
+ state.codexContextBudgetRawWindows[key] = model.contextWindow;
61
+ return model.contextWindow;
62
+ }
63
+
64
+ const cachedAdjusted = getPiContextWindowForCodexAutoCompact(cachedRaw, state.codexContextBudgetReserveTokens);
65
+ const previousAdjusted = state.codexContextBudgetAdjustedWindows?.[key];
66
+ if (model.contextWindow !== cachedRaw && model.contextWindow !== cachedAdjusted && model.contextWindow !== previousAdjusted) {
67
+ state.codexContextBudgetRawWindows ??= {};
68
+ state.codexContextBudgetRawWindows[key] = model.contextWindow;
69
+ delete state.codexContextBudgetAdjustedWindows?.[key];
70
+ return model.contextWindow;
71
+ }
72
+ return cachedRaw;
73
+ }
74
+
75
+ export function getCodexContextBudgetAdjustedModel<TApi extends Api>(model: Model<TApi>, state?: AdapterState): Model<TApi> {
76
+ if (!shouldUseCodexContextBudgetModel(model)) return model;
77
+ const rawContextWindow = resolveRawContextWindow(model, state);
78
+ const contextWindow = getPiContextWindowForCodexAutoCompact(rawContextWindow, state?.codexContextBudgetReserveTokens);
79
+ if (state) {
80
+ state.codexContextBudgetAdjustedWindows ??= {};
81
+ state.codexContextBudgetAdjustedWindows[getModelKey(model)] = contextWindow;
82
+ }
83
+ return contextWindow === model.contextWindow ? model : { ...model, contextWindow };
84
+ }
85
+
86
+ export function applyCodexContextBudgetToModel<TApi extends Api>(model: Model<TApi> | undefined, state: AdapterState): void {
87
+ if (!model) return;
88
+ state.codexContextBudgetReserveTokens ??= readPiCompactionReserveTokens(state.cwd);
89
+ const adjustedModel = getCodexContextBudgetAdjustedModel(model, state);
90
+ if (adjustedModel !== model) {
91
+ model.contextWindow = adjustedModel.contextWindow;
92
+ }
93
+ }
@@ -95,12 +95,20 @@ function clampCodexReasoningEffort(modelId: string, effort: string): string {
95
95
  return effort;
96
96
  }
97
97
 
98
+ const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
99
+
100
+ function clampOpenAIPromptCacheKey(key: string): string {
101
+ const chars = Array.from(key);
102
+ if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH) return key;
103
+ return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
104
+ }
105
+
98
106
  function buildCompactionRequestOptions(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterState, compactionModel: string): NativeCompactionRequestOptions {
99
107
  const tools = buildCompactionTools(pi, ctx, state);
100
108
  const reasoning = buildCompactionReasoning(pi, ctx, state, compactionModel);
101
109
  return {
102
110
  parallel_tool_calls: true,
103
- prompt_cache_key: ctx.sessionManager.getSessionId(),
111
+ prompt_cache_key: clampOpenAIPromptCacheKey(ctx.sessionManager.getSessionId()),
104
112
  ...(isOpenAICodexContext(ctx) && state.config.fast ? { service_tier: "priority" } : {}),
105
113
  text: { verbosity: state.config.verbosity },
106
114
  ...(tools ? { tools } : {}),
@@ -19,4 +19,7 @@ export interface AdapterState {
19
19
  promptSkills: PromptSkill[];
20
20
  config: CodexConversionConfig;
21
21
  pendingPiCompactionNativeWindow?: PendingPiCompactionNativeWindow;
22
+ codexContextBudgetRawWindows?: Record<string, number>;
23
+ codexContextBudgetAdjustedWindows?: Record<string, number>;
24
+ codexContextBudgetReserveTokens?: number;
22
25
  }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { Model } from "@earendil-works/pi-ai";
2
3
  import { Box, Text, truncateToWidth } from "@earendil-works/pi-tui";
3
4
  import { getCodexRuntimeShell } from "./adapter/runtime-shell.ts";
4
5
  import { clearApplyPatchRenderState, registerApplyPatchTool } from "./tools/apply-patch-tool.ts";
@@ -22,6 +23,7 @@ import { getCodexSkillPaths, hasNoSkillsFlag } from "./adapter/skills.ts";
22
23
  import type { AdapterState } from "./adapter/state.ts";
23
24
  import { registerCodexCommand } from "./codex-settings/command.ts";
24
25
  import { WEB_SEARCH_TOOL_NAME } from "./adapter/tool-set.ts";
26
+ import { applyCodexContextBudgetToModel, readPiCompactionReserveTokens } from "./adapter/codex-context-budget.ts";
25
27
 
26
28
  function getCommandArg(args: unknown): string | undefined {
27
29
  if (!args || typeof args !== "object" || !("cmd" in args) || typeof args.cmd !== "string") {
@@ -62,6 +64,10 @@ export default function codexConversion(pi: ExtensionAPI) {
62
64
  }
63
65
  }
64
66
 
67
+ function ensureCodexContextBudgetModel(ctx: { model: Model<any> | undefined }): void {
68
+ applyCodexContextBudgetToModel(ctx.model, state);
69
+ }
70
+
65
71
  registerOpenAICodexCustomProvider(pi, {
66
72
  getCurrentCwd: () => state.cwd,
67
73
  getNativeToolRewriteConfig: () => ({
@@ -92,6 +98,8 @@ export default function codexConversion(pi: ExtensionAPI) {
92
98
  pi.on("session_start", async (_event, ctx) => {
93
99
  state.cwd = ctx.cwd;
94
100
  state.config = readCodexConversionConfig();
101
+ state.codexContextBudgetReserveTokens = readPiCompactionReserveTokens(ctx.cwd);
102
+ ensureCodexContextBudgetModel(ctx);
95
103
  ensureOptionalNativeToolsRegistered();
96
104
  state.promptSkills = extractPiPromptSkills(ctx.getSystemPrompt());
97
105
  registerViewImageTool(pi, { allowOriginalDetail: supportsOriginalImageDetail(ctx.model) });
@@ -108,6 +116,8 @@ export default function codexConversion(pi: ExtensionAPI) {
108
116
 
109
117
  pi.on("model_select", async (_event, ctx) => {
110
118
  state.cwd = ctx.cwd;
119
+ state.codexContextBudgetReserveTokens = readPiCompactionReserveTokens(ctx.cwd);
120
+ ensureCodexContextBudgetModel(ctx);
111
121
  state.promptSkills = extractPiPromptSkills(ctx.getSystemPrompt());
112
122
  registerViewImageTool(pi, { allowOriginalDetail: supportsOriginalImageDetail(ctx.model) });
113
123
  syncAdapter(pi, ctx, state);
@@ -37,6 +37,14 @@ const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
37
37
  const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
38
38
  const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
39
39
  const dynamicImport = (specifier: string) => import(specifier);
40
+ const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
41
+
42
+ function clampOpenAIPromptCacheKey(key: string | undefined): string | undefined {
43
+ if (key === undefined) return undefined;
44
+ const chars = Array.from(key);
45
+ if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH) return key;
46
+ return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
47
+ }
40
48
  let _os: { platform(): string; release(): string; arch(): string } | null = null;
41
49
 
42
50
  if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
@@ -87,6 +95,7 @@ interface QueuedWebSearchActivity {
87
95
  }
88
96
 
89
97
  type PendingActivity = QueuedImageActivity | QueuedWebSearchActivity;
98
+ type SendActivityMessage = ExtensionAPI["sendMessage"];
90
99
 
91
100
  interface CachedImagePreview {
92
101
  data: string;
@@ -577,7 +586,7 @@ export function buildRequestBody<TApi extends Api>(model: Model<TApi>, context:
577
586
  input: messages,
578
587
  text: { verbosity: ((options as { textVerbosity?: string } | undefined)?.textVerbosity ?? "low") as string },
579
588
  include: ["reasoning.encrypted_content"],
580
- prompt_cache_key: options?.sessionId,
589
+ prompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),
581
590
  tool_choice: "auto",
582
591
  parallel_tool_calls: true,
583
592
  };
@@ -1449,6 +1458,87 @@ export function buildWebSearchSummaryText(searches: SurfacedWebSearch[]): string
1449
1458
  return searches.length === 1 ? "Searched the web once" : `Searched the web ${searches.length} times`;
1450
1459
  }
1451
1460
 
1461
+ function sendActivityMessages(
1462
+ sendMessage: SendActivityMessage,
1463
+ imagePreviewCache: Map<string, CachedImagePreview>,
1464
+ activities: PendingActivity[],
1465
+ ): void {
1466
+ for (let index = 0; index < activities.length; index++) {
1467
+ const activity = activities[index];
1468
+ if (activity.kind === "image") {
1469
+ imagePreviewCache.set(activity.savedImage.absolutePath, activity.imageData);
1470
+ sendMessage(
1471
+ {
1472
+ customType: IMAGE_SAVE_DISPLAY_MESSAGE_TYPE,
1473
+ content: [{ type: "text", text: buildGeneratedImageDisplayText(activity.savedImage, { expanded: false }) }],
1474
+ display: true,
1475
+ details: { savedImages: [activity.savedImage] } satisfies ImageDisplayMessageDetails,
1476
+ },
1477
+ { triggerTurn: false },
1478
+ );
1479
+ continue;
1480
+ }
1481
+
1482
+ const searches = [activity.search];
1483
+ while (index + 1 < activities.length && activities[index + 1]?.kind === "web-search") {
1484
+ searches.push((activities[++index] as QueuedWebSearchActivity).search);
1485
+ }
1486
+ sendMessage(
1487
+ {
1488
+ customType: WEB_SEARCH_ACTIVITY_MESSAGE_TYPE,
1489
+ content: buildWebSearchActivityMessage(searches),
1490
+ display: true,
1491
+ details: { searches },
1492
+ },
1493
+ { triggerTurn: false },
1494
+ );
1495
+ }
1496
+ }
1497
+
1498
+ export function createActivityMessageDispatcher(sendMessage: SendActivityMessage): {
1499
+ imagePreviewCache: Map<string, CachedImagePreview>;
1500
+ enqueueSettledActivities(activities: PendingActivity[]): void;
1501
+ flushNow(): void;
1502
+ scheduleFlush(): void;
1503
+ clear(): void;
1504
+ } {
1505
+ const completedActivities: PendingActivity[] = [];
1506
+ const imagePreviewCache = new Map<string, CachedImagePreview>();
1507
+ let pendingFlushTimer: ReturnType<typeof setTimeout> | undefined;
1508
+
1509
+ const flush = () => {
1510
+ pendingFlushTimer = undefined;
1511
+ const activities = completedActivities.splice(0, completedActivities.length);
1512
+ if (activities.length > 0) sendActivityMessages(sendMessage, imagePreviewCache, activities);
1513
+ };
1514
+
1515
+ return {
1516
+ imagePreviewCache,
1517
+ enqueueSettledActivities(activities) {
1518
+ completedActivities.push(...activities);
1519
+ },
1520
+ flushNow() {
1521
+ if (pendingFlushTimer) {
1522
+ clearTimeout(pendingFlushTimer);
1523
+ pendingFlushTimer = undefined;
1524
+ }
1525
+ flush();
1526
+ },
1527
+ scheduleFlush() {
1528
+ if (pendingFlushTimer || completedActivities.length === 0) return;
1529
+ pendingFlushTimer = setTimeout(flush, 0);
1530
+ },
1531
+ clear() {
1532
+ if (pendingFlushTimer) {
1533
+ clearTimeout(pendingFlushTimer);
1534
+ pendingFlushTimer = undefined;
1535
+ }
1536
+ completedActivities.length = 0;
1537
+ imagePreviewCache.clear();
1538
+ },
1539
+ };
1540
+ }
1541
+
1452
1542
  function loadCachedImagePreview(savedImage: SavedGeneratedImage, imagePreviewCache: Map<string, CachedImagePreview>): CachedImagePreview | undefined {
1453
1543
  const cached = imagePreviewCache.get(savedImage.absolutePath);
1454
1544
  if (cached) return cached;
@@ -1543,6 +1633,7 @@ function createCodexStream<TApi extends Api>(
1543
1633
  getNativeToolRewriteConfig?: () => { webSearch: boolean; imageGeneration: boolean };
1544
1634
  onImageSaved?: (savedImage: SavedGeneratedImage, imageData: { data: string; mimeType: string }) => void;
1545
1635
  onWebSearchCaptured?: (search: SurfacedWebSearch) => void;
1636
+ onStreamSettled?: () => void;
1546
1637
  },
1547
1638
  ): AssistantMessageEventStream {
1548
1639
  const stream = createAssistantMessageEventStream();
@@ -1696,6 +1787,8 @@ function createCodexStream<TApi extends Api>(
1696
1787
  error: createErrorMessage(output, error, !!options?.signal?.aborted),
1697
1788
  });
1698
1789
  stream.end();
1790
+ } finally {
1791
+ deps.onStreamSettled?.();
1699
1792
  }
1700
1793
  })();
1701
1794
 
@@ -1703,75 +1796,31 @@ function createCodexStream<TApi extends Api>(
1703
1796
  }
1704
1797
 
1705
1798
  export function registerOpenAICodexCustomProvider(pi: ExtensionAPI, options: { getCurrentCwd: () => string; getNativeToolRewriteConfig?: () => { webSearch: boolean; imageGeneration: boolean } }): void {
1706
- const pendingActivities: PendingActivity[] = [];
1707
- const imagePreviewCache = new Map<string, CachedImagePreview>();
1708
- let pendingFlushTimer: ReturnType<typeof setTimeout> | undefined;
1709
-
1710
- const flushPendingMessages = () => {
1711
- pendingFlushTimer = undefined;
1712
- const activities = pendingActivities.splice(0, pendingActivities.length);
1713
-
1714
- for (let index = 0; index < activities.length; index++) {
1715
- const activity = activities[index];
1716
- if (activity.kind === "image") {
1717
- imagePreviewCache.set(activity.savedImage.absolutePath, activity.imageData);
1718
- pi.sendMessage(
1719
- {
1720
- customType: IMAGE_SAVE_DISPLAY_MESSAGE_TYPE,
1721
- content: [{ type: "text", text: buildGeneratedImageDisplayText(activity.savedImage, { expanded: false }) }],
1722
- display: true,
1723
- details: { savedImages: [activity.savedImage] } satisfies ImageDisplayMessageDetails,
1724
- },
1725
- { triggerTurn: false },
1726
- );
1727
- continue;
1728
- }
1729
-
1730
- const searches = [activity.search];
1731
- while (index + 1 < activities.length && activities[index + 1]?.kind === "web-search") {
1732
- searches.push((activities[++index] as QueuedWebSearchActivity).search);
1733
- }
1734
- pi.sendMessage(
1735
- {
1736
- customType: WEB_SEARCH_ACTIVITY_MESSAGE_TYPE,
1737
- content: buildWebSearchActivityMessage(searches),
1738
- display: true,
1739
- details: { searches },
1740
- },
1741
- { triggerTurn: false },
1742
- );
1743
- }
1744
- };
1745
-
1746
- const schedulePendingMessageFlush = () => {
1747
- if (pendingFlushTimer || pendingActivities.length === 0) {
1748
- return;
1749
- }
1750
- pendingFlushTimer = setTimeout(flushPendingMessages, 0);
1751
- };
1799
+ const activityDispatcher = createActivityMessageDispatcher(pi.sendMessage.bind(pi));
1752
1800
 
1753
1801
  const clearPendingMessages = () => {
1754
- if (pendingFlushTimer) {
1755
- clearTimeout(pendingFlushTimer);
1756
- pendingFlushTimer = undefined;
1757
- }
1758
- pendingActivities.length = 0;
1759
- imagePreviewCache.clear();
1802
+ activityDispatcher.clear();
1760
1803
  };
1761
1804
 
1762
1805
  pi.registerProvider("openai-codex", {
1763
1806
  api: "openai-codex-responses",
1764
- streamSimple: (model, context, streamOptions) =>
1765
- createCodexStream(model, context, streamOptions, {
1807
+ streamSimple: (model, context, streamOptions) => {
1808
+ const turnActivities: PendingActivity[] = [];
1809
+ return createCodexStream(model, context, streamOptions, {
1766
1810
  getCurrentCwd: options.getCurrentCwd,
1767
1811
  getNativeToolRewriteConfig: options.getNativeToolRewriteConfig,
1768
1812
  onImageSaved: (savedImage, imageData) => {
1769
- pendingActivities.push({ kind: "image", savedImage, imageData });
1813
+ turnActivities.push({ kind: "image", savedImage, imageData });
1770
1814
  },
1771
1815
  onWebSearchCaptured: (search) => {
1772
- pendingActivities.push({ kind: "web-search", search });
1816
+ turnActivities.push({ kind: "web-search", search });
1817
+ },
1818
+ onStreamSettled: () => {
1819
+ const activities = turnActivities.splice(0, turnActivities.length);
1820
+ if (activities.length > 0) activityDispatcher.enqueueSettledActivities(activities);
1773
1821
  },
1774
- }),
1822
+ });
1823
+ },
1775
1824
  });
1776
1825
 
1777
1826
  pi.on("session_start", async () => {
@@ -1779,15 +1828,13 @@ export function registerOpenAICodexCustomProvider(pi: ExtensionAPI, options: { g
1779
1828
  });
1780
1829
 
1781
1830
  pi.on("session_shutdown", async () => {
1782
- if (pendingActivities.length > 0) {
1783
- flushPendingMessages();
1784
- }
1831
+ activityDispatcher.flushNow();
1785
1832
  clearPendingMessages();
1786
1833
  closeOpenAICodexWebSocketSessions();
1787
1834
  });
1788
1835
 
1789
1836
  pi.on("agent_end", async () => {
1790
- schedulePendingMessageFlush();
1837
+ activityDispatcher.scheduleFlush();
1791
1838
  });
1792
1839
 
1793
1840
  pi.registerMessageRenderer<ImageDisplayMessageDetails>(IMAGE_SAVE_DISPLAY_MESSAGE_TYPE, (message, options, theme) => {
@@ -1804,7 +1851,7 @@ export function registerOpenAICodexCustomProvider(pi: ExtensionAPI, options: { g
1804
1851
  .join("\n");
1805
1852
  box.addChild(new Text(`\n${theme.fg("customMessageText", textContent)}`, 0, 0));
1806
1853
  if (savedImage) {
1807
- const preview = loadCachedImagePreview(savedImage, imagePreviewCache);
1854
+ const preview = loadCachedImagePreview(savedImage, activityDispatcher.imagePreviewCache);
1808
1855
  if (preview) {
1809
1856
  box.addChild(new Spacer(1));
1810
1857
  box.addChild(