dsh-lcx-codex 0.4.0 → 0.4.1-pre.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 +6 -0
- package/README.md +120 -178
- package/README_EN.md +126 -108
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/compact-v2.js +129 -16
- package/lib/dsh-compat.js +124 -0
- package/lib/index.js +126 -92
- package/lib/native-checkpoint.js +241 -22
- package/lib/route.js +174 -12
- package/lib/token-budget.js +165 -0
- package/lib/web-search-alpha.js +148 -7
- package/lib/web-search-capability.js +5 -3
- package/package.json +4 -2
package/README_EN.md
CHANGED
|
@@ -1,185 +1,197 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
4
|
-
|
|
5
|
-
# DSH-LCX-CODEX
|
|
6
|
-
|
|
7
|
-
**OpenAI Responses / Codex-native capabilities for DeepSeek Harness.**
|
|
3
|
+
<img src="assets/dsh-lcx-codex-banner-en.png" alt="dsh-lcx-codex — GPT Hosted Search, Codex-style Web Actions, and Native V2 Compaction for DSH" width="100%" />
|
|
8
4
|
|
|
9
5
|
[](https://www.npmjs.com/package/dsh-lcx-codex)
|
|
10
|
-
](https://github.com/kk3ya03-star/dsh-lcx-codex/actions/workflows/publish.yml)
|
|
11
7
|

|
|
12
|
-

|
|
9
|
+

|
|
13
10
|
|
|
14
|
-
[简体中文](README.md) · **English**
|
|
11
|
+
[简体中文](README.md) · **English** · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md)
|
|
15
12
|
|
|
16
13
|
</div>
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
> **DSH remains the host. LCX fills missing native capabilities on GPT Responses routes without modifying DSH core or replacing Agent / Session / Web / Compaction.**
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
## What it solves
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
If DSH already has a working GPT `openai-responses` route, `dsh-lcx-codex` lets that route use more of the native GPT Responses capability set while keeping DSH's existing entries and lifecycle ownership intact.
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
| Scenario | Existing DSH surface | With LCX |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Ordinary web search | DSH `web_search` | **Keep the same `web_search` entry** while LCX maps its SearchProvider to the active GPT Hosted Search route |
|
|
24
|
+
| Advanced Hosted Search controls | Keep ordinary search simple | Optionally add `websearch_gpt_advanced` for domain filters, approximate location, search context, image search, and related controls |
|
|
25
|
+
| Stateful page / PDF browsing | DSH keeps Web lifecycle ownership | Optionally add Codex / Alpha Web Actions: `search → open → find/click → screenshot` |
|
|
26
|
+
| Long-session compaction | DSH keeps pressure, transactions, and recovery | Prefer Responses Native V2 checkpoints at the existing compaction seam |
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
**The goal is not to build another Agent stack. It is to let a DSH GPT route use the native capabilities it should already have access to.**
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|---|---|---:|
|
|
30
|
-
| **DSH `web_search` → GPT Hosted Search** | Uses DSH's existing ordinary search tool; rc.7+ follows the active Agent GPT Responses model | opt-in |
|
|
31
|
-
| **Advanced Hosted Search** | Native domain/location/context/image search controls | off |
|
|
32
|
-
| **Alpha Search** | Stateful `search/open/find/click/screenshot`-style commands, capability-gated | off |
|
|
33
|
-
| **Native Remote Compaction V2** | Uses Responses `compaction_trigger` and provider-native opaque state | opt-in |
|
|
34
|
-
| **Conversation fidelity retention** | Bounded user + assistant-visible history protects low-salience facts | built-in |
|
|
35
|
-
| **Session-native checkpoints** | Durable state lives in the DSH append-only session log | built-in |
|
|
36
|
-
| **Native-first auto compaction** | 90% Native V2, 95% emergency DSH pruning; configurable | opt-in |
|
|
37
|
-
| **Long web timeout** | DSH `web_search` deadline defaults to 240s, configurable 30–600s | built-in |
|
|
30
|
+
## Three core capabilities
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
### 1. GPT Hosted Search: keep the ordinary search entry
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
When enabled, the model still sees the native DSH `web_search` tool for ordinary search. LCX changes the SearchProvider path instead of registering a second duplicate search tool.
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
Enable `websearch_gpt_advanced` only when you need finer Hosted Search controls.
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
Agent: lcx / gpt-5.6-luna
|
|
47
|
-
└─ DSH web_search
|
|
48
|
-
└─ Hosted Search: lcx / gpt-5.6-luna
|
|
49
|
-
```
|
|
38
|
+
### 2. Codex-style Web Actions: opt in when needed
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Hosted Search also receives its own stable cache namespace:
|
|
40
|
+
`websearch_alpha` is for stateful browsing and structured Web actions:
|
|
54
41
|
|
|
55
42
|
```text
|
|
56
|
-
|
|
57
|
-
hosted search: dsh-lcx-search:<route hash>
|
|
43
|
+
search → open → find / click → screenshot
|
|
58
44
|
```
|
|
59
45
|
|
|
60
|
-
|
|
46
|
+
It is off by default and is registered only after the exact endpoint / provider / model / schema passes a capability probe. Unknown deployments are **fail-closed** rather than mislabeled as Alpha-capable.
|
|
47
|
+
|
|
48
|
+
### 3. Native V2 Compaction: preserve DSH session semantics
|
|
61
49
|
|
|
62
|
-
|
|
50
|
+
LCX does not create a second compaction engine. DSH still owns pressure, compactable ranges, `/compact`, durable session transactions, tool-result pruning, and overflow recovery. LCX only requests Responses Native V2 at the existing compaction LLM seam.
|
|
63
51
|
|
|
64
|
-
|
|
52
|
+
Default automatic policy:
|
|
65
53
|
|
|
66
54
|
```text
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
0% ─────────────────── 90% ───── 95% ───── 100%
|
|
56
|
+
normal Native emergency hard cap
|
|
57
|
+
V2 DSH prune
|
|
70
58
|
```
|
|
71
59
|
|
|
72
|
-
|
|
60
|
+
- **90%**: prefer Native V2.
|
|
61
|
+
- **95%**: allow emergency DSH pruning.
|
|
62
|
+
- provider-confirmed context overflow: keep DSH's original recovery path.
|
|
63
|
+
- manual `/compact`: keep the native DSH session transaction.
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
The exact source session can reuse its own Native checkpoint. Forks or model migrations never send a parent's opaque state across sessions and use portable migration instead. Restart/resume rebuilds from the DSH session log.
|
|
75
66
|
|
|
76
|
-
|
|
67
|
+
## Get started in 30 seconds
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
69
|
+
### 1. Install
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
dsh plugin --profile web add dsh-lcx-codex
|
|
73
|
+
dsh web
|
|
82
74
|
```
|
|
83
75
|
|
|
84
|
-
|
|
76
|
+
### 2. Enable it in DSH Settings
|
|
85
77
|
|
|
86
|
-
|
|
78
|
+
For a first run, start with the smallest useful configuration:
|
|
87
79
|
|
|
88
|
-
|
|
80
|
+
| Setting | Suggested first-run value |
|
|
81
|
+
|---|---:|
|
|
82
|
+
| Enable plugin | **On** |
|
|
83
|
+
| Use GPT Hosted Search | **On** when you want GPT Hosted Search |
|
|
84
|
+
| Advanced Hosted Search | **Off** until you need advanced controls |
|
|
85
|
+
| Alpha Search | **Off** until the current route is confirmed compatible |
|
|
86
|
+
| Native V2 remote compaction | **On** only when the upstream actually supports Native V2 |
|
|
87
|
+
| Native-first auto compaction | **On** when using Native V2 |
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
0% -------------------- 90% ----- 95% ----- 100%
|
|
92
|
-
normal Native emergency hard cap
|
|
93
|
-
V2 DSH prune
|
|
94
|
-
```
|
|
89
|
+
**Requirements**
|
|
95
90
|
|
|
96
|
-
|
|
91
|
+
- Node.js `>=20`
|
|
92
|
+
- a working GPT `openai-responses` route in DSH
|
|
93
|
+
- actual upstream support for the Hosted Search / Native V2 / Alpha capabilities you plan to enable
|
|
94
|
+
|
|
95
|
+
### 3. Verify that it is actually active
|
|
96
|
+
|
|
97
|
+
Do not stop at checking toggles. Verify observable behavior for each capability:
|
|
98
|
+
|
|
99
|
+
| Capability | What you should observe |
|
|
100
|
+
|---|---|
|
|
101
|
+
| GPT Hosted Search | The ordinary tool entry remains DSH `web_search`, while the actual request follows the active GPT Responses route |
|
|
102
|
+
| Advanced Hosted | `websearch_gpt_advanced` appears after enablement and exposes the advanced controls separately |
|
|
103
|
+
| Alpha Web Actions | `websearch_alpha` appears only after the capability probe succeeds |
|
|
104
|
+
| Native V2 | Compaction follows the provider-native checkpoint path; ordinary Basic Compaction is never presented as a successful Native V2 run |
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
- 90–95%: Native V2 first;
|
|
100
|
-
- 95%+: DSH's replay-safe tool-result pruning may run as an emergency guard;
|
|
101
|
-
- provider-confirmed context overflow keeps DSH's normal recovery path;
|
|
102
|
-
- manual `/compact` is unaffected.
|
|
106
|
+
## Which search tool should I use?
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
The three entries serve different jobs. **You do not need to enable all of them.**
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
| What you want to do | Entry | Best for |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| Ordinary web lookup | DSH `web_search` | General search, research, and retrieval |
|
|
113
|
+
| Control Hosted Search parameters | `websearch_gpt_advanced` | Domain allow/block, approximate location, search context, image search, and related controls |
|
|
114
|
+
| Browse pages or PDFs statefully | `websearch_alpha` | `search/open/find/click/screenshot` and structured Web actions |
|
|
107
115
|
|
|
108
|
-
|
|
116
|
+
## How it fits into DSH
|
|
109
117
|
|
|
110
118
|
```text
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
DSH Agent / Session / Web
|
|
120
|
+
├─ web_search ──────────> LCX SearchProvider ──> GPT Hosted Search
|
|
121
|
+
├─ Advanced / Alpha ────> LCX Web tools ───────> GPT Web actions
|
|
122
|
+
└─ compact / replay ────> LCX Native bridge ───> Responses Native V2
|
|
113
123
|
```
|
|
114
124
|
|
|
115
|
-
|
|
125
|
+
**DSH is the host; LCX is a compatibility extension.** The plugin prefers the active DSH route's `baseURL`, credential reference, headers, model, and retry policy instead of maintaining a second account configuration.
|
|
116
126
|
|
|
117
|
-
|
|
127
|
+
For checkpoint internals, canonical replay, Pi serialization, cache identity, fork safety, and pressure coordination, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
## Compatibility
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
Current formally verified stack:
|
|
122
132
|
|
|
123
|
-
|
|
133
|
+
| Plugin | DSH | DSH host Pi | Plugin Pi | Status |
|
|
134
|
+
|---|---|---|---|---|
|
|
135
|
+
| `0.4.0` | `0.1.1-rc.2` | `0.82.1` | `0.82.1` | **VERIFIED** |
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
A new DSH release is **not** assumed compatible. The project first reviews the DSH / Pi seams that changed and then runs risk-appropriate tests. A newer Pi registry version alone does not automatically upgrade the plugin dependency.
|
|
126
138
|
|
|
127
|
-
|
|
139
|
+
<details>
|
|
140
|
+
<summary><strong>Full recommended settings</strong></summary>
|
|
128
141
|
|
|
129
|
-
|
|
142
|
+
| Setting | Recommended | Notes |
|
|
143
|
+
|---|---:|---|
|
|
144
|
+
| Enable plugin | On | Enables LCX |
|
|
145
|
+
| Use GPT Hosted Search | As needed | Routes DSH `web_search` through GPT Hosted Search |
|
|
146
|
+
| Advanced Hosted Search | Off | Enable only for advanced Hosted parameters |
|
|
147
|
+
| Alpha Search | Off | Enable after capability verification |
|
|
148
|
+
| Native V2 remote compaction | On* | *When the upstream route actually supports Native V2 |
|
|
149
|
+
| Native-first auto compaction | On | Enables automatic pressure coordination |
|
|
150
|
+
| Native threshold | 90% | Prefer Native V2 from 90% |
|
|
151
|
+
| Emergency DSH prune | 95% | Allow emergency prune from 95% |
|
|
152
|
+
| `web_search` timeout | 240 s | Avoid false timeout on longer searches |
|
|
130
153
|
|
|
131
|
-
|
|
154
|
+
Key fields: `remoteCompaction`, `autoCompaction`, `fallbackToBasicCompaction`, `autoCompactionThresholdPercent`, `emergencyPruneThresholdPercent`, `webSearchTimeoutSeconds`, `advancedHostedSearch`, `alphaSearch`.
|
|
132
155
|
|
|
133
|
-
|
|
156
|
+
</details>
|
|
134
157
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```
|
|
158
|
+
<details>
|
|
159
|
+
<summary><strong>Other installation options</strong></summary>
|
|
138
160
|
|
|
139
|
-
|
|
161
|
+
**Prerelease**
|
|
140
162
|
|
|
141
163
|
```powershell
|
|
142
164
|
dsh plugin --profile web add dsh-lcx-codex@next
|
|
143
165
|
```
|
|
144
166
|
|
|
145
|
-
Local
|
|
167
|
+
**Local tarball**
|
|
146
168
|
|
|
147
169
|
```powershell
|
|
148
170
|
dsh plugin --profile web remove dsh-lcx-codex
|
|
149
|
-
dsh plugin --profile web add .\dsh-lcx-codex-0.4.0
|
|
171
|
+
dsh plugin --profile web add .\dsh-lcx-codex-0.4.0.tgz
|
|
150
172
|
dsh web
|
|
151
173
|
```
|
|
152
174
|
|
|
153
|
-
Do not delete
|
|
175
|
+
Do not delete DSH sessions or `$DSH_HOME/storages/lcx-codex/` just to “clean up” during an upgrade. Older sessions may still reference compatibility data there.
|
|
154
176
|
|
|
155
|
-
|
|
177
|
+
</details>
|
|
156
178
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
GPT Hosted Search on
|
|
160
|
-
Advanced Hosted Search off
|
|
161
|
-
Alpha Search off
|
|
162
|
-
Native V2 remote compaction on
|
|
163
|
-
Native-first auto compaction on
|
|
164
|
-
Native threshold 90%
|
|
165
|
-
Emergency DSH prune 95%
|
|
166
|
-
web_search timeout 240s
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Requirements
|
|
179
|
+
<details>
|
|
180
|
+
<summary><strong>Common cases</strong></summary>
|
|
170
181
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- a working GPT `openai-responses` route in DSH
|
|
174
|
-
- an upstream that actually supports the enabled Hosted Search / Native V2 / Alpha capabilities
|
|
182
|
+
**`web_search` is not using GPT Hosted Search**
|
|
183
|
+
Make sure the plugin and Hosted Search are enabled and that the active Agent resolves to a compatible GPT `openai-responses` route.
|
|
175
184
|
|
|
176
|
-
|
|
185
|
+
**`websearch_alpha` is missing**
|
|
186
|
+
That is expected fail-closed behavior. Alpha is registered only after a trusted capability probe succeeds for the current route/schema.
|
|
177
187
|
|
|
178
|
-
|
|
188
|
+
**Native V2 is not taking effect**
|
|
189
|
+
Verify that the current GPT Responses endpoint actually supports `remote_compaction_v2`. The plugin does not present ordinary Basic Compaction as a successful Native V2 run.
|
|
179
190
|
|
|
180
|
-
|
|
191
|
+
</details>
|
|
181
192
|
|
|
182
|
-
|
|
193
|
+
<details>
|
|
194
|
+
<summary><strong>Development</strong></summary>
|
|
183
195
|
|
|
184
196
|
```bash
|
|
185
197
|
npm test
|
|
@@ -187,8 +199,14 @@ npm run test:schema
|
|
|
187
199
|
npm pack --ignore-scripts
|
|
188
200
|
```
|
|
189
201
|
|
|
190
|
-
|
|
202
|
+
- Design and protocol details: [ARCHITECTURE.md](ARCHITECTURE.md)
|
|
203
|
+
- Release history: [CHANGELOG.md](CHANGELOG.md)
|
|
204
|
+
- npm: [`dsh-lcx-codex`](https://www.npmjs.com/package/dsh-lcx-codex)
|
|
205
|
+
|
|
206
|
+
</details>
|
|
191
207
|
|
|
192
208
|
## License
|
|
193
209
|
|
|
194
210
|
MIT
|
|
211
|
+
|
|
212
|
+
> `LCX` is only the project name. This is a community project and is not affiliated with OpenAI, DeepSeek, Sub2API, or NewAPI.
|
|
Binary file
|
package/lib/compact-v2.js
CHANGED
|
@@ -1,15 +1,89 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import { consumeSse, fetchSseWithRetry } from './transport.js'
|
|
2
4
|
import { responsesTools } from './dsh-responses.js'
|
|
3
5
|
|
|
6
|
+
/** @typedef {Record<string, unknown>} UnknownRecord */
|
|
7
|
+
/** @typedef {Record<string, string>} HeaderMap */
|
|
8
|
+
/** @typedef {Error & { code?: string, status?: number, providerCode?: string, providerType?: string, providerParam?: string }} LcxError */
|
|
9
|
+
/** @typedef {{ type: 'compaction', encrypted_content: string }} CompactionItem */
|
|
10
|
+
/** @typedef {{ type: 'function_call', call_id: string }} FunctionCallItem */
|
|
11
|
+
/** @typedef {{ type: 'function_call_output', call_id: string }} FunctionCallOutputItem */
|
|
12
|
+
/** @typedef {CompactionItem | FunctionCallItem | FunctionCallOutputItem | { type: string }} ValidatedOutputItem */
|
|
13
|
+
/** @typedef {{ inputTokens: number, outputTokens: number, cacheReadTokens?: number, cacheWriteTokens?: number, reasoningTokens?: number }} CanonicalUsage */
|
|
14
|
+
/** @typedef {{ reasoningEffort?: unknown, temperature?: unknown, maxTokens?: unknown }} GenerationControls */
|
|
15
|
+
/** @typedef {{ reasoning?: { effort: string, summary: 'auto' }, include?: string[], temperature?: number, max_output_tokens?: number }} GenerationEnvelope */
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} NativeCompactionBodyOptions
|
|
18
|
+
* @property {string} model
|
|
19
|
+
* @property {unknown[]} input
|
|
20
|
+
* @property {string} [instructions]
|
|
21
|
+
* @property {unknown} [tools]
|
|
22
|
+
* @property {string} [promptCacheKey]
|
|
23
|
+
* @property {string} [promptCacheRetention]
|
|
24
|
+
* @property {unknown} [reasoningEffort]
|
|
25
|
+
* @property {unknown} [temperature]
|
|
26
|
+
* @property {unknown} [maxTokens]
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} NativeCompactionBody
|
|
30
|
+
* @property {string} model
|
|
31
|
+
* @property {unknown[]} input
|
|
32
|
+
* @property {true} stream
|
|
33
|
+
* @property {false} store
|
|
34
|
+
* @property {'auto'} tool_choice
|
|
35
|
+
* @property {true} parallel_tool_calls
|
|
36
|
+
* @property {string} [instructions]
|
|
37
|
+
* @property {unknown[]} [tools]
|
|
38
|
+
* @property {string} [prompt_cache_key]
|
|
39
|
+
* @property {string} [prompt_cache_retention]
|
|
40
|
+
* @property {{ effort: string, summary: 'auto' }} [reasoning]
|
|
41
|
+
* @property {string[]} [include]
|
|
42
|
+
* @property {number} [temperature]
|
|
43
|
+
* @property {number} [max_output_tokens]
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {NativeCompactionBodyOptions & {
|
|
47
|
+
* baseURL: string,
|
|
48
|
+
* idempotencyKey?: string,
|
|
49
|
+
* headers?: HeaderMap,
|
|
50
|
+
* signal?: AbortSignal,
|
|
51
|
+
* timeoutMs?: number,
|
|
52
|
+
* maxAttempts?: number,
|
|
53
|
+
* maxResponseBytes?: number
|
|
54
|
+
* }} NativeCompactionRequestOptions
|
|
55
|
+
*/
|
|
56
|
+
/** @typedef {{ signal?: AbortSignal, maxResponseBytes?: number }} SseParseOptions */
|
|
57
|
+
/** @typedef {{ object: unknown, id?: string, output: ValidatedOutputItem[], compaction: CompactionItem, usage?: CanonicalUsage }} NativeCompactionResult */
|
|
58
|
+
|
|
4
59
|
export const REMOTE_COMPACTION_V2_FEATURE = 'remote_compaction_v2'
|
|
5
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @param {unknown} value
|
|
63
|
+
* @returns {value is UnknownRecord}
|
|
64
|
+
*/
|
|
6
65
|
function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) }
|
|
7
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} message
|
|
68
|
+
* @param {string} [code]
|
|
69
|
+
* @returns {LcxError}
|
|
70
|
+
*/
|
|
71
|
+
function fail(message, code = 'LCX_COMPACT_INVALID_RESPONSE') {
|
|
72
|
+
/** @type {LcxError} */
|
|
73
|
+
const e = new Error(message)
|
|
74
|
+
e.code = code
|
|
75
|
+
return e
|
|
76
|
+
}
|
|
77
|
+
/** @param {unknown} value */
|
|
8
78
|
function safeMachineField(value) {
|
|
9
79
|
if (typeof value !== 'string' || value.length === 0 || value.length > 96) return undefined
|
|
10
80
|
return /^[A-Za-z0-9_.:\[\]-]+$/u.test(value) ? value : undefined
|
|
11
81
|
}
|
|
12
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @param {HeaderMap} [headers]
|
|
85
|
+
* @returns {HeaderMap}
|
|
86
|
+
*/
|
|
13
87
|
export function mergeFeatureHeader(headers = {}) {
|
|
14
88
|
const result = { ...headers }
|
|
15
89
|
const key = Object.keys(result).find((name) => name.toLowerCase() === 'x-codex-beta-features')
|
|
@@ -19,7 +93,12 @@ export function mergeFeatureHeader(headers = {}) {
|
|
|
19
93
|
return result
|
|
20
94
|
}
|
|
21
95
|
|
|
96
|
+
/**
|
|
97
|
+
* @param {GenerationControls} [controls]
|
|
98
|
+
* @returns {GenerationEnvelope}
|
|
99
|
+
*/
|
|
22
100
|
export function responsesGenerationEnvelope({ reasoningEffort, temperature, maxTokens } = {}) {
|
|
101
|
+
/** @type {GenerationEnvelope} */
|
|
23
102
|
const result = {}
|
|
24
103
|
if (reasoningEffort !== undefined && reasoningEffort !== 'off') {
|
|
25
104
|
result.reasoning = { effort: String(reasoningEffort), summary: 'auto' }
|
|
@@ -30,15 +109,20 @@ export function responsesGenerationEnvelope({ reasoningEffort, temperature, maxT
|
|
|
30
109
|
result.temperature = Number(temperature)
|
|
31
110
|
}
|
|
32
111
|
if (maxTokens !== undefined) {
|
|
33
|
-
if (!Number.isSafeInteger(maxTokens) || maxTokens <= 0) throw fail('Responses maxTokens must be a positive safe integer', 'LCX_COMPACT_INVALID_INPUT')
|
|
34
|
-
result.max_output_tokens = Math.max(16, maxTokens)
|
|
112
|
+
if (!Number.isSafeInteger(maxTokens) || /** @type {number} */ (maxTokens) <= 0) throw fail('Responses maxTokens must be a positive safe integer', 'LCX_COMPACT_INVALID_INPUT')
|
|
113
|
+
result.max_output_tokens = Math.max(16, /** @type {number} */ (maxTokens))
|
|
35
114
|
}
|
|
36
115
|
return result
|
|
37
116
|
}
|
|
38
117
|
|
|
118
|
+
/**
|
|
119
|
+
* @param {NativeCompactionBodyOptions} options
|
|
120
|
+
* @returns {NativeCompactionBody}
|
|
121
|
+
*/
|
|
39
122
|
export function buildNativeCompactionBody({ model, input, instructions, tools, promptCacheKey, promptCacheRetention, reasoningEffort, temperature, maxTokens }) {
|
|
40
123
|
if (!Array.isArray(input)) throw fail('native compaction input must be an array', 'LCX_COMPACT_INVALID_INPUT')
|
|
41
|
-
if (input.some((item) => item?.type === 'compaction_trigger')) throw fail('native compaction input already contains compaction_trigger', 'LCX_COMPACT_DUPLICATE_TRIGGER')
|
|
124
|
+
if (input.some((item) => /** @type {UnknownRecord | undefined} */ (item)?.type === 'compaction_trigger')) throw fail('native compaction input already contains compaction_trigger', 'LCX_COMPACT_DUPLICATE_TRIGGER')
|
|
125
|
+
/** @type {unknown[] | undefined} */
|
|
42
126
|
const nativeTools = responsesTools(tools)
|
|
43
127
|
return {
|
|
44
128
|
model,
|
|
@@ -55,31 +139,43 @@ export function buildNativeCompactionBody({ model, input, instructions, tools, p
|
|
|
55
139
|
}
|
|
56
140
|
}
|
|
57
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Provider usage stays unknown until the object guard and numeric normalization.
|
|
144
|
+
* @param {unknown} raw
|
|
145
|
+
* @returns {CanonicalUsage | undefined}
|
|
146
|
+
*/
|
|
58
147
|
function usageFrom(raw) {
|
|
59
148
|
if (!isObject(raw)) return undefined
|
|
60
149
|
const inputTotal = Number(raw.input_tokens ?? 0)
|
|
61
150
|
const output = Number(raw.output_tokens ?? 0)
|
|
62
|
-
const details = raw.input_tokens_details ?? raw.input_token_details ?? raw.prompt_tokens_details ?? {}
|
|
151
|
+
const details = /** @type {UnknownRecord} */ (raw.input_tokens_details ?? raw.input_token_details ?? raw.prompt_tokens_details ?? {})
|
|
63
152
|
const cacheRead = Number(details.cached_tokens ?? 0)
|
|
64
153
|
const cacheWrite = Number(details.cache_write_tokens ?? 0)
|
|
154
|
+
/** @type {CanonicalUsage} */
|
|
65
155
|
const result = {
|
|
66
156
|
inputTokens: Math.max(0, Number.isFinite(inputTotal) ? inputTotal - (Number.isFinite(cacheRead) ? cacheRead : 0) - (Number.isFinite(cacheWrite) ? cacheWrite : 0) : 0),
|
|
67
157
|
outputTokens: Number.isFinite(output) && output > 0 ? output : 0,
|
|
68
158
|
}
|
|
69
159
|
if (Number.isFinite(cacheRead) && cacheRead > 0) result.cacheReadTokens = cacheRead
|
|
70
160
|
if (Number.isFinite(cacheWrite) && cacheWrite > 0) result.cacheWriteTokens = cacheWrite
|
|
71
|
-
const reasoning = Number(raw.output_tokens_details?.reasoning_tokens ?? raw.output_token_details?.reasoning_tokens ?? 0)
|
|
161
|
+
const reasoning = Number(/** @type {UnknownRecord | undefined} */ (raw.output_tokens_details)?.reasoning_tokens ?? /** @type {UnknownRecord | undefined} */ (raw.output_token_details)?.reasoning_tokens ?? 0)
|
|
72
162
|
if (Number.isFinite(reasoning) && reasoning > 0) result.reasoningTokens = reasoning
|
|
73
163
|
return Object.values(result).some((v) => Number(v) > 0) ? result : undefined
|
|
74
164
|
}
|
|
75
165
|
|
|
166
|
+
/**
|
|
167
|
+
* @param {unknown} output
|
|
168
|
+
* @returns {{ output: ValidatedOutputItem[], compaction: CompactionItem }}
|
|
169
|
+
*/
|
|
76
170
|
function validateOutput(output) {
|
|
77
171
|
if (!Array.isArray(output) || output.length === 0) throw fail('native compaction output is empty')
|
|
78
172
|
const compactions = output.filter((item) => isObject(item) && item.type === 'compaction')
|
|
79
173
|
if (compactions.length !== 1) throw fail(`native compaction must return exactly one compaction item, got ${compactions.length}`)
|
|
80
174
|
const compact = compactions[0]
|
|
81
175
|
if (typeof compact.encrypted_content !== 'string' || compact.encrypted_content.length === 0) throw fail('native compaction item has no encrypted_content')
|
|
176
|
+
/** @type {Set<string>} */
|
|
82
177
|
const calls = new Set()
|
|
178
|
+
/** @type {Set<string>} */
|
|
83
179
|
const results = new Set()
|
|
84
180
|
for (const item of output) {
|
|
85
181
|
if (!isObject(item) || typeof item.type !== 'string') throw fail('native compaction output contains a malformed item')
|
|
@@ -93,16 +189,29 @@ function validateOutput(output) {
|
|
|
93
189
|
}
|
|
94
190
|
for (const id of calls) if (!results.has(id)) throw fail(`native compaction output has orphan function_call ${id}`)
|
|
95
191
|
for (const id of results) if (!calls.has(id)) throw fail(`native compaction output has orphan function_call_output ${id}`)
|
|
96
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
output: /** @type {ValidatedOutputItem[]} */ (structuredClone(output)),
|
|
194
|
+
compaction: /** @type {CompactionItem} */ (structuredClone(compact)),
|
|
195
|
+
}
|
|
97
196
|
}
|
|
98
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Provider SSE events enter as unknown; response.completed remains authoritative.
|
|
200
|
+
* @param {Response} response
|
|
201
|
+
* @param {SseParseOptions} [options]
|
|
202
|
+
* @returns {Promise<NativeCompactionResult>}
|
|
203
|
+
*/
|
|
99
204
|
export async function parseNativeCompactionSse(response, options = {}) {
|
|
205
|
+
/** @type {Map<number, UnknownRecord>} */
|
|
100
206
|
const byIndex = new Map()
|
|
207
|
+
/** @type {Map<string, UnknownRecord>} */
|
|
101
208
|
const byId = new Map()
|
|
209
|
+
/** @type {UnknownRecord | undefined} */
|
|
102
210
|
let terminal
|
|
211
|
+
/** @param {UnknownRecord} event */
|
|
103
212
|
const mergeItem = (event) => {
|
|
104
213
|
if (!isObject(event?.item)) return
|
|
105
|
-
const index = Number.isInteger(event.output_index) ? event.output_index : undefined
|
|
214
|
+
const index = Number.isInteger(event.output_index) ? /** @type {number} */ (event.output_index) : undefined
|
|
106
215
|
const id = typeof event.item.id === 'string' ? event.item.id : typeof event.item_id === 'string' ? event.item_id : undefined
|
|
107
216
|
let record = (index !== undefined ? byIndex.get(index) : undefined) ?? (id ? byId.get(id) : undefined)
|
|
108
217
|
if (!record) record = {}
|
|
@@ -112,27 +221,28 @@ export async function parseNativeCompactionSse(response, options = {}) {
|
|
|
112
221
|
if (index !== undefined) byIndex.set(index, record)
|
|
113
222
|
if (id) byId.set(id, record)
|
|
114
223
|
}
|
|
115
|
-
await consumeSse(response, (event) => {
|
|
224
|
+
await consumeSse(response, (/** @type {unknown} */ event) => {
|
|
116
225
|
if (!isObject(event)) return
|
|
117
226
|
if (event.type === 'error' || event.type === 'response.failed' || event.type === 'response.incomplete') {
|
|
118
|
-
const upstream = event.type === 'response.failed' ? event.response?.error : event.error
|
|
119
|
-
const providerCode = safeMachineField(upstream?.code)
|
|
120
|
-
const providerType = safeMachineField(upstream?.type)
|
|
121
|
-
const providerParam = safeMachineField(upstream?.param)
|
|
227
|
+
const upstream = event.type === 'response.failed' ? /** @type {{ error?: unknown } | undefined} */ (event.response)?.error : event.error
|
|
228
|
+
const providerCode = safeMachineField(/** @type {UnknownRecord | undefined} */ (upstream)?.code)
|
|
229
|
+
const providerType = safeMachineField(/** @type {UnknownRecord | undefined} */ (upstream)?.type)
|
|
230
|
+
const providerParam = safeMachineField(/** @type {UnknownRecord | undefined} */ (upstream)?.param)
|
|
122
231
|
const diagnostics = [
|
|
123
232
|
providerCode ? `providerCode=${providerCode}` : '',
|
|
124
233
|
providerType ? `providerType=${providerType}` : '',
|
|
125
234
|
providerParam ? `providerParam=${providerParam}` : '',
|
|
126
235
|
].filter(Boolean).join(' ')
|
|
236
|
+
/** @type {LcxError} */
|
|
127
237
|
const e = fail(`native compaction ended with ${event.type}${diagnostics ? ` ${diagnostics}` : ''}`, 'LCX_COMPACT_UPSTREAM_ERROR')
|
|
128
|
-
if (Number.isInteger(upstream?.status ?? event.error?.status)) e.status = upstream?.status ?? event.error?.status
|
|
238
|
+
if (Number.isInteger(/** @type {UnknownRecord | undefined} */ (upstream)?.status ?? /** @type {UnknownRecord | undefined} */ (event.error)?.status)) e.status = /** @type {number} */ (/** @type {UnknownRecord | undefined} */ (upstream)?.status ?? /** @type {UnknownRecord | undefined} */ (event.error)?.status)
|
|
129
239
|
if (providerCode) e.providerCode = providerCode
|
|
130
240
|
if (providerType) e.providerType = providerType
|
|
131
241
|
if (providerParam) e.providerParam = providerParam
|
|
132
242
|
throw e
|
|
133
243
|
}
|
|
134
244
|
if (event.type === 'response.output_item.added' || event.type === 'response.output_item.done') mergeItem(event)
|
|
135
|
-
if (event.type === 'response.completed') terminal = event.response
|
|
245
|
+
if (event.type === 'response.completed') terminal = /** @type {UnknownRecord} */ (event.response)
|
|
136
246
|
}, options)
|
|
137
247
|
if (!isObject(terminal)) throw fail('native compaction stream ended without response.completed', 'LCX_COMPACT_INCOMPLETE_SSE')
|
|
138
248
|
if (terminal.status !== 'completed') throw fail('native compaction response.completed did not carry completed status', 'LCX_COMPACT_INCOMPLETE_SSE')
|
|
@@ -147,12 +257,15 @@ export async function parseNativeCompactionSse(response, options = {}) {
|
|
|
147
257
|
}
|
|
148
258
|
}
|
|
149
259
|
|
|
260
|
+
/**
|
|
261
|
+
* @param {NativeCompactionRequestOptions} options
|
|
262
|
+
*/
|
|
150
263
|
export async function requestNativeCompaction({ baseURL, model, input, instructions, tools, promptCacheKey, promptCacheRetention, reasoningEffort, temperature, maxTokens, idempotencyKey, headers, signal, timeoutMs, maxAttempts, maxResponseBytes }) {
|
|
151
264
|
const body = buildNativeCompactionBody({ model, input, instructions, tools, promptCacheKey, promptCacheRetention, reasoningEffort, temperature, maxTokens })
|
|
152
265
|
const requestHeaders = mergeFeatureHeader({ ...headers, ...(idempotencyKey ? { 'idempotency-key': String(idempotencyKey) } : {}) })
|
|
153
266
|
return fetchSseWithRetry(`${String(baseURL).replace(/\/+$/u, '')}/responses`, body, requestHeaders, signal, timeoutMs, {
|
|
154
267
|
maxAttempts,
|
|
155
268
|
maxResponseBytes,
|
|
156
|
-
consume: (response, consumeOptions) => parseNativeCompactionSse(response, { signal: consumeOptions.requestSignal ?? signal, maxResponseBytes }),
|
|
269
|
+
consume: (/** @type {Response} */ response, /** @type {{ requestSignal?: AbortSignal }} */ consumeOptions) => parseNativeCompactionSse(response, { signal: consumeOptions.requestSignal ?? signal, maxResponseBytes }),
|
|
157
270
|
})
|
|
158
271
|
}
|