mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
287 lines
12 KiB
TypeScript
287 lines
12 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import { AGENT, CONSUMER_CODE, LISTENER_CODE, PROVIDER_CODE, REVERSE_TOOL_CODE, call, defineAndRun, setup, setupWithBrowser, text } from './helpers.ts'
|
|
|
|
/**
|
|
* The five model-facing tools driven through the real registry pipeline: define
|
|
* records and mints, run starts and reports, stop and undefine unwind, and every
|
|
* refusal reaches the model as a tool error carrying the runner's teaching text.
|
|
* The runner's own semantics are covered by its package; here the subject is the
|
|
* model-facing contract (arguments, canonical values, rendered text, metadata).
|
|
*/
|
|
|
|
describe('cordis_define', () => {
|
|
it('records a definition and carries the minted id in its value and its presentation metadata', async () => {
|
|
const ctx = await setup()
|
|
|
|
const result = await call(ctx, 'cordis_define', {
|
|
name: 'greeter',
|
|
purpose: 'greets by name',
|
|
code: PROVIDER_CODE,
|
|
})
|
|
|
|
expect(result.isError).toBe(false)
|
|
if (result.isError) throw new Error('expected cordis_define success')
|
|
expect(result.value).toEqual({
|
|
id: 'dyn-1',
|
|
name: 'greeter',
|
|
purpose: 'greets by name',
|
|
hasHostHalf: true,
|
|
hasClientHalf: false,
|
|
})
|
|
// The card addresses run/stop by this id, and only the durable metadata
|
|
// carries it (the model never wrote it).
|
|
expect(result.meta).toEqual({ id: 'dyn-1' })
|
|
expect(text(result)).toBe(
|
|
'Dynamic package dyn-1 ("greeter") is defined with a host half and is NOT running yet. '
|
|
+ 'Run it with cordis_run id:"dyn-1", or let the user press start on its card.',
|
|
)
|
|
// Nothing ran: the provided service is absent until cordis_run.
|
|
expect(ctx.get('greeter')).toBeUndefined()
|
|
})
|
|
|
|
it('names both halves in the rendered summary', async () => {
|
|
const ctx = await setup()
|
|
const result = await call(ctx, 'cordis_define', {
|
|
name: 'dual',
|
|
purpose: 'both halves',
|
|
code: PROVIDER_CODE,
|
|
client: 'return () => {}',
|
|
})
|
|
expect(text(result)).toContain('is defined with a host + browser half')
|
|
})
|
|
|
|
it('reports a parse failure as a tool error and records nothing', async () => {
|
|
const ctx = await setup()
|
|
const result = await call(ctx, 'cordis_define', {
|
|
name: 'broken',
|
|
purpose: 'p',
|
|
code: 'return { name: \'ts\' as const }',
|
|
})
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('plain JavaScript, not TypeScript')
|
|
expect(text(await call(ctx, 'cordis_runtime_inspect', { what: 'temporary' })))
|
|
.toContain('No dynamic packages are defined in this session')
|
|
})
|
|
|
|
it('refuses a definition with neither half', async () => {
|
|
const ctx = await setup()
|
|
const result = await call(ctx, 'cordis_define', { name: 'empty', purpose: 'p' })
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('needs `code` (host half), `client` (browser half), or both')
|
|
})
|
|
})
|
|
|
|
describe('cordis_run', () => {
|
|
it('starts the host half and reports what it provides', async () => {
|
|
const ctx = await setup()
|
|
const { value } = await call(ctx, 'cordis_define', { name: 'greeter', purpose: 'p', code: PROVIDER_CODE }) as { value: { id: string } }
|
|
|
|
const result = await call(ctx, 'cordis_run', { id: value.id })
|
|
|
|
expect(result.isError).toBe(false)
|
|
if (result.isError) throw new Error('expected cordis_run success')
|
|
expect(result.value).toEqual({ id: 'dyn-1', rev: 1, provides: ['greeter'], waitingFor: [] })
|
|
expect(text(result)).toContain('is running at rev 1: host half is running (provides: greeter)')
|
|
expect(ctx.get('greeter')).toBeDefined()
|
|
})
|
|
|
|
it('keeps a package whose host half waits for a service, naming what it waits for', async () => {
|
|
const ctx = await setup()
|
|
const { value } = await call(ctx, 'cordis_define', { name: 'consumer', purpose: 'p', code: CONSUMER_CODE }) as { value: { id: string } }
|
|
|
|
const result = await call(ctx, 'cordis_run', { id: value.id })
|
|
|
|
expect(result.isError).toBe(false)
|
|
expect(text(result)).toContain('host half is pending (missing services: greeter)')
|
|
expect(ctx.tools.get('greet')).toBeUndefined()
|
|
})
|
|
|
|
it('lets the agent give ITSELF a tool, callable on the next step', async () => {
|
|
const ctx = await setup()
|
|
await defineAndRun(ctx, REVERSE_TOOL_CODE)
|
|
|
|
expect(ctx.tools.get('reverse_text')).toBeDefined()
|
|
expect(text(await call(ctx, 'reverse_text', { text: 'abc' }))).toBe('cba')
|
|
})
|
|
|
|
it('runs a host-only package again without re-evaluating its host half', async () => {
|
|
const ctx = await setup()
|
|
const id = await defineAndRun(ctx, PROVIDER_CODE)
|
|
|
|
// Re-evaluating would collide on the provided service; binding a live host
|
|
// half is what lets a second call succeed at all.
|
|
const again = await call(ctx, 'cordis_run', { id })
|
|
|
|
expect(again.isError).toBe(false)
|
|
if (again.isError) throw new Error('expected the re-run to succeed')
|
|
expect(again.value).toMatchObject({ id, rev: 1 })
|
|
})
|
|
|
|
it('reports a sandbox failure as a tool error, leaving nothing running', async () => {
|
|
const ctx = await setup()
|
|
const { value } = await call(ctx, 'cordis_define', {
|
|
name: 'boom',
|
|
purpose: 'p',
|
|
code: 'throw new Error(\'host half exploded\')',
|
|
}) as { value: { id: string } }
|
|
|
|
const result = await call(ctx, 'cordis_run', { id: value.id })
|
|
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('host half exploded')
|
|
expect(text(await call(ctx, 'cordis_runtime_inspect', { what: 'temporary' }))).toContain('[defined, not running]')
|
|
})
|
|
|
|
it('answers an unknown id with the memory-only explanation', async () => {
|
|
const ctx = await setup()
|
|
const result = await call(ctx, 'cordis_run', { id: 'dyn-99' })
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('definitions live in memory only')
|
|
})
|
|
})
|
|
|
|
describe('cordis_stop', () => {
|
|
it('unwinds the package\'s registrations before it returns, and keeps the definition runnable', async () => {
|
|
const ctx = await setup()
|
|
const id = await defineAndRun(ctx, REVERSE_TOOL_CODE)
|
|
|
|
const result = await call(ctx, 'cordis_stop', { id })
|
|
|
|
expect(result.isError).toBe(false)
|
|
if (result.isError) throw new Error('expected cordis_stop success')
|
|
expect(result.value).toEqual({ id })
|
|
expect(text(result)).toContain('is stopped; its definition remains')
|
|
expect(ctx.tools.get('reverse_text')).toBeUndefined()
|
|
|
|
// Runnable again on a fresh revision, with no code re-sent.
|
|
const again = await call(ctx, 'cordis_run', { id })
|
|
expect(again.isError).toBe(false)
|
|
expect(ctx.tools.get('reverse_text')).toBeDefined()
|
|
})
|
|
|
|
it('refuses to stop a package that is not running', async () => {
|
|
const ctx = await setup()
|
|
const { value } = await call(ctx, 'cordis_define', { name: 'idle', purpose: 'p', code: PROVIDER_CODE }) as { value: { id: string } }
|
|
|
|
const result = await call(ctx, 'cordis_stop', { id: value.id })
|
|
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('is not running')
|
|
})
|
|
})
|
|
|
|
describe('cordis_undefine', () => {
|
|
it('stops a running package, forgets it, and invalidates its id', async () => {
|
|
const ctx = await setup()
|
|
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const id = await defineAndRun(ctx, LISTENER_CODE)
|
|
|
|
const result = await call(ctx, 'cordis_undefine', { id })
|
|
|
|
expect(result.isError).toBe(false)
|
|
if (result.isError) throw new Error('expected cordis_undefine success')
|
|
expect(result.value).toEqual({ id, wasRunning: true })
|
|
expect(text(result)).toContain('is stopped and undefined')
|
|
const calls = log.mock.calls.length
|
|
ctx.tools.register({
|
|
name: 'post_undefine_trigger',
|
|
description: 'test trigger',
|
|
parameters: { type: 'object' as const, properties: {} },
|
|
output: { schema: { type: 'null' as const }, render: () => [] },
|
|
execute: async (): Promise<null> => null,
|
|
})
|
|
expect(log).toHaveBeenCalledTimes(calls)
|
|
expect((await call(ctx, 'cordis_run', { id })).isError).toBe(true)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('forgets a defined-but-never-run package', async () => {
|
|
const ctx = await setup()
|
|
const { value } = await call(ctx, 'cordis_define', { name: 'idle', purpose: 'p', code: PROVIDER_CODE }) as { value: { id: string } }
|
|
|
|
const result = await call(ctx, 'cordis_undefine', { id: value.id })
|
|
|
|
expect(result.isError).toBe(false)
|
|
if (result.isError) throw new Error('expected cordis_undefine success')
|
|
expect(result.value).toEqual({ id: value.id, wasRunning: false })
|
|
})
|
|
})
|
|
|
|
describe('session scope', () => {
|
|
it('hides another session\'s package from every verb', async () => {
|
|
const ctx = await setup()
|
|
const id = await defineAndRun(ctx, PROVIDER_CODE)
|
|
|
|
// A call from a different agent addresses a different definition space.
|
|
const other = await ctx.tools.execute({
|
|
signal: new AbortController().signal,
|
|
callId: 'call-other' as never,
|
|
name: 'cordis_run',
|
|
arguments: { id },
|
|
agent: { ...AGENT, id: 'S-other' } as never,
|
|
})
|
|
|
|
expect(other.isError).toBe(true)
|
|
expect(text(other)).toContain('no dynamic package')
|
|
})
|
|
|
|
it('refuses a dynamic-package call that arrives without an agent', async () => {
|
|
const ctx = await setup()
|
|
const result = await ctx.tools.execute({
|
|
signal: new AbortController().signal,
|
|
callId: 'call-agentless' as never,
|
|
name: 'cordis_define',
|
|
arguments: { name: 'x', purpose: 'p', code: PROVIDER_CODE },
|
|
})
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('need a session')
|
|
})
|
|
})
|
|
|
|
describe('cordis_run with a browser half', () => {
|
|
it('reports what each half provides and waits for once a page carried it out', async () => {
|
|
const ctx = await setupWithBrowser(['someClientService'])
|
|
const defined = await call(ctx, 'cordis_define', {
|
|
name: 'both halves',
|
|
purpose: 'host + browser',
|
|
code: 'return { name: \'both-host\', apply(ctx) { ctx.provide(\'dynBoth\', {}) } }',
|
|
client: 'return () => {}',
|
|
})
|
|
if (defined.isError) throw new Error('define failed')
|
|
const result = await call(ctx, 'cordis_run', { id: (defined.value as { id: string }).id })
|
|
if (result.isError) throw new Error(text(result))
|
|
const value = result.value as { rev: number; provides: string[]; clientWaitingFor?: string[] }
|
|
expect(value.rev).toBe(1)
|
|
expect(value.provides).toEqual(['dynBoth'])
|
|
// The answering page's own parked services ride back to the model.
|
|
expect(value.clientWaitingFor).toEqual(['someClientService'])
|
|
expect(text(result)).toContain('browser half is pending (missing services: someClientService)')
|
|
})
|
|
|
|
it('reports a browser-only package as running even though no host fiber exists', async () => {
|
|
const ctx = await setupWithBrowser()
|
|
const defined = await call(ctx, 'cordis_define', {
|
|
name: 'browser only',
|
|
purpose: 'ui only',
|
|
client: 'return () => {}',
|
|
})
|
|
if (defined.isError) throw new Error('define failed')
|
|
const result = await call(ctx, 'cordis_run', { id: (defined.value as { id: string }).id })
|
|
if (result.isError) throw new Error(text(result))
|
|
const value = result.value as { rev: number; provides: string[]; waitingFor: string[] }
|
|
// No host half means no fiber to read provides/waits from — not an error.
|
|
expect(value.provides).toEqual([])
|
|
expect(value.waitingFor).toEqual([])
|
|
expect(text(result)).toContain('host half is running (provides: none)')
|
|
})
|
|
})
|
|
|
|
describe('cordis_undefine refusals', () => {
|
|
it('fails loud for an id the registry never minted', async () => {
|
|
const ctx = await setup()
|
|
const result = await call(ctx, 'cordis_undefine', { id: 'dyn-99' })
|
|
expect(result.isError).toBe(true)
|
|
expect(text(result)).toContain('dyn-99')
|
|
})
|
|
})
|