thinking.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import { createMemo, type Setter } from "solid-js"
  2. import { useKV } from "./kv"
  3. export type ThinkingMode = "show" | "hide"
  4. const MODES: readonly ThinkingMode[] = ["show", "hide"] as const
  5. // OpenAI's Responses API surfaces reasoning summaries that start with a bolded
  6. // title block: "**Inspecting PR workflow**\n\n<body>". Treat that first block,
  7. // or a complete title still awaiting its body while streaming, as disclosure
  8. // metadata so the TUI can style its header independently from the markdown body.
  9. export function reasoningSummary(text: string) {
  10. const content = text.trim()
  11. const match = content.match(/^\*\*([^*\n]+)\*\*(?:\r?\n\r?\n|$)/)
  12. if (!match) return { title: null, body: content }
  13. return { title: match[1].trim(), body: content.slice(match[0].length).trimEnd() }
  14. }
  15. export function isThinkingMode(value: unknown): value is ThinkingMode {
  16. return typeof value === "string" && (MODES as readonly string[]).includes(value)
  17. }
  18. // Cycle order matches the slash command: show → hide → show.
  19. export function nextThinkingMode(current: ThinkingMode): ThinkingMode {
  20. const idx = MODES.indexOf(current)
  21. return MODES[(idx + 1) % MODES.length] ?? "show"
  22. }
  23. export function useThinkingMode() {
  24. const kv = useKV()
  25. // Capture pre-state before `kv.signal` seeds a default, so we can detect
  26. // first-time users with a legacy `thinking_visibility` boolean and migrate.
  27. // The KVProvider only renders children once kv.ready, so reads here are safe.
  28. const hadStored = kv.get("thinking_mode") !== undefined
  29. const legacy = kv.get("thinking_visibility")
  30. const [stored, setStored] = kv.signal<ThinkingMode>("thinking_mode", "hide")
  31. // The kv signal exposes its setter typed as `Setter<T>` which carries Solid's
  32. // overload set; passing an updater fn through a property access loses the
  33. // bivariance trick the existing `setX((prev) => ...)` callsites rely on.
  34. // Wrap it in a sane shape so consumers can just call `set(next)` or pass
  35. // an updater.
  36. const set = (next: ThinkingMode | ((prev: ThinkingMode) => ThinkingMode)) => {
  37. if (typeof next === "function") setStored(next as Setter<ThinkingMode>)
  38. else setStored(() => next)
  39. }
  40. // Preserve previous experience for users who had explicitly toggled the
  41. // legacy `thinking_visibility` boolean. First-time users (no legacy key)
  42. // get the new "hide" default (collapsed thinking).
  43. if (!hadStored) {
  44. if (legacy === true) set("show")
  45. else if (legacy === false) set("hide")
  46. }
  47. if ((stored() as string) === "minimal") set("hide")
  48. const mode = createMemo<ThinkingMode>(() => {
  49. const value = stored()
  50. return isThinkingMode(value) ? value : "hide"
  51. })
  52. return {
  53. mode,
  54. set,
  55. }
  56. }