pi-openai-codex-compat 0.0.17 → 0.0.19

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 (26) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +62 -38
  3. package/extensions/openai-codex-compat/apply-patch.ts +7 -0
  4. package/extensions/openai-codex-compat/codex-provider/codex-provider-contracts.ts +0 -2
  5. package/extensions/openai-codex-compat/codex-provider/codex-provider-runtime.ts +58 -23
  6. package/extensions/openai-codex-compat/codex-transport/codex-transport-contracts.ts +6 -0
  7. package/extensions/openai-codex-compat/codex-transport/codex-transport-errors.ts +13 -1
  8. package/extensions/openai-codex-compat/codex-transport/codex-transport-sse-stream.ts +1 -0
  9. package/extensions/openai-codex-compat/codex-transport/codex-transport-websocket-stream.ts +1 -0
  10. package/extensions/openai-codex-compat/codex-transport.ts +1 -0
  11. package/extensions/openai-codex-compat/command-tool-contract.ts +9 -0
  12. package/extensions/openai-codex-compat/command-tools.ts +4 -0
  13. package/extensions/openai-codex-compat/compaction-checkpoint.ts +35 -145
  14. package/extensions/openai-codex-compat/config.ts +0 -24
  15. package/extensions/openai-codex-compat/footer.ts +23 -3
  16. package/extensions/openai-codex-compat/image-generation.ts +8 -0
  17. package/extensions/openai-codex-compat/index.ts +2 -2
  18. package/extensions/openai-codex-compat/pi-runtime.ts +62 -9
  19. package/extensions/openai-codex-compat/remote-compaction.ts +46 -71
  20. package/extensions/openai-codex-compat/request-options.ts +2 -9
  21. package/extensions/openai-codex-compat/request-tools-entry.ts +113 -0
  22. package/extensions/openai-codex-compat/responses-lite.ts +1 -0
  23. package/extensions/openai-codex-compat/settings-pane.ts +2 -11
  24. package/extensions/openai-codex-compat/web-run.ts +2 -0
  25. package/extensions/settings-session.ts +11 -7
  26. package/package.json +7 -7
@@ -1,4 +1,3 @@
1
- import { isString } from "./value-contracts.ts";
2
1
  import type {
3
2
  BeforeProviderHeadersEvent,
4
3
  CompactionResult,
@@ -6,31 +5,34 @@ import type {
6
5
  ExtensionAPI,
7
6
  ExtensionContext,
8
7
  SessionBeforeCompactEvent,
9
- ToolInfo,
10
8
  } from "@earendil-works/pi-coding-agent";
11
- import type {
12
- Api,
13
- Model,
14
- OpenAICodexResponsesOptions,
15
- ProviderHeaders,
9
+ import {
10
+ getCurrentTools,
11
+ type Api,
12
+ type Message,
13
+ type Model,
14
+ type OpenAICodexResponsesOptions,
15
+ type ProviderHeaders,
16
16
  } from "@earendil-works/pi-ai";
17
- import { addRemoteCompactionFeature, isObject, type JsonRecord } from "./codex-protocol.ts";
17
+ import { addRemoteCompactionFeature, type JsonRecord } from "./codex-protocol.ts";
18
18
  import { CODEX_API, CODEX_PROVIDER } from "./codex-identifiers.ts";
19
19
  import {
20
- activeResponsesTools,
21
20
  branchInstructions,
22
- declaresActiveTools,
21
+ branchTranscript,
23
22
  providerHistory,
24
23
  remoteCompactionMarkerSummary,
24
+ requestGrammarToolInputProperties,
25
+ requestTools,
25
26
  responsesCompatibility,
26
27
  searchCheckpoint,
27
28
  type GrammarToolInputProperties,
28
29
  } from "./compaction-checkpoint.ts";
30
+ import { APPLY_PATCH_INPUT_PROPERTY, APPLY_PATCH_TOOL_NAME } from "./apply-patch.ts";
29
31
  import { resolveFileConfig, type ConfigResolver } from "./config-context.ts";
30
32
  import type { CodexProviderRuntime } from "./codex-provider.ts";
31
33
  import { responsesCompactionV2Metadata, type CodexCompactionMetadata } from "./codex-metadata.ts";
32
- import { APPLY_PATCH_INPUT_PROPERTY, APPLY_PATCH_TOOL_NAME } from "./apply-patch.ts";
33
34
  import { errorFromThrown } from "./error-from-thrown.ts";
35
+ import { latestRequestTools } from "./request-tools-entry.ts";
34
36
  import { selectedRegistryModel } from "./model-context.ts";
35
37
 
36
38
  export type RemoteCompactionContext = Parameters<ConfigResolver>[0] & {
@@ -88,7 +90,6 @@ export type RemoteCompactionHookHandler = (
88
90
 
89
91
  export type RemoteCompactionApi = {
90
92
  getActiveTools: () => string[];
91
- getAllTools: () => ToolInfo[];
92
93
  onBeforeProviderHeaders: (handler: RemoteCompactionHeadersHandler) => void;
93
94
  onContext: (handler: RemoteCompactionContextHandler) => void;
94
95
  onSessionBeforeCompact: (handler: RemoteCompactionHookHandler) => void;
@@ -113,7 +114,6 @@ export function remoteCompactionApi(pi: ExtensionAPI): RemoteCompactionApi {
113
114
 
114
115
  return {
115
116
  getActiveTools: () => pi.getActiveTools(),
116
- getAllTools: () => pi.getAllTools(),
117
117
  onBeforeProviderHeaders: (handler) =>
118
118
  pi.on("before_provider_headers", (event, ctx) => handler(event, context(ctx))),
119
119
  // The full-transcript event returns messages verbatim. A changed `context`
@@ -156,45 +156,28 @@ function instructionsForCompaction(systemPrompt: string, customInstructions?: st
156
156
  : systemPrompt;
157
157
  }
158
158
 
159
- function toolInputProperty(tool: ToolInfo | undefined): string | undefined {
160
- if (tool?.name === APPLY_PATCH_TOOL_NAME) return APPLY_PATCH_INPUT_PROPERTY;
161
- if (!tool || !isObject(tool.parameters)) return undefined;
162
- const required = Array.isArray(tool.parameters["required"])
163
- ? tool.parameters["required"].filter((name): name is string => typeof name === "string")
164
- : [];
165
- if (required.length !== 1 || !isObject(tool.parameters["properties"])) return undefined;
166
- const requiredProperty = required[0];
167
- if (requiredProperty === undefined) return undefined;
168
- const property = tool.parameters["properties"][requiredProperty];
169
- return isObject(property) && property.type === "string" ? requiredProperty : undefined;
170
- }
171
-
172
- function requestGrammarToolInputProperties(
173
- payload: JsonRecord,
174
- tools: readonly ToolInfo[],
175
- ): GrammarToolInputProperties {
176
- const byName = new Map(tools.map((tool) => [tool.name, tool]));
177
- const properties = new Map<string, string>();
178
- if (!Array.isArray(payload.tools)) return properties;
179
-
180
- for (const declaration of payload.tools) {
181
- if (!isObject(declaration)) continue;
182
- const tool = declaration;
183
- if (tool.type !== "custom" || !isString(tool.name)) continue;
184
- const property = toolInputProperty(byName.get(tool.name));
185
- if (property) properties.set(tool.name, property);
186
- }
187
- return properties;
188
- }
189
-
159
+ /**
160
+ * Grammar input properties for a branch without saved request tools. The next
161
+ * turn declares the transcript's tools plus the active ones. Sessions from
162
+ * before Pi recorded tool declarations declare none, so an active
163
+ * `apply_patch`, the only grammar tool Pi's tool registry can identify, is
164
+ * added as that turn would add it. Its replayed calls then keep their custom
165
+ * tool call and output types.
166
+ */
190
167
  function fallbackGrammarToolInputProperties(
191
- activeNames: readonly string[],
192
168
  model: Model<Api>,
169
+ transcript: readonly Message[],
170
+ activeNames: readonly string[],
193
171
  ): GrammarToolInputProperties {
194
- const compat = responsesCompatibility(model.compat);
195
- return activeNames.includes(APPLY_PATCH_TOOL_NAME) && compat?.supportsOpenAIGrammarTools
196
- ? new Map([[APPLY_PATCH_TOOL_NAME, APPLY_PATCH_INPUT_PROPERTY]])
197
- : new Map();
172
+ const properties = new Map(requestGrammarToolInputProperties(model, transcript));
173
+ if (
174
+ !properties.has(APPLY_PATCH_TOOL_NAME) &&
175
+ activeNames.includes(APPLY_PATCH_TOOL_NAME) &&
176
+ responsesCompatibility(model.compat).supportsOpenAIGrammarTools
177
+ ) {
178
+ properties.set(APPLY_PATCH_TOOL_NAME, APPLY_PATCH_INPUT_PROPERTY);
179
+ }
180
+ return properties;
198
181
  }
199
182
 
200
183
  function compactionMetadata(reason: SessionBeforeCompactEvent["reason"]): CodexCompactionMetadata {
@@ -242,13 +225,19 @@ export default function registerRemoteCompaction(
242
225
  if (!authentication.apiKey) throw new Error("OpenAI Codex authentication is unavailable.");
243
226
 
244
227
  const sessionId = ctx.sessionManager.getSessionId();
245
- const allTools = pi.getAllTools();
246
228
  const cached = runtime.latestTemplate(sessionId);
247
229
  const matching = cached?.modelId === ctx.model.id ? cached : undefined;
230
+ // Declare the tools the branch's latest request to this model sent, as
231
+ // saved in the session. The transcript also lists tools Pi leaves out of
232
+ // requests (codemode `only` mode), and other extensions can edit them.
233
+ // Sessions without a saved entry, such as those older versions wrote,
234
+ // declare the transcript's current tools.
235
+ const saved = latestRequestTools(event.branchEntries, ctx.model.id);
236
+ const transcript = saved ? undefined : branchTranscript(event.branchEntries);
248
237
  const config = resolveConfig(ctx);
249
238
  const grammarToolInputProperties =
250
- matching?.grammarToolInputProperties ??
251
- fallbackGrammarToolInputProperties(pi.getActiveTools(), ctx.model);
239
+ saved?.grammarToolInputProperties ??
240
+ fallbackGrammarToolInputProperties(ctx.model, transcript ?? [], pi.getActiveTools());
252
241
  const history = providerHistory({
253
242
  branch: event.branchEntries,
254
243
  wireModel: ctx.model,
@@ -256,22 +245,10 @@ export default function registerRemoteCompaction(
256
245
  imageDetail: config.imageDetail,
257
246
  recoverLatestOverflowPrefix: event.reason === "overflow" && event.willRetry,
258
247
  });
259
- // Reuse the last turn's exact declarations when they still describe the
260
- // active tools, so the compaction request shares the turn requests'
261
- // prompt-cache prefix. Rebuild from the registry when the active set or
262
- // any definition (including a parameter schema under an unchanged name)
263
- // changed since that turn, or when no turn template exists.
264
- const template: JsonRecord = { ...matching?.payload };
265
- const activeNames = pi.getActiveTools();
266
- const cachedTools = Array.isArray(matching?.payload.tools)
267
- ? matching.payload.tools.filter(isObject)
268
- : undefined;
269
- template.tools =
270
- cachedTools &&
271
- matching &&
272
- declaresActiveTools(cachedTools, matching.toolFingerprint, allTools, activeNames)
273
- ? cachedTools
274
- : (activeResponsesTools(allTools, activeNames, grammarToolInputProperties) ?? []);
248
+ const template: JsonRecord = {
249
+ ...matching?.payload,
250
+ tools: saved?.tools ?? requestTools(ctx.model, getCurrentTools(transcript ?? [])),
251
+ };
275
252
  const requestOptions: OpenAICodexResponsesOptions = {
276
253
  ...matching?.requestOptions,
277
254
  apiKey: authentication.apiKey,
@@ -291,9 +268,7 @@ export default function registerRemoteCompaction(
291
268
  branchInstructions(ctx.model, event.branchEntries) || ctx.getSystemPrompt(),
292
269
  event.customInstructions,
293
270
  ),
294
- grammarToolInputProperties:
295
- matching?.grammarToolInputProperties ??
296
- requestGrammarToolInputProperties(template, allTools),
271
+ grammarToolInputProperties,
297
272
  template,
298
273
  priority: config.fastMode,
299
274
  compactionMetadata: compactionMetadata(event.reason),
@@ -19,10 +19,6 @@ export function isCodexModel(model: Model<Api> | undefined): model is Model<type
19
19
  return model !== undefined && model.provider === CODEX_PROVIDER && hasApi(model, CODEX_API);
20
20
  }
21
21
 
22
- export function supportsReasoningMode(modelId: string): boolean {
23
- return modelId === "gpt-6-astra" || /^gpt-5\.6(?:-|$)/.test(modelId);
24
- }
25
-
26
22
  function isWebSearchTool(value: unknown): boolean {
27
23
  return isObject(value) && value.type === "web_search";
28
24
  }
@@ -72,11 +68,8 @@ export function applyCodexRequestOptions(
72
68
  } else {
73
69
  updatedReasoning["summary"] = config.reasoningSummary;
74
70
  }
75
- if (supportsReasoningMode(options.modelId) && config.reasoningMode === "pro") {
76
- updatedReasoning["mode"] = "pro";
77
- } else {
78
- Reflect.deleteProperty(updatedReasoning, "mode");
79
- }
71
+ // The Codex endpoint rejects `reasoning.mode`.
72
+ Reflect.deleteProperty(updatedReasoning, "mode");
80
73
  result["reasoning"] = updatedReasoning;
81
74
  }
82
75
 
@@ -0,0 +1,113 @@
1
+ import { isString } from "./value-contracts.ts";
2
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
3
+ import { isJsonValue, isObject, type JsonRecord } from "./codex-protocol.ts";
4
+ import type { GrammarToolInputProperties } from "./compaction-checkpoint.ts";
5
+ import { stableResponsesJson } from "./responses-replay.ts";
6
+
7
+ /**
8
+ * Custom session entry holding the exact tool declarations a turn request sent.
9
+ * Pi's transcript declares every active tool, but Pi removes some from requests
10
+ * (codemode `only` mode) and other extensions can edit the request. Native
11
+ * compaction declares these saved tools so it shares the turns' prompt-cache
12
+ * prefix, also after a resume or a branch switch.
13
+ */
14
+ export const REQUEST_TOOLS_ENTRY_TYPE = "openai-codex-compat-request-tools";
15
+ export const REQUEST_TOOLS_FORMAT_VERSION = 1;
16
+
17
+ export type RequestToolsData = {
18
+ kind: typeof REQUEST_TOOLS_ENTRY_TYPE;
19
+ version: typeof REQUEST_TOOLS_FORMAT_VERSION;
20
+ modelId: string;
21
+ tools: JsonRecord[];
22
+ /** Input properties of grammar tools, which replayed history encodes as custom tool calls. */
23
+ grammarToolInputProperties: Array<[string, string]>;
24
+ };
25
+
26
+ export type RequestTools = {
27
+ tools: JsonRecord[];
28
+ grammarToolInputProperties: GrammarToolInputProperties;
29
+ };
30
+
31
+ export function requestToolsData(
32
+ modelId: string,
33
+ tools: readonly JsonRecord[],
34
+ grammarToolInputProperties: GrammarToolInputProperties,
35
+ ): RequestToolsData {
36
+ return {
37
+ kind: REQUEST_TOOLS_ENTRY_TYPE,
38
+ version: REQUEST_TOOLS_FORMAT_VERSION,
39
+ modelId,
40
+ tools: tools.map((tool) => structuredClone(tool)),
41
+ grammarToolInputProperties: [...grammarToolInputProperties].toSorted(([left], [right]) =>
42
+ left.localeCompare(right),
43
+ ),
44
+ };
45
+ }
46
+
47
+ function parseRequestTools(value: unknown): RequestToolsData | undefined {
48
+ if (
49
+ !isObject(value) ||
50
+ value.kind !== REQUEST_TOOLS_ENTRY_TYPE ||
51
+ value.version !== REQUEST_TOOLS_FORMAT_VERSION ||
52
+ !isString(value.modelId) ||
53
+ !Array.isArray(value["tools"]) ||
54
+ !Array.isArray(value["grammarToolInputProperties"])
55
+ ) {
56
+ return undefined;
57
+ }
58
+ const tools: JsonRecord[] = [];
59
+ for (const tool of value["tools"]) {
60
+ if (!isObject(tool) || !isJsonValue(tool)) return undefined;
61
+ tools.push(structuredClone(tool));
62
+ }
63
+ const properties: Array<[string, string]> = [];
64
+ for (const pair of value["grammarToolInputProperties"]) {
65
+ if (!Array.isArray(pair) || pair.length !== 2) return undefined;
66
+ const [name, property] = pair;
67
+ if (!isString(name) || !isString(property)) return undefined;
68
+ properties.push([name, property]);
69
+ }
70
+ return {
71
+ kind: REQUEST_TOOLS_ENTRY_TYPE,
72
+ version: REQUEST_TOOLS_FORMAT_VERSION,
73
+ modelId: value.modelId,
74
+ tools,
75
+ grammarToolInputProperties: properties,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * The tools of the branch's latest request to a model. Entries that do not
81
+ * parse, such as a format a later version wrote, are skipped.
82
+ */
83
+ export function latestRequestTools(
84
+ branch: readonly SessionEntry[],
85
+ modelId: string,
86
+ ): RequestTools | undefined {
87
+ const data = latestRequestToolsData(branch, modelId);
88
+ return data
89
+ ? { tools: data.tools, grammarToolInputProperties: new Map(data.grammarToolInputProperties) }
90
+ : undefined;
91
+ }
92
+
93
+ function latestRequestToolsData(
94
+ branch: readonly SessionEntry[],
95
+ modelId: string,
96
+ ): RequestToolsData | undefined {
97
+ for (let index = branch.length - 1; index >= 0; index--) {
98
+ const entry = branch[index];
99
+ if (entry?.type !== "custom" || entry.customType !== REQUEST_TOOLS_ENTRY_TYPE) continue;
100
+ const data = parseRequestTools(entry.data);
101
+ if (data?.modelId === modelId) return data;
102
+ }
103
+ return undefined;
104
+ }
105
+
106
+ /** Whether a request's tools differ from the branch's latest saved tools for its model. */
107
+ export function requestToolsChanged(
108
+ branch: readonly SessionEntry[],
109
+ data: RequestToolsData,
110
+ ): boolean {
111
+ const latest = latestRequestToolsData(branch, data.modelId);
112
+ return latest === undefined || stableResponsesJson(latest) !== stableResponsesJson(data);
113
+ }
@@ -12,6 +12,7 @@ const RESPONSES_LITE_MODELS: ReadonlySet<string> = new Set([
12
12
  "gpt-6-astra",
13
13
  "gpt-6-sol",
14
14
  "gpt-6-luna",
15
+ "gpt-6.1-sol",
15
16
  ]);
16
17
 
17
18
  function isHostedTool(tool: JsonRecord): boolean {
@@ -17,7 +17,6 @@ import {
17
17
  CODEX_TOOL_BACKGROUND_SCHEMA,
18
18
  CODEX_SHELL_TOOL_SCHEMA,
19
19
  IMAGE_DETAIL_SCHEMA,
20
- REASONING_MODE_SCHEMA,
21
20
  REASONING_SUMMARY_SCHEMA,
22
21
  TEXT_VERBOSITY_SCHEMA,
23
22
  WEB_SEARCH_MODE_SCHEMA,
@@ -32,7 +31,7 @@ import {
32
31
  } from "./config.ts";
33
32
  import type { ConfigContext } from "./config-context.ts";
34
33
  import { selectedRegistryModel } from "./model-context.ts";
35
- import { isCodexModel, supportsReasoningMode } from "./request-options.ts";
34
+ import { isCodexModel } from "./request-options.ts";
36
35
  import { usesResponsesLite } from "./responses-lite.ts";
37
36
 
38
37
  export const settingFields: SettingsField[] = [
@@ -60,12 +59,6 @@ export const settingFields: SettingsField[] = [
60
59
  description: "Choose reasoning summary detail, or omit summaries.",
61
60
  choices: [...REASONING_SUMMARY_SCHEMA.enum],
62
61
  },
63
- {
64
- id: "reasoningMode",
65
- label: "Reasoning mode",
66
- description: "Standard or pro execution on supported models, independent of effort.",
67
- choices: [...REASONING_MODE_SCHEMA.enum],
68
- },
69
62
  {
70
63
  id: "toolBackground",
71
64
  label: "Codex tool background",
@@ -205,9 +198,7 @@ export default function registerCodexSettings(
205
198
  const model = selectedRegistryModel(ctx);
206
199
  const fields = settingFields.map((field) => {
207
200
  const inactive =
208
- !isCodexModel(model) ||
209
- (field.id === "responsesLite" && !usesResponsesLite(model.id)) ||
210
- (field.id === "reasoningMode" && !supportsReasoningMode(model.id));
201
+ !isCodexModel(model) || (field.id === "responsesLite" && !usesResponsesLite(model.id));
211
202
  return {
212
203
  ...field,
213
204
  description: `${inactive ? "Inactive on this model. " : ""}${field.description}`,
@@ -169,6 +169,8 @@ export default function registerWebRun(
169
169
  parameters: WEB_RUN_PARAMETERS,
170
170
  executionMode: "parallel",
171
171
  renderShell: "self",
172
+ // Searching and browsing change nothing locally but reach the open web.
173
+ annotations: { readOnlyHint: true, openWorldHint: true },
172
174
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
173
175
  const model = ctx.model;
174
176
  if (!isCodexModel(model)) {
@@ -57,22 +57,25 @@ export function readSessionSettings(
57
57
  !object(data["observed"])
58
58
  )
59
59
  return fail();
60
+ // Newer versions may remove a setting or narrow its values. Skip such fields so a session
61
+ // saved by an older version still resumes; only a malformed record fails.
60
62
  const values: SettingValues = {};
61
63
  const session: SettingsSessionState = { changes: {} };
62
64
  for (const [id, value] of Object.entries(data["values"])) {
63
65
  const field = fields.find((candidate) => candidate.id === id);
64
- if (!field || !valid(field, value)) return fail();
65
- values[id] = value;
66
+ if (field && valid(field, value)) values[id] = value;
66
67
  }
67
68
  const observed: Record<string, [string, string]> = {};
68
69
  for (const [id, change] of Object.entries(data["changes"])) {
70
+ if (!Array.isArray(change) || change.length > 1) return fail();
69
71
  const field = fields.find((candidate) => candidate.id === id);
70
- if (!field || !Array.isArray(change) || change.length > 1 || !Object.hasOwn(values, id))
71
- return fail();
72
+ if (!field || !Object.hasOwn(values, id)) continue;
72
73
  const value: unknown = change[0];
73
- if (change.length === 0) session.changes[id] = undefined;
74
- else if (valid(field, value)) session.changes[id] = value;
75
- else return fail();
74
+ let next: SettingValue | undefined;
75
+ if (change.length !== 0) {
76
+ if (!valid(field, value)) continue;
77
+ next = value;
78
+ }
76
79
  const hashes = data["observed"][id];
77
80
  if (!Array.isArray(hashes) || hashes.length !== 2) return fail();
78
81
  const global: unknown = hashes[0];
@@ -84,6 +87,7 @@ export function readSessionSettings(
84
87
  !/^[a-f0-9]{64}$/.test(project)
85
88
  )
86
89
  return fail();
90
+ session.changes[id] = next;
87
91
  observed[id] = [global, project];
88
92
  }
89
93
  if (Object.keys(session.changes).length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-openai-codex-compat",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -51,9 +51,9 @@
51
51
  "devDependencies": {
52
52
  "@2h2d/oxlint-config": "0.1.1",
53
53
  "@2h2d/ts-config": "0.0.1",
54
- "@earendil-works/pi-ai": "0.87.1",
55
- "@earendil-works/pi-coding-agent": "0.87.1",
56
- "@earendil-works/pi-tui": "0.87.1",
54
+ "@earendil-works/pi-ai": "0.99.1",
55
+ "@earendil-works/pi-coding-agent": "0.99.1",
56
+ "@earendil-works/pi-tui": "0.99.1",
57
57
  "@types/node": "22.20.1",
58
58
  "oxfmt": "0.64.0",
59
59
  "oxlint": "1.79.0",
@@ -62,9 +62,9 @@
62
62
  "typescript": "7.0.2"
63
63
  },
64
64
  "peerDependencies": {
65
- "@earendil-works/pi-ai": ">=0.87.0 <0.88.0",
66
- "@earendil-works/pi-coding-agent": ">=0.87.0 <0.88.0",
67
- "@earendil-works/pi-tui": ">=0.87.0 <0.88.0",
65
+ "@earendil-works/pi-ai": ">=0.99.1 <0.100.0",
66
+ "@earendil-works/pi-coding-agent": ">=0.99.1 <0.100.0",
67
+ "@earendil-works/pi-tui": ">=0.99.1 <0.100.0",
68
68
  "typebox": "*"
69
69
  },
70
70
  "engines": {