pi-openai-toolkit 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # pi-openai-toolkit
2
2
 
3
3
  <p align="center">
4
- <strong>OpenAI-focused toolkit for <a href="https://github.com/badlogic/pi">Pi</a>: Native Remote Compaction v2, Hosted Web Search & Real-time Reasoning Translation</strong>
4
+ <strong>OpenAI-focused toolkit for <a href="https://github.com/earendil-works/pi">Pi</a>: Native Remote Compaction v2 & Hosted Web Search</strong>
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -10,11 +10,10 @@
10
10
 
11
11
  ---
12
12
 
13
- `pi-openai-toolkit` is an all-in-one OpenAI enhancement package for **Pi (>= 0.84.x)**. It bundles three independent extensions:
13
+ `pi-openai-toolkit` is an all-in-one OpenAI enhancement package for **Pi (>= 0.84.x)**. It bundles two high-performance extensions designed for OpenAI Responses endpoints:
14
14
 
15
15
  1. **Remote Responses Compaction v2** (`extensions/compaction.ts`): Server-side opaque context compaction using the `remote_compaction_v2` SSE streaming protocol. Preserves context fidelity without lossy text re-summarization.
16
16
  2. **Model-Scoped Web Search** (`extensions/web-search.ts`): Toolkit-owned hosted Web Search for an exact `provider/model-id` allowlist configured with `webSearch.models`, across the supported `openai-responses` and `openai-codex-responses` APIs.
17
- 3. **Real-time Reasoning Translation** (`extensions/reasoning-translation.ts`): TUI-only, display-layer translation of streaming thinking/reasoning segments through a separately configured model. It never changes the assistant message or the model context.
18
17
 
19
18
  ## Installation
20
19
 
@@ -58,9 +57,6 @@ pi --no-extensions -e /absolute/path/to/pi-openai-toolkit/extensions/compaction.
58
57
 
59
58
  # Web Search only
60
59
  pi --no-extensions -e /absolute/path/to/pi-openai-toolkit/extensions/web-search.ts
61
-
62
- # Reasoning Translation only
63
- pi --no-extensions -e /absolute/path/to/pi-openai-toolkit/extensions/reasoning-translation.ts
64
60
  ```
65
61
 
66
62
  > ⚠️ **Conflict Warning**: Do not run `pi-remote-compact`, `@lll9p/pi-better-compaction`, or standalone `pi-openai-web-search` alongside `pi-openai-toolkit`. Duplicate hooks cause race conditions and duplicated system prompts.
@@ -91,6 +87,11 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
91
87
  "remoteCompactModel": "uwoacrimson/gpt-5.6-luna",
92
88
  "model": null,
93
89
  "thinkingLevel": "off",
90
+ "autoCompaction": {
91
+ "enabled": true,
92
+ "continuation": "inline",
93
+ "unsupportedFallback": "followUp"
94
+ },
94
95
  "responsesApis": [
95
96
  "openai-responses",
96
97
  "openai-codex-responses"
@@ -107,14 +108,6 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
107
108
  "models": [
108
109
  "uwoacrimson/gpt-5.6-luna"
109
110
  ]
110
- },
111
- "reasoningTranslation": {
112
- "enabled": true,
113
- "models": [
114
- "uwoacrimson/gpt-5.6-luna"
115
- ],
116
- "model": "openai/gpt-4o-mini",
117
- "targetLanguage": "Simplified Chinese"
118
111
  }
119
112
  }
120
113
  ```
@@ -125,9 +118,13 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
125
118
 
126
119
  - `enabled` (*boolean*, default: `true`): Enable or disable Remote Compaction.
127
120
  - `allowCompactionContinuityBreak` (*boolean*, default: `false`): When `true`, allows restarting a fresh native opaque compaction chain from Pi's current session text if the latest compaction was made by another strategy (e.g. text summary).
128
- - `remoteCompactModel` (*string | null*, default: `null`): Model formatted as `provider/model-id` used only for the synthetic `remote_compaction_v2` request. Pi's active session model is not switched and resumes normal requests after compaction. The override must resolve to the same provider, Responses API identifier, and effective base URL as the active model. `null` preserves the current same-model path.
121
+ - `remoteCompactModel` (*string | null*, default: `null`): Model formatted as `provider/model-id` used only for the synthetic `remote_compaction_v2` request. Pi's active session model is not switched and resumes normal requests after compaction. The override must resolve to the same effective base URL as the active model; provider, Responses API identifier, and model may differ. `null` preserves the current same-model path.
129
122
  - `model` (*string | null*, default: `null`): Separate native text-summary fallback model formatted as `provider/model-id` (e.g. `"openai/gpt-4o-mini"`) used if remote v2 is unavailable or fails. If `null`, Pi uses its default current-model compaction path.
130
123
  - `thinkingLevel` (*string*, default: `"off"`): Reasoning / thinking level passed to Pi native compaction fallback (`"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`).
124
+ - `autoCompaction.enabled` (*boolean*, default: `true`): Check for compaction after a completed tool-result batch crosses Pi's context threshold.
125
+ - `autoCompaction.continuation` (*`"inline" | "followUp" | "off"`*, default: `"inline"`): `inline` compacts inside the current official Pi 0.84.3 tool loop and refreshes the next provider context; `followUp` uses Pi's public hidden follow-up message path; `off` disables toolkit-triggered automatic compaction.
126
+ - `autoCompaction.unsupportedFallback` (*`"followUp" | "off"`*, default: `"followUp"`): Behavior when the guarded official-host inline adapter is unavailable. It does not change normal remote/native compaction fallback ordering.
127
+ - `autoCompaction.reserveTokens` (*non-negative integer*, optional): Override only the automatic trigger reserve. When omitted, the active Pi compaction setting is used. Pi's `keepRecentTokens` remains authoritative for compactability.
131
128
  - `responsesApis` (*string[]*): Narrow the supported remote compaction API identifiers (subset of `["openai-responses", "openai-codex-responses"]`).
132
129
  - `notifyOnLoad` (*boolean*, default: `false`): Show a notification banner on Pi startup.
133
130
  - `debug` (*boolean*, default: `false`): Output diagnostics and write execution artifacts to disk.
@@ -140,19 +137,6 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
140
137
  - `enabled` (*boolean*, default: `true`): Enable or disable native OpenAI Web Search injection.
141
138
  - `models` (*string[]*): Exact `provider/model-id` allowlist. Toolkit Web Search is used only for listed models running on `openai-responses` or `openai-codex-responses`. An empty list disables toolkit Web Search for all models.
142
139
 
143
- #### `reasoningTranslation`
144
-
145
- - `enabled` (*boolean*, default: `true`): Enable display-only reasoning translation when the other fields also match.
146
- - `models` (*string[]*): Exact source-model allowlist. The key must be `provider/model-id`; an empty list disables translation.
147
- - `model` (*string | null*, default: `null`): Separate `provider/model-id` used for translation requests. It is never selected as Pi's active model and is called with thinking/reasoning disabled.
148
- - `targetLanguage` (*string*, default: `"Simplified Chinese"`): Natural-language name passed to the translator prompt.
149
-
150
- Reasoning Translation runs only in Pi's interactive **TUI** mode and only while the thinking area is visible. It listens to streaming `thinking_delta` events, groups text at sentence/newline boundaries, cuts unbroken text at about 400 UTF-16 characters, and flushes an idle tail after about 800 ms or at `thinking_end`. Requests run one at a time in FIFO order, so a slow segment cannot reorder the display.
151
-
152
- The Markdown transformer replaces only what the TUI renders. Original assistant thinking remains in the session and in every later provider/context payload; no custom message or tool call is sent to the main model. Completed display projections are stored as invisible custom session entries so resumed sessions can restore the same presentation. RPC, JSON, and print modes never call the translator and preserve Pi's original stream output.
153
-
154
- Translation adds model latency and usage cost. If the translator is missing, unavailable, rate-limited, times out, returns empty output, or fails for one segment, the main response continues; that segment falls back to its exact source text and later segments continue. Aborting, switching models/sessions, reloading, or quitting cancels the active queue and ignores late results.
155
-
156
140
  ---
157
141
 
158
142
  ## Core Mechanics
@@ -184,7 +168,16 @@ Content-Type: application/json
184
168
  2. Gateway failure or non-Responses model &rarr; Configured `compaction.model` fallback.
185
169
  3. Otherwise &rarr; Pi default native text compaction.
186
170
 
187
- ### 2. Model-Scoped Web Search
171
+ ### 2. Tool-Loop Auto-Compaction and Continuation
172
+
173
+ After every complete tool-call/result batch, the toolkit checks the current Pi usage threshold. It then mirrors official Pi's `prepareCompaction()` eligibility using Pi's exported cut-point and session-entry helpers before invoking the normal `AgentSession.compact()` path.
174
+
175
+ - **Inline mode**: On the guarded official Pi `0.84.3` host shape, compaction finishes before the next provider request and `prepareNextTurnWithContext` receives the newly built Pi session context. Remote v2, native fallback, replay rewriting, hook order, and deferred-tool carryover still run through their existing owners.
176
+ - **Too-small sessions**: Crossing the token threshold does not prove there is discardable history. If Pi has nothing to summarize, the toolkit does not call `compact()`, does not abort the active run, and lets the unfinished tool loop continue normally.
177
+ - **Public fallback**: `followUp` uses `ExtensionAPI.sendMessage` with a hidden `followUp` message after compaction. The turn is handled once, preventing repeated compaction/follow-up storms.
178
+ - **Version guard**: The private same-loop adapter is enabled only for the structurally verified official Pi `0.84.3` `AgentSession`. Unsupported hosts use the configured public fallback or disable automatic continuation.
179
+
180
+ ### 3. Model-Scoped Web Search
188
181
 
189
182
  For allowlisted models using `openai-responses` or `openai-codex-responses`, the extension hooks into `before_provider_request` and `before_agent_start`:
190
183
 
@@ -194,10 +187,6 @@ For allowlisted models using `openai-responses` or `openai-codex-responses`, the
194
187
  - **Zero-Latency**: Uses Pi's existing connection, auth headers, and base URL without standalone HTTP overhead.
195
188
  - **Toolkit Ownership**: For an eligible model, the outgoing payload removes the local function tool named `web_search` so native search is the only search path. Switching to an ineligible model restores that local tool's previous active state.
196
189
 
197
- ### 3. Real-time Reasoning Translation
198
-
199
- The translation extension is intentionally independent from compaction and Web Search. It registers lifecycle listeners and one synchronous Markdown transformer, but no Function Tool and no provider-request rewrite. A nested `streamSimple()` call is made directly on the configured translator provider, which avoids switching the active Pi model and avoids recursive toolkit hooks. The TUI invalidation bridge refreshes the existing thinking container after each completed segment, preserving Pi's color, italic, and folding behavior.
200
-
201
190
  ---
202
191
 
203
192
  ## Development & Testing
package/README_zh.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # pi-openai-toolkit
2
2
 
3
3
  <p align="center">
4
- <strong>专为 <a href="https://github.com/badlogic/pi">Pi</a> 打造的 OpenAI 全功能增强工具包:远端无损压缩 v2、原生托管联网搜索与实时思考翻译</strong>
4
+ <strong>专为 <a href="https://github.com/earendil-works/pi">Pi</a> 打造的 OpenAI 全功能增强工具包:远端无损压缩 v2 & 原生托管联网搜索</strong>
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -10,11 +10,10 @@
10
10
 
11
11
  ---
12
12
 
13
- `pi-openai-toolkit` 是专为 **Pi (>= 0.84.x)** 打造的 OpenAI 全功能增强工具包,集成了三个可独立加载的扩展:
13
+ `pi-openai-toolkit` 是专为 **Pi (>= 0.84.x)** 打造的 OpenAI 全功能增强工具包,集成了两项针对 OpenAI Responses 协议的核心扩展:
14
14
 
15
15
  1. **远端无损压缩 (Remote Compaction v2)** (`extensions/compaction.ts`): 基于 `remote_compaction_v2` SSE 流式协议实现的服务端密文无损会话压缩,彻底告别会话变长后丢失细节的文本二次摘要。
16
16
  2. **按模型启用的联网搜索** (`extensions/web-search.ts`): 通过 `webSearch.models` 精确匹配 `provider/model-id`,由 Toolkit 为符合条件的 `openai-responses` 与 `openai-codex-responses` 会话提供并拥有原生联网搜索。
17
- 3. **实时思考翻译** (`extensions/reasoning-translation.ts`): 仅在 TUI 展示层把流式 thinking/reasoning 分段翻译为目标语言。它使用独立配置的翻译模型,不修改 assistant 消息、会话原文或主模型上下文。
18
17
 
19
18
  ## 安装使用
20
19
 
@@ -58,9 +57,6 @@ pi --no-extensions -e /本地绝对路径/pi-openai-toolkit/extensions/compactio
58
57
 
59
58
  # 仅加载原生联网搜索
60
59
  pi --no-extensions -e /本地绝对路径/pi-openai-toolkit/extensions/web-search.ts
61
-
62
- # 仅加载实时思考翻译
63
- pi --no-extensions -e /本地绝对路径/pi-openai-toolkit/extensions/reasoning-translation.ts
64
60
  ```
65
61
 
66
62
  > ⚠️ **冲突提示**:请勿同时启用 `pi-remote-compact`、`@lll9p/pi-better-compaction` 或独立的 `pi-openai-web-search`。多重钩子会产生竞态或注入重复的 System Prompt。
@@ -91,6 +87,11 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
91
87
  "remoteCompactModel": "uwoacrimson/gpt-5.6-luna",
92
88
  "model": null,
93
89
  "thinkingLevel": "off",
90
+ "autoCompaction": {
91
+ "enabled": true,
92
+ "continuation": "inline",
93
+ "unsupportedFallback": "followUp"
94
+ },
94
95
  "responsesApis": [
95
96
  "openai-responses",
96
97
  "openai-codex-responses"
@@ -107,14 +108,6 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
107
108
  "models": [
108
109
  "uwoacrimson/gpt-5.6-luna"
109
110
  ]
110
- },
111
- "reasoningTranslation": {
112
- "enabled": true,
113
- "models": [
114
- "uwoacrimson/gpt-5.6-luna"
115
- ],
116
- "model": "openai/gpt-4o-mini",
117
- "targetLanguage": "Simplified Chinese"
118
111
  }
119
112
  }
120
113
  ```
@@ -125,9 +118,13 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
125
118
 
126
119
  - `enabled` (*boolean*, 默认 `true`): 是否启用远端无损压缩。
127
120
  - `allowCompactionContinuityBreak` (*boolean*, 默认 `false`): 若最近一次历史压缩由其他方式(如文本摘要)生成,设为 `true` 允许直接从当前有效文本上下文重建密文压缩链。
128
- - `remoteCompactModel` (*string | null*, 默认 `null`): 仅用于 synthetic `remote_compaction_v2` 请求的模型,格式为 `"provider/model-id"`。Pi 不会切换当前会话模型,压缩完成后仍由原模型继续正常请求。覆盖模型必须与当前模型使用相同的 Provider、Responses API 标识符和实际生效的 Base URL。为 `null` 时保持当前模型执行远端压缩的原有行为。
121
+ - `remoteCompactModel` (*string | null*, 默认 `null`): 仅用于 synthetic `remote_compaction_v2` 请求的模型,格式为 `"provider/model-id"`。Pi 不会切换当前会话模型,压缩完成后仍由原模型继续正常请求。覆盖模型必须与当前模型使用相同的实际生效 Base URL;Provider、Responses API 标识符和模型本身可以不同。为 `null` 时保持当前模型执行远端压缩的原有行为。
129
122
  - `model` (*string | null*, 默认 `null`): 独立的原生文本摘要降级模型,格式为 `"provider/model-id"`(例如 `"openai/gpt-4o-mini"`)。当 Remote v2 不可用或失败时使用;为 `null` 时进入 Pi 默认的当前模型压缩路径。
130
123
  - `thinkingLevel` (*string*, 默认 `"off"`): 降级模型调用 Pi 原生压缩时的思考级别(`"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`)。
124
+ - `autoCompaction.enabled` (*boolean*, 默认 `true`): 完整工具调用/结果批次结束并越过 Pi 上下文阈值后,检查是否需要压缩。
125
+ - `autoCompaction.continuation` (*`"inline" | "followUp" | "off"`*, 默认 `"inline"`): `inline` 在官方 Pi 0.84.3 当前工具循环内完成压缩并刷新下一次 Provider 上下文;`followUp` 使用 Pi 公共的隐藏后续消息;`off` 关闭 Toolkit 触发的自动压缩。
126
+ - `autoCompaction.unsupportedFallback` (*`"followUp" | "off"`*, 默认 `"followUp"`): 官方 Host 私有 inline 适配器不可用时的处理方式,不改变原有远端/原生压缩降级顺序。
127
+ - `autoCompaction.reserveTokens` (*非负整数*, 可选): 仅覆盖自动触发阈值的保留 Token 数。未设置时使用当前 Pi 压缩配置;是否具有可压缩历史仍以 Pi 的 `keepRecentTokens` 为准。
131
128
  - `responsesApis` (*string[]*): 允许尝试远端压缩的 API 标识符子集(从 `["openai-responses", "openai-codex-responses"]` 中选定)。
132
129
  - `notifyOnLoad` (*boolean*, 默认 `false`): Pi 启动加载时是否弹出气泡提示。
133
130
  - `debug` (*boolean*, 默认 `false`): 是否开启调试模式并记录诊断日志。
@@ -140,19 +137,6 @@ C:\Users\<user>\.pi\agent\extensions\pi-openai-toolkit\config.json
140
137
  - `enabled` (*boolean*, 默认 `true`): 是否启用原生 OpenAI 联网搜索注入。
141
138
  - `models` (*string[]*): 精确的 `provider/model-id` 模型白名单。只有列表中的模型在 `openai-responses` 或 `openai-codex-responses` 端点上运行时,才会使用 Toolkit Web Search。空列表表示所有模型都不启用 Toolkit Web Search。
142
139
 
143
- #### `reasoningTranslation`(实时思考翻译配置)
144
-
145
- - `enabled` (*boolean*, 默认 `true`): 是否启用展示层思考翻译。
146
- - `models` (*string[]*): 主模型的精确 `provider/model-id` 白名单。空列表表示不翻译任何模型。
147
- - `model` (*string | null*, 默认 `null`): 独立的翻译模型,格式为 `provider/model-id`。它不会被设置为 Pi 当前模型,翻译请求也不会启用 thinking/reasoning。
148
- - `targetLanguage` (*string*, 默认 `"Simplified Chinese"`): 传给翻译模型的目标语言名称,可以改为其他自然语言。
149
-
150
- 实时思考翻译只在 Pi 交互式 **TUI** 模式运行,并且要求 Pi 没有全局隐藏 thinking 区域。扩展监听流式 `thinking_delta`,优先在句号、问号、分号或换行处分段;没有自然边界时约每 400 个 UTF-16 字符切分,空闲约 800 ms 或收到 `thinking_end` 时刷新尾部。翻译请求严格按 FIFO 单并发执行,不会因为响应速度不同而乱序。
151
-
152
- 替换只发生在 TUI 的 Markdown 展示层。session 中的原始 assistant thinking、后续 Provider payload、LLM 上下文和压缩输入都保持不变;扩展不会注册 Function Tool,也不会向主模型发送 custom message。已完成的展示映射会作为不可见的 custom entry 保存,恢复 session 或切换会话树后可以继续替换对应原文。RPC、JSON 与 print 模式不会调用翻译模型,也不会改变 Pi 原始输出。
153
-
154
- 翻译会增加额外延迟和模型费用。翻译模型未配置、认证失败、模型不存在、超时、限流、空输出或某个分段失败时,主模型响应仍会继续;失败分段精确回退为原文,后续分段继续处理。中止、切换模型/会话、扩展重载或退出时会取消当前队列并忽略迟到结果。
155
-
156
140
  ---
157
141
 
158
142
  ## 核心工作原理解析
@@ -184,7 +168,16 @@ Content-Type: application/json
184
168
  2. 网关不支持或报错 &rarr; 降级调用用户指定的 `compaction.model`;
185
169
  3. 未配置降级模型 &rarr; 回退至 Pi 默认的当前模型摘要压缩。
186
170
 
187
- ### 2. 按模型启用的联网搜索
171
+ ### 2. 工具循环内自动压缩与续跑
172
+
173
+ 每个完整工具调用/结果批次结束后,Toolkit 会检查当前 Pi 上下文阈值。真正调用压缩前,它使用 Pi 导出的 cut-point 与 session-entry helper 复现官方 `prepareCompaction()` 的可压缩性门禁,然后仍通过正常的 `AgentSession.compact()` 路径执行。
174
+
175
+ - **Inline 模式**:仅在结构校验通过的官方 Pi `0.84.3` Host 上启用。压缩会在下一次 Provider 请求前完成,`prepareNextTurnWithContext` 使用 Pi 重建后的最新会话上下文。Remote v2、原生降级、密文重放、Hook 顺序和 deferred-tool carryover 仍由原有实现负责。
176
+ - **历史过短**:越过 Token 阈值不代表一定存在可丢弃历史。若 Pi 没有可摘要内容,Toolkit 不调用 `compact()`、不终止当前运行,让未完成的工具循环正常继续。
177
+ - **公共降级**:`followUp` 通过 `ExtensionAPI.sendMessage` 发送隐藏的后续消息,并对同一工具批次只处理一次,避免重复压缩或消息风暴。
178
+ - **版本门禁**:私有同循环适配器仅支持结构验证通过的官方 Pi `0.84.3` `AgentSession`。其他 Host 按配置使用公共降级或关闭自动续跑。
179
+
180
+ ### 3. 按模型启用的联网搜索
188
181
 
189
182
  对于白名单中的 `openai-responses` 或 `openai-codex-responses` 模型,扩展直接拦截请求载荷:
190
183
 
@@ -194,10 +187,6 @@ Content-Type: application/json
194
187
  - **零额外网络延迟**:复用 Pi 当前活跃的 Provider 配置、认证凭据与 Base URL;
195
188
  - **Toolkit 接管搜索**:对于符合条件的模型,发给 Provider 的载荷会移除名为 `web_search` 的本地 Function Tool,只保留原生搜索;切换到不符合条件的模型后,会恢复该本地工具原先的 active 状态。
196
189
 
197
- ### 3. 实时思考翻译
198
-
199
- 该扩展与压缩、联网搜索彼此独立:只注册生命周期监听器和一个同步 Markdown transformer,不注册 Function Tool,也不改写主模型的 Provider request。翻译直接调用配置模型所属 Provider 的 `streamSimple()`,因此不会切换当前模型,也不会递归触发 Toolkit 的其他请求钩子。每个分段完成后通过 Pi 公共的 `setHiddenThinkingLabel()` 触发展示刷新,继续使用 Pi 原有 thinking 容器的颜色、斜体和折叠行为。
200
-
201
190
  ---
202
191
 
203
192
  ## 测试与验证
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "pi-openai-toolkit",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "OpenAI-focused Pi toolkit providing remote Responses compaction, native Web Search, and TUI reasoning translation.",
6
+ "description": "OpenAI-focused Pi toolkit providing remote Responses compaction and native Web Search.",
7
7
  "author": "awoaCrim and contributors",
8
8
  "license": "MIT",
9
9
  "repository": {
@@ -24,25 +24,24 @@
24
24
  "openai",
25
25
  "compaction",
26
26
  "web-search",
27
- "reasoning-translation",
28
27
  "codex",
29
28
  "responses"
30
29
  ],
31
30
  "pi": {
32
31
  "extensions": [
33
32
  "./extensions/compaction.ts",
34
- "./extensions/web-search.ts",
35
- "./extensions/reasoning-translation.ts"
33
+ "./extensions/web-search.ts"
36
34
  ]
37
35
  },
38
36
  "files": [
39
37
  "extensions/compaction.ts",
40
38
  "extensions/web-search.ts",
41
- "extensions/reasoning-translation.ts",
42
39
  "src/compact-client.ts",
43
40
  "src/config.ts",
44
41
  "src/debug.ts",
45
42
  "src/details-store.ts",
43
+ "src/deferred-tool-carryover.ts",
44
+ "src/inline-compaction.ts",
46
45
  "src/extension-runtime.ts",
47
46
  "src/native-fallback.ts",
48
47
  "src/payload-rewrite.ts",
@@ -57,12 +56,6 @@
57
56
  "src/web-search/payload.ts",
58
57
  "src/web-search/prompt.ts",
59
58
  "src/web-search/types.ts",
60
- "src/reasoning-translation/client.ts",
61
- "src/reasoning-translation/extension.ts",
62
- "src/reasoning-translation/persistence.ts",
63
- "src/reasoning-translation/segmenter.ts",
64
- "src/reasoning-translation/state.ts",
65
- "src/reasoning-translation/types.ts",
66
59
  "README.md",
67
60
  "README_zh.md",
68
61
  "LICENSE",
@@ -81,6 +74,9 @@
81
74
  "@earendil-works/pi-coding-agent": ">=0.80.0"
82
75
  },
83
76
  "devDependencies": {
77
+ "@earendil-works/pi-agent-core": "0.84.3",
78
+ "@earendil-works/pi-ai": "0.84.3",
79
+ "@earendil-works/pi-coding-agent": "0.84.3",
84
80
  "@types/node": "24.12.4",
85
81
  "bun": "1.2.23",
86
82
  "typescript": "5.9.3"
package/src/config.ts CHANGED
@@ -2,18 +2,16 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
5
- import { parseModelSpec } from "./runtime";
6
5
  import {
7
6
  DEFAULT_COMPACTION_CONFIG,
8
- DEFAULT_REASONING_TRANSLATION_CONFIG,
9
7
  DEFAULT_TOOLKIT_CONFIG,
8
+ DEFAULT_AUTO_COMPACTION_CONFIG,
10
9
  DEFAULT_WEB_SEARCH_CONFIG,
11
10
  RESPONSES_COMPACT_CAPABLE_APIS,
12
11
  THINKING_LEVELS,
13
12
  TOOLKIT_ID,
14
13
  type CompactionConfig,
15
14
  type LoadedToolkitConfig,
16
- type ReasoningTranslationConfig,
17
15
  type ToolkitConfig,
18
16
  type WebSearchConfig,
19
17
  } from "./types";
@@ -21,13 +19,14 @@ import {
21
19
  export const CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", TOOLKIT_ID);
22
20
  export const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
23
21
 
24
- const TOP_LEVEL_FIELDS = new Set(["compaction", "webSearch", "reasoningTranslation"]);
22
+ const TOP_LEVEL_FIELDS = new Set(["compaction", "webSearch"]);
25
23
  const COMPACTION_FIELDS = new Set([
26
24
  "enabled",
27
25
  "allowCompactionContinuityBreak",
28
26
  "remoteCompactModel",
29
27
  "model",
30
28
  "thinkingLevel",
29
+ "autoCompaction",
31
30
  "responsesApis",
32
31
  "notifyOnLoad",
33
32
  "debug",
@@ -36,8 +35,8 @@ const COMPACTION_FIELDS = new Set([
36
35
  "redactSensitiveData",
37
36
  "artifactRoot",
38
37
  ]);
38
+ const AUTO_COMPACTION_FIELDS = new Set(["enabled", "continuation", "unsupportedFallback", "reserveTokens"]);
39
39
  const WEB_SEARCH_FIELDS = new Set(["enabled", "models"]);
40
- const REASONING_TRANSLATION_FIELDS = new Set(["enabled", "models", "model", "targetLanguage"]);
41
40
 
42
41
  function isRecord(value: unknown): value is Record<string, unknown> {
43
42
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -115,6 +114,37 @@ function toThinkingLevel(value: unknown, fieldPath: string, warnings: string[]):
115
114
  return undefined;
116
115
  }
117
116
 
117
+ function toCompactionContinuation(
118
+ value: unknown,
119
+ fieldPath: string,
120
+ warnings: string[],
121
+ ): "inline" | "followUp" | "off" | undefined {
122
+ if (value === undefined) return undefined;
123
+ if (value === "inline" || value === "followUp" || value === "off") return value;
124
+ warnings.push(`Ignoring ${fieldPath}: expected one of inline, followUp, off.`);
125
+ return undefined;
126
+ }
127
+
128
+ function toUnsupportedFallback(
129
+ value: unknown,
130
+ fieldPath: string,
131
+ warnings: string[],
132
+ ): "followUp" | "off" | undefined {
133
+ if (value === undefined) return undefined;
134
+ if (value === "followUp" || value === "off") return value;
135
+ warnings.push(`Ignoring ${fieldPath}: expected one of followUp, off.`);
136
+ return undefined;
137
+ }
138
+
139
+ function toNonNegativeInteger(value: unknown, fieldPath: string, warnings: string[]): number | undefined {
140
+ if (value === undefined) return undefined;
141
+ if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value >= 0) {
142
+ return value;
143
+ }
144
+ warnings.push(`Ignoring ${fieldPath}: expected a non-negative integer.`);
145
+ return undefined;
146
+ }
147
+
118
148
  function toSupportedApis(
119
149
  value: unknown,
120
150
  fieldPath: string,
@@ -156,19 +186,42 @@ function cloneDefaults(): ToolkitConfig {
156
186
  return {
157
187
  compaction: {
158
188
  ...DEFAULT_COMPACTION_CONFIG,
189
+ autoCompaction: { ...DEFAULT_AUTO_COMPACTION_CONFIG },
159
190
  responsesApis: [...DEFAULT_COMPACTION_CONFIG.responsesApis],
160
191
  },
161
192
  webSearch: {
162
193
  ...DEFAULT_WEB_SEARCH_CONFIG,
163
194
  models: [...DEFAULT_WEB_SEARCH_CONFIG.models],
164
195
  },
165
- reasoningTranslation: {
166
- ...DEFAULT_REASONING_TRANSLATION_CONFIG,
167
- models: [...DEFAULT_REASONING_TRANSLATION_CONFIG.models],
168
- },
169
196
  };
170
197
  }
171
198
 
199
+ function applyAutoCompactionConfig(
200
+ raw: Record<string, unknown>,
201
+ resolved: CompactionConfig["autoCompaction"],
202
+ warnings: string[],
203
+ ): void {
204
+ warnUnknownFields(raw, AUTO_COMPACTION_FIELDS, "compaction.autoCompaction", warnings);
205
+ resolved.enabled = toBoolean(raw.enabled, "compaction.autoCompaction.enabled", warnings) ?? resolved.enabled;
206
+ resolved.continuation =
207
+ toCompactionContinuation(raw.continuation, "compaction.autoCompaction.continuation", warnings) ??
208
+ resolved.continuation;
209
+ resolved.unsupportedFallback =
210
+ toUnsupportedFallback(
211
+ raw.unsupportedFallback,
212
+ "compaction.autoCompaction.unsupportedFallback",
213
+ warnings,
214
+ ) ?? resolved.unsupportedFallback;
215
+ const reserveTokens = toNonNegativeInteger(
216
+ raw.reserveTokens,
217
+ "compaction.autoCompaction.reserveTokens",
218
+ warnings,
219
+ );
220
+ if (reserveTokens !== undefined) {
221
+ resolved.reserveTokens = reserveTokens;
222
+ }
223
+ }
224
+
172
225
  function applyCompactionConfig(
173
226
  raw: Record<string, unknown>,
174
227
  resolved: CompactionConfig,
@@ -213,6 +266,14 @@ function applyCompactionConfig(
213
266
  resolved.thinkingLevel =
214
267
  toThinkingLevel(raw.thinkingLevel, "compaction.thinkingLevel", warnings) ?? resolved.thinkingLevel;
215
268
 
269
+ if (raw.autoCompaction !== undefined) {
270
+ if (isRecord(raw.autoCompaction)) {
271
+ applyAutoCompactionConfig(raw.autoCompaction, resolved.autoCompaction, warnings);
272
+ } else {
273
+ warnings.push("Ignoring compaction.autoCompaction: expected a JSON object.");
274
+ }
275
+ }
276
+
216
277
  const apis = toSupportedApis(
217
278
  raw.responsesApis,
218
279
  "compaction.responsesApis",
@@ -244,40 +305,6 @@ function applyWebSearchConfig(
244
305
  }
245
306
  }
246
307
 
247
- function applyReasoningTranslationConfig(
248
- raw: Record<string, unknown>,
249
- resolved: ReasoningTranslationConfig,
250
- warnings: string[],
251
- ): void {
252
- warnUnknownFields(raw, REASONING_TRANSLATION_FIELDS, "reasoningTranslation", warnings);
253
- resolved.enabled =
254
- toBoolean(raw.enabled, "reasoningTranslation.enabled", warnings) ?? resolved.enabled;
255
-
256
- const models = toModelAllowlist(raw.models, "reasoningTranslation.models", warnings);
257
- if (models !== undefined) {
258
- resolved.models = models;
259
- }
260
-
261
- const modelSpec = toModelSpec(raw.model, "reasoningTranslation.model", warnings);
262
- if (modelSpec !== undefined) {
263
- if (modelSpec !== null && !parseModelSpec(modelSpec)) {
264
- warnings.push(
265
- "Ignoring reasoningTranslation.model: expected \"provider/model-id\" or null.",
266
- );
267
- } else {
268
- resolved.model = modelSpec === null ? undefined : modelSpec;
269
- }
270
- }
271
-
272
- if (raw.targetLanguage !== undefined) {
273
- if (typeof raw.targetLanguage === "string" && raw.targetLanguage.trim().length > 0) {
274
- resolved.targetLanguage = raw.targetLanguage.trim();
275
- } else {
276
- warnings.push("Ignoring reasoningTranslation.targetLanguage: expected a non-empty string.");
277
- }
278
- }
279
- }
280
-
281
308
  /**
282
309
  * Load the canonical toolkit config from
283
310
  * `~/.pi/agent/extensions/pi-openai-toolkit/config.json`.
@@ -308,14 +335,6 @@ export function loadToolkitConfig(configPath: string = CONFIG_PATH): LoadedToolk
308
335
  warnings.push("Ignoring webSearch: expected a JSON object.");
309
336
  }
310
337
  }
311
-
312
- if (raw.reasoningTranslation !== undefined) {
313
- if (isRecord(raw.reasoningTranslation)) {
314
- applyReasoningTranslationConfig(raw.reasoningTranslation, resolved.reasoningTranslation, warnings);
315
- } else {
316
- warnings.push("Ignoring reasoningTranslation: expected a JSON object.");
317
- }
318
- }
319
338
  }
320
339
 
321
340
  resolved.compaction.artifactRoot = resolveConfiguredPath(