fix(core): close turn cancellation contract gaps
This commit is contained in:
@@ -1,35 +1,6 @@
|
||||
/** Public normalization helpers for explicit turn cancellation. @module @deepseek-ai/dsh-agent/cancellation */
|
||||
/** Runtime reason inspection for explicit turn cancellation. @module @deepseek-ai/dsh-agent/cancellation */
|
||||
|
||||
import type { AgentCancelCause, AgentInterruptReason } from './types.ts'
|
||||
|
||||
/**
|
||||
* Validate and detach a caller-supplied Agent cancellation cause.
|
||||
* @param value - the candidate cancellation cause.
|
||||
* @returns a fresh frozen cause suitable for the current turn signal.
|
||||
* @throws {TypeError} when the value is not an exact supported cause.
|
||||
*/
|
||||
export function normalizeAgentCancelCause(value: unknown): AgentCancelCause {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
throw new TypeError('agent cancel cause must be an exact plain object with kind "user" or "parent"')
|
||||
}
|
||||
const prototype = Object.getPrototypeOf(value) as unknown
|
||||
if (prototype !== Object.prototype && prototype !== null) {
|
||||
throw new TypeError('agent cancel cause must be an exact plain object with kind "user" or "parent"')
|
||||
}
|
||||
const keys = Reflect.ownKeys(value)
|
||||
if (keys.length !== 1 || keys[0] !== 'kind') {
|
||||
throw new TypeError('agent cancel cause must contain exactly one field: kind')
|
||||
}
|
||||
const kind = (value as { readonly kind?: unknown }).kind
|
||||
switch (kind) {
|
||||
case 'user':
|
||||
return Object.freeze({ kind: 'user' })
|
||||
case 'parent':
|
||||
return Object.freeze({ kind: 'parent' })
|
||||
default:
|
||||
throw new TypeError(`unsupported agent cancel cause kind: ${String(kind)}`)
|
||||
}
|
||||
}
|
||||
import type { AgentInterruptReason } from './types.ts'
|
||||
|
||||
/**
|
||||
* Read a supported agent interruption from an explicitly supplied signal.
|
||||
@@ -41,19 +12,19 @@ export function normalizeAgentCancelCause(value: unknown): AgentCancelCause {
|
||||
export function agentInterruptReasonOf(signal: AbortSignal): AgentInterruptReason | undefined {
|
||||
if (!signal.aborted) return undefined
|
||||
const reason: unknown = signal.reason
|
||||
if (typeof reason === 'object' && reason !== null && !Array.isArray(reason)) {
|
||||
const prototype = Object.getPrototypeOf(reason) as unknown
|
||||
const keys = Reflect.ownKeys(reason)
|
||||
if ((prototype === Object.prototype || prototype === null)
|
||||
&& keys.length === 1 && keys[0] === 'kind'
|
||||
&& (reason as { readonly kind?: unknown }).kind === 'disposed') {
|
||||
if (typeof reason !== 'object' || reason === null || Array.isArray(reason)) return undefined
|
||||
const prototype = Object.getPrototypeOf(reason) as unknown
|
||||
const keys = Reflect.ownKeys(reason)
|
||||
if ((prototype !== Object.prototype && prototype !== null)
|
||||
|| keys.length !== 1 || keys[0] !== 'kind') return undefined
|
||||
switch ((reason as { readonly kind?: unknown }).kind) {
|
||||
case 'user':
|
||||
return Object.freeze({ kind: 'user' })
|
||||
case 'parent':
|
||||
return Object.freeze({ kind: 'parent' })
|
||||
case 'disposed':
|
||||
return Object.freeze({ kind: 'disposed' })
|
||||
}
|
||||
}
|
||||
try {
|
||||
return normalizeAgentCancelCause(reason)
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof TypeError) return undefined
|
||||
throw error
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { Agent, AgentOptions } from './types.ts'
|
||||
|
||||
export * from './types.ts'
|
||||
export { agentInterruptReasonOf, normalizeAgentCancelCause } from './cancellation.ts'
|
||||
export { agentInterruptReasonOf } from './cancellation.ts'
|
||||
export { agentEvents, assembleContextFor } from './dispatch.ts'
|
||||
export type { AgentEventDispatch, AgentSubjectEvent } from './dispatch.ts'
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ export interface Agent {
|
||||
* Clear all queued and steering work, including items waiting to start, and
|
||||
* abort the active turn. The first cause wins for that turn, and `whenIdle()`
|
||||
* resolves after cancellation reaches quiescence. Omission means
|
||||
* `{ kind: 'user' }`; invalid causes throw synchronously even while idle.
|
||||
* Idle cancellation is a no-op after validation and does not arm a later cancel.
|
||||
* `{ kind: 'user' }`. Idle cancellation is a no-op and does not arm a later
|
||||
* cancel. The active turn snapshots and freezes the typed cause.
|
||||
* @param cause - the stable caller intent carried by the current turn signal.
|
||||
*/
|
||||
cancel(cause?: AgentCancelCause): void
|
||||
|
||||
Reference in New Issue
Block a user