@vybestack/llxprt-code 0.4.8 → 0.5.0-nightly.251102.6bb3db7a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/dist/package.json +5 -3
  2. package/dist/src/auth/__tests__/oauthManager.safety.test.d.ts +6 -0
  3. package/dist/src/auth/__tests__/oauthManager.safety.test.js +49 -0
  4. package/dist/src/auth/__tests__/oauthManager.safety.test.js.map +1 -0
  5. package/dist/src/auth/oauth-manager.d.ts +11 -0
  6. package/dist/src/auth/oauth-manager.js +62 -29
  7. package/dist/src/auth/oauth-manager.js.map +1 -1
  8. package/dist/src/auth/oauth-manager.spec.js +7 -2
  9. package/dist/src/auth/oauth-manager.spec.js.map +1 -1
  10. package/dist/src/config/__tests__/nonInteractiveTools.test.d.ts +6 -0
  11. package/dist/src/config/__tests__/nonInteractiveTools.test.js +13 -0
  12. package/dist/src/config/__tests__/nonInteractiveTools.test.js.map +1 -0
  13. package/dist/src/config/__tests__/profileBootstrap.test.d.ts +6 -0
  14. package/dist/src/config/__tests__/profileBootstrap.test.js +91 -0
  15. package/dist/src/config/__tests__/profileBootstrap.test.js.map +1 -0
  16. package/dist/src/config/config.d.ts +6 -2
  17. package/dist/src/config/config.js +219 -18
  18. package/dist/src/config/config.js.map +1 -1
  19. package/dist/src/config/profileBootstrap.d.ts +64 -0
  20. package/dist/src/config/profileBootstrap.js +140 -0
  21. package/dist/src/config/profileBootstrap.js.map +1 -0
  22. package/dist/src/gemini.js +68 -23
  23. package/dist/src/gemini.js.map +1 -1
  24. package/dist/src/gemini.test.js +1 -2
  25. package/dist/src/gemini.test.js.map +1 -1
  26. package/dist/src/generated/git-commit.d.ts +1 -1
  27. package/dist/src/generated/git-commit.js +1 -1
  28. package/dist/src/integration-tests/base-url-behavior.integration.test.js +110 -450
  29. package/dist/src/integration-tests/base-url-behavior.integration.test.js.map +1 -1
  30. package/dist/src/integration-tests/model-params-isolation.integration.test.js +101 -539
  31. package/dist/src/integration-tests/model-params-isolation.integration.test.js.map +1 -1
  32. package/dist/src/integration-tests/modelParams.integration.test.js +86 -761
  33. package/dist/src/integration-tests/modelParams.integration.test.js.map +1 -1
  34. package/dist/src/integration-tests/provider-multi-runtime.integration.test.d.ts +6 -0
  35. package/dist/src/integration-tests/provider-multi-runtime.integration.test.js +198 -0
  36. package/dist/src/integration-tests/provider-multi-runtime.integration.test.js.map +1 -0
  37. package/dist/src/integration-tests/provider-switching.integration.test.js +97 -151
  38. package/dist/src/integration-tests/provider-switching.integration.test.js.map +1 -1
  39. package/dist/src/integration-tests/runtime-isolation.test.d.ts +13 -0
  40. package/dist/src/integration-tests/runtime-isolation.test.js +170 -0
  41. package/dist/src/integration-tests/runtime-isolation.test.js.map +1 -0
  42. package/dist/src/integration-tests/test-utils.js +19 -2
  43. package/dist/src/integration-tests/test-utils.js.map +1 -1
  44. package/dist/src/integration-tests/test-utils.test.js +9 -8
  45. package/dist/src/integration-tests/test-utils.test.js.map +1 -1
  46. package/dist/src/integration-tests/todo-continuation.integration.test.js +5 -2
  47. package/dist/src/integration-tests/todo-continuation.integration.test.js.map +1 -1
  48. package/dist/src/integration-tests/tools-governance.integration.test.d.ts +6 -0
  49. package/dist/src/integration-tests/tools-governance.integration.test.js +98 -0
  50. package/dist/src/integration-tests/tools-governance.integration.test.js.map +1 -0
  51. package/dist/src/nonInteractiveCli.js +36 -11
  52. package/dist/src/nonInteractiveCli.js.map +1 -1
  53. package/dist/src/providers/logging/git-stats.test.js +11 -1
  54. package/dist/src/providers/logging/git-stats.test.js.map +1 -1
  55. package/dist/src/providers/logging/multi-provider-logging.integration.test.js +1 -2
  56. package/dist/src/providers/logging/multi-provider-logging.integration.test.js.map +1 -1
  57. package/dist/src/providers/logging/performance.test.js +1 -1
  58. package/dist/src/providers/logging/performance.test.js.map +1 -1
  59. package/dist/src/providers/oauth-provider-registration.d.ts +2 -2
  60. package/dist/src/providers/oauth-provider-registration.js +25 -9
  61. package/dist/src/providers/oauth-provider-registration.js.map +1 -1
  62. package/dist/src/providers/provider-gemini-switching.test.js +67 -89
  63. package/dist/src/providers/provider-gemini-switching.test.js.map +1 -1
  64. package/dist/src/providers/provider-switching.integration.test.js +42 -98
  65. package/dist/src/providers/provider-switching.integration.test.js.map +1 -1
  66. package/dist/src/providers/providerConfigUtils.d.ts +12 -7
  67. package/dist/src/providers/providerConfigUtils.js +31 -99
  68. package/dist/src/providers/providerConfigUtils.js.map +1 -1
  69. package/dist/src/providers/providerManagerInstance.d.ts +17 -1
  70. package/dist/src/providers/providerManagerInstance.js +157 -175
  71. package/dist/src/providers/providerManagerInstance.js.map +1 -1
  72. package/dist/src/providers/providerManagerInstance.oauthRegistration.test.js +19 -15
  73. package/dist/src/providers/providerManagerInstance.oauthRegistration.test.js.map +1 -1
  74. package/dist/src/providers/providerManagerInstance.test.js +2 -5
  75. package/dist/src/providers/providerManagerInstance.test.js.map +1 -1
  76. package/dist/src/runtime/__tests__/profileApplication.test.d.ts +5 -0
  77. package/dist/src/runtime/__tests__/profileApplication.test.js +232 -0
  78. package/dist/src/runtime/__tests__/profileApplication.test.js.map +1 -0
  79. package/dist/src/runtime/__tests__/runtimeIsolation.test.d.ts +5 -0
  80. package/dist/src/runtime/__tests__/runtimeIsolation.test.js +376 -0
  81. package/dist/src/runtime/__tests__/runtimeIsolation.test.js.map +1 -0
  82. package/dist/src/runtime/agentRuntimeAdapter.d.ts +249 -0
  83. package/dist/src/runtime/agentRuntimeAdapter.js +506 -0
  84. package/dist/src/runtime/agentRuntimeAdapter.js.map +1 -0
  85. package/dist/src/runtime/agentRuntimeAdapter.spec.d.ts +6 -0
  86. package/dist/src/runtime/agentRuntimeAdapter.spec.js +866 -0
  87. package/dist/src/runtime/agentRuntimeAdapter.spec.js.map +1 -0
  88. package/dist/src/runtime/messages.d.ts +28 -0
  89. package/dist/src/runtime/messages.js +64 -0
  90. package/dist/src/runtime/messages.js.map +1 -0
  91. package/dist/src/runtime/profileApplication.d.ts +33 -0
  92. package/dist/src/runtime/profileApplication.js +191 -0
  93. package/dist/src/runtime/profileApplication.js.map +1 -0
  94. package/dist/src/runtime/providerConfigUtils.test.d.ts +1 -0
  95. package/dist/src/runtime/providerConfigUtils.test.js +68 -0
  96. package/dist/src/runtime/providerConfigUtils.test.js.map +1 -0
  97. package/dist/src/runtime/runtimeContextFactory.d.ts +102 -0
  98. package/dist/src/runtime/runtimeContextFactory.js +190 -0
  99. package/dist/src/runtime/runtimeContextFactory.js.map +1 -0
  100. package/dist/src/runtime/runtimeSettings.d.ts +217 -0
  101. package/dist/src/runtime/runtimeSettings.js +1094 -0
  102. package/dist/src/runtime/runtimeSettings.js.map +1 -0
  103. package/dist/src/runtime/runtimeSettings.test.d.ts +1 -0
  104. package/dist/src/runtime/runtimeSettings.test.js +320 -0
  105. package/dist/src/runtime/runtimeSettings.test.js.map +1 -0
  106. package/dist/src/services/BuiltinCommandLoader.d.ts +13 -4
  107. package/dist/src/services/BuiltinCommandLoader.js +17 -4
  108. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  109. package/dist/src/services/McpPromptLoader.js +34 -13
  110. package/dist/src/services/McpPromptLoader.js.map +1 -1
  111. package/dist/src/test-utils/mockCommandContext.js +5 -2
  112. package/dist/src/test-utils/mockCommandContext.js.map +1 -1
  113. package/dist/src/ui/App.js +29 -49
  114. package/dist/src/ui/App.js.map +1 -1
  115. package/dist/src/ui/commands/aboutCommand.js +59 -38
  116. package/dist/src/ui/commands/aboutCommand.js.map +1 -1
  117. package/dist/src/ui/commands/authCommand.js +7 -9
  118. package/dist/src/ui/commands/authCommand.js.map +1 -1
  119. package/dist/src/ui/commands/baseurlCommand.js +8 -44
  120. package/dist/src/ui/commands/baseurlCommand.js.map +1 -1
  121. package/dist/src/ui/commands/chatCommand.js +28 -12
  122. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  123. package/dist/src/ui/commands/diagnosticsCommand.d.ts +0 -3
  124. package/dist/src/ui/commands/diagnosticsCommand.js +45 -191
  125. package/dist/src/ui/commands/diagnosticsCommand.js.map +1 -1
  126. package/dist/src/ui/commands/keyCommand.js +9 -58
  127. package/dist/src/ui/commands/keyCommand.js.map +1 -1
  128. package/dist/src/ui/commands/keyCommand.test.js +48 -102
  129. package/dist/src/ui/commands/keyCommand.test.js.map +1 -1
  130. package/dist/src/ui/commands/keyfileCommand.js +42 -93
  131. package/dist/src/ui/commands/keyfileCommand.js.map +1 -1
  132. package/dist/src/ui/commands/logoutCommand.js +2 -2
  133. package/dist/src/ui/commands/logoutCommand.js.map +1 -1
  134. package/dist/src/ui/commands/mcpCommand.js +29 -7
  135. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  136. package/dist/src/ui/commands/modelCommand.js +8 -59
  137. package/dist/src/ui/commands/modelCommand.js.map +1 -1
  138. package/dist/src/ui/commands/profileCommand.js +151 -267
  139. package/dist/src/ui/commands/profileCommand.js.map +1 -1
  140. package/dist/src/ui/commands/profileCommand.test.js +88 -344
  141. package/dist/src/ui/commands/profileCommand.test.js.map +1 -1
  142. package/dist/src/ui/commands/providerCommand.js +9 -3
  143. package/dist/src/ui/commands/providerCommand.js.map +1 -1
  144. package/dist/src/ui/commands/restoreCommand.js +38 -18
  145. package/dist/src/ui/commands/restoreCommand.js.map +1 -1
  146. package/dist/src/ui/commands/schema/argumentResolver.test.d.ts +6 -0
  147. package/dist/src/ui/commands/schema/argumentResolver.test.js +619 -0
  148. package/dist/src/ui/commands/schema/argumentResolver.test.js.map +1 -0
  149. package/dist/src/ui/commands/schema/index.d.ts +15 -0
  150. package/dist/src/ui/commands/schema/index.js +320 -0
  151. package/dist/src/ui/commands/schema/index.js.map +1 -0
  152. package/dist/src/ui/commands/schema/types.d.ts +61 -0
  153. package/dist/src/ui/commands/schema/types.js +12 -0
  154. package/dist/src/ui/commands/schema/types.js.map +1 -0
  155. package/dist/src/ui/commands/setCommand.js +641 -325
  156. package/dist/src/ui/commands/setCommand.js.map +1 -1
  157. package/dist/src/ui/commands/setCommand.test.js +92 -388
  158. package/dist/src/ui/commands/setCommand.test.js.map +1 -1
  159. package/dist/src/ui/commands/statusCommand.js +2 -2
  160. package/dist/src/ui/commands/statusCommand.js.map +1 -1
  161. package/dist/src/ui/commands/subagentCommand.d.ts +16 -0
  162. package/dist/src/ui/commands/subagentCommand.js +674 -0
  163. package/dist/src/ui/commands/subagentCommand.js.map +1 -0
  164. package/dist/src/ui/commands/test/setCommand.mutation.test.d.ts +6 -0
  165. package/dist/src/ui/commands/test/setCommand.mutation.test.js +132 -0
  166. package/dist/src/ui/commands/test/setCommand.mutation.test.js.map +1 -0
  167. package/dist/src/ui/commands/test/setCommand.phase09.test.d.ts +6 -0
  168. package/dist/src/ui/commands/test/setCommand.phase09.test.js +222 -0
  169. package/dist/src/ui/commands/test/setCommand.phase09.test.js.map +1 -0
  170. package/dist/src/ui/commands/test/subagentCommand.schema.test.d.ts +6 -0
  171. package/dist/src/ui/commands/test/subagentCommand.schema.test.js +125 -0
  172. package/dist/src/ui/commands/test/subagentCommand.schema.test.js.map +1 -0
  173. package/dist/src/ui/commands/test/subagentCommand.test.d.ts +1 -0
  174. package/dist/src/ui/commands/test/subagentCommand.test.js +598 -0
  175. package/dist/src/ui/commands/test/subagentCommand.test.js.map +1 -0
  176. package/dist/src/ui/commands/toolformatCommand.js +25 -98
  177. package/dist/src/ui/commands/toolformatCommand.js.map +1 -1
  178. package/dist/src/ui/commands/toolformatCommand.test.js +56 -102
  179. package/dist/src/ui/commands/toolformatCommand.test.js.map +1 -1
  180. package/dist/src/ui/commands/toolsCommand.js +187 -31
  181. package/dist/src/ui/commands/toolsCommand.js.map +1 -1
  182. package/dist/src/ui/commands/types.d.ts +11 -2
  183. package/dist/src/ui/commands/types.js.map +1 -1
  184. package/dist/src/ui/components/AuthDialog.js +16 -55
  185. package/dist/src/ui/components/AuthDialog.js.map +1 -1
  186. package/dist/src/ui/components/Footer.js +4 -5
  187. package/dist/src/ui/components/Footer.js.map +1 -1
  188. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  189. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  190. package/dist/src/ui/components/InputPrompt.js +1 -1
  191. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  192. package/dist/src/ui/components/StatsDisplay.js +6 -11
  193. package/dist/src/ui/components/StatsDisplay.js.map +1 -1
  194. package/dist/src/ui/components/SuggestionsDisplay.d.ts +13 -1
  195. package/dist/src/ui/components/SuggestionsDisplay.js +22 -3
  196. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  197. package/dist/src/ui/components/messages/ToolGroupMessage.d.ts +1 -0
  198. package/dist/src/ui/components/messages/ToolGroupMessage.js +14 -14
  199. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  200. package/dist/src/ui/components/messages/ToolGroupMessage.test.js +1 -0
  201. package/dist/src/ui/components/messages/ToolGroupMessage.test.js.map +1 -1
  202. package/dist/src/ui/containers/SessionController.js +61 -117
  203. package/dist/src/ui/containers/SessionController.js.map +1 -1
  204. package/dist/src/ui/contexts/RuntimeContext.d.ts +61 -0
  205. package/dist/src/ui/contexts/RuntimeContext.js +118 -0
  206. package/dist/src/ui/contexts/RuntimeContext.js.map +1 -0
  207. package/dist/src/ui/contexts/TodoProvider.d.ts +1 -0
  208. package/dist/src/ui/contexts/TodoProvider.js +10 -8
  209. package/dist/src/ui/contexts/TodoProvider.js.map +1 -1
  210. package/dist/src/ui/contexts/ToolCallProvider.d.ts +1 -0
  211. package/dist/src/ui/contexts/ToolCallProvider.js +10 -9
  212. package/dist/src/ui/contexts/ToolCallProvider.js.map +1 -1
  213. package/dist/src/ui/hooks/__tests__/useSlashCompletion.set.phase09.test.d.ts +6 -0
  214. package/dist/src/ui/hooks/__tests__/useSlashCompletion.set.phase09.test.js +39 -0
  215. package/dist/src/ui/hooks/__tests__/useSlashCompletion.set.phase09.test.js.map +1 -0
  216. package/dist/src/ui/hooks/atCommandProcessor.js +11 -3
  217. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  218. package/dist/src/ui/hooks/atCommandProcessor.test.js +3 -1
  219. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  220. package/dist/src/ui/hooks/shellCommandProcessor.js +4 -1
  221. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  222. package/dist/src/ui/hooks/shellCommandProcessor.test.js +1 -0
  223. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  224. package/dist/src/ui/hooks/slashCommandProcessor.js +27 -1
  225. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  226. package/dist/src/ui/hooks/useAuthCommand.js +11 -3
  227. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  228. package/dist/src/ui/hooks/useCommandCompletion.d.ts +1 -0
  229. package/dist/src/ui/hooks/useCommandCompletion.js +2 -0
  230. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  231. package/dist/src/ui/hooks/useGeminiStream.js +37 -11
  232. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  233. package/dist/src/ui/hooks/useGeminiStream.subagent.spec.d.ts +6 -0
  234. package/dist/src/ui/hooks/useGeminiStream.subagent.spec.js +232 -0
  235. package/dist/src/ui/hooks/useGeminiStream.subagent.spec.js.map +1 -0
  236. package/dist/src/ui/hooks/useLoadProfileDialog.d.ts +1 -1
  237. package/dist/src/ui/hooks/useLoadProfileDialog.js +18 -57
  238. package/dist/src/ui/hooks/useLoadProfileDialog.js.map +1 -1
  239. package/dist/src/ui/hooks/useOpenAIProviderInfo.d.ts +1 -1
  240. package/dist/src/ui/hooks/useOpenAIProviderInfo.js +12 -7
  241. package/dist/src/ui/hooks/useOpenAIProviderInfo.js.map +1 -1
  242. package/dist/src/ui/hooks/useProviderDialog.d.ts +1 -1
  243. package/dist/src/ui/hooks/useProviderDialog.js +17 -90
  244. package/dist/src/ui/hooks/useProviderDialog.js.map +1 -1
  245. package/dist/src/ui/hooks/useProviderModelDialog.d.ts +2 -2
  246. package/dist/src/ui/hooks/useProviderModelDialog.js +11 -12
  247. package/dist/src/ui/hooks/useProviderModelDialog.js.map +1 -1
  248. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +3 -1
  249. package/dist/src/ui/hooks/useReactToolScheduler.js +144 -34
  250. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  251. package/dist/src/ui/hooks/useSlashCompletion.d.ts +32 -0
  252. package/dist/src/ui/hooks/useSlashCompletion.js +154 -77
  253. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
  254. package/dist/src/ui/hooks/useSlashCompletion.test.js +39 -14
  255. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  256. package/dist/src/ui/hooks/useToolScheduler.test.js +108 -79
  257. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  258. package/dist/src/ui/types.d.ts +1 -0
  259. package/dist/src/ui/types.js.map +1 -1
  260. package/dist/src/utils/sandbox.js +7 -5
  261. package/dist/src/utils/sandbox.js.map +1 -1
  262. package/dist/src/validateNonInterActiveAuth.js +4 -2
  263. package/dist/src/validateNonInterActiveAuth.js.map +1 -1
  264. package/dist/src/zed-integration/schema.d.ts +30 -30
  265. package/dist/src/zed-integration/zedIntegration.js +112 -39
  266. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  267. package/dist/tsconfig.build.tsbuildinfo +1 -0
  268. package/package.json +5 -3
  269. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -4,326 +4,685 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { CommandKind, } from './types.js';
7
- import { ephemeralSettingHelp, parseEphemeralSettingValue, } from '../../settings/ephemeralSettings.js';
8
- import { parseModelParamValue } from '../../settings/modelParamParser.js';
7
+ import { getRuntimeApi } from '../contexts/RuntimeContext.js';
9
8
  // Subcommand for /set unset - removes ephemeral settings or model parameters
10
- const unsetCommand = {
11
- name: 'unset',
12
- description: 'remove an ephemeral setting or model parameter',
9
+ /**
10
+ * Implementation for the /set command that handles both:
11
+ * - /set modelparam <key> <value>
12
+ * - /set <ephemeral-key> <value>
13
+ */
14
+ // Help text for ephemeral settings - these are session-only and saved only via profiles
15
+ const ephemeralSettingHelp = {
16
+ 'context-limit': 'Maximum number of tokens for the context window (e.g., 100000)',
17
+ 'compression-threshold': 'Fraction of context limit that triggers compression (0.0-1.0, e.g., 0.7 for 70%)',
18
+ 'base-url': 'Base URL for API requests',
19
+ 'tool-format': 'Tool format override for the provider',
20
+ 'api-version': 'API version to use',
21
+ 'custom-headers': 'Custom HTTP headers as JSON object',
22
+ 'stream-options': 'Stream options for OpenAI API (default: { include_usage: true })',
23
+ streaming: 'Enable or disable streaming responses (enabled/disabled, default: enabled)',
24
+ 'shell-replacement': 'Allow command substitution ($(), <(), backticks) in shell commands (default: false)',
25
+ // Socket configuration for local AI servers (LM Studio, Ollama, etc.)
26
+ 'socket-timeout': 'Request timeout in milliseconds for local AI servers (default: 60000)',
27
+ 'socket-keepalive': 'Enable TCP keepalive for local AI server connections (true/false, default: true)',
28
+ 'socket-nodelay': 'Enable TCP_NODELAY concept for local AI servers (true/false, default: true)',
29
+ // Tool output limit settings - apply to all tools that can return large outputs
30
+ 'tool-output-max-items': 'Maximum number of items/files/matches returned by tools (default: 50)',
31
+ 'tool-output-max-tokens': 'Maximum tokens in tool output (default: 50000)',
32
+ 'tool-output-truncate-mode': 'How to handle exceeding limits: warn, truncate, or sample (default: warn)',
33
+ 'tool-output-item-size-limit': 'Maximum size per item/file in bytes (default: 524288 = 512KB)',
34
+ // Final catch-all to prevent context overflow
35
+ 'max-prompt-tokens': 'Maximum tokens allowed in any prompt sent to LLM (default: 200000)',
36
+ // Emoji filter settings
37
+ emojifilter: 'Emoji filter mode (allowed, auto, warn, error)',
38
+ // Retry settings
39
+ retries: 'Maximum number of retry attempts for API calls (default: varies by provider)',
40
+ retrywait: 'Initial delay in milliseconds between retry attempts (default: varies by provider)',
41
+ // Loop prevention settings
42
+ maxTurnsPerPrompt: 'Maximum number of turns allowed per prompt before stopping (default: 100, -1 for unlimited)',
43
+ };
44
+ /**
45
+ * Schema-based completion for /set command, redesigned to match P09 test expectations.
46
+ *
47
+ * @plan:PLAN-20251013-AUTOCOMPLETE.P10
48
+ * @requirement:REQ-006
49
+ * @pseudocode ArgumentSchema.md lines 111-130
50
+ * - Line 111: literal `unset`
51
+ * - Line 112: literal `modelparam`
52
+ * - Line 113: literal `emojifilter`
53
+ * - Line 114: nested value arg for param name
54
+ * - Line 115: nested value arg for param value
55
+ * - Line 116: hint for emoji mode
56
+ * - Line 117-120: dynamic completers for providers/params
57
+ *
58
+ * Note: The P09 test uses a mixed approach: literals for 'unset', 'modelparam', 'emojifilter'
59
+ * and a single top-level 'value' argument for other settings. This implementation matches that.
60
+ */
61
+ const toTitleCase = (input) => input
62
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
63
+ .replace(/[-_]/g, ' ')
64
+ .replace(/\s+/g, ' ')
65
+ .trim()
66
+ .replace(/\b\w/g, (char) => char.toUpperCase());
67
+ const booleanOptions = [
68
+ { value: 'true', description: 'true' },
69
+ { value: 'false', description: 'false' },
70
+ ];
71
+ const streamingOptions = [
72
+ { value: 'enabled', description: 'enabled' },
73
+ { value: 'disabled', description: 'disabled' },
74
+ ];
75
+ const emojifilterOptions = [
76
+ { value: 'allowed', description: 'Allow all emojis' },
77
+ { value: 'auto', description: 'Automatically filter inappropriate emojis' },
78
+ { value: 'warn', description: 'Warn about filtered emojis' },
79
+ { value: 'error', description: 'Error on filtered emojis' },
80
+ ];
81
+ const truncateModeOptions = [
82
+ { value: 'warn', description: 'warn' },
83
+ { value: 'truncate', description: 'truncate' },
84
+ { value: 'sample', description: 'sample' },
85
+ ];
86
+ const directSettingSpecs = [
87
+ {
88
+ value: 'emojifilter',
89
+ hint: 'filter mode',
90
+ description: 'filter mode',
91
+ options: emojifilterOptions,
92
+ },
93
+ {
94
+ value: 'context-limit',
95
+ hint: 'positive integer (e.g., 100000)',
96
+ },
97
+ {
98
+ value: 'compression-threshold',
99
+ hint: 'decimal between 0 and 1 (e.g., 0.7)',
100
+ },
101
+ {
102
+ value: 'base-url',
103
+ hint: 'URL string (e.g., https://api.example.com)',
104
+ },
105
+ {
106
+ value: 'api-version',
107
+ hint: 'API version (e.g., v1, 2024-05-01)',
108
+ },
109
+ {
110
+ value: 'streaming',
111
+ hint: 'enabled or disabled',
112
+ description: 'streaming mode',
113
+ options: streamingOptions,
114
+ },
115
+ {
116
+ value: 'socket-timeout',
117
+ hint: 'positive integer in milliseconds (e.g., 60000)',
118
+ },
119
+ {
120
+ value: 'socket-keepalive',
121
+ hint: 'true or false',
122
+ description: 'boolean value',
123
+ options: booleanOptions,
124
+ },
125
+ {
126
+ value: 'socket-nodelay',
127
+ hint: 'true or false',
128
+ description: 'boolean value',
129
+ options: booleanOptions,
130
+ },
131
+ {
132
+ value: 'shell-replacement',
133
+ hint: 'true or false',
134
+ description: 'boolean value',
135
+ options: booleanOptions,
136
+ },
137
+ {
138
+ value: 'tool-output-max-items',
139
+ hint: 'positive integer (e.g., 50)',
140
+ },
141
+ {
142
+ value: 'tool-output-max-tokens',
143
+ hint: 'positive integer (e.g., 50000)',
144
+ },
145
+ {
146
+ value: 'tool-output-item-size-limit',
147
+ hint: 'positive integer (e.g., 1048576)',
148
+ },
149
+ {
150
+ value: 'tool-output-truncate-mode',
151
+ hint: 'warn, truncate, or sample',
152
+ description: 'truncate mode',
153
+ options: truncateModeOptions,
154
+ },
155
+ {
156
+ value: 'max-prompt-tokens',
157
+ hint: 'positive integer (e.g., 200000)',
158
+ },
159
+ {
160
+ value: 'maxTurnsPerPrompt',
161
+ hint: 'positive integer or -1 (unlimited)',
162
+ },
163
+ ];
164
+ const createSettingLiteral = (spec) => ({
165
+ kind: 'literal',
166
+ value: spec.value,
167
+ description: `${toTitleCase(spec.value)} option`,
168
+ next: [
169
+ {
170
+ kind: 'value',
171
+ name: `${spec.value}-value`,
172
+ description: spec.description ?? `${toTitleCase(spec.value)} value`,
173
+ hint: spec.hint,
174
+ options: spec.options,
175
+ completer: spec.completer,
176
+ },
177
+ ],
178
+ });
179
+ const directSettingLiterals = directSettingSpecs.map(createSettingLiteral);
180
+ const directSettingKeys = new Set(directSettingSpecs.map((spec) => spec.value));
181
+ // Stryker disable StringLiteral -- literal descriptions are static UX copy verified via interaction tests.
182
+ const setSchema = [
183
+ {
184
+ kind: 'literal',
185
+ value: 'unset',
186
+ description: 'Unset option',
187
+ next: [
188
+ {
189
+ kind: 'value',
190
+ name: 'key',
191
+ description: 'setting key to remove',
192
+ completer: async (_ctx, partial) => {
193
+ const ephemeralSettings = getRuntimeApi().getEphemeralSettings();
194
+ const ephemeralKeys = Object.keys(ephemeralSettings).filter((key) => ephemeralSettings[key] !== undefined);
195
+ const specialKeys = [
196
+ 'modelparam',
197
+ 'custom-headers',
198
+ ...Array.from(directSettingKeys),
199
+ ];
200
+ const allKeys = Array.from(new Set([...ephemeralKeys, ...specialKeys]));
201
+ return allKeys
202
+ .filter((key) => key.startsWith(partial))
203
+ .map((key) => ({ value: key, description: `setting: ${key}` }));
204
+ },
205
+ next: [
206
+ {
207
+ kind: 'value',
208
+ name: 'subkey',
209
+ description: 'nested key for specific settings',
210
+ hint: async (_ctx, tokens) => {
211
+ const key = tokens.tokens[1];
212
+ if (key === 'modelparam') {
213
+ return 'model parameter name (e.g., temperature, max_tokens)';
214
+ }
215
+ if (key === 'custom-headers') {
216
+ return 'header name (e.g., Authorization)';
217
+ }
218
+ return 'subkey (optional)';
219
+ },
220
+ completer: async (ctx, partial, tokens) => {
221
+ const key = tokens.tokens[1];
222
+ if (key === 'modelparam') {
223
+ const params = getRuntimeApi().getActiveModelParams();
224
+ const paramNames = Object.keys(params);
225
+ return paramNames
226
+ .filter((name) => name.startsWith(partial))
227
+ .map((name) => ({
228
+ value: name,
229
+ description: `Parameter: ${name}`,
230
+ }));
231
+ }
232
+ if (key === 'custom-headers') {
233
+ const headers = getRuntimeApi().getEphemeralSettings()['custom-headers'];
234
+ if (headers) {
235
+ return Object.keys(headers)
236
+ .filter((name) => name.startsWith(partial))
237
+ .map((name) => ({
238
+ value: name,
239
+ description: `header: ${name}`,
240
+ }));
241
+ }
242
+ }
243
+ return [];
244
+ },
245
+ },
246
+ ],
247
+ },
248
+ ],
249
+ },
250
+ {
251
+ kind: 'literal',
252
+ value: 'modelparam',
253
+ description: 'Model parameter option',
254
+ next: [
255
+ {
256
+ kind: 'value',
257
+ name: 'param-name',
258
+ description: 'parameter name',
259
+ hint: 'parameter name',
260
+ completer: async (_ctx, partial) => {
261
+ const modelParams = getRuntimeApi().getActiveModelParams();
262
+ if (modelParams && Object.keys(modelParams).length > 0) {
263
+ const paramNames = Object.keys(modelParams);
264
+ const matches = paramNames.filter((name) => name.startsWith(partial));
265
+ if (matches.length > 0) {
266
+ return matches.map((name) => ({
267
+ value: name,
268
+ description: `Parameter: ${name}`,
269
+ }));
270
+ }
271
+ }
272
+ const commonParams = [
273
+ 'temperature',
274
+ 'max_tokens',
275
+ 'top_p',
276
+ 'top_k',
277
+ 'frequency_penalty',
278
+ 'presence_penalty',
279
+ ];
280
+ return commonParams
281
+ .filter((name) => name.startsWith(partial))
282
+ .map((name) => ({
283
+ value: name,
284
+ description: `Parameter: ${name}`,
285
+ }));
286
+ },
287
+ next: [
288
+ {
289
+ kind: 'value',
290
+ name: 'param-value',
291
+ description: 'model parameter value',
292
+ hint: 'value to set for the parameter (number, string, boolean, or JSON)',
293
+ },
294
+ ],
295
+ },
296
+ ],
297
+ },
298
+ {
299
+ kind: 'literal',
300
+ value: 'emojifilter',
301
+ description: 'Emoji filter option',
302
+ next: [
303
+ {
304
+ kind: 'value',
305
+ name: 'mode',
306
+ description: 'filter mode',
307
+ hint: 'filter mode',
308
+ options: emojifilterOptions,
309
+ },
310
+ ],
311
+ },
312
+ ...directSettingLiterals.filter((literal) => literal.value !== 'emojifilter'),
313
+ // Stryker disable all -- fallback value handler is exercised through runtime flows and
314
+ // would require extensive integration scaffolding beyond the schema migration scope.
315
+ {
316
+ kind: 'value',
317
+ name: 'setting',
318
+ description: 'any ephemeral setting key',
319
+ options: Object.entries(ephemeralSettingHelp)
320
+ .filter(([key]) => !directSettingKeys.has(key))
321
+ .map(([value, description]) => ({
322
+ value,
323
+ description,
324
+ })),
325
+ next: [
326
+ {
327
+ kind: 'value',
328
+ name: 'setting-value',
329
+ description: 'setting value',
330
+ hint: async (_ctx, tokens) => {
331
+ const setting = tokens.partialToken || tokens.tokens[0];
332
+ switch (setting) {
333
+ case 'context-limit':
334
+ return 'positive integer (e.g., 100000)';
335
+ case 'compression-threshold':
336
+ return 'decimal between 0 and 1 (e.g., 0.7)';
337
+ case 'emojifilter':
338
+ return 'allowed, auto, warn, or error';
339
+ case 'streaming':
340
+ return 'enabled or disabled';
341
+ case 'socket-timeout':
342
+ return 'positive integer in milliseconds (e.g., 60000)';
343
+ case 'socket-keepalive':
344
+ case 'socket-nodelay':
345
+ case 'shell-replacement':
346
+ return 'true or false';
347
+ case 'tool-output-truncate-mode':
348
+ return 'warn, truncate, or sample';
349
+ case 'maxTurnsPerPrompt':
350
+ return 'positive integer or -1 (unlimited)';
351
+ default:
352
+ return 'value to set';
353
+ }
354
+ },
355
+ completer: async (ctx, partial, tokens) => {
356
+ const setting = tokens.tokens[0] || tokens.partialToken;
357
+ if (setting === 'emojifilter') {
358
+ return emojifilterOptions
359
+ .filter((option) => option.value.startsWith(partial))
360
+ .map((option) => ({
361
+ value: option.value,
362
+ description: option.description,
363
+ }));
364
+ }
365
+ if (setting === 'streaming') {
366
+ return streamingOptions
367
+ .filter((option) => option.value.startsWith(partial))
368
+ .map((option) => ({
369
+ value: option.value,
370
+ description: option.description,
371
+ }));
372
+ }
373
+ if (setting === 'socket-keepalive' ||
374
+ setting === 'socket-nodelay' ||
375
+ setting === 'shell-replacement') {
376
+ return booleanOptions
377
+ .filter((option) => option.value.startsWith(partial))
378
+ .map((option) => ({
379
+ value: option.value,
380
+ description: option.description,
381
+ }));
382
+ }
383
+ if (setting === 'tool-output-truncate-mode') {
384
+ return truncateModeOptions
385
+ .filter((option) => option.value.startsWith(partial))
386
+ .map((option) => ({
387
+ value: option.value,
388
+ description: option.description,
389
+ }));
390
+ }
391
+ if (setting === 'custom-headers') {
392
+ const headers = getRuntimeApi().getEphemeralSettings()['custom-headers'];
393
+ if (headers) {
394
+ return Object.keys(headers)
395
+ .filter((name) => name.startsWith(partial))
396
+ .map((name) => ({
397
+ value: name,
398
+ description: `header: ${name}`,
399
+ }));
400
+ }
401
+ }
402
+ return [];
403
+ },
404
+ },
405
+ ],
406
+ },
407
+ // Stryker restore all
408
+ ];
409
+ // Stryker restore StringLiteral
410
+ export const setCommand = {
411
+ name: 'set',
412
+ description: 'set model parameters or ephemeral settings',
13
413
  kind: CommandKind.BUILT_IN,
414
+ schema: setSchema,
14
415
  action: async (context, args) => {
15
- const parts = args?.trim().split(/\s+/);
16
- if (!parts || parts.length === 0 || !parts[0]) {
416
+ const runtime = getRuntimeApi();
417
+ // This handles direct ephemeral settings: /set <ephemeral-key> <value>
418
+ const trimmedArgs = args?.trim();
419
+ if (!trimmedArgs) {
17
420
  return {
18
421
  type: 'message',
19
422
  messageType: 'error',
20
- content: 'Usage: /set unset <key> [subkey]\nExamples:\n /set unset context-limit\n /set unset custom-headers Authorization\n /set unset modelparam max_tokens',
423
+ content: 'Usage: /set <ephemeral-key> <value>\nExample: /set context-limit 100000\n\nFor model parameters use: /set modelparam <key> <value>',
21
424
  };
22
425
  }
426
+ const parts = trimmedArgs.split(/\s+/);
23
427
  const key = parts[0];
24
- const subkey = parts[1];
25
- // Get the config
26
- const config = context.services.config;
27
- if (!config) {
428
+ if (key === 'modelparam') {
429
+ if (parts.length < 3) {
430
+ return {
431
+ type: 'message',
432
+ messageType: 'error',
433
+ content: 'Usage: /set modelparam <key> <value>\nExample: /set modelparam temperature 0.7',
434
+ };
435
+ }
436
+ const paramName = parts[1];
437
+ const rawValue = parts.slice(2).join(' ');
438
+ const parsedParamValue = parseValue(rawValue);
439
+ try {
440
+ runtime.setActiveModelParam(paramName, parsedParamValue);
441
+ }
442
+ catch (error) {
443
+ return {
444
+ type: 'message',
445
+ messageType: 'error',
446
+ content: `Failed to set model parameter: ${error instanceof Error ? error.message : String(error)}`,
447
+ };
448
+ }
449
+ const formattedValue = typeof parsedParamValue === 'string'
450
+ ? parsedParamValue
451
+ : typeof parsedParamValue === 'number' ||
452
+ typeof parsedParamValue === 'boolean' ||
453
+ parsedParamValue === null
454
+ ? String(parsedParamValue)
455
+ : JSON.stringify(parsedParamValue);
28
456
  return {
29
457
  type: 'message',
30
- messageType: 'error',
31
- content: 'No configuration available',
458
+ messageType: 'info',
459
+ content: `Model parameter '${paramName}' set to ${formattedValue}`,
32
460
  };
33
461
  }
34
- // Handle unset for model parameters
35
- if (key === 'modelparam' && subkey) {
36
- const providerManager = config.getProviderManager();
37
- const activeProvider = providerManager?.getActiveProvider();
38
- if (!activeProvider) {
462
+ if (key === 'unset') {
463
+ if (parts.length < 2) {
39
464
  return {
40
465
  type: 'message',
41
466
  messageType: 'error',
42
- content: 'No active provider',
467
+ content: 'Usage: /set unset <ephemeral-key|modelparam> [subkey]\nExample: /set unset base-url',
43
468
  };
44
469
  }
45
- if ('getModelParams' in activeProvider &&
46
- typeof activeProvider.getModelParams === 'function') {
47
- const modelParams = activeProvider.getModelParams();
48
- if (!modelParams || !(subkey in modelParams)) {
470
+ const targetKey = parts[1];
471
+ const subKey = parts[2];
472
+ if (targetKey === 'modelparam') {
473
+ if (!subKey) {
49
474
  return {
50
475
  type: 'message',
51
476
  messageType: 'error',
52
- content: `Model parameter '${subkey}' is not set`,
477
+ content: 'Usage: /set unset modelparam <key>\nExample: /set unset modelparam temperature',
53
478
  };
54
479
  }
55
- if ('setModelParams' in activeProvider &&
56
- typeof activeProvider.setModelParams === 'function') {
57
- activeProvider.setModelParams({ [subkey]: undefined });
480
+ try {
481
+ runtime.clearActiveModelParam(subKey);
482
+ }
483
+ catch (error) {
58
484
  return {
59
485
  type: 'message',
60
- messageType: 'info',
61
- content: `Model parameter '${subkey}' has been removed`,
486
+ messageType: 'error',
487
+ content: `Failed to clear model parameter: ${error instanceof Error ? error.message : String(error)}`,
62
488
  };
63
489
  }
64
- }
65
- return {
66
- type: 'message',
67
- messageType: 'error',
68
- content: 'Provider does not support model parameters',
69
- };
70
- }
71
- // Handle nested unset for custom-headers
72
- if (key === 'custom-headers' && subkey) {
73
- const currentHeaders = config.getEphemeralSetting('custom-headers');
74
- if (!currentHeaders || !(subkey in currentHeaders)) {
75
490
  return {
76
491
  type: 'message',
77
- messageType: 'error',
78
- content: `Custom header '${subkey}' is not set`,
492
+ messageType: 'info',
493
+ content: `Model parameter '${subKey}' cleared`,
79
494
  };
80
495
  }
81
- // Remove the specific header
82
- const updatedHeaders = { ...currentHeaders };
83
- delete updatedHeaders[subkey];
84
- // If no headers left, remove the entire setting
85
- if (Object.keys(updatedHeaders).length === 0) {
86
- // Note: SettingsService doesn't currently support ephemeral settings,
87
- // so we continue to use the config directly for these session-only settings
88
- config.setEphemeralSetting('custom-headers', undefined);
496
+ const validEphemeralKeys = Object.keys(ephemeralSettingHelp);
497
+ if (!validEphemeralKeys.includes(targetKey)) {
89
498
  return {
90
499
  type: 'message',
91
- messageType: 'info',
92
- content: `Removed custom header '${subkey}' and cleared custom-headers setting`,
500
+ messageType: 'error',
501
+ content: `Invalid setting key: ${targetKey}. Valid keys are: ${validEphemeralKeys.join(', ')}`,
93
502
  };
94
503
  }
95
- else {
96
- config.setEphemeralSetting('custom-headers', updatedHeaders);
504
+ if (targetKey === 'custom-headers' && subKey) {
505
+ const currentHeaders = runtime.getEphemeralSettings()['custom-headers'];
506
+ if (currentHeaders && subKey in currentHeaders) {
507
+ const nextHeaders = { ...currentHeaders };
508
+ delete nextHeaders[subKey];
509
+ runtime.setEphemeralSetting(targetKey, Object.keys(nextHeaders).length > 0 ? nextHeaders : undefined);
510
+ return {
511
+ type: 'message',
512
+ messageType: 'info',
513
+ content: `Custom header '${subKey}' cleared`,
514
+ };
515
+ }
97
516
  return {
98
517
  type: 'message',
99
518
  messageType: 'info',
100
- content: `Removed custom header '${subkey}'`,
519
+ content: `No custom header named '${subKey}' found`,
101
520
  };
102
521
  }
103
- }
104
- // Handle regular unset (non-nested)
105
- if (subkey) {
106
- return {
107
- type: 'message',
108
- messageType: 'error',
109
- content: `Setting '${key}' does not support nested unset. Use: /set unset ${key}`,
110
- };
111
- }
112
- // No special handling for emojifilter - treat it like any other ephemeral setting
113
- // Check if the setting exists
114
- const currentValue = config.getEphemeralSetting(key);
115
- if (currentValue === undefined) {
522
+ runtime.setEphemeralSetting(targetKey, undefined);
116
523
  return {
117
524
  type: 'message',
118
- messageType: 'error',
119
- content: `Ephemeral setting '${key}' is not set`,
525
+ messageType: 'info',
526
+ content: `Ephemeral setting '${targetKey}' cleared`,
120
527
  };
121
528
  }
122
- // Clear the ephemeral setting
123
- // Note: SettingsService doesn't currently support ephemeral settings,
124
- // so we continue to use the config directly for these session-only settings
125
- config.setEphemeralSetting(key, undefined);
126
- // Compression settings are now handled via ephemeral settings only
127
- // No special handling needed - the unsetEphemeralSetting above handles it
128
- return {
129
- type: 'message',
130
- messageType: 'info',
131
- content: `Ephemeral setting '${key}' has been removed`,
132
- };
133
- },
134
- completion: async (_context, partialArg) => {
135
- // Get all current ephemeral settings
136
- const config = _context.services.config;
137
- if (!config)
138
- return [];
139
- const ephemeralSettings = config.getEphemeralSettings();
140
- const ephemeralKeys = Object.keys(ephemeralSettings).filter((key) => ephemeralSettings[key] !== undefined);
141
- // Add 'modelparam' and 'emojifilter' as completion options
142
- const specialKeys = ['modelparam', 'emojifilter'];
143
- const allKeys = [...ephemeralKeys, ...specialKeys];
144
- if (partialArg) {
145
- const parts = partialArg.split(/\s+/);
146
- // If user typed "emojifilter " (with space), offer mode options
147
- if (parts.length === 2 && parts[0] === 'emojifilter') {
148
- const modes = ['allowed', 'auto', 'warn', 'error'];
149
- if (parts[1]) {
150
- return modes.filter((mode) => mode.startsWith(parts[1]));
151
- }
152
- return modes;
153
- }
154
- // If user typed "modelparam " (with space), offer model param names
155
- if (parts.length === 2 && parts[0] === 'modelparam') {
156
- const providerManager = config.getProviderManager();
157
- const activeProvider = providerManager?.getActiveProvider();
158
- if (activeProvider &&
159
- 'getModelParams' in activeProvider &&
160
- typeof activeProvider.getModelParams === 'function') {
161
- const modelParams = activeProvider.getModelParams();
162
- if (modelParams) {
163
- const paramNames = Object.keys(modelParams);
164
- if (parts[1]) {
165
- return paramNames.filter((name) => name.startsWith(parts[1]));
166
- }
167
- return paramNames;
168
- }
169
- }
170
- return [];
171
- }
172
- // If user typed "custom-headers " (with space), offer header names
173
- if (parts.length === 2 && parts[0] === 'custom-headers') {
174
- const headers = ephemeralSettings['custom-headers'];
175
- if (headers) {
176
- const headerNames = Object.keys(headers);
177
- if (parts[1]) {
178
- return headerNames.filter((name) => name.startsWith(parts[1]));
179
- }
180
- return headerNames;
181
- }
182
- return [];
529
+ // If only key is provided, show help for that key
530
+ if (parts.length === 1) {
531
+ if (ephemeralSettingHelp[key]) {
532
+ return {
533
+ type: 'message',
534
+ messageType: 'info',
535
+ content: `${key}: ${ephemeralSettingHelp[key]}`,
536
+ };
183
537
  }
184
- // Otherwise, complete the setting key
185
- return allKeys.filter((key) => key.startsWith(parts[0]));
186
- }
187
- return allKeys;
188
- },
189
- };
190
- // Subcommand for /set modelparam
191
- const modelParamCommand = {
192
- name: 'modelparam',
193
- description: 'set model parameters like temperature, max_tokens, etc',
194
- kind: CommandKind.BUILT_IN,
195
- action: async (context, args) => {
196
- const parts = args?.trim().split(/\s+/);
197
- if (!parts || parts.length < 2) {
198
538
  return {
199
539
  type: 'message',
200
540
  messageType: 'error',
201
- content: 'Usage: /set modelparam <key> <value>\nExample: /set modelparam temperature 0.7',
541
+ content: `Usage: /set ${key} <value>\n\nValid ephemeral keys:\n${Object.entries(ephemeralSettingHelp)
542
+ .map(([k, v]) => ` ${k}: ${v}`)
543
+ .join('\n')}`,
202
544
  };
203
545
  }
204
- const key = parts[0];
205
- const value = parts.slice(1).join(' ');
206
- // Get provider manager from config
207
- const config = context.services.config;
208
- if (!config) {
546
+ const value = parts.slice(1).join(' '); // Join remaining parts as value
547
+ // List of valid ephemeral settings from the specification
548
+ const validEphemeralKeys = Object.keys(ephemeralSettingHelp);
549
+ // Check if it's a valid ephemeral key
550
+ if (!validEphemeralKeys.includes(key)) {
209
551
  return {
210
552
  type: 'message',
211
553
  messageType: 'error',
212
- content: 'No configuration available',
554
+ content: `Invalid setting key: ${key}. Valid keys are: ${validEphemeralKeys.join(', ')}`,
213
555
  };
214
556
  }
215
- const providerManager = config.getProviderManager();
216
- if (!providerManager) {
217
- return {
218
- type: 'message',
219
- messageType: 'error',
220
- content: 'Provider manager not initialized',
221
- };
557
+ // Parse the value
558
+ let parsedValue = parseValue(value);
559
+ // Validate specific settings
560
+ if (key === 'compression-threshold') {
561
+ const numValue = parsedValue;
562
+ if (typeof numValue !== 'number' || numValue <= 0 || numValue > 1) {
563
+ return {
564
+ type: 'message',
565
+ messageType: 'error',
566
+ content: `compression-threshold must be a decimal between 0 and 1 (e.g., 0.7 for 70%)`,
567
+ };
568
+ }
222
569
  }
223
- const activeProvider = providerManager.getActiveProvider();
224
- if (!activeProvider) {
225
- return {
226
- type: 'message',
227
- messageType: 'error',
228
- content: 'No active provider',
229
- };
570
+ if (key === 'context-limit') {
571
+ const numValue = parsedValue;
572
+ if (typeof numValue !== 'number' ||
573
+ numValue <= 0 ||
574
+ !Number.isInteger(numValue)) {
575
+ return {
576
+ type: 'message',
577
+ messageType: 'error',
578
+ content: `context-limit must be a positive integer (e.g., 100000)`,
579
+ };
580
+ }
230
581
  }
231
- // Check if provider supports setModelParams
232
- if (!('setModelParams' in activeProvider) ||
233
- typeof activeProvider.setModelParams !== 'function') {
234
- return {
235
- type: 'message',
236
- messageType: 'error',
237
- content: `Provider '${activeProvider.name}' does not support model parameters`,
238
- };
582
+ // Validate socket configuration settings
583
+ if (key === 'socket-timeout') {
584
+ const numValue = parsedValue;
585
+ if (typeof numValue !== 'number' ||
586
+ numValue <= 0 ||
587
+ !Number.isInteger(numValue)) {
588
+ return {
589
+ type: 'message',
590
+ messageType: 'error',
591
+ content: `socket-timeout must be a positive integer in milliseconds (e.g., 60000)`,
592
+ };
593
+ }
239
594
  }
240
- const parsedValue = parseModelParamValue(value);
241
- // Set the model parameter
242
- try {
243
- await activeProvider.setModelParams({ [key]: parsedValue });
244
- return {
245
- type: 'message',
246
- messageType: 'info',
247
- content: `Model parameter '${key}' set to ${JSON.stringify(parsedValue)} (use /profile save to persist)`,
248
- };
595
+ if (key === 'socket-keepalive' || key === 'socket-nodelay') {
596
+ if (typeof parsedValue !== 'boolean') {
597
+ return {
598
+ type: 'message',
599
+ messageType: 'error',
600
+ content: `${key} must be either 'true' or 'false'`,
601
+ };
602
+ }
249
603
  }
250
- catch (error) {
251
- return {
252
- type: 'message',
253
- messageType: 'error',
254
- content: `Failed to set model parameter: ${error instanceof Error ? error.message : String(error)}`,
255
- };
604
+ // Validate tool output settings
605
+ if (key === 'tool-output-max-items' ||
606
+ key === 'tool-output-max-tokens' ||
607
+ key === 'tool-output-item-size-limit' ||
608
+ key === 'max-prompt-tokens') {
609
+ const numValue = parsedValue;
610
+ if (typeof numValue !== 'number' ||
611
+ numValue <= 0 ||
612
+ !Number.isInteger(numValue)) {
613
+ return {
614
+ type: 'message',
615
+ messageType: 'error',
616
+ content: `${key} must be a positive integer`,
617
+ };
618
+ }
256
619
  }
257
- },
258
- completion: async (_context, partialArg) => {
259
- // Common model parameters across providers
260
- const commonParams = [
261
- 'temperature',
262
- 'max_tokens',
263
- 'maxOutputTokens',
264
- 'top_p',
265
- 'top_k',
266
- 'presence_penalty',
267
- 'frequency_penalty',
268
- 'stop_sequences',
269
- 'seed',
270
- 'enable_thinking',
271
- ];
272
- // If user has typed part of a parameter name, filter suggestions
273
- if (partialArg) {
274
- const parts = partialArg.split(/\s+/);
275
- if (parts.length === 1) {
276
- // Still typing the parameter name
277
- return commonParams.filter((param) => param.startsWith(parts[0]));
620
+ // Validate maxTurnsPerPrompt
621
+ if (key === 'maxTurnsPerPrompt') {
622
+ const numValue = parsedValue;
623
+ if (typeof numValue !== 'number' ||
624
+ !Number.isInteger(numValue) ||
625
+ (numValue !== -1 && numValue <= 0)) {
626
+ return {
627
+ type: 'message',
628
+ messageType: 'error',
629
+ content: `${key} must be a positive integer or -1 for unlimited`,
630
+ };
278
631
  }
279
632
  }
280
- return [];
281
- },
282
- };
283
- export const setCommand = {
284
- name: 'set',
285
- description: 'set model parameters or ephemeral settings',
286
- kind: CommandKind.BUILT_IN,
287
- subCommands: [modelParamCommand, unsetCommand],
288
- action: async (context, args) => {
289
- // This handles direct ephemeral settings: /set <ephemeral-key> <value>
290
- const trimmedArgs = args?.trim();
291
- if (!trimmedArgs) {
292
- return {
293
- type: 'message',
294
- messageType: 'error',
295
- content: 'Usage: /set <ephemeral-key> <value>\nExample: /set context-limit 100000\n\nFor model parameters use: /set modelparam <key> <value>',
296
- };
633
+ if (key === 'tool-output-truncate-mode') {
634
+ const validModes = ['warn', 'truncate', 'sample'];
635
+ if (!validModes.includes(parsedValue)) {
636
+ return {
637
+ type: 'message',
638
+ messageType: 'error',
639
+ content: `${key} must be one of: ${validModes.join(', ')}`,
640
+ };
641
+ }
297
642
  }
298
- const parts = trimmedArgs.split(/\s+/);
299
- const key = parts[0];
300
- // If only key is provided, show help for that key
301
- if (parts.length === 1) {
302
- if (ephemeralSettingHelp[key]) {
643
+ // Validate emojifilter mode
644
+ if (key === 'emojifilter') {
645
+ const validModes = [
646
+ 'allowed',
647
+ 'auto',
648
+ 'warn',
649
+ 'error',
650
+ ];
651
+ const normalizedValue = parsedValue.toLowerCase();
652
+ if (!validModes.includes(normalizedValue)) {
303
653
  return {
304
654
  type: 'message',
305
- messageType: 'info',
306
- content: `${key}: ${ephemeralSettingHelp[key]}`,
655
+ messageType: 'error',
656
+ content: `Invalid emoji filter mode '${parsedValue}'. Valid modes are: ${validModes.join(', ')}`,
307
657
  };
308
658
  }
309
- return {
310
- type: 'message',
311
- messageType: 'error',
312
- content: `Usage: /set ${key} <value>\n\nValid ephemeral keys:\n${Object.entries(ephemeralSettingHelp)
313
- .map(([k, v]) => ` ${k}: ${v}`)
314
- .join('\n')}`,
315
- };
659
+ // Override the parsed value with normalized lowercase version
660
+ parsedValue = normalizedValue;
316
661
  }
317
- const value = parts.slice(1).join(' ');
318
- const parseResult = parseEphemeralSettingValue(key, value);
319
- if (!parseResult.success) {
320
- return {
321
- type: 'message',
322
- messageType: 'error',
323
- content: parseResult.message,
324
- };
662
+ // Validate shell-replacement setting
663
+ if (key === 'shell-replacement') {
664
+ if (typeof parsedValue !== 'boolean') {
665
+ return {
666
+ type: 'message',
667
+ messageType: 'error',
668
+ content: `shell-replacement must be either 'true' or 'false'`,
669
+ };
670
+ }
671
+ }
672
+ // Validate streaming mode
673
+ if (key === 'streaming') {
674
+ const validModes = ['enabled', 'disabled'];
675
+ const normalizedValue = parsedValue.toLowerCase();
676
+ if (!validModes.includes(normalizedValue)) {
677
+ return {
678
+ type: 'message',
679
+ messageType: 'error',
680
+ content: `Invalid streaming mode '${parsedValue}'. Valid modes are: ${validModes.join(', ')}`,
681
+ };
682
+ }
683
+ // Override the parsed value with normalized lowercase version
684
+ parsedValue = normalizedValue;
325
685
  }
326
- const parsedValue = parseResult.value;
327
686
  // Get the config to apply settings
328
687
  const config = context.services.config;
329
688
  if (!config) {
@@ -333,8 +692,6 @@ export const setCommand = {
333
692
  content: 'No configuration available',
334
693
  };
335
694
  }
336
- const settingsService = config.getSettingsService();
337
- const useSettingsService = settingsService !== null;
338
695
  // Store compression settings as ephemeral settings
339
696
  // They will be read by geminiChat.ts when compression is needed
340
697
  if (key === 'context-limit' || key === 'compression-threshold') {
@@ -347,83 +704,42 @@ export const setCommand = {
347
704
  // They will be saved only when user explicitly saves a profile
348
705
  // Note: SettingsService doesn't currently support ephemeral settings,
349
706
  // so we continue to use the config directly for these session-only settings
350
- if (useSettingsService) {
351
- // When SettingsService is available, we still use config for ephemeral settings
352
- // as they are session-only and not persisted to the settings file
353
- config.setEphemeralSetting(key, parsedValue);
354
- }
355
- else {
356
- // Fallback to direct config usage
357
- config.setEphemeralSetting(key, parsedValue);
358
- }
707
+ runtime.setEphemeralSetting(key, parsedValue);
359
708
  return {
360
709
  type: 'message',
361
710
  messageType: 'info',
362
711
  content: `Ephemeral setting '${key}' set to ${JSON.stringify(parsedValue)} (session only, use /profile save to persist)`,
363
712
  };
364
713
  },
365
- completion: async (_context, partialArg) => {
366
- // Provide completions for ephemeral settings
367
- const ephemeralKeys = Object.keys(ephemeralSettingHelp);
368
- if (partialArg) {
369
- const parts = partialArg.split(/\s+/);
370
- if (parts.length === 1) {
371
- // Still typing the key
372
- return ephemeralKeys.filter((key) => key.startsWith(parts[0]));
373
- }
374
- else if (parts.length === 2) {
375
- // User has typed the key and a space, provide value completions for specific keys
376
- const key = parts[0];
377
- // Provide completions for tool-output-truncate-mode
378
- if (key === 'tool-output-truncate-mode') {
379
- const modes = ['warn', 'truncate', 'sample'];
380
- if (parts[1]) {
381
- return modes.filter((mode) => mode.startsWith(parts[1]));
382
- }
383
- return modes;
384
- }
385
- // Provide completions for emojifilter
386
- if (key === 'emojifilter') {
387
- const modes = ['allowed', 'auto', 'warn', 'error'];
388
- if (parts[1]) {
389
- return modes.filter((mode) => mode.startsWith(parts[1].toLowerCase()));
390
- }
391
- return modes;
392
- }
393
- // Provide completions for shell-replacement
394
- if (key === 'shell-replacement') {
395
- const values = ['true', 'false'];
396
- if (parts[1]) {
397
- return values.filter((value) => value.startsWith(parts[1].toLowerCase()));
398
- }
399
- return values;
400
- }
401
- // Provide completions for streaming
402
- if (key === 'streaming') {
403
- const modes = ['enabled', 'disabled'];
404
- if (parts[1]) {
405
- return modes.filter((mode) => mode.startsWith(parts[1].toLowerCase()));
406
- }
407
- return modes;
408
- }
409
- // Provide completions for socket boolean settings
410
- if (key === 'socket-keepalive' || key === 'socket-nodelay') {
411
- const values = ['true', 'false'];
412
- if (parts[1]) {
413
- return values.filter((value) => value.startsWith(parts[1].toLowerCase()));
414
- }
415
- return values;
416
- }
417
- if (key === 'authOnly') {
418
- const values = ['true', 'false'];
419
- if (parts[1]) {
420
- return values.filter((value) => value.startsWith(parts[1].toLowerCase()));
421
- }
422
- return values;
423
- }
424
- }
425
- }
426
- return ephemeralKeys;
427
- },
428
714
  };
715
+ // Stryker disable all -- Parsing is covered by higher-level integration tests and mutating this
716
+ // helper introduces hundreds of equivalent mutants unrelated to autocomplete behaviour.
717
+ /**
718
+ * Parse a string value into the appropriate type.
719
+ * Handles numbers, booleans, and JSON objects/arrays.
720
+ */
721
+ function parseValue(value) {
722
+ // Try to parse as number
723
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
724
+ const num = Number(value);
725
+ if (!isNaN(num)) {
726
+ return num;
727
+ }
728
+ }
729
+ // Try to parse as boolean
730
+ if (value.toLowerCase() === 'true') {
731
+ return true;
732
+ }
733
+ if (value.toLowerCase() === 'false') {
734
+ return false;
735
+ }
736
+ // Try to parse as JSON
737
+ try {
738
+ return JSON.parse(value);
739
+ }
740
+ catch {
741
+ // If all parsing fails, return as string
742
+ return value;
743
+ }
744
+ }
429
745
  //# sourceMappingURL=setCommand.js.map