@mars-sea/dsh-commandcode-provider 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -102,7 +102,7 @@ declare const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
102
102
  /** Head-of-request timeout: how long to wait for the first response byte. */
103
103
  declare const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
104
104
  /** Stream idle timeout: a generation that stalls this long is a dead connection. */
105
- declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120000;
105
+ declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000;
106
106
  /**
107
107
  * Official display label for a model's minimum plan, or undefined for models
108
108
  * outside the snapshot (e.g. future catalog additions).
@@ -140,9 +140,13 @@ interface CommandCodeConnectionOptions {
140
140
  workingDir: string;
141
141
  /** Model catalog cache path. */
142
142
  modelsCachePath: string;
143
- /** Milliseconds to wait for the generate response's first byte (default 60s). */
143
+ /**
144
+ * Milliseconds to wait for generate response headers / first byte (default 60s).
145
+ * Must not bound the subsequent body stream — long generations are gated by
146
+ * {@link streamIdleTimeoutMs} and the caller AbortSignal instead.
147
+ */
144
148
  requestTimeoutMs: number;
145
- /** Milliseconds a stream may stall before it is treated as a dead connection (default 120s). */
149
+ /** Milliseconds a stream may stall before it is treated as a dead connection (default 300s). */
146
150
  streamIdleTimeoutMs: number;
147
151
  }
148
152
  /**
@@ -276,7 +280,7 @@ interface Config {
276
280
  modelsCachePath?: string;
277
281
  /** Milliseconds to wait for the generate response's first byte; defaults to 60s. */
278
282
  requestTimeoutMs?: number;
279
- /** Milliseconds a stream may stall before being treated as a dead connection; defaults to 120s. */
283
+ /** Milliseconds a stream may stall before being treated as a dead connection; defaults to 300s. */
280
284
  streamIdleTimeoutMs?: number;
281
285
  }
282
286
  declare const Config: z<Config>;
package/lib/index.js CHANGED
@@ -383,7 +383,7 @@ const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
383
383
  /** Head-of-request timeout: how long to wait for the first response byte. */
384
384
  const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
385
385
  /** Stream idle timeout: a generation that stalls this long is a dead connection. */
386
- const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 12e4;
386
+ const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
387
387
  const MODEL_CACHE_VERSION = 1;
388
388
  function isRecord(value) {
389
389
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -446,6 +446,23 @@ function stringValue(value) {
446
446
  function numberValue(value) {
447
447
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
448
448
  }
449
+ function booleanValue(value) {
450
+ return typeof value === "boolean" ? value : void 0;
451
+ }
452
+ /**
453
+ * Terminal stream-error markers from the official CLI (`Xw` in command-code's
454
+ * cli.mjs): these always mean "retrying cannot succeed", so the adapter must
455
+ * not classify them as transient server errors.
456
+ */
457
+ const TERMINAL_STREAM_ERROR_MARKERS = [
458
+ "premium_credits_exhausted",
459
+ "model_not_in_plan",
460
+ "insufficient credits"
461
+ ];
462
+ function hasTerminalStreamMarker(message) {
463
+ const lower = message.toLowerCase();
464
+ return TERMINAL_STREAM_ERROR_MARKERS.some((marker) => lower.includes(marker));
465
+ }
449
466
  function recordOrEmpty(value) {
450
467
  if (isRecord(value)) return value;
451
468
  if (typeof value === "string") try {
@@ -827,6 +844,19 @@ var CommandCodeAdapter = class extends LlmAdapter {
827
844
  },
828
845
  threadId: randomUUID()
829
846
  };
847
+ const connectAbort = new AbortController();
848
+ let connectTimedOut = false;
849
+ const connectTimer = setTimeout(() => {
850
+ connectTimedOut = true;
851
+ connectAbort.abort(new DOMException(`Command Code API request to ${connection.apiBase}/alpha/generate did not respond within ${connection.requestTimeoutMs}ms`, "TimeoutError"));
852
+ }, connection.requestTimeoutMs);
853
+ const onCallerAbort = () => {
854
+ connectAbort.abort(options.signal?.reason);
855
+ };
856
+ if (options.signal) {
857
+ if (options.signal.aborted) onCallerAbort();
858
+ else options.signal.addEventListener("abort", onCallerAbort, { once: true });
859
+ }
830
860
  let response;
831
861
  try {
832
862
  response = await this.fetchImpl(`${connection.apiBase}/alpha/generate`, {
@@ -842,14 +872,18 @@ var CommandCodeAdapter = class extends LlmAdapter {
842
872
  ...attributionHeaders()
843
873
  },
844
874
  body: JSON.stringify(body),
845
- signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(connection.requestTimeoutMs)]) : AbortSignal.timeout(connection.requestTimeoutMs)
875
+ signal: connectAbort.signal
846
876
  });
877
+ clearTimeout(connectTimer);
847
878
  } catch (error) {
879
+ clearTimeout(connectTimer);
880
+ if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
848
881
  if (options.signal?.aborted) throw error;
849
- if (error instanceof DOMException && error.name === "TimeoutError") throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate did not respond within ${connection.requestTimeoutMs}ms: ${errorChain(error)}`, "TIMEOUT", { cause: error });
882
+ if (connectTimedOut || error instanceof DOMException && error.name === "TimeoutError") throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate did not respond within ${connection.requestTimeoutMs}ms: ${errorChain(error)}`, "TIMEOUT", { cause: error });
850
883
  throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate failed: ${errorChain(error)}`, "TRANSPORT", { cause: error });
851
884
  }
852
885
  if (!response.ok) {
886
+ if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
853
887
  const errText = await response.text().catch(() => "");
854
888
  let providerCode;
855
889
  try {
@@ -860,7 +894,10 @@ var CommandCodeAdapter = class extends LlmAdapter {
860
894
  if (response.status === 401) throw new LlmError(`Command Code API error 401 (${detail}): the API key is missing or invalid — check the key stored for COMMANDCODE_API_KEY (Models page) or the auth file`, "INVALID_CREDENTIAL", { status: 401 });
861
895
  throw new LlmError(`Command Code API error ${response.status}${detail === `HTTP ${response.status}` ? "" : ` (${detail})`}: ${errText.slice(0, 500)}`, response.status === 429 ? "RATE_LIMIT" : "PROVIDER_HTTP_ERROR", { status: response.status });
862
896
  }
863
- if (!response.body) throw new LlmError("Command Code API returned no response body", "PROVIDER_PROTOCOL_ERROR");
897
+ if (!response.body) {
898
+ if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
899
+ throw new LlmError("Command Code API returned no response body", "PROVIDER_PROTOCOL_ERROR");
900
+ }
864
901
  const reader = response.body.getReader();
865
902
  const decoder = new TextDecoder();
866
903
  let buffer = "";
@@ -1015,8 +1052,14 @@ var CommandCodeAdapter = class extends LlmAdapter {
1015
1052
  break;
1016
1053
  }
1017
1054
  case "error": {
1055
+ const err = isRecord(event.error) ? event.error : void 0;
1018
1056
  const detail = isRecord(event.error) ? stringValue(event.error.message) ?? JSON.stringify(event.error) : stringValue(event.error) ?? stringValue(event.message) ?? "Stream error";
1019
- throw new LlmError(`Command Code stream error: ${detail}`, "PROVIDER_STREAM_ERROR");
1057
+ const statusCode = err ? numberValue(err.statusCode) : void 0;
1058
+ const isRetryable = err ? booleanValue(err.isRetryable) : void 0;
1059
+ const retryableStatus = statusCode !== void 0 && (statusCode === 429 || statusCode >= 500);
1060
+ const terminal = hasTerminalStreamMarker(detail);
1061
+ if (!(isRetryable === true || (statusCode !== void 0 ? retryableStatus : isRetryable !== false && !terminal))) throw new LlmError(`Command Code stream error: ${detail}`, "PROVIDER_STREAM_ERROR", statusCode !== void 0 ? { status: statusCode } : void 0);
1062
+ throw new LlmError(`Command Code stream error: ${detail}`, "SERVER", statusCode !== void 0 ? { status: statusCode } : void 0);
1020
1063
  }
1021
1064
  }
1022
1065
  return chunks;
@@ -1063,6 +1106,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
1063
1106
  }
1064
1107
  } finally {
1065
1108
  clearIdle();
1109
+ if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
1066
1110
  await reader.cancel().catch(() => void 0);
1067
1111
  reader.releaseLock();
1068
1112
  }
@@ -1209,7 +1253,7 @@ function resolveAdapterOptions(config) {
1209
1253
  workingDir: config.workingDir ?? process.cwd(),
1210
1254
  modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH,
1211
1255
  requestTimeoutMs: config.requestTimeoutMs ?? 6e4,
1212
- streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 12e4
1256
+ streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5
1213
1257
  };
1214
1258
  }
1215
1259
  function apply(ctx, config) {