feat(llm): send DeepSeek user identity header

This commit is contained in:
kingwl
2026-08-11 12:39:04 +08:00
parent ac266ed2de
commit 93b0451ed5
36 changed files with 216 additions and 62 deletions

View File

@@ -19,6 +19,7 @@ import type {
} from '@deepseek-ai/dsh-llm'
import type { CredentialRef } from '@deepseek-ai/dsh-credentials'
import { idleWatchdog, timeoutOf } from '@deepseek-ai/dsh-timeout'
import type { AnonymousUserId } from '@deepseek-ai/dsh-user-id'
import { serializeRequest } from './serialize.ts'
import type { RequestDefaults } from './serialize.ts'
import { parseSse } from './sse.ts'
@@ -69,7 +70,7 @@ export interface DeepSeekConnectionOptions {
retryPolicy: ResolvedRetryPolicy
}
/** Constructor options for {@link DeepSeekAdapter}: the two resolution hooks the plugin owns. */
/** Constructor options for {@link DeepSeekAdapter}: the operation-local resolution hooks the plugin owns. */
export interface DeepSeekAdapterOptions {
/** Current validated connection facts; called once per operation. */
options: () => DeepSeekConnectionOptions
@@ -80,6 +81,8 @@ export interface DeepSeekAdapterOptions {
* `MISSING_CREDENTIAL` when no key is available anywhere.
*/
resolveApiKey: (connection: DeepSeekConnectionOptions) => Promise<string>
/** Resolve the harness-home anonymous id shared with telemetry and feedback. */
resolveUserId: () => AnonymousUserId
}
/** Default maximum idle interval while an adapter stream read is outstanding. */
@@ -216,6 +219,7 @@ export class DeepSeekAdapter extends LlmAdapter {
// sent to it can never come from different configuration generations.
const connection = this.config.options()
const apiKey = await this.config.resolveApiKey(connection)
const userId = this.config.resolveUserId()
const consumer = new AbortController()
const upstream = options.signal === undefined
? consumer.signal
@@ -226,6 +230,7 @@ export class DeepSeekAdapter extends LlmAdapter {
watchdog.signal,
connection,
apiKey,
userId,
() => { watchdog.pulse() },
)[Symbol.asyncIterator]()
let exhausted = false
@@ -268,6 +273,7 @@ export class DeepSeekAdapter extends LlmAdapter {
signal: AbortSignal,
connection: DeepSeekConnectionOptions,
apiKey: string,
userId: AnonymousUserId,
onComment: () => void,
): AsyncIterable<StreamChunk> {
const body = serializeRequest(options, connection.defaults)
@@ -279,6 +285,7 @@ export class DeepSeekAdapter extends LlmAdapter {
'content-type': 'application/json',
'accept': 'text/event-stream',
...attributionHeaders(),
'x-deepseek-harness-user-id': String(userId),
...options.sessionId !== undefined
? { 'x-deepseek-harness-session-id': String(options.sessionId) }
: {},

View File

@@ -19,6 +19,7 @@ import { credentialRef } from '@deepseek-ai/dsh-credentials'
import { environmentOf, type EnvironmentSnapshot } from '@deepseek-ai/dsh-environment'
import { deepEqualJson, installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { getOrCreateAnonymousUserId, type AnonymousUserId } from '@deepseek-ai/dsh-user-id'
import {
DEFAULT_CONTEXT_WINDOW,
DEFAULT_MAX_TOKENS,
@@ -244,7 +245,9 @@ export function apply(ctx: Context, config: Config): void {
)
}
const adapter = new DeepSeekAdapter({ options, resolveApiKey })
let userId: AnonymousUserId | undefined
const resolveUserId = (): AnonymousUserId => userId ??= getOrCreateAnonymousUserId()
const adapter = new DeepSeekAdapter({ options, resolveApiKey, resolveUserId })
ctx.llm.registerConfigurableProviders([
{ provider: PROVIDER, displayName: 'DeepSeek', settingsNs: NS, settingsPath: [] },
])