@criogaid/pi-codex-compaction 0.4.0 → 0.4.1

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,10 @@
2
2
 
3
3
  ## 未发布
4
4
 
5
+ ## 0.4.1
6
+
7
+ - V2 模型识别改为显式 `compat.remoteCompaction` 配置优先;未配置时,`openai-responses` 或 `openai-codex-responses` 模型的 ID 包含 `gpt`(不区分大小写)即尝试 V2,不再限制服务商或官方地址;其他 API 的 GPT 模型未配置时直接使用 Pi 原生压缩,不显示失败警告。无效配置不会退回名称匹配。检查点支持保存和恢复自定义 API 标识,继续校验后端身份。
8
+
5
9
  ## 0.4.0
6
10
 
7
11
  - 更新压缩历史的恢复提示,明确使用当前包 `@criogaid/pi-codex-compaction`。保留旧会话摘要的精确识别与消息编辑保护,无需迁移会话文件。
package/README.md CHANGED
@@ -141,9 +141,9 @@ Pi 原生压缩(文字摘要) <----------------------------+
141
141
 
142
142
  ## 自定义网关
143
143
 
144
- 默认只有 Pi 自带的 `openai-codex` 服务商(官方地址)会尝试 V2。其他网关须本身支持 Codex Remote Compaction V2,仅兼容普通 Responses API 不够;模型的 API 类型须为 `openai-responses` 或 `openai-codex-responses`。
144
+ 模型是否尝试 V2 由 `compat.remoteCompaction` 决定:有配置时按配置判断;没有配置时,API 类型为 `openai-responses` 或 `openai-codex-responses` 且模型 ID 包含 `gpt`(不区分大小写)即尝试 V2。其他 API 的请求格式无法携带 V2 压缩,未配置时直接使用 Pi 原生压缩。服务商名称和是否使用官方地址不参与启用判断。显示名称不参与匹配。
145
145
 
146
- 在 Pi 的 `models.json` 中为模型添加 `compat.remoteCompaction`:
146
+ 显式配置须指定 `protocol: "v2"`,可同时指定 `endpoint`。配置无效时不尝试 V2,也不会退回名称匹配。非 GPT 模型或需要覆盖端点时,可以在 Pi 的 `models.json` 中添加:
147
147
 
148
148
  ```json
149
149
  {
@@ -154,7 +154,7 @@ Pi 原生压缩(文字摘要) <----------------------------+
154
154
  "apiKey": "$CUSTOM_CODEX_API_KEY",
155
155
  "models": [
156
156
  {
157
- "id": "gpt-example",
157
+ "id": "custom-model",
158
158
  "compat": {
159
159
  "remoteCompaction": {
160
160
  "protocol": "v2"
@@ -171,6 +171,8 @@ Pi 原生压缩(文字摘要) <----------------------------+
171
171
 
172
172
  网关的压缩请求使用其他路径时,可以在 `remoteCompaction` 内增加 `endpoint`。它的协议、主机和端口须与 Pi 认证后实际使用的 `baseUrl` 一致,且不能包含用户名、密码、查询参数(`?`)或片段(`#`)。
173
173
 
174
+ V2 总开关仍决定是否发送新的远程压缩请求。模型名匹配只决定是否尝试;Pi 的适配器和网关仍须实际支持 V2,请求失败时按现有规则退回 Pi 原生压缩。默认端点按 API 推导:`openai-codex-responses` 使用 Codex 路径,其他 API 使用 `/responses`。检查点继续绑定实际 provider、API 和端点,防止跨后端重放。
175
+
174
176
  ## 注意事项
175
177
 
176
178
  - **V2 压缩结果绑定服务商。** V2 压缩后的旧记录以加密数据保存,只有原服务能读取,扩展无法还原成文字。重新打开会话或创建分支时可以继续使用,但须保持服务商、API 类型和请求地址不变。换到其他服务后,原生压缩只能根据 Pi 仍可读取的摘要和消息生成,无法包含这部分加密记录。
@@ -197,7 +199,7 @@ npm run pack:check
197
199
  <details>
198
200
  <summary><strong>发布流程</strong></summary>
199
201
 
200
- [Publish](.github/workflows/publish.yml) 在推送 `v*` 标签时运行。标签须与 `package.json` 的版本一致,例如版本 `0.4.0` 对应标签 `v0.4.0`。目前只发布正式版本,不接受 `-beta`、`-rc` 等预发布后缀。
202
+ [Publish](.github/workflows/publish.yml) 在推送 `v*` 标签时运行。标签须与 `package.json` 的版本一致,例如版本 `0.4.1` 对应标签 `v0.4.1`。目前只发布正式版本,不接受 `-beta`、`-rc` 等预发布后缀。
201
203
 
202
204
  首次发布前,在仓库的 **Settings → Secrets and variables → Actions** 中添加 `NPM_TOKEN`。按 [npm 文档](https://docs.npmjs.com/creating-and-viewing-access-tokens)创建 granular access token,授予 `@criogaid` 作用域的发布权限(Read and write / publish and stage),并启用 Bypass two-factor authentication。包创建后,可以将 token 权限缩小到该包。
203
205
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@criogaid/pi-codex-compaction",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Provider-aware Codex Remote Compaction V2 for Pi.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/capability.ts CHANGED
@@ -1,23 +1,20 @@
1
1
  // Own model eligibility and endpoint identity; authentication supplies the effective base URL.
2
2
  import type { Api, Model } from "@earendil-works/pi-ai";
3
- import { hasApi } from "@earendil-works/pi-ai";
4
3
  import { isObject } from "./protocol.js";
5
4
 
6
5
  export const CODEX_API = "openai-codex-responses" as const;
7
- export const OPENAI_RESPONSES_API = "openai-responses" as const;
8
- export type RemoteCompactionApi = typeof CODEX_API | typeof OPENAI_RESPONSES_API;
9
- const OFFICIAL_PROVIDER = "openai-codex";
10
- const OFFICIAL_BASE_URL = "https://chatgpt.com/backend-api";
6
+ // Name-matched models need a Responses payload with an input array and a query-free request URL.
7
+ const NAME_MATCH_APIS: readonly Api[] = ["openai-responses", CODEX_API];
11
8
 
12
9
  export interface ProviderIdentity {
13
10
  readonly provider: string;
14
- readonly api: RemoteCompactionApi;
11
+ readonly api: Api;
15
12
  readonly modelId: string;
16
13
  readonly baseUrl: string;
17
14
  readonly endpoint: string;
18
15
  }
19
16
  export interface CapableModel {
20
- readonly model: Model<RemoteCompactionApi>;
17
+ readonly model: Model<Api>;
21
18
  readonly identity: ProviderIdentity;
22
19
  }
23
20
 
@@ -30,18 +27,15 @@ export function normalizeUrl(value: string): string {
30
27
  url.pathname = url.pathname.replace(/\/+$/, "") || "/";
31
28
  return url.toString().replace(/\/$/, "");
32
29
  }
33
- /** Mirror Pi 0.99's resolveCodexUrl and the OpenAI SDK's `/responses` route for a normalized base URL. */
34
- export function deriveEndpoint(baseUrl: string, api: RemoteCompactionApi): string {
35
- if (api === OPENAI_RESPONSES_API) return `${baseUrl}/responses`;
30
+ /** Use Pi's Codex route for that adapter and the Responses route for other APIs. */
31
+ export function deriveEndpoint(baseUrl: string, api: Api): string {
32
+ if (api !== CODEX_API) return `${baseUrl}/responses`;
36
33
  if (baseUrl.endsWith("/codex/responses")) return baseUrl;
37
34
  if (baseUrl.endsWith("/codex")) return `${baseUrl}/responses`;
38
35
  return `${baseUrl}/codex/responses`;
39
36
  }
40
37
 
41
- function configuredEndpoint(model: Model<RemoteCompactionApi>, baseUrl: string): string | undefined {
42
- // Pi preserves extension metadata but its built-in compatibility type does not declare it.
43
- const compat: unknown = model.compat;
44
- const value = isObject(compat) ? compat.remoteCompaction : undefined;
38
+ function configuredEndpoint(value: unknown, model: Model<Api>, baseUrl: string): string | undefined {
45
39
  if (!isObject(value) || value.protocol !== "v2" ||
46
40
  (value.endpoint !== undefined && typeof value.endpoint !== "string")) return undefined;
47
41
  try {
@@ -54,7 +48,12 @@ function configuredEndpoint(model: Model<RemoteCompactionApi>, baseUrl: string):
54
48
  }
55
49
 
56
50
  export function capableModel(model: Model<Api> | undefined, effectiveBaseUrl?: string): CapableModel | undefined {
57
- if (!model || (!hasApi(model, CODEX_API) && !hasApi(model, OPENAI_RESPONSES_API))) return undefined;
51
+ if (!model) return undefined;
52
+ // Pi preserves extension metadata but its built-in compatibility type does not declare it.
53
+ const compat: unknown = model.compat;
54
+ const configured = isObject(compat) ? compat.remoteCompaction : undefined;
55
+ if (configured === undefined &&
56
+ (!NAME_MATCH_APIS.includes(model.api) || !model.id.toLowerCase().includes("gpt"))) return undefined;
58
57
  let configuredBaseUrl: string;
59
58
  let baseUrl: string;
60
59
  try {
@@ -63,8 +62,8 @@ export function capableModel(model: Model<Api> | undefined, effectiveBaseUrl?: s
63
62
  } catch {
64
63
  return undefined;
65
64
  }
66
- const endpoint = model.provider === OFFICIAL_PROVIDER && hasApi(model, CODEX_API) && configuredBaseUrl === OFFICIAL_BASE_URL
67
- ? deriveEndpoint(baseUrl, CODEX_API) : configuredEndpoint(model, baseUrl);
65
+ const endpoint = configured === undefined
66
+ ? deriveEndpoint(baseUrl, model.api) : configuredEndpoint(configured, model, baseUrl);
68
67
  return endpoint ? {
69
68
  model,
70
69
  identity: { provider: model.provider, api: model.api, modelId: model.id, baseUrl, endpoint },
package/src/checkpoint.ts CHANGED
@@ -12,8 +12,6 @@ import {
12
12
  } from "@earendil-works/pi-coding-agent";
13
13
  import { isObject, type JsonObject, REMOTE_COMPACTION_PROTOCOL, validateCompactionItem } from "./protocol.js";
14
14
  import {
15
- CODEX_API,
16
- OPENAI_RESPONSES_API,
17
15
  normalizeUrl,
18
16
  type ProviderIdentity,
19
17
  } from "./capability.js";
@@ -102,7 +100,8 @@ export function parseCheckpointDetails(value: unknown): CodexCheckpointDetails |
102
100
  value.checkpointId.length < 8 ||
103
101
  typeof value.provider !== "string" ||
104
102
  !value.provider ||
105
- (value.api !== CODEX_API && value.api !== OPENAI_RESPONSES_API) ||
103
+ typeof value.api !== "string" ||
104
+ !value.api ||
106
105
  typeof value.modelId !== "string" ||
107
106
  !value.modelId ||
108
107
  typeof value.baseUrl !== "string" ||
package/src/remote.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // Own V2 request adaptation; Pi's model registry owns authentication and provider dispatch.
2
2
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
3
- import type { Context, Model, ProviderHeaders, ThinkingBudgets, Usage } from "@earendil-works/pi-ai";
3
+ import type { Api, Context, Model, ProviderHeaders, ThinkingBudgets, Usage } from "@earendil-works/pi-ai";
4
4
  import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
5
- import { capableModel, deriveEndpoint, normalizeUrl, sameBackend, sameModel, type ProviderIdentity, type RemoteCompactionApi } from "./capability.js";
5
+ import { capableModel, deriveEndpoint, normalizeUrl, sameBackend, sameModel, type ProviderIdentity } from "./capability.js";
6
6
  import { trimToolOutputsToContextWindow } from "./context-window.js";
7
7
  import { estimateImages, type ImageEstimates } from "./image-budget.js";
8
8
  import { contextUserItems, type UserItemOrigin } from "./retention-input.js";
@@ -16,7 +16,7 @@ const MISSING_PAYLOAD_MESSAGE = "Provider did not expose a request payload";
16
16
 
17
17
  export interface RemoteCompactionRequest {
18
18
  modelRegistry: Pick<ModelRegistry, "streamSimple">;
19
- model: Model<RemoteCompactionApi>;
19
+ model: Model<Api>;
20
20
  context: Context;
21
21
  reasoning: ThinkingLevel;
22
22
  sessionId: string;