terminal.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import type { ModelProbeState } from "./model-selection"
  2. export const terminalAttr = "data-pty-id"
  3. export type TerminalProbeState = {
  4. connected: boolean
  5. connects: number
  6. rendered: string
  7. settled: number
  8. focusing: number
  9. }
  10. type TerminalProbeControl = {
  11. disconnect?: VoidFunction
  12. }
  13. export type E2EWindow = Window & {
  14. __opencode_e2e?: {
  15. model?: {
  16. enabled?: boolean
  17. current?: ModelProbeState
  18. }
  19. prompt?: {
  20. enabled?: boolean
  21. current?: import("./prompt").PromptProbeState
  22. sent?: import("./prompt").PromptSendState
  23. }
  24. terminal?: {
  25. enabled?: boolean
  26. terminals?: Record<string, TerminalProbeState>
  27. controls?: Record<string, TerminalProbeControl>
  28. }
  29. }
  30. }
  31. const seed = (): TerminalProbeState => ({
  32. connected: false,
  33. connects: 0,
  34. rendered: "",
  35. settled: 0,
  36. focusing: 0,
  37. })
  38. const root = () => {
  39. if (typeof window === "undefined") return
  40. const state = (window as E2EWindow).__opencode_e2e?.terminal
  41. if (!state?.enabled) return
  42. return state
  43. }
  44. const terms = () => {
  45. const state = root()
  46. if (!state) return
  47. state.terminals ??= {}
  48. return state.terminals
  49. }
  50. const controls = () => {
  51. const state = root()
  52. if (!state) return
  53. state.controls ??= {}
  54. return state.controls
  55. }
  56. export const terminalProbe = (id: string) => {
  57. const set = (next: Partial<TerminalProbeState>) => {
  58. const state = terms()
  59. if (!state) return
  60. state[id] = { ...(state[id] ?? seed()), ...next }
  61. }
  62. return {
  63. init() {
  64. set(seed())
  65. },
  66. connect() {
  67. const state = terms()
  68. if (!state) return
  69. const prev = state[id] ?? seed()
  70. state[id] = {
  71. ...prev,
  72. connected: true,
  73. connects: prev.connects + 1,
  74. }
  75. },
  76. render(data: string) {
  77. const state = terms()
  78. if (!state) return
  79. const prev = state[id] ?? seed()
  80. state[id] = { ...prev, rendered: prev.rendered + data }
  81. },
  82. settle() {
  83. const state = terms()
  84. if (!state) return
  85. const prev = state[id] ?? seed()
  86. state[id] = { ...prev, settled: prev.settled + 1 }
  87. },
  88. focus(count: number) {
  89. set({ focusing: Math.max(0, count) })
  90. },
  91. step() {
  92. const state = terms()
  93. if (!state) return
  94. const prev = state[id] ?? seed()
  95. state[id] = { ...prev, focusing: Math.max(0, prev.focusing - 1) }
  96. },
  97. control(next: Partial<TerminalProbeControl>) {
  98. const state = controls()
  99. if (!state) return
  100. state[id] = { ...(state[id] ?? {}), ...next }
  101. },
  102. drop() {
  103. const state = terms()
  104. if (state) delete state[id]
  105. const control = controls()
  106. if (control) delete control[id]
  107. },
  108. }
  109. }