fix(client): consume typed business session events

This commit is contained in:
imccyu
2026-08-09 18:42:25 +08:00
parent 3d70889a8d
commit 10464d155d
47 changed files with 405 additions and 359 deletions

View File

@@ -16,8 +16,8 @@
"default": "./lib/invariant.js"
},
"./types": {
"types": "./lib/types/types.d.ts",
"default": "./lib/types/types.js"
"types": "./lib/types/session-types.d.ts",
"default": "./lib/types/session-types.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"

View File

@@ -10,7 +10,7 @@ import type { Context, Events } from 'cordis'
import { scopeTarget } from '@deepseek-ai/dsh-scope'
import type { Scoped } from '@deepseek-ai/dsh-scope'
import type { AssembleContext } from '@deepseek-ai/dsh-system-prompt'
import type { Agent } from './types.ts'
import type { Agent } from './runtime-types.ts'
/** Extract the parameter tuple from an event handler type (its `this` is not part of the tuple). */
type Params<F> = F extends (...args: infer P) => unknown ? P : never

View File

@@ -6,9 +6,7 @@
import type { MessageId } from '@deepseek-ai/dsh-llm'
import type { Session, SessionEventMap, UserMessage } from '@deepseek-ai/dsh-session'
/** One of the two ordered pending-message lists owned by an agent. */
export type InboxTarget = 'next-turn' | 'next-step'
import type { InboxTarget } from './session-types.ts'
/** Mutable state privately owned by an {@link Inbox}. */
type InboxState = Record<InboxTarget, UserMessage[]>

View File

@@ -13,9 +13,10 @@ import { scopeTarget } from '@deepseek-ai/dsh-scope'
import type { Scoped } from '@deepseek-ai/dsh-scope'
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
import type { TypeRTContext, TypeRTLookup } from '@deepseek-ai/dsh-type-meta'
import type { Agent, AgentOptions } from './types.ts'
import type { Agent, AgentOptions } from './runtime-types.ts'
export * from './types.ts'
export * from './runtime-types.ts'
export * from './session-types.ts'
export * from './inbox.ts'
export * from './model-selection.ts'
export { agentCarrier, agentEvents, assembleContextFor, emitAgentEvent } from './dispatch.ts'

View File

@@ -2,7 +2,7 @@
* Public agent types and live-runtime events. Durable transcript facts and
* turn/step boundaries remain `@deepseek-ai/dsh-session` events.
*
* @module @deepseek-ai/dsh-agent/types
* @module @deepseek-ai/dsh-agent
*/
import type { Context } from 'cordis'
@@ -10,7 +10,8 @@ import type { Scoped } from '@deepseek-ai/dsh-scope'
import type { LlmCallConfig, LlmFailure, ResolvedRetryPolicy } from '@deepseek-ai/dsh-llm'
import type { AgentCancelCause, Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session'
export type { AgentCancelCause } from '@deepseek-ai/dsh-session'
import type { Inbox, InboxTarget } from './inbox.ts'
import type { Inbox } from './inbox.ts'
import type { InboxTarget } from './session-types.ts'
import type {} from '@deepseek-ai/dsh-system-prompt'
declare module '@deepseek-ai/dsh-system-prompt' {
interface AssembleContext {
@@ -289,20 +290,3 @@ declare module 'cordis' {
'agent/error'(this: Scoped<Agent>, payload: { agent: Agent; turn: number; step: number; error: unknown }): void
}
}
declare module '@deepseek-ai/dsh-session/types' {
interface SessionEventMap {
/**
* One normalized mutation of an agent's durable pending-message lists.
* Live dispatch precedes projection mutation, so synchronous observers may
* read the pre-splice inbox to recover the removed messages.
*/
'agent/inbox/spliced': {
target: InboxTarget
start: number
removedCount?: number
inserted: UserMessage[]
outcome?: 'canceled'
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* Durable agent session-event vocabulary shared with type-only consumers.
*
* @module @deepseek-ai/dsh-agent/types
*/
import type { UserMessage } from '@deepseek-ai/dsh-llm/types'
/** One of the two ordered pending-message lists owned by an agent. */
export type InboxTarget = 'next-turn' | 'next-step'
declare module '@deepseek-ai/dsh-session/types' {
interface SessionEventMap {
/**
* One normalized mutation of an agent's durable pending-message lists.
* Live dispatch precedes projection mutation, so synchronous observers may
* read the pre-splice inbox to recover the removed messages.
*/
'agent/inbox/spliced': {
target: InboxTarget
start: number
removedCount?: number
inserted: UserMessage[]
outcome?: 'canceled'
}
}
}

View File

@@ -89,7 +89,7 @@ describe('gen-persistence-catalog collectLogEvents', () => {
it('hard-errors on an extends clause (inherited keys would escape the catalog)', () => {
expect(() => collectLogEvents(make({
'packages/group/fix/src/types.ts':
'interface Extra { \'fix/hidden\': { turn: number } }\ndeclare module \'@deepseek-ai/dsh-session\' {\n interface SessionEventMap extends Extra {\n /** Declared directly. */\n \'fix/direct\': { turn: number }\n }\n}\n',
'interface Extra { \'fix/hidden\': { turn: number } }\ndeclare module \'@deepseek-ai/dsh-session/types\' {\n interface SessionEventMap extends Extra {\n /** Declared directly. */\n \'fix/direct\': { turn: number }\n }\n}\n',
}))).toThrow(/uses extends; inherited keys would join keyof SessionEventMap without a catalog row/)
})

View File

@@ -15,6 +15,10 @@
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./types": {
"types": "./lib/types/types.d.ts",
"default": "./lib/types/types.js"
},
"./presentation": {
"types": "./lib/types/presentation.d.ts",
"default": "./lib/types/presentation.js"

View File

@@ -14,41 +14,7 @@ import type { JsonValue } from '@deepseek-ai/dsh-session'
import { defineTool, parameterSchemaSpecToJsonSchema } from './schema.ts'
import { TOOL_REGISTRY_SCHEDULER } from './index.ts'
import type { CodeDispatchLog, ToolDefinition, ToolExecutionResult, ToolRegistry, ToolRunContext } from './index.ts'
declare module '@deepseek-ai/dsh-session/types' {
interface SessionEventMap {
/**
* One sub-dispatch STARTING inside a `run_code` program: the parent
* `run_code` call id, the deterministic sub-call id (`<parent>:code:<n>`,
* numbered in submission order), and the tool `name` with its
* JSON-normalized `arguments` — the exact value dispatched, normalized
* BEFORE dispatch, so this append can never fail on payload shape.
* Appended when the scheduler actually starts the call (not at
* submission), so a start means the tool body pipeline was entered; a
* call abandoned in the queue logs nothing. Log-only: `deriveMessages()`
* ignores it; UIs use it for live per-sub-call running state and pair it
* with `tool/code-dispatch` by `subCallId` (timing = the two events'
* `time` fields).
*/
'tool/code-dispatch-start': { rootCallId: CallId; parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown }
/**
* One bridged sub-dispatch SETTLING: the pairing ids (matching the
* `tool/code-dispatch-start` with the same `subCallId`), the tool `name`
* with the same JSON-normalized `arguments`, and the sub-call's complete
* model-facing outcome in `tool/result`'s own vocabulary
* (`content` + `isError`), so UIs render a sub-call through the exact
* code path that renders a native call. Every started sub-call settles
* with exactly one of these (abort included: the aborted pipeline result
* is an `isError` outcome).
* Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter
* model context; persistence and UIs get every call. Appended inside the
* parent `run_code`'s execution (the bridge drains in-flight dispatches
* before returning), so its execution-enclosure relation holds by
* construction.
*/
'tool/code-dispatch': { rootCallId: CallId; parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; content: ContentBlock[] }
}
}
import type {} from './types.ts'
/** The model-facing name of the Code Mode tool. */
export const RUN_CODE_NAME = 'run_code'

View File

@@ -85,6 +85,7 @@ export {
} from './json-schema.ts'
export type { JsonValue } from '@deepseek-ai/dsh-session'
export type { CodeDispatchEventData, CodeDispatchStartEventData } from './types.ts'
export { CodeRunFailedError, RUN_CODE_NAME } from './code-mode.ts'
export { jsonSchemaToTs, renderToolsSdk } from './ts-types.ts'

View File

@@ -0,0 +1,58 @@
/**
* Durable Tool event vocabulary shared with type-only consumers.
*
* @module @deepseek-ai/dsh-tools/types
*/
import type { CallId } from '@deepseek-ai/dsh-llm/brand'
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
/** Payload recorded when one nested Code Mode Tool dispatch starts. */
export interface CodeDispatchStartEventData {
rootCallId: CallId
parentCallId: CallId
subCallId: CallId
name: string
arguments: unknown
}
/** Payload recorded when one nested Code Mode Tool dispatch settles. */
export interface CodeDispatchEventData extends CodeDispatchStartEventData {
isError: boolean
content: ContentBlock[]
}
declare module '@deepseek-ai/dsh-session/types' {
interface SessionEventMap {
/**
* One sub-dispatch STARTING inside a `run_code` program: the parent
* `run_code` call id, the deterministic sub-call id (`<parent>:code:<n>`,
* numbered in submission order), and the tool `name` with its
* JSON-normalized `arguments` — the exact value dispatched, normalized
* BEFORE dispatch, so this append can never fail on payload shape.
* Appended when the scheduler actually starts the call (not at
* submission), so a start means the tool body pipeline was entered; a
* call abandoned in the queue logs nothing. Log-only: `deriveMessages()`
* ignores it; UIs use it for live per-sub-call running state and pair it
* with `tool/code-dispatch` by `subCallId` (timing = the two events'
* `time` fields).
*/
'tool/code-dispatch-start': CodeDispatchStartEventData
/**
* One bridged sub-dispatch SETTLING: the pairing ids (matching the
* `tool/code-dispatch-start` with the same `subCallId`), the tool `name`
* with the same JSON-normalized `arguments`, and the sub-call's complete
* model-facing outcome in `tool/result`'s own vocabulary
* (`content` + `isError`), so UIs render a sub-call through the exact
* code path that renders a native call. Every started sub-call settles
* with exactly one of these (abort included: the aborted pipeline result
* is an `isError` outcome).
* Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter
* model context; persistence and UIs get every call. Appended inside the
* parent `run_code`'s execution (the bridge drains in-flight dispatches
* before returning), so its execution-enclosure relation holds by
* construction.
*/
'tool/code-dispatch': CodeDispatchEventData
}
}