dsh-lcx-codex 0.3.2 → 0.3.4
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 +18 -0
- package/README.md +20 -16
- package/README_EN.md +20 -16
- package/lib/client.js +4 -4
- package/lib/compact.js +101 -2
- package/lib/index.js +20 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.3.4 - 2026-08-21
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Hosted/Alpha Search 现在可由任意 DSH 主模型调用:当前主模型有兼容 GPT Responses 路由时优先复用,否则使用插件配置的 GPT 搜索路由。Native Compact 仍只作用于当前兼容 GPT 主模型。
|
|
12
|
+
- Hosted 启用时继续接管 DSH 的 `searchProviderId`,关闭或卸载时恢复启用前的 provider;该接管行为不再阻止非 GPT 主模型使用插件配置的 GPT 搜索路由。
|
|
13
|
+
|
|
14
|
+
## 0.3.3 - 2026-08-21
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Native V2 checkpoint 现在由客户端按 64K token 预算保留最近真实 user messages,再追加 exactly-one compaction item;不再错误假设上游 `response.output` 会返回 retained messages。旧的 compaction-only v3 checkpoint 会从 portable history 安全补全,运行时上下文注入不会进入 replacement history。
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
- 记录 installed `0.3.2` 的 compact + 三次 replay 缓存实测,并说明 Sub2API Codex OAuth 路径会删除上游不支持的 `prompt_cache_retention`;短缓存过期后的单次冷请求不等于插件改变了稳定前缀。
|
|
23
|
+
- 增加连续 replay 的前缀稳定性回归断言,区分缓存生命周期与 replacement-history 保真问题。
|
|
24
|
+
|
|
7
25
|
## 0.3.2 - 2026-08-21
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-lcx-codex)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
社区维护的 DSH
|
|
8
|
+
社区维护的 DSH 插件:让任意 DSH 主模型通过单独配置的 GPT Responses 路由调用 Hosted/Alpha Search,并为兼容 OpenAI Responses/Codex 协议的 GPT 主模型增加 Native V2 远程压缩。
|
|
9
9
|
|
|
10
10
|
> [!IMPORTANT]
|
|
11
11
|
> Alpha Search 有 5 种可用部署路径:Sub2API 直连,以及经 NewAPI 的 4 种渠道类型:`Sub2API`、`New API`、`ChatGPT Subscription (Codex)`、`Advanced Custom`。普通 `OpenAI` 渠道不支持 `/v1/alpha/search`,会在 NewAPI 内被拒绝。
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
```mermaid
|
|
21
21
|
flowchart LR
|
|
22
22
|
accTitle: dsh-lcx-codex 技术路线
|
|
23
|
-
accDescr:
|
|
23
|
+
accDescr: Hosted 和 Alpha 可由任意 DSH 主模型调用,经已配置的 GPT openai-responses 路由发送;启用 Hosted 时插件接管 DSH 搜索 provider,关闭后恢复原 provider。Native V2 Compact 只作用于当前兼容 GPT 路由。
|
|
24
24
|
|
|
25
|
-
dsh_session([DSH
|
|
25
|
+
dsh_session([任意 DSH 主模型]) --> search_route[当前兼容 GPT 路由<br/>否则使用插件配置的 GPT 路由]
|
|
26
|
+
gpt_session([兼容 GPT 主模型]) --> compact_route[复用当前 provider、model、baseURL 和凭据]
|
|
26
27
|
|
|
27
28
|
subgraph plugin_capabilities ["dsh-lcx-codex"]
|
|
28
29
|
hosted_search[Hosted Search<br/>POST /responses + web_search]
|
|
@@ -32,9 +33,9 @@ flowchart LR
|
|
|
32
33
|
native_compact[Native V2 Compact<br/>stream + compaction_trigger]
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
search_route --> hosted_search
|
|
37
|
+
search_route --> alpha_gate
|
|
38
|
+
compact_route --> native_compact
|
|
38
39
|
alpha_gate -->|是| alpha_search
|
|
39
40
|
alpha_gate -->|否| alpha_disabled
|
|
40
41
|
|
|
@@ -61,11 +62,11 @@ flowchart LR
|
|
|
61
62
|
|
|
62
63
|
| 功能 | 工具或协议 | 说明 |
|
|
63
64
|
|---|---|---|
|
|
64
|
-
| Hosted Web Search | `websearch_gpt` | `/responses` + `web_search`,返回正文、来源和 citations |
|
|
65
|
-
| Alpha Search | `websearch_alpha` | `/alpha/search`,支持 search、open/find/click、PDF screenshot、image、finance、weather、sports 和 time |
|
|
65
|
+
| Hosted Web Search | `websearch_gpt` | 任意主模型可调用;经 GPT Responses 路由执行 `/responses` + `web_search`,返回正文、来源和 citations |
|
|
66
|
+
| Alpha Search | `websearch_alpha` | 任意主模型可调用;经已验证的 GPT 路由执行 `/alpha/search`,支持 search、open/find/click、PDF screenshot、image、finance、weather、sports 和 time |
|
|
66
67
|
| Native V2 Compact | `/responses` + `compaction_trigger` | 保存 checkpoint v3,支持同路由回放、模型迁移、fork/tree、重启和图片 attachment |
|
|
67
68
|
|
|
68
|
-
Alpha 只有在 capability
|
|
69
|
+
Alpha 只有在 capability 记录与搜索后端的 endpoint、provider、model 和 schema 匹配时才会启用。Hosted 与 Alpha 是两条独立协议,不会互相静默降级。启用 Hosted 时,DSH 会临时切换为插件搜索 provider,原始 provider 不再处理搜索;关闭 Hosted 或卸载插件后恢复启用前的 provider。
|
|
69
70
|
|
|
70
71
|
## 安装
|
|
71
72
|
|
|
@@ -78,7 +79,7 @@ dsh plugin --profile web add dsh-lcx-codex
|
|
|
78
79
|
也可以下载 GitHub Release 中的 `.tgz` 安装指定版本:
|
|
79
80
|
|
|
80
81
|
```powershell
|
|
81
|
-
dsh plugin --profile web add .\dsh-lcx-codex-0.3.
|
|
82
|
+
dsh plugin --profile web add .\dsh-lcx-codex-0.3.4.tgz
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
安装后启动 DSH:
|
|
@@ -93,12 +94,12 @@ dsh web
|
|
|
93
94
|
|
|
94
95
|
- Node.js 20 或更高版本
|
|
95
96
|
- DSH `0.1.0-rc.8` 或兼容版本
|
|
96
|
-
- 已在 DSH
|
|
97
|
-
-
|
|
97
|
+
- 已在 DSH 中添加并能正常对话的主模型;调用 Hosted/Alpha 的主模型不限于 GPT
|
|
98
|
+
- 已配置一个可用的 GPT `openai-responses` provider,供 Hosted/Alpha 作为独立搜索后端;Native Compact 则要求当前主模型本身使用该类路由
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
Hosted/Alpha 在当前主模型本身是兼容 GPT Responses 路由时复用该路由,否则使用插件设置中的 GPT Responses 路由。Native Compact 只复用当前 GPT 主模型的 provider、model、Responses 地址、凭据引用、headers 和 retry policy。凭据由 DSH credentials service 解析,正常运行不需要再给插件配置一份 `LCX_API_KEY`。
|
|
100
101
|
|
|
101
|
-
界面中的 endpoint 和 model
|
|
102
|
+
界面中的 provider、endpoint 和 model 字段用于搜索回退路由,以及旧版直连配置兼容;当前主模型不是兼容 GPT 路由时,Hosted/Alpha 使用这里选择的 GPT 路由。同名 DSH provider 已存在时,以 DSH provider 配置为准。
|
|
102
103
|
|
|
103
104
|
## Alpha probe
|
|
104
105
|
|
|
@@ -124,12 +125,15 @@ node (Join-Path $dshHome 'profiles\web\node_modules\dsh-lcx-codex\scripts\probe-
|
|
|
124
125
|
|
|
125
126
|
## 数据与限制
|
|
126
127
|
|
|
127
|
-
-
|
|
128
|
-
-
|
|
128
|
+
- 搜索网络目标优先由当前兼容的 DSH `openai-responses` provider 决定,否则由插件设置中的 GPT provider 决定;不固定到 LCX 或其他域名,且只在该地址下调用 `/responses` 和 `/alpha/search`
|
|
129
|
+
- 凭据名称取自最终选中的 GPT provider 的 `apiKeyEnv`,并由 DSH credentials service 解析;插件不会自行保存 API key
|
|
130
|
+
- 启用 Hosted 时插件将 `ctx.web.searchProviderId` 临时切换为插件搜索 provider;关闭 Hosted 或卸载插件时恢复启用前的原 provider
|
|
129
131
|
- Checkpoint:`$DSH_HOME/storages/lcx-codex/checkpoints-v3.json`
|
|
130
132
|
- Alpha capability:`$DSH_HOME/storages/lcx-codex/web-alpha-capabilities.json`
|
|
131
133
|
- Alpha refs:`$DSH_HOME/storages/lcx-codex/web-alpha-refs.json`
|
|
132
134
|
- 只支持 Native remote-compaction V2,不调用 `/responses/compact`
|
|
135
|
+
- 同路由 replay 会复用 DSH session 的 `prompt_cache_key` 并保持已有请求前缀稳定;短期缓存过期后仍可能出现单次冷请求,不能用会话累计命中率判断插件是否破坏缓存
|
|
136
|
+
- Sub2API 的 Codex OAuth 转换层会删除上游不支持的 `prompt_cache_retention`,因此经该路径设置 `24h` 不会延长缓存;以实际连续请求的 `cached_tokens` 为准
|
|
133
137
|
- Checkpoint 不保存图片原始字节或 data URL
|
|
134
138
|
- Opaque checkpoint 不跨不兼容 provider、model、base URL、session 或 lineage 回放
|
|
135
139
|
- 不包含图片生成功能
|
package/README_EN.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-lcx-codex)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
A community-maintained DSH plugin that
|
|
8
|
+
A community-maintained DSH plugin that lets any DSH primary model call Hosted or Alpha Search through a separately configured GPT Responses route, and adds Native V2 remote compaction for compatible GPT primary models.
|
|
9
9
|
|
|
10
10
|
> [!IMPORTANT]
|
|
11
11
|
> Alpha Search currently has five supported deployment paths: a direct Sub2API connection, or one of four NewAPI channel types: `Sub2API`, `New API`, `ChatGPT Subscription (Codex)`, and `Advanced Custom`. A regular `OpenAI` channel does not support `/v1/alpha/search` and is rejected by NewAPI.
|
|
@@ -20,9 +20,10 @@ This list comes from NewAPI's current [`AlphaSearchHelper`](https://github.com/Q
|
|
|
20
20
|
```mermaid
|
|
21
21
|
flowchart LR
|
|
22
22
|
accTitle: dsh-lcx-codex architecture
|
|
23
|
-
accDescr:
|
|
23
|
+
accDescr: Hosted and Alpha can be called by any DSH primary model through a configured GPT openai-responses route. Enabling Hosted selects the plugin DSH search provider; disabling it restores the previous provider. Native V2 Compact applies only to the active compatible GPT route.
|
|
24
24
|
|
|
25
|
-
dsh_session([DSH
|
|
25
|
+
dsh_session([Any DSH primary model]) --> search_route[Use active compatible GPT route<br/>or configured GPT fallback route]
|
|
26
|
+
gpt_session([Compatible GPT primary model]) --> compact_route[Reuse active provider, model, baseURL, and credential]
|
|
26
27
|
|
|
27
28
|
subgraph plugin_capabilities ["dsh-lcx-codex"]
|
|
28
29
|
hosted_search[Hosted Search<br/>POST /responses + web_search]
|
|
@@ -32,9 +33,9 @@ flowchart LR
|
|
|
32
33
|
native_compact[Native V2 Compact<br/>stream + compaction_trigger]
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
search_route --> hosted_search
|
|
37
|
+
search_route --> alpha_gate
|
|
38
|
+
compact_route --> native_compact
|
|
38
39
|
alpha_gate -->|Yes| alpha_search
|
|
39
40
|
alpha_gate -->|No| alpha_disabled
|
|
40
41
|
|
|
@@ -61,11 +62,11 @@ This project is not affiliated with OpenAI and is not an official OpenAI plugin
|
|
|
61
62
|
|
|
62
63
|
| Feature | Tool or protocol | Behavior |
|
|
63
64
|
|---|---|---|
|
|
64
|
-
| Hosted Web Search | `websearch_gpt` |
|
|
65
|
-
| Alpha Search | `websearch_alpha` |
|
|
65
|
+
| Hosted Web Search | `websearch_gpt` | Callable by any primary model; uses a GPT Responses route for `/responses` with `web_search` and returns text, direct sources, and citations |
|
|
66
|
+
| Alpha Search | `websearch_alpha` | Callable by any primary model; uses a verified GPT route for `/alpha/search` search, image, open/find/click, PDF screenshot, finance, weather, sports, and time actions |
|
|
66
67
|
| Native V2 Compact | `/responses` with `compaction_trigger` | Stores checkpoint v3 and supports same-route replay, model migration, fork/tree sessions, restart recovery, and image attachments |
|
|
67
68
|
|
|
68
|
-
Alpha is enabled only when a capability record matches the
|
|
69
|
+
Alpha is enabled only when a capability record matches the search backend endpoint, provider, model, and schema. Hosted and Alpha are separate protocols and never silently fall back to each other. Enabling Hosted temporarily selects the plugin search provider, so the previous provider no longer handles search; disabling Hosted or removing the plugin restores the provider selected before activation.
|
|
69
70
|
|
|
70
71
|
## Installation
|
|
71
72
|
|
|
@@ -78,7 +79,7 @@ dsh plugin --profile web add dsh-lcx-codex
|
|
|
78
79
|
You can also install a specific `.tgz` file from a GitHub Release:
|
|
79
80
|
|
|
80
81
|
```powershell
|
|
81
|
-
dsh plugin --profile web add .\dsh-lcx-codex-0.3.
|
|
82
|
+
dsh plugin --profile web add .\dsh-lcx-codex-0.3.4.tgz
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
Start DSH after installation:
|
|
@@ -93,12 +94,12 @@ Open `Settings -> Plugins -> LCX / Codex capabilities` and enable Hosted, Alpha,
|
|
|
93
94
|
|
|
94
95
|
- Node.js 20 or later
|
|
95
96
|
- DSH `0.1.0-rc.8` or a compatible release
|
|
96
|
-
- A
|
|
97
|
-
-
|
|
97
|
+
- A primary model already added to DSH and working in a normal conversation; the caller of Hosted or Alpha does not need to be GPT
|
|
98
|
+
- A working GPT `openai-responses` provider for the Hosted/Alpha search backend; Native Compact requires the active primary model itself to use such a route
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
Hosted and Alpha reuse the active route when the primary model already uses a compatible GPT Responses route; otherwise they use the GPT Responses route selected in the plugin settings. Native Compact only reuses the active GPT primary model's provider, model, Responses URL, credential reference, headers, and retry policy. Credentials are resolved by the DSH credentials service, so normal operation does not require a second `LCX_API_KEY` configuration.
|
|
100
101
|
|
|
101
|
-
The endpoint and model fields in the plugin UI
|
|
102
|
+
The provider, endpoint, and model fields in the plugin UI define the search fallback route and preserve compatibility with the older direct-route configuration. Hosted and Alpha use this GPT route when the active primary model is not on a compatible GPT route. An existing DSH provider configuration takes precedence.
|
|
102
103
|
|
|
103
104
|
## Alpha Probe
|
|
104
105
|
|
|
@@ -124,12 +125,15 @@ The probe does not print the key or full response bodies. Restart DSH, or disabl
|
|
|
124
125
|
|
|
125
126
|
## Data and Limits
|
|
126
127
|
|
|
127
|
-
- The
|
|
128
|
-
- The credential name comes from the
|
|
128
|
+
- The search network target prefers the active compatible DSH `openai-responses` provider and otherwise uses the GPT provider selected in the plugin settings. It is not fixed to LCX or any other domain, and the plugin only calls `/responses` and `/alpha/search` under that address.
|
|
129
|
+
- The credential name comes from the selected GPT provider's `apiKeyEnv` and is resolved by the DSH credentials service. The plugin does not store API keys.
|
|
130
|
+
- Enabling Hosted temporarily changes `ctx.web.searchProviderId` to the plugin search provider; disabling Hosted or removing the plugin restores the provider selected before activation.
|
|
129
131
|
- Checkpoints: `$DSH_HOME/storages/lcx-codex/checkpoints-v3.json`
|
|
130
132
|
- Alpha capabilities: `$DSH_HOME/storages/lcx-codex/web-alpha-capabilities.json`
|
|
131
133
|
- Alpha references: `$DSH_HOME/storages/lcx-codex/web-alpha-refs.json`
|
|
132
134
|
- Only Native remote-compaction V2 is supported; `/responses/compact` is never called.
|
|
135
|
+
- Same-route replay reuses the DSH session `prompt_cache_key` and preserves the existing request prefix. A single cold request can still occur after the short-lived upstream cache expires, so the cumulative session hit rate is not evidence that the plugin changed the prefix.
|
|
136
|
+
- Sub2API strips the unsupported `prompt_cache_retention` field on its Codex OAuth path, so requesting `24h` does not extend cache lifetime on that route. Use the `cached_tokens` reported by consecutive real requests as the authoritative signal.
|
|
133
137
|
- Checkpoints never store raw image bytes or data URLs.
|
|
134
138
|
- Opaque checkpoints are not replayed across an incompatible provider, model, base URL, session, or lineage.
|
|
135
139
|
- Image generation is not included.
|
package/lib/client.js
CHANGED
|
@@ -49,9 +49,9 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
enabled: '启用 LCX/Codex 能力',
|
|
50
50
|
enabledHelp: '关闭时不改变当前模型、搜索提供方或压缩流程。',
|
|
51
51
|
webSearch: '启用 Responses Hosted 搜索(websearch_gpt)',
|
|
52
|
-
webSearchHelp: '
|
|
52
|
+
webSearchHelp: '开启后由本插件接管 DSH 搜索 provider,任意主模型都可通过已配置的 GPT Responses 路由调用 /responses + web_search;关闭或卸载后恢复原 provider。',
|
|
53
53
|
alphaSearch: '启用 Alpha command 搜索(websearch_alpha)',
|
|
54
|
-
alphaSearchHelp: '
|
|
54
|
+
alphaSearchHelp: '仅当配置的 GPT endpoint、provider、model 和 schema 的 capability probe 已通过时注册;任意主模型可调用,PAT 模拟或未知能力不会暴露该工具。',
|
|
55
55
|
compaction: '启用 GPT 专属原生远程压缩',
|
|
56
56
|
compactionHelp: '只适用于兼容 Responses 的 GPT 路由;使用与 Sub2API 压缩测试一致的 native V2(/responses + compaction_trigger),保存完整 output 并在后续请求中回放。Legacy compact transport 已不支持,相关配置会被拒绝。',
|
|
57
57
|
fallback: 'native 压缩失败时回退 DSH 普通压缩',
|
|
@@ -73,9 +73,9 @@ window.__ModuleLoader__.load({
|
|
|
73
73
|
enabled: 'Enable LCX/Codex capabilities',
|
|
74
74
|
enabledHelp: 'When off, the current model, search provider, and compaction flow are unchanged.',
|
|
75
75
|
webSearch: 'Enable Responses Hosted search (websearch_gpt)',
|
|
76
|
-
webSearchHelp: 'When on, the model can
|
|
76
|
+
webSearchHelp: 'When on, this plugin takes over the DSH search provider and any primary model can use the configured GPT Responses route. Disabling or removing it restores the previous provider.',
|
|
77
77
|
alphaSearch: 'Enable Alpha command search (websearch_alpha)',
|
|
78
|
-
alphaSearchHelp: 'Registered only after a capability probe matches the
|
|
78
|
+
alphaSearchHelp: 'Registered only after a capability probe matches the configured GPT endpoint, provider, model, and schema. Any primary model can call it; PAT emulation and unknown capabilities remain hidden.',
|
|
79
79
|
compaction: 'Enable GPT-only native remote compaction',
|
|
80
80
|
compactionHelp: 'Works only on a compatible Responses GPT route; uses the native V2 request used by Sub2API compact tests (/responses + compaction_trigger), stores the complete output, and replays it. Legacy compact transport is unsupported and rejected.',
|
|
81
81
|
fallback: 'Fall back to normal DSH compaction on native failure',
|
package/lib/compact.js
CHANGED
|
@@ -4,8 +4,107 @@ import { offloadDshRequestImages, resolveDshImage, serializeDshResponsesInput }
|
|
|
4
4
|
const UNSUPPORTED_CHECKPOINT_PATTERN = /\[dsh-lcx-codex-checkpoint:[0-9a-f-]{36}\]/iu
|
|
5
5
|
const PORTABLE_CHECKPOINT_PATTERN = /\[dsh-lcx-codex-v3-checkpoint:([0-9a-f-]{36})\]/giu
|
|
6
6
|
const PORTABLE_HISTORY_TOKEN_BUDGET = 20_000
|
|
7
|
+
export const NATIVE_RETAINED_MESSAGE_TOKEN_BUDGET = 64_000
|
|
7
8
|
export const PORTABLE_HISTORY_BYTE_BUDGET = 2 * 1024 * 1024
|
|
8
9
|
const UNSUPPORTED_IMAGE_PLACEHOLDER = '[image omitted because the target model does not support image input]'
|
|
10
|
+
const NATIVE_IMAGE_TOKEN_ESTIMATE = 765
|
|
11
|
+
const CONTEXTUAL_USER_PREFIXES = [
|
|
12
|
+
'<environment_context>',
|
|
13
|
+
'<user_instructions>',
|
|
14
|
+
'<additional_context>',
|
|
15
|
+
'<skills',
|
|
16
|
+
'<token_budget>',
|
|
17
|
+
'<model_switch>',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
function nativeContentPartTokens(part) {
|
|
21
|
+
if (part?.type === 'input_image' || part?.type === 'dsh_image_attachment') return NATIVE_IMAGE_TOKEN_ESTIMATE
|
|
22
|
+
if (typeof part?.text === 'string') return Math.ceil(part.text.length / 4)
|
|
23
|
+
return Math.ceil(JSON.stringify(part ?? null).length / 4)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nativeMessageTokens(item) {
|
|
27
|
+
return Math.max(1, (item?.content ?? []).reduce((total, part) => total + nativeContentPartTokens(part), 0))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function truncatedNativeText(text, maxTokens) {
|
|
31
|
+
const maxChars = maxTokens * 4
|
|
32
|
+
if (maxChars <= 0) return ''
|
|
33
|
+
if (text.length <= maxChars) return text
|
|
34
|
+
const omittedTokens = Math.max(1, Math.ceil((text.length - maxChars) / 4))
|
|
35
|
+
const marker = `[...${omittedTokens} tokens truncated...]`
|
|
36
|
+
if (maxChars <= marker.length + 2) return text.slice(-maxChars)
|
|
37
|
+
const sideChars = Math.max(1, Math.floor((maxChars - marker.length) / 2))
|
|
38
|
+
return `${text.slice(0, sideChars)}${marker}${text.slice(-sideChars)}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function truncateNativeUserMessage(item, maxTokens) {
|
|
42
|
+
let remaining = maxTokens
|
|
43
|
+
const content = []
|
|
44
|
+
for (const part of item?.content ?? []) {
|
|
45
|
+
if (remaining <= 0) break
|
|
46
|
+
const tokens = Math.max(1, nativeContentPartTokens(part))
|
|
47
|
+
if (tokens <= remaining) {
|
|
48
|
+
content.push(structuredClone(part))
|
|
49
|
+
remaining -= tokens
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
if (typeof part?.text === 'string') {
|
|
53
|
+
const text = truncatedNativeText(part.text, remaining)
|
|
54
|
+
if (text) content.push({ ...structuredClone(part), text })
|
|
55
|
+
}
|
|
56
|
+
remaining = 0
|
|
57
|
+
}
|
|
58
|
+
return content.length > 0 ? { ...structuredClone(item), content } : undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isNativeRetainedUserMessage(item) {
|
|
62
|
+
if (item?.type !== 'message' || item.role !== 'user' || !Array.isArray(item.content)) return false
|
|
63
|
+
return !item.content.some((part) => {
|
|
64
|
+
if (part?.type !== 'input_text' || typeof part.text !== 'string') return false
|
|
65
|
+
const text = part.text.trimStart().toLowerCase()
|
|
66
|
+
return CONTEXTUAL_USER_PREFIXES.some((prefix) => text.startsWith(prefix))
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildNativeReplacementHistory(input, compaction, options = {}) {
|
|
71
|
+
if (compaction?.type !== 'compaction' || typeof compaction.encrypted_content !== 'string' || compaction.encrypted_content.length === 0) {
|
|
72
|
+
const error = new Error('LCX Native replacement history requires one non-empty compaction item')
|
|
73
|
+
error.code = 'LCX_COMPACT_INVALID_RESPONSE'
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
let remaining = options.tokenBudget ?? NATIVE_RETAINED_MESSAGE_TOKEN_BUDGET
|
|
77
|
+
if (!Number.isSafeInteger(remaining) || remaining <= 0) {
|
|
78
|
+
const error = new Error('LCX Native retained message token budget must be a positive integer')
|
|
79
|
+
error.code = 'LCX_COMPACT_INVALID_INPUT'
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
82
|
+
const retained = []
|
|
83
|
+
const candidates = (input ?? []).filter(isNativeRetainedUserMessage)
|
|
84
|
+
for (let index = candidates.length - 1; index >= 0 && remaining > 0; index -= 1) {
|
|
85
|
+
const item = candidates[index]
|
|
86
|
+
const tokens = nativeMessageTokens(item)
|
|
87
|
+
if (tokens <= remaining) {
|
|
88
|
+
retained.push(structuredClone(item))
|
|
89
|
+
remaining -= tokens
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
const truncated = truncateNativeUserMessage(item, remaining)
|
|
93
|
+
if (truncated) retained.push(truncated)
|
|
94
|
+
remaining = 0
|
|
95
|
+
}
|
|
96
|
+
retained.reverse()
|
|
97
|
+
retained.push(structuredClone(compaction))
|
|
98
|
+
return retained
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function checkpointNativeReplacementHistory(record) {
|
|
102
|
+
const nativeOutput = Array.isArray(record?.nativeOutput) ? record.nativeOutput : []
|
|
103
|
+
if (nativeOutput.some(isNativeRetainedUserMessage)) return structuredClone(nativeOutput)
|
|
104
|
+
const compaction = nativeOutput.find((item) => item?.type === 'compaction') ?? record?.nativeCompaction
|
|
105
|
+
if (!compaction) return structuredClone(nativeOutput)
|
|
106
|
+
return buildNativeReplacementHistory(record?.portableHistory ?? [], compaction)
|
|
107
|
+
}
|
|
9
108
|
|
|
10
109
|
export function portableCheckpointIds(text) {
|
|
11
110
|
return [...String(text ?? '').matchAll(PORTABLE_CHECKPOINT_PATTERN)].map((match) => match[1].toLowerCase())
|
|
@@ -461,7 +560,7 @@ export function buildPortableResponsesInput(messages, store, route) {
|
|
|
461
560
|
throw error
|
|
462
561
|
}
|
|
463
562
|
const prefix = state === 'native-compatible'
|
|
464
|
-
? record
|
|
563
|
+
? checkpointNativeReplacementHistory(record)
|
|
465
564
|
: [...portableSummaryItem(record.portableSummary), ...buildPortableHistory(record.portableHistory)]
|
|
466
565
|
const tail = normalInput((messages ?? []).slice(marker.index + 1), { strict: true })
|
|
467
566
|
return [...structuredClone(prefix), ...tail]
|
|
@@ -513,7 +612,7 @@ export async function buildPortableResponsesInputWithImages(messages, store, rou
|
|
|
513
612
|
}
|
|
514
613
|
}
|
|
515
614
|
const prefix = state === 'native-compatible'
|
|
516
|
-
? await hydrateNativeImageReferences(record
|
|
615
|
+
? await hydrateNativeImageReferences(checkpointNativeReplacementHistory(record), options)
|
|
517
616
|
: [...portableSummaryItem(record.portableSummary), ...portableHistory]
|
|
518
617
|
const tail = state === 'native-compatible'
|
|
519
618
|
? await serializeDshResponsesInput(requestMessages.slice(marker.index + 1), { ...options, imageSupport, route })
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { join } from 'node:path'
|
|
|
8
8
|
import { CheckpointV3Store, CHECKPOINT_V3_VERSION } from './checkpoint-store-v3.js'
|
|
9
9
|
import {
|
|
10
10
|
baseURLFingerprint,
|
|
11
|
+
buildNativeReplacementHistory,
|
|
11
12
|
buildPortableHistory,
|
|
12
13
|
assertCheckpointRoute,
|
|
13
14
|
buildPortableResponsesInput,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
hasPortableCheckpoint,
|
|
16
17
|
hydrateNativeImageReferences,
|
|
17
18
|
inputImageCount,
|
|
19
|
+
checkpointNativeReplacementHistory,
|
|
18
20
|
latestPortableMarker,
|
|
19
21
|
normalizeCompactionResponse,
|
|
20
22
|
persistNativeImageReferences,
|
|
@@ -774,7 +776,7 @@ async function* nativeCheckpointReplayStream(options, config, portableStore, ctx
|
|
|
774
776
|
? await resolveModelImageSupport(llm, route, options.signal)
|
|
775
777
|
: 'supported'
|
|
776
778
|
const imageOptions = { resolveImage: attachmentImageResolver(ctx), imageSupport, signal: options.signal, maxRequestImageBytes: config.maxRequestImageBytes }
|
|
777
|
-
const nativeOutput = await hydrateNativeImageReferences(record
|
|
779
|
+
const nativeOutput = await hydrateNativeImageReferences(checkpointNativeReplacementHistory(record), imageOptions)
|
|
778
780
|
const tailInput = await buildPortableResponsesInputWithImages(
|
|
779
781
|
(options.messages ?? []).slice(marker.index + 1),
|
|
780
782
|
portableStore,
|
|
@@ -887,7 +889,8 @@ function portableMarkerTextWithSummary(id, model, usage, summary) {
|
|
|
887
889
|
async function commitRemoteCompaction(prepared, result, portableStore, config, summary, usage) {
|
|
888
890
|
const { history, route, lease, input, imageReferences } = prepared
|
|
889
891
|
lease?.assert('commit')
|
|
890
|
-
const
|
|
892
|
+
const replacementHistory = buildNativeReplacementHistory(input, result.compaction)
|
|
893
|
+
const persistedNativeOutput = persistNativeImageReferences(replacementHistory, imageReferences)
|
|
891
894
|
const persistedPortableInput = persistNativeImageReferences(input, imageReferences)
|
|
892
895
|
const id = randomUUID()
|
|
893
896
|
const parent = latestPortableMarker(history)
|
|
@@ -1410,17 +1413,21 @@ function activeAgentRoute(agent) {
|
|
|
1410
1413
|
}
|
|
1411
1414
|
}
|
|
1412
1415
|
|
|
1416
|
+
function searchRouteConfig(ctx, config, agent) {
|
|
1417
|
+
const { provider, model } = activeAgentRoute(agent)
|
|
1418
|
+
return resolveResponsesRouteConfig(ctx, {
|
|
1419
|
+
provider: provider ?? config.provider,
|
|
1420
|
+
model: model ?? config.model,
|
|
1421
|
+
}, config) ?? configuredResponsesRoute(ctx, config)
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1413
1424
|
async function executeAlphaSearch(ctx, config, capabilityStore, refStore, args, exec) {
|
|
1414
1425
|
abortIfNeeded(exec?.signal)
|
|
1415
1426
|
const normalized = normalizeAlphaSearchArgs(args)
|
|
1416
1427
|
const sessionId = typeof exec?.agent?.id === 'string' ? exec.agent.id : undefined
|
|
1417
1428
|
if (!sessionId) throw webError('Alpha Web Search requires a live DSH agent session', 'LCX_ALPHA_SESSION_REQUIRED')
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1420
|
-
provider: agentProvider ?? config.provider,
|
|
1421
|
-
model: agentModel ?? config.model,
|
|
1422
|
-
}, config)
|
|
1423
|
-
if (!routeConfig) throw alphaUnavailable('Alpha Web Search requires an active DSH GPT openai-responses model route')
|
|
1429
|
+
const routeConfig = searchRouteConfig(ctx, config, exec?.agent)
|
|
1430
|
+
if (!routeConfig) throw alphaUnavailable('Alpha Web Search requires a configured DSH GPT openai-responses route')
|
|
1424
1431
|
const fingerprint = currentAlphaCapabilityFingerprint(routeConfig)
|
|
1425
1432
|
const capability = capabilityStore.get(fingerprint)
|
|
1426
1433
|
if (!alphaCapabilityUsable(capability) || capability.schemaFingerprint !== ALPHA_SCHEMA_FINGERPRINT || capability.actions?.[normalized.action] !== 'supported') {
|
|
@@ -1488,7 +1495,7 @@ class LcxResponsesSearchProvider {
|
|
|
1488
1495
|
function createWebSearchGptTool(ctx, state, getConfig) {
|
|
1489
1496
|
return {
|
|
1490
1497
|
name: WEB_SEARCH_TOOL_NAME,
|
|
1491
|
-
description: 'GPT
|
|
1498
|
+
description: '通过已配置 GPT Responses 路由执行的一次性 Hosted Web Search,调用该工具的主模型不限。支持 query、域名过滤、用户位置、搜索上下文、外部 Web 访问、返回预算和图片搜索控制,并返回来源、引用和检索时间;不提供基于当前会话 ref_id 的 open、find、click 或 screenshot 页面操作。',
|
|
1492
1499
|
parameters: HOSTED_SEARCH_PARAMETERS,
|
|
1493
1500
|
output: {
|
|
1494
1501
|
schema: HOSTED_SEARCH_OUTPUT,
|
|
@@ -1518,14 +1525,10 @@ function createWebSearchGptTool(ctx, state, getConfig) {
|
|
|
1518
1525
|
rawInput: JSON.stringify(args ?? {}),
|
|
1519
1526
|
}),
|
|
1520
1527
|
async execute(args, exec) {
|
|
1521
|
-
if (!state.enabled || !state.webSearch) throw new Error(`${WEB_SEARCH_TOOL_NAME} 未启用,请先在设置中开启
|
|
1528
|
+
if (!state.enabled || !state.webSearch) throw new Error(`${WEB_SEARCH_TOOL_NAME} 未启用,请先在设置中开启 Hosted Web Search`)
|
|
1522
1529
|
const config = getConfig()
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
provider: provider ?? config.provider,
|
|
1526
|
-
model: model ?? config.model,
|
|
1527
|
-
}, config)
|
|
1528
|
-
if (!routeConfig) throw webError('Hosted Web Search requires an active DSH GPT openai-responses model route', 'LCX_WEB_ROUTE_UNAVAILABLE')
|
|
1530
|
+
const routeConfig = searchRouteConfig(ctx, config, exec?.agent)
|
|
1531
|
+
if (!routeConfig) throw webError('Hosted Web Search requires a configured DSH GPT openai-responses route', 'LCX_WEB_ROUTE_UNAVAILABLE')
|
|
1529
1532
|
return executeHostedSearch(ctx, routeConfig, args, exec?.signal)
|
|
1530
1533
|
},
|
|
1531
1534
|
}
|
|
@@ -1534,7 +1537,7 @@ function createWebSearchGptTool(ctx, state, getConfig) {
|
|
|
1534
1537
|
function createWebSearchAlphaTool(ctx, getConfig, capabilityStore, refStore) {
|
|
1535
1538
|
return {
|
|
1536
1539
|
name: ALPHA_SEARCH_TOOL_NAME,
|
|
1537
|
-
description: '
|
|
1540
|
+
description: '通过已验证 GPT Responses 路由执行的独立、有状态 Alpha Web Search command 工具,调用该工具的主模型不限。一次只执行一个 search_query、image_query、open、find、click、screenshot、finance、weather、sports 或 time action;open、find、click 和 screenshot 使用当前会话已返回的 ref_id。仅在匹配的 capability probe 已验证时注册。',
|
|
1538
1541
|
parameters: ALPHA_SEARCH_PARAMETERS,
|
|
1539
1542
|
output: {
|
|
1540
1543
|
schema: ALPHA_SEARCH_OUTPUT,
|