@mofeng2223/dsh-claude-provider 0.2.1 → 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.1",
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.6",
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.6"
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,210 +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 effortFromBudget(budget) {
308
- if (typeof budget !== 'number' || !Number.isFinite(budget)) return 'high'
309
- if (budget <= 2048) return 'low'
310
- if (budget <= 8192) return 'medium'
311
- return 'high'
312
- }
313
-
314
- function selectedEffort(state, thinking) {
315
- if (state.level !== undefined && state.level !== 'off') {
316
- return state.effortMap[state.level] ?? state.level
317
- }
318
- return effortFromBudget(thinking.budget_tokens)
319
- }
320
-
321
- /**
322
- * Convert one matching Anthropic Messages body from legacy extended thinking
323
- * to adaptive thinking. The input object is never mutated.
324
- */
325
- export function rewriteAnthropicPayload(payload, state) {
326
- if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) return payload
327
- if (payload.model !== state.model) return payload
328
- const thinking = payload.thinking
329
- if (thinking === null || typeof thinking !== 'object' || Array.isArray(thinking)) return payload
330
-
331
- if (thinking.type === 'disabled') {
332
- const rewritten = { ...payload }
333
- delete rewritten.thinking
334
- delete rewritten.output_config
335
- return rewritten
336
- }
337
-
338
- if (thinking.type !== 'enabled') return payload
339
- const { budget_tokens: _budgetTokens, ...preservedThinking } = thinking
340
- return {
341
- ...payload,
342
- thinking: { ...preservedThinking, type: 'adaptive' },
343
- output_config: {
344
- ...(payload.output_config ?? {}),
345
- effort: selectedEffort(state, thinking),
346
- },
347
- }
348
- }
349
-
350
- function withoutLegacyThinkingBeta(headers) {
351
- if (headers === undefined) return undefined
352
- const rewritten = new Headers(headers)
353
- const value = rewritten.get('anthropic-beta')
354
- if (value === null) return rewritten
355
- const features = value.split(',').map(feature => feature.trim()).filter(Boolean)
356
- const kept = features.filter(feature => feature !== INTERLEAVED_THINKING_BETA)
357
- if (kept.length === features.length) return rewritten
358
- if (kept.length === 0) rewritten.delete('anthropic-beta')
359
- else rewritten.set('anthropic-beta', kept.join(','))
360
- return rewritten
361
- }
362
-
363
- function decodeBody(body) {
364
- if (typeof body === 'string') return body
365
- if (body instanceof Uint8Array) return new TextDecoder().decode(body)
366
- if (body instanceof ArrayBuffer) return new TextDecoder().decode(new Uint8Array(body))
367
- if (ArrayBuffer.isView(body)) {
368
- return new TextDecoder().decode(new Uint8Array(body.buffer, body.byteOffset, body.byteLength))
369
- }
370
- return undefined
371
- }
372
-
373
- function rewriteJsonBody(body, state) {
374
- const text = decodeBody(body)
375
- if (text === undefined) return undefined
376
- let parsed
377
- try {
378
- parsed = JSON.parse(text)
379
- } catch {
380
- return undefined
381
- }
382
- const rewritten = rewriteAnthropicPayload(parsed, state)
383
- return rewritten === parsed ? undefined : JSON.stringify(rewritten)
384
- }
385
-
386
- async function rewriteFetchArguments(input, init, state) {
387
- if (init?.body !== undefined && init.body !== null) {
388
- const body = rewriteJsonBody(init.body, state)
389
- if (body === undefined) return undefined
390
- return [input, { ...init, body, headers: withoutLegacyThinkingBeta(init.headers) }]
391
- }
392
-
393
- if (typeof Request !== 'undefined' && input instanceof Request && input.body !== null) {
394
- const text = await input.clone().text()
395
- const body = rewriteJsonBody(text, state)
396
- if (body === undefined) return undefined
397
- const requestInit = {
398
- body,
399
- headers: withoutLegacyThinkingBeta(input.headers),
400
- }
401
- if (input.method !== 'GET' && input.method !== 'HEAD') requestInit.duplex = 'half'
402
- return [new Request(input, requestInit), init]
403
- }
404
-
405
- return undefined
406
- }
407
-
408
- function contextualStream(storage, state, next) {
409
- return {
410
- [Symbol.asyncIterator]() {
411
- let iterator
412
- const invoke = (method, value) => storage.run(state, () => {
413
- iterator ??= next()[Symbol.asyncIterator]()
414
- const operation = iterator[method]
415
- if (operation === undefined) return Promise.resolve({ done: true, value })
416
- return operation.call(iterator, value)
417
- })
418
- return {
419
- next: value => invoke('next', value),
420
- return: value => invoke('return', value),
421
- throw: error => invoke('throw', error),
422
- }
423
- },
424
- }
425
- }
426
-
427
- async function* modelAwareStream(ctx, storage, resolved, options, next) {
428
- let modelInfo
429
- try {
430
- modelInfo = await ctx.llm.resolveModelInfo(options.provider, options.model, options.signal)
431
- } catch {
432
- yield* next()
433
- return
434
- }
435
- if (!shouldUseAdaptiveThinking(modelInfo)) {
436
- yield* next()
437
- return
438
- }
439
- const state = {
440
- provider: options.provider,
441
- model: options.model,
442
- level: options.reasoningEffort === undefined ? undefined : String(options.reasoningEffort),
443
- effortMap: resolved.effortMap,
444
- }
445
- yield* contextualStream(storage, state, next)
446
- }
447
-
448
- export function apply(ctx, config) {
449
- const resolved = resolveConfig(config)
450
- const providerTypes = ctx.settings.register(
242
+ export function apply(ctx) {
243
+ ctx.settings.register(
451
244
  CLAUDE_PROVIDER_SETTINGS_NS,
452
245
  ProviderTypeSettings,
453
246
  { base: { providerTypes: {} } },
@@ -458,37 +251,5 @@ export function apply(ctx, config) {
458
251
  settingsNs: CLAUDE_PROVIDER_SETTINGS_NS,
459
252
  settingsPath: ['providerTypes'],
460
253
  }])
461
- installClaudeModelInfoDefaults(ctx, providerTypes)
462
254
  installClaudeModelDiscovery(ctx)
463
- const storage = new AsyncLocalStorage()
464
- const upstreamFetch = globalThis.fetch
465
- if (typeof upstreamFetch !== 'function') {
466
- throw new Error('claude-provider: global fetch is unavailable')
467
- }
468
-
469
- const adaptiveFetch = async (input, init) => {
470
- const state = storage.getStore()
471
- if (state === undefined) return upstreamFetch(input, init)
472
- const rewritten = await rewriteFetchArguments(input, init, state)
473
- if (rewritten !== undefined && resolved.debug) {
474
- process.stderr.write(
475
- `[claude-provider] rewrote ${state.provider}/${state.model}`
476
- + ` effort=${state.level ?? 'inferred'}\n`,
477
- )
478
- }
479
- return rewritten === undefined
480
- ? upstreamFetch(input, init)
481
- : upstreamFetch(rewritten[0], rewritten[1])
482
- }
483
-
484
- globalThis.fetch = adaptiveFetch
485
- ctx.effect(() => () => {
486
- storage.disable()
487
- if (globalThis.fetch === adaptiveFetch) globalThis.fetch = upstreamFetch
488
- })
489
-
490
- ctx.on('llm/stream', (options, next) => {
491
- if (!isClaudeProviderType(providerTypes.get(), options.provider)) return next()
492
- return modelAwareStream(ctx, storage, resolved, options, next)
493
- })
494
255
  }