@goodandready/dsh-model-sync 0.5.13 → 0.5.15

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/client.js CHANGED
@@ -528,6 +528,25 @@ window.__ModuleLoader__.load({
528
528
  }
529
529
 
530
530
 
531
+ async function safeJson(r) {
532
+ const text = await r.text()
533
+ if (!text || !text.trim()) {
534
+ if (!r.ok) throw new Error(`HTTP ${r.status}${r.statusText ? ': ' + r.statusText : ''}`)
535
+ return {}
536
+ }
537
+ let parsed
538
+ try {
539
+ parsed = JSON.parse(text)
540
+ } catch {
541
+ if (!r.ok) throw new Error(`HTTP ${r.status}${text ? ': ' + text.slice(0, 120) : ''}`)
542
+ throw new Error(`Invalid JSON response (HTTP ${r.status})`)
543
+ }
544
+ if (!r.ok) {
545
+ throw new Error(parsed?.error?.message ?? parsed?.message ?? `HTTP ${r.status}`)
546
+ }
547
+ return parsed
548
+ }
549
+
531
550
  function UpdaterSection({ t, api }) {
532
551
  const [updaterStatus, setUpdaterStatus] = React.useState(null)
533
552
  const [updaterChecking, setUpdaterChecking] = React.useState(false)
@@ -541,8 +560,7 @@ window.__ModuleLoader__.load({
541
560
  setUpdaterNotice('')
542
561
  fetch(`${api}/update?fresh=1`)
543
562
  .then((res) => {
544
- if (!res.ok) throw new Error(`HTTP ${res.status}`)
545
- return res.json()
563
+ return safeJson(res)
546
564
  })
547
565
  .then((data) => {
548
566
  setUpdaterStatus(data)
@@ -563,7 +581,7 @@ window.__ModuleLoader__.load({
563
581
  method: 'POST',
564
582
  headers: { 'x-dsh-plugin-update': '1', 'content-type': 'application/json' },
565
583
  })
566
- .then((res) => res.json())
584
+ .then(safeJson)
567
585
  .then((resData) => {
568
586
  if (resData.error) throw new Error(resData.error.message || resData.error)
569
587
  const cur = resData.updatedVersion || (updaterStatus && updaterStatus.latestVersion) || ''
@@ -759,7 +777,7 @@ window.__ModuleLoader__.load({
759
777
  secretRef: webhookDraft.diffWebhookSecretRef || webhookDraft.diffWebhookSecret || '',
760
778
  }),
761
779
  })
762
- const body = await res.json()
780
+ const body = await safeJson(res)
763
781
  if (res.ok && body.ok) {
764
782
  setWebhookMsg(t.webhookSuccess)
765
783
  } else {
@@ -893,7 +911,7 @@ window.__ModuleLoader__.load({
893
911
  const key = `${targetProvider}:${targetModel}`
894
912
  setTryState((current) => ({ ...current, [key]: { loading: true } }))
895
913
  fetch(`${API}/try`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider, model: targetModel }) })
896
- .then(async (r) => { const b = await r.json(); return { ok: r.ok && b.ok !== false, latency: b.latencyMs, status: b.status, error: b.error } })
914
+ .then(async (r) => { const b = await safeJson(r); return { ok: r.ok && b.ok !== false, latency: b.latencyMs, status: b.status, error: b.error } })
897
915
  .then((res) => { setTryState((current) => ({ ...current, [key]: { loading: false, ok: res.ok, latency: res.latency, status: res.status, error: res.error } })) })
898
916
  .catch((err) => { setTryState((current) => ({ ...current, [key]: { loading: false, ok: false, error: String(err?.message ?? err) } })) })
899
917
  }
@@ -908,9 +926,7 @@ window.__ModuleLoader__.load({
908
926
  body: JSON.stringify({ provider: selectionProvider, models: selectionDraft }),
909
927
  })
910
928
  .then(async (r) => {
911
- const data = await r.json()
912
- if (!r.ok || data.error) throw new Error(data?.error?.message ?? `HTTP ${r.status}`)
913
- return data
929
+ return safeJson(r)
914
930
  })
915
931
  .then((data) => {
916
932
  const next = { ...tryState }
@@ -964,9 +980,7 @@ window.__ModuleLoader__.load({
964
980
  body: JSON.stringify(parsed),
965
981
  })
966
982
  .then(async (r) => {
967
- const res = await r.json()
968
- if (!r.ok) throw new Error(res.error?.message ?? `HTTP ${r.status}`)
969
- return res
983
+ return safeJson(r)
970
984
  })
971
985
  .then(() => {
972
986
  setImportMsg(t.importSuccess)
@@ -991,9 +1005,7 @@ window.__ModuleLoader__.load({
991
1005
  body: JSON.stringify({ alias: aliasDraft.alias.trim(), provider: aliasDraft.provider, model: aliasDraft.model.trim() }),
992
1006
  })
993
1007
  .then(async (r) => {
994
- const res = await r.json()
995
- if (!r.ok) throw new Error(res.error?.message ?? `HTTP ${r.status}`)
996
- return res
1008
+ return safeJson(r)
997
1009
  })
998
1010
  .then(() => {
999
1011
  setAliasDraft({ alias: '', provider: '', model: '' })
@@ -1012,9 +1024,7 @@ window.__ModuleLoader__.load({
1012
1024
  body: JSON.stringify({ alias: aliasName, action: 'delete' }),
1013
1025
  })
1014
1026
  .then(async (r) => {
1015
- const res = await r.json()
1016
- if (!r.ok) throw new Error(res.error?.message ?? `HTTP ${r.status}`)
1017
- return res
1027
+ return safeJson(r)
1018
1028
  })
1019
1029
  .then(() => load())
1020
1030
  .catch((e) => setError(String(e?.message ?? e)))
@@ -1024,9 +1034,8 @@ window.__ModuleLoader__.load({
1024
1034
  const resolveSmartAlias = (aliasName) => {
1025
1035
  setResolvingKey(aliasName)
1026
1036
  fetch(`${API}/aliases/resolve?alias=${encodeURIComponent(aliasName)}`)
1027
- .then(async (r) => {
1028
- const res = await r.json()
1029
- if (!r.ok) throw new Error(res.error?.message ?? `HTTP ${r.status}`)
1037
+ .then(safeJson)
1038
+ .then((res) => {
1030
1039
  setResolvedAliases((prev) => ({ ...prev, [aliasName]: res }))
1031
1040
  })
1032
1041
  .catch((e) => {
@@ -1039,7 +1048,7 @@ window.__ModuleLoader__.load({
1039
1048
  if (typeof document !== 'undefined' && document.hidden) return
1040
1049
  setError('')
1041
1050
  fetch(`${API}/status`)
1042
- .then((r) => r.json())
1051
+ .then(safeJson)
1043
1052
  .then(setStatus)
1044
1053
  .catch((e) => setError(String(e?.message ?? e)))
1045
1054
  loadCredentials()
@@ -1047,7 +1056,7 @@ window.__ModuleLoader__.load({
1047
1056
 
1048
1057
  const loadCredentials = () => {
1049
1058
  fetch(`${API}/credentials`)
1050
- .then((r) => r.json())
1059
+ .then(safeJson)
1051
1060
  .then(setCredentialStatus)
1052
1061
  .catch(() => {})
1053
1062
  }
@@ -1058,7 +1067,7 @@ window.__ModuleLoader__.load({
1058
1067
  const effectiveDryRun = options.dryRun !== undefined ? options.dryRun : dryRun
1059
1068
  setBusy(true); setError(''); setPreview(null)
1060
1069
  fetch(`${API}/run`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider, dryRun: effectiveDryRun, removeMissing }) })
1061
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1070
+ .then(safeJson)
1062
1071
  .then((body) => {
1063
1072
  setStatus(body)
1064
1073
  if (effectiveDryRun && body.results) setPreview(body.results)
@@ -1074,7 +1083,7 @@ window.__ModuleLoader__.load({
1074
1083
  const checkHealth = (targetProvider) => {
1075
1084
  setBusy(true); setError('')
1076
1085
  fetch(`${API}/health`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider }) })
1077
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1086
+ .then(safeJson)
1078
1087
  .then(setStatus)
1079
1088
  .catch((e) => setError(String(e?.message ?? e)))
1080
1089
  .finally(() => setBusy(false))
@@ -1083,7 +1092,7 @@ window.__ModuleLoader__.load({
1083
1092
  const checkCredentials = (targetProvider) => {
1084
1093
  setBusy(true); setError('')
1085
1094
  fetch(`${API}/credentials/check`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider }) })
1086
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1095
+ .then(safeJson)
1087
1096
  .then((body) => { if (body.credentials) setCredentialStatus(body.credentials); load() })
1088
1097
  .catch((e) => setError(String(e?.message ?? e)))
1089
1098
  .finally(() => setBusy(false))
@@ -1092,7 +1101,7 @@ window.__ModuleLoader__.load({
1092
1101
  const updateNotification = (id, action) => {
1093
1102
  setBusy(true); setError('')
1094
1103
  fetch(`${API}/notifications/${action}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id }) })
1095
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1104
+ .then(safeJson)
1096
1105
  .then(() => load())
1097
1106
  .catch((e) => setError(String(e?.message ?? e)))
1098
1107
  .finally(() => setBusy(false))
@@ -1187,7 +1196,7 @@ window.__ModuleLoader__.load({
1187
1196
  headers: { 'content-type': 'application/json' },
1188
1197
  body: JSON.stringify(payload),
1189
1198
  })
1190
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1199
+ .then(safeJson)
1191
1200
  .then(() => { closePolicy(); return load() })
1192
1201
  .catch((e) => setError(String(e?.message ?? e)))
1193
1202
  .finally(() => setBusy(false))
@@ -1204,7 +1213,7 @@ window.__ModuleLoader__.load({
1204
1213
  headers: { 'content-type': 'application/json' },
1205
1214
  body: JSON.stringify({ provider: selectionProvider, models }),
1206
1215
  })
1207
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1216
+ .then(safeJson)
1208
1217
  .then((body) => { setStatus(body); closeSelection() })
1209
1218
  .catch((e) => setError(String(e?.message ?? e)))
1210
1219
  .finally(() => setBusy(false))
@@ -1218,7 +1227,7 @@ window.__ModuleLoader__.load({
1218
1227
  headers: { 'content-type': 'application/json' },
1219
1228
  body: JSON.stringify({ historyId: entry.id, provider: targetProvider }),
1220
1229
  })
1221
- .then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
1230
+ .then(safeJson)
1222
1231
  .then(() => load())
1223
1232
  .catch((e) => setError(String(e?.message ?? e)))
1224
1233
  .finally(() => setBusy(false))
@@ -1807,7 +1816,7 @@ window.__ModuleLoader__.load({
1807
1816
  ensureStyles()
1808
1817
  const load = () => {
1809
1818
  if (typeof document !== 'undefined' && document.hidden) return
1810
- fetch(`${API}/status`).then((r) => r.json()).then(setCardStatus).catch(() => {})
1819
+ fetch(`${API}/status`).then(safeJson).then(setCardStatus).catch(() => {})
1811
1820
  }
1812
1821
  load()
1813
1822
  if (open) return
@@ -1,3 +1,15 @@
1
+ function getOptionalService(ctx, name) {
2
+ try {
3
+ return ctx?.[name] ?? ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
4
+ } catch {
5
+ try {
6
+ return ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
7
+ } catch {
8
+ return undefined
9
+ }
10
+ }
11
+ }
12
+
1
13
  import { createHmac } from "node:crypto"
2
14
 
3
15
  export function extractDiffEvents(results, options = {}) {
@@ -220,9 +232,10 @@ export async function dispatchDiffNotifications(ctx, config, results, { fetchImp
220
232
  }
221
233
 
222
234
  try {
223
- if (typeof ctx?.pluginNotify?.send === "function") {
235
+ const pluginNotify = getOptionalService(ctx, 'pluginNotify')
236
+ if (typeof pluginNotify?.send === "function") {
224
237
  const summary = `Model Sync: +${events.counts.added} added, -${events.counts.deprecated} deprecated, ~${events.counts.priceChanged} price changed`
225
- await ctx.pluginNotify.send({
238
+ await pluginNotify.send({
226
239
  source: "dsh-model-sync",
227
240
  title: "Model Sync Catalog Changes",
228
241
  message: summary,
package/lib/http.js CHANGED
@@ -1,3 +1,15 @@
1
+ function getOptionalService(ctx, name) {
2
+ try {
3
+ return ctx?.[name] ?? ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
4
+ } catch {
5
+ try {
6
+ return ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
7
+ } catch {
8
+ return undefined
9
+ }
10
+ }
11
+ }
12
+
1
13
  import { isForbiddenWebhookHost, isLoopbackHost } from './diff-notifier.js'
2
14
  import crypto from 'node:crypto'
3
15
  import { validateAliasName } from './alias-resolver.js'
@@ -43,7 +55,7 @@ export function isLoopbackAddress(ip) {
43
55
 
44
56
  export function trusted(req, { requireLoopback = false, ctx = null } = {}) {
45
57
  // 1. Core connection rejection check (#237)
46
- const conn = ctx?.connection || ctx?.get?.('connection') || req?.connection
58
+ const conn = getOptionalService(ctx, 'connection') || req?.connection
47
59
  if (conn && typeof conn.requestRejection === 'function') {
48
60
  const rejection = conn.requestRejection(req)
49
61
  if (rejection !== undefined) return false
@@ -272,8 +284,9 @@ export function registerHttpApi(ctx, synchronizer, { getSchedulerStatus = () =>
272
284
  return []
273
285
  }
274
286
  const makeHandler = (route) => async (req, res) => {
275
- // 1. Core connection check on all routes (#237)
276
- const conn = ctx?.connection || ctx?.get?.('connection') || req?.connection
287
+ try {
288
+ // 1. Core connection check on all routes (#237)
289
+ const conn = getOptionalService(ctx, 'connection') || req?.connection
277
290
  if (conn && typeof conn.requestRejection === 'function') {
278
291
  const rejection = conn.requestRejection(req)
279
292
  if (rejection !== undefined) {
@@ -382,6 +395,12 @@ export function registerHttpApi(ctx, synchronizer, { getSchedulerStatus = () =>
382
395
  const code = error?.code === 'SETTINGS_CONFLICT' ? 409 : error?.code === 'SETTINGS_UNAVAILABLE' || error?.code === 'CONFIG_UNAVAILABLE' ? 503 : error?.code === 'PROVIDER_NOT_CONFIGURED' || error?.code === 'UNKNOWN_MODEL' || error?.code === 'INVALID_POLICY' || error?.code === 'INVALID_HISTORY' || error?.code === 'HISTORY_NOT_FOUND' || error?.code === 'INVALID_NOTIFICATION' || error?.code === 'NOTIFICATION_NOT_FOUND' || error?.code === 'INVALID_ALIAS' || error?.code === 'INVALID_CONFIG' ? 400 : 500
383
396
  return json(res, code, { error: { code: error?.code ?? 'run-failed', message: error instanceof Error ? error.message : String(error) } })
384
397
  }
398
+ } catch (unhandledError) {
399
+ if (typeof ctx?.logger?.error === 'function') {
400
+ ctx.logger.error('[dsh-model-sync] unhandled HTTP error on route ' + route + ':', unhandledError)
401
+ }
402
+ return json(res, 500, { error: { code: 'internal_error', message: unhandledError instanceof Error ? unhandledError.message : String(unhandledError) } })
403
+ }
385
404
  }
386
405
  return [
387
406
  ctx.webServer.register({ kind: 'exact', path: `${BASE}/status`, handler: makeHandler('status') }),
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ import { registerHttpApi } from './http.js'
6
6
  import { registerPluginUpdater } from './updater.js'
7
7
 
8
8
  export const name = '@goodandready/dsh-model-sync'
9
- export const inject = ['llm', 'webServer', 'credentials']
9
+ export const inject = ['llm', 'webServer', 'credentials', 'settings']
10
10
 
11
11
  const NS = 'dsh-model-sync'
12
12
 
@@ -3,18 +3,18 @@ import { listApiKeyProviders } from './inventory.js'
3
3
  export function createProviderService(ctx) {
4
4
  const readSettings = () => {
5
5
  try {
6
- return (typeof ctx.get === 'function' ? (ctx.get('settings') ?? ctx.settings) : ctx.settings)
6
+ return ctx?.settings ?? ctx?.reflect?.get?.('settings') ?? (typeof ctx?.get === 'function' ? ctx.get('settings') : undefined)
7
7
  } catch {
8
- return ctx.settings
8
+ return undefined
9
9
  }
10
10
  }
11
11
 
12
12
  const readLlm = () => {
13
13
  try {
14
- const llm = typeof ctx.get === 'function' ? ctx.get('llm') : null
15
- return (llm && typeof llm === 'object' && (llm.listConfigurableProviders || llm.listProviders)) ? llm : ctx.llm
14
+ const llm = ctx?.llm ?? ctx?.reflect?.get?.('llm') ?? (typeof ctx?.get === 'function' ? ctx.get('llm') : null)
15
+ return (llm && typeof llm === 'object' && (llm.listConfigurableProviders || llm.listProviders)) ? llm : undefined
16
16
  } catch {
17
- return ctx.llm
17
+ return undefined
18
18
  }
19
19
  }
20
20
 
@@ -38,16 +38,32 @@ export function reliabilityOptions(config, options) {
38
38
  }
39
39
  }
40
40
 
41
+ export function getOptionalService(ctx, name) {
42
+ try {
43
+ return ctx?.[name] ?? ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
44
+ } catch {
45
+ try {
46
+ return ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
47
+ } catch {
48
+ return undefined
49
+ }
50
+ }
51
+ }
52
+
41
53
  export function settingsOf(ctx) {
42
- try { return (typeof ctx.get === 'function' ? (ctx.get('settings') ?? ctx.settings) : ctx.settings) } catch { return ctx.settings }
54
+ try {
55
+ return ctx?.settings ?? ctx?.reflect?.get?.('settings') ?? (typeof ctx?.get === 'function' ? ctx.get('settings') : undefined)
56
+ } catch {
57
+ return undefined
58
+ }
43
59
  }
44
60
 
45
61
  export function llmOf(ctx) {
46
62
  try {
47
- const llm = typeof ctx.get === 'function' ? ctx.get('llm') : null
48
- return (llm && typeof llm === 'object' && (llm.listConfigurableProviders || llm.listProviders)) ? llm : ctx.llm
63
+ const llm = ctx?.llm ?? ctx?.reflect?.get?.('llm') ?? (typeof ctx?.get === 'function' ? ctx.get('llm') : null)
64
+ return (llm && typeof llm === 'object' && (llm.listConfigurableProviders || llm.listProviders)) ? llm : undefined
49
65
  } catch {
50
- return ctx.llm
66
+ return undefined
51
67
  }
52
68
  }
53
69
 
package/lib/updater.js CHANGED
@@ -1,3 +1,15 @@
1
+ function getOptionalService(ctx, name) {
2
+ try {
3
+ return ctx?.[name] ?? ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
4
+ } catch {
5
+ try {
6
+ return ctx?.reflect?.get?.(name) ?? (typeof ctx?.get === 'function' ? ctx.get(name) : undefined)
7
+ } catch {
8
+ return undefined
9
+ }
10
+ }
11
+ }
12
+
1
13
  import { spawn } from 'node:child_process'
2
14
  import { existsSync, readFileSync } from 'node:fs'
3
15
  import { readFile } from 'node:fs/promises'
@@ -315,7 +327,7 @@ export function registerPluginUpdater(ctx, options = {}) {
315
327
  const handler = async (request, response) => {
316
328
  try {
317
329
  // 1. Core connection rejection check on all updater routes (#237)
318
- const conn = ctx?.connection || ctx?.get?.('connection') || request?.connection
330
+ const conn = getOptionalService(ctx, 'connection') || request?.connection
319
331
  if (conn && typeof conn.requestRejection === 'function') {
320
332
  const rejection = conn.requestRejection(request)
321
333
  if (rejection !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-model-sync",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "Automatic model catalog synchronization for API-key DeepSeek Harness providers.",
5
5
  "keywords": [
6
6
  "deepseek-harness",