dsh-cline-pass 0.1.0
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/LICENSE +28 -0
- package/README.en.md +124 -0
- package/README.md +124 -0
- package/cordis.patch.yml +27 -0
- package/lib/adapter.js +620 -0
- package/lib/catalog.js +193 -0
- package/lib/client.js +945 -0
- package/lib/cline.js +194 -0
- package/lib/engine.js +405 -0
- package/lib/index.js +409 -0
- package/lib/panel.js +464 -0
- package/lib/protocol.js +226 -0
- package/lib/store.js +92 -0
- package/lib/tools.js +756 -0
- package/package.json +72 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-cline-pass — a self-contained Cline Pass provider for the DeepSeek
|
|
3
|
+
* Harness. It registers on the host plane:
|
|
4
|
+
*
|
|
5
|
+
* 1. One LLM provider route (default `cline-pass`), served by
|
|
6
|
+
* {@link ClinePassAdapter}.
|
|
7
|
+
* 2. A configurable-provider directory entry for that route.
|
|
8
|
+
* 3. The `cline_pass_*` tools, and the setup panel's host half.
|
|
9
|
+
*
|
|
10
|
+
* Configuration lives in the `cline-pass` settings section, so it survives
|
|
11
|
+
* restarts and reloads live. API keys never go there: accounts name a
|
|
12
|
+
* credential reference resolved per request through the credential seam.
|
|
13
|
+
*
|
|
14
|
+
* Routing behavior reimplements the MIT-licensed cline-pass-switcher findings.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-cline-pass
|
|
17
|
+
*/
|
|
18
|
+
import z from '@deepseek-ai/schemastery'
|
|
19
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
20
|
+
import { assertUsableApiKey, errorChain, LlmError } from '@deepseek-ai/dsh-llm'
|
|
21
|
+
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
|
|
22
|
+
import {
|
|
23
|
+
ClinePassAdapter,
|
|
24
|
+
DEFAULT_CONTEXT_WINDOW,
|
|
25
|
+
DEFAULT_MAX_TOKENS,
|
|
26
|
+
DEFAULT_REQUEST_IMAGE_POLICY,
|
|
27
|
+
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
28
|
+
} from './adapter.js'
|
|
29
|
+
import { catalogEntry, MODEL_CATALOG, REASONING_EFFORTS } from './catalog.js'
|
|
30
|
+
import { createEngine } from './engine.js'
|
|
31
|
+
import { createPanel, PANEL_PATH, registerPanel } from './panel.js'
|
|
32
|
+
import { createStore } from './store.js'
|
|
33
|
+
import { registerTools } from './tools.js'
|
|
34
|
+
|
|
35
|
+
export { ClinePassAdapter, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_REQUEST_IMAGE_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS }
|
|
36
|
+
export { collectImageRefs, prepareRequestImages } from './adapter.js'
|
|
37
|
+
export { MODEL_CATALOG, REASONING_EFFORTS } from './catalog.js'
|
|
38
|
+
export { createEngine } from './engine.js'
|
|
39
|
+
export { createPanel, PANEL_PATH, registerPanel } from './panel.js'
|
|
40
|
+
export { createStore } from './store.js'
|
|
41
|
+
export { buildAttempts, injectPrefs, parseRouting } from './protocol.js'
|
|
42
|
+
|
|
43
|
+
/** Stable Loader identity. */
|
|
44
|
+
export const name = 'cline-pass'
|
|
45
|
+
|
|
46
|
+
/** The LLM seam and the host tool registry. */
|
|
47
|
+
export const inject = ['llm', 'tools']
|
|
48
|
+
|
|
49
|
+
/** The settings namespace this plugin owns. */
|
|
50
|
+
export const DEFAULT_SETTINGS_NS = 'cline-pass'
|
|
51
|
+
|
|
52
|
+
/** The provider route registered when configuration names none. */
|
|
53
|
+
export const DEFAULT_PROVIDER = 'cline-pass'
|
|
54
|
+
|
|
55
|
+
/** The credential name used when no account declares one. */
|
|
56
|
+
export const DEFAULT_API_KEY_ENV = 'CLINE_PASS_API_KEY'
|
|
57
|
+
|
|
58
|
+
/** Cline Pass subscription models current at release time; refreshed on demand. */
|
|
59
|
+
export const DEFAULT_MODELS = [
|
|
60
|
+
'cline-pass/glm-5.3-flash',
|
|
61
|
+
'cline-pass/kimi-k3',
|
|
62
|
+
'cline-pass/deepseek-v4-flash',
|
|
63
|
+
'cline-pass/deepseek-v4.1-flash',
|
|
64
|
+
'cline-pass/qwen3.8-max',
|
|
65
|
+
'cline-pass/minimax-m3',
|
|
66
|
+
'cline-pass/glm-5.3',
|
|
67
|
+
'cline-pass/glm-5.2',
|
|
68
|
+
'cline-pass/deepseek-v4-pro',
|
|
69
|
+
'cline-pass/mimo-v2.5-pro',
|
|
70
|
+
'cline-pass/mimo-v2.5',
|
|
71
|
+
'cline-pass/kimi-k2.6',
|
|
72
|
+
'cline-pass/qwen3.7-plus',
|
|
73
|
+
'cline-pass/kimi-k2.7-code',
|
|
74
|
+
'cline-pass/qwen3.7-max',
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
/** One Cline Pass account: a credential reference plus routing participation. */
|
|
78
|
+
export const AccountProfile = z.object({
|
|
79
|
+
displayName: z.string().default(''),
|
|
80
|
+
apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV),
|
|
81
|
+
enabled: z.boolean().default(true),
|
|
82
|
+
baseURL: z.string().default(''),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/** One model's catalog override, merged over the published metadata. */
|
|
86
|
+
export const ModelProfile = z.object({
|
|
87
|
+
name: z.string().default(''),
|
|
88
|
+
contextWindow: z.number().default(0),
|
|
89
|
+
maxTokens: z.number().default(0),
|
|
90
|
+
reasoning: z.union([z.boolean(), z.const(undefined)]).default(undefined),
|
|
91
|
+
input: z.array(z.string()).default([]),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
/** One model's upstream pin. */
|
|
95
|
+
export const PinProfile = z.object({
|
|
96
|
+
upstreams: z.array(z.string()).default([]),
|
|
97
|
+
exclude: z.array(z.string()).default([]),
|
|
98
|
+
pinMode: z.string().default('strict'),
|
|
99
|
+
sort: z.string().default(''),
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
/** Validated plugin configuration (this plugin's settings section). */
|
|
103
|
+
export const Config = z.object({
|
|
104
|
+
provider: z.string().default(DEFAULT_PROVIDER),
|
|
105
|
+
displayName: z.string().default('Cline Pass'),
|
|
106
|
+
baseURL: z.string().default('https://api.cline.bot/api/v1'),
|
|
107
|
+
apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV),
|
|
108
|
+
accounts: z.dict(AccountProfile).default({}),
|
|
109
|
+
accountMode: z.string().default('single'),
|
|
110
|
+
activeAccount: z.string().default(''),
|
|
111
|
+
knownModels: z.array(z.string()).default(DEFAULT_MODELS),
|
|
112
|
+
models: z.dict(ModelProfile).default({}),
|
|
113
|
+
perModel: z.dict(PinProfile).default({}),
|
|
114
|
+
reasoningModels: z.boolean().default(true),
|
|
115
|
+
defaultContextWindow: z.number().default(DEFAULT_CONTEXT_WINDOW),
|
|
116
|
+
defaultMaxTokens: z.number().default(DEFAULT_MAX_TOKENS),
|
|
117
|
+
streamIdleTimeoutMs: z.number().default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
|
118
|
+
exposeCatalog: z.boolean().default(false),
|
|
119
|
+
historyLimit: z.number().default(100),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
/** Redact a secret, keeping just enough to recognize it. */
|
|
123
|
+
function maskKey(value) {
|
|
124
|
+
const key = String(value ?? '')
|
|
125
|
+
if (key.length === 0) return ''
|
|
126
|
+
if (key.length <= 10) return `${key.slice(0, 2)}…${key.slice(-2)}`
|
|
127
|
+
return `${key.slice(0, 6)}…${key.slice(-4)}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Register the Cline Pass provider on the harness LLM seam, expose its
|
|
132
|
+
* management tools, and publish its setup panel.
|
|
133
|
+
*
|
|
134
|
+
* @param ctx - host plugin context.
|
|
135
|
+
* @param config - validated plugin configuration.
|
|
136
|
+
*/
|
|
137
|
+
export function apply(ctx, config) {
|
|
138
|
+
const store = createStore({ historyLimit: config.historyLimit })
|
|
139
|
+
let liveConfig = config
|
|
140
|
+
let current = () => liveConfig
|
|
141
|
+
const live = () => current()
|
|
142
|
+
|
|
143
|
+
// ── account pool ──────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
let roundRobin = 0
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Resolve the configured accounts.
|
|
149
|
+
*
|
|
150
|
+
* An empty `accounts` dictionary means one implicit account on the top-level
|
|
151
|
+
* fields, so a fresh install works as soon as `CLINE_PASS_API_KEY` exists.
|
|
152
|
+
*/
|
|
153
|
+
const accountProfiles = () => {
|
|
154
|
+
const cfg = live()
|
|
155
|
+
const declared = Object.entries(cfg.accounts ?? {})
|
|
156
|
+
if (declared.length === 0) {
|
|
157
|
+
return [['default', {
|
|
158
|
+
displayName: cfg.displayName,
|
|
159
|
+
apiKeyEnv: credentialRef(cfg.apiKeyEnv),
|
|
160
|
+
enabled: true,
|
|
161
|
+
baseURL: cfg.baseURL,
|
|
162
|
+
}]]
|
|
163
|
+
}
|
|
164
|
+
return declared.map(([key, profile]) => [key, {
|
|
165
|
+
displayName: String(profile?.displayName ?? '') || key,
|
|
166
|
+
apiKeyEnv: credentialRef(profile?.apiKeyEnv || cfg.apiKeyEnv),
|
|
167
|
+
enabled: profile?.enabled !== false,
|
|
168
|
+
baseURL: String(profile?.baseURL ?? '') || cfg.baseURL,
|
|
169
|
+
}])
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Resolve one credential reference through the credential seam, falling back
|
|
174
|
+
* to the launching environment when no credentials service is composed.
|
|
175
|
+
* @returns the key value, or undefined when nothing is stored.
|
|
176
|
+
*/
|
|
177
|
+
const resolveKey = async (ref) => {
|
|
178
|
+
const credentials = ctx.get('credentials')
|
|
179
|
+
if (credentials !== undefined) {
|
|
180
|
+
const hit = await credentials.resolve(ref)
|
|
181
|
+
const value = hit?.value
|
|
182
|
+
return value !== undefined && String(value).length > 0 ? String(value) : undefined
|
|
183
|
+
}
|
|
184
|
+
const ambient = launchEnvironmentOf(ctx).get(ref)
|
|
185
|
+
return ambient !== undefined && ambient.value.length > 0 ? ambient.value : undefined
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Pick the account for the next request and resolve its key. */
|
|
189
|
+
const resolveAccount = async () => {
|
|
190
|
+
const cfg = live()
|
|
191
|
+
const enabled = accountProfiles().filter(([, profile]) => profile.enabled)
|
|
192
|
+
if (enabled.length === 0) {
|
|
193
|
+
throw new LlmError('cline-pass: every configured account is disabled; enable one or add another', 'MISSING_CREDENTIAL')
|
|
194
|
+
}
|
|
195
|
+
let picked
|
|
196
|
+
if (cfg.accountMode === 'roundrobin' && enabled.length > 1) {
|
|
197
|
+
picked = enabled[roundRobin % enabled.length]
|
|
198
|
+
roundRobin = (roundRobin + 1) % 1000000000
|
|
199
|
+
} else {
|
|
200
|
+
picked = enabled.find(([key]) => key === cfg.activeAccount) ?? enabled[0]
|
|
201
|
+
}
|
|
202
|
+
const [key, profile] = picked
|
|
203
|
+
const value = await resolveKey(profile.apiKeyEnv)
|
|
204
|
+
if (value === undefined) {
|
|
205
|
+
throw new LlmError(
|
|
206
|
+
`cline-pass: no API key for account "${key}"; store it at ${profile.apiKeyEnv} (cline_pass_accounts action="add" name=${key} key=… writes it, or use the dsh Models page), or export ${profile.apiKeyEnv} in the launching environment`,
|
|
207
|
+
'MISSING_CREDENTIAL',
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
return { name: key, key: assertUsableApiKey(value, 'cline-pass', profile.apiKeyEnv), baseURL: profile.baseURL }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── connection facts ──────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/** The model ids the route advertises: configured, plus the catalog when asked. */
|
|
216
|
+
const modelIds = () => {
|
|
217
|
+
const cfg = live()
|
|
218
|
+
const ids = (cfg.knownModels ?? []).map(String)
|
|
219
|
+
if (cfg.exposeCatalog === true) {
|
|
220
|
+
for (const id of store.catalog().ids ?? []) if (!ids.includes(id)) ids.push(String(id))
|
|
221
|
+
}
|
|
222
|
+
return ids
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const connection = () => {
|
|
226
|
+
const cfg = live()
|
|
227
|
+
return {
|
|
228
|
+
baseURL: cfg.baseURL,
|
|
229
|
+
displayName: cfg.displayName,
|
|
230
|
+
models: modelIds().map((id) => {
|
|
231
|
+
const entry = catalogEntry(id) ?? {}
|
|
232
|
+
const override = cfg.models?.[id] ?? {}
|
|
233
|
+
return {
|
|
234
|
+
id,
|
|
235
|
+
name: override.name || entry.name || id.replace(/^cline-pass\//, ''),
|
|
236
|
+
contextWindow: override.contextWindow || entry.contextWindow,
|
|
237
|
+
maxTokens: override.maxTokens || entry.maxTokens,
|
|
238
|
+
reasoning: override.reasoning ?? entry.reasoning,
|
|
239
|
+
input: override.input?.length ? override.input : entry.input,
|
|
240
|
+
}
|
|
241
|
+
}),
|
|
242
|
+
defaultContextWindow: cfg.defaultContextWindow,
|
|
243
|
+
maxTokens: cfg.defaultMaxTokens,
|
|
244
|
+
reasoningModels: cfg.reasoningModels,
|
|
245
|
+
streamIdleTimeoutMs: cfg.streamIdleTimeoutMs,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Best context-window guess for one model: the largest window OpenRouter
|
|
251
|
+
* reports for its endpoints, when a probe has seen them.
|
|
252
|
+
*/
|
|
253
|
+
const discoveredContext = (model) => {
|
|
254
|
+
const detail = store.metaOf(model)?.upstreamDetail ?? {}
|
|
255
|
+
let max = 0
|
|
256
|
+
for (const entry of Object.values(detail)) max = Math.max(max, Number(entry?.context ?? 0))
|
|
257
|
+
return max > 0 ? max : undefined
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── engine + adapter ──────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
const engine = createEngine({
|
|
263
|
+
resolveAccount,
|
|
264
|
+
store,
|
|
265
|
+
logger: ctx.logger,
|
|
266
|
+
attemptTimeoutMs: 180000,
|
|
267
|
+
probeTimeoutMs: 60000,
|
|
268
|
+
})
|
|
269
|
+
engine.setPinReader((model) => live().perModel?.[model] ?? {})
|
|
270
|
+
|
|
271
|
+
const adapter = new ClinePassAdapter({
|
|
272
|
+
connection,
|
|
273
|
+
modelMeta: (model) => store.metaOf(model),
|
|
274
|
+
pin: (model) => live().perModel?.[model] ?? {},
|
|
275
|
+
resolveAccount,
|
|
276
|
+
discoveredContext,
|
|
277
|
+
// Images reach an adapter as durable attachment references, never as bytes,
|
|
278
|
+
// so the adapter resolves them through the attachment service per request.
|
|
279
|
+
// Resolved lazily: a text-only call must not require that service.
|
|
280
|
+
resolveAttachments: () => ctx.get('attachments'),
|
|
281
|
+
record: (model, info) => store.record({ model, ...info }),
|
|
282
|
+
learnUpstream: (model, upstream, status, note, ms) => store.learnUpstream(model, upstream, status, note, ms),
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const provider = String(config.provider ?? '').trim() || DEFAULT_PROVIDER
|
|
286
|
+
if (config.provider !== provider) {
|
|
287
|
+
throw new LlmError('cline-pass: `provider` must be a non-empty route name', 'INVALID_ADAPTER')
|
|
288
|
+
}
|
|
289
|
+
ctx.llm.registerAdapter([provider], adapter)
|
|
290
|
+
|
|
291
|
+
// The directory entry is what makes the web Models page treat this settings
|
|
292
|
+
// namespace as the configuration of this route; a conflict here is cosmetic
|
|
293
|
+
// and must not take the route down with it.
|
|
294
|
+
try {
|
|
295
|
+
ctx.llm.registerConfigurableProviders([{
|
|
296
|
+
provider,
|
|
297
|
+
displayName: config.displayName,
|
|
298
|
+
settingsNs: DEFAULT_SETTINGS_NS,
|
|
299
|
+
settingsPath: [],
|
|
300
|
+
}])
|
|
301
|
+
} catch (error) {
|
|
302
|
+
ctx.logger?.warn('[cline-pass] configurable-provider directory entry refused: %s', errorChain(error))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── configuration surface ─────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Persist one shallow patch into this plugin's settings section.
|
|
309
|
+
*
|
|
310
|
+
* Nested dictionaries (`accounts`, `perModel`) are always written whole by
|
|
311
|
+
* the caller, so the section's merge semantics never half-apply a change.
|
|
312
|
+
* Without a settings service the patch is applied in memory, which keeps the
|
|
313
|
+
* session working but does not survive a restart.
|
|
314
|
+
*/
|
|
315
|
+
const updateConfig = async (patch) => {
|
|
316
|
+
const settings = ctx.get('settings')
|
|
317
|
+
if (settings === undefined) {
|
|
318
|
+
liveConfig = { ...liveConfig, ...patch }
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
await settings.update(DEFAULT_SETTINGS_NS, patch)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
325
|
+
settingsCtx.settings.installSection(ctx, DEFAULT_SETTINGS_NS, Config, config, {
|
|
326
|
+
setSource: (source) => {
|
|
327
|
+
current = source
|
|
328
|
+
},
|
|
329
|
+
// Every consumer reads the live config through a thunk, so a settings
|
|
330
|
+
// change reaches the next request with nothing to re-register.
|
|
331
|
+
onChange: () => {},
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
// ── catalog ───────────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/** Rescan the official subscription list and adopt models that are new. */
|
|
338
|
+
const refreshCatalog = async () => {
|
|
339
|
+
const scan = await engine.officialModels({})
|
|
340
|
+
const cfg = live()
|
|
341
|
+
const currentModels = (cfg.knownModels ?? []).map(String)
|
|
342
|
+
const added = scan.models.filter((id) => !currentModels.includes(id))
|
|
343
|
+
if (added.length > 0) await updateConfig({ knownModels: [...currentModels, ...added] })
|
|
344
|
+
store.setCatalog({
|
|
345
|
+
ids: [...new Set([...scan.models, ...(cfg.exposeCatalog === true ? await engine.gatewayCatalog({}).catch(() => []) : [])])],
|
|
346
|
+
sources: scan.sources,
|
|
347
|
+
})
|
|
348
|
+
return { added, models: scan.models, sources: scan.sources }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── tools ─────────────────────────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
// One control surface serves both faces of the configuration: the
|
|
354
|
+
// `cline_pass_*` tools an agent calls, and the browser setup panel. Sharing
|
|
355
|
+
// it is what keeps the two from ever disagreeing about what is configured.
|
|
356
|
+
const control = {
|
|
357
|
+
providerName: provider,
|
|
358
|
+
displayName: config.displayName,
|
|
359
|
+
settingsAvailable: () => ctx.get('settings') !== undefined,
|
|
360
|
+
routeRegistered: () => true,
|
|
361
|
+
readConfig: () => live(),
|
|
362
|
+
updateConfig,
|
|
363
|
+
accounts: () => accountProfiles().map(([key, profile]) => ({
|
|
364
|
+
key,
|
|
365
|
+
displayName: profile.displayName,
|
|
366
|
+
apiKeyEnv: String(profile.apiKeyEnv),
|
|
367
|
+
enabled: profile.enabled,
|
|
368
|
+
baseURL: profile.baseURL,
|
|
369
|
+
})),
|
|
370
|
+
accountsWithKeys: async () => await Promise.all(accountProfiles().map(async ([key, profile]) => {
|
|
371
|
+
const value = await resolveKey(profile.apiKeyEnv).catch(() => undefined)
|
|
372
|
+
return {
|
|
373
|
+
key,
|
|
374
|
+
displayName: profile.displayName,
|
|
375
|
+
apiKeyEnv: String(profile.apiKeyEnv),
|
|
376
|
+
enabled: profile.enabled,
|
|
377
|
+
keyConfigured: value !== undefined,
|
|
378
|
+
keyHint: maskKey(value),
|
|
379
|
+
}
|
|
380
|
+
})),
|
|
381
|
+
readCredential: async (ref) => (await resolveKey(credentialRef(ref))) ?? '',
|
|
382
|
+
setCredential: async (ref, value) => {
|
|
383
|
+
const credentials = ctx.get('credentials')
|
|
384
|
+
if (credentials === undefined) {
|
|
385
|
+
throw new Error('the dsh credentials service is unavailable, so the key cannot be stored; set the environment variable instead')
|
|
386
|
+
}
|
|
387
|
+
await credentials.set(credentialRef(ref), String(value))
|
|
388
|
+
},
|
|
389
|
+
refreshCatalog,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
registerTools(ctx, { store, engine, control })
|
|
393
|
+
|
|
394
|
+
// The same surface as an ordinary browser page, for users who would rather
|
|
395
|
+
// click than compose a tool call. Absent in a headless composition.
|
|
396
|
+
try {
|
|
397
|
+
registerPanel(ctx, { control, engine, store, logger: ctx.logger })
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ctx.logger?.warn('[cline-pass] setup panel unavailable (the tools still work): %s', errorChain(error))
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Keep the provider directory honest without adopting anything: an explicit
|
|
403
|
+
// `cline_pass_models refresh` is what changes the model list.
|
|
404
|
+
void refreshCatalog().catch((error) => {
|
|
405
|
+
ctx.logger?.warn('[cline-pass] initial catalog scan failed (models stay on the configured list): %s', errorChain(error))
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
ctx.logger?.info('[cline-pass] provider route "%s" registered with %d model(s)', provider, modelIds().length)
|
|
409
|
+
}
|