dsh-lcx-codex 0.4.3 → 0.4.4-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.
@@ -66,7 +66,7 @@ export function responsesGenerationEnvelope({ model = "unknown", reasoningEffort
66
66
  return result;
67
67
  }
68
68
  /**
69
- * Build the shared LCX-owned request envelope while retaining Pi 0.85.1 Responses semantics.
69
+ * Build the shared LCX-owned request envelope while retaining Pi 0.86.0 Responses semantics.
70
70
  * Production LCX ordinary/compact/replay construction places the DSH system prompt in canonical input.
71
71
  * @param {BuildResponsesBodyOptions} options
72
72
  */
@@ -84,7 +84,7 @@ export function buildResponsesBody({ model, input, tools, sessionId, promptCache
84
84
  const longRetention = retention === "long" && compat.supportsExplicitPromptCacheMode !== true
85
85
  ? promptCacheRetention ?? (compat.supportsLongCacheRetention === true ? "24h" : undefined)
86
86
  : undefined;
87
- // Pi 0.85.1 uses explicit 30m only for long retention; short is implicit.
87
+ // Pi 0.86.0 uses explicit 30m only for long retention; short is implicit.
88
88
  const currentCache = retention === "long" && compat.supportsLongCacheRetention === true && compat.supportsExplicitPromptCacheMode === true;
89
89
  const explicitCache = retention === "none" && compat.supportsExplicitPromptCacheMode === true;
90
90
  const promptCacheOptions = currentCache
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
  import { createAssistantMessageEventStream, processResponsesStream, } from "./pi-responses-runtime.js";
3
- import { ToolCallId } from "@deepseek-ai/dsh-llm";
3
+ import { abortError } from "./abort-error.js";
4
+ import { IMAGE_OFFLOAD_REQUIRED_CODE, ToolCallId } from "@deepseek-ai/dsh-llm";
4
5
  import { fetchSseWithRetry } from "./transport.js";
5
6
  import { createGrokNativeReplayEnvelope, grokPendingSourcesStart, sanitizeGrokVisibleText, } from "./grok-native-search.js";
6
7
  function emptyUsage() {
@@ -159,7 +160,35 @@ export function managedFailure(error, signal) {
159
160
  };
160
161
  }
161
162
  /** @param {unknown} error @param {AbortSignal} [signal] */
163
+ /**
164
+ * The host image-offload contract, when this failure carries it.
165
+ *
166
+ * `IMAGE_OFFLOAD_REQUIRED` is not an LCX failure class: it is a request the
167
+ * route makes of DSH's durable image-offload owner, which matches on the exact
168
+ * code and `offloadImages` count before recording an `image/offload` decision
169
+ * and retrying. Normalizing it into the closed LCX taxonomy would drop both
170
+ * fields and turn a recoverable step into a terminal turn failure.
171
+ * @param {unknown} error
172
+ * @returns {LlmFailure | undefined}
173
+ */
174
+ function imageOffloadFailure(error) {
175
+ if (!isObject(error))
176
+ return undefined;
177
+ const source = isObject(error.failure) ? error.failure : error;
178
+ if (source.code !== IMAGE_OFFLOAD_REQUIRED_CODE)
179
+ return undefined;
180
+ const offloadImages = source.offloadImages;
181
+ if (!Number.isSafeInteger(offloadImages) || offloadImages <= 0)
182
+ return undefined;
183
+ const message = typeof source.message === "string" && source.message.length > 0
184
+ ? source.message
185
+ : "Request images exceed the route budget";
186
+ return { message, code: IMAGE_OFFLOAD_REQUIRED_CODE, offloadImages: offloadImages };
187
+ }
162
188
  export function managedFailureChunk(error, signal) {
189
+ const offload = imageOffloadFailure(error);
190
+ if (offload !== undefined)
191
+ return { type: "finish", reason: { kind: "error", failure: offload } };
163
192
  const managed = managedFailure(error, signal);
164
193
  const failure = {
165
194
  message: managed.message,
@@ -180,9 +209,9 @@ async function readWithSignal(reader, signal) {
180
209
  if (!signal)
181
210
  return reader.read();
182
211
  if (signal.aborted)
183
- throw signal.reason;
212
+ throw abortError(signal);
184
213
  return new Promise((resolve, reject) => {
185
- const onAbort = () => reject(signal.reason);
214
+ const onAbort = () => reject(abortError(signal));
186
215
  signal.addEventListener("abort", onAbort, { once: true });
187
216
  reader.read().then(resolve, reject).finally(() => {
188
217
  signal.removeEventListener("abort", onAbort);
@@ -285,8 +314,7 @@ async function* responseEvents(response, options = {}) {
285
314
  try {
286
315
  while (true) {
287
316
  if (options.signal?.aborted)
288
- throw (options.signal.reason ??
289
- Object.assign(new Error("request aborted"), { code: "LCX_ABORTED" }));
317
+ throw abortError(options.signal);
290
318
  const { done, value } = await readWithSignal(reader, options.signal);
291
319
  if (done)
292
320
  break;
@@ -1312,8 +1340,7 @@ export async function* streamResponsesRequest({ baseURL, provider, model, piMode
1312
1340
  wireMeta.responseModel.length > 0)
1313
1341
  output.responseModel = wireMeta.responseModel;
1314
1342
  if (signal?.aborted)
1315
- throw (signal.reason ??
1316
- Object.assign(new Error("request aborted"), { code: "LCX_ABORTED" }));
1343
+ throw abortError(signal);
1317
1344
  if (output.stopReason === "pending")
1318
1345
  throw Object.assign(new Error("Responses stream ended without a stop reason"), { code: "LCX_INVALID_SSE" });
1319
1346
  if (output.stopReason === "aborted" || output.stopReason === "error")
@@ -1344,7 +1371,7 @@ export async function* streamResponsesRequest({ baseURL, provider, model, piMode
1344
1371
  while (true) {
1345
1372
  const result = await chunks.next();
1346
1373
  if (watchdog.signal.aborted)
1347
- throw watchdog.signal.reason;
1374
+ throw abortError(watchdog.signal);
1348
1375
  if (result.done) {
1349
1376
  exhausted = true;
1350
1377
  watchdog.disarm();
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import { SessionSeq } from '@deepseek-ai/dsh-session';
3
2
  import { addUsage, aggregateContextOf, auxiliaryUsageOf, isSearchTool, zeroBuckets } from './search-accounting.js';
4
3
  const n = z.number().int().nonnegative();
5
4
  const schema = z.object({ auxiliary: z.object({ uncachedInputTokens: n, outputTokens: n, cacheReadTokens: n, cacheWriteTokens: n }).strict(), aggregateContext: z.boolean() }).strict();
@@ -26,7 +25,7 @@ export const searchUsageProjection = {
26
25
  };
27
26
  const installed = new WeakMap();
28
27
  /** Own a reversible adapter on the public measure method, never a DSH file or private fold. */
29
- export function installSearchMeasurement(meter) {
28
+ export function installSearchMeasurement(meter, projections) {
30
29
  const existing = installed.get(meter);
31
30
  if (existing) {
32
31
  existing.refs++;
@@ -37,17 +36,10 @@ export function installSearchMeasurement(meter) {
37
36
  } };
38
37
  }
39
38
  const original = meter.measure, descriptor = Object.getOwnPropertyDescriptor(meter, 'measure');
40
- const cursors = new WeakMap();
41
39
  function measure(session, header) {
42
40
  const value = original.call(this, session, header);
43
- let state = cursors.get(session) ?? { seq: 0, aggregate: false };
44
- while (state.seq < session.seq) {
45
- const e = session.eventAt(SessionSeq(state.seq++));
46
- if (e?.type === 'assistant/message')
47
- state.aggregate = aggregateContextOf(e) ?? false;
48
- }
49
- cursors.set(session, state);
50
- if (!state.aggregate || value.baseline.kind !== 'usage')
41
+ const state = projections.stateOf(session, 'lcxSearchUsage');
42
+ if (!state?.aggregateContext || value.baseline.kind !== 'usage')
51
43
  return value;
52
44
  // The official measure already prices retained images/files and surface replacements.
53
45
  // Only discard its unsuitable aggregate anchor; keep its current surface and node prices.
@@ -80,7 +72,7 @@ function release(meter) {
80
72
  }
81
73
  export function installSearchUsage(ctx) {
82
74
  ctx.inject(['sessionProjections'], c => { c.sessionProjections.register(searchUsageProjection); });
83
- ctx.inject(['tokenMeter'], c => {
84
- c.effect(() => installSearchMeasurement(c.tokenMeter), 'lcx search context measurement');
75
+ ctx.inject(['tokenMeter', 'sessionProjections'], c => {
76
+ c.effect(() => installSearchMeasurement(c.tokenMeter, c.sessionProjections), 'lcx search context measurement');
85
77
  });
86
78
  }
@@ -1,8 +1,4 @@
1
- function abortReason(signal) {
2
- if (signal?.reason instanceof Error)
3
- return signal.reason;
4
- return new DOMException("The operation was aborted", "AbortError");
5
- }
1
+ import { abortError } from "./abort-error.js";
6
2
  export class ServiceMutex {
7
3
  locked = false;
8
4
  closed = false;
@@ -14,7 +10,7 @@ export class ServiceMutex {
14
10
  if (this.closed)
15
11
  return Promise.reject(this.closeReason ?? new Error("service mutex closed"));
16
12
  if (signal?.aborted)
17
- return Promise.reject(abortReason(signal));
13
+ return Promise.reject(abortError(signal));
18
14
  if (!this.locked) {
19
15
  this.locked = true;
20
16
  return Promise.resolve(() => this.release());
@@ -27,7 +23,7 @@ export class ServiceMutex {
27
23
  if (index >= 0)
28
24
  this.queue.splice(index, 1);
29
25
  signal.removeEventListener("abort", onAbort);
30
- reject(abortReason(signal));
26
+ reject(abortError(signal));
31
27
  };
32
28
  waiter.onAbort = onAbort;
33
29
  signal.addEventListener("abort", onAbort, { once: true });
@@ -43,7 +39,7 @@ export class ServiceMutex {
43
39
  if (waiter.signal && waiter.onAbort)
44
40
  waiter.signal.removeEventListener("abort", waiter.onAbort);
45
41
  if (waiter.signal?.aborted) {
46
- waiter.reject(abortReason(waiter.signal));
42
+ waiter.reject(abortError(waiter.signal));
47
43
  continue;
48
44
  }
49
45
  waiter.resolve(() => this.release());
package/lib/transport.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { abortError } from "./abort-error.js";
1
2
  const DEFAULT_TIMEOUT_MS = 300_000;
2
3
  const DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
3
4
  const MAX_ERROR_BYTES = 512 * 1024;
@@ -6,8 +7,7 @@ function errorFacts(value) {
6
7
  }
7
8
  export function abortIfNeeded(signal) {
8
9
  if (signal?.aborted)
9
- throw (signal.reason ??
10
- Object.assign(new Error("request aborted"), { code: "LCX_ABORTED" }));
10
+ throw abortError(signal);
11
11
  }
12
12
  function makeError(message, code, extra = {}) {
13
13
  const error = new Error(message);
@@ -63,11 +63,11 @@ async function sleep(ms, signal) {
63
63
  return;
64
64
  await new Promise((resolve, reject) => {
65
65
  if (signal?.aborted)
66
- return reject(signal.reason);
66
+ return reject(abortError(signal));
67
67
  const onAbort = () => {
68
68
  clearTimeout(timer);
69
69
  signal?.removeEventListener("abort", onAbort);
70
- reject(signal?.reason);
70
+ reject(abortError(signal));
71
71
  };
72
72
  const timer = setTimeout(() => {
73
73
  signal?.removeEventListener("abort", onAbort);
@@ -161,7 +161,7 @@ export async function fetchJsonWithRetry(url, body, headers = {}, signal, timeou
161
161
  }
162
162
  catch (error) {
163
163
  if (signal?.aborted)
164
- throw signal.reason ?? error;
164
+ throw abortError(signal, error);
165
165
  const details = errorFacts(error);
166
166
  const retryable = details.retryable === true ||
167
167
  details.code === "LCX_HTTP_RETRYABLE" ||
@@ -231,7 +231,7 @@ export async function fetchSseWithRetry(url, body, headers = {}, signal, timeout
231
231
  }
232
232
  catch (error) {
233
233
  if (signal?.aborted)
234
- throw signal.reason ?? error;
234
+ throw abortError(signal, error);
235
235
  const details = errorFacts(error);
236
236
  const retryable = details.retryable === true ||
237
237
  details.code === "LCX_HTTP_RETRYABLE" ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-lcx-codex",
3
- "version": "0.4.3",
3
+ "version": "0.4.4-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,13 @@
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
60
  ]
61
61
  }
62
62
  },
63
63
  "devDependencies": {
64
- "@deepseek-ai/dsh": "0.1.5-rc.1",
65
- "@earendil-works/pi-ai": "0.85.1",
64
+ "@deepseek-ai/dsh": "0.1.6-alpha.2",
65
+ "@earendil-works/pi-ai": "0.86.0",
66
66
  "@deepseek-ai/schemastery": "^3.18.2",
67
67
  "@types/node": "^22.20.0",
68
68
  "@types/react": "~18.3.1",
@@ -72,27 +72,27 @@
72
72
  },
73
73
  "peerDependencies": {
74
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",
75
+ "@deepseek-ai/dsh-agent": ">=0.1.6-alpha.2 <0.1.7",
76
+ "@deepseek-ai/dsh-attachment": ">=0.1.6-alpha.2 <0.1.7",
77
+ "@deepseek-ai/dsh-client-locale": ">=0.1.6-alpha.2 <0.1.7",
78
+ "@deepseek-ai/dsh-client-modules": ">=0.1.6-alpha.2 <0.1.7",
79
+ "@deepseek-ai/dsh-client-store": ">=0.1.6-alpha.2 <0.1.7",
80
+ "@deepseek-ai/dsh-client-ui-chat": ">=0.1.6-alpha.2 <0.1.7",
81
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.6-alpha.2 <0.1.7",
82
+ "@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.1.6-alpha.2 <0.1.7",
83
+ "@deepseek-ai/dsh-compaction": ">=0.1.6-alpha.2 <0.1.7",
84
+ "@deepseek-ai/dsh-credentials": ">=0.1.6-alpha.2 <0.1.7",
85
+ "@deepseek-ai/dsh-fs": ">=0.1.6-alpha.2 <0.1.7",
86
+ "@deepseek-ai/dsh-llm": ">=0.1.6-alpha.2 <0.1.7",
87
+ "@deepseek-ai/dsh-session": ">=0.1.6-alpha.2 <0.1.7",
88
+ "@deepseek-ai/dsh-settings": ">=0.1.6-alpha.2 <0.1.7",
89
+ "@deepseek-ai/dsh-tool-web": ">=0.1.6-alpha.2 <0.1.7",
90
+ "@deepseek-ai/dsh-tools": ">=0.1.6-alpha.2 <0.1.7",
91
+ "@deepseek-ai/dsh-web": ">=0.1.6-alpha.2 <0.1.7",
92
92
  "@deepseek-ai/schemastery": "^3.18.2",
93
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",
94
+ "@deepseek-ai/dsh-token-meter": ">=0.1.6-alpha.2 <0.1.7",
95
+ "@deepseek-ai/dsh-session-projection": ">=0.1.6-alpha.2 <0.1.7",
96
96
  "zod": "^4.5.4"
97
97
  },
98
98
  "peerDependenciesMeta": {
@@ -117,7 +117,7 @@
117
117
  "@deepseek-ai/dsh-client-ui-conversation": {
118
118
  "optional": true
119
119
  },
120
- "@deepseek-ai/dsh-client-ui-settings-plugins": {
120
+ "@deepseek-ai/dsh-client-ui-plugin-manager": {
121
121
  "optional": true
122
122
  },
123
123
  "@deepseek-ai/dsh-compaction": {
@@ -166,8 +166,9 @@
166
166
  },
167
167
  "license": "MIT",
168
168
  "lcxCompatibility": {
169
- "dshInstallRange": ">=0.1.5-rc.1 <0.1.6",
170
- "verifiedDsh": "0.1.5-rc.1",
171
- "verifiedHostPi": "0.85.1"
169
+ "dshInstallRange": ">=0.1.6-alpha.2 <0.1.7",
170
+ "verifiedDsh": "0.1.6-alpha.2",
171
+ "verifiedHostPi": "0.85.1",
172
+ "verifiedPluginPi": "0.86.0"
172
173
  }
173
174
  }