@vybestack/llxprt-code 0.10.0 → 0.11.0-nightly.260728.ffaa8d5d7

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 (126) hide show
  1. package/package.json +13 -13
  2. package/src/commands/mcp/add.ts +8 -4
  3. package/src/config/cliArgParser.ts +2 -0
  4. package/src/config/config.ts +2 -0
  5. package/src/config/configBuilder.ts +3 -0
  6. package/src/config/configError.ts +23 -0
  7. package/src/config/extensions/extensionEnablement.ts +62 -1
  8. package/src/config/interactiveContext.ts +2 -1
  9. package/src/config/intermediateConfig.ts +4 -0
  10. package/src/config/keyBindings.ts +29 -3
  11. package/src/config/policy.ts +7 -1
  12. package/src/config/postConfigRuntime.ts +20 -14
  13. package/src/config/settings-schema/schema-ui.ts +9 -0
  14. package/src/config/settingsLoader.ts +21 -17
  15. package/src/config/settingsSchema.ts +2 -2
  16. package/src/config/trustedFolders.ts +275 -58
  17. package/src/config/yargsOptions.ts +7 -0
  18. package/src/generated/git-commit.json +1 -1
  19. package/src/nonInteractiveCli.ts +1 -0
  20. package/src/nonInteractiveCliSupport.ts +171 -51
  21. package/src/runtime/interactiveToolScheduler.ts +6 -6
  22. package/src/services/BuiltinCommandLoader.ts +33 -25
  23. package/src/services/FileCommandLoader.ts +34 -7
  24. package/src/test-utils/async.ts +20 -0
  25. package/src/test-utils/render.tsx +10 -1
  26. package/src/ui/App.tsx +17 -10
  27. package/src/ui/AppContainerRuntime.tsx +7 -9
  28. package/src/ui/cliUiRuntime.ts +28 -0
  29. package/src/ui/commands/authCommand.ts +240 -3
  30. package/src/ui/commands/compressCommand.ts +9 -1
  31. package/src/ui/commands/directoryCommand.tsx +1 -1
  32. package/src/ui/commands/logoutCommand.ts +2 -2
  33. package/src/ui/commands/permissionsCommand.ts +77 -20
  34. package/src/ui/commands/policiesCommand.ts +6 -1
  35. package/src/ui/commands/quotaCommand.ts +501 -0
  36. package/src/ui/commands/statsCommand.ts +2 -2
  37. package/src/ui/commands/statsQuota.ts +1 -1
  38. package/src/ui/commands/subagentCommand.ts +59 -27
  39. package/src/ui/components/AuthDialog.tsx +3 -3
  40. package/src/ui/components/Composer.tsx +1 -0
  41. package/src/ui/components/DialogManager.tsx +9 -2
  42. package/src/ui/components/FolderTrustDialog.tsx +71 -60
  43. package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
  44. package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
  45. package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
  46. package/src/ui/components/inputPromptHooks.ts +2 -0
  47. package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
  48. package/src/ui/components/inputPromptTypes.ts +6 -0
  49. package/src/ui/components/messages/AiMessage.tsx +24 -12
  50. package/src/ui/components/messages/ToolMessage.tsx +26 -40
  51. package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
  52. package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
  53. package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
  54. package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
  55. package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
  56. package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
  57. package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
  58. package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
  59. package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
  60. package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
  61. package/src/ui/contexts/KeypressContext.tsx +13 -2
  62. package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
  63. package/src/ui/contexts/TodoContext.tsx +0 -13
  64. package/src/ui/contexts/TodoProvider.tsx +1 -34
  65. package/src/ui/contexts/UIActionsContext.tsx +2 -1
  66. package/src/ui/contexts/UIStateContext.tsx +3 -2
  67. package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
  68. package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
  69. package/src/ui/hooks/agentStream/types.ts +1 -0
  70. package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
  71. package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
  72. package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
  73. package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
  74. package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
  75. package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
  76. package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
  77. package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
  78. package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
  79. package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
  80. package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
  81. package/src/ui/hooks/useFolderTrust.ts +118 -81
  82. package/src/ui/hooks/useGitBranchName.ts +79 -33
  83. package/src/ui/hooks/useIdeTrustListener.ts +5 -19
  84. package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
  85. package/src/ui/hooks/useReactToolScheduler.ts +13 -17
  86. package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
  87. package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
  88. package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
  89. package/src/ui/layouts/InlineContent.tsx +198 -0
  90. package/src/ui/trustDialogHelpers.ts +190 -0
  91. package/src/utils/cleanup-state.ts +115 -0
  92. package/src/utils/cleanup.ts +32 -34
  93. package/src/utils/sandbox-containers.ts +183 -53
  94. package/src/utils/sandbox-exec.ts +9 -3
  95. package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
  96. package/src/zed-integration/acp-types.ts +35 -0
  97. package/src/zed-integration/fileSystemService.ts +1 -1
  98. package/src/zed-integration/runZedIntegration.ts +142 -0
  99. package/src/zed-integration/zed-agent-event-handler.ts +179 -0
  100. package/src/zed-integration/zed-agent-setup.ts +43 -0
  101. package/src/zed-integration/zed-command-registry.ts +167 -0
  102. package/src/zed-integration/zed-config-options.ts +280 -0
  103. package/src/zed-integration/zed-content-utils.ts +2 -2
  104. package/src/zed-integration/zed-helpers.ts +132 -0
  105. package/src/zed-integration/zed-initialize.ts +64 -0
  106. package/src/zed-integration/zed-plan-update.ts +20 -0
  107. package/src/zed-integration/zed-prompt-command.ts +77 -0
  108. package/src/zed-integration/zed-session-config.ts +119 -0
  109. package/src/zed-integration/zed-session-errors.ts +337 -0
  110. package/src/zed-integration/zed-session-events.ts +216 -0
  111. package/src/zed-integration/zed-session-info.ts +255 -0
  112. package/src/zed-integration/zed-session-lifecycle.ts +186 -0
  113. package/src/zed-integration/zed-session-listing.ts +153 -0
  114. package/src/zed-integration/zed-session-loader.ts +193 -0
  115. package/src/zed-integration/zed-session-pagination.ts +240 -0
  116. package/src/zed-integration/zed-session-replay.ts +564 -0
  117. package/src/zed-integration/zed-stream-batcher.ts +236 -0
  118. package/src/zed-integration/zed-terminal-manager.ts +416 -0
  119. package/src/zed-integration/zed-terminal-setup.ts +72 -0
  120. package/src/zed-integration/zed-test-helpers.ts +349 -4
  121. package/src/zed-integration/zed-tool-handler.ts +47 -5
  122. package/src/zed-integration/zedIntegration.ts +538 -587
  123. package/src/services/todo-continuation/todoContinuationService.ts +0 -610
  124. package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
  125. package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
  126. package/src/ui/hooks/useTodoContinuation.ts +0 -299
@@ -0,0 +1,280 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Vybestack LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as acp from '@agentclientprotocol/sdk';
8
+ import type { Agent } from '@vybestack/llxprt-code-agents';
9
+ import {
10
+ coreEvents,
11
+ CoreEvent,
12
+ DebugLogger,
13
+ type Config,
14
+ type RuntimeModel,
15
+ } from '@vybestack/llxprt-code-core';
16
+ import { parseEphemeralSettingValue } from '@vybestack/llxprt-code-providers/runtime.js';
17
+ import type { ClientCapabilitiesWithSession } from './acp-types.js';
18
+
19
+ const REASONING_VALUES = ['minimal', 'low', 'medium', 'high', 'xhigh'];
20
+ const EMOJI_VALUES = ['allowed', 'auto', 'warn', 'error'];
21
+ const logger = new DebugLogger('llxprt:zed-integration:config-options');
22
+
23
+ function selectOption(value: string): acp.SessionConfigSelectOption {
24
+ return { value, name: value };
25
+ }
26
+
27
+ function settingOption(
28
+ config: Config,
29
+ id: string,
30
+ name: string,
31
+ category: acp.SessionConfigOptionCategory,
32
+ values: readonly string[],
33
+ fallback: string,
34
+ ): acp.SessionConfigOption {
35
+ const current = config.getEphemeralSetting(id);
36
+ return {
37
+ type: 'select',
38
+ id,
39
+ name,
40
+ category,
41
+ currentValue: typeof current === 'string' ? current : fallback,
42
+ options: values.map(selectOption),
43
+ };
44
+ }
45
+
46
+ function modelOption(
47
+ currentModel: string,
48
+ models: readonly RuntimeModel[],
49
+ ): acp.SessionConfigOption {
50
+ const available = models.some(({ id }) => id === currentModel)
51
+ ? models
52
+ : [{ id: currentModel, name: currentModel, provider: '' }, ...models];
53
+ return {
54
+ type: 'select',
55
+ id: 'model',
56
+ name: 'Model',
57
+ category: 'model',
58
+ currentValue: currentModel,
59
+ options: available.map((model) => ({ value: model.id, name: model.name })),
60
+ };
61
+ }
62
+
63
+ async function availableModels(
64
+ agent: Pick<Agent, 'getProviderStatus'>,
65
+ config: Config,
66
+ ): Promise<RuntimeModel[] | undefined> {
67
+ try {
68
+ const provider = agent.getProviderStatus().provider;
69
+ return await config.getProviderManager()?.getAvailableModels(provider);
70
+ } catch (error) {
71
+ logger.debug(() => `Failed to load available models: ${String(error)}`);
72
+ return undefined;
73
+ }
74
+ }
75
+
76
+ export async function buildZedConfigOptions(
77
+ agent: Pick<Agent, 'getModel' | 'getProviderStatus'>,
78
+ config: Config,
79
+ ): Promise<acp.SessionConfigOption[]> {
80
+ const currentModel = agent.getModel();
81
+ const models = await availableModels(agent, config);
82
+ return [
83
+ ...(models === undefined || currentModel.length === 0
84
+ ? []
85
+ : [modelOption(currentModel, models)]),
86
+ settingOption(
87
+ config,
88
+ 'reasoning.effort',
89
+ 'Thinking level',
90
+ 'thought_level',
91
+ REASONING_VALUES,
92
+ 'medium',
93
+ ),
94
+ settingOption(
95
+ config,
96
+ 'emojifilter',
97
+ 'Emoji filter',
98
+ '_display',
99
+ EMOJI_VALUES,
100
+ 'auto',
101
+ ),
102
+ ];
103
+ }
104
+
105
+ export async function applyZedConfigOption(
106
+ agent: Agent,
107
+ config: Config,
108
+ configId: string,
109
+ value: string,
110
+ ): Promise<acp.SessionConfigOption[]> {
111
+ if (configId === 'model') {
112
+ const models = await availableModels(agent, config);
113
+ if (models === undefined) {
114
+ throw acp.RequestError.internalError(
115
+ { configId },
116
+ 'Unable to verify model availability.',
117
+ );
118
+ }
119
+ if (value !== agent.getModel() && !models.some(({ id }) => id === value)) {
120
+ throw acp.RequestError.invalidParams({ value }, 'Unavailable model.');
121
+ }
122
+ try {
123
+ await agent.setModel(value);
124
+ } catch (error) {
125
+ logger.debug(() => `Failed to set model: ${String(error)}`);
126
+ throw acp.RequestError.internalError(
127
+ { configId },
128
+ 'Unable to set model.',
129
+ );
130
+ }
131
+ return buildZedConfigOptions(agent, config);
132
+ }
133
+ if (configId !== 'reasoning.effort' && configId !== 'emojifilter') {
134
+ throw acp.RequestError.invalidParams(
135
+ { configId },
136
+ 'Unknown config option.',
137
+ );
138
+ }
139
+ const parsed = parseEphemeralSettingValue(configId, value);
140
+ if (!parsed.success) {
141
+ throw acp.RequestError.invalidParams({ configId }, parsed.message);
142
+ }
143
+ try {
144
+ config.setEphemeralSetting(configId, parsed.value);
145
+ coreEvents.emitSettingsChanged();
146
+ } catch (error) {
147
+ logger.debug(() => `Failed to apply config option: ${String(error)}`);
148
+ throw acp.RequestError.internalError(
149
+ { configId },
150
+ 'Unable to apply config option.',
151
+ );
152
+ }
153
+ return buildZedConfigOptions(agent, config);
154
+ }
155
+
156
+ export async function zedConfigOptionsForClient(
157
+ capabilities: ClientCapabilitiesWithSession | undefined,
158
+ agent: Pick<Agent, 'getModel' | 'getProviderStatus'>,
159
+ config: Config,
160
+ ): Promise<Pick<acp.NewSessionResponse, 'configOptions'>> {
161
+ return capabilities?.session?.configOptions === true
162
+ ? { configOptions: await buildZedConfigOptions(agent, config) }
163
+ : {};
164
+ }
165
+
166
+ export async function setZedConfigOption(
167
+ agent: Agent,
168
+ config: Config,
169
+ configId: string,
170
+ value: string,
171
+ ): Promise<acp.SetSessionConfigOptionResponse> {
172
+ return {
173
+ configOptions: await applyZedConfigOption(agent, config, configId, value),
174
+ };
175
+ }
176
+
177
+ interface ConfigurableZedSession {
178
+ setConfigOption(
179
+ configId: string,
180
+ value: string,
181
+ ): Promise<acp.SetSessionConfigOptionResponse>;
182
+ }
183
+
184
+ export function dispatchZedConfigOption(
185
+ capabilities: ClientCapabilitiesWithSession | undefined,
186
+ sessions: ReadonlyMap<string, ConfigurableZedSession>,
187
+ params: acp.SetSessionConfigOptionRequest,
188
+ ): Promise<acp.SetSessionConfigOptionResponse> {
189
+ const session = sessions.get(params.sessionId);
190
+ if (session === undefined) {
191
+ throw acp.RequestError.resourceNotFound(params.sessionId);
192
+ }
193
+ if (capabilities?.session?.configOptions !== true) {
194
+ throw acp.RequestError.invalidParams(
195
+ { sessionId: params.sessionId },
196
+ 'Config options not supported by client.',
197
+ );
198
+ }
199
+ if (typeof params.value !== 'string') {
200
+ throw acp.RequestError.invalidParams(
201
+ { configId: params.configId },
202
+ 'Config value must be a string.',
203
+ );
204
+ }
205
+ return session.setConfigOption(params.configId, params.value);
206
+ }
207
+
208
+ export function observeZedConfigOptions(
209
+ agent: Pick<Agent, 'getModel' | 'getProviderStatus'>,
210
+ config: Config,
211
+ sendUpdate: (update: acp.SessionUpdate) => Promise<void>,
212
+ onError: (error: unknown) => void,
213
+ ): () => void {
214
+ let stopped = false;
215
+ let running = false;
216
+ let pending = false;
217
+ const isStopped = () => stopped;
218
+ const refresh = () => {
219
+ pending = true;
220
+ // One loop owns refresh work; events received while it runs coalesce into
221
+ // one additional pass instead of starting concurrent snapshots.
222
+ if (running) return;
223
+ running = true;
224
+ void runRefreshLoop();
225
+ };
226
+
227
+ async function runRefreshLoop(): Promise<void> {
228
+ try {
229
+ while (pending && !stopped) {
230
+ pending = false;
231
+ await sendConfigOptionUpdate(
232
+ agent,
233
+ config,
234
+ sendUpdate,
235
+ isStopped,
236
+ onError,
237
+ );
238
+ }
239
+ } finally {
240
+ running = false;
241
+ }
242
+ }
243
+ coreEvents.on(CoreEvent.ModelChanged, refresh);
244
+ coreEvents.on(CoreEvent.ModelProfileChanged, refresh);
245
+ coreEvents.on(CoreEvent.SettingsChanged, refresh);
246
+ return () => {
247
+ stopped = true;
248
+ coreEvents.off(CoreEvent.ModelChanged, refresh);
249
+ coreEvents.off(CoreEvent.ModelProfileChanged, refresh);
250
+ coreEvents.off(CoreEvent.SettingsChanged, refresh);
251
+ };
252
+ }
253
+
254
+ export function zedSessionConfigOptions(
255
+ capabilities: ClientCapabilitiesWithSession | undefined,
256
+ session: { getConfigOptions(): Promise<acp.SessionConfigOption[]> },
257
+ ): Promise<Pick<acp.LoadSessionResponse, 'configOptions'>> {
258
+ return capabilities?.session?.configOptions === true
259
+ ? session.getConfigOptions().then((configOptions) => ({ configOptions }))
260
+ : Promise.resolve({});
261
+ }
262
+
263
+ async function sendConfigOptionUpdate(
264
+ agent: Pick<Agent, 'getModel' | 'getProviderStatus'>,
265
+ config: Config,
266
+ sendUpdate: (update: acp.SessionUpdate) => Promise<void>,
267
+ isStopped: () => boolean,
268
+ onError: (error: unknown) => void,
269
+ ): Promise<void> {
270
+ try {
271
+ const configOptions = await buildZedConfigOptions(agent, config);
272
+ if (isStopped()) return;
273
+ await sendUpdate({
274
+ sessionUpdate: 'config_option_update',
275
+ configOptions,
276
+ });
277
+ } catch (error) {
278
+ onError(error);
279
+ }
280
+ }
@@ -63,9 +63,9 @@ export function extractToolResultText(
63
63
  }
64
64
 
65
65
  export function extractTextFromPartList(
66
- llmContent: string | ContentBlock[] | IContent | undefined,
66
+ llmContent: string | ContentBlock[] | IContent | null | undefined,
67
67
  ): string | null {
68
- if (llmContent === undefined) {
68
+ if (llmContent === undefined || llmContent === null) {
69
69
  return null;
70
70
  }
71
71
 
@@ -9,6 +9,11 @@ import {
9
9
  ToolConfirmationOutcome,
10
10
  ApprovalMode,
11
11
  } from '@vybestack/llxprt-code-core';
12
+ import type {
13
+ AgentEvent,
14
+ DoneReason,
15
+ ThoughtSummary,
16
+ } from '@vybestack/llxprt-code-agents';
12
17
  import type * as acp from '@agentclientprotocol/sdk';
13
18
  import { z } from 'zod';
14
19
 
@@ -22,6 +27,36 @@ export function parseZedAuthMethodId(
22
27
  return z.enum(availableProfiles as [string, ...string[]]).parse(methodId);
23
28
  }
24
29
 
30
+ /**
31
+ * Maps an Agent-API {@link DoneReason} to the ACP {@link acp.StopReason} the Zed
32
+ * client expects. Terminal failure reasons (`error`, `hook-stopped`) have no
33
+ * clean stop reason and throw so the prompt path surfaces them as errors. Kept
34
+ * here (a pure mapper alongside the other Zed mapping helpers) so zedIntegration.ts
35
+ * stays within its max-lines budget.
36
+ */
37
+ export function mapDoneReasonToStopReason(reason: DoneReason): acp.StopReason {
38
+ switch (reason) {
39
+ case 'stop':
40
+ case 'loop-detected':
41
+ return 'end_turn';
42
+ case 'aborted':
43
+ return 'cancelled';
44
+ case 'max-turns':
45
+ return 'max_turn_requests';
46
+ case 'context-overflow':
47
+ return 'max_tokens';
48
+ case 'refusal':
49
+ return 'refusal';
50
+ case 'error':
51
+ case 'hook-stopped':
52
+ throw new Error(`Agent stopped with terminal reason: ${reason}`);
53
+ default: {
54
+ const exhaustive: never = reason;
55
+ return exhaustive;
56
+ }
57
+ }
58
+ }
59
+
25
60
  /**
26
61
  * Structural input for {@link toToolCallContent}: the display/error fields of
27
62
  * a tool execution result. Both the core `ToolResult` and the public
@@ -190,3 +225,100 @@ export function buildAvailableModes(): acp.SessionMode[] {
190
225
  },
191
226
  ];
192
227
  }
228
+
229
+ /**
230
+ * Builds the ACP session `modes` block (available modes + the session's current
231
+ * mode) shared by the newSession and loadSession (disk-resume AND #1604
232
+ * re-attach) responses. Typed via the response's `modes` field so it stays exact
233
+ * without importing a possibly-renamed acp type. Centralized here so the mode
234
+ * advertisement is identical across all three response paths.
235
+ */
236
+ export function buildSessionModes(
237
+ currentModeId: ApprovalMode,
238
+ ): acp.LoadSessionResponse['modes'] {
239
+ return {
240
+ availableModes: buildAvailableModes(),
241
+ currentModeId,
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Flattens an Agent-API {@link ThoughtSummary} into the display text the Zed
247
+ * thought-chunk carries: the non-empty subject/description joined by a space.
248
+ * Pure mapper kept alongside the other Zed event mappers so zedIntegration.ts
249
+ * stays within its max-lines budget.
250
+ */
251
+ export function extractThoughtText(thought: ThoughtSummary): string {
252
+ const parts = [thought.subject, thought.description].filter(
253
+ (v) => v.length > 0,
254
+ );
255
+ return parts.join(' ');
256
+ }
257
+
258
+ /**
259
+ * Translates an Agent `error` event into an Error for the prompt path, carrying
260
+ * the upstream numeric `status` (when present) so downstream 429 handling can
261
+ * still detect a rate-limit. Pure; extracted here to keep zedIntegration.ts thin.
262
+ */
263
+ export function translateErrorEvent(
264
+ event: Extract<AgentEvent, { type: 'error' }>,
265
+ ): Error {
266
+ const error = new Error(event.error.message);
267
+ if (event.error.status !== undefined) {
268
+ Object.assign(error, { status: event.error.status });
269
+ }
270
+ return error;
271
+ }
272
+
273
+ /**
274
+ * Translates an Agent `idle-timeout` event into an Error carrying its message.
275
+ * Pure; extracted here to keep zedIntegration.ts within its max-lines budget.
276
+ */
277
+ export function translateIdleTimeout(
278
+ event: Extract<AgentEvent, { type: 'idle-timeout' }>,
279
+ ): Error {
280
+ return new Error(event.error.message);
281
+ }
282
+
283
+ /**
284
+ * Renders the pre-delivery debug line for a session/update notification: the
285
+ * update kind plus the text length when the update carries text content. Pure;
286
+ * extracted here to keep zedIntegration.ts within its max-lines budget.
287
+ */
288
+ export function describeSessionUpdateForLog(update: acp.SessionUpdate): string {
289
+ const chars =
290
+ 'content' in update && update.content && 'text' in update.content
291
+ ? `(${update.content.text.length} chars)`
292
+ : '';
293
+ return `sendUpdate: ${update.sessionUpdate} ${chars}`;
294
+ }
295
+
296
+ /**
297
+ * Builds the usage_update session notification for a turn's usage metadata
298
+ * (issue #1607): `used` is the total tokens now in context (the response's
299
+ * cumulative totalTokenCount) and `size` is the configured context-window
300
+ * limit, so the client can render consumption against the real window rather
301
+ * than against itself. When a provider omits cumulative `totalTokenCount`, the
302
+ * candidate count is retained as a lower-bound fallback rather than suppressing
303
+ * usage entirely; it represents only this completion, so it may under-report
304
+ * context consumption but never overstates it. Returns null when the metadata
305
+ * carries no usable count. `size` is clamped up to `used` so a mid-flight limit
306
+ * change can never report used > size on the wire.
307
+ */
308
+ export function buildUsageUpdate(
309
+ usage: {
310
+ readonly totalTokenCount?: number;
311
+ readonly candidatesTokenCount?: number;
312
+ },
313
+ contextWindowSize: number,
314
+ ): acp.SessionUpdate | null {
315
+ const used = usage.totalTokenCount ?? usage.candidatesTokenCount ?? 0;
316
+ if (used === 0) {
317
+ return null;
318
+ }
319
+ return {
320
+ sessionUpdate: 'usage_update',
321
+ used,
322
+ size: Math.max(contextWindowSize, used),
323
+ };
324
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Vybestack LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import type { Config } from '@vybestack/llxprt-code-core';
8
+ import * as acp from '@agentclientprotocol/sdk';
9
+ import { loadProfileByName } from '@vybestack/llxprt-code-providers/runtime.js';
10
+ import { parseZedAuthMethodId } from './zed-helpers.js';
11
+
12
+ export async function initializeZedAgent(
13
+ config: Config,
14
+ ): Promise<acp.InitializeResponse> {
15
+ let profileNames: string[];
16
+ try {
17
+ profileNames = await getAvailableProfileNames(config);
18
+ } catch (error) {
19
+ throw new Error(
20
+ `Failed to initialize Zed agent: ${error instanceof Error ? error.message : String(error)}`,
21
+ { cause: error },
22
+ );
23
+ }
24
+ return {
25
+ protocolVersion: acp.PROTOCOL_VERSION,
26
+ authMethods: profileNames.map((name) => ({
27
+ id: name,
28
+ name,
29
+ description: null,
30
+ })),
31
+ agentCapabilities: {
32
+ loadSession: true,
33
+ sessionCapabilities: {
34
+ list: {},
35
+ resume: {},
36
+ },
37
+ promptCapabilities: {
38
+ image: true,
39
+ audio: true,
40
+ embeddedContext: true,
41
+ },
42
+ },
43
+ };
44
+ }
45
+
46
+ export async function authenticateZedAgent(
47
+ config: Config,
48
+ methodId: string,
49
+ ): Promise<void> {
50
+ try {
51
+ const profileNames = await getAvailableProfileNames(config);
52
+ const profileName = parseZedAuthMethodId(methodId, profileNames);
53
+ await loadProfileByName(profileName);
54
+ } catch (error) {
55
+ throw new Error(
56
+ `Failed to authenticate with profile "${methodId}": ${error instanceof Error ? error.message : String(error)}`,
57
+ { cause: error },
58
+ );
59
+ }
60
+ }
61
+
62
+ async function getAvailableProfileNames(config: Config): Promise<string[]> {
63
+ return (await config.getProfileManager()?.listProfiles()) ?? [];
64
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Vybestack LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import type * as acp from '@agentclientprotocol/sdk';
8
+ import type { Todo } from '@vybestack/llxprt-code-core';
9
+
10
+ export function buildZedPlanUpdate(todos: readonly Todo[]): acp.SessionUpdate {
11
+ return {
12
+ sessionUpdate: 'plan',
13
+ entries: todos.map((todo) => ({
14
+ content: todo.content,
15
+ status: todo.status,
16
+ // ACP plan entries only support a fixed 'medium' priority.
17
+ priority: 'medium',
18
+ })),
19
+ };
20
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Vybestack LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as acp from '@agentclientprotocol/sdk';
8
+ import type { Agent } from '@vybestack/llxprt-code-agents';
9
+ import { DebugLogger } from '@vybestack/llxprt-code-core';
10
+ import {
11
+ executeZedCommand,
12
+ type ZedCommandResult,
13
+ } from './zed-command-registry.js';
14
+
15
+ const logger = new DebugLogger('llxprt:zed-integration:prompt-command');
16
+
17
+ /**
18
+ * Extracts the first text content from an ACP prompt content-block array. A
19
+ * Zed slash command arrives as a single text block, so only the first text
20
+ * block's text is relevant for command detection.
21
+ *
22
+ * Returns `null` when the prompt has no text block (e.g. only image/audio),
23
+ * so the caller treats it as an ordinary (non-slash) prompt.
24
+ */
25
+ export function extractPromptText(
26
+ prompt: readonly acp.ContentBlock[],
27
+ ): string | null {
28
+ if (prompt.length !== 1 || prompt[0].type !== 'text') {
29
+ return null;
30
+ }
31
+ return prompt[0].text;
32
+ }
33
+
34
+ /**
35
+ * Attempts to handle a prompt as a Zed slash command. When the prompt is a
36
+ * known Zed command (e.g. `/compact`, `/tools`), this executes the command,
37
+ * streams the result text as an `agent_message_chunk` via the provided
38
+ * `sendUpdate` callback, and returns the final {@link acp.PromptResponse}.
39
+ *
40
+ * Returns `null` when the prompt is NOT a slash command (or is an unknown
41
+ * command), so the caller continues with ordinary model-streaming.
42
+ *
43
+ * @param prompt The raw ACP content blocks from the prompt request.
44
+ * @param agent The session's real Agent.
45
+ * @param sendUpdate Callback to stream an `agent_message_chunk`.
46
+ */
47
+ export async function tryHandleZedCommand(
48
+ prompt: readonly acp.ContentBlock[],
49
+ agent: Agent,
50
+ sendUpdate: (update: acp.SessionUpdate) => Promise<void>,
51
+ ): Promise<{ response: acp.PromptResponse } | null> {
52
+ const text = extractPromptText(prompt);
53
+ if (text === null) {
54
+ return null;
55
+ }
56
+ const result: ZedCommandResult | null = await executeZedCommand(text, {
57
+ agent,
58
+ });
59
+ if (result === null) {
60
+ return null;
61
+ }
62
+ try {
63
+ await sendUpdate({
64
+ sessionUpdate: 'agent_message_chunk',
65
+ content: { type: 'text', text: result.text },
66
+ });
67
+ } catch (error) {
68
+ logger.debug(() => `Command response delivery failed: ${String(error)}`);
69
+ throw acp.RequestError.internalError(
70
+ { cause: error },
71
+ 'Command response delivery failed.',
72
+ );
73
+ }
74
+ return {
75
+ response: { stopReason: 'end_turn' },
76
+ };
77
+ }