@dan-ai-studio/dshopencodego 0.1.21 → 0.1.23
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 +2 -1
- package/README.md +2 -1
- package/lib/client.js +299 -10
- package/lib/index.js +156 -10
- package/lib/types/catalog/constants.d.ts +3 -1
- package/lib/types/catalog/constants.js +3 -1
- package/lib/types/catalog/index.d.ts +1 -1
- package/lib/types/catalog/index.js +1 -1
- package/lib/types/conversion/stream.js +6 -0
- package/lib/types/go-limits.js +1 -1
- package/lib/types/index.d.ts +3 -3
- package/lib/types/index.js +7 -4
- package/lib/types/remotes.js +2 -1
- package/lib/types/session-header.d.ts +5 -3
- package/lib/types/session-header.js +8 -4
- package/lib/types/update/check.d.ts +74 -0
- package/lib/types/update/check.js +132 -0
- package/lib/types/update/contract.d.ts +23 -0
- package/lib/types/update/contract.js +52 -0
- package/lib/types/update/index.d.ts +12 -0
- package/lib/types/update/index.js +10 -0
- package/lib/types/update/service.d.ts +28 -0
- package/lib/types/update/service.js +28 -0
- package/lib/types/update/version.d.ts +14 -0
- package/lib/types/update/version.js +16 -0
- package/package.json +26 -26
package/lib/index.js
CHANGED
|
@@ -73,7 +73,8 @@ import { attributionHeaders as attributionHeaders3, LlmError as LlmError4 } from
|
|
|
73
73
|
// src/catalog/constants.ts
|
|
74
74
|
var PROVIDER_ID = "opencode-go";
|
|
75
75
|
var DISPLAY_NAME = "OpenCode Go";
|
|
76
|
-
var DEFAULT_BASE_URL = "https://opencode.ai/
|
|
76
|
+
var DEFAULT_BASE_URL = "https://opencode.ai/inference/go/v1";
|
|
77
|
+
var CONSOLE_BASE_URL = "https://opencode.ai/inference/v1";
|
|
77
78
|
var MODEL_METADATA_URL = "https://models.dev/api.json";
|
|
78
79
|
var MODEL_METADATA_PROVIDER = "opencode-go";
|
|
79
80
|
var METADATA_FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -1177,6 +1178,7 @@ var ACCOUNT_QUOTA_CODE = "ACCOUNT_QUOTA";
|
|
|
1177
1178
|
function classifyPiAiError(message) {
|
|
1178
1179
|
if (/\b(?:401|403)\b/.test(message)) return "AUTH";
|
|
1179
1180
|
if (isQuotaExceededError(message)) return ACCOUNT_QUOTA_CODE;
|
|
1181
|
+
if (/server_error/i.test(message)) return "SERVER";
|
|
1180
1182
|
if (/\b429\b|rate.?limit/i.test(message)) return "RATE_LIMIT";
|
|
1181
1183
|
if (/\b413\b|payload too large|request body too large|length limit exceeded/i.test(message)) return "INVALID_REQUEST";
|
|
1182
1184
|
if (/\b400\b|invalid.?request/i.test(message)) return "INVALID_REQUEST";
|
|
@@ -1347,7 +1349,7 @@ var SEED_QUOTAS = {
|
|
|
1347
1349
|
"deepseek-v4-flash-vision-exp": { monthlyUsd: 15, monthlyRequests: 32500 },
|
|
1348
1350
|
"hy4-preview": { monthlyUsd: 30, monthlyRequests: 6770 },
|
|
1349
1351
|
"hy3": { monthlyUsd: 60, monthlyRequests: 21500 },
|
|
1350
|
-
|
|
1352
|
+
// 注意:space-bunny-free 并不在 Go 端点支持的 37 个模型列表中(请求会返回 400 错误),已依据 R-B4 予以剔除
|
|
1351
1353
|
"grok-4.7": { monthlyUsd: 15, monthlyRequests: 845 },
|
|
1352
1354
|
"grok-4.6": { monthlyUsd: 15, monthlyRequests: 845 },
|
|
1353
1355
|
"gpt-6-luna": { monthlyUsd: 15, monthlyRequests: 21130 },
|
|
@@ -1396,12 +1398,15 @@ function sortModels(models, now = Date.now()) {
|
|
|
1396
1398
|
import { randomUUID } from "node:crypto";
|
|
1397
1399
|
import { attributionHeaders as attributionHeaders4 } from "@deepseek-ai/dsh-llm";
|
|
1398
1400
|
var SESSION_HEADER = "x-opencode-session";
|
|
1401
|
+
var SESSION_AFFINITY_HEADER = "x-session-affinity";
|
|
1399
1402
|
function opencodeSessionValue(sessionId) {
|
|
1400
1403
|
return sessionId !== void 0 && sessionId.length > 0 ? sessionId : randomUUID();
|
|
1401
1404
|
}
|
|
1402
1405
|
function providerHeaders(sessionId) {
|
|
1406
|
+
const session = opencodeSessionValue(sessionId);
|
|
1403
1407
|
return {
|
|
1404
|
-
[SESSION_HEADER]:
|
|
1408
|
+
[SESSION_HEADER]: session,
|
|
1409
|
+
[SESSION_AFFINITY_HEADER]: session,
|
|
1405
1410
|
...attributionHeaders4()
|
|
1406
1411
|
};
|
|
1407
1412
|
}
|
|
@@ -1762,6 +1767,44 @@ var catalogRemote = {
|
|
|
1762
1767
|
]
|
|
1763
1768
|
};
|
|
1764
1769
|
|
|
1770
|
+
// src/update/contract.ts
|
|
1771
|
+
function parseUpdateStatus(value) {
|
|
1772
|
+
if (value === null || typeof value !== "object") throw new Error("invalid update reading");
|
|
1773
|
+
const row = value;
|
|
1774
|
+
if (typeof row["current"] !== "string") throw new Error("invalid update reading");
|
|
1775
|
+
if (row["latest"] !== void 0 && typeof row["latest"] !== "string") throw new Error("invalid update reading");
|
|
1776
|
+
if (typeof row["available"] !== "boolean") throw new Error("invalid update reading");
|
|
1777
|
+
if (typeof row["checkedAt"] !== "number" || !Number.isFinite(row["checkedAt"])) {
|
|
1778
|
+
throw new Error("invalid update reading");
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
current: row["current"],
|
|
1782
|
+
latest: typeof row["latest"] === "string" ? row["latest"] : void 0,
|
|
1783
|
+
available: row["available"],
|
|
1784
|
+
checkedAt: row["checkedAt"]
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
var codec2 = (typeSymbol, parse) => ({
|
|
1788
|
+
mode: "strict",
|
|
1789
|
+
typeSymbol,
|
|
1790
|
+
schema: { parse },
|
|
1791
|
+
create: () => ({ parse })
|
|
1792
|
+
});
|
|
1793
|
+
var updateRemote = {
|
|
1794
|
+
package: "@dan-ai-studio/dshopencodego",
|
|
1795
|
+
descriptors: [
|
|
1796
|
+
{
|
|
1797
|
+
id: "@dan-ai-studio/dshopencodego#opencodeGoUpdate/readUpdate",
|
|
1798
|
+
service: "opencodeGoUpdate",
|
|
1799
|
+
namespace: "opencodeGoUpdate",
|
|
1800
|
+
method: "readUpdate",
|
|
1801
|
+
invocation: { kind: "direct" },
|
|
1802
|
+
parameters: [],
|
|
1803
|
+
result: codec2("@dan-ai-studio/dshopencodego#UpdateStatus", parseUpdateStatus)
|
|
1804
|
+
}
|
|
1805
|
+
]
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1765
1808
|
// src/usage/contract.ts
|
|
1766
1809
|
function parseWindow(value, key) {
|
|
1767
1810
|
if (value === null || typeof value !== "object") throw new Error(`invalid usage window "${key}"`);
|
|
@@ -1816,7 +1859,7 @@ function parseGoMeter(value) {
|
|
|
1816
1859
|
})
|
|
1817
1860
|
};
|
|
1818
1861
|
}
|
|
1819
|
-
var
|
|
1862
|
+
var codec3 = (typeSymbol, parse) => ({
|
|
1820
1863
|
mode: "strict",
|
|
1821
1864
|
typeSymbol,
|
|
1822
1865
|
schema: { parse },
|
|
@@ -1832,7 +1875,7 @@ var usageRemote = {
|
|
|
1832
1875
|
method: "readWindows",
|
|
1833
1876
|
invocation: { kind: "direct" },
|
|
1834
1877
|
parameters: [],
|
|
1835
|
-
result:
|
|
1878
|
+
result: codec3("@dan-ai-studio/dshopencodego#GoUsageWindows", parseUsageWindows)
|
|
1836
1879
|
},
|
|
1837
1880
|
{
|
|
1838
1881
|
id: "@dan-ai-studio/dshopencodego#opencodeGoUsage/readMeter",
|
|
@@ -1841,7 +1884,7 @@ var usageRemote = {
|
|
|
1841
1884
|
method: "readMeter",
|
|
1842
1885
|
invocation: { kind: "direct" },
|
|
1843
1886
|
parameters: [],
|
|
1844
|
-
result:
|
|
1887
|
+
result: codec3("@dan-ai-studio/dshopencodego#GoMeter", parseGoMeter)
|
|
1845
1888
|
}
|
|
1846
1889
|
]
|
|
1847
1890
|
};
|
|
@@ -1849,7 +1892,7 @@ var usageRemote = {
|
|
|
1849
1892
|
// src/remotes.ts
|
|
1850
1893
|
var contribution = {
|
|
1851
1894
|
package: usageRemote.package,
|
|
1852
|
-
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors]
|
|
1895
|
+
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors, ...updateRemote.descriptors]
|
|
1853
1896
|
};
|
|
1854
1897
|
function registerRemotes(ctx) {
|
|
1855
1898
|
ctx.inject(["typert"], (scope) => {
|
|
@@ -1944,6 +1987,106 @@ var OpencodeGoCatalogService = class extends TypertRemoteService {
|
|
|
1944
1987
|
}
|
|
1945
1988
|
};
|
|
1946
1989
|
|
|
1990
|
+
// src/update/check.ts
|
|
1991
|
+
var PACKAGE_NAME = "@dan-ai-studio/dshopencodego";
|
|
1992
|
+
var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
1993
|
+
var CHECK_CACHE_MS = 6 * 60 * 60 * 1e3;
|
|
1994
|
+
var CHECK_TIMEOUT_MS = 1e4;
|
|
1995
|
+
function parseVersion(value) {
|
|
1996
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+[0-9a-zA-Z.-]+)?$/.exec(value.trim());
|
|
1997
|
+
if (match === void 0 || match === null) return void 0;
|
|
1998
|
+
const [, major, minor, patch, prerelease] = match;
|
|
1999
|
+
return {
|
|
2000
|
+
core: [Number(major), Number(minor), Number(patch)],
|
|
2001
|
+
prerelease: prerelease === void 0 ? void 0 : prerelease.split(".")
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
function compareVersions(left, right) {
|
|
2005
|
+
const ours = parseVersion(left);
|
|
2006
|
+
const theirs = parseVersion(right);
|
|
2007
|
+
if (ours === void 0 || theirs === void 0) throw new Error(`dshopencodego: cannot compare versions "${left}" and "${right}"`);
|
|
2008
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2009
|
+
const difference = (ours.core[index] ?? 0) - (theirs.core[index] ?? 0);
|
|
2010
|
+
if (difference !== 0) return difference;
|
|
2011
|
+
}
|
|
2012
|
+
if (ours.prerelease === void 0 && theirs.prerelease === void 0) return 0;
|
|
2013
|
+
if (ours.prerelease === void 0) return 1;
|
|
2014
|
+
if (theirs.prerelease === void 0) return -1;
|
|
2015
|
+
const width = Math.max(ours.prerelease.length, theirs.prerelease.length);
|
|
2016
|
+
for (let index = 0; index < width; index += 1) {
|
|
2017
|
+
const oursPart = ours.prerelease[index];
|
|
2018
|
+
const theirsPart = theirs.prerelease[index];
|
|
2019
|
+
if (oursPart === void 0) return -1;
|
|
2020
|
+
if (theirsPart === void 0) return 1;
|
|
2021
|
+
if (oursPart !== theirsPart) return oursPart < theirsPart ? -1 : 1;
|
|
2022
|
+
}
|
|
2023
|
+
return 0;
|
|
2024
|
+
}
|
|
2025
|
+
function isUpdateAvailable(current, latest) {
|
|
2026
|
+
if (latest === void 0) return false;
|
|
2027
|
+
try {
|
|
2028
|
+
return compareVersions(latest, current) > 0;
|
|
2029
|
+
} catch {
|
|
2030
|
+
return false;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
function parseLatestVersion(body) {
|
|
2034
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
2035
|
+
const version = body["version"];
|
|
2036
|
+
return typeof version === "string" && parseVersion(version) !== void 0 ? version : void 0;
|
|
2037
|
+
}
|
|
2038
|
+
function createUpdateChecker(deps) {
|
|
2039
|
+
const now = deps.now ?? Date.now;
|
|
2040
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
2041
|
+
let cached;
|
|
2042
|
+
let inflight;
|
|
2043
|
+
async function lookup() {
|
|
2044
|
+
const checkedAt = now();
|
|
2045
|
+
try {
|
|
2046
|
+
const response = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(CHECK_TIMEOUT_MS) });
|
|
2047
|
+
if (!response.ok) throw new Error(`registry answered ${response.status}`);
|
|
2048
|
+
const latest = parseLatestVersion(await response.json());
|
|
2049
|
+
return {
|
|
2050
|
+
current: deps.currentVersion,
|
|
2051
|
+
latest,
|
|
2052
|
+
available: isUpdateAvailable(deps.currentVersion, latest),
|
|
2053
|
+
checkedAt
|
|
2054
|
+
};
|
|
2055
|
+
} catch {
|
|
2056
|
+
return { current: deps.currentVersion, latest: void 0, available: false, checkedAt };
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return {
|
|
2060
|
+
check(force = false) {
|
|
2061
|
+
if (!force && cached !== void 0 && now() - cached.checkedAt < CHECK_CACHE_MS) return Promise.resolve(cached);
|
|
2062
|
+
inflight ??= lookup().then((status) => {
|
|
2063
|
+
cached = status;
|
|
2064
|
+
inflight = void 0;
|
|
2065
|
+
return status;
|
|
2066
|
+
});
|
|
2067
|
+
return inflight;
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// src/update/version.ts
|
|
2073
|
+
var PLUGIN_VERSION = true ? "0.1.23" : "0.0.0-dev";
|
|
2074
|
+
|
|
2075
|
+
// src/update/service.ts
|
|
2076
|
+
import { TypertRemoteService as TypertRemoteService2 } from "@deepseek-ai/dsh-typert-protocol";
|
|
2077
|
+
var OpencodeGoUpdateService = class extends TypertRemoteService2 {
|
|
2078
|
+
check;
|
|
2079
|
+
constructor(ctx, options) {
|
|
2080
|
+
super(ctx, "opencodeGoUpdate");
|
|
2081
|
+
const checker = createUpdateChecker({ currentVersion: options.currentVersion });
|
|
2082
|
+
this.check = (force) => checker.check(force);
|
|
2083
|
+
}
|
|
2084
|
+
/** Whether a newer release is published; `force` bypasses the cache. */
|
|
2085
|
+
readUpdate(force) {
|
|
2086
|
+
return this.check(force);
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
|
|
1947
2090
|
// src/usage/meter.ts
|
|
1948
2091
|
var EMPTY = {
|
|
1949
2092
|
calls: 0,
|
|
@@ -2049,7 +2192,7 @@ async function readUsageWindows(options) {
|
|
|
2049
2192
|
|
|
2050
2193
|
// src/usage/service.ts
|
|
2051
2194
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2052
|
-
import { RemoteError as RemoteError2, TypertRemoteService as
|
|
2195
|
+
import { RemoteError as RemoteError2, TypertRemoteService as TypertRemoteService3 } from "@deepseek-ai/dsh-typert-protocol";
|
|
2053
2196
|
import { LlmError as LlmError11 } from "@deepseek-ai/dsh-llm";
|
|
2054
2197
|
function usageFailure(error, source) {
|
|
2055
2198
|
const missing = error instanceof LlmError11 && error.code === "MISSING_CREDENTIAL";
|
|
@@ -2060,7 +2203,7 @@ function usageFailure(error, source) {
|
|
|
2060
2203
|
{ cause: error }
|
|
2061
2204
|
);
|
|
2062
2205
|
}
|
|
2063
|
-
var OpencodeGoUsageService = class extends
|
|
2206
|
+
var OpencodeGoUsageService = class extends TypertRemoteService3 {
|
|
2064
2207
|
identity;
|
|
2065
2208
|
options;
|
|
2066
2209
|
constructor(ctx, options) {
|
|
@@ -2153,6 +2296,7 @@ function apply(ctx, raw) {
|
|
|
2153
2296
|
catalog: () => adapter.catalogOf(current()),
|
|
2154
2297
|
visibility: () => current().modelVisibility
|
|
2155
2298
|
});
|
|
2299
|
+
ctx.plugin(OpencodeGoUpdateService, { currentVersion: PLUGIN_VERSION });
|
|
2156
2300
|
let registration;
|
|
2157
2301
|
let visibilityFacts = JSON.stringify(current().modelVisibility);
|
|
2158
2302
|
const applyRoute = (credentialConfigured) => {
|
|
@@ -2195,7 +2339,7 @@ function apply(ctx, raw) {
|
|
|
2195
2339
|
const undiscover = ctx.llm.registerModelDiscovery(name, async (request) => {
|
|
2196
2340
|
if (request.provider !== PROVIDER_ID && !(request.baseURL ?? "").includes("opencode.ai")) {
|
|
2197
2341
|
throw new LlmError12(
|
|
2198
|
-
"dshopencodego discovers only OpenCode
|
|
2342
|
+
"dshopencodego discovers only OpenCode Go/Console endpoints; enter this provider's models by hand",
|
|
2199
2343
|
"DISCOVERY_UNSUPPORTED"
|
|
2200
2344
|
);
|
|
2201
2345
|
}
|
|
@@ -2220,6 +2364,7 @@ function apply(ctx, raw) {
|
|
|
2220
2364
|
ctx.logger.info(`dshopencodego: route "${PROVIDER_ID}" registered as ${DISPLAY_NAME}`);
|
|
2221
2365
|
}
|
|
2222
2366
|
export {
|
|
2367
|
+
CONSOLE_BASE_URL,
|
|
2223
2368
|
Config,
|
|
2224
2369
|
DEFAULT_API_KEY_ENV,
|
|
2225
2370
|
DEFAULT_BASE_URL,
|
|
@@ -2228,6 +2373,7 @@ export {
|
|
|
2228
2373
|
OpencodeGoCatalog,
|
|
2229
2374
|
PROVIDER_ID,
|
|
2230
2375
|
PlainConfig,
|
|
2376
|
+
SESSION_AFFINITY_HEADER,
|
|
2231
2377
|
SESSION_HEADER,
|
|
2232
2378
|
apply,
|
|
2233
2379
|
assertBaseURL,
|
|
@@ -4,7 +4,9 @@ export declare const PROVIDER_ID = "opencode-go";
|
|
|
4
4
|
/** Selector label for the route. */
|
|
5
5
|
export declare const DISPLAY_NAME = "OpenCode Go";
|
|
6
6
|
/** Gateway base URL; `/models` and `/usage` hang off it. */
|
|
7
|
-
export declare const DEFAULT_BASE_URL = "https://opencode.ai/
|
|
7
|
+
export declare const DEFAULT_BASE_URL = "https://opencode.ai/inference/go/v1";
|
|
8
|
+
/** Console base URL (on-demand / free models, pay-as-you-go). */
|
|
9
|
+
export declare const CONSOLE_BASE_URL = "https://opencode.ai/inference/v1";
|
|
8
10
|
/** Online model metadata: capability, lifecycle, and pricing per model id. */
|
|
9
11
|
export declare const MODEL_METADATA_URL = "https://models.dev/api.json";
|
|
10
12
|
/** The models.dev provider key this plugin reads. */
|
|
@@ -4,7 +4,9 @@ export const PROVIDER_ID = 'opencode-go';
|
|
|
4
4
|
/** Selector label for the route. */
|
|
5
5
|
export const DISPLAY_NAME = 'OpenCode Go';
|
|
6
6
|
/** Gateway base URL; `/models` and `/usage` hang off it. */
|
|
7
|
-
export const DEFAULT_BASE_URL = 'https://opencode.ai/
|
|
7
|
+
export const DEFAULT_BASE_URL = 'https://opencode.ai/inference/go/v1';
|
|
8
|
+
/** Console base URL (on-demand / free models, pay-as-you-go). */
|
|
9
|
+
export const CONSOLE_BASE_URL = 'https://opencode.ai/inference/v1';
|
|
8
10
|
/** Online model metadata: capability, lifecycle, and pricing per model id. */
|
|
9
11
|
export const MODEL_METADATA_URL = 'https://models.dev/api.json';
|
|
10
12
|
/** The models.dev provider key this plugin reads. */
|
|
@@ -23,7 +23,7 @@ import type { LlmDiscoveredModel } from '@deepseek-ai/dsh-llm';
|
|
|
23
23
|
import type { GoDoc } from './go-doc.ts';
|
|
24
24
|
import type { ModelDefaults, ModelFacts } from './metadata.ts';
|
|
25
25
|
import type { GoQuota } from '../go-limits.ts';
|
|
26
|
-
export { PROVIDER_ID, DISPLAY_NAME, DEFAULT_BASE_URL } from './constants.ts';
|
|
26
|
+
export { PROVIDER_ID, DISPLAY_NAME, DEFAULT_BASE_URL, CONSOLE_BASE_URL } from './constants.ts';
|
|
27
27
|
export { readModelIds, fetchModelIds } from './gateway.ts';
|
|
28
28
|
export { readOnlineMetadata, toPiModel, modelBaseURL } from './metadata.ts';
|
|
29
29
|
export type { ModelFacts, OnlineMetadata, ModelDefaults } from './metadata.ts';
|
|
@@ -29,7 +29,7 @@ import { fetchModelIds } from "./gateway.js";
|
|
|
29
29
|
import { fetchGoDocument, GO_DOC_URLS } from "./go-doc.js";
|
|
30
30
|
import { readBoundedJson } from "./json-response.js";
|
|
31
31
|
import { readOnlineMetadata, toPiModel } from "./metadata.js";
|
|
32
|
-
export { PROVIDER_ID, DISPLAY_NAME, DEFAULT_BASE_URL } from "./constants.js";
|
|
32
|
+
export { PROVIDER_ID, DISPLAY_NAME, DEFAULT_BASE_URL, CONSOLE_BASE_URL } from "./constants.js";
|
|
33
33
|
export { readModelIds, fetchModelIds } from "./gateway.js";
|
|
34
34
|
export { readOnlineMetadata, toPiModel, modelBaseURL } from "./metadata.js";
|
|
35
35
|
export { decideProtocol, inferProtocol, protocolOfNpm } from "./protocol.js";
|
|
@@ -51,6 +51,12 @@ export function classifyPiAiError(message) {
|
|
|
51
51
|
return 'AUTH';
|
|
52
52
|
if (isQuotaExceededError(message))
|
|
53
53
|
return ACCOUNT_QUOTA_CODE;
|
|
54
|
+
// A 429 typed as `server_error` is the gateway reporting an unavailable
|
|
55
|
+
// upstream, not the caller exceeding a rate limit: the remedy is waiting for
|
|
56
|
+
// the endpoint to recover rather than slowing down, so it classifies as
|
|
57
|
+
// SERVER even though the status code alone reads as a rate limit.
|
|
58
|
+
if (/server_error/i.test(message))
|
|
59
|
+
return 'SERVER';
|
|
54
60
|
if (/\b429\b|rate.?limit/i.test(message))
|
|
55
61
|
return 'RATE_LIMIT';
|
|
56
62
|
if (/\b413\b|payload too large|request body too large|length limit exceeded/i.test(message))
|
package/lib/types/go-limits.js
CHANGED
|
@@ -58,7 +58,7 @@ export const SEED_QUOTAS = {
|
|
|
58
58
|
'deepseek-v4-flash-vision-exp': { monthlyUsd: 15, monthlyRequests: 32_500 },
|
|
59
59
|
'hy4-preview': { monthlyUsd: 30, monthlyRequests: 6_770 },
|
|
60
60
|
'hy3': { monthlyUsd: 60, monthlyRequests: 21_500 },
|
|
61
|
-
|
|
61
|
+
// 注意:space-bunny-free 并不在 Go 端点支持的 37 个模型列表中(请求会返回 400 错误),已依据 R-B4 予以剔除
|
|
62
62
|
'grok-4.7': { monthlyUsd: 15, monthlyRequests: 845 },
|
|
63
63
|
'grok-4.6': { monthlyUsd: 15, monthlyRequests: 845 },
|
|
64
64
|
'gpt-6-luna': { monthlyUsd: 15, monthlyRequests: 21_130 },
|
package/lib/types/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* config:
|
|
20
20
|
* enabled: true # false withdraws the route only
|
|
21
21
|
* apiKeyEnv: OPENCODE_GO_API_KEY # default
|
|
22
|
-
* baseURL: https://opencode.ai/
|
|
22
|
+
* baseURL: https://opencode.ai/inference/go/v1 # default
|
|
23
23
|
* refreshMinutes: 60 # live catalog TTL
|
|
24
24
|
* modelProtocols: # last-resort protocol override
|
|
25
25
|
* some-model: openai-responses
|
|
@@ -35,10 +35,10 @@ declare module '@deepseek-ai/cordis' {
|
|
|
35
35
|
}
|
|
36
36
|
export { OpencodeGoAdapter } from './adapter.ts';
|
|
37
37
|
export type { OpencodeGoAdapterOptions, OpencodeGoImageAccess } from './adapter.ts';
|
|
38
|
-
export { DEFAULT_BASE_URL, DISPLAY_NAME, PROVIDER_ID, OpencodeGoCatalog, discoverCatalogModels } from './catalog/index.ts';
|
|
38
|
+
export { DEFAULT_BASE_URL, CONSOLE_BASE_URL, DISPLAY_NAME, PROVIDER_ID, OpencodeGoCatalog, discoverCatalogModels } from './catalog/index.ts';
|
|
39
39
|
export { Config, PlainConfig, assertBaseURL, DEFAULT_API_KEY_ENV } from './config.ts';
|
|
40
40
|
export type { OpencodeGoConfig } from './config.ts';
|
|
41
|
-
export { SESSION_HEADER, opencodeSessionValue, providerHeaders } from './session-header.ts';
|
|
41
|
+
export { SESSION_HEADER, SESSION_AFFINITY_HEADER, opencodeSessionValue, providerHeaders } from './session-header.ts';
|
|
42
42
|
export { isModelEnabled, sortModels } from './models.ts';
|
|
43
43
|
export type { ModelSummary } from './models.ts';
|
|
44
44
|
export declare const name = "dshopencodego";
|
package/lib/types/index.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* config:
|
|
20
20
|
* enabled: true # false withdraws the route only
|
|
21
21
|
* apiKeyEnv: OPENCODE_GO_API_KEY # default
|
|
22
|
-
* baseURL: https://opencode.ai/
|
|
22
|
+
* baseURL: https://opencode.ai/inference/go/v1 # default
|
|
23
23
|
* refreshMinutes: 60 # live catalog TTL
|
|
24
24
|
* modelProtocols: # last-resort protocol override
|
|
25
25
|
* some-model: openai-responses
|
|
@@ -36,11 +36,13 @@ import { DISPLAY_NAME, PROVIDER_ID, discoverCatalogModels } from "./catalog/inde
|
|
|
36
36
|
import { assertBaseURL, PlainConfig, readConfig } from "./config.js";
|
|
37
37
|
import { registerRemotes } from "./remotes.js";
|
|
38
38
|
import { OpencodeGoCatalogService } from "./catalog/service.js";
|
|
39
|
+
import { OpencodeGoUpdateService } from "./update/index.js";
|
|
40
|
+
import { PLUGIN_VERSION } from "./update/index.js";
|
|
39
41
|
import { OpencodeGoUsageService, UsageMeter } from "./usage/index.js";
|
|
40
42
|
export { OpencodeGoAdapter } from "./adapter.js";
|
|
41
|
-
export { DEFAULT_BASE_URL, DISPLAY_NAME, PROVIDER_ID, OpencodeGoCatalog, discoverCatalogModels } from "./catalog/index.js";
|
|
43
|
+
export { DEFAULT_BASE_URL, CONSOLE_BASE_URL, DISPLAY_NAME, PROVIDER_ID, OpencodeGoCatalog, discoverCatalogModels } from "./catalog/index.js";
|
|
42
44
|
export { Config, PlainConfig, assertBaseURL, DEFAULT_API_KEY_ENV } from "./config.js";
|
|
43
|
-
export { SESSION_HEADER, opencodeSessionValue, providerHeaders } from "./session-header.js";
|
|
45
|
+
export { SESSION_HEADER, SESSION_AFFINITY_HEADER, opencodeSessionValue, providerHeaders } from "./session-header.js";
|
|
44
46
|
export { isModelEnabled, sortModels } from "./models.js";
|
|
45
47
|
export const name = 'dshopencodego';
|
|
46
48
|
export const inject = ['llm'];
|
|
@@ -107,6 +109,7 @@ export function apply(ctx, raw) {
|
|
|
107
109
|
catalog: () => adapter.catalogOf(current()),
|
|
108
110
|
visibility: () => current().modelVisibility,
|
|
109
111
|
});
|
|
112
|
+
ctx.plugin(OpencodeGoUpdateService, { currentVersion: PLUGIN_VERSION });
|
|
110
113
|
let registration;
|
|
111
114
|
let visibilityFacts = JSON.stringify(current().modelVisibility);
|
|
112
115
|
/**
|
|
@@ -166,7 +169,7 @@ export function apply(ctx, raw) {
|
|
|
166
169
|
syncRoute();
|
|
167
170
|
const undiscover = ctx.llm.registerModelDiscovery(name, async (request) => {
|
|
168
171
|
if (request.provider !== PROVIDER_ID && !(request.baseURL ?? '').includes('opencode.ai')) {
|
|
169
|
-
throw new LlmError('dshopencodego discovers only OpenCode
|
|
172
|
+
throw new LlmError('dshopencodego discovers only OpenCode Go/Console endpoints; enter this provider\'s models by hand', 'DISCOVERY_UNSUPPORTED');
|
|
170
173
|
}
|
|
171
174
|
return discoverCatalogModels(adapter.catalogOf(current()));
|
|
172
175
|
});
|
package/lib/types/remotes.js
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
* @module @dan-ai-studio/dshopencodego/remotes
|
|
9
9
|
*/
|
|
10
10
|
import { catalogRemote } from "./catalog/contract.js";
|
|
11
|
+
import { updateRemote } from "./update/contract.js";
|
|
11
12
|
import { usageRemote } from "./usage/contract.js";
|
|
12
13
|
/** Every Remote method this package owns, mounted as one contribution. */
|
|
13
14
|
const contribution = {
|
|
14
15
|
package: usageRemote.package,
|
|
15
|
-
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors],
|
|
16
|
+
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors, ...updateRemote.descriptors],
|
|
16
17
|
};
|
|
17
18
|
/** Register this package's Remote contribution for the mount's lifetime. */
|
|
18
19
|
export function registerRemotes(ctx) {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
/** Header name the gateway requires. */
|
|
19
19
|
export declare const SESSION_HEADER = "x-opencode-session";
|
|
20
|
+
/** Session affinity header for gateway routing and prompt caching. */
|
|
21
|
+
export declare const SESSION_AFFINITY_HEADER = "x-session-affinity";
|
|
20
22
|
/**
|
|
21
23
|
* The header value for one request.
|
|
22
24
|
* @param sessionId - the request's session id, if it names one.
|
|
@@ -27,9 +29,9 @@ export declare function opencodeSessionValue(sessionId: string | undefined): str
|
|
|
27
29
|
* Every header one provider request carries.
|
|
28
30
|
*
|
|
29
31
|
* The attribution `user-agent` is Harness-owned and always present; the session
|
|
30
|
-
*
|
|
31
|
-
* never win, because the gateway's routing decision must follow
|
|
32
|
-
* not a deployment string.
|
|
32
|
+
* and affinity headers are added on top of it. A caller-supplied header of the
|
|
33
|
+
* same name can never win, because the gateway's routing decision must follow
|
|
34
|
+
* the session, not a deployment string.
|
|
33
35
|
* @param sessionId - the request's session id, if it names one.
|
|
34
36
|
* @returns headers for the pi-ai request options.
|
|
35
37
|
*/
|
|
@@ -19,6 +19,8 @@ import { randomUUID } from 'node:crypto';
|
|
|
19
19
|
import { attributionHeaders } from '@deepseek-ai/dsh-llm';
|
|
20
20
|
/** Header name the gateway requires. */
|
|
21
21
|
export const SESSION_HEADER = 'x-opencode-session';
|
|
22
|
+
/** Session affinity header for gateway routing and prompt caching. */
|
|
23
|
+
export const SESSION_AFFINITY_HEADER = 'x-session-affinity';
|
|
22
24
|
/**
|
|
23
25
|
* The header value for one request.
|
|
24
26
|
* @param sessionId - the request's session id, if it names one.
|
|
@@ -31,15 +33,17 @@ export function opencodeSessionValue(sessionId) {
|
|
|
31
33
|
* Every header one provider request carries.
|
|
32
34
|
*
|
|
33
35
|
* The attribution `user-agent` is Harness-owned and always present; the session
|
|
34
|
-
*
|
|
35
|
-
* never win, because the gateway's routing decision must follow
|
|
36
|
-
* not a deployment string.
|
|
36
|
+
* and affinity headers are added on top of it. A caller-supplied header of the
|
|
37
|
+
* same name can never win, because the gateway's routing decision must follow
|
|
38
|
+
* the session, not a deployment string.
|
|
37
39
|
* @param sessionId - the request's session id, if it names one.
|
|
38
40
|
* @returns headers for the pi-ai request options.
|
|
39
41
|
*/
|
|
40
42
|
export function providerHeaders(sessionId) {
|
|
43
|
+
const session = opencodeSessionValue(sessionId);
|
|
41
44
|
return {
|
|
42
|
-
[SESSION_HEADER]:
|
|
45
|
+
[SESSION_HEADER]: session,
|
|
46
|
+
[SESSION_AFFINITY_HEADER]: session,
|
|
43
47
|
...attributionHeaders(),
|
|
44
48
|
};
|
|
45
49
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self update check: is a newer release of this package published?
|
|
3
|
+
*
|
|
4
|
+
* This module only *reads*: it asks the npm registry for the latest published
|
|
5
|
+
* version and compares it with the running build. It never downloads,
|
|
6
|
+
* installs, or replaces anything — the actual update stays with the host's
|
|
7
|
+
* `pluginManager` (`removeBundle` + `installBundle`), driven by an explicit
|
|
8
|
+
* user click on the settings page. Automatic self-replacement would bypass
|
|
9
|
+
* the host's manifest lock, peer-compatibility checks, build approvals, and
|
|
10
|
+
* HMR lifecycle, so it is deliberately not implemented here.
|
|
11
|
+
*
|
|
12
|
+
* The check runs only when the settings page asks (page open or manual
|
|
13
|
+
* refresh), never on the model-request path, and fails silent: an unreachable
|
|
14
|
+
* registry simply hides the banner instead of erroring the page.
|
|
15
|
+
*
|
|
16
|
+
* @module @dan-ai-studio/dshopencodego/update/check
|
|
17
|
+
*/
|
|
18
|
+
/** This package as addressed on the registry. */
|
|
19
|
+
export declare const PACKAGE_NAME = "@dan-ai-studio/dshopencodego";
|
|
20
|
+
/** Document address answering `{ "version": "x.y.z", ... }`. */
|
|
21
|
+
export declare const REGISTRY_LATEST_URL: string;
|
|
22
|
+
/** How long one lookup answers for before the registry is asked again. */
|
|
23
|
+
export declare const CHECK_CACHE_MS: number;
|
|
24
|
+
/** Bound on one registry round trip; expiry fails silent, never throws. */
|
|
25
|
+
export declare const CHECK_TIMEOUT_MS = 10000;
|
|
26
|
+
/** What the settings page renders. */
|
|
27
|
+
export interface UpdateStatus {
|
|
28
|
+
/** Version of the running build. */
|
|
29
|
+
readonly current: string;
|
|
30
|
+
/** Latest published version, when the registry answered. */
|
|
31
|
+
readonly latest: string | undefined;
|
|
32
|
+
/** True when the registry answered and `latest` is newer than `current`. */
|
|
33
|
+
readonly available: boolean;
|
|
34
|
+
/** Milliseconds since the epoch of the lookup backing this reading. */
|
|
35
|
+
readonly checkedAt: number;
|
|
36
|
+
}
|
|
37
|
+
interface ParsedVersion {
|
|
38
|
+
readonly core: readonly number[];
|
|
39
|
+
/** Absent for a release; present (lower precedence) for `1.2.3-tag.4`. */
|
|
40
|
+
readonly prerelease: readonly string[] | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Split `1.2.3-tag.4+build` into comparable parts; invalid input yields undefined. */
|
|
43
|
+
export declare function parseVersion(value: string): ParsedVersion | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Compare two version strings.
|
|
46
|
+
* @returns negative when `left` is older, positive when newer, zero when equal.
|
|
47
|
+
* @throws {Error} when either side is not a version.
|
|
48
|
+
*/
|
|
49
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
50
|
+
/** True when `latest` is a newer release than `current`. Unparsable input is never an update. */
|
|
51
|
+
export declare function isUpdateAvailable(current: string, latest: string | undefined): boolean;
|
|
52
|
+
/** Read the `version` field out of a registry `latest` document. */
|
|
53
|
+
export declare function parseLatestVersion(body: unknown): string | undefined;
|
|
54
|
+
/** Dependencies the check needs, injectable for tests. */
|
|
55
|
+
export interface UpdateCheckDeps {
|
|
56
|
+
/** Running build's version (build-time constant in production). */
|
|
57
|
+
readonly currentVersion: string;
|
|
58
|
+
/** Fetch implementation; defaults to the global one. */
|
|
59
|
+
readonly fetchImpl?: typeof fetch;
|
|
60
|
+
/** Clock; defaults to `Date.now`. */
|
|
61
|
+
readonly now?: () => number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ask the registry for the latest published version, cached per process.
|
|
65
|
+
*
|
|
66
|
+
* Concurrent callers share one in-flight lookup. A lookup that fails for any
|
|
67
|
+
* reason — network, timeout, malformed document — resolves as "no update
|
|
68
|
+
* known" rather than rejecting, so the settings page never breaks because
|
|
69
|
+
* the registry is unreachable.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createUpdateChecker(deps: UpdateCheckDeps): {
|
|
72
|
+
check(force?: boolean): Promise<UpdateStatus>;
|
|
73
|
+
};
|
|
74
|
+
export {};
|