@goodandready/dsh-model-sync 0.5.10 → 0.5.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/README.md CHANGED
@@ -137,23 +137,31 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
137
137
  ```yaml
138
138
  dsh-model-sync:
139
139
  enabled: true
140
- syncIntervalMinutes: 60
141
- autoReconcile: true
142
- enableBalanceChecks: true
140
+ scheduleEnabled: false
141
+ intervalMinutes: 60
142
+ autoApply: false
143
+ requestTimeoutMs: 15000
144
+ retryAttempts: 3
145
+ concurrency: 4
143
146
  providers:
144
- commandcode:
147
+ - provider: commandcode
145
148
  enabled: true
146
- clinebot:
149
+ autoApply: false
150
+ - provider: clinebot
147
151
  enabled: true
152
+ autoApply: false
148
153
  ```
149
154
 
150
155
  | Parameter | Type | Default | Description |
151
156
  |---|---|---|---|
152
157
  | `enabled` | `boolean` | `true` | Master switch for background synchronization |
153
- | `syncIntervalMinutes` | `number` | `60` | Polling frequency for catalog refreshes |
154
- | `autoReconcile` | `boolean` | `true` | Automatically apply discovered models to active catalog |
155
- | `enableBalanceChecks` | `boolean` | `true` | Query billing endpoints where supported |
156
- | `providers.<id>.enabled`| `boolean` | `true` | Enable or disable discovery for specific provider |
158
+ | `scheduleEnabled` | `boolean` | `false` | Enable periodic background synchronization schedule |
159
+ | `intervalMinutes` | `number` | `60` | Polling frequency for catalog refreshes in minutes |
160
+ | `autoApply` | `boolean` | `false` | Automatically apply discovered models to active catalog |
161
+ | `requestTimeoutMs`| `number` | `15000` | HTTP request timeout for catalog fetching (ms) |
162
+ | `retryAttempts` | `number` | `3` | Maximum retry attempts for transient network failures |
163
+ | `concurrency` | `number` | `4` | Maximum parallel provider synchronization workers |
164
+ | `providers` | `array` | `[]` | Per-provider configuration list (`provider`, `enabled`, `autoApply`) |
157
165
 
158
166
  ---
159
167
 
package/README.ru.md CHANGED
@@ -137,23 +137,31 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
137
137
  ```yaml
138
138
  dsh-model-sync:
139
139
  enabled: true
140
- syncIntervalMinutes: 60
141
- autoReconcile: true
142
- enableBalanceChecks: true
140
+ scheduleEnabled: false
141
+ intervalMinutes: 60
142
+ autoApply: false
143
+ requestTimeoutMs: 15000
144
+ retryAttempts: 3
145
+ concurrency: 4
143
146
  providers:
144
- commandcode:
147
+ - provider: commandcode
145
148
  enabled: true
146
- clinebot:
149
+ autoApply: false
150
+ - provider: clinebot
147
151
  enabled: true
152
+ autoApply: false
148
153
  ```
149
154
 
150
155
  | Параметр | Тип | По умолчанию | Описание |
151
156
  |---|---|---|---|
152
157
  | `enabled` | `boolean` | `true` | Главный переключатель фоновой синхронизации |
153
- | `syncIntervalMinutes` | `number` | `60` | Периодичность опроса провайдеров (в минутах) |
154
- | `autoReconcile` | `boolean` | `true` | Автоматически применять обнаруженные модели к активному каталогу |
155
- | `enableBalanceChecks` | `boolean` | `true` | Опрашивать биллинговые эндпоинты при наличии поддержки |
156
- | `providers.<id>.enabled`| `boolean` | `true` | Включение или отключение обнаружения для конкретного провайдера |
158
+ | `scheduleEnabled` | `boolean` | `false` | Включение периодического фонового расписания синхронизации |
159
+ | `intervalMinutes` | `number` | `60` | Периодичность опроса провайдеров (в минутах) |
160
+ | `autoApply` | `boolean` | `false` | Автоматически применять обнаруженные модели к активному каталогу |
161
+ | `requestTimeoutMs`| `number` | `15000` | Таймаут HTTP-запросов синхронизации каталогов (в миллисекундах) |
162
+ | `retryAttempts` | `number` | `3` | Максимальное число повторных попыток при сетевых сбоях |
163
+ | `concurrency` | `number` | `4` | Максимальное количество параллельных задач синхронизации |
164
+ | `providers` | `array` | `[]` | Список индивидуальных настроек провайдеров (`provider`, `enabled`, `autoApply`) |
157
165
 
158
166
  ---
159
167
 
package/README.zh.md CHANGED
@@ -137,23 +137,31 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
137
137
  ```yaml
138
138
  dsh-model-sync:
139
139
  enabled: true
140
- syncIntervalMinutes: 60
141
- autoReconcile: true
142
- enableBalanceChecks: true
140
+ scheduleEnabled: false
141
+ intervalMinutes: 60
142
+ autoApply: false
143
+ requestTimeoutMs: 15000
144
+ retryAttempts: 3
145
+ concurrency: 4
143
146
  providers:
144
- commandcode:
147
+ - provider: commandcode
145
148
  enabled: true
146
- clinebot:
149
+ autoApply: false
150
+ - provider: clinebot
147
151
  enabled: true
152
+ autoApply: false
148
153
  ```
149
154
 
150
155
  | 参数名称 | 类型 | 默认值 | 功能说明 |
151
156
  |---|---|---|---|
152
157
  | `enabled` | `boolean` | `true` | 后台自动同步主开关 |
153
- | `syncIntervalMinutes` | `number` | `60` | 目录更新轮询间隔(分钟) |
154
- | `autoReconcile` | `boolean` | `true` | 是否自动将新发现的模型生效至可用列表 |
155
- | `enableBalanceChecks` | `boolean` | `true` | 在受支持的服务商处查询余额 |
156
- | `providers.<id>.enabled`| `boolean` | `true` | 单独开启或关闭指定服务商的同步 |
158
+ | `scheduleEnabled` | `boolean` | `false` | 启用周期性后台定时同步任务 |
159
+ | `intervalMinutes` | `number` | `60` | 目录更新轮询间隔(分钟) |
160
+ | `autoApply` | `boolean` | `false` | 是否自动将新发现的模型生效至可用列表 |
161
+ | `requestTimeoutMs`| `number` | `15000` | 目录拉取 HTTP 请求超时时间(毫秒) |
162
+ | `retryAttempts` | `number` | `3` | 瞬态网络故障的最大重试次数 |
163
+ | `concurrency` | `number` | `4` | 最大并发服务商同步任务数 |
164
+ | `providers` | `array` | `[]` | 服务商独立配置列表(`provider`、`enabled`、`autoApply`) |
157
165
 
158
166
  ---
159
167
 
@@ -219,6 +219,9 @@ async function requestSpecific(profile, descriptor, { resolveCredential, fetchIm
219
219
  }
220
220
  if (response.status === 304) return response
221
221
  if (!response.ok) {
222
+ if (typeof response.body?.cancel === "function") {
223
+ try { await response.body.cancel() } catch (_err) { /* best-effort cancel */ }
224
+ }
222
225
  const error = catalogRequestError(response.status, response)
223
226
  error.adapterCode = [401, 403].includes(response.status) ? 'auth' : response.status === 429 ? 'rate-limit' : 'http'
224
227
  throw error
@@ -231,6 +234,9 @@ async function fetchSpecific(profile, descriptor, options) {
231
234
  try {
232
235
  const response = await requestSpecific(profile, descriptor, options)
233
236
  if (response.status === 304) {
237
+ if (typeof response.body?.cancel === "function") {
238
+ try { await response.body.cancel() } catch (_err) { /* best-effort cancel */ }
239
+ }
234
240
  return { notModified: true, etag: response.headers?.get?.('etag') || options.etag, lastModified: response.headers?.get?.('last-modified') || options.lastModified }
235
241
  }
236
242
  const respEtag = response.headers?.get?.('etag') || undefined
@@ -53,6 +53,16 @@ function estimateLatency(provider, model, probes = {}) {
53
53
  return Math.round(base)
54
54
  }
55
55
 
56
+ function isProviderEligible(provider, { healthResults = [], isCircuitOpen = () => false } = {}) {
57
+ if (isCircuitOpen(provider)) return false
58
+ const health = healthResults.find((h) => h.provider === provider)
59
+ if (health) {
60
+ if (health.ok === false) return false
61
+ if (health.status && health.status !== "ok") return false
62
+ }
63
+ return true
64
+ }
65
+
56
66
  export function resolveAlias(aliasName, {
57
67
  aliases = {},
58
68
  modelCatalogs = {},
@@ -72,7 +82,8 @@ export function resolveAlias(aliasName, {
72
82
  for (let i = 0; i < custom.targets.length; i++) {
73
83
  const target = custom.targets[i]
74
84
  const circuitBlocked = isCircuitOpen(target.provider)
75
- const healthFail = healthResults.find((h) => h.provider === target.provider && h.status === 'error')
85
+ const healthEntry = healthResults.find((h) => h.provider === target.provider)
86
+ const healthFail = Boolean(healthEntry && (healthEntry.status === "error" || healthEntry.status === "circuit-open" || healthEntry.ok === false || (healthEntry.status && healthEntry.status !== "ok")))
76
87
  if (circuitBlocked || healthFail) {
77
88
  skipped.push({ ...target, reason: circuitBlocked ? 'circuit_open' : 'health_failed' })
78
89
  continue
@@ -101,12 +112,15 @@ export function resolveAlias(aliasName, {
101
112
  // 2. Static explicit alias
102
113
  if (custom?.provider && custom?.model && !custom.strategy) {
103
114
  const circuitBlocked = isCircuitOpen(custom.provider)
115
+ const healthEntry = healthResults.find((h) => h.provider === custom.provider)
116
+ const healthFailed = Boolean(healthEntry && (healthEntry.status === "error" || healthEntry.status === "circuit-open" || healthEntry.ok === false || (healthEntry.status && healthEntry.status !== "ok")))
104
117
  return {
105
118
  alias: clean,
106
119
  provider: custom.provider,
107
120
  model: custom.model,
108
121
  fallbackUsed: false,
109
122
  ...(circuitBlocked ? { warning: 'circuit_open', circuitOpen: true } : {}),
123
+ ...(healthFailed ? { warning: 'health_failed', healthFailed: true } : {}),
110
124
  }
111
125
  }
112
126
 
@@ -125,7 +139,7 @@ export function resolveAlias(aliasName, {
125
139
  }
126
140
  const available = []
127
141
  for (const [provider, models] of Object.entries(modelCatalogs)) {
128
- if (isCircuitOpen(provider)) continue
142
+ if (!isProviderEligible(provider, { healthResults, isCircuitOpen })) continue
129
143
  if (!Array.isArray(models)) continue
130
144
  for (const m of models) {
131
145
  if (!m?.id) continue