dsh-opencode-go 0.1.16 → 0.1.18

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.js CHANGED
@@ -51,7 +51,7 @@ import { LlmError as LlmError7, assertUsableApiKey, resolveImageAttachmentAccess
51
51
 
52
52
  // src/adapter.ts
53
53
  import { randomUUID as randomUUID2 } from "node:crypto";
54
- import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
54
+ import { getSupportedThinkingLevels, normalizeContext } from "opencode-go-pi-ai";
55
55
  import {
56
56
  LlmAdapter,
57
57
  LlmError as LlmError6,
@@ -86,13 +86,14 @@ function emptyPiUsage() {
86
86
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
87
87
  };
88
88
  }
89
- function toPiReplayState(message, requestedModel = message.model) {
89
+ function toPiReplayState(message, requestedModel = message.model, requestedProvider = message.provider) {
90
90
  const responseModel = message.api === "anthropic-messages" && message.model !== requestedModel ? message.model : message.responseModel;
91
91
  const response = {
92
92
  kind: "pi-ai",
93
93
  version: 2,
94
94
  api: message.api,
95
- provider: message.provider,
95
+ provider: requestedProvider,
96
+ ...requestedProvider === message.provider ? {} : { sdkProvider: message.provider },
96
97
  model: requestedModel,
97
98
  ...responseModel === void 0 ? {} : { responseModel },
98
99
  ...message.responseId === void 0 ? {} : { responseId: message.responseId },
@@ -137,6 +138,9 @@ function readReplayState(value) {
137
138
  for (const key of ["api", "provider", "model"]) {
138
139
  if (typeof response[key] !== "string" || response[key].length === 0) return invalidReplay(`${key} must be a non-empty string`);
139
140
  }
141
+ if (response["sdkProvider"] !== void 0 && (typeof response["sdkProvider"] !== "string" || response["sdkProvider"].length === 0)) {
142
+ return invalidReplay("sdkProvider must be a non-empty string");
143
+ }
140
144
  if (!["stop", "length", "toolUse", "error", "aborted"].includes(String(response["stopReason"]))) {
141
145
  return invalidReplay("unknown stopReason");
142
146
  }
@@ -236,7 +240,7 @@ function replayedAssistant(message, source, rawState) {
236
240
  role: "assistant",
237
241
  content,
238
242
  api: state.response.api,
239
- provider: state.response.provider,
243
+ provider: state.response.sdkProvider ?? state.response.provider,
240
244
  // Anthropic reports aliases and fallbacks as model, unlike Completions' informational responseModel.
241
245
  model: state.response.api === "anthropic-messages" ? state.response.responseModel ?? state.response.model : state.response.model,
242
246
  ...state.response.responseModel === void 0 ? {} : { responseModel: state.response.responseModel },
@@ -551,7 +555,7 @@ async function toPiContextWithImages(options, images, onReplayDegrade) {
551
555
  // src/conversion/stream.ts
552
556
  import { brandString as brandString2 } from "@deepseek-ai/dsh-brand";
553
557
  import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError as LlmError4, QUOTA_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
554
- import { isContextOverflow } from "@earendil-works/pi-ai";
558
+ import { isContextOverflow } from "opencode-go-pi-ai";
555
559
  function mapUsage(usage) {
556
560
  return {
557
561
  inputTokens: usage.input,
@@ -624,7 +628,7 @@ function mapStopReason(message, contextWindow) {
624
628
  }
625
629
  }
626
630
  }
627
- async function* toStreamChunks(events, contextWindow, callerSignal, requestedModel) {
631
+ async function* toStreamChunks(events, contextWindow, callerSignal, requestedModel, requestedProvider) {
628
632
  const toolIds = /* @__PURE__ */ new Map();
629
633
  for await (const event of events) {
630
634
  switch (event.type) {
@@ -686,7 +690,7 @@ async function* toStreamChunks(events, contextWindow, callerSignal, requestedMod
686
690
  yield {
687
691
  type: "finish",
688
692
  reason: mapStopReason(event.message, contextWindow),
689
- replayState: toPiReplayState(event.message, requestedModel)
693
+ replayState: toPiReplayState(event.message, requestedModel, requestedProvider)
690
694
  };
691
695
  return;
692
696
  case "error":
@@ -708,11 +712,11 @@ async function* toStreamChunks(events, contextWindow, callerSignal, requestedMod
708
712
  import { idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
709
713
 
710
714
  // src/catalog.ts
711
- import { createProvider } from "@earendil-works/pi-ai";
712
- import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
713
- import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
714
- import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
715
- import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
715
+ import { createProvider } from "opencode-go-pi-ai";
716
+ import { getBuiltinModels } from "opencode-go-pi-ai/providers/all";
717
+ import { anthropicMessagesApi } from "opencode-go-pi-ai/api/anthropic-messages.lazy";
718
+ import { openAICompletionsApi } from "opencode-go-pi-ai/api/openai-completions.lazy";
719
+ import { openAIResponsesApi } from "opencode-go-pi-ai/api/openai-responses.lazy";
716
720
  import { attributionHeaders, LlmError as LlmError5 } from "@deepseek-ai/dsh-llm";
717
721
 
718
722
  // src/models-contract.ts
@@ -808,6 +812,28 @@ var modelsRemote = {
808
812
  }]
809
813
  };
810
814
 
815
+ // src/reasoning.ts
816
+ var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
817
+ var NATIVE_THINKING_FLAGS = /* @__PURE__ */ new Set(["deepseek", "zai", "qwen", "qwen-chat-template"]);
818
+ function unsupportedThinkingLevels() {
819
+ return Object.fromEntries(THINKING_LEVELS.map((level) => [level, null]));
820
+ }
821
+ function withGatewayReasoning(model) {
822
+ if (model.id !== "mimo-v2.6-flash" || model.api !== "openai-completions" || !model.reasoning) return model;
823
+ return { ...model, thinkingLevelMap: {
824
+ ...unsupportedThinkingLevels(),
825
+ off: "none",
826
+ low: "low",
827
+ medium: "medium",
828
+ high: "high"
829
+ } };
830
+ }
831
+ function withRequestReasoning(model, level) {
832
+ const format = model.compat?.thinkingFormat;
833
+ if (level !== void 0 || model.api !== "openai-completions" || format !== void 0 && format !== "openai" || typeof model.thinkingLevelMap?.off !== "string") return model;
834
+ return { ...model, thinkingLevelMap: { ...model.thinkingLevelMap, off: null } };
835
+ }
836
+
811
837
  // src/model-metadata.ts
812
838
  var MODEL_METADATA_URL = "https://models.dev/api.json";
813
839
  function record(value) {
@@ -827,23 +853,28 @@ function rates(value) {
827
853
  };
828
854
  return { input: rate("input"), output: rate("output"), cacheRead: rate("cache_read"), cacheWrite: rate("cache_write") };
829
855
  }
830
- var LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
831
- function thinkingLevels(metadata, known) {
832
- if (!Array.isArray(metadata.reasoning_options) && known?.thinkingLevelMap !== void 0) return known.thinkingLevelMap;
833
- const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
834
- for (const item of Array.isArray(metadata.reasoning_options) ? metadata.reasoning_options : []) {
835
- const option = record(item);
836
- if (option.type === "toggle" || option.type === "budget_tokens") {
837
- map.off = "off";
838
- map.high = "high";
839
- }
840
- if (option.type !== "effort" || !Array.isArray(option.values)) continue;
856
+ function thinkingLevels(id, api2, metadata, known) {
857
+ if (!Array.isArray(metadata.reasoning_options) && known?.id === id && known.thinkingLevelMap !== void 0) return known.thinkingLevelMap;
858
+ const map = unsupportedThinkingLevels();
859
+ const options = (Array.isArray(metadata.reasoning_options) ? metadata.reasoning_options : []).map(record);
860
+ const efforts = options.filter((option) => option.type === "effort" && Array.isArray(option.values));
861
+ for (const option of efforts) {
841
862
  for (const value of option.values) {
842
863
  const level = value === "none" ? "off" : value;
843
- if (LEVELS.includes(level)) map[level] = String(value);
864
+ if (THINKING_LEVELS.includes(level)) map[level] = String(value);
844
865
  }
845
866
  }
846
- if (known?.reasoning && known.thinkingLevelMap?.off !== null) map.off ??= known.thinkingLevelMap?.off ?? "off";
867
+ const format = known?.compat?.thinkingFormat;
868
+ const native = api2 === "anthropic-messages" || NATIVE_THINKING_FLAGS.has(format ?? "");
869
+ const toggle = options.some((option) => option.type === "toggle");
870
+ const budget = options.some((option) => option.type === "budget_tokens");
871
+ if (native && efforts.length === 0 && (toggle || budget)) map.high = "high";
872
+ const enabled = Object.entries(map).some(([level, wire]) => level !== "off" && typeof wire === "string");
873
+ if (native && (toggle || enabled && known?.reasoning) && known?.thinkingLevelMap?.off !== null && map.off === null) {
874
+ delete map.off;
875
+ } else if (enabled && known?.id === id && known.reasoning && typeof known.thinkingLevelMap?.off === "string") {
876
+ map.off ??= known.thinkingLevelMap.off;
877
+ }
847
878
  return map;
848
879
  }
849
880
  function modelBaseURL(api2, baseURL) {
@@ -894,20 +925,20 @@ function readModelMetadata(body, baseURL, builtin) {
894
925
  return tier.type === "context" && typeof tier.size === "number" && Number.isSafeInteger(tier.size) && tier.size > 0 ? [{ ...rates(item), inputTokensAbove: tier.size }] : [];
895
926
  }).sort((a, b) => a.inputTokensAbove - b.inputTokensAbove);
896
927
  }
897
- models.set(id, {
928
+ models.set(id, withGatewayReasoning({
898
929
  id,
899
930
  name: typeof metadata.name === "string" && metadata.name.length > 0 ? metadata.name : id,
900
931
  provider: "opencode-go",
901
932
  api: api2,
902
933
  baseUrl: modelBaseURL(api2, baseURL),
903
934
  reasoning: metadata.reasoning,
904
- thinkingLevelMap: thinkingLevels(metadata, known),
935
+ thinkingLevelMap: thinkingLevels(id, api2, metadata, known),
905
936
  input: input.includes("image") ? ["text", "image"] : ["text"],
906
937
  contextWindow: positiveInteger(limit.context, "context limit"),
907
938
  maxTokens: positiveInteger(limit.output, "output limit"),
908
939
  cost,
909
940
  compat
910
- });
941
+ }));
911
942
  } catch (error) {
912
943
  errors.set(id, error instanceof Error ? error.message : String(error));
913
944
  }
@@ -917,6 +948,9 @@ function readModelMetadata(body, baseURL, builtin) {
917
948
 
918
949
  // src/json-response.ts
919
950
  import { setTimeout as delay } from "node:timers/promises";
951
+ import { Readable, Writable } from "node:stream";
952
+ import { pipeline } from "node:stream/promises";
953
+ import { createGunzip } from "node:zlib";
920
954
  var RETRYABLE_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "EPIPE", "UND_ERR_SOCKET", "EAI_AGAIN"]);
921
955
  function errorCauses(error) {
922
956
  const causes = [];
@@ -954,10 +988,10 @@ function diagnosticURL(raw) {
954
988
  url.hash = "";
955
989
  return url.href;
956
990
  }
957
- async function fetchJsonResponse(url, init, maxBytes) {
991
+ async function fetchJsonResponse(url, init, maxBytes, encoding = "identity") {
958
992
  if ((init.method ?? "GET").toUpperCase() !== "GET") throw new TypeError("JSON requests must use GET");
959
993
  const headers = new Headers(init.headers);
960
- headers.set("accept-encoding", "identity");
994
+ headers.set("accept-encoding", encoding);
961
995
  const options = { ...init, method: "GET", headers };
962
996
  for (let attempt = 0; ; attempt += 1) {
963
997
  init.signal?.throwIfAborted();
@@ -968,7 +1002,7 @@ async function fetchJsonResponse(url, init, maxBytes) {
968
1002
  });
969
1003
  return { response, body: void 0 };
970
1004
  }
971
- return { response, body: await readJsonResponse(response, maxBytes) };
1005
+ return { response, body: await readJsonResponse(response, maxBytes, { gzip: encoding === "gzip", signal: init.signal }) };
972
1006
  } catch (error) {
973
1007
  init.signal?.throwIfAborted();
974
1008
  if (attempt > 0 || !retryableTransportFailure(error)) throw error;
@@ -998,8 +1032,33 @@ async function readBoundedBody(response, maxBytes) {
998
1032
  reader.releaseLock();
999
1033
  }
1000
1034
  }
1001
- async function readJsonResponse(response, maxBytes) {
1002
- const bytes = await readBoundedBody(response, maxBytes);
1035
+ async function readGzipBody(bytes, maxBytes, signal) {
1036
+ const chunks = [];
1037
+ let size = 0;
1038
+ await pipeline(Readable.from([bytes]), createGunzip(), new Writable({
1039
+ write(chunk, _encoding, done) {
1040
+ if (chunk.length > maxBytes - size) {
1041
+ done(new RangeError(`Response body exceeds ${maxBytes} byte limit`));
1042
+ return;
1043
+ }
1044
+ size += chunk.length;
1045
+ chunks.push(chunk);
1046
+ done();
1047
+ }
1048
+ }), { signal: signal ?? void 0 });
1049
+ return Buffer.concat(chunks, size);
1050
+ }
1051
+ async function readJsonResponse(response, maxBytes, options = {}) {
1052
+ let bytes = await readBoundedBody(response, maxBytes);
1053
+ if (options.gzip && bytes[0] === 31 && bytes[1] === 139) {
1054
+ try {
1055
+ bytes = await readGzipBody(bytes, maxBytes, options.signal);
1056
+ } catch (error) {
1057
+ options.signal?.throwIfAborted();
1058
+ if (error instanceof RangeError) throw error;
1059
+ throw new SyntaxError("Response is not valid gzip", { cause: error });
1060
+ }
1061
+ }
1003
1062
  try {
1004
1063
  return JSON.parse(new TextDecoder().decode(bytes));
1005
1064
  } catch (error) {
@@ -1056,9 +1115,12 @@ async function writeMetadataCache(path, value) {
1056
1115
  }
1057
1116
  }
1058
1117
 
1118
+ // src/provider-identity.ts
1119
+ var PROVIDER_ID = "dsh-opencode-go";
1120
+ var DISPLAY_NAME = "DSH OpenCode Go";
1121
+ var SDK_PROVIDER_ID = "opencode-go";
1122
+
1059
1123
  // src/catalog.ts
1060
- var PROVIDER_ID = "opencode-go";
1061
- var DISPLAY_NAME = "OpenCode Go";
1062
1124
  var DEFAULT_BASE_URL = "https://opencode.ai/zen/go/v1";
1063
1125
  var MODELS_FETCH_TIMEOUT_MS = 1e4;
1064
1126
  var METADATA_FETCH_TIMEOUT_MS = 3e4;
@@ -1089,11 +1151,11 @@ function waitForSnapshot(pending, signal) {
1089
1151
  });
1090
1152
  }
1091
1153
  function builtinModels(baseURL) {
1092
- return new Map(getBuiltinModels("opencode-go").map((model) => [model.id, {
1154
+ return new Map(getBuiltinModels(SDK_PROVIDER_ID).map((model) => [model.id, withGatewayReasoning({
1093
1155
  ...model,
1094
- provider: PROVIDER_ID,
1156
+ provider: SDK_PROVIDER_ID,
1095
1157
  baseUrl: modelBaseURL(model.api, baseURL)
1096
- }]));
1158
+ })]));
1097
1159
  }
1098
1160
  function readLiveModelIds(body) {
1099
1161
  const data = body?.data;
@@ -1137,7 +1199,7 @@ function harnessApiKeyAuth() {
1137
1199
  }
1138
1200
  function buildProvider(baseURL, models) {
1139
1201
  return createProvider({
1140
- id: PROVIDER_ID,
1202
+ id: SDK_PROVIDER_ID,
1141
1203
  name: DISPLAY_NAME,
1142
1204
  baseUrl: baseURL,
1143
1205
  auth: harnessApiKeyAuth(),
@@ -1242,7 +1304,7 @@ var OpencodeGoCatalog = class {
1242
1304
  },
1243
1305
  cache: "no-cache",
1244
1306
  signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)
1245
- }, MODEL_METADATA_MAX_BYTES);
1307
+ }, MODEL_METADATA_MAX_BYTES, "gzip");
1246
1308
  if (response.status === 304 && this.metadata !== void 0) {
1247
1309
  this.metadataUpdatedAtMs = Date.now();
1248
1310
  this.metadataETag = metadataETag(response.headers.get("etag")) ?? this.metadataETag;
@@ -1443,7 +1505,6 @@ function assertBaseURL(raw) {
1443
1505
  }
1444
1506
 
1445
1507
  // src/adapter.ts
1446
- var DISABLES_THINKING_WHEN_UNSET = /* @__PURE__ */ new Set(["deepseek", "zai", "qwen", "qwen-chat-template"]);
1447
1508
  function withModelLimit(model, limits) {
1448
1509
  const limit = limits[model.id];
1449
1510
  if (limit == null) return model;
@@ -1523,7 +1584,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1523
1584
  const levels = model.reasoning ? getSupportedThinkingLevels(model) : [];
1524
1585
  if (levels.length > 0) {
1525
1586
  const format = model.compat?.thinkingFormat;
1526
- const fallback = format !== void 0 && DISABLES_THINKING_WHEN_UNSET.has(format) ? levels.includes("high") ? "high" : levels.findLast((level) => level !== "off") : void 0;
1587
+ const fallback = format !== void 0 && NATIVE_THINKING_FLAGS.has(format) ? levels.includes("high") ? "high" : levels.findLast((level) => level !== "off") : void 0;
1527
1588
  reasoning.reasoning = {
1528
1589
  efforts: levels.map((level) => ({
1529
1590
  id: ReasoningEffortId(level),
@@ -1608,7 +1669,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1608
1669
  };
1609
1670
  }
1610
1671
  const context = imageRequest === void 0 ? toPiContext(options, void 0, this.options.onReplayDegrade) : await toPiContext({ ...options, signal: watchdog.signal }, imageRequest, this.options.onReplayDegrade);
1611
- const events = snapshot.provider.streamSimple(model, context, {
1672
+ const events = snapshot.provider.streamSimple(withRequestReasoning(model, reasoning), normalizeContext(context), {
1612
1673
  apiKey,
1613
1674
  ...reasoning === void 0 || reasoning === "off" ? {} : { reasoning },
1614
1675
  ...options.temperature === void 0 ? {} : { temperature: options.temperature },
@@ -1626,7 +1687,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1626
1687
  // one SDK attempt.
1627
1688
  maxRetries: 0
1628
1689
  });
1629
- const iterator = toStreamChunks(events, model.contextWindow, options.signal, model.id)[Symbol.asyncIterator]();
1690
+ const iterator = toStreamChunks(events, model.contextWindow, options.signal, model.id, options.provider)[Symbol.asyncIterator]();
1630
1691
  let exhausted = false;
1631
1692
  try {
1632
1693
  while (true) {
@@ -1878,6 +1939,7 @@ function apply(ctx, raw) {
1878
1939
  if (configured && current().enabled && registration === void 0) {
1879
1940
  try {
1880
1941
  registration = ctx.llm.registerAdapter([PROVIDER_ID], adapter);
1942
+ ctx.logger.info(`llm-opencode-go: route "${PROVIDER_ID}" registered as ${DISPLAY_NAME}`);
1881
1943
  } catch (error) {
1882
1944
  ctx.logger.error(`llm-opencode-go: not registering the "${PROVIDER_ID}" route (${String(error)})`);
1883
1945
  }
@@ -1951,7 +2013,6 @@ function apply(ctx, raw) {
1951
2013
  });
1952
2014
  syncRoute();
1953
2015
  });
1954
- ctx.logger.info(`llm-opencode-go: route "${PROVIDER_ID}" registered as ${DISPLAY_NAME}`);
1955
2016
  }
1956
2017
  export {
1957
2018
  Config,
@@ -1,9 +1,8 @@
1
- import type { Api, Model, Provider } from '@earendil-works/pi-ai';
1
+ import type { Api, Model, Provider } from 'opencode-go-pi-ai';
2
2
  import type { LlmDiscoveredModel } from '@deepseek-ai/dsh-llm';
3
3
  import { type GoModelCatalog } from './models-contract.ts';
4
4
  import type { ModelMetadata } from './model-metadata.ts';
5
- export declare const PROVIDER_ID = "opencode-go";
6
- export declare const DISPLAY_NAME = "OpenCode Go";
5
+ export { PROVIDER_ID, DISPLAY_NAME } from './provider-identity.ts';
7
6
  export declare const DEFAULT_BASE_URL = "https://opencode.ai/zen/go/v1";
8
7
  export interface CatalogSnapshot {
9
8
  readonly details: ModelMetadata['details'];
@@ -1,9 +1,10 @@
1
+ import { type ReactNode } from 'react';
1
2
  import type { OpencodeGoModelLimit, OpencodeGoModelLimits, OpencodeGoModels } from './section-controller.ts';
2
3
  import type { en } from './locales.ts';
3
4
  type Translate = (key: keyof typeof en, params?: Record<string, unknown>) => string;
4
5
  export declare function hasCapacityOverride(limit: OpencodeGoModelLimit | null | undefined): boolean;
5
6
  /** Per-model switches apply immediately; capacity edits stay in the staged form. */
6
- export declare function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visibilitySaving, onEdit, onModelEnabled }: {
7
+ export declare function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visibilitySaving, filterAction, onEdit, onModelEnabled }: {
7
8
  models: OpencodeGoModels;
8
9
  draft: OpencodeGoModelLimits;
9
10
  modelVisibility: Readonly<Record<string, boolean>>;
@@ -11,6 +12,8 @@ export declare function ModelEditor({ models, draft, modelVisibility, t, locale,
11
12
  locale?: string;
12
13
  disabled: boolean;
13
14
  visibilitySaving: boolean;
15
+ /** Optional control pinned to the right of the filter row, such as a refresh. */
16
+ filterAction?: ReactNode;
14
17
  onEdit: (next: OpencodeGoModelLimits) => void;
15
18
  onModelEnabled: (id: string, enabled: boolean) => void;
16
19
  }): import("react").JSX.Element;
@@ -46,7 +46,6 @@ export declare const en: {
46
46
  modelsSourceUpdated: string;
47
47
  modelsRefreshing: string;
48
48
  modelsEmpty: string;
49
- modelsCount: string;
50
49
  limitsLabel: string;
51
50
  limitsHint: string;
52
51
  limitsFilterLabel: string;
@@ -32,7 +32,7 @@ export type OpencodeGoModelLimits = Record<string, OpencodeGoModelLimit | null>;
32
32
  export interface OpencodeGoConfig {
33
33
  /**
34
34
  * Whether this adapter serves its route at all. False withdraws the
35
- * `opencode-go` route and its models from every picker without unloading the
35
+ * `dsh-opencode-go` route and its models from every picker without unloading the
36
36
  * plugin, so the settings page that owns this switch stays reachable to turn
37
37
  * it back on. Independent of the credential: a key present while this is
38
38
  * false registers nothing.
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { GenerateOptions, ImageAttachmentAccessResolver } from '@deepseek-ai/dsh-llm';
7
7
  import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
8
- import type { Context as PiContext } from '@earendil-works/pi-ai';
8
+ import type { Context as PiContext } from 'opencode-go-pi-ai';
9
9
  /** Inputs that bind deterministic request images to one current tool execution world. */
10
10
  export interface PiImageRequestContext {
11
11
  /** Durable provider that resolves request-image bytes and provider-owned host objects. */
@@ -8,7 +8,7 @@
8
8
  * @module dsh-opencode-go/conversion/replay
9
9
  */
10
10
  import type { Message, ReplayEnvelope } from '@deepseek-ai/dsh-llm';
11
- import type { Api, AssistantMessage } from '@earendil-works/pi-ai';
11
+ import type { Api, AssistantMessage } from 'opencode-go-pi-ai';
12
12
  /** Per-block half of the pi-ai replay envelope, one entry per content block. */
13
13
  export type PiAiReplayBlock = {
14
14
  type: 'text';
@@ -26,7 +26,10 @@ export interface PiAiReplayResponse {
26
26
  kind: 'pi-ai';
27
27
  version: 2;
28
28
  api: Api;
29
+ /** Requested DSH provider identity, matching the durable assistant source. */
29
30
  provider: string;
31
+ /** Native SDK provider when it differs from the DSH route. */
32
+ sdkProvider?: string;
30
33
  /** Requested model identity, matching the durable assistant source. */
31
34
  model: string;
32
35
  /** Provider-reported model; only Anthropic replays it as the native model (reported in `message.model`, not `message.responseModel`). */
@@ -43,9 +46,10 @@ export interface PiAiReplayResponse {
43
46
  * removes one.
44
47
  * @param message - completed native pi-ai assistant response.
45
48
  * @param requestedModel - request identity stored in the assistant source; defaults to the native model.
49
+ * @param requestedProvider - DSH route stored in the assistant source; defaults to the native provider.
46
50
  * @returns the versioned lossless-JSON replay projection.
47
51
  */
48
- export declare function toPiReplayState(message: AssistantMessage, requestedModel?: string): ReplayEnvelope;
52
+ export declare function toPiReplayState(message: AssistantMessage, requestedModel?: string, requestedProvider?: string): ReplayEnvelope;
49
53
  /**
50
54
  * Convert one durable Harness assistant message into pi-ai history.
51
55
  *
@@ -8,7 +8,7 @@
8
8
  * @module dsh-opencode-go/conversion/stream
9
9
  */
10
10
  import type { FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
11
- import type { AssistantMessage, AssistantMessageEvent, Usage as PiUsage } from '@earendil-works/pi-ai';
11
+ import type { AssistantMessage, AssistantMessageEvent, Usage as PiUsage } from 'opencode-go-pi-ai';
12
12
  /**
13
13
  * Map pi-ai usage (reasoning folded into output by pi-ai).
14
14
  * @param usage - cumulative usage from the terminal pi-ai event.
@@ -36,7 +36,8 @@ export declare function mapStopReason(message: AssistantMessage, contextWindow?:
36
36
  * @param callerSignal - caller cancellation state; an aborted caller makes any
37
37
  * in-band terminal error an aborted finish.
38
38
  * @param requestedModel - request model identity recorded for durable replay.
39
+ * @param requestedProvider - DSH provider identity recorded for durable replay.
39
40
  * @returns the harness chunks, ending with `usage` then `finish`; throws
40
41
  * `LlmError` (`STREAM_CLOSED`) if the source ends without a terminal event.
41
42
  */
42
- export declare function toStreamChunks(events: AsyncIterable<AssistantMessageEvent>, contextWindow?: number, callerSignal?: AbortSignal, requestedModel?: string): AsyncGenerator<StreamChunk>;
43
+ export declare function toStreamChunks(events: AsyncIterable<AssistantMessageEvent>, contextWindow?: number, callerSignal?: AbortSignal, requestedModel?: string, requestedProvider?: string): AsyncGenerator<StreamChunk>;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dedicated OpenCode Go adapter plugin. Registers one `opencode-go` route
2
+ * Dedicated OpenCode Go adapter plugin. Registers one `dsh-opencode-go` route
3
3
  * whose catalog follows the gateway's live model listing and models.dev
4
4
  * metadata, and installs the `llm-opencode-go` settings section: the Web UI
5
5
  * renders it as its own settings page where the API key and every knob are
@@ -24,7 +24,7 @@
24
24
  * The credential resolves per request through the credentials seam, falling
25
25
  * back to the process environment — the same reference semantics the generic
26
26
  * pi-ai adapter uses. The route registers atomically: if another adapter
27
- * family already owns `opencode-go` (a profile in `llm-pi-ai`, for example),
27
+ * mount already owns `dsh-opencode-go`,
28
28
  * the refusal is logged with the reason and everything else this plugin does
29
29
  * still works.
30
30
  *
@@ -3,9 +3,12 @@ export declare function transportFailure(error: unknown): string;
3
3
  /** Keep the endpoint useful in diagnostics without user information or query secrets. */
4
4
  export declare function diagnosticURL(raw: string): string;
5
5
  /** The caller's signal covers both attempts, the retry delay, and all response reads. */
6
- export declare function fetchJsonResponse(url: string, init: RequestInit, maxBytes: number): Promise<{
6
+ export declare function fetchJsonResponse(url: string, init: RequestInit, maxBytes: number, encoding?: 'identity' | 'gzip'): Promise<{
7
7
  response: Response;
8
8
  body: unknown;
9
9
  }>;
10
- /** Fetch handles HTTP decoding; this reader bounds the bytes it delivers. */
11
- export declare function readJsonResponse(response: Response, maxBytes: number): Promise<unknown>;
10
+ /** Fetch normally decodes HTTP; only negotiated gzip may recover a raw gzip body (#7). */
11
+ export declare function readJsonResponse(response: Response, maxBytes: number, options?: {
12
+ gzip?: boolean;
13
+ signal?: AbortSignal | null;
14
+ }): Promise<unknown>;
@@ -1,5 +1,5 @@
1
1
  /** Convert OpenCode's online models.dev metadata into the SDK's three wire protocols. */
2
- import type { Api, Model } from '@earendil-works/pi-ai';
2
+ import type { Api, Model } from 'opencode-go-pi-ai';
3
3
  import { type GoModel } from './models-contract.ts';
4
4
  export declare const MODEL_METADATA_URL = "https://models.dev/api.json";
5
5
  /** Lifecycle and capability data the online catalog adds to a gateway listing. */
@@ -0,0 +1,4 @@
1
+ /** Public DSH route and the private SDK transport have separate identities. */
2
+ export declare const PROVIDER_ID = "dsh-opencode-go";
3
+ export declare const DISPLAY_NAME = "DSH OpenCode Go";
4
+ export declare const SDK_PROVIDER_ID = "opencode-go";
@@ -0,0 +1,10 @@
1
+ /** Gateway reasoning controls shared by live metadata, fallbacks and requests. */
2
+ import type { Api, Model, ModelThinkingLevel, ThinkingLevelMap } from 'opencode-go-pi-ai';
3
+ export declare const THINKING_LEVELS: readonly ModelThinkingLevel[];
4
+ /** Formats that explicitly disable thinking when no SDK effort is supplied. */
5
+ export declare const NATIVE_THINKING_FLAGS: ReadonlySet<string>;
6
+ export declare function unsupportedThinkingLevels(): ThinkingLevelMap;
7
+ /** Exact model/protocol rules verified against OpenCode Go, never inherited by a family. */
8
+ export declare function withGatewayReasoning(model: Model<Api>): Model<Api>;
9
+ /** An unset OpenAI effort preserves the provider default; explicit Off retains its wire value. */
10
+ export declare function withRequestReasoning(model: Model<Api>, level: ModelThinkingLevel | undefined): Model<Api>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "OpenCode Go model provider and settings UI for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -89,7 +89,7 @@
89
89
  "dependencies": {
90
90
  "@deepseek-ai/dsh-brand": "0.1.6-alpha.1",
91
91
  "@deepseek-ai/schemastery": "3.18.3",
92
- "@earendil-works/pi-ai": "0.85.1"
92
+ "opencode-go-pi-ai": "npm:@earendil-works/pi-ai@0.87.1"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@deepseek-ai/cordis": "4.0.2",