mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
/**
|
|
* Test-only direct-agent turn driver shared by assembled Loader fixtures.
|
|
* @module @deepseek-ai/dsh-loader-smoke/agent-turn
|
|
*/
|
|
|
|
import type { Context } from '@deepseek-ai/cordis'
|
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
import { createUserMessage, type TokenUsage } from '@deepseek-ai/dsh-llm'
|
|
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
|
|
|
/** Result envelope consumed only by snapshot and composition tests. */
|
|
export interface FixtureTurnResult {
|
|
readonly type: 'result'
|
|
readonly sessionId: string
|
|
readonly output: string
|
|
readonly usage?: TokenUsage
|
|
}
|
|
|
|
/** Options for one fixture turn against exactly one configured root agent. */
|
|
export interface FixtureTurnOptions {
|
|
readonly task: string
|
|
readonly onEvent?: (sessionId: string, event: SessionEvent) => void
|
|
}
|
|
|
|
function addUsage(total: TokenUsage | undefined, step: TokenUsage): TokenUsage {
|
|
const next: TokenUsage = {
|
|
inputTokens: (total?.inputTokens ?? 0) + step.inputTokens,
|
|
outputTokens: (total?.outputTokens ?? 0) + step.outputTokens,
|
|
}
|
|
for (const key of ['cacheReadTokens', 'cacheWriteTokens', 'reasoningTokens'] as const) {
|
|
if (total?.[key] !== undefined || step[key] !== undefined) next[key] = (total?.[key] ?? 0) + (step[key] ?? 0)
|
|
}
|
|
return next
|
|
}
|
|
|
|
function assistantText(event: Extract<SessionEvent, { type: 'assistant/message' }>): string | undefined {
|
|
const blocks = event.data.message.content.filter(block => block.type === 'text')
|
|
return blocks.length === 0 ? undefined : blocks.map(block => block.text).join('')
|
|
}
|
|
|
|
function onlyRootAgent(ctx: Context): Agent {
|
|
const agents = ctx.get('agents')?.roots() ?? []
|
|
const [agent] = agents
|
|
if (agent === undefined || agents.length !== 1) {
|
|
throw new Error(`fixture turn requires exactly one top-level agent, found ${agents.length}`)
|
|
}
|
|
return agent
|
|
}
|
|
|
|
/**
|
|
* Drive one task from its durable inbox receipt through whole-agent idle.
|
|
* @param ctx - settled Loader context with exactly one configured root agent.
|
|
* @param options - task and optional canonical-event observer.
|
|
* @returns the final assistant text and accumulated model usage.
|
|
*/
|
|
export async function runFixtureTurn(ctx: Context, options: FixtureTurnOptions): Promise<FixtureTurnResult> {
|
|
const agent = onlyRootAgent(ctx)
|
|
await agent.whenIdle()
|
|
|
|
const message = createUserMessage({
|
|
content: [{ type: 'text', text: options.task }],
|
|
source: { kind: 'user' },
|
|
})
|
|
let received = false
|
|
let output = ''
|
|
const usageByStep = new Map<string, TokenUsage>()
|
|
const disposeListener = ctx.on('session/event', (session, event) => {
|
|
if (session !== agent.session) return
|
|
if (!received) {
|
|
if (event.type !== 'agent/inbox/spliced'
|
|
|| !event.data.inserted.some(inserted => inserted.id === message.id)) return
|
|
received = true
|
|
}
|
|
options.onEvent?.(session.id, event)
|
|
if (event.type === 'assistant/chunk' && event.data.chunk.type === 'usage') {
|
|
usageByStep.set(`${event.data.turn}/${event.data.step}`, event.data.chunk.usage)
|
|
}
|
|
if (event.type === 'assistant/message') {
|
|
output = assistantText(event) ?? output
|
|
if (event.data.usage !== undefined) {
|
|
usageByStep.set(`${event.data.turn}/${event.data.step}`, event.data.usage)
|
|
}
|
|
}
|
|
})
|
|
|
|
try {
|
|
agent.followup(message)
|
|
await agent.whenIdle()
|
|
} finally {
|
|
disposeListener()
|
|
}
|
|
await ctx.sessions.flush(agent.session)
|
|
const usage = [...usageByStep.values()].reduce<TokenUsage | undefined>(addUsage, undefined)
|
|
return {
|
|
type: 'result',
|
|
sessionId: agent.session.id,
|
|
output,
|
|
...usage === undefined ? {} : { usage },
|
|
}
|
|
}
|