@lvce-editor/chat-view 6.7.0 → 6.9.0

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.
@@ -1714,6 +1714,7 @@ const createDefaultState = () => {
1714
1714
  name: '_blank',
1715
1715
  uri: ''
1716
1716
  }],
1717
+ questionToolEnabled: false,
1717
1718
  renamingSessionId: '',
1718
1719
  selectedModelId: defaultModelId,
1719
1720
  selectedProjectId: defaultProjectId,
@@ -1729,10 +1730,10 @@ const createDefaultState = () => {
1729
1730
  tokensUsed: 0,
1730
1731
  uid: 0,
1731
1732
  usageOverviewEnabled: false,
1732
- useChatCoordinatorWorker: true,
1733
+ useChatCoordinatorWorker: false,
1733
1734
  useChatMathWorker: true,
1734
1735
  useChatNetworkWorkerForRequests: false,
1735
- useChatToolWorker: false,
1736
+ useChatToolWorker: true,
1736
1737
  useMockApi: false,
1737
1738
  userName: '',
1738
1739
  userSubscriptionPlan: '',
@@ -3344,6 +3345,20 @@ const getAiResponse$1 = async options => {
3344
3345
  const execute = async (name, rawArguments, options) => {
3345
3346
  return invoke$3('ChatTool.execute', name, rawArguments, options);
3346
3347
  };
3348
+ const getTools = async () => {
3349
+ return invoke$3('ChatTool.getTools');
3350
+ };
3351
+
3352
+ const executeAskQuestionTool = args => {
3353
+ const normalized = args && typeof args === 'object' ? args : {};
3354
+ const question = Reflect.get(normalized, 'question');
3355
+ const answers = Reflect.get(normalized, 'answers');
3356
+ return JSON.stringify({
3357
+ answers: Array.isArray(answers) ? answers.filter(answer => typeof answer === 'string') : [],
3358
+ ok: true,
3359
+ question: typeof question === 'string' ? question : ''
3360
+ });
3361
+ };
3347
3362
 
3348
3363
  const getToolErrorPayload = error => {
3349
3364
  const rawStack = error && typeof error === 'object' ? Reflect.get(error, 'stack') : undefined;
@@ -3484,6 +3499,38 @@ const executeRenderHtmlTool = async (args, _options) => {
3484
3499
  });
3485
3500
  };
3486
3501
 
3502
+ const toLines = value => {
3503
+ if (!value) {
3504
+ return [];
3505
+ }
3506
+ const split = value.split('\n').map(line => line.endsWith('\r') ? line.slice(0, -1) : line);
3507
+ if (split.length > 0 && split.at(-1) === '') {
3508
+ split.pop();
3509
+ }
3510
+ return split;
3511
+ };
3512
+ const getLineCounts = (before, after) => {
3513
+ const beforeLines = toLines(before);
3514
+ const afterLines = toLines(after);
3515
+ let start = 0;
3516
+ while (start < beforeLines.length && start < afterLines.length && beforeLines[start] === afterLines[start]) {
3517
+ start++;
3518
+ }
3519
+ let beforeEnd = beforeLines.length - 1;
3520
+ let afterEnd = afterLines.length - 1;
3521
+ while (beforeEnd >= start && afterEnd >= start && beforeLines[beforeEnd] === afterLines[afterEnd]) {
3522
+ beforeEnd--;
3523
+ afterEnd--;
3524
+ }
3525
+ return {
3526
+ linesAdded: Math.max(0, afterEnd - start + 1),
3527
+ linesDeleted: Math.max(0, beforeEnd - start + 1)
3528
+ };
3529
+ };
3530
+ const isFileNotFoundError = error => {
3531
+ const message = String(error).toLowerCase();
3532
+ return message.includes('enoent') || message.includes('not found');
3533
+ };
3487
3534
  const executeWriteFileTool = async (args, _options) => {
3488
3535
  const filePath = typeof args.path === 'string' ? args.path : '';
3489
3536
  const content = typeof args.content === 'string' ? args.content : '';
@@ -3494,8 +3541,22 @@ const executeWriteFileTool = async (args, _options) => {
3494
3541
  }
3495
3542
  const normalizedPath = normalizeRelativePath(filePath);
3496
3543
  try {
3544
+ let previousContent = '';
3545
+ try {
3546
+ previousContent = await readFile(normalizedPath);
3547
+ } catch (error) {
3548
+ if (!isFileNotFoundError(error)) {
3549
+ throw error;
3550
+ }
3551
+ }
3497
3552
  await writeFile(normalizedPath, content);
3553
+ const {
3554
+ linesAdded,
3555
+ linesDeleted
3556
+ } = getLineCounts(previousContent, content);
3498
3557
  return JSON.stringify({
3558
+ linesAdded,
3559
+ linesDeleted,
3499
3560
  ok: true,
3500
3561
  path: normalizedPath
3501
3562
  });
@@ -3522,12 +3583,19 @@ const parseToolArguments = rawArguments => {
3522
3583
  }
3523
3584
  };
3524
3585
 
3586
+ const stringifyToolOutput = output => {
3587
+ if (typeof output === 'string') {
3588
+ return output;
3589
+ }
3590
+ return JSON.stringify(output) ?? 'null';
3591
+ };
3525
3592
  const executeChatTool = async (name, rawArguments, options) => {
3526
3593
  if (options.useChatToolWorker) {
3527
- return execute(name, rawArguments, {
3594
+ const workerOutput = await execute(name, rawArguments, {
3528
3595
  assetDir: options.assetDir,
3529
3596
  platform: options.platform
3530
3597
  });
3598
+ return stringifyToolOutput(workerOutput);
3531
3599
  }
3532
3600
  const args = parseToolArguments(rawArguments);
3533
3601
  if (name === 'read_file') {
@@ -3545,6 +3613,9 @@ const executeChatTool = async (name, rawArguments, options) => {
3545
3613
  if (name === 'render_html') {
3546
3614
  return executeRenderHtmlTool(args);
3547
3615
  }
3616
+ if (name === 'ask_question') {
3617
+ return executeAskQuestionTool(args);
3618
+ }
3548
3619
  return JSON.stringify({
3549
3620
  error: `Unknown tool: ${name}`
3550
3621
  });
@@ -3553,7 +3624,7 @@ const executeChatTool = async (name, rawArguments, options) => {
3553
3624
  const getReadFileTool = () => {
3554
3625
  return {
3555
3626
  function: {
3556
- description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI. When you reference files in your response, use markdown links like [index.ts](file:///workspace/src/index.ts).',
3627
+ description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI. When you reference files in your response, use markdown links like [index.ts](vscode-references:///workspace/src/index.ts).',
3557
3628
  name: 'read_file',
3558
3629
  parameters: {
3559
3630
  additionalProperties: false,
@@ -3656,8 +3727,53 @@ const getRenderHtmlTool = () => {
3656
3727
  type: 'function'
3657
3728
  };
3658
3729
  };
3659
- const getBasicChatTools = () => {
3660
- return [getReadFileTool(), getWriteFileTool(), getListFilesTool(), getGetWorkspaceUriTool(), getRenderHtmlTool()];
3730
+ const getAskQuestionTool = () => {
3731
+ return {
3732
+ function: {
3733
+ description: 'Ask the user a multiple-choice question in the chat UI. Use this when you need a user decision before continuing. Provide short answer options.',
3734
+ name: 'ask_question',
3735
+ parameters: {
3736
+ additionalProperties: false,
3737
+ properties: {
3738
+ answers: {
3739
+ description: 'List of answer options shown to the user.',
3740
+ items: {
3741
+ type: 'string'
3742
+ },
3743
+ type: 'array'
3744
+ },
3745
+ question: {
3746
+ description: 'The question text shown to the user.',
3747
+ type: 'string'
3748
+ }
3749
+ },
3750
+ required: ['question', 'answers'],
3751
+ type: 'object'
3752
+ }
3753
+ },
3754
+ type: 'function'
3755
+ };
3756
+ };
3757
+
3758
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
3759
+ const withQuestionTool = (tools, questionToolEnabled) => {
3760
+ if (!questionToolEnabled) {
3761
+ return tools;
3762
+ }
3763
+ for (const tool of tools) {
3764
+ if (tool.function.name === 'ask_question') {
3765
+ return tools;
3766
+ }
3767
+ }
3768
+ return [...tools, getAskQuestionTool()];
3769
+ };
3770
+ const getBasicChatTools = async (questionToolEnabled = false) => {
3771
+ const fallbackTools = [getReadFileTool(), getWriteFileTool(), getListFilesTool(), getGetWorkspaceUriTool(), getRenderHtmlTool()];
3772
+ try {
3773
+ return withQuestionTool(await getTools(), questionToolEnabled);
3774
+ } catch {
3775
+ return withQuestionTool(fallbackTools, questionToolEnabled);
3776
+ }
3661
3777
  };
3662
3778
 
3663
3779
  const getClientRequestIdHeader = () => {
@@ -4767,9 +4883,10 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4767
4883
  onEventStreamFinished,
4768
4884
  onTextChunk,
4769
4885
  onToolCallsChunk,
4886
+ questionToolEnabled = false,
4770
4887
  stream,
4771
4888
  useChatNetworkWorkerForRequests = false,
4772
- useChatToolWorker = false,
4889
+ useChatToolWorker = true,
4773
4890
  webSearchEnabled = false
4774
4891
  } = options ?? {
4775
4892
  stream: false
@@ -4778,7 +4895,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4778
4895
  content: message.text,
4779
4896
  role: message.role
4780
4897
  }));
4781
- const tools = getBasicChatTools();
4898
+ const tools = await getBasicChatTools(questionToolEnabled);
4782
4899
  const maxToolIterations = 4;
4783
4900
  let previousResponseId;
4784
4901
  for (let i = 0; i <= maxToolIterations; i++) {
@@ -5356,12 +5473,12 @@ const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl, us
5356
5473
  }
5357
5474
  return normalizedLimitInfo;
5358
5475
  };
5359
- const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform, useChatNetworkWorkerForRequests = false, useChatToolWorker = false) => {
5476
+ const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform, useChatNetworkWorkerForRequests = false, useChatToolWorker = true, questionToolEnabled = false) => {
5360
5477
  const completionMessages = messages.map(message => ({
5361
5478
  content: message.text,
5362
5479
  role: message.role
5363
5480
  }));
5364
- const tools = getBasicChatTools();
5481
+ const tools = await getBasicChatTools(questionToolEnabled);
5365
5482
  const maxToolIterations = 4;
5366
5483
  for (let i = 0; i <= maxToolIterations; i++) {
5367
5484
  let parsed;
@@ -5683,11 +5800,12 @@ const getAiResponse = async ({
5683
5800
  openRouterApiKey,
5684
5801
  passIncludeObfuscation = false,
5685
5802
  platform,
5803
+ questionToolEnabled = false,
5686
5804
  selectedModelId,
5687
5805
  streamingEnabled = true,
5688
5806
  useChatCoordinatorWorker = false,
5689
5807
  useChatNetworkWorkerForRequests = false,
5690
- useChatToolWorker = false,
5808
+ useChatToolWorker = true,
5691
5809
  useMockApi,
5692
5810
  userText,
5693
5811
  webSearchEnabled = false
@@ -5710,6 +5828,7 @@ const getAiResponse = async ({
5710
5828
  openRouterApiKey,
5711
5829
  passIncludeObfuscation,
5712
5830
  platform,
5831
+ questionToolEnabled,
5713
5832
  selectedModelId,
5714
5833
  streamingEnabled,
5715
5834
  useChatNetworkWorkerForRequests,
@@ -5761,7 +5880,7 @@ const getAiResponse = async ({
5761
5880
  capture({
5762
5881
  headers,
5763
5882
  method: 'POST',
5764
- payload: getOpenAiParams(openAiInput, modelId, streamingEnabled, passIncludeObfuscation, getBasicChatTools(), webSearchEnabled, previousResponseId),
5883
+ payload: getOpenAiParams(openAiInput, modelId, streamingEnabled, passIncludeObfuscation, await getBasicChatTools(questionToolEnabled), webSearchEnabled, previousResponseId),
5765
5884
  url: getOpenApiApiEndpoint(openApiApiBaseUrl)
5766
5885
  });
5767
5886
  const result = await getMockOpenApiAssistantText(streamingEnabled, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished);
@@ -5805,6 +5924,7 @@ const getAiResponse = async ({
5805
5924
  ...(onToolCallsChunk ? {
5806
5925
  onToolCallsChunk
5807
5926
  } : {}),
5927
+ questionToolEnabled,
5808
5928
  stream: streamingEnabled,
5809
5929
  useChatNetworkWorkerForRequests,
5810
5930
  useChatToolWorker,
@@ -5834,7 +5954,7 @@ const getAiResponse = async ({
5834
5954
  text = getOpenRouterErrorMessage(result);
5835
5955
  }
5836
5956
  } else if (openRouterApiKey) {
5837
- const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform, useChatNetworkWorkerForRequests, useChatToolWorker);
5957
+ const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform, useChatNetworkWorkerForRequests, useChatToolWorker, questionToolEnabled);
5838
5958
  if (result.type === 'success') {
5839
5959
  const {
5840
5960
  text: assistantText
@@ -6051,7 +6171,7 @@ const isAlphaNumeric = value => {
6051
6171
  };
6052
6172
  const sanitizeLinkUrl = url => {
6053
6173
  const normalized = url.trim().toLowerCase();
6054
- if (normalized.startsWith('http://') || normalized.startsWith('https://') || normalized.startsWith('file://')) {
6174
+ if (normalized.startsWith('http://') || normalized.startsWith('https://') || normalized.startsWith('file://') || normalized.startsWith('vscode-references://')) {
6055
6175
  return url;
6056
6176
  }
6057
6177
  return '#';
@@ -6063,6 +6183,73 @@ const sanitizeImageUrl = url => {
6063
6183
  }
6064
6184
  return '#';
6065
6185
  };
6186
+ const isOpenBracket = value => {
6187
+ return value === '(' || value === '[' || value === '{';
6188
+ };
6189
+ const isCloseBracket = value => {
6190
+ return value === ')' || value === ']' || value === '}';
6191
+ };
6192
+ const findRawUrlEnd = (value, start) => {
6193
+ let index = start;
6194
+ while (index < value.length) {
6195
+ const current = value[index];
6196
+ if (current === ' ' || current === '\n' || current === '\r' || current === '\t' || current === '"' || current === "'" || current === '`' || current === '<' || current === '>') {
6197
+ break;
6198
+ }
6199
+ index++;
6200
+ }
6201
+ return index;
6202
+ };
6203
+ const trimRawUrlEnd = url => {
6204
+ let end = url.length;
6205
+ while (end > 0) {
6206
+ const current = url[end - 1];
6207
+ if (current === '.' || current === ',' || current === ':' || current === ';' || current === '!' || current === '?') {
6208
+ end--;
6209
+ continue;
6210
+ }
6211
+ if (isCloseBracket(current)) {
6212
+ const inner = url.slice(0, end - 1);
6213
+ let openCount = 0;
6214
+ let closeCount = 0;
6215
+ for (let i = 0; i < inner.length; i++) {
6216
+ if (isOpenBracket(inner[i])) {
6217
+ openCount++;
6218
+ } else if (isCloseBracket(inner[i])) {
6219
+ closeCount++;
6220
+ }
6221
+ }
6222
+ if (closeCount >= openCount) {
6223
+ end--;
6224
+ continue;
6225
+ }
6226
+ }
6227
+ break;
6228
+ }
6229
+ return url.slice(0, end);
6230
+ };
6231
+ const parseRawLinkToken = (value, start) => {
6232
+ if (!value.startsWith('https://', start) && !value.startsWith('http://', start)) {
6233
+ return undefined;
6234
+ }
6235
+ if (start >= 2 && value[start - 1] === '(' && value[start - 2] === ']') {
6236
+ return undefined;
6237
+ }
6238
+ const end = findRawUrlEnd(value, start);
6239
+ const rawUrl = value.slice(start, end);
6240
+ const href = trimRawUrlEnd(rawUrl);
6241
+ if (!href) {
6242
+ return undefined;
6243
+ }
6244
+ return {
6245
+ length: href.length,
6246
+ node: {
6247
+ href: sanitizeLinkUrl(href),
6248
+ text: href,
6249
+ type: 'link'
6250
+ }
6251
+ };
6252
+ };
6066
6253
  const parseLinkToken = (value, start) => {
6067
6254
  if (value[start] !== '[') {
6068
6255
  return undefined;
@@ -6291,7 +6478,7 @@ const parseMathToken = (value, start) => {
6291
6478
  return undefined;
6292
6479
  };
6293
6480
  const parseInlineToken = (value, start) => {
6294
- return parseImageToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseStrikethroughToken(value, start) || parseInlineCodeToken(value, start) || parseMathToken(value, start);
6481
+ return parseImageToken(value, start) || parseLinkToken(value, start) || parseRawLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseStrikethroughToken(value, start) || parseInlineCodeToken(value, start) || parseMathToken(value, start);
6295
6482
  };
6296
6483
  const parseInlineNodes = value => {
6297
6484
  const nodes = [];
@@ -7394,6 +7581,7 @@ const handleSubmit = async state => {
7394
7581
  openRouterApiKey,
7395
7582
  passIncludeObfuscation,
7396
7583
  platform,
7584
+ questionToolEnabled,
7397
7585
  selectedModelId,
7398
7586
  selectedSessionId,
7399
7587
  sessions,
@@ -7562,6 +7750,9 @@ const handleSubmit = async state => {
7562
7750
  openRouterApiKey,
7563
7751
  passIncludeObfuscation,
7564
7752
  platform,
7753
+ ...(typeof questionToolEnabled === 'boolean' ? {
7754
+ questionToolEnabled
7755
+ } : {}),
7565
7756
  selectedModelId,
7566
7757
  streamingEnabled,
7567
7758
  useChatCoordinatorWorker,
@@ -7875,11 +8066,18 @@ const handleClickNew = async state => {
7875
8066
  return focusInput(clearedState);
7876
8067
  };
7877
8068
 
8069
+ const normalizeFileReferenceUri = uri => {
8070
+ if (uri.startsWith('vscode-references://')) {
8071
+ return `file://${uri.slice('vscode-references://'.length)}`;
8072
+ }
8073
+ return uri;
8074
+ };
7878
8075
  const handleClickReadFile = async uri => {
7879
8076
  if (!uri) {
7880
8077
  return;
7881
8078
  }
7882
- await invoke$1('Main.openUri', uri);
8079
+ const normalizedUri = normalizeFileReferenceUri(uri);
8080
+ await invoke$1('Main.openUri', normalizedUri);
7883
8081
  };
7884
8082
 
7885
8083
  const handleClickSessionDebug = async state => {
@@ -8406,9 +8604,9 @@ const loadUseChatNetworkWorkerForRequests = async () => {
8406
8604
  const loadUseChatToolWorker = async () => {
8407
8605
  try {
8408
8606
  const savedUseChatToolWorker = await get('chatView.useChatToolWorker');
8409
- return typeof savedUseChatToolWorker === 'boolean' ? savedUseChatToolWorker : false;
8607
+ return typeof savedUseChatToolWorker === 'boolean' ? savedUseChatToolWorker : true;
8410
8608
  } catch {
8411
- return false;
8609
+ return true;
8412
8610
  }
8413
8611
  };
8414
8612
 
@@ -9045,6 +9243,29 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
9045
9243
  .TokenProperty {
9046
9244
  color: var(--ColorChartsOrange, #ef9f76);
9047
9245
  }
9246
+
9247
+ .ChatToolCallQuestionText {
9248
+ margin-bottom: 6px;
9249
+ }
9250
+
9251
+ .ChatToolCallQuestionOptions {
9252
+ display: flex;
9253
+ flex-wrap: wrap;
9254
+ gap: 6px;
9255
+ }
9256
+
9257
+ .ChatToolCallQuestionOption {
9258
+ background: color-mix(in srgb, var(--ColorBadgeBackground, #2f3640) 70%, transparent);
9259
+ border: 1px solid color-mix(in srgb, var(--ColorBorder, #3a3d41) 70%, transparent);
9260
+ border-radius: 999px;
9261
+ color: var(--ColorForeground, #d5dbe3);
9262
+ display: inline-block;
9263
+ max-width: 100%;
9264
+ overflow: hidden;
9265
+ padding: 2px 8px;
9266
+ text-overflow: ellipsis;
9267
+ white-space: nowrap;
9268
+ }
9048
9269
  `;
9049
9270
  if (!renderHtmlCss.trim()) {
9050
9271
  return baseCss;
@@ -9113,12 +9334,14 @@ const ChatHeader = 'ChatHeader';
9113
9334
  const Button = 'Button';
9114
9335
  const ButtonPrimary = 'ButtonPrimary';
9115
9336
  const ButtonSecondary = 'ButtonSecondary';
9337
+ const Deletion = 'Deletion';
9116
9338
  const Empty = '';
9117
9339
  const FileIcon = 'FileIcon';
9118
9340
  const IconButton = 'IconButton';
9119
9341
  const IconButtonDisabled = 'IconButtonDisabled';
9120
9342
  const ImageElement = 'ImageElement';
9121
9343
  const InputBox = 'InputBox';
9344
+ const Insertion = 'Insertion';
9122
9345
  const Label = 'Label';
9123
9346
  const LabelDetail = 'LabelDetail';
9124
9347
  const ChatList = 'ChatList';
@@ -9147,6 +9370,9 @@ const ChatMessageContent = 'ChatMessageContent';
9147
9370
  const ChatToolCalls = 'ChatToolCalls';
9148
9371
  const ChatToolCallsLabel = 'ChatToolCallsLabel';
9149
9372
  const ChatToolCallReadFileLink = 'ChatToolCallReadFileLink';
9373
+ const ChatToolCallQuestionOption = 'ChatToolCallQuestionOption';
9374
+ const ChatToolCallQuestionOptions = 'ChatToolCallQuestionOptions';
9375
+ const ChatToolCallQuestionText = 'ChatToolCallQuestionText';
9150
9376
  const ChatToolCallRenderHtmlLabel = 'ChatToolCallRenderHtmlLabel';
9151
9377
  const ChatToolCallRenderHtmlContent = 'ChatToolCallRenderHtmlContent';
9152
9378
  const ChatToolCallRenderHtmlBody = 'ChatToolCallRenderHtmlBody';
@@ -9337,8 +9563,9 @@ const getImageAltText = alt => {
9337
9563
  }
9338
9564
  return `${alt} (image could not be loaded)`;
9339
9565
  };
9340
- const isFileUri = href => {
9341
- return href.trim().toLowerCase().startsWith('file://');
9566
+ const isFileReferenceUri = href => {
9567
+ const normalized = href.trim().toLowerCase();
9568
+ return normalized.startsWith('file://') || normalized.startsWith('vscode-references://');
9342
9569
  };
9343
9570
  const getInlineNodeDom = (inlineNode, useChatMathWorker = false) => {
9344
9571
  if (inlineNode.type === 'text') {
@@ -9385,7 +9612,7 @@ const getInlineNodeDom = (inlineNode, useChatMathWorker = false) => {
9385
9612
  if (inlineNode.type === 'math-inline-dom') {
9386
9613
  return inlineNode.dom;
9387
9614
  }
9388
- if (isFileUri(inlineNode.href)) {
9615
+ if (isFileReferenceUri(inlineNode.href)) {
9389
9616
  return [{
9390
9617
  childCount: 1,
9391
9618
  className: ChatMessageLink,
@@ -9486,6 +9713,19 @@ const pythonRules = [{
9486
9713
  className: TokenKeyword,
9487
9714
  regex: /\b(?:def|class|return|if|elif|else|for|while|in|import|from|as|with|try|except|finally|raise|lambda|yield|pass|break|continue|True|False|None|and|or|not|is)\b/
9488
9715
  }];
9716
+ const jsonRules = [{
9717
+ className: TokenProperty,
9718
+ regex: /"[^"\\]*(?:\\.[^"\\]*)*"(?=\s*:)/
9719
+ }, {
9720
+ className: TokenString,
9721
+ regex: /"[^"\\]*(?:\\.[^"\\]*)*"/
9722
+ }, {
9723
+ className: TokenNumber,
9724
+ regex: /-?\b\d+\.?\d*(?:[eE][+-]?\d+)?\b/
9725
+ }, {
9726
+ className: TokenKeyword,
9727
+ regex: /\b(?:true|false|null)\b/
9728
+ }];
9489
9729
  const tokenize = (code, rules) => {
9490
9730
  const tokens = [];
9491
9731
  let pos = 0;
@@ -9542,6 +9782,9 @@ const highlightCode = (code, language) => {
9542
9782
  if (normalized === 'py' || normalized === 'python') {
9543
9783
  return tokenize(code, pythonRules);
9544
9784
  }
9785
+ if (normalized === 'json') {
9786
+ return tokenize(code, jsonRules);
9787
+ }
9545
9788
  return [{
9546
9789
  className: '',
9547
9790
  text: code
@@ -9810,33 +10053,6 @@ const getOpenRouterTooManyRequestsDom = () => {
9810
10053
  })];
9811
10054
  };
9812
10055
 
9813
- const getToolCallArgumentPreview = rawArguments => {
9814
- if (!rawArguments.trim()) {
9815
- return '""';
9816
- }
9817
- let parsed;
9818
- try {
9819
- parsed = JSON.parse(rawArguments);
9820
- } catch {
9821
- return rawArguments;
9822
- }
9823
- if (!parsed || typeof parsed !== 'object') {
9824
- return rawArguments;
9825
- }
9826
- const path = Reflect.get(parsed, 'path');
9827
- if (typeof path === 'string') {
9828
- return `"${path}"`;
9829
- }
9830
- const keys = Object.keys(parsed);
9831
- if (keys.length === 1) {
9832
- const value = Reflect.get(parsed, keys[0]);
9833
- if (typeof value === 'string') {
9834
- return `"${value}"`;
9835
- }
9836
- }
9837
- return rawArguments;
9838
- };
9839
-
9840
10056
  const RE_QUERY_OR_HASH = /[?#].*$/;
9841
10057
  const RE_TRAILING_SLASH = /\/$/;
9842
10058
  const getFileNameFromUri = uri => {
@@ -9916,6 +10132,33 @@ const getReadFileTarget = rawArguments => {
9916
10132
  };
9917
10133
  };
9918
10134
 
10135
+ const getToolCallArgumentPreview = rawArguments => {
10136
+ if (!rawArguments.trim()) {
10137
+ return '""';
10138
+ }
10139
+ let parsed;
10140
+ try {
10141
+ parsed = JSON.parse(rawArguments);
10142
+ } catch {
10143
+ return rawArguments;
10144
+ }
10145
+ if (!parsed || typeof parsed !== 'object') {
10146
+ return rawArguments;
10147
+ }
10148
+ const path = Reflect.get(parsed, 'path');
10149
+ if (typeof path === 'string') {
10150
+ return `"${path}"`;
10151
+ }
10152
+ const keys = Object.keys(parsed);
10153
+ if (keys.length === 1) {
10154
+ const value = Reflect.get(parsed, keys[0]);
10155
+ if (typeof value === 'string') {
10156
+ return `"${value}"`;
10157
+ }
10158
+ }
10159
+ return rawArguments;
10160
+ };
10161
+
9919
10162
  const getToolCallStatusLabel = toolCall => {
9920
10163
  if (toolCall.status === 'not-found') {
9921
10164
  return ' (not-found)';
@@ -9929,6 +10172,58 @@ const getToolCallStatusLabel = toolCall => {
9929
10172
  return '';
9930
10173
  };
9931
10174
 
10175
+ const parseAskQuestionArguments = rawArguments => {
10176
+ let parsed;
10177
+ try {
10178
+ parsed = JSON.parse(rawArguments);
10179
+ } catch {
10180
+ return {
10181
+ answers: [],
10182
+ question: ''
10183
+ };
10184
+ }
10185
+ if (!parsed || typeof parsed !== 'object') {
10186
+ return {
10187
+ answers: [],
10188
+ question: ''
10189
+ };
10190
+ }
10191
+ const question = Reflect.get(parsed, 'question');
10192
+ const rawAnswers = Reflect.get(parsed, 'answers');
10193
+ const rawChoices = Reflect.get(parsed, 'choices');
10194
+ const rawOptions = Reflect.get(parsed, 'options');
10195
+ const arrayValue = Array.isArray(rawAnswers) ? rawAnswers : Array.isArray(rawChoices) ? rawChoices : Array.isArray(rawOptions) ? rawOptions : [];
10196
+ const answers = arrayValue.filter(value => typeof value === 'string');
10197
+ return {
10198
+ answers,
10199
+ question: typeof question === 'string' ? question : ''
10200
+ };
10201
+ };
10202
+ const getToolCallAskQuestionVirtualDom = toolCall => {
10203
+ const parsed = parseAskQuestionArguments(toolCall.arguments);
10204
+ const statusLabel = getToolCallStatusLabel(toolCall);
10205
+ const questionLabel = parsed.question.trim() ? parsed.question : '(empty question)';
10206
+ const answers = parsed.answers.length > 0 ? parsed.answers : ['(no answers)'];
10207
+ const childCount = 2;
10208
+ return [{
10209
+ childCount,
10210
+ className: ChatOrderedListItem,
10211
+ type: Li
10212
+ }, {
10213
+ childCount: 1,
10214
+ className: ChatToolCallQuestionText,
10215
+ type: Div
10216
+ }, text(`ask_question: ${questionLabel}${statusLabel}`), {
10217
+ childCount: answers.length,
10218
+ className: ChatToolCallQuestionOptions,
10219
+ type: Div
10220
+ }, ...answers.flatMap(answer => [{
10221
+ childCount: 1,
10222
+ className: ChatToolCallQuestionOption,
10223
+ type: Span
10224
+ }, text(answer.trim() ? answer : '(empty answer)')])];
10225
+ };
10226
+
9932
10227
  const getToolCallReadFileVirtualDom = toolCall => {
9933
10228
  const target = getReadFileTarget(toolCall.arguments);
9934
10229
  if (!target) {
@@ -10267,10 +10562,18 @@ const getToolCallDisplayName = name => {
10267
10562
  }
10268
10563
  return name;
10269
10564
  };
10565
+ const hasIncompleteJsonArguments = rawArguments => {
10566
+ try {
10567
+ JSON.parse(rawArguments);
10568
+ return false;
10569
+ } catch {
10570
+ return true;
10571
+ }
10572
+ };
10270
10573
  const getToolCallLabel = toolCall => {
10271
10574
  const displayName = getToolCallDisplayName(toolCall.name);
10272
- if (toolCall.name === 'getWorkspaceUri' && toolCall.result) {
10273
- return `${displayName} ${toolCall.result}`;
10575
+ if (toolCall.name === 'write_file' && !toolCall.status && hasIncompleteJsonArguments(toolCall.arguments)) {
10576
+ return `${displayName} (in progress)`;
10274
10577
  }
10275
10578
  const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
10276
10579
  const statusLabel = getToolCallStatusLabel(toolCall);
@@ -10279,19 +10582,128 @@ const getToolCallLabel = toolCall => {
10279
10582
  }
10280
10583
  return `${displayName} ${argumentPreview}${statusLabel}`;
10281
10584
  };
10585
+ const getToolCallGetWorkspaceUriVirtualDom = toolCall => {
10586
+ if (!toolCall.result) {
10587
+ return [];
10588
+ }
10589
+ const statusLabel = getToolCallStatusLabel(toolCall);
10590
+ const fileName = getFileNameFromUri(toolCall.result);
10591
+ return [{
10592
+ childCount: statusLabel ? 4 : 3,
10593
+ className: ChatOrderedListItem,
10594
+ title: toolCall.result,
10595
+ type: Li
10596
+ }, {
10597
+ childCount: 0,
10598
+ className: FileIcon,
10599
+ type: Div
10600
+ }, text('get_workspace_uri '), {
10601
+ childCount: 1,
10602
+ className: ChatToolCallReadFileLink,
10603
+ 'data-uri': toolCall.result,
10604
+ onClick: HandleClickReadFile,
10605
+ type: Span
10606
+ }, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
10607
+ };
10608
+ const parseWriteFileLineCounts = rawResult => {
10609
+ if (!rawResult) {
10610
+ return {
10611
+ linesAdded: 0,
10612
+ linesDeleted: 0
10613
+ };
10614
+ }
10615
+ let parsed;
10616
+ try {
10617
+ parsed = JSON.parse(rawResult);
10618
+ } catch {
10619
+ return {
10620
+ linesAdded: 0,
10621
+ linesDeleted: 0
10622
+ };
10623
+ }
10624
+ if (!parsed || typeof parsed !== 'object') {
10625
+ return {
10626
+ linesAdded: 0,
10627
+ linesDeleted: 0
10628
+ };
10629
+ }
10630
+ const linesAdded = Reflect.get(parsed, 'linesAdded');
10631
+ const linesDeleted = Reflect.get(parsed, 'linesDeleted');
10632
+ return {
10633
+ linesAdded: typeof linesAdded === 'number' ? Math.max(0, linesAdded) : 0,
10634
+ linesDeleted: typeof linesDeleted === 'number' ? Math.max(0, linesDeleted) : 0
10635
+ };
10636
+ };
10637
+ const getToolCallWriteFileVirtualDom = toolCall => {
10638
+ const target = getReadFileTarget(toolCall.arguments);
10639
+ if (!target) {
10640
+ return [];
10641
+ }
10642
+ const fileName = getFileNameFromUri(target.title);
10643
+ const statusLabel = getToolCallStatusLabel(toolCall);
10644
+ const {
10645
+ linesAdded,
10646
+ linesDeleted
10647
+ } = parseWriteFileLineCounts(toolCall.result);
10648
+ const fileNameClickableProps = target.clickableUri ? {
10649
+ 'data-uri': target.clickableUri,
10650
+ onClick: HandleClickReadFile
10651
+ } : {};
10652
+ return [{
10653
+ childCount: statusLabel ? 6 : 5,
10654
+ className: ChatOrderedListItem,
10655
+ title: target.title,
10656
+ type: Li
10657
+ }, {
10658
+ childCount: 0,
10659
+ className: FileIcon,
10660
+ type: Div
10661
+ }, text('write_file '), {
10662
+ childCount: 1,
10663
+ className: ChatToolCallReadFileLink,
10664
+ ...fileNameClickableProps,
10665
+ type: Span
10666
+ }, text(fileName), {
10667
+ childCount: 1,
10668
+ className: Insertion,
10669
+ type: Span
10670
+ }, text(` +${linesAdded}`), {
10671
+ childCount: 1,
10672
+ className: Deletion,
10673
+ type: Span
10674
+ }, text(` -${linesDeleted}`), ...(statusLabel ? [text(statusLabel)] : [])];
10675
+ };
10282
10676
  const getToolCallDom = toolCall => {
10677
+ if (toolCall.name === 'getWorkspaceUri') {
10678
+ const virtualDom = getToolCallGetWorkspaceUriVirtualDom(toolCall);
10679
+ if (virtualDom.length > 0) {
10680
+ return virtualDom;
10681
+ }
10682
+ }
10283
10683
  if (toolCall.name === 'read_file' || toolCall.name === 'list_files' || toolCall.name === 'list_file') {
10284
10684
  const virtualDom = getToolCallReadFileVirtualDom(toolCall);
10285
10685
  if (virtualDom.length > 0) {
10286
10686
  return virtualDom;
10287
10687
  }
10288
10688
  }
10689
+ if (toolCall.name === 'write_file') {
10690
+ const virtualDom = getToolCallWriteFileVirtualDom(toolCall);
10691
+ if (virtualDom.length > 0) {
10692
+ return virtualDom;
10693
+ }
10694
+ }
10289
10695
  if (toolCall.name === 'render_html') {
10290
10696
  const virtualDom = getToolCallRenderHtmlVirtualDom(toolCall);
10291
10697
  if (virtualDom.length > 0) {
10292
10698
  return virtualDom;
10293
10699
  }
10294
10700
  }
10701
+ if (toolCall.name === 'ask_question') {
10702
+ const virtualDom = getToolCallAskQuestionVirtualDom(toolCall);
10703
+ if (virtualDom.length > 0) {
10704
+ return virtualDom;
10705
+ }
10706
+ }
10295
10707
  const label = getToolCallLabel(toolCall);
10296
10708
  return [{
10297
10709
  childCount: 1,
@@ -11151,6 +11563,13 @@ const setEmitStreamingFunctionCallEvents = (state, emitStreamingFunctionCallEven
11151
11563
  };
11152
11564
  };
11153
11565
 
11566
+ const setQuestionToolEnabled = (state, questionToolEnabled) => {
11567
+ return {
11568
+ ...state,
11569
+ questionToolEnabled
11570
+ };
11571
+ };
11572
+
11154
11573
  const setStreamingEnabled = (state, streamingEnabled) => {
11155
11574
  return {
11156
11575
  ...state,
@@ -11272,6 +11691,7 @@ const commandMap = {
11272
11691
  'Chat.setChatList': wrapCommand(setChatList),
11273
11692
  'Chat.setEmitStreamingFunctionCallEvents': wrapCommand(setEmitStreamingFunctionCallEvents),
11274
11693
  'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
11694
+ 'Chat.setQuestionToolEnabled': wrapCommand(setQuestionToolEnabled),
11275
11695
  'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
11276
11696
  'Chat.setUseChatCoordinatorWorker': wrapCommand(setUseChatCoordinatorWorker),
11277
11697
  'Chat.setUseChatMathWorker': wrapCommand(setUseChatMathWorker),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "6.7.0",
3
+ "version": "6.9.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",