config.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * Serializable configuration and defaults for the pi-tui terminal mode. Loader
  3. * schema validation normally fills defaults; {@link resolveTuiConfig} applies
  4. * the same defaults for direct callers that bypass the Loader.
  5. * @module @deepseek-ai/dsh-tui/config
  6. */
  7. import z from 'schemastery'
  8. import {
  9. DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES,
  10. DEFAULT_FILE_SEARCH_MAX_ENTRIES,
  11. DEFAULT_FILE_SEARCH_MAX_RESULTS,
  12. } from './chat/file-autocomplete.ts'
  13. /** Theme and prompt-template settings for the pi-tui terminal mode. */
  14. export interface TuiThemeConfig {
  15. /** Apply the built-in ANSI color palette. */
  16. color?: boolean
  17. /** Paint the startup banner with the 24-bit DeepSeek brand gradient. */
  18. truecolor?: boolean
  19. /** Left-aligned template on the row above the editor. */
  20. leftPrompt?: string
  21. /** Right-aligned template on the row above the editor. */
  22. rightPrompt?: string
  23. /** Template used as the editor's first-line prefix. */
  24. inputPrompt?: string
  25. /** Static placeholder shown in an empty editor while the agent is running. */
  26. inputPlaceholder?: string
  27. }
  28. /** Interaction and presentation settings for the pi-tui terminal mode. */
  29. export interface TuiConfig {
  30. /** Render model reasoning blocks. */
  31. showReasoning?: boolean
  32. /** Maximum tool-card body lines retained in its collapsed head/tail preview. */
  33. maxToolOutputLines?: number
  34. /** Maximum options visible at once in a user-question panel. */
  35. maxQuestionOptions?: number
  36. /** Maximum models visible at once in the model selector. */
  37. maxModelOptions?: number
  38. /** Maximum sessions visible at once in the resume selector. */
  39. maxResumeOptions?: number
  40. /** User-question panel width in terminal columns, clamped to the terminal. */
  41. questionDialogWidth?: number
  42. /** User-question panel maximum height in terminal rows. */
  43. questionDialogMaxHeight?: number
  44. /** Model-selector width in terminal columns. */
  45. modelDialogWidth?: number
  46. /** Model-selector maximum height in terminal rows. */
  47. modelDialogMaxHeight?: number
  48. /** Maximum fuzzy file candidates displayed for one `@` query. */
  49. fileSearchMaxResults?: number
  50. /** Maximum paths retained in one `@` workspace index. */
  51. fileSearchMaxEntries?: number
  52. /** Directory basenames excluded from `@` traversal and completion. */
  53. fileSearchExcludedDirectories?: string[]
  54. /** Show the terminal's hardware cursor at the pi editor's IME marker. */
  55. showHardwareCursor?: boolean
  56. /** Color and prompt-template settings. */
  57. theme?: TuiThemeConfig
  58. /** Terminal window title while the UI is mounted; a logged session title prefixes it. */
  59. title?: string
  60. }
  61. const showReasoningSchema = z.boolean().default(true)
  62. const maxToolOutputLinesSchema = z.number().step(1).min(1).default(6)
  63. const maxQuestionOptionsSchema = z.number().step(1).min(1).default(8)
  64. const maxModelOptionsSchema = z.number().step(1).min(1).default(8)
  65. const maxResumeOptionsSchema = z.number().step(1).min(1).default(8)
  66. const questionDialogWidthSchema = z.number().step(1).min(20).default(200)
  67. const questionDialogMaxHeightSchema = z.number().step(1).min(6).default(20)
  68. const modelDialogWidthSchema = z.number().step(1).min(20).default(76)
  69. const modelDialogMaxHeightSchema = z.number().step(1).min(6).default(20)
  70. const fileSearchMaxResultsSchema = z.number().step(1).min(1).default(DEFAULT_FILE_SEARCH_MAX_RESULTS)
  71. const fileSearchMaxEntriesSchema = z.number().step(1).min(1).default(DEFAULT_FILE_SEARCH_MAX_ENTRIES)
  72. const fileSearchExcludedDirectoriesSchema = z.array(z.string()).default([...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES])
  73. const showHardwareCursorSchema = z.boolean().default(false)
  74. const colorSchema = z.boolean().default(true)
  75. // No default: an unset value auto-detects truecolor from COLORTERM in `apply`.
  76. const truecolorSchema = z.boolean()
  77. const DEFAULT_LEFT_PROMPT = '${cwd}${git/worktree}${model}${token_meter/cache_hit_rate}${context}'
  78. const DEFAULT_RIGHT_PROMPT = '${timing}'
  79. const DEFAULT_INPUT_PROMPT = '${symbol} ${indicator}'
  80. const DEFAULT_INPUT_PLACEHOLDER = 'press enter to steer and esc to cancel'
  81. const TuiThemeConfigSchema: z<TuiThemeConfig> = z.object({
  82. color: colorSchema,
  83. truecolor: truecolorSchema,
  84. leftPrompt: z.string().default(DEFAULT_LEFT_PROMPT),
  85. rightPrompt: z.string().default(DEFAULT_RIGHT_PROMPT),
  86. inputPrompt: z.string().default(DEFAULT_INPUT_PROMPT),
  87. inputPlaceholder: z.string().default(DEFAULT_INPUT_PLACEHOLDER),
  88. })
  89. const titleSchema = z.string().default('DeepSeek Harness')
  90. const tuiConfigSchemaFields = {
  91. showReasoning: showReasoningSchema,
  92. maxToolOutputLines: maxToolOutputLinesSchema,
  93. maxQuestionOptions: maxQuestionOptionsSchema,
  94. maxModelOptions: maxModelOptionsSchema,
  95. maxResumeOptions: maxResumeOptionsSchema,
  96. questionDialogWidth: questionDialogWidthSchema,
  97. questionDialogMaxHeight: questionDialogMaxHeightSchema,
  98. modelDialogWidth: modelDialogWidthSchema,
  99. modelDialogMaxHeight: modelDialogMaxHeightSchema,
  100. fileSearchMaxResults: fileSearchMaxResultsSchema,
  101. fileSearchMaxEntries: fileSearchMaxEntriesSchema,
  102. fileSearchExcludedDirectories: fileSearchExcludedDirectoriesSchema,
  103. showHardwareCursor: showHardwareCursorSchema,
  104. theme: TuiThemeConfigSchema,
  105. title: titleSchema,
  106. }
  107. /** Schemastery schema for presentation settings embedded by app bundles. */
  108. export const TuiConfigSchema: z<TuiConfig> = z.object(tuiConfigSchemaFields)
  109. /** Serializable plugin configuration. */
  110. export interface Config extends TuiConfig {
  111. /** Banner subtitle line. When absent, the banner has no subtitle and sweeps in on start. */
  112. welcome?: string
  113. /** Exact shared agent/session identity driven by this terminal. Defaults to `main`. */
  114. sessionId?: string
  115. /**
  116. * Shell command fallback printed on exit or after selecting a session when
  117. * the host cannot hand off in place. Every `{session}` becomes the selected
  118. * id; the TUI never executes this text. Absent disables only the fallback,
  119. * not the interactive selector.
  120. */
  121. resumeCommand?: string
  122. }
  123. /** Schemastery schema for the full plugin configuration. */
  124. export const Config: z<Config> = z.object({
  125. welcome: z.string(),
  126. sessionId: z.string().default('main'),
  127. resumeCommand: z.string(),
  128. showReasoning: tuiConfigSchemaFields.showReasoning,
  129. maxToolOutputLines: tuiConfigSchemaFields.maxToolOutputLines,
  130. maxQuestionOptions: tuiConfigSchemaFields.maxQuestionOptions,
  131. maxModelOptions: tuiConfigSchemaFields.maxModelOptions,
  132. maxResumeOptions: tuiConfigSchemaFields.maxResumeOptions,
  133. questionDialogWidth: tuiConfigSchemaFields.questionDialogWidth,
  134. questionDialogMaxHeight: tuiConfigSchemaFields.questionDialogMaxHeight,
  135. modelDialogWidth: tuiConfigSchemaFields.modelDialogWidth,
  136. modelDialogMaxHeight: tuiConfigSchemaFields.modelDialogMaxHeight,
  137. fileSearchMaxResults: tuiConfigSchemaFields.fileSearchMaxResults,
  138. fileSearchMaxEntries: tuiConfigSchemaFields.fileSearchMaxEntries,
  139. fileSearchExcludedDirectories: tuiConfigSchemaFields.fileSearchExcludedDirectories,
  140. showHardwareCursor: tuiConfigSchemaFields.showHardwareCursor,
  141. theme: tuiConfigSchemaFields.theme,
  142. title: tuiConfigSchemaFields.title,
  143. })
  144. /** Fully defaulted TUI theme settings. */
  145. export interface ResolvedTuiThemeConfig {
  146. color: boolean
  147. truecolor: boolean
  148. leftPrompt: string
  149. rightPrompt: string
  150. inputPrompt: string
  151. inputPlaceholder: string
  152. }
  153. /** Fully defaulted TUI presentation settings. */
  154. export interface ResolvedTuiConfig {
  155. showReasoning: boolean
  156. maxToolOutputLines: number
  157. maxQuestionOptions: number
  158. maxModelOptions: number
  159. maxResumeOptions: number
  160. questionDialogWidth: number
  161. questionDialogMaxHeight: number
  162. modelDialogWidth: number
  163. modelDialogMaxHeight: number
  164. fileSearchMaxResults: number
  165. fileSearchMaxEntries: number
  166. fileSearchExcludedDirectories: string[]
  167. showHardwareCursor: boolean
  168. theme: ResolvedTuiThemeConfig
  169. title: string
  170. }
  171. /**
  172. * Apply direct-call defaults after Loader schema validation has normally run.
  173. *
  174. * @param config - Deployment-provided terminal presentation settings.
  175. * @returns Complete settings consumed by the TUI renderer.
  176. */
  177. export function resolveTuiConfig(config: TuiConfig | undefined): ResolvedTuiConfig {
  178. return {
  179. showReasoning: config?.showReasoning ?? true,
  180. maxToolOutputLines: config?.maxToolOutputLines ?? 6,
  181. maxQuestionOptions: config?.maxQuestionOptions ?? 8,
  182. maxModelOptions: config?.maxModelOptions ?? 8,
  183. maxResumeOptions: config?.maxResumeOptions ?? 8,
  184. questionDialogWidth: config?.questionDialogWidth ?? 200,
  185. questionDialogMaxHeight: config?.questionDialogMaxHeight ?? 20,
  186. modelDialogWidth: config?.modelDialogWidth ?? 76,
  187. modelDialogMaxHeight: config?.modelDialogMaxHeight ?? 20,
  188. fileSearchMaxResults: config?.fileSearchMaxResults ?? DEFAULT_FILE_SEARCH_MAX_RESULTS,
  189. fileSearchMaxEntries: config?.fileSearchMaxEntries ?? DEFAULT_FILE_SEARCH_MAX_ENTRIES,
  190. fileSearchExcludedDirectories: [...(config?.fileSearchExcludedDirectories ?? DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES)],
  191. showHardwareCursor: config?.showHardwareCursor ?? false,
  192. theme: {
  193. color: config?.theme?.color ?? true,
  194. truecolor: config?.theme?.truecolor ?? false,
  195. leftPrompt: config?.theme?.leftPrompt ?? DEFAULT_LEFT_PROMPT,
  196. rightPrompt: config?.theme?.rightPrompt ?? DEFAULT_RIGHT_PROMPT,
  197. inputPrompt: config?.theme?.inputPrompt ?? DEFAULT_INPUT_PROMPT,
  198. inputPlaceholder: config?.theme?.inputPlaceholder ?? DEFAULT_INPUT_PLACEHOLDER,
  199. },
  200. title: config?.title ?? 'DeepSeek Harness',
  201. }
  202. }