@mofeng2223/dsh-claude-provider 0.2.3 → 0.3.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/README.md +13 -5
- package/README.zh-CN.md +13 -5
- package/cordis.patch.yml +0 -9
- package/lib/client.js +312 -217
- package/package.json +3 -3
- package/src/index.js +2 -297
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mofeng2223/dsh-claude-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Custom Claude provider support for DeepSeek Harness",
|
|
5
5
|
"author": "mofeng2223",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@deepseek-ai/dsh-settings": "0.1.0-rc.
|
|
27
|
+
"@deepseek-ai/dsh-settings": "0.1.0-rc.8",
|
|
28
28
|
"@deepseek-ai/schemastery": "3.18.1"
|
|
29
29
|
},
|
|
30
30
|
"exports": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=22.19"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-settings-models": "0.1.0-rc.
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-settings-models": "0.1.0-rc.8"
|
|
52
52
|
},
|
|
53
53
|
"dsh": {
|
|
54
54
|
"bundle": {
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
1
|
import z from '@deepseek-ai/schemastery'
|
|
3
2
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
4
3
|
|
|
@@ -13,16 +12,6 @@ const ProviderTypeSettings = z.object({
|
|
|
13
12
|
providerTypes: z.dict(z.const(CLAUDE_PROVIDER_TYPE)).default({}),
|
|
14
13
|
})
|
|
15
14
|
|
|
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
15
|
export const CLAUDE_DISCOVERY_API = 'mofeng-anthropic-models'
|
|
27
16
|
|
|
28
17
|
const PI_AI_SETTINGS_NS = 'llm-pi-ai'
|
|
@@ -244,265 +233,14 @@ function installClaudeModelDiscovery(ctx) {
|
|
|
244
233
|
})
|
|
245
234
|
}
|
|
246
235
|
|
|
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
236
|
/** Whether an explicitly registered provider belongs to this plugin's Claude type. */
|
|
258
237
|
export function isClaudeProviderType(settings, provider) {
|
|
259
238
|
if (typeof provider !== 'string' || provider.length === 0) return false
|
|
260
239
|
return settings?.providerTypes?.[provider] === CLAUDE_PROVIDER_TYPE
|
|
261
240
|
}
|
|
262
241
|
|
|
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(
|
|
242
|
+
export function apply(ctx) {
|
|
243
|
+
ctx.settings.register(
|
|
506
244
|
CLAUDE_PROVIDER_SETTINGS_NS,
|
|
507
245
|
ProviderTypeSettings,
|
|
508
246
|
{ base: { providerTypes: {} } },
|
|
@@ -513,38 +251,5 @@ export function apply(ctx, config) {
|
|
|
513
251
|
settingsNs: CLAUDE_PROVIDER_SETTINGS_NS,
|
|
514
252
|
settingsPath: ['providerTypes'],
|
|
515
253
|
}])
|
|
516
|
-
installClaudeModelInfoDefaults(ctx, providerTypes)
|
|
517
|
-
installForceAdaptiveThinking(ctx, providerTypes)
|
|
518
254
|
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
255
|
}
|