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/README.en.md +14 -8
- package/README.md +14 -8
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/docs/development.md +13 -3
- package/docs/independent-package.md +1 -1
- package/docs/verification.md +67 -0
- package/examples/headless.patch.yml +1 -1
- package/lib/client.js +66 -49
- package/lib/index.js +106 -45
- package/lib/types/catalog.d.ts +2 -3
- package/lib/types/client/ModelEditor.d.ts +4 -1
- package/lib/types/client/locales.d.ts +0 -1
- package/lib/types/config.d.ts +1 -1
- package/lib/types/conversion/context.d.ts +1 -1
- package/lib/types/conversion/replay.d.ts +6 -2
- package/lib/types/conversion/stream.d.ts +3 -2
- package/lib/types/index.d.ts +2 -2
- package/lib/types/json-response.d.ts +6 -3
- package/lib/types/model-metadata.d.ts +1 -1
- package/lib/types/provider-identity.d.ts +4 -0
- package/lib/types/reasoning.d.ts +10 -0
- package/package.json +2 -2
- package/docs/fork-review-2026-09-25.md +0 -191
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 "
|
|
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:
|
|
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 "
|
|
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 "
|
|
712
|
-
import { getBuiltinModels } from "
|
|
713
|
-
import { anthropicMessagesApi } from "
|
|
714
|
-
import { openAICompletionsApi } from "
|
|
715
|
-
import { openAIResponsesApi } from "
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
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 (
|
|
864
|
+
if (THINKING_LEVELS.includes(level)) map[level] = String(value);
|
|
844
865
|
}
|
|
845
866
|
}
|
|
846
|
-
|
|
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",
|
|
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
|
|
1002
|
-
const
|
|
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(
|
|
1154
|
+
return new Map(getBuiltinModels(SDK_PROVIDER_ID).map((model) => [model.id, withGatewayReasoning({
|
|
1093
1155
|
...model,
|
|
1094
|
-
provider:
|
|
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:
|
|
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 &&
|
|
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,
|
package/lib/types/catalog.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { Api, Model, Provider } from '
|
|
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
|
|
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;
|
package/lib/types/config.d.ts
CHANGED
|
@@ -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 '
|
|
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 '
|
|
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 '
|
|
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>;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
11
|
-
export declare function readJsonResponse(response: Response, maxBytes: number
|
|
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 '
|
|
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,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.
|
|
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
|
-
"
|
|
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",
|