@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mofeng2223/dsh-claude-provider",
3
- "version": "0.2.3",
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.7",
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.7"
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
- /** Give typed Claude routes a concrete default and readable labels for toggle-only models. */
264
- export function withClaudeProviderReasoningDefaults(modelInfo) {
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
  }