dsh-lcx-codex 0.4.2 → 0.4.3-pre.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +75 -224
  2. package/THIRD_PARTY_NOTICES.md +64 -0
  3. package/cordis.patch.yml +3 -20
  4. package/lib/auxiliary-usage.js +63 -0
  5. package/lib/client.js +1398 -167
  6. package/lib/compact-v2.js +218 -199
  7. package/lib/dsh-compat.js +294 -100
  8. package/lib/dsh-responses.js +512 -277
  9. package/lib/grok-native-search.js +391 -0
  10. package/lib/index.js +1066 -758
  11. package/lib/invocation-policy-scope.js +261 -0
  12. package/lib/json-store.js +57 -31
  13. package/lib/native-checkpoint.js +520 -194
  14. package/lib/pi-responses-runtime.js +1571 -0
  15. package/lib/responses-request.js +109 -121
  16. package/lib/responses-stream.js +1280 -447
  17. package/lib/route.js +425 -369
  18. package/lib/search-accounting.js +86 -0
  19. package/lib/search-usage.js +86 -0
  20. package/lib/service-mutex.js +73 -64
  21. package/lib/token-budget.js +176 -108
  22. package/lib/transport.js +308 -68
  23. package/lib/types/client/index.d.ts +18 -0
  24. package/lib/types/client/search-media.d.ts +16 -0
  25. package/lib/types/index.d.ts +83 -0
  26. package/lib/web-run-output.js +189 -18
  27. package/lib/web-search-alpha.js +1067 -163
  28. package/lib/web-search-capability.js +80 -65
  29. package/lib/web-search-hosted.js +321 -33
  30. package/lib/web-search-ref-store.js +145 -60
  31. package/package.json +112 -32
  32. package/ARCHITECTURE.md +0 -117
  33. package/CHANGELOG.md +0 -224
  34. package/README_EN.md +0 -277
  35. package/assets/dsh-lcx-codex-banner.jpg +0 -0
  36. package/lib/legacy-v3.js +0 -20
  37. package/lib/responses-replay.js +0 -68
  38. package/scripts/probe-alpha.mjs +0 -43
  39. package/scripts/validate-dsh-schema.mjs +0 -31
@@ -1,70 +1,155 @@
1
- import { JsonStore } from './json-store.js'
2
-
3
- const VERSION = 1
4
-
1
+ import { createHash } from "node:crypto";
2
+ import { JsonStore } from "./json-store.js";
3
+ const VERSION = 2;
5
4
  function isRecord(value) {
6
- return value !== null && typeof value === 'object' && !Array.isArray(value)
5
+ return value !== null && typeof value === "object" && !Array.isArray(value);
7
6
  }
8
-
9
7
  function validHttpUrl(value) {
10
- if (value === undefined) return true
11
- try {
12
- return ['http:', 'https:'].includes(new URL(value).protocol)
13
- } catch {
14
- return false
15
- }
16
- }
17
-
8
+ if (value === undefined)
9
+ return true;
10
+ if (typeof value !== "string")
11
+ return false;
12
+ try {
13
+ return ["http:", "https:"].includes(new URL(value).protocol);
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ function validFingerprint(value) {
20
+ return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
21
+ }
22
+ function validProvenance(value) {
23
+ return isRecord(value) &&
24
+ typeof value.action === "string" && value.action.length > 0 &&
25
+ (value.originKind === "response" || value.originKind === "request" || value.originKind === "legacy-unattributed") &&
26
+ validFingerprint(value.originFingerprint) &&
27
+ (value.artifactFingerprint === undefined || validFingerprint(value.artifactFingerprint));
28
+ }
29
+ function validRef(refId, value) {
30
+ return isRecord(value) && value.refId === refId && validHttpUrl(value.url) && validProvenance(value.provenance);
31
+ }
32
+ function validSession(value) {
33
+ return isRecord(value) &&
34
+ typeof value.routeFingerprint === "string" && value.routeFingerprint.length > 0 &&
35
+ typeof value.updatedAt === "string" && !Number.isNaN(Date.parse(value.updatedAt)) &&
36
+ isRecord(value.refs) && Object.entries(value.refs).every(([refId, ref]) => refId.length > 0 && validRef(refId, ref));
37
+ }
18
38
  function validData(data) {
19
- if (data?.version !== VERSION || !isRecord(data.sessions)) return false
20
- return Object.entries(data.sessions).every(([sessionId, session]) =>
21
- sessionId.length > 0 && isRecord(session) && typeof session.routeFingerprint === 'string' && session.routeFingerprint.length > 0 &&
22
- typeof session.updatedAt === 'string' && !Number.isNaN(Date.parse(session.updatedAt)) && isRecord(session.refs) &&
23
- Object.entries(session.refs).every(([refId, ref]) => refId.length > 0 && isRecord(ref) && ref.refId === refId && validHttpUrl(ref.url)))
39
+ return isRecord(data) && data.version === VERSION && isRecord(data.sessions) &&
40
+ Object.entries(data.sessions).every(([sessionId, session]) => sessionId.length > 0 && validSession(session));
41
+ }
42
+ function validLegacyRef(refId, value) {
43
+ return isRecord(value) && value.refId === refId && validHttpUrl(value.url);
44
+ }
45
+ function validLegacySession(value) {
46
+ return isRecord(value) &&
47
+ typeof value.routeFingerprint === "string" && value.routeFingerprint.length > 0 &&
48
+ typeof value.updatedAt === "string" && !Number.isNaN(Date.parse(value.updatedAt)) &&
49
+ isRecord(value.refs) && Object.entries(value.refs).every(([refId, ref]) => refId.length > 0 && validLegacyRef(refId, ref));
50
+ }
51
+ function validLegacyData(data) {
52
+ return isRecord(data) && data.version === 1 && isRecord(data.sessions) &&
53
+ Object.entries(data.sessions).every(([sessionId, session]) => sessionId.length > 0 && validLegacySession(session));
54
+ }
55
+ function legacyFingerprint(sessionId, routeFingerprint, ref) {
56
+ return createHash("sha256")
57
+ .update(JSON.stringify([sessionId, routeFingerprint, ref.refId, ref.url ?? null]))
58
+ .digest("hex");
59
+ }
60
+ function migrateLegacyData(data) {
61
+ if (!validLegacyData(data))
62
+ return undefined;
63
+ return {
64
+ version: VERSION,
65
+ sessions: Object.fromEntries(Object.entries(data.sessions).map(([sessionId, session]) => [sessionId, {
66
+ routeFingerprint: session.routeFingerprint,
67
+ updatedAt: session.updatedAt,
68
+ refs: Object.fromEntries(Object.entries(session.refs).map(([refId, ref]) => [refId, {
69
+ refId,
70
+ ...(ref.url ? { url: ref.url } : {}),
71
+ provenance: {
72
+ action: "legacy-unattributed",
73
+ originKind: "legacy-unattributed",
74
+ originFingerprint: legacyFingerprint(sessionId, session.routeFingerprint, ref),
75
+ },
76
+ }])),
77
+ }])),
78
+ };
24
79
  }
25
-
26
80
  function unavailable(refId) {
27
- const error = new Error(`Alpha reference is unavailable in this session and route: ${String(refId)}`)
28
- error.code = 'LCX_ALPHA_REF_UNAVAILABLE'
29
- return error
81
+ return Object.assign(new Error(`Alpha reference is unavailable in this session and route: ${refId}`), {
82
+ code: "LCX_ALPHA_REF_UNAVAILABLE",
83
+ });
84
+ }
85
+ function collision(refId) {
86
+ return Object.assign(new Error(`Alpha reference collision in this session and route: ${refId}`), {
87
+ code: "LCX_ALPHA_REF_COLLISION",
88
+ });
89
+ }
90
+ function sameObservation(left, right) {
91
+ return left.refId === right.refId && left.url === right.url &&
92
+ left.provenance.action === right.provenance.action &&
93
+ left.provenance.originKind === right.provenance.originKind &&
94
+ left.provenance.originFingerprint === right.provenance.originFingerprint &&
95
+ left.provenance.artifactFingerprint === right.provenance.artifactFingerprint;
30
96
  }
31
-
32
97
  export class AlphaRefStore {
33
- constructor(file) {
34
- this.store = new JsonStore(
35
- file,
36
- () => ({ version: VERSION, sessions: {} }),
37
- validData,
38
- 'LCX_ALPHA_REF_STORE_CORRUPT',
39
- )
40
- }
41
-
42
- record(sessionId, routeFingerprint, refs) {
43
- if (typeof sessionId !== 'string' || sessionId.length === 0 || typeof routeFingerprint !== 'string' || routeFingerprint.length === 0 || !Array.isArray(refs)) {
44
- throw unavailable('invalid-record')
98
+ store;
99
+ constructor(file) {
100
+ this.store = new JsonStore(file, () => ({ version: VERSION, sessions: {} }), validData, "LCX_ALPHA_REF_STORE_CORRUPT", migrateLegacyData);
101
+ }
102
+ record(sessionId, routeFingerprint, refs) {
103
+ if (typeof sessionId !== "string" || !sessionId || typeof routeFingerprint !== "string" || !routeFingerprint || !Array.isArray(refs)) {
104
+ throw unavailable("invalid-record");
105
+ }
106
+ this.store.update((current) => {
107
+ const previous = current.sessions[sessionId];
108
+ const sameRoute = previous?.routeFingerprint === routeFingerprint;
109
+ const nextRefs = { ...(sameRoute ? previous.refs : {}) };
110
+ let changed = !sameRoute;
111
+ for (const value of refs) {
112
+ if (!isRecord(value) || typeof value.refId !== "string" || !value.refId)
113
+ throw unavailable("invalid-record");
114
+ const accepted = nextRefs[value.refId];
115
+ if (!validHttpUrl(value.url) || !validProvenance(value.provenance)) {
116
+ if (accepted)
117
+ throw collision(value.refId);
118
+ throw unavailable("invalid-record");
119
+ }
120
+ const observation = {
121
+ refId: value.refId,
122
+ ...(value.url ? { url: value.url } : {}),
123
+ provenance: { ...value.provenance },
124
+ };
125
+ if (accepted) {
126
+ if (!sameObservation(accepted, observation))
127
+ throw collision(observation.refId);
128
+ continue;
129
+ }
130
+ nextRefs[observation.refId] = observation;
131
+ changed = true;
132
+ }
133
+ if (!changed && previous)
134
+ return current;
135
+ const sessions = {
136
+ ...current.sessions,
137
+ [sessionId]: { routeFingerprint, refs: nextRefs, updatedAt: new Date().toISOString() },
138
+ };
139
+ const ordered = Object.entries(sessions)
140
+ .sort((left, right) => Date.parse(right[1].updatedAt) - Date.parse(left[1].updatedAt))
141
+ .slice(0, 256);
142
+ return { version: VERSION, sessions: Object.fromEntries(ordered) };
143
+ });
144
+ }
145
+ assertUsable(sessionId, routeFingerprint, refId) {
146
+ this.store.refresh();
147
+ if (typeof sessionId !== "string" || typeof refId !== "string")
148
+ throw unavailable(String(refId));
149
+ const session = this.store.data.sessions[sessionId];
150
+ const ref = session?.routeFingerprint === routeFingerprint ? session.refs[refId] : undefined;
151
+ if (!ref)
152
+ throw unavailable(refId);
153
+ return structuredClone(ref);
45
154
  }
46
- this.store.update((current) => {
47
- const previous = current.sessions[sessionId]
48
- const previousRefs = previous?.routeFingerprint === routeFingerprint ? previous.refs : {}
49
- const nextRefs = { ...previousRefs }
50
- for (const value of refs) {
51
- if (!isRecord(value) || typeof value.refId !== 'string' || value.refId.length === 0 || !validHttpUrl(value.url)) continue
52
- nextRefs[value.refId] = { refId: value.refId, ...(value.url ? { url: value.url } : {}) }
53
- }
54
- const sessions = {
55
- ...current.sessions,
56
- [sessionId]: { routeFingerprint, refs: nextRefs, updatedAt: new Date().toISOString() },
57
- }
58
- const ordered = Object.entries(sessions).sort((left, right) => Date.parse(right[1].updatedAt) - Date.parse(left[1].updatedAt)).slice(0, 256)
59
- return { version: VERSION, sessions: Object.fromEntries(ordered) }
60
- })
61
- }
62
-
63
- assertUsable(sessionId, routeFingerprint, refId) {
64
- this.store.refresh()
65
- const session = this.store.data.sessions[sessionId]
66
- const ref = session?.routeFingerprint === routeFingerprint ? session.refs?.[refId] : undefined
67
- if (!ref) throw unavailable(refId)
68
- return structuredClone(ref)
69
- }
70
155
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-lcx-codex",
3
- "version": "0.4.2",
4
- "description": "DSH GPT Responses lifecycle owner with Native V2 compaction/replay and Hosted/Alpha Search capabilities",
3
+ "version": "0.4.3-pre.13",
4
+ "description": "Model-scoped DSH plugin: GPT Responses lifecycle, Native V2 compaction and Hosted/Alpha Search; Grok native Web/X Search",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
@@ -11,7 +11,10 @@
11
11
  "openai-responses",
12
12
  "sub2api",
13
13
  "newapi",
14
- "compaction"
14
+ "compaction",
15
+ "grok",
16
+ "xai",
17
+ "x-search"
15
18
  ],
16
19
  "repository": {
17
20
  "type": "git",
@@ -23,11 +26,14 @@
23
26
  },
24
27
  "type": "module",
25
28
  "main": "lib/index.js",
29
+ "types": "lib/types/index.d.ts",
26
30
  "exports": {
27
31
  ".": {
32
+ "types": "./lib/types/index.d.ts",
28
33
  "default": "./lib/index.js"
29
34
  },
30
35
  "./client": {
36
+ "types": "./lib/types/client/index.d.ts",
31
37
  "default": "./lib/client.js"
32
38
  },
33
39
  "./cordis.patch.yml": "./cordis.patch.yml",
@@ -35,50 +41,119 @@
35
41
  },
36
42
  "files": [
37
43
  "lib/*.js",
38
- "scripts/*.mjs",
39
- "assets/*.jpg",
44
+ "lib/types/index.d.ts",
45
+ "lib/types/client/index.d.ts",
46
+ "lib/types/client/search-media.d.ts",
40
47
  "cordis.patch.yml",
41
- "LICENSE",
42
- "README.md",
43
- "README_EN.md",
44
- "ARCHITECTURE.md",
45
- "CHANGELOG.md"
48
+ "THIRD_PARTY_NOTICES.md"
46
49
  ],
47
50
  "dsh": {
48
51
  "bundle": {
49
52
  "patch": "./cordis.patch.yml"
50
53
  },
51
54
  "client": {
52
- "platform": "web"
55
+ "platform": "web",
56
+ "inject": [
57
+ "@deepseek-ai/dsh-client-ui-chat",
58
+ "@deepseek-ai/dsh-client-ui-conversation",
59
+ "@deepseek-ai/dsh-client-ui-settings-plugins"
60
+ ]
53
61
  }
54
62
  },
55
- "dependencies": {
56
- "@earendil-works/pi-ai": "0.84.3"
57
- },
58
63
  "devDependencies": {
59
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
60
- "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
61
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
62
- "@deepseek-ai/schemastery": "^3.18.1",
64
+ "@deepseek-ai/dsh": "0.1.5-rc.1",
65
+ "@earendil-works/pi-ai": "0.85.1",
66
+ "@deepseek-ai/schemastery": "^3.18.2",
67
+ "@types/node": "^22.20.0",
68
+ "@types/react": "~18.3.1",
69
+ "esbuild": "^0.28.2",
70
+ "linkedom": "^0.18.13",
63
71
  "typescript": "7.0.2"
64
72
  },
65
73
  "peerDependencies": {
66
- "@deepseek-ai/cordis": "^4.0.1",
67
- "@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
68
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
69
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
70
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
71
- "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
72
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
73
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
74
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
75
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
76
- "@deepseek-ai/schemastery": "^3.18.1",
77
- "react": "^18.2.0"
74
+ "@deepseek-ai/cordis": "^4.0.2",
75
+ "@deepseek-ai/dsh-agent": ">=0.1.5-rc.1 <0.1.6",
76
+ "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 <0.1.6",
77
+ "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.1 <0.1.6",
78
+ "@deepseek-ai/dsh-client-modules": ">=0.1.5-rc.1 <0.1.6",
79
+ "@deepseek-ai/dsh-client-store": ">=0.1.5-rc.1 <0.1.6",
80
+ "@deepseek-ai/dsh-client-ui-chat": ">=0.1.5-rc.1 <0.1.6",
81
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1 <0.1.6",
82
+ "@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.5-rc.1 <0.1.6",
83
+ "@deepseek-ai/dsh-compaction": ">=0.1.5-rc.1 <0.1.6",
84
+ "@deepseek-ai/dsh-credentials": ">=0.1.5-rc.1 <0.1.6",
85
+ "@deepseek-ai/dsh-fs": ">=0.1.5-rc.1 <0.1.6",
86
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6",
87
+ "@deepseek-ai/dsh-session": ">=0.1.5-rc.1 <0.1.6",
88
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.1.6",
89
+ "@deepseek-ai/dsh-tool-web": ">=0.1.5-rc.1 <0.1.6",
90
+ "@deepseek-ai/dsh-tools": ">=0.1.5-rc.1 <0.1.6",
91
+ "@deepseek-ai/dsh-web": ">=0.1.5-rc.1 <0.1.6",
92
+ "@deepseek-ai/schemastery": "^3.18.2",
93
+ "react": "^18.2.0",
94
+ "@deepseek-ai/dsh-token-meter": ">=0.1.5-rc.1 <0.1.6",
95
+ "@deepseek-ai/dsh-session-projection": ">=0.1.5-rc.1 <0.1.6",
96
+ "zod": "^4.5.4"
97
+ },
98
+ "peerDependenciesMeta": {
99
+ "@deepseek-ai/dsh-client-locale": {
100
+ "optional": true
101
+ },
102
+ "@deepseek-ai/dsh-agent": {
103
+ "optional": true
104
+ },
105
+ "@deepseek-ai/dsh-attachment": {
106
+ "optional": true
107
+ },
108
+ "@deepseek-ai/dsh-client-modules": {
109
+ "optional": true
110
+ },
111
+ "@deepseek-ai/dsh-client-store": {
112
+ "optional": true
113
+ },
114
+ "@deepseek-ai/dsh-client-ui-chat": {
115
+ "optional": true
116
+ },
117
+ "@deepseek-ai/dsh-client-ui-conversation": {
118
+ "optional": true
119
+ },
120
+ "@deepseek-ai/dsh-client-ui-settings-plugins": {
121
+ "optional": true
122
+ },
123
+ "@deepseek-ai/dsh-compaction": {
124
+ "optional": true
125
+ },
126
+ "@deepseek-ai/dsh-credentials": {
127
+ "optional": true
128
+ },
129
+ "@deepseek-ai/dsh-fs": {
130
+ "optional": true
131
+ },
132
+ "@deepseek-ai/dsh-llm": {
133
+ "optional": true
134
+ },
135
+ "@deepseek-ai/dsh-session": {
136
+ "optional": true
137
+ },
138
+ "@deepseek-ai/dsh-settings": {
139
+ "optional": true
140
+ },
141
+ "@deepseek-ai/dsh-tool-web": {
142
+ "optional": true
143
+ },
144
+ "@deepseek-ai/dsh-tools": {
145
+ "optional": true
146
+ },
147
+ "@deepseek-ai/dsh-web": {
148
+ "optional": true
149
+ }
78
150
  },
79
151
  "scripts": {
152
+ "build": "npm run build:host && npm run build:client",
153
+ "build:host": "tsc -p tsconfig.json && node scripts/build-pi-runtime.mjs",
154
+ "build:client": "tsc -p tsconfig.client.json && node scripts/build-client.mjs",
80
155
  "test": "node --test tests/*.test.mjs",
81
- "typecheck": "tsc -p tsconfig.typed-core.json",
156
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
82
157
  "probe:alpha": "node scripts/probe-alpha.mjs",
83
158
  "pack:local": "npm pack --ignore-scripts",
84
159
  "test:schema": "node scripts/validate-dsh-schema.mjs"
@@ -89,5 +164,10 @@
89
164
  "publishConfig": {
90
165
  "access": "public"
91
166
  },
92
- "license": "MIT"
167
+ "license": "MIT",
168
+ "lcxCompatibility": {
169
+ "dshInstallRange": ">=0.1.5-rc.1 <0.1.6",
170
+ "verifiedDsh": "0.1.5-rc.1",
171
+ "verifiedHostPi": "0.85.1"
172
+ }
93
173
  }
package/ARCHITECTURE.md DELETED
@@ -1,117 +0,0 @@
1
- # GPT Responses full-lifecycle ownership — 0.4.2-pre.1
2
-
3
- ## Product contract
4
-
5
- `LCX OFF` leaves every LLM request on the native DSH adapter path. `LCX ON` is the ownership switch for the selected GPT Responses conversation: LCX owns the final Responses wire from the first ordinary turn through tool loops, Native V2 compaction, native replay, portable model migration, and restart/resume. Users turn LCX off before selecting a non-GPT model; DSH model switching itself remains hot and requires no restart.
6
-
7
- ## Responsibility boundary
8
-
9
- - **DSH** remains the Agent, Session, request assembly, model/settings/credential, tool-execution, attachment, pressure-policy and compaction-transaction owner.
10
- - **The rc.2 compatibility seam** projects DSH `GenerateOptions` and durable replay content into Pi's provider-neutral `Context`. It exists only because DSH 0.1.1-rc.2 does not publish `toPiContext()` / `toStreamChunks()` as stable package APIs.
11
- - **Plugin Pi 0.84.3** owns canonical Responses message/tool serialization and stream semantics: reasoning, IDs, custom tools, strict/grammar tools, `additional_tools`, `tool_search`, namespace, cache semantics and event parsing.
12
- - **LCX** owns the final body, HTTP/SSE wire, safe error normalization, ordinary/compact/replay orchestration, Remote V2 opaque state and checkpoint compatibility.
13
-
14
- ## Unified request path
15
-
16
- `serializeNativeAware()` is the shared history compiler. With no checkpoint it serializes ordinary history; with a compatible checkpoint it injects retained native history plus the opaque compaction item; with an incompatible GPT model/route it reconstructs portable DSH history. All three representations continue through one standard LCX request builder and one LCX transport/parser bridge. Compact is the standard request plus `compaction_trigger` and the Remote V2 feature header; native replay is the standard request plus opaque history. Portable migration never recursively bypasses LCX back to `PiAiAdapter`.
17
-
18
- Ordinary and replay requests perform one provider attempt and emit DSH terminal failure chunks using the host taxonomy, leaving visible retries to DSH's `agent/request-error` policy. Native compaction retains its bounded idempotent retry and first-checkpoint Basic fallback. Genuine upstream tool namespace is stored only in adapter-private replay metadata because DSH rc.2's visible ToolCall block has no namespace field.
19
-
20
- ## Gateway continuity
21
-
22
- The stable DSH session id drives Pi-compatible prompt-cache/session-affinity fields. Sub2API v0.1.183 accepts `session_id`, `session-id`, and `x-client-request-id` for Codex account stickiness and repairs recovered custom-tool/tool-search item ID prefixes. Runtime acceptance therefore verifies both LCX wire continuity and gateway account continuity; release prose is not treated as the wire contract.
23
-
24
- ---
25
-
26
- ## rc.6 pressure coordination
27
-
28
- DSH 0.1.1-rc.2 `compaction-basic` defaults to a `0.8` pressure threshold and, once pressure qualifies, runs `toolResultPruner` before summary compaction. Real long-session traces showed that a large prune can reduce the measured surface below 80%, preventing the Native summarizer from running while still rewriting an old request prefix and invalidating cache. rc.6 coordinates the existing engine instead of replacing it:
29
-
30
- - for a compatible GPT Responses route with Native auto-compaction enabled, `compactIfNeeded(..., "pressure")` returns `null` below the configured Native threshold (default 90%), so the stock 80% path does not mutate history;
31
- - from 90% up to the emergency threshold, the existing engine still owns range selection and the durable transaction, but `toolResultPruner.pruneSession()` is temporarily suppressed for that call; the engine's summary transport therefore reaches the existing `purpose=compaction` Native V2 override first;
32
- - at the emergency threshold (default 95%) or above, the pruner is no longer suppressed and DSH may shrink oversized tool results before attempting summary compaction;
33
- - provider-confirmed context overflow continues to use DSH's original `context-overflow` recovery unchanged;
34
- - manual `/compact` remains unchanged.
35
-
36
- Agent presets may isolate `compaction` and `toolResultPruner` inside entry-local Cordis realms. DSH 0.1.1-rc.2 explicitly documents that these preset services are invisible to both the host and ordinary `agent.ctx`; host-side code must address them through `agentPresets.serviceFor(agent, name)`. LCX therefore observes agent lifecycle events globally, resolves each Agent's real preset-local compaction/pruner through that public resolver, and patches the concrete compaction instance. A root `ctx.inject(['compaction'], ...)` hook remains only for non-preset/non-isolated deployments. Concrete Cordis service identity is used for de-duplication. DSH's own pre-step listener dynamically dispatches `this.compactIfNeeded()` at event time, and the wrapper is restored on plugin cleanup.
37
-
38
- ## rc.6 search timeout coordination
39
-
40
- `dsh-tool-web` stores the cooperative search deadline only in `ToolDefinition.timeoutMs`; timeout metadata is explicitly not sent to the model. rc.6 adjusts the visible `web_search` definition's timeout to 240 seconds by default and restores the original value on cleanup. This avoids the observed 60-second false timeout while leaving the model-visible tool schema byte-stable.
41
-
42
- # Architecture Notes — 0.4 Native Session Refactor / rc.6 Pressure Coordination
43
-
44
- ## rc.11 Native cache identity
45
-
46
- Native compaction and same-route replay reuse the active DSH/Pi conversation cache identity: the clamped session id is the `prompt_cache_key`, provider `cacheRetention` is respected, and `long` may emit `prompt_cache_retention: 24h` when supported. `cacheRetention: none` omits Native prompt-cache/session affinity. Ordinary Hosted Search remains intentionally isolated under `dsh-lcx-search:<route hash>` so search traffic cannot share the main conversation request/cache namespace.
47
-
48
- ## Design invariants
49
-
50
- 1. **DSH owns compaction policy.** LCX never independently decides threshold, compact range, pruning, transaction boundaries or overflow retries.
51
- 2. **Native success performs one compaction model request.** Basic summary is a failure fallback, not a parallel portable-copy generator.
52
- 3. **DSH session log is the new checkpoint source of truth.** Opaque Native V2 state lives in `compaction/summary.rawOutput`; v3 sidecar access is legacy read-only.
53
- 4. **Opaque state is same-session only.** Provider, model, base URL and exact `sourceSessionId === currentSessionId` gate Native opaque replay. Verified parent/child ancestry authorizes portable migration only; a fork never sends the parent's opaque checkpoint state.
54
- 5. **Route migration is transparent and transient.** Reconstruct shadowed DSH messages and hand them to the normal adapter; do not persist a second portable history copy.
55
- 6. **Ordinary search has one model tool.** `web_search` is ordinary search; `websearch_gpt_advanced` exists only for parameters absent from `WebSearchRequest`; Alpha remains its own stateful protocol.
56
- 7. **Provider-native wire code is isolated.** Direct `/responses` SSE code is limited to Native V2 compaction/replay and Hosted Search protocol calls.
57
-
58
- ## Why not subclass `BasicCompactionEngine`
59
-
60
- `BasicCompactionEngine.summarize()` is the intended subclass customization hook, but a subclass is a new `ctx.compaction` service provider. The shipped DSH profile already mounts `dsh-compaction-basic`; mounting a second engine would duplicate service ownership/listeners unless the profile explicitly replaces the existing row.
61
-
62
- The stock summarizer already routes through `ctx.llm.stream({ purpose: 'compaction' })`. For an out-of-tree optional plugin that must install without rewriting the base profile, narrowly intercepting that purpose is the less invasive integration.
63
-
64
- If DSH later adds a public **summarizer provider registry** (distinct from the compaction engine service), LCX should migrate to it.
65
-
66
- ## Why not inline opaque JSON in checkpoint text
67
-
68
- Inlining `encrypted_content` makes the session self-contained, but also exposes a large opaque string to DSH's visible surface/token accounting. Using a non-text block in `compaction/summary.rawOutput` keeps the session self-contained without turning provider state into prompt text.
69
-
70
- ## Remaining deliberate low-level seams
71
-
72
- ### Native Responses replay
73
-
74
- Generic DSH/Pi messages do not expose an input type for OpenAI `compaction` items. Same-route resume therefore builds the Responses request directly. This is a bounded compatibility adapter, not a second general LLM stack.
75
-
76
- ### Runtime Web SearchProvider selection
77
-
78
- DSH 0.1.1-rc.2 pins `deepseek-official` and has no public live setter. LCX uses an isolated compatibility write to the 0.1.1-rc.2 runtime field so the settings toggle works without restart. A future DSH public setter/configuration hook should replace this shim.
79
-
80
- ## Cache expectations
81
-
82
- - Stable ordinary tool schema improves prefix stability versus exposing two ordinary search tools.
83
- - Enabling/disabling Advanced or Alpha changes tools and may reset provider prefix cache.
84
- - Compaction necessarily changes visible history and therefore starts a new post-checkpoint prefix.
85
- - `prompt_cache_key` remains stable per exact route/session across Native compaction and native replay.
86
- - Remote-first avoids an otherwise redundant large-prefix local summary call.
87
-
88
- ## Native V2 retained-history invariant
89
-
90
- Current Codex V2 retains selected client messages and appends the opaque compaction item. Real DSH testing showed an additional product-level fidelity problem: a low-salience fact that existed only in an assistant answer can be omitted by the opaque state. LCX rc.5 therefore keeps the Native ordering but adds a bounded assistant-visible protection layer:
91
-
92
- ```text
93
- selected user/developer/system message items
94
- + selected assistant visible output_text items
95
- → opaque compaction item
96
- → later DSH-retained / post-compaction messages
97
- ```
98
-
99
- The fidelity prefix is capped at an estimated 64k tokens total. Up to 24k is reserved for assistant-visible answers; each retained assistant answer is capped at about 3k tokens. Assistant copies deliberately exclude reasoning, response IDs, tool calls, tool outputs, and provider-private state. The opaque item remains the only durable representation of those process details.
100
-
101
- The DSH surface still stores only the short checkpoint marker. The retained wire items and opaque compaction state remain log-only in `compaction/summary.rawOutput`, so they do not inflate DSH's visible token-meter surface. They do, intentionally, increase the post-compaction provider request relative to an opaque-only checkpoint; the total explicit retention ceiling prevents this protection from defeating compaction.
102
-
103
- Compatibility:
104
-
105
- - `0.4.0-rc.3`: v4 checkpoint could contain only the opaque item.
106
- - `0.4.0-rc.4`: v4 checkpoint retained client messages but not assistant-visible answers.
107
- - `0.4.0-rc.5`: writes `lcx-native-compaction-v5`; when replaying a v4 checkpoint it reconstructs the shadowed DSH transcript and derives the v5 fidelity prefix before reusing the original opaque state.
108
-
109
-
110
-
111
- ## rc.7 active-Agent Hosted Search routing
112
-
113
- DSH intentionally keeps `SearchProvider.search()` small: the provider receives the normalized search request and cancellation signal, not the calling Agent. Ordinary Hosted Search still needs the exact active GPT Responses route, especially when a user switches between Sol/Luna or multiple proxy routes.
114
-
115
- rc.7 therefore captures route identity at the model-facing `tools/execute` boundary for `web_search` and propagates it through Node `AsyncLocalStorage` only for the lifetime of that tool execution. `LcxResponsesSearchProvider.search()` resolves the route from that async context and falls back to the plugin-configured route only when no compatible active Agent route exists. No fields are added to the DSH `web_search` schema.
116
-
117
- Hosted Search uses a dedicated stable cache namespace (`dsh-lcx-search:<route fingerprint>`) rather than the Native replay namespace (`dsh-lcx:<route fingerprint>`). The two requests have different prefixes and should not be intentionally co-routed under one prompt-cache key.