mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
The native adapter's route was named deepseek, colliding with pi-ai's catalog provider of the same name, so the two DeepSeek paths could never be mounted side by side. The web settings page needs both configurable at once. Compositions, fixtures, goldens, scaffolding defaults, and docs all move together (pre-release, no shim); TUI/session-query-spill/ missing-credential goldens re-recorded through their keyless refresh modes because provider-name length shifts box padding and spill truncation points.
99 lines
3.6 KiB
TypeScript
99 lines
3.6 KiB
TypeScript
/**
|
|
* Headless create input: a structured project spec supplied by an agent or CI
|
|
* instead of interactive prompts.
|
|
*
|
|
* @module @deepseek-ai/create-sdk/headless
|
|
*/
|
|
|
|
import { readFile } from 'node:fs/promises'
|
|
import type { FeatureSelection, PackageManagerName, RunInterface } from '@deepseek-ai/dsh-helper'
|
|
import type { CreateArgs } from './args.ts'
|
|
|
|
/**
|
|
* Structured, non-interactive create input. Scalar fields mirror {@link CreateArgs}
|
|
* project answers; `features` is the headless feature plan handed to `CreateWizard`
|
|
* (the interactive tree/suggests prompts are skipped). Absent required answers make
|
|
* the run fail loud through `HeadlessPromptPort` rather than blocking.
|
|
*/
|
|
interface HeadlessCreateSpec {
|
|
directory?: string
|
|
description?: string
|
|
provider?: 'deepseek-official' | 'custom'
|
|
baseURL?: string
|
|
apiKey?: string
|
|
model?: string
|
|
interface?: RunInterface
|
|
pm?: PackageManagerName
|
|
install?: boolean
|
|
linkWorkspace?: boolean
|
|
features?: readonly FeatureSelection[]
|
|
}
|
|
|
|
/** Resolved headless input: the args the wizard reads plus the feature plan. */
|
|
export interface ResolvedHeadless {
|
|
args: CreateArgs
|
|
features: readonly FeatureSelection[] | undefined
|
|
}
|
|
|
|
function asRecord(value: unknown, source: string): Record<string, unknown> {
|
|
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw new Error(`${source}: expected a JSON object`)
|
|
}
|
|
return value as Record<string, unknown>
|
|
}
|
|
|
|
/** Parse and shallow-validate a headless spec from JSON text. */
|
|
function parseHeadlessSpec(text: string, source: string): HeadlessCreateSpec {
|
|
let parsed: unknown
|
|
try {
|
|
parsed = JSON.parse(text)
|
|
} catch (error) {
|
|
/* v8 ignore next -- JSON.parse only throws Error instances; the String() branch is defensive */
|
|
throw new Error(`${source}: invalid JSON (${error instanceof Error ? error.message : String(error)})`)
|
|
}
|
|
const record = asRecord(parsed, source)
|
|
if (record.features !== undefined && !Array.isArray(record.features)) {
|
|
throw new Error(`${source}: "features" must be an array`)
|
|
}
|
|
return record
|
|
}
|
|
|
|
/**
|
|
* Load a headless spec from `--config-json` (inline) or `--config` (a JSON file),
|
|
* returning `undefined` when neither is supplied.
|
|
* @param args - parsed create args.
|
|
* @param readFileText - file reader seam for tests.
|
|
* @returns the resolved args + feature plan, or `undefined` for interactive runs.
|
|
*/
|
|
export async function resolveHeadless(
|
|
args: CreateArgs,
|
|
readFileText: (path: string) => Promise<string> = path => readFile(path, 'utf8'),
|
|
): Promise<ResolvedHeadless | undefined> {
|
|
let text: string
|
|
let source: string
|
|
if (args.configJson !== undefined) {
|
|
text = args.configJson
|
|
source = '--config-json'
|
|
} else if (args.config !== undefined) {
|
|
source = args.config
|
|
text = await readFileText(args.config)
|
|
} else {
|
|
return undefined
|
|
}
|
|
const spec = parseHeadlessSpec(text, source)
|
|
const resolvedArgs: CreateArgs = {
|
|
...spec.directory === undefined ? {} : { directory: spec.directory },
|
|
...spec.description === undefined ? {} : { description: spec.description },
|
|
...spec.provider === undefined ? {} : { provider: spec.provider },
|
|
...spec.baseURL === undefined ? {} : { baseURL: spec.baseURL },
|
|
...spec.apiKey === undefined ? {} : { apiKey: spec.apiKey },
|
|
...spec.model === undefined ? {} : { model: spec.model },
|
|
...spec.interface === undefined ? {} : { runInterface: spec.interface },
|
|
...spec.pm === undefined ? {} : { packageManager: spec.pm },
|
|
...spec.install === undefined ? {} : { install: spec.install },
|
|
...spec.linkWorkspace ? { linkWorkspace: true } : {},
|
|
help: false,
|
|
}
|
|
return { args: resolvedArgs, features: spec.features }
|
|
}
|