fix(web): fence request telemetry lifecycles
This commit is contained in:
@@ -60,9 +60,15 @@
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-token-meter": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@deepseek-ai/dsh-token-meter": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-storage": "workspace:^",
|
||||
"@deepseek-ai/dsh-storage-domain": "workspace:^",
|
||||
|
||||
@@ -41,7 +41,7 @@ export const muxFrameSchema = z.discriminatedUnion('type', [
|
||||
type: z.literal('session/model-request'),
|
||||
sessionId: sessionIdSchema,
|
||||
turn: z.number().int().positive(),
|
||||
step: z.number().int().nonnegative(),
|
||||
step: z.number().int().positive(),
|
||||
provider: z.string().min(1),
|
||||
model: z.string().min(1),
|
||||
contextTokens: z.number().int().nonnegative().optional(),
|
||||
|
||||
@@ -33,6 +33,12 @@ export type ToolEventView =
|
||||
|
||||
/** Atomic telemetry captured at one observed model-request boundary. */
|
||||
export interface ModelRequestTelemetry {
|
||||
/**
|
||||
* Agent request identity and resolved route. The current StatsLine consumes
|
||||
* only occupancy, while the complete snapshot preserves provenance for
|
||||
* diagnostics and later request-bound consumers without consulting mutable
|
||||
* selected-model state.
|
||||
*/
|
||||
turn: number
|
||||
step: number
|
||||
provider: string
|
||||
|
||||
@@ -13,7 +13,8 @@ import type {
|
||||
GenerateOptions, LlmCallConfig, LlmModelInfo, LlmModelReasoningInfo, LlmProviderInfo,
|
||||
LlmResolvedModelInfo, StreamChunk,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
import type { RpcRequest } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
|
||||
@@ -51,7 +52,6 @@ class CatalogAdapter extends LlmAdapter {
|
||||
provider,
|
||||
id: model,
|
||||
name: model,
|
||||
context: { contextWindow: model === 'private-preview' ? 128_000 : 64_000 },
|
||||
...this.reasoning === undefined ? {} : { reasoning: this.reasoning },
|
||||
})
|
||||
}
|
||||
@@ -70,7 +70,15 @@ const REASONING: LlmModelReasoningInfo = {
|
||||
defaultEffort: ReasoningEffortId('high'),
|
||||
}
|
||||
|
||||
async function hostContext(): Promise<Context> {
|
||||
async function harness(logged?: {
|
||||
provider: string
|
||||
model: string
|
||||
reasoningEffort?: ReasoningEffortId
|
||||
}): Promise<{
|
||||
ctx: Context
|
||||
agent: Agent
|
||||
sessionId: SessionId
|
||||
}> {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt, { persona: '' })
|
||||
@@ -90,19 +98,6 @@ async function hostContext(): Promise<Context> {
|
||||
{ provider: 'duplicate', id: 'same', name: 'Same' },
|
||||
{ provider: 'duplicate', id: 'same', name: 'Same Again' },
|
||||
]))
|
||||
return ctx
|
||||
}
|
||||
|
||||
async function harness(logged?: {
|
||||
provider: string
|
||||
model: string
|
||||
reasoningEffort?: ReasoningEffortId
|
||||
}): Promise<{
|
||||
ctx: Context
|
||||
agent: Agent
|
||||
sessionId: SessionId
|
||||
}> {
|
||||
const ctx = await hostContext()
|
||||
const session = ctx.sessions.create()
|
||||
if (logged !== undefined) {
|
||||
session.append('request/header', { header: { config: logged }, reason: 'initial' })
|
||||
@@ -129,12 +124,7 @@ describe('Web session model selection', () => {
|
||||
model: 'private-preview',
|
||||
reasoningEffort: ReasoningEffortId('max'),
|
||||
})
|
||||
const api = createApiProxy(ctx, {
|
||||
provider: 'deepseek',
|
||||
model: 'deepseek-chat',
|
||||
cwd: '/tmp',
|
||||
workspaceRoot: '/tmp',
|
||||
})
|
||||
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
|
||||
|
||||
const catalog = expectValue(await api.sessions.models(request({ sessionId })))
|
||||
expect(catalog.current).toEqual({
|
||||
@@ -175,12 +165,7 @@ describe('Web session model selection', () => {
|
||||
|
||||
it('accepts an advisory-unlisted model, rejects an unavailable provider, and switches only after the next assembly', async () => {
|
||||
const { ctx, agent, sessionId } = await harness()
|
||||
const api = createApiProxy(ctx, {
|
||||
provider: 'deepseek',
|
||||
model: 'deepseek-chat',
|
||||
cwd: '/tmp',
|
||||
workspaceRoot: '/tmp',
|
||||
})
|
||||
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
|
||||
const seed: LlmCallConfig = { provider: 'seed', model: 'seed', temperature: 0.2 }
|
||||
const signal = new AbortController().signal
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@ describe('events frame schemas', () => {
|
||||
type: 'session/model-request',
|
||||
sessionId: 's',
|
||||
turn: 2,
|
||||
step: 0,
|
||||
step: 1,
|
||||
provider: 'deepseek',
|
||||
model: 'deepseek-chat',
|
||||
contextTokens: 8_000,
|
||||
@@ -392,7 +392,7 @@ describe('events frame schemas', () => {
|
||||
expect(() => muxFrameSchema.parse({ type: 'unknown/frame' })).toThrow()
|
||||
for (const invalid of [
|
||||
{ type: 'session/model-request', sessionId: 's', turn: 0, step: 1, provider: 'p', model: 'm' },
|
||||
{ type: 'session/model-request', sessionId: 's', turn: 1, step: -1, provider: 'p', model: 'm' },
|
||||
{ type: 'session/model-request', sessionId: 's', turn: 1, step: 0, provider: 'p', model: 'm' },
|
||||
{ type: 'session/model-request', sessionId: 's', turn: 1, step: 1, provider: 'p', model: 'm', contextTokens: -1 },
|
||||
{ type: 'session/model-request', sessionId: 's', turn: 1, step: 1, provider: 'p', model: 'm', contextWindow: 0 },
|
||||
{ type: 'session/projection', sessionId: 's', key: '', value: null, seq: 0 },
|
||||
|
||||
Reference in New Issue
Block a user