dsh-lcx-codex 0.4.3 → 0.5.0-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.
@@ -1,5 +1,5 @@
1
1
  import { outputDomains, outputLineRange, parseWebRunOutput } from "./web-run-output.js";
2
- export const HOSTED_SEARCH_PARAMETERS = { type: "object", properties: { query: { type: "string", description: "Advanced Responses Hosted Web Search query. Use DSH web_search for ordinary searches." }, searchContextSize: { type: "string", enum: ["low", "medium", "high"] }, allowedDomains: { type: "array", items: { type: "string" } }, blockedDomains: { type: "array", items: { type: "string" } }, userLocation: { type: "object", properties: { country: { type: "string" }, city: { type: "string" }, region: { type: "string" }, timezone: { type: "string" } }, additionalProperties: false }, externalWebAccess: { type: "boolean" }, returnTokenBudget: { type: "string", enum: ["default", "unlimited"] }, searchContentTypes: { type: "array", items: { type: "string", enum: ["text", "image"] } }, imageSettings: { type: "object", properties: { maxResults: { type: "integer" }, caption: { type: "boolean" } }, additionalProperties: false } }, required: ["query"], additionalProperties: false };
2
+ export const HOSTED_SEARCH_PARAMETERS = { type: "object", properties: { query: { type: "string", description: "Advanced Responses Hosted Web Search query. Use DSH web_search for ordinary searches." }, searchContextSize: { type: "string", enum: ["low", "medium", "high"] }, allowedDomains: { type: "array", items: { type: "string" }, description: "Optional allow filter. Omit or use [] for no filter; only nonempty lists are sent." }, blockedDomains: { type: "array", items: { type: "string" }, description: "Optional block filter. Omit or use [] for no filter; only nonempty lists are sent." }, userLocation: { type: "object", properties: { country: { type: "string" }, city: { type: "string" }, region: { type: "string" }, timezone: { type: "string" } }, additionalProperties: false }, externalWebAccess: { type: "boolean" }, returnTokenBudget: { type: "string", enum: ["default", "unlimited"] }, searchContentTypes: { type: "array", items: { type: "string", enum: ["text", "image"] }, description: "Optional content types. Omit unless image results are needed; [] means no explicit content-type override." }, imageSettings: { type: "object", description: "Optional image settings. Omit unless searchContentTypes includes image; ignored (after validation) without image search.", properties: { maxResults: { type: "integer" }, caption: { type: "boolean" } }, additionalProperties: false } }, required: ["query"], additionalProperties: false };
3
3
  export const HOSTED_SEARCH_OUTPUT = { type: "object", properties: { mode: { type: "string", enum: ["hosted"] }, action: { type: "string" }, emulation: { type: "string", enum: ["native"] }, content: { type: "string" }, sources: { type: "array", items: { type: "object" } }, citations: { type: "array", items: { type: "object" } }, images: { type: "array", items: { type: "object" } }, warnings: { type: "array", items: { type: "string" } }, outputBlocks: { type: "array", items: { type: "object" } }, domains: { type: "array", items: { type: "string" } }, lineRange: { type: "object" }, requestId: { type: "string" }, responseId: { type: "string" }, retrievedAt: { type: "string" }, truncated: { type: "boolean" }, usage: { type: "object", properties: { inputTokens: { type: "number" }, outputTokens: { type: "number" }, totalTokens: { type: "number" }, cachedInputTokens: { type: "number" }, actionCount: { type: "number" }, serverWebSearchCalls: { type: "number" } }, additionalProperties: false } }, required: ["mode", "action", "emulation", "content", "sources", "citations", "images", "warnings", "requestId", "retrievedAt", "truncated"], additionalProperties: false };
4
4
  function failure(message, code = "WEB_INVALID_REQUEST") { return Object.assign(new Error(message), { code }); }
5
5
  function isRecord(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
@@ -16,8 +16,10 @@ catch {
16
16
  function normalizeDomains(value, field) {
17
17
  if (value === undefined)
18
18
  return undefined;
19
- if (!isStringArray(value) || value.length < 1 || value.length > 100)
20
- throw failure(`websearch_gpt_advanced.${field} must contain 1 to 100 domains`);
19
+ if (!isStringArray(value) || value.length > 100)
20
+ throw failure(`websearch_gpt_advanced.${field} must contain 0 to 100 domains`);
21
+ if (value.length === 0)
22
+ return undefined;
21
23
  const result = value.map((item) => {
22
24
  const domain = item.trim().toLowerCase();
23
25
  if (!domain || domain.length > 253 || domain.includes("/") || domain.includes(":") || domain.endsWith(".") || domain.split(".").length < 2)
@@ -32,7 +34,7 @@ function normalizeLocation(value) {
32
34
  if (!isRecord(value))
33
35
  throw failure("websearch_gpt_advanced.userLocation must be an object");
34
36
  const result = {};
35
- if (value.country !== undefined) {
37
+ if (value.country !== undefined && !(typeof value.country === "string" && !value.country.trim())) {
36
38
  if (typeof value.country !== "string" || !/^[a-z]{2}$/iu.test(value.country.trim()))
37
39
  throw failure("userLocation.country must be ISO alpha-2");
38
40
  result.country = value.country.trim().toUpperCase();
@@ -40,11 +42,13 @@ function normalizeLocation(value) {
40
42
  for (const field of ["city", "region"])
41
43
  if (value[field] !== undefined) {
42
44
  const text = value[field];
43
- if (typeof text !== "string" || !text.trim() || text.length > 200)
45
+ if (typeof text === "string" && !text.trim())
46
+ continue;
47
+ if (typeof text !== "string" || text.length > 200)
44
48
  throw failure(`userLocation.${field} is invalid`);
45
49
  result[field] = text.trim();
46
50
  }
47
- if (value.timezone !== undefined) {
51
+ if (value.timezone !== undefined && !(typeof value.timezone === "string" && !value.timezone.trim())) {
48
52
  if (typeof value.timezone !== "string")
49
53
  throw failure("userLocation.timezone must be an IANA timezone");
50
54
  try {
@@ -55,9 +59,7 @@ function normalizeLocation(value) {
55
59
  }
56
60
  result.timezone = value.timezone;
57
61
  }
58
- if (!Object.keys(result).length)
59
- throw failure("userLocation is empty");
60
- return result;
62
+ return Object.keys(result).length ? result : undefined;
61
63
  }
62
64
  export function normalizeHostedSearchArgs(args) {
63
65
  if (!isRecord(args) || typeof args.query !== "string" || !args.query.trim())
@@ -79,8 +81,11 @@ export function normalizeHostedSearchArgs(args) {
79
81
  result.blockedDomains = blockedDomains;
80
82
  if (allowedDomains && blockedDomains && allowedDomains.some((domain) => blockedDomains.includes(domain)))
81
83
  throw failure("domain filters conflict");
82
- if (args.userLocation !== undefined)
83
- result.userLocation = normalizeLocation(args.userLocation);
84
+ if (args.userLocation !== undefined) {
85
+ const userLocation = normalizeLocation(args.userLocation);
86
+ if (userLocation)
87
+ result.userLocation = userLocation;
88
+ }
84
89
  if (args.externalWebAccess !== undefined) {
85
90
  if (typeof args.externalWebAccess !== "boolean")
86
91
  throw failure("externalWebAccess must be boolean");
@@ -92,17 +97,23 @@ export function normalizeHostedSearchArgs(args) {
92
97
  result.returnTokenBudget = args.returnTokenBudget;
93
98
  }
94
99
  if (args.searchContentTypes !== undefined) {
95
- if (!isStringArray(args.searchContentTypes) || !args.searchContentTypes.length || args.searchContentTypes.length > 2 || args.searchContentTypes.some((value) => value !== "text" && value !== "image"))
100
+ if (!isStringArray(args.searchContentTypes) || args.searchContentTypes.length > 2 || args.searchContentTypes.some((value) => value !== "text" && value !== "image"))
96
101
  throw failure("searchContentTypes is invalid");
97
- result.searchContentTypes = [...new Set(args.searchContentTypes)];
102
+ if (args.searchContentTypes.length)
103
+ result.searchContentTypes = [...new Set(args.searchContentTypes)];
98
104
  }
99
105
  if (args.imageSettings !== undefined) {
100
- if (!result.searchContentTypes?.includes("image") || !isRecord(args.imageSettings))
101
- throw failure("imageSettings requires image search");
106
+ // Always validated structurally. Without image search the object is inert model padding: it is
107
+ // dropped entirely, and maxResults 0 is tolerated only there. With image search, 1..100 is strict.
108
+ const imageSearch = result.searchContentTypes?.includes("image") === true;
109
+ if (!isRecord(args.imageSettings))
110
+ throw failure("imageSettings must be an object");
111
+ if (Object.keys(args.imageSettings).some((key) => key !== "maxResults" && key !== "caption"))
112
+ throw failure("imageSettings has unsupported fields");
102
113
  const imageSettings = {};
103
114
  if (args.imageSettings.maxResults !== undefined) {
104
115
  const maxResults = args.imageSettings.maxResults;
105
- if (!isInteger(maxResults) || maxResults < 1 || maxResults > 100)
116
+ if (!isInteger(maxResults) || maxResults < (imageSearch ? 1 : 0) || maxResults > 100)
106
117
  throw failure("imageSettings.maxResults is invalid");
107
118
  imageSettings.maxResults = maxResults;
108
119
  }
@@ -111,7 +122,8 @@ export function normalizeHostedSearchArgs(args) {
111
122
  throw failure("imageSettings.caption must be boolean");
112
123
  imageSettings.caption = args.imageSettings.caption;
113
124
  }
114
- result.imageSettings = imageSettings;
125
+ if (imageSearch)
126
+ result.imageSettings = imageSettings;
115
127
  }
116
128
  return result;
117
129
  }
@@ -27,7 +27,9 @@ function validProvenance(value) {
27
27
  (value.artifactFingerprint === undefined || validFingerprint(value.artifactFingerprint));
28
28
  }
29
29
  function validRef(refId, value) {
30
- return isRecord(value) && value.refId === refId && validHttpUrl(value.url) && validProvenance(value.provenance);
30
+ return isRecord(value) && value.refId === refId &&
31
+ (value.rawRefId === undefined || (typeof value.rawRefId === "string" && value.rawRefId.length > 0)) &&
32
+ validHttpUrl(value.url) && validProvenance(value.provenance);
31
33
  }
32
34
  function validSession(value) {
33
35
  return isRecord(value) &&
@@ -88,7 +90,7 @@ function collision(refId) {
88
90
  });
89
91
  }
90
92
  function sameObservation(left, right) {
91
- return left.refId === right.refId && left.url === right.url &&
93
+ return (left.rawRefId ?? left.refId) === (right.rawRefId ?? right.refId) && left.url === right.url &&
92
94
  left.provenance.action === right.provenance.action &&
93
95
  left.provenance.originKind === right.provenance.originKind &&
94
96
  left.provenance.originFingerprint === right.provenance.originFingerprint &&
@@ -99,10 +101,11 @@ export class AlphaRefStore {
99
101
  constructor(file) {
100
102
  this.store = new JsonStore(file, () => ({ version: VERSION, sessions: {} }), validData, "LCX_ALPHA_REF_STORE_CORRUPT", migrateLegacyData);
101
103
  }
102
- record(sessionId, routeFingerprint, refs) {
104
+ record(sessionId, routeFingerprint, refs, allowAliases = false) {
103
105
  if (typeof sessionId !== "string" || !sessionId || typeof routeFingerprint !== "string" || !routeFingerprint || !Array.isArray(refs)) {
104
106
  throw unavailable("invalid-record");
105
107
  }
108
+ const handles = {};
106
109
  this.store.update((current) => {
107
110
  const previous = current.sessions[sessionId];
108
111
  const sameRoute = previous?.routeFingerprint === routeFingerprint;
@@ -122,12 +125,26 @@ export class AlphaRefStore {
122
125
  ...(value.url ? { url: value.url } : {}),
123
126
  provenance: { ...value.provenance },
124
127
  };
128
+ const rawHistory = Object.values(nextRefs).filter((ref) => (ref.rawRefId ?? ref.refId) === observation.refId);
129
+ const latest = rawHistory.at(-1);
130
+ if (latest && sameObservation(latest, observation)) {
131
+ handles[observation.refId] = latest.refId;
132
+ continue;
133
+ }
134
+ if (accepted && !allowAliases)
135
+ throw collision(observation.refId);
125
136
  if (accepted) {
126
- if (!sameObservation(accepted, observation))
127
- throw collision(observation.refId);
137
+ const digest = createHash("sha256").update(JSON.stringify([observation, rawHistory.length])).digest("hex");
138
+ const handle = `lcx-alpha-${digest}`;
139
+ if (nextRefs[handle] && !sameObservation(nextRefs[handle], observation))
140
+ throw collision(handle);
141
+ nextRefs[handle] = { ...observation, refId: handle, rawRefId: observation.refId };
142
+ handles[observation.refId] = handle;
143
+ changed = true;
128
144
  continue;
129
145
  }
130
146
  nextRefs[observation.refId] = observation;
147
+ handles[observation.refId] = observation.refId;
131
148
  changed = true;
132
149
  }
133
150
  if (!changed && previous)
@@ -141,6 +158,7 @@ export class AlphaRefStore {
141
158
  .slice(0, 256);
142
159
  return { version: VERSION, sessions: Object.fromEntries(ordered) };
143
160
  });
161
+ return handles;
144
162
  }
145
163
  assertUsable(sessionId, routeFingerprint, refId) {
146
164
  this.store.refresh();
@@ -150,6 +168,10 @@ export class AlphaRefStore {
150
168
  const ref = session?.routeFingerprint === routeFingerprint ? session.refs[refId] : undefined;
151
169
  if (!ref)
152
170
  throw unavailable(refId);
171
+ const raw = ref.rawRefId ?? ref.refId;
172
+ const latest = Object.values(session?.refs ?? {}).filter((other) => (other.rawRefId ?? other.refId) === raw).at(-1);
173
+ if (latest?.refId !== refId)
174
+ throw unavailable(refId);
153
175
  return structuredClone(ref);
154
176
  }
155
177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-lcx-codex",
3
- "version": "0.4.3",
3
+ "version": "0.5.0-pre.1",
4
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",
@@ -56,13 +56,14 @@
56
56
  "inject": [
57
57
  "@deepseek-ai/dsh-client-ui-chat",
58
58
  "@deepseek-ai/dsh-client-ui-conversation",
59
- "@deepseek-ai/dsh-client-ui-settings-plugins"
59
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
60
+ "@deepseek-ai/dsh-client-ui-settings"
60
61
  ]
61
62
  }
62
63
  },
63
64
  "devDependencies": {
64
- "@deepseek-ai/dsh": "0.1.5-rc.1",
65
- "@earendil-works/pi-ai": "0.85.1",
65
+ "@deepseek-ai/dsh": "0.2.0-rc.2",
66
+ "@earendil-works/pi-ai": "0.87.1",
66
67
  "@deepseek-ai/schemastery": "^3.18.2",
67
68
  "@types/node": "^22.20.0",
68
69
  "@types/react": "~18.3.1",
@@ -72,30 +73,36 @@
72
73
  },
73
74
  "peerDependencies": {
74
75
  "@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",
76
+ "@deepseek-ai/cordis-plugin-loader": "~1.0.5",
77
+ "@deepseek-ai/dsh-agent": ">=0.2.0-rc.2 <0.2.1",
78
+ "@deepseek-ai/dsh-attachment": ">=0.2.0-rc.2 <0.2.1",
79
+ "@deepseek-ai/dsh-client-locale": ">=0.2.0-rc.2 <0.2.1",
80
+ "@deepseek-ai/dsh-client-modules": ">=0.2.0-rc.2 <0.2.1",
81
+ "@deepseek-ai/dsh-client-store": ">=0.2.0-rc.2 <0.2.1",
82
+ "@deepseek-ai/dsh-client-ui-chat": ">=0.2.0-rc.2 <0.2.1",
83
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.2.0-rc.2 <0.2.1",
84
+ "@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.2.0-rc.2 <0.2.1",
85
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.2 <0.2.1",
86
+ "@deepseek-ai/dsh-compaction": ">=0.2.0-rc.2 <0.2.1",
87
+ "@deepseek-ai/dsh-credentials": ">=0.2.0-rc.2 <0.2.1",
88
+ "@deepseek-ai/dsh-fs": ">=0.2.0-rc.2 <0.2.1",
89
+ "@deepseek-ai/dsh-llm": ">=0.2.0-rc.2 <0.2.1",
90
+ "@deepseek-ai/dsh-session": ">=0.2.0-rc.2 <0.2.1",
91
+ "@deepseek-ai/dsh-session-query": ">=0.2.0-rc.2 <0.2.1",
92
+ "@deepseek-ai/dsh-settings": ">=0.2.0-rc.2 <0.2.1",
93
+ "@deepseek-ai/dsh-tool-web": ">=0.2.0-rc.2 <0.2.1",
94
+ "@deepseek-ai/dsh-tools": ">=0.2.0-rc.2 <0.2.1",
95
+ "@deepseek-ai/dsh-web": ">=0.2.0-rc.2 <0.2.1",
92
96
  "@deepseek-ai/schemastery": "^3.18.2",
93
97
  "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",
98
+ "@deepseek-ai/dsh-token-meter": ">=0.2.0-rc.2 <0.2.1",
99
+ "@deepseek-ai/dsh-session-projection": ">=0.2.0-rc.2 <0.2.1",
96
100
  "zod": "^4.5.4"
97
101
  },
98
102
  "peerDependenciesMeta": {
103
+ "@deepseek-ai/cordis-plugin-loader": {
104
+ "optional": true
105
+ },
99
106
  "@deepseek-ai/dsh-client-locale": {
100
107
  "optional": true
101
108
  },
@@ -117,7 +124,10 @@
117
124
  "@deepseek-ai/dsh-client-ui-conversation": {
118
125
  "optional": true
119
126
  },
120
- "@deepseek-ai/dsh-client-ui-settings-plugins": {
127
+ "@deepseek-ai/dsh-client-ui-plugin-manager": {
128
+ "optional": true
129
+ },
130
+ "@deepseek-ai/dsh-client-ui-settings": {
121
131
  "optional": true
122
132
  },
123
133
  "@deepseek-ai/dsh-compaction": {
@@ -135,6 +145,9 @@
135
145
  "@deepseek-ai/dsh-session": {
136
146
  "optional": true
137
147
  },
148
+ "@deepseek-ai/dsh-session-query": {
149
+ "optional": true
150
+ },
138
151
  "@deepseek-ai/dsh-settings": {
139
152
  "optional": true
140
153
  },
@@ -166,8 +179,9 @@
166
179
  },
167
180
  "license": "MIT",
168
181
  "lcxCompatibility": {
169
- "dshInstallRange": ">=0.1.5-rc.1 <0.1.6",
170
- "verifiedDsh": "0.1.5-rc.1",
171
- "verifiedHostPi": "0.85.1"
182
+ "dshInstallRange": ">=0.2.0-rc.2 <0.2.1",
183
+ "verifiedDsh": "0.2.0-rc.2",
184
+ "verifiedHostPi": "0.87.1",
185
+ "verifiedPluginPi": "0.87.1"
172
186
  }
173
187
  }