393 lines
12 KiB
TypeScript
393 lines
12 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
featureId,
|
|
createPackageManager,
|
|
type NestedMultiSelectValue,
|
|
type PromptPort,
|
|
} from '@deepseek-ai/dsh-helper'
|
|
import type {
|
|
ConfirmPromptRequest,
|
|
MultiSelectPromptRequest,
|
|
NestedMultiSelectRequest,
|
|
PromptOutcome,
|
|
SecretPromptRequest,
|
|
SelectPromptRequest,
|
|
TextPromptRequest,
|
|
} from '../../helper/src/questions/prompt-port.ts'
|
|
import { parseCreateArgs } from '../src/args.ts'
|
|
import { CreateWizard } from '../src/create-wizard.ts'
|
|
import { CREATE_TEMPLATES, packageManagerTemplateModel } from '../src/templates/create-templates.ts'
|
|
|
|
class RecordingPort implements PromptPort {
|
|
readonly transcript: unknown[] = []
|
|
readonly #answers: unknown[]
|
|
|
|
constructor(answers: unknown[]) { this.#answers = [...answers] }
|
|
|
|
answer<T>(record: unknown): Promise<PromptOutcome<T>> {
|
|
this.transcript.push(record)
|
|
return Promise.resolve({ status: 'answered', value: this.#answers.shift() as T })
|
|
}
|
|
|
|
text(request: TextPromptRequest): Promise<PromptOutcome<string>> {
|
|
return this.answer({
|
|
kind: 'text',
|
|
message: request.message,
|
|
defaultValue: request.defaultValue,
|
|
initialValue: request.initialValue,
|
|
})
|
|
}
|
|
secret(request: SecretPromptRequest): Promise<PromptOutcome<string>> {
|
|
return this.answer({ kind: 'secret', message: request.message })
|
|
}
|
|
select<T>(request: SelectPromptRequest<T>): Promise<PromptOutcome<T>> {
|
|
return this.answer({
|
|
kind: 'select', message: request.message, options: request.options.map(option => option.label),
|
|
initialValue: request.initialValue,
|
|
})
|
|
}
|
|
multiselect<T>(request: MultiSelectPromptRequest<T>): Promise<PromptOutcome<readonly T[]>> {
|
|
return this.answer({
|
|
kind: 'multiselect', message: request.message, options: request.options.map(option => option.label),
|
|
initialValues: request.initialValues,
|
|
})
|
|
}
|
|
confirm(request: ConfirmPromptRequest): Promise<PromptOutcome<boolean>> {
|
|
return this.answer({ kind: 'confirm', message: request.message, initialValue: request.initialValue })
|
|
}
|
|
nestedMultiselect<TValue, TChoice>(
|
|
request: NestedMultiSelectRequest<TValue, TChoice>,
|
|
): Promise<PromptOutcome<readonly NestedMultiSelectValue<TValue, TChoice>[]>> {
|
|
return this.answer({
|
|
kind: 'nested-multiselect',
|
|
message: request.message,
|
|
options: request.options.map(option => ({
|
|
label: option.label,
|
|
required: option.required,
|
|
default: option.default,
|
|
choices: option.choices?.map(choice => choice.label),
|
|
})),
|
|
})
|
|
}
|
|
}
|
|
|
|
describe.skipIf(process.platform === 'win32')('create-sdk terminal contract', () => {
|
|
it('renders package-manager-specific setup commands', () => {
|
|
const model = packageManagerTemplateModel(createPackageManager('yarn', '4.0.0'))
|
|
expect(CREATE_TEMPLATES.installQuestion.render(model)).toBe('Run yarn install and then build the project?\n')
|
|
expect(CREATE_TEMPLATES.setupFailure.render({
|
|
directory: '/workspace/agent',
|
|
error: 'offline',
|
|
...model,
|
|
})).toContain('yarn install && yarn build')
|
|
})
|
|
|
|
it('pins the full unresolved question order and completion messages', async () => {
|
|
const port = new RecordingPort([
|
|
'my-agent',
|
|
'my-agent',
|
|
'Snapshot agent',
|
|
'deepseek',
|
|
'secret-key',
|
|
'acp',
|
|
[
|
|
{ value: featureId('persistence'), choices: ['jsonl'] },
|
|
{ value: featureId('hmr'), choices: [] },
|
|
{ value: featureId('web'), choices: ['exa'] },
|
|
{ value: featureId('workflow'), choices: [] },
|
|
],
|
|
true,
|
|
'exa-key',
|
|
'none',
|
|
'npm',
|
|
false,
|
|
])
|
|
const resolved = await new CreateWizard({
|
|
args: parseCreateArgs([]),
|
|
port,
|
|
cwd: '/workspace',
|
|
releaseVersion: '0.0.1',
|
|
userAgent: '',
|
|
versionProbe: async () => '10.0.0',
|
|
}).run()
|
|
expect({
|
|
prompts: port.transcript,
|
|
result: {
|
|
directory: resolved.directory,
|
|
name: resolved.request.name,
|
|
manager: resolved.request.packageManager.name,
|
|
install: resolved.install,
|
|
features: resolved.request.features.map(item => ({ id: item.id, options: item.options })),
|
|
},
|
|
messages: {
|
|
created: CREATE_TEMPLATES.created.render({
|
|
name: resolved.request.name,
|
|
directory: resolved.directory,
|
|
}),
|
|
next: CREATE_TEMPLATES.nextSteps.render({
|
|
directory: resolved.directory,
|
|
setupRequired: false,
|
|
...packageManagerTemplateModel(resolved.request.packageManager),
|
|
}),
|
|
failure: CREATE_TEMPLATES.setupFailure.render({
|
|
directory: resolved.directory,
|
|
error: String(new Error('offline')),
|
|
...packageManagerTemplateModel(resolved.request.packageManager),
|
|
}),
|
|
},
|
|
}).toMatchInlineSnapshot(`
|
|
{
|
|
"messages": {
|
|
"created": "Created my-agent in /workspace/my-agent
|
|
",
|
|
"failure": "Project files are ready, but setup failed: Error: offline
|
|
Retry: cd /workspace/my-agent && npm install && npm run build
|
|
",
|
|
"next": "Next: cd /workspace/my-agent && npm start
|
|
",
|
|
},
|
|
"prompts": [
|
|
{
|
|
"defaultValue": "my-agent",
|
|
"initialValue": undefined,
|
|
"kind": "text",
|
|
"message": "Where should the project be created?",
|
|
},
|
|
{
|
|
"defaultValue": "my-agent",
|
|
"initialValue": undefined,
|
|
"kind": "text",
|
|
"message": "Package name",
|
|
},
|
|
{
|
|
"defaultValue": "A DeepSeek Harness agent named my-agent",
|
|
"initialValue": undefined,
|
|
"kind": "text",
|
|
"message": "Project description",
|
|
},
|
|
{
|
|
"initialValue": "deepseek",
|
|
"kind": "select",
|
|
"message": "Model provider",
|
|
"options": [
|
|
"DeepSeek",
|
|
"Custom endpoint (pi-ai)",
|
|
],
|
|
},
|
|
{
|
|
"kind": "secret",
|
|
"message": "DeepSeek API key",
|
|
},
|
|
{
|
|
"initialValue": "tui",
|
|
"kind": "select",
|
|
"message": "Run interface",
|
|
"options": [
|
|
"ACP automation server",
|
|
"Terminal TUI",
|
|
"Embedded context",
|
|
],
|
|
},
|
|
{
|
|
"kind": "nested-multiselect",
|
|
"message": "Select features",
|
|
"options": [
|
|
{
|
|
"choices": [
|
|
"Local executor",
|
|
"Sandboxed executor",
|
|
],
|
|
"default": true,
|
|
"label": "Command execution",
|
|
"required": true,
|
|
},
|
|
{
|
|
"choices": [
|
|
"JSONL files",
|
|
"SQLite database",
|
|
],
|
|
"default": true,
|
|
"label": "Durable session storage",
|
|
"required": true,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": true,
|
|
"label": "Hot-module reload",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": true,
|
|
"label": "Read, write, and edit local files",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": true,
|
|
"label": "Model-facing task tracking",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": true,
|
|
"label": "Local skill discovery",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": [
|
|
"DeepSeek search",
|
|
"Exa search",
|
|
"Perplexity search",
|
|
"Fetch only",
|
|
],
|
|
"default": false,
|
|
"label": "Web search and fetch tools",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": [
|
|
"Fresh child agent",
|
|
"Fork parent history",
|
|
],
|
|
"default": false,
|
|
"label": "Delegate work to child agents",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": false,
|
|
"label": "Scripted multi-agent workflows",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": false,
|
|
"label": "Automatic context compaction",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": [
|
|
"Claude Code hooks",
|
|
"Codex hooks",
|
|
],
|
|
"default": false,
|
|
"label": "Run Claude Code or Codex hooks",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": false,
|
|
"label": "Loop-hygiene reminders",
|
|
"required": false,
|
|
},
|
|
{
|
|
"choices": undefined,
|
|
"default": false,
|
|
"label": "Tool timeout policy",
|
|
"required": false,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"initialValue": true,
|
|
"kind": "confirm",
|
|
"message": "Add the recommended tool timeout policy for web search and fetch tools?",
|
|
},
|
|
{
|
|
"kind": "secret",
|
|
"message": "Exa API key",
|
|
},
|
|
{
|
|
"initialValue": "none",
|
|
"kind": "select",
|
|
"message": "Local plugin",
|
|
"options": [
|
|
"No local plugin",
|
|
"Cordis plugin",
|
|
"Model-facing tool",
|
|
],
|
|
},
|
|
{
|
|
"initialValue": "npm",
|
|
"kind": "select",
|
|
"message": "Package manager",
|
|
"options": [
|
|
"npm",
|
|
"pnpm",
|
|
"Yarn",
|
|
],
|
|
},
|
|
{
|
|
"initialValue": true,
|
|
"kind": "confirm",
|
|
"message": "Run npm install and then build the project?",
|
|
},
|
|
],
|
|
"result": {
|
|
"directory": "/workspace/my-agent",
|
|
"features": [
|
|
{
|
|
"id": "provider",
|
|
"options": [
|
|
"deepseek",
|
|
],
|
|
},
|
|
{
|
|
"id": "spine",
|
|
"options": [
|
|
"default",
|
|
],
|
|
},
|
|
{
|
|
"id": "app",
|
|
"options": [
|
|
"acp",
|
|
],
|
|
},
|
|
{
|
|
"id": "bash",
|
|
"options": [
|
|
"local",
|
|
],
|
|
},
|
|
{
|
|
"id": "persistence",
|
|
"options": [
|
|
"jsonl",
|
|
],
|
|
},
|
|
{
|
|
"id": "hmr",
|
|
"options": [
|
|
"default",
|
|
],
|
|
},
|
|
{
|
|
"id": "web",
|
|
"options": [
|
|
"exa",
|
|
],
|
|
},
|
|
{
|
|
"id": "workflow",
|
|
"options": [
|
|
"workerthread",
|
|
],
|
|
},
|
|
{
|
|
"id": "timeout-policy",
|
|
"options": [
|
|
"default",
|
|
],
|
|
},
|
|
],
|
|
"install": false,
|
|
"manager": "npm",
|
|
"name": "my-agent",
|
|
},
|
|
}
|
|
`)
|
|
})
|
|
})
|