dsh-gateway-provider 1.0.11 → 1.0.12

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/catalog.js CHANGED
@@ -121,15 +121,31 @@ async function fetchManagementModels(baseURL, apiKey, userId, signal) {
121
121
  return entries;
122
122
  }
123
123
 
124
+ /**
125
+ * Strip trailing slashes and a trailing version segment (`/v1`, `/v2`, …)
126
+ * from a gateway base URL. The catalog always appends its own full paths
127
+ * (`/v1/models`, `/api/user/models`), so a user-supplied base that already
128
+ * carries `/v1` (the OpenAI-SDK convention, which the chat path also accepts)
129
+ * must not leak through as `/v1/v1/models`. Mirrors the derivation the custom
130
+ * (protocol-URL) template performs in `protocols.js`.
131
+ */
132
+ function stripVersion(baseURL) {
133
+ return String(baseURL ?? "").replace(/\/+$/, "").replace(/\/v\d+$/, "");
134
+ }
135
+
124
136
  /**
125
137
  * The base URL model discovery fetches `/v1/models` against. Gateways with
126
138
  * protocol URL fields (custom template) derive it from their OpenAI-style
127
139
  * URL; `null` means discovery is impossible (e.g. only an Anthropic address
128
140
  * is configured) and the catalog is exactly the user-declared models.
141
+ * The returned base is version-stripped: the catalog suffixes below always
142
+ * append the version segment themselves.
129
143
  */
130
144
  function discoveryBase(connection) {
131
145
  const base = connection.catalogBase !== undefined ? connection.catalogBase : connection.baseURL;
132
- return typeof base === "string" && base.length > 0 ? base : null;
146
+ if (typeof base !== "string" || base.length === 0) return null;
147
+ const stripped = stripVersion(base);
148
+ return stripped.length > 0 ? stripped : null;
133
149
  }
134
150
 
135
151
  /**
package/lib/client.js CHANGED
@@ -77,7 +77,7 @@ window.__ModuleLoader__.load({
77
77
  responsesURLPlaceholder: "https://gw.example.com/v1/responses",
78
78
  anthropicURLPlaceholder: "https://gw.example.com/v1/messages",
79
79
  catalogHint: "auto:优先 /v1/models,鉴权失败时回退管理 API",
80
- baseURLClearHint: "留空则回退到环境变量 NEWAPI_BASE_URL 或官方云端地址",
80
+ baseURLClearHint: "留空则回退到环境变量 NEWAPI_BASE_URL 或官方云端地址;结尾带不带 /v1 均可",
81
81
  labelClearHint: "留空则回退为 gateway:<id>",
82
82
  keyEnvHint: "留空则继承默认网关的 Key(NEWAPI_API_KEY)",
83
83
  keyKeepHint: "留空保持不变;填写则覆盖存储的 Key",
@@ -137,7 +137,7 @@ window.__ModuleLoader__.load({
137
137
  responsesURLPlaceholder: "https://gw.example.com/v1/responses",
138
138
  anthropicURLPlaceholder: "https://gw.example.com/v1/messages",
139
139
  catalogHint: "auto: prefer /v1/models, fall back to the management API on auth failure",
140
- baseURLClearHint: "Empty falls back to NEWAPI_BASE_URL or the public cloud default",
140
+ baseURLClearHint: "Empty falls back to NEWAPI_BASE_URL or the public cloud default; a trailing /v1 is optional",
141
141
  labelClearHint: "Empty falls back to gateway:<id>",
142
142
  keyEnvHint: "Empty inherits the default gateway's key (NEWAPI_API_KEY)",
143
143
  keyKeepHint: "Empty keeps the stored key; type to overwrite",
@@ -820,8 +820,8 @@ window.__ModuleLoader__.load({
820
820
  var snapRef = React.useRef(null);
821
821
 
822
822
  var load = React.useCallback(function () {
823
- api.settings.describe({}).then(function (res) {
824
- var nsList = res && res.result && res.result.ok ? (res.result.value.namespaces || []) : [];
823
+ api.settings.describe().then(function (res) {
824
+ var nsList = res && res.ok ? (res.value.namespaces || []) : [];
825
825
  var desc = nsList.find(function (n) { return n.ns === NS; });
826
826
  var snap = desc ? desc.value || {} : {};
827
827
  setSnapshot(snap);
@@ -831,8 +831,8 @@ window.__ModuleLoader__.load({
831
831
  (snap.gateways || []).forEach(function (gw) {
832
832
  if (gw.apiKeyEnv && refs.indexOf(gw.apiKeyEnv) === -1) refs.push(gw.apiKeyEnv);
833
833
  });
834
- return api.credentials.describe({ refs: refs }).then(function (cres) {
835
- var creds = (cres && cres.result && cres.result.ok) ? (cres.result.value.credentials || {}) : {};
834
+ return api.credentials.describe(refs).then(function (cres) {
835
+ var creds = (cres && cres.ok) ? (cres.value || {}) : {};
836
836
  setCredentials(creds);
837
837
  }).catch(function () { setCredentials({}); });
838
838
  }).catch(function () { setSnapshot({}); });
@@ -849,9 +849,9 @@ window.__ModuleLoader__.load({
849
849
  /** Mutate one path op into the settings namespace; always reload
850
850
  * afterwards (success resyncs the ref, failure rolls it back). */
851
851
  var mutate = function (ops) {
852
- return api.settings.mutate({ ns: NS, ops: ops }).then(function (res) {
853
- if (!(res && res.result && res.result.ok)) {
854
- console.warn("llm-newapi: settings write failed", res && res.result && res.result.error);
852
+ return api.settings.mutate(NS, ops, undefined).then(function (res) {
853
+ if (!(res && res.ok)) {
854
+ console.warn("llm-newapi: settings write failed", res && res.error);
855
855
  load();
856
856
  return false;
857
857
  }
@@ -897,13 +897,13 @@ window.__ModuleLoader__.load({
897
897
  * An empty baseURL (URL-addressed custom gateway) is sent as
898
898
  * absent so the host uses its configured discovery base. */
899
899
  var discover = function (gwIdx, baseURL, provider) {
900
- return api.llm.discoverModels({ settingsNs: NS, provider: provider, baseURL: baseURL || undefined, apiKey: undefined }).then(function (res) {
901
- if (!(res && res.result && res.result.ok)) {
902
- var raw = res && res.result && res.result.error;
903
- var msg = typeof raw === "string" ? raw : (raw && raw.message) || "";
900
+ return api.llm.discoverModels(NS, { provider: provider, baseURL: baseURL || undefined }).then(function (res) {
901
+ if (!(res && res.ok)) {
902
+ var raw = res && res.error;
903
+ var msg = (raw && raw.message) || "";
904
904
  throw new Error(msg || T.testFail);
905
905
  }
906
- return (res.result.value.models || []);
906
+ return (res.value || []);
907
907
  });
908
908
  };
909
909
 
@@ -993,8 +993,8 @@ window.__ModuleLoader__.load({
993
993
  ? { op: "set", path: [k], value: changes[k] }
994
994
  : { op: "unset", path: [k] });
995
995
  });
996
- if (ops.length > 0) writes.push(api.settings.mutate({ ns: NS, ops: ops }));
997
- if (changes.apiKey !== undefined) writes.push(api.credentials.set({ ref: defaultEnv, value: changes.apiKey }));
996
+ if (ops.length > 0) writes.push(api.settings.mutate(NS, ops, undefined));
997
+ if (changes.apiKey !== undefined) writes.push(api.credentials.set(defaultEnv, changes.apiKey));
998
998
  // Optimistically sync the ref; load() reconciles after settle.
999
999
  var nextSnap = Object.assign({}, snap);
1000
1000
  if (changes.baseURL !== undefined) { if (changes.baseURL.length > 0) nextSnap.baseURL = changes.baseURL; else delete nextSnap.baseURL; }
@@ -1033,16 +1033,16 @@ window.__ModuleLoader__.load({
1033
1033
  if (touched) {
1034
1034
  var nextGateways = gws.slice();
1035
1035
  nextGateways[gwIdx] = updated;
1036
- writes.push(api.settings.mutate({ ns: NS, ops: [{ op: "set", path: ["gateways"], value: nextGateways }] }));
1036
+ writes.push(api.settings.mutate(NS, [{ op: "set", path: ["gateways"], value: nextGateways }], undefined));
1037
1037
  syncGateways(nextGateways);
1038
1038
  }
1039
1039
  if (changes.apiKey !== undefined) {
1040
1040
  var keyRef = changes.apiKeyEnv || gw.apiKeyEnv || defaultEnv;
1041
- writes.push(api.credentials.set({ ref: keyRef, value: changes.apiKey }));
1041
+ writes.push(api.credentials.set(keyRef, changes.apiKey));
1042
1042
  }
1043
1043
  }
1044
1044
  return Promise.all(writes).then(function (results) {
1045
- var ok = results.every(function (r) { return r && r.result && r.result.ok; });
1045
+ var ok = results.every(function (r) { return r === undefined || r === null || (r && r.ok) !== false; });
1046
1046
  load(); // resync the ref with the persisted truth (success or not)
1047
1047
  return { ok: ok, label: ok ? T.configSaved : T.configSaveFail };
1048
1048
  }).catch(function () {
@@ -1068,14 +1068,14 @@ window.__ModuleLoader__.load({
1068
1068
  var nextGateways = gws.concat([entry]);
1069
1069
  syncGateways(nextGateways);
1070
1070
  var writes = [
1071
- api.settings.mutate({ ns: NS, ops: [{ op: "set", path: ["gateways"], value: nextGateways }] }),
1071
+ api.settings.mutate(NS, [{ op: "set", path: ["gateways"], value: nextGateways }], undefined),
1072
1072
  ];
1073
1073
  if (spec.apiKey !== undefined && spec.apiKey.length > 0) {
1074
1074
  var keyEnv = (snapRef.current && snapRef.current.apiKeyEnv) || "NEWAPI_API_KEY";
1075
- writes.push(api.credentials.set({ ref: spec.apiKeyEnv || keyEnv, value: spec.apiKey }));
1075
+ writes.push(api.credentials.set(spec.apiKeyEnv || keyEnv, spec.apiKey));
1076
1076
  }
1077
1077
  return Promise.all(writes).then(function (results) {
1078
- var ok = results.every(function (r) { return r && r.result && r.result.ok; });
1078
+ var ok = results.every(function (r) { return r === undefined || r === null || (r && r.ok) !== false; });
1079
1079
  load();
1080
1080
  return { ok: ok, label: ok ? T.configSaved : T.configSaveFail };
1081
1081
  }).catch(function () {
@@ -1136,7 +1136,7 @@ window.__ModuleLoader__.load({
1136
1136
  );
1137
1137
  }
1138
1138
 
1139
- var inject = ["slots", "connection"];
1139
+ var inject = ["slots", "remote", "remote.settings", "remote.credentials", "remote.llm"];
1140
1140
 
1141
1141
  function apply(ctx) {
1142
1142
  var slots = ctx.get("slots");
@@ -1147,7 +1147,15 @@ window.__ModuleLoader__.load({
1147
1147
  id: "newapi-gateways",
1148
1148
  order: 20,
1149
1149
  label: function () { return T.nav; },
1150
- inject: function () { return { api: ctx.get("connection").api }; },
1150
+ inject: function () {
1151
+ // Typed Remote namespaces (dsh ≥ 0.1.2-rc.1): the old
1152
+ // `ctx.get("connection").api` RPC facade was removed.
1153
+ return { api: {
1154
+ settings: ctx.get("remote.settings"),
1155
+ credentials: ctx.get("remote.credentials"),
1156
+ llm: ctx.get("remote.llm"),
1157
+ } };
1158
+ },
1151
1159
  }, function (props) {
1152
1160
  return React.createElement(GatewayModelsSection, { api: props.api });
1153
1161
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gateway-provider",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Generic LLM gateway model provider plugin for DeepSeek Harness: mounts newapi / LiteLLM / Higress / any OpenAI-compatible gateway as provider routes, auto-discovers the model list, enriches every model with models.dev parameters, and dispatches each model over its native wire protocol (OpenAI / Anthropic / Gemini) via the pi-ai SDK",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,7 @@
29
29
  "smoke": "node test/smoke.mjs",
30
30
  "test:session": "node test/session-affinity.mjs",
31
31
  "test:client": "node test/client-render.mjs",
32
+ "test:client-api": "node test/client-api.mjs",
32
33
  "test:urls": "node test/protocol-urls.mjs",
33
34
  "test:schema": "node test/schema-coercion.mjs",
34
35
  "test:errors": "node test/error-codes.mjs"
@@ -53,8 +54,10 @@
53
54
  "dsh": {
54
55
  "client": {
55
56
  "inject": [
56
- "@deepseek-ai/dsh-client-runtime",
57
- "@deepseek-ai/dsh-client-web-react"
57
+ "@deepseek-ai/dsh-client-connection",
58
+ "@deepseek-ai/dsh-api-gateway",
59
+ "@deepseek-ai/dsh-api-remotes",
60
+ "@deepseek-ai/dsh-client-ui-settings"
58
61
  ],
59
62
  "platform": "web"
60
63
  },