runtime-flags.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. import { describe, expect } from "bun:test"
  2. import { ConfigProvider, Effect, Layer } from "effect"
  3. import { RuntimeFlags } from "../../src/effect/runtime-flags"
  4. import { it } from "../lib/effect"
  5. const fromConfig = (input: Record<string, unknown>) =>
  6. RuntimeFlags.defaultLayer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input))))
  7. const readFlags = RuntimeFlags.Service.useSync((flags) => flags)
  8. describe("RuntimeFlags", () => {
  9. it.effect("defaultLayer defaults autoShare to false", () =>
  10. Effect.gen(function* () {
  11. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  12. expect(flags.autoShare).toBe(false)
  13. }),
  14. )
  15. it.effect("defaultLayer parses plugin flags from the active ConfigProvider", () =>
  16. Effect.gen(function* () {
  17. const flags = yield* readFlags.pipe(
  18. Effect.provide(
  19. fromConfig({
  20. OPENCODE_PURE: "true",
  21. OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
  22. OPENCODE_AUTO_SHARE: "true",
  23. OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true",
  24. OPENCODE_DISABLE_EXTERNAL_SKILLS: "true",
  25. OPENCODE_DISABLE_LSP_DOWNLOAD: "true",
  26. OPENCODE_EXPERIMENTAL: "true",
  27. OPENCODE_ENABLE_EXA: "true",
  28. OPENCODE_ENABLE_PARALLEL: "true",
  29. OPENCODE_ENABLE_EXPERIMENTAL_MODELS: "true",
  30. OPENCODE_ENABLE_QUESTION_TOOL: "true",
  31. OPENCODE_CLIENT: "desktop",
  32. }),
  33. ),
  34. )
  35. expect(flags.pure).toBe(true)
  36. expect(flags.autoShare).toBe(true)
  37. expect(flags.disableDefaultPlugins).toBe(true)
  38. expect(flags.disableEmbeddedWebUi).toBe(true)
  39. expect(flags.disableExternalSkills).toBe(true)
  40. expect(flags.disableLspDownload).toBe(true)
  41. expect(flags.disableClaudeCodePrompt).toBe(false)
  42. expect(flags.enableExa).toBe(true)
  43. expect(flags.enableParallel).toBe(true)
  44. expect(flags.enableExperimentalModels).toBe(true)
  45. expect(flags.enableQuestionTool).toBe(true)
  46. expect(flags.experimentalReferences).toBe(true)
  47. expect(flags.experimentalBackgroundSubagents).toBe(true)
  48. expect(flags.experimentalLspTy).toBe(false)
  49. expect(flags.experimentalLspTool).toBe(true)
  50. expect(flags.experimentalOxfmt).toBe(true)
  51. expect(flags.experimentalPlanMode).toBe(true)
  52. expect(flags.experimentalEventSystem).toBe(true)
  53. expect(flags.experimentalWorkspaces).toBe(true)
  54. expect(flags.experimentalIconDiscovery).toBe(true)
  55. expect(flags.experimentalNativeLlm).toBe(false)
  56. expect(flags.experimentalWebSockets).toBe(false)
  57. expect(flags.client).toBe("desktop")
  58. }),
  59. )
  60. it.effect("defaultLayer parses OPENCODE_EXPERIMENTAL_LSP_TY", () =>
  61. Effect.gen(function* () {
  62. const flags = yield* readFlags.pipe(
  63. Effect.provide(
  64. fromConfig({
  65. OPENCODE_EXPERIMENTAL_LSP_TY: "true",
  66. }),
  67. ),
  68. )
  69. expect(flags.experimentalLspTy).toBe(true)
  70. }),
  71. )
  72. it.effect("enables native LLM via dedicated flag only", () =>
  73. Effect.gen(function* () {
  74. const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_NATIVE_LLM: "true" })))
  75. const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" })))
  76. expect(explicit.experimentalNativeLlm).toBe(true)
  77. expect(umbrella.experimentalNativeLlm).toBe(false)
  78. }),
  79. )
  80. it.effect("enables WebSockets via dedicated flag only", () =>
  81. Effect.gen(function* () {
  82. const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_WEBSOCKETS: "true" })))
  83. const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" })))
  84. expect(explicit.experimentalWebSockets).toBe(true)
  85. expect(umbrella.experimentalWebSockets).toBe(false)
  86. }),
  87. )
  88. it.effect("layer accepts partial test overrides and fills defaults from Config definitions", () =>
  89. Effect.gen(function* () {
  90. const flags = yield* readFlags.pipe(
  91. Effect.provide(RuntimeFlags.layer({ disableDefaultPlugins: true, bashDefaultTimeoutMs: 1_000 })),
  92. )
  93. expect(flags.pure).toBe(false)
  94. expect(flags.autoShare).toBe(false)
  95. expect(flags.disableDefaultPlugins).toBe(true)
  96. expect(flags.disableEmbeddedWebUi).toBe(false)
  97. expect(flags.disableExternalSkills).toBe(false)
  98. expect(flags.disableLspDownload).toBe(false)
  99. expect(flags.disableClaudeCodePrompt).toBe(false)
  100. expect(flags.disableClaudeCodeSkills).toBe(false)
  101. expect(flags.enableExa).toBe(false)
  102. expect(flags.experimentalIconDiscovery).toBe(false)
  103. expect(flags.experimentalOxfmt).toBe(false)
  104. expect(flags.outputTokenMax).toBeUndefined()
  105. expect(flags.bashDefaultTimeoutMs).toBe(1_000)
  106. expect(flags.enableExperimentalModels).toBe(false)
  107. expect(flags.client).toBe("cli")
  108. }),
  109. )
  110. it.effect("experimentalIconDiscovery defaults to false", () =>
  111. Effect.gen(function* () {
  112. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  113. expect(flags.experimentalIconDiscovery).toBe(false)
  114. }),
  115. )
  116. it.effect("disableExternalSkills defaults to false", () =>
  117. Effect.gen(function* () {
  118. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  119. expect(flags.disableExternalSkills).toBe(false)
  120. }),
  121. )
  122. it.effect("disableExternalSkills reads OPENCODE_DISABLE_EXTERNAL_SKILLS", () =>
  123. Effect.gen(function* () {
  124. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_EXTERNAL_SKILLS: "true" })))
  125. expect(flags.disableExternalSkills).toBe(true)
  126. }),
  127. )
  128. it.effect("disableLspDownload defaults to false", () =>
  129. Effect.gen(function* () {
  130. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  131. expect(flags.disableLspDownload).toBe(false)
  132. }),
  133. )
  134. it.effect("disableLspDownload reads OPENCODE_DISABLE_LSP_DOWNLOAD", () =>
  135. Effect.gen(function* () {
  136. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_LSP_DOWNLOAD: "true" })))
  137. expect(flags.disableLspDownload).toBe(true)
  138. }),
  139. )
  140. it.effect("disableClaudeCodePrompt defaults to false", () =>
  141. Effect.gen(function* () {
  142. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  143. expect(flags.disableClaudeCodePrompt).toBe(false)
  144. }),
  145. )
  146. it.effect("disableClaudeCodePrompt reads OPENCODE_DISABLE_CLAUDE_CODE_PROMPT", () =>
  147. Effect.gen(function* () {
  148. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE_PROMPT: "true" })))
  149. expect(flags.disableClaudeCodePrompt).toBe(true)
  150. }),
  151. )
  152. it.effect("disableClaudeCodePrompt inherits OPENCODE_DISABLE_CLAUDE_CODE", () =>
  153. Effect.gen(function* () {
  154. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE: "true" })))
  155. expect(flags.disableClaudeCodePrompt).toBe(true)
  156. }),
  157. )
  158. it.effect("experimentalIconDiscovery reads OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", () =>
  159. Effect.gen(function* () {
  160. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true" })))
  161. expect(flags.experimentalIconDiscovery).toBe(true)
  162. }),
  163. )
  164. it.effect("experimentalIconDiscovery inherits OPENCODE_EXPERIMENTAL", () =>
  165. Effect.gen(function* () {
  166. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" })))
  167. expect(flags.experimentalIconDiscovery).toBe(true)
  168. }),
  169. )
  170. it.effect("specific experimental flags override OPENCODE_EXPERIMENTAL", () =>
  171. Effect.gen(function* () {
  172. const flags = yield* readFlags.pipe(
  173. Effect.provide(
  174. fromConfig({
  175. OPENCODE_EXPERIMENTAL: "true",
  176. OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "false",
  177. }),
  178. ),
  179. )
  180. expect(flags.experimentalIconDiscovery).toBe(false)
  181. }),
  182. )
  183. it.effect("experimentalOxfmt defaults to false", () =>
  184. Effect.gen(function* () {
  185. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  186. expect(flags.experimentalOxfmt).toBe(false)
  187. }),
  188. )
  189. it.effect("experimentalOxfmt is enabled by OPENCODE_EXPERIMENTAL_OXFMT", () =>
  190. Effect.gen(function* () {
  191. const flags = yield* readFlags.pipe(
  192. Effect.provide(
  193. fromConfig({
  194. OPENCODE_EXPERIMENTAL_OXFMT: "true",
  195. }),
  196. ),
  197. )
  198. expect(flags.experimentalOxfmt).toBe(true)
  199. }),
  200. )
  201. it.effect("experimentalOxfmt inherits OPENCODE_EXPERIMENTAL", () =>
  202. Effect.gen(function* () {
  203. const flags = yield* readFlags.pipe(
  204. Effect.provide(
  205. fromConfig({
  206. OPENCODE_EXPERIMENTAL: "true",
  207. }),
  208. ),
  209. )
  210. expect(flags.experimentalOxfmt).toBe(true)
  211. }),
  212. )
  213. for (const input of [
  214. { name: "absent", config: {}, expected: undefined },
  215. {
  216. name: "valid positive integer",
  217. config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234" },
  218. expected: 1234,
  219. },
  220. {
  221. name: "invalid string",
  222. config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "nope" },
  223. expected: undefined,
  224. },
  225. { name: "zero", config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "0" }, expected: undefined },
  226. { name: "negative", config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "-1" }, expected: undefined },
  227. {
  228. name: "non-integer",
  229. config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1.5" },
  230. expected: undefined,
  231. },
  232. ]) {
  233. it.effect(`parses bashDefaultTimeoutMs from config: ${input.name}`, () =>
  234. Effect.gen(function* () {
  235. const flags = yield* readFlags.pipe(Effect.provide(fromConfig(input.config)))
  236. expect(flags.bashDefaultTimeoutMs).toBe(input.expected)
  237. }),
  238. )
  239. }
  240. for (const input of [
  241. { name: "absent", config: {}, expected: undefined },
  242. {
  243. name: "valid positive integer",
  244. config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1234" },
  245. expected: 1234,
  246. },
  247. {
  248. name: "invalid string",
  249. config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "nope" },
  250. expected: undefined,
  251. },
  252. { name: "zero", config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "0" }, expected: undefined },
  253. { name: "negative", config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "-1" }, expected: undefined },
  254. {
  255. name: "non-integer",
  256. config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1.5" },
  257. expected: undefined,
  258. },
  259. ]) {
  260. it.effect(`parses outputTokenMax from config: ${input.name}`, () =>
  261. Effect.gen(function* () {
  262. const flags = yield* readFlags.pipe(Effect.provide(fromConfig(input.config)))
  263. expect(flags.outputTokenMax).toBe(input.expected)
  264. }),
  265. )
  266. }
  267. it.effect("layer ignores the active ConfigProvider for omitted test overrides", () =>
  268. Effect.gen(function* () {
  269. const flags = yield* readFlags.pipe(
  270. Effect.provide(RuntimeFlags.layer()),
  271. Effect.provide(
  272. ConfigProvider.layer(
  273. ConfigProvider.fromUnknown({
  274. OPENCODE_PURE: "true",
  275. OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
  276. OPENCODE_DISABLE_EXTERNAL_SKILLS: "true",
  277. OPENCODE_DISABLE_LSP_DOWNLOAD: "true",
  278. OPENCODE_EXPERIMENTAL: "true",
  279. OPENCODE_ENABLE_EXA: "true",
  280. OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234",
  281. OPENCODE_CLIENT: "desktop",
  282. }),
  283. ),
  284. ),
  285. )
  286. expect(flags.pure).toBe(false)
  287. expect(flags.disableDefaultPlugins).toBe(false)
  288. expect(flags.disableEmbeddedWebUi).toBe(false)
  289. expect(flags.disableExternalSkills).toBe(false)
  290. expect(flags.disableLspDownload).toBe(false)
  291. expect(flags.disableClaudeCodePrompt).toBe(false)
  292. expect(flags.disableClaudeCodeSkills).toBe(false)
  293. expect(flags.enableExa).toBe(false)
  294. expect(flags.experimentalIconDiscovery).toBe(false)
  295. expect(flags.experimentalOxfmt).toBe(false)
  296. expect(flags.outputTokenMax).toBeUndefined()
  297. expect(flags.bashDefaultTimeoutMs).toBeUndefined()
  298. expect(flags.client).toBe("cli")
  299. }),
  300. )
  301. it.effect("disableClaudeCodeSkills defaults to false", () =>
  302. Effect.gen(function* () {
  303. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
  304. expect(flags.disableClaudeCodeSkills).toBe(false)
  305. }),
  306. )
  307. it.effect("disableClaudeCodeSkills reads OPENCODE_DISABLE_CLAUDE_CODE_SKILLS", () =>
  308. Effect.gen(function* () {
  309. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "true" })))
  310. expect(flags.disableClaudeCodeSkills).toBe(true)
  311. }),
  312. )
  313. it.effect("disableClaudeCodeSkills inherits OPENCODE_DISABLE_CLAUDE_CODE", () =>
  314. Effect.gen(function* () {
  315. const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE: "true" })))
  316. expect(flags.disableClaudeCodeSkills).toBe(true)
  317. }),
  318. )
  319. })