@mofeng2223/dsh-claude-provider 0.2.3 → 0.3.1
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 +12 -4
- package/README.zh-CN.md +12 -4
- package/cordis.patch.yml +0 -9
- package/lib/client.js +313 -217
- package/package.json +2 -3
- package/src/index.js +3 -299
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mofeng2223/dsh-claude-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Custom Claude provider support for DeepSeek Harness",
|
|
5
5
|
"author": "mofeng2223",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@deepseek-ai/dsh-settings": "0.1.0-rc.7",
|
|
28
27
|
"@deepseek-ai/schemastery": "3.18.1"
|
|
29
28
|
},
|
|
30
29
|
"exports": {
|
|
@@ -48,7 +47,7 @@
|
|
|
48
47
|
"node": ">=22.19"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-settings-models": "0.1.
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-settings-models": "0.1.1-rc.1"
|
|
52
51
|
},
|
|
53
52
|
"dsh": {
|
|
54
53
|
"bundle": {
|
package/src/index.js
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
1
|
import z from '@deepseek-ai/schemastery'
|
|
3
|
-
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
4
2
|
|
|
5
3
|
export const name = 'claude-provider'
|
|
6
4
|
export const inject = ['llm', 'settings']
|
|
7
5
|
|
|
8
6
|
export const CLAUDE_PROVIDER_TYPE = 'claude-adaptive'
|
|
9
|
-
export const CLAUDE_PROVIDER_SETTINGS_NS =
|
|
7
|
+
export const CLAUDE_PROVIDER_SETTINGS_NS = 'dsh-claude-provider'
|
|
10
8
|
export const CLAUDE_PROVIDER_DIRECTORY_SENTINEL = 'dsh-claude-provider-type'
|
|
11
9
|
|
|
12
10
|
const ProviderTypeSettings = z.object({
|
|
13
11
|
providerTypes: z.dict(z.const(CLAUDE_PROVIDER_TYPE)).default({}),
|
|
14
12
|
})
|
|
15
13
|
|
|
16
|
-
const DEFAULT_EFFORT_MAP = Object.freeze({
|
|
17
|
-
minimal: 'low',
|
|
18
|
-
low: 'low',
|
|
19
|
-
medium: 'medium',
|
|
20
|
-
high: 'high',
|
|
21
|
-
xhigh: 'xhigh',
|
|
22
|
-
max: 'max',
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const INTERLEAVED_THINKING_BETA = 'interleaved-thinking-2025-05-14'
|
|
26
14
|
export const CLAUDE_DISCOVERY_API = 'mofeng-anthropic-models'
|
|
27
15
|
|
|
28
16
|
const PI_AI_SETTINGS_NS = 'llm-pi-ai'
|
|
@@ -244,265 +232,14 @@ function installClaudeModelDiscovery(ctx) {
|
|
|
244
232
|
})
|
|
245
233
|
}
|
|
246
234
|
|
|
247
|
-
function resolveConfig(config = {}) {
|
|
248
|
-
const effortMap = { ...DEFAULT_EFFORT_MAP, ...config.effortMap }
|
|
249
|
-
for (const [level, effort] of Object.entries(effortMap)) {
|
|
250
|
-
requireNonEmptyString(level, 'effortMap key')
|
|
251
|
-
requireNonEmptyString(effort, `effortMap.${level}`)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return { effortMap, debug: config.debug === true }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
235
|
/** Whether an explicitly registered provider belongs to this plugin's Claude type. */
|
|
258
236
|
export function isClaudeProviderType(settings, provider) {
|
|
259
237
|
if (typeof provider !== 'string' || provider.length === 0) return false
|
|
260
238
|
return settings?.providerTypes?.[provider] === CLAUDE_PROVIDER_TYPE
|
|
261
239
|
}
|
|
262
240
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const reasoning = modelInfo?.reasoning
|
|
266
|
-
if (reasoning === undefined || !Array.isArray(reasoning.efforts)) return modelInfo
|
|
267
|
-
const ids = new Set(reasoning.efforts.map(effort => String(effort.id)))
|
|
268
|
-
const toggleOnly = ids.size === 2 && ids.has('off') && ids.has('high')
|
|
269
|
-
return {
|
|
270
|
-
...modelInfo,
|
|
271
|
-
reasoning: {
|
|
272
|
-
...reasoning,
|
|
273
|
-
defaultEffort: 'high',
|
|
274
|
-
efforts: toggleOnly
|
|
275
|
-
? reasoning.efforts.map(effort => ({
|
|
276
|
-
...effort,
|
|
277
|
-
name: effort.id === 'off' ? 'Off' : effort.id === 'high' ? 'On' : effort.name,
|
|
278
|
-
}))
|
|
279
|
-
: reasoning.efforts,
|
|
280
|
-
},
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function installClaudeModelInfoDefaults(ctx, providerTypes) {
|
|
285
|
-
const llm = ctx.llm
|
|
286
|
-
const upstream = llm.resolveModelInfo
|
|
287
|
-
const wrapped = async function (provider, model, signal) {
|
|
288
|
-
const info = await upstream.call(this, provider, model, signal)
|
|
289
|
-
return isClaudeProviderType(providerTypes.get(), provider)
|
|
290
|
-
? withClaudeProviderReasoningDefaults(info)
|
|
291
|
-
: info
|
|
292
|
-
}
|
|
293
|
-
llm.resolveModelInfo = wrapped
|
|
294
|
-
ctx.effect(() => () => {
|
|
295
|
-
if (llm.resolveModelInfo === wrapped) llm.resolveModelInfo = upstream
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** Four- and five-level model profiles are adaptive; the two-level toggle is legacy budget thinking. */
|
|
300
|
-
export function shouldUseAdaptiveThinking(modelInfo) {
|
|
301
|
-
const efforts = modelInfo?.reasoning?.efforts
|
|
302
|
-
if (!Array.isArray(efforts)) return false
|
|
303
|
-
const ids = new Set(efforts.map(effort => String(effort.id)))
|
|
304
|
-
return ids.has('low') && ids.has('medium') && ids.has('high') && ids.has('max')
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function hasAdaptiveEffortMap(map) {
|
|
308
|
-
if (map === null || typeof map !== 'object' || Array.isArray(map)) return false
|
|
309
|
-
return ['low', 'medium', 'high', 'max'].every(level => typeof map[level] === 'string' && map[level].length > 0)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/** Whether a pi-ai catalog model should be serialized with adaptive thinking. */
|
|
313
|
-
export function piModelHasAdaptiveEfforts(model) {
|
|
314
|
-
return hasAdaptiveEffortMap(model?.thinkingLevelMap)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Stamp `compat.forceAdaptiveThinking` onto a pi-ai model so the Anthropic
|
|
319
|
-
* adapter builds `thinking.type: adaptive` before `@anthropic-ai/sdk` sees the
|
|
320
|
-
* payload. That is what removes the SDK deprecation warning for Opus 4.6.
|
|
321
|
-
*/
|
|
322
|
-
export function withForcedAdaptiveThinking(model, provider, settings) {
|
|
323
|
-
if (!isClaudeProviderType(settings, provider)) return model
|
|
324
|
-
if (model === null || typeof model !== 'object' || Array.isArray(model)) return model
|
|
325
|
-
if (model.compat?.forceAdaptiveThinking === true) return model
|
|
326
|
-
if (!piModelHasAdaptiveEfforts(model)) return model
|
|
327
|
-
return {
|
|
328
|
-
...model,
|
|
329
|
-
compat: { ...model.compat, forceAdaptiveThinking: true },
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function installForceAdaptiveThinking(ctx, providerTypes) {
|
|
334
|
-
const llm = ctx.llm
|
|
335
|
-
if (typeof llm.listProviders !== 'function' || typeof llm.registration !== 'function') return
|
|
336
|
-
|
|
337
|
-
const patched = new WeakSet()
|
|
338
|
-
const wrap = () => {
|
|
339
|
-
for (const provider of llm.listProviders()) {
|
|
340
|
-
const id = provider?.id
|
|
341
|
-
if (typeof id !== 'string' || id.length === 0) continue
|
|
342
|
-
let adapter
|
|
343
|
-
try {
|
|
344
|
-
adapter = llm.registration(id).adapter
|
|
345
|
-
} catch {
|
|
346
|
-
continue
|
|
347
|
-
}
|
|
348
|
-
if (adapter === null || typeof adapter !== 'object' || typeof adapter.modelOf !== 'function') continue
|
|
349
|
-
if (patched.has(adapter)) continue
|
|
350
|
-
const original = adapter.modelOf.bind(adapter)
|
|
351
|
-
adapter.modelOf = (snapshot, providerId, modelId) => (
|
|
352
|
-
withForcedAdaptiveThinking(original(snapshot, providerId, modelId), providerId, providerTypes.get())
|
|
353
|
-
)
|
|
354
|
-
patched.add(adapter)
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
wrap()
|
|
359
|
-
ctx.effect(() => ctx.on('llm/adapters-updated', wrap), 'claude-provider: force adaptive thinking')
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function effortFromBudget(budget) {
|
|
363
|
-
if (typeof budget !== 'number' || !Number.isFinite(budget)) return 'high'
|
|
364
|
-
if (budget <= 2048) return 'low'
|
|
365
|
-
if (budget <= 8192) return 'medium'
|
|
366
|
-
return 'high'
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function selectedEffort(state, thinking) {
|
|
370
|
-
if (state.level !== undefined && state.level !== 'off') {
|
|
371
|
-
return state.effortMap[state.level] ?? state.level
|
|
372
|
-
}
|
|
373
|
-
return effortFromBudget(thinking.budget_tokens)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Convert one matching Anthropic Messages body from legacy extended thinking
|
|
378
|
-
* to adaptive thinking. The input object is never mutated.
|
|
379
|
-
*/
|
|
380
|
-
export function rewriteAnthropicPayload(payload, state) {
|
|
381
|
-
if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) return payload
|
|
382
|
-
if (payload.model !== state.model) return payload
|
|
383
|
-
const thinking = payload.thinking
|
|
384
|
-
if (thinking === null || typeof thinking !== 'object' || Array.isArray(thinking)) return payload
|
|
385
|
-
|
|
386
|
-
if (thinking.type === 'disabled') {
|
|
387
|
-
const rewritten = { ...payload }
|
|
388
|
-
delete rewritten.thinking
|
|
389
|
-
delete rewritten.output_config
|
|
390
|
-
return rewritten
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (thinking.type !== 'enabled') return payload
|
|
394
|
-
const { budget_tokens: _budgetTokens, ...preservedThinking } = thinking
|
|
395
|
-
return {
|
|
396
|
-
...payload,
|
|
397
|
-
thinking: { ...preservedThinking, type: 'adaptive' },
|
|
398
|
-
output_config: {
|
|
399
|
-
...(payload.output_config ?? {}),
|
|
400
|
-
effort: selectedEffort(state, thinking),
|
|
401
|
-
},
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function withoutLegacyThinkingBeta(headers) {
|
|
406
|
-
if (headers === undefined) return undefined
|
|
407
|
-
const rewritten = new Headers(headers)
|
|
408
|
-
const value = rewritten.get('anthropic-beta')
|
|
409
|
-
if (value === null) return rewritten
|
|
410
|
-
const features = value.split(',').map(feature => feature.trim()).filter(Boolean)
|
|
411
|
-
const kept = features.filter(feature => feature !== INTERLEAVED_THINKING_BETA)
|
|
412
|
-
if (kept.length === features.length) return rewritten
|
|
413
|
-
if (kept.length === 0) rewritten.delete('anthropic-beta')
|
|
414
|
-
else rewritten.set('anthropic-beta', kept.join(','))
|
|
415
|
-
return rewritten
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function decodeBody(body) {
|
|
419
|
-
if (typeof body === 'string') return body
|
|
420
|
-
if (body instanceof Uint8Array) return new TextDecoder().decode(body)
|
|
421
|
-
if (body instanceof ArrayBuffer) return new TextDecoder().decode(new Uint8Array(body))
|
|
422
|
-
if (ArrayBuffer.isView(body)) {
|
|
423
|
-
return new TextDecoder().decode(new Uint8Array(body.buffer, body.byteOffset, body.byteLength))
|
|
424
|
-
}
|
|
425
|
-
return undefined
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function rewriteJsonBody(body, state) {
|
|
429
|
-
const text = decodeBody(body)
|
|
430
|
-
if (text === undefined) return undefined
|
|
431
|
-
let parsed
|
|
432
|
-
try {
|
|
433
|
-
parsed = JSON.parse(text)
|
|
434
|
-
} catch {
|
|
435
|
-
return undefined
|
|
436
|
-
}
|
|
437
|
-
const rewritten = rewriteAnthropicPayload(parsed, state)
|
|
438
|
-
return rewritten === parsed ? undefined : JSON.stringify(rewritten)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async function rewriteFetchArguments(input, init, state) {
|
|
442
|
-
if (init?.body !== undefined && init.body !== null) {
|
|
443
|
-
const body = rewriteJsonBody(init.body, state)
|
|
444
|
-
if (body === undefined) return undefined
|
|
445
|
-
return [input, { ...init, body, headers: withoutLegacyThinkingBeta(init.headers) }]
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (typeof Request !== 'undefined' && input instanceof Request && input.body !== null) {
|
|
449
|
-
const text = await input.clone().text()
|
|
450
|
-
const body = rewriteJsonBody(text, state)
|
|
451
|
-
if (body === undefined) return undefined
|
|
452
|
-
const requestInit = {
|
|
453
|
-
body,
|
|
454
|
-
headers: withoutLegacyThinkingBeta(input.headers),
|
|
455
|
-
}
|
|
456
|
-
if (input.method !== 'GET' && input.method !== 'HEAD') requestInit.duplex = 'half'
|
|
457
|
-
return [new Request(input, requestInit), init]
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return undefined
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function contextualStream(storage, state, next) {
|
|
464
|
-
return {
|
|
465
|
-
[Symbol.asyncIterator]() {
|
|
466
|
-
let iterator
|
|
467
|
-
const invoke = (method, value) => storage.run(state, () => {
|
|
468
|
-
iterator ??= next()[Symbol.asyncIterator]()
|
|
469
|
-
const operation = iterator[method]
|
|
470
|
-
if (operation === undefined) return Promise.resolve({ done: true, value })
|
|
471
|
-
return operation.call(iterator, value)
|
|
472
|
-
})
|
|
473
|
-
return {
|
|
474
|
-
next: value => invoke('next', value),
|
|
475
|
-
return: value => invoke('return', value),
|
|
476
|
-
throw: error => invoke('throw', error),
|
|
477
|
-
}
|
|
478
|
-
},
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
async function* modelAwareStream(ctx, storage, resolved, options, next) {
|
|
483
|
-
let modelInfo
|
|
484
|
-
try {
|
|
485
|
-
modelInfo = await ctx.llm.resolveModelInfo(options.provider, options.model, options.signal)
|
|
486
|
-
} catch {
|
|
487
|
-
yield* next()
|
|
488
|
-
return
|
|
489
|
-
}
|
|
490
|
-
if (!shouldUseAdaptiveThinking(modelInfo)) {
|
|
491
|
-
yield* next()
|
|
492
|
-
return
|
|
493
|
-
}
|
|
494
|
-
const state = {
|
|
495
|
-
provider: options.provider,
|
|
496
|
-
model: options.model,
|
|
497
|
-
level: options.reasoningEffort === undefined ? undefined : String(options.reasoningEffort),
|
|
498
|
-
effortMap: resolved.effortMap,
|
|
499
|
-
}
|
|
500
|
-
yield* contextualStream(storage, state, next)
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
export function apply(ctx, config) {
|
|
504
|
-
const resolved = resolveConfig(config)
|
|
505
|
-
const providerTypes = ctx.settings.register(
|
|
241
|
+
export function apply(ctx) {
|
|
242
|
+
ctx.settings.register(
|
|
506
243
|
CLAUDE_PROVIDER_SETTINGS_NS,
|
|
507
244
|
ProviderTypeSettings,
|
|
508
245
|
{ base: { providerTypes: {} } },
|
|
@@ -513,38 +250,5 @@ export function apply(ctx, config) {
|
|
|
513
250
|
settingsNs: CLAUDE_PROVIDER_SETTINGS_NS,
|
|
514
251
|
settingsPath: ['providerTypes'],
|
|
515
252
|
}])
|
|
516
|
-
installClaudeModelInfoDefaults(ctx, providerTypes)
|
|
517
|
-
installForceAdaptiveThinking(ctx, providerTypes)
|
|
518
253
|
installClaudeModelDiscovery(ctx)
|
|
519
|
-
const storage = new AsyncLocalStorage()
|
|
520
|
-
const upstreamFetch = globalThis.fetch
|
|
521
|
-
if (typeof upstreamFetch !== 'function') {
|
|
522
|
-
throw new Error('claude-provider: global fetch is unavailable')
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const adaptiveFetch = async (input, init) => {
|
|
526
|
-
const state = storage.getStore()
|
|
527
|
-
if (state === undefined) return upstreamFetch(input, init)
|
|
528
|
-
const rewritten = await rewriteFetchArguments(input, init, state)
|
|
529
|
-
if (rewritten !== undefined && resolved.debug) {
|
|
530
|
-
process.stderr.write(
|
|
531
|
-
`[claude-provider] rewrote ${state.provider}/${state.model}`
|
|
532
|
-
+ ` effort=${state.level ?? 'inferred'}\n`,
|
|
533
|
-
)
|
|
534
|
-
}
|
|
535
|
-
return rewritten === undefined
|
|
536
|
-
? upstreamFetch(input, init)
|
|
537
|
-
: upstreamFetch(rewritten[0], rewritten[1])
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
globalThis.fetch = adaptiveFetch
|
|
541
|
-
ctx.effect(() => () => {
|
|
542
|
-
storage.disable()
|
|
543
|
-
if (globalThis.fetch === adaptiveFetch) globalThis.fetch = upstreamFetch
|
|
544
|
-
})
|
|
545
|
-
|
|
546
|
-
ctx.on('llm/stream', (options, next) => {
|
|
547
|
-
if (!isClaudeProviderType(providerTypes.get(), options.provider)) return next()
|
|
548
|
-
return modelAwareStream(ctx, storage, resolved, options, next)
|
|
549
|
-
})
|
|
550
254
|
}
|