Files
deepseek-harness/packages/ptc-runtime/ptc-runtime-node/src/index.ts
T

105 lines
4.8 KiB
TypeScript

/** Node PTC provider with uniform admission and configurable evaluation and process ownership. */
import { Context } from '@deepseek-ai/cordis'
import { PtcRuntime } from '@deepseek-ai/dsh-ptc-runtime'
import type { PtcProgramFormat, PtcRunRequest, PtcRunResult, PtcRunSpec } from '@deepseek-ai/dsh-ptc-runtime'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
import type {} from '@deepseek-ai/dsh-sandbox-policy'
import type {} from '@deepseek-ai/dsh-fs'
import { resolveLaunch } from './launch.ts'
import { NodeConfig, validateConfig } from './config.ts'
import { createExecution } from './execution.ts'
import { PolicyProcesses } from './policy-processes.ts'
import type { Config, ResolvedConfig } from './config.ts'
export type { Config } from './config.ts'
export type { LaunchConfig } from './launch.ts'
interface LiveRun { controller: AbortController; finished: Promise<void> }
/** Node execution under file policy, with optional owner-scoped retained memory. */
export class NodePtcRuntime extends PtcRuntime {
static inject = ['fs', 'subprocess', 'sandbox', 'sandboxPolicy']
static Config = NodeConfig
readonly language = 'typescript'
readonly isolation = 'process'
private readonly execution: ReturnType<typeof createExecution>
private readonly processes: PolicyProcesses
override get programFormat(): PtcProgramFormat { return this.execution.mode.format }
override get retainsState(): boolean { return this.execution.mode.retainsState }
override get executionInstructions(): string { return this.execution.mode.executionInstructions }
private readonly config: ResolvedConfig
private readonly live = new Set<LiveRun>()
private disposed = false
constructor(ctx: Context, config: Config) {
super(ctx)
this.config = { ...config, launch: resolveLaunch(config.launch) } as ResolvedConfig
validateConfig(this.config)
this.processes = new PolicyProcesses(ctx)
this.execution = createExecution(ctx, this.config, this.processes)
ctx.effect(() => async () => {
this.disposed = true
const active = [...this.live]
for (const run of active) run.controller.abort('runtime disposed')
const results = await Promise.allSettled([this.execution.dispose(), ...active.map(run => run.finished)])
const failed = results.find(result => result.status === 'rejected')
if (failed?.status === 'rejected') throw failed.reason
}, 'Node ptc-runtime cleanup')
}
override get sandboxMode(): SandboxMode | undefined { return this.ctx.sandboxPolicy.defaultMode }
override release(owner: object): Promise<void> {
return this.execution.release(owner)
}
override get timeout(): { defaultMs: number; maxMs: number } {
return { defaultMs: Math.min(this.config.timeoutMs, this.config.maxTimeoutMs), maxMs: this.config.maxTimeoutMs }
}
/**
* Resolve directory, deadline and file policy for the selected execution mode.
* @param request - Program, bindings, optional cwd/deadline, and resolved authority.
* @returns Complete execution inputs with a capped numeric budget or an explicit null deadline.
*/
resolve(request: PtcRunRequest): PtcRunSpec {
if (this.disposed) {
const source = this.retainsState ? 'persistent PTC' : 'ptc-runtime-node'
throw new Error(`${source}: resolve after disposal`)
}
return this.execution.resolve(request)
}
/**
* Run a resolved program under the configured execution mode.
* Session mode changes cancel admitted calls before process launch as well as active programs.
* @param spec - Inputs returned by resolve for the configured mode.
* @returns Logs, JSON completion or failure; retained processes remain until release or invalidation.
*/
async run(spec: PtcRunSpec): Promise<PtcRunResult> {
if (this.disposed) throw new Error('ptc-runtime-node: run after disposal')
const controller = new AbortController()
const completion = Promise.withResolvers<void>()
const unregister = spec.sandboxPolicy === undefined ? undefined : this.processes.register(spec.sandboxPolicy, async () => {
controller.abort(new Error('sandbox mode changed'))
await completion.promise
})
const live = { controller, finished: completion.promise }
this.live.add(live)
try {
await this.processes.wait(spec.sandboxPolicy?.sessionId)
// Disposal can begin while a previous policy's process cleanup is pending.
// oxlint-disable-next-line typescript/no-unnecessary-condition
if (this.disposed) throw new Error(`${this.retainsState ? 'persistent PTC' : 'ptc-runtime-node'}: run after disposal`)
const signal = spec.signal === undefined ? controller.signal : AbortSignal.any([spec.signal, controller.signal])
return await this.execution.run({ ...spec, signal })
} finally {
unregister?.()
this.live.delete(live)
completion.resolve()
}
}
}
export default NodePtcRuntime