@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.
- package/package.json +13 -13
- package/src/commands/mcp/add.ts +8 -4
- package/src/config/cliArgParser.ts +2 -0
- package/src/config/config.ts +2 -0
- package/src/config/configBuilder.ts +3 -0
- package/src/config/configError.ts +23 -0
- package/src/config/extensions/extensionEnablement.ts +62 -1
- package/src/config/interactiveContext.ts +2 -1
- package/src/config/intermediateConfig.ts +4 -0
- package/src/config/keyBindings.ts +29 -3
- package/src/config/policy.ts +7 -1
- package/src/config/postConfigRuntime.ts +20 -14
- package/src/config/settings-schema/schema-ui.ts +9 -0
- package/src/config/settingsLoader.ts +21 -17
- package/src/config/settingsSchema.ts +2 -2
- package/src/config/trustedFolders.ts +275 -58
- package/src/config/yargsOptions.ts +7 -0
- package/src/generated/git-commit.json +1 -1
- package/src/nonInteractiveCli.ts +1 -0
- package/src/nonInteractiveCliSupport.ts +171 -51
- package/src/runtime/interactiveToolScheduler.ts +6 -6
- package/src/services/BuiltinCommandLoader.ts +33 -25
- package/src/services/FileCommandLoader.ts +34 -7
- package/src/test-utils/async.ts +20 -0
- package/src/test-utils/render.tsx +10 -1
- package/src/ui/App.tsx +17 -10
- package/src/ui/AppContainerRuntime.tsx +7 -9
- package/src/ui/cliUiRuntime.ts +28 -0
- package/src/ui/commands/authCommand.ts +240 -3
- package/src/ui/commands/compressCommand.ts +9 -1
- package/src/ui/commands/directoryCommand.tsx +1 -1
- package/src/ui/commands/logoutCommand.ts +2 -2
- package/src/ui/commands/permissionsCommand.ts +77 -20
- package/src/ui/commands/policiesCommand.ts +6 -1
- package/src/ui/commands/quotaCommand.ts +501 -0
- package/src/ui/commands/statsCommand.ts +2 -2
- package/src/ui/commands/statsQuota.ts +1 -1
- package/src/ui/commands/subagentCommand.ts +59 -27
- package/src/ui/components/AuthDialog.tsx +3 -3
- package/src/ui/components/Composer.tsx +1 -0
- package/src/ui/components/DialogManager.tsx +9 -2
- package/src/ui/components/FolderTrustDialog.tsx +71 -60
- package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
- package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
- package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
- package/src/ui/components/inputPromptHooks.ts +2 -0
- package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
- package/src/ui/components/inputPromptTypes.ts +6 -0
- package/src/ui/components/messages/AiMessage.tsx +24 -12
- package/src/ui/components/messages/ToolMessage.tsx +26 -40
- package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
- package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
- package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
- package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
- package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
- package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
- package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
- package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
- package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
- package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
- package/src/ui/contexts/KeypressContext.tsx +13 -2
- package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
- package/src/ui/contexts/TodoContext.tsx +0 -13
- package/src/ui/contexts/TodoProvider.tsx +1 -34
- package/src/ui/contexts/UIActionsContext.tsx +2 -1
- package/src/ui/contexts/UIStateContext.tsx +3 -2
- package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
- package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
- package/src/ui/hooks/agentStream/types.ts +1 -0
- package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
- package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
- package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
- package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
- package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
- package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
- package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
- package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
- package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
- package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
- package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
- package/src/ui/hooks/useFolderTrust.ts +118 -81
- package/src/ui/hooks/useGitBranchName.ts +79 -33
- package/src/ui/hooks/useIdeTrustListener.ts +5 -19
- package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
- package/src/ui/hooks/useReactToolScheduler.ts +13 -17
- package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
- package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
- package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
- package/src/ui/layouts/InlineContent.tsx +198 -0
- package/src/ui/trustDialogHelpers.ts +190 -0
- package/src/utils/cleanup-state.ts +115 -0
- package/src/utils/cleanup.ts +32 -34
- package/src/utils/sandbox-containers.ts +183 -53
- package/src/utils/sandbox-exec.ts +9 -3
- package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
- package/src/zed-integration/acp-types.ts +35 -0
- package/src/zed-integration/fileSystemService.ts +1 -1
- package/src/zed-integration/runZedIntegration.ts +142 -0
- package/src/zed-integration/zed-agent-event-handler.ts +179 -0
- package/src/zed-integration/zed-agent-setup.ts +43 -0
- package/src/zed-integration/zed-command-registry.ts +167 -0
- package/src/zed-integration/zed-config-options.ts +280 -0
- package/src/zed-integration/zed-content-utils.ts +2 -2
- package/src/zed-integration/zed-helpers.ts +132 -0
- package/src/zed-integration/zed-initialize.ts +64 -0
- package/src/zed-integration/zed-plan-update.ts +20 -0
- package/src/zed-integration/zed-prompt-command.ts +77 -0
- package/src/zed-integration/zed-session-config.ts +119 -0
- package/src/zed-integration/zed-session-errors.ts +337 -0
- package/src/zed-integration/zed-session-events.ts +216 -0
- package/src/zed-integration/zed-session-info.ts +255 -0
- package/src/zed-integration/zed-session-lifecycle.ts +186 -0
- package/src/zed-integration/zed-session-listing.ts +153 -0
- package/src/zed-integration/zed-session-loader.ts +193 -0
- package/src/zed-integration/zed-session-pagination.ts +240 -0
- package/src/zed-integration/zed-session-replay.ts +564 -0
- package/src/zed-integration/zed-stream-batcher.ts +236 -0
- package/src/zed-integration/zed-terminal-manager.ts +416 -0
- package/src/zed-integration/zed-terminal-setup.ts +72 -0
- package/src/zed-integration/zed-test-helpers.ts +349 -4
- package/src/zed-integration/zed-tool-handler.ts +47 -5
- package/src/zed-integration/zedIntegration.ts +538 -587
- package/src/services/todo-continuation/todoContinuationService.ts +0 -610
- package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
- package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
- 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
|
+
}
|