@lvce-editor/chat-view 2.6.0 → 2.8.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.
@@ -1079,6 +1079,7 @@ const {
1079
1079
  } = create$2(6002);
1080
1080
 
1081
1081
  const Div$1 = 4;
1082
+ const Ol$1 = 49;
1082
1083
 
1083
1084
  const ClientX = 'event.clientX';
1084
1085
  const ClientY = 'event.clientY';
@@ -1469,6 +1470,7 @@ const createDefaultState = () => {
1469
1470
  composerHeight: composerLineHeight + 8,
1470
1471
  composerLineHeight,
1471
1472
  composerValue: '',
1473
+ emitStreamingFunctionCallEvents: false,
1472
1474
  errorCount: 0,
1473
1475
  focus: 'composer',
1474
1476
  focused: false,
@@ -1511,6 +1513,7 @@ const createDefaultState = () => {
1511
1513
  useMockApi: false,
1512
1514
  viewMode: 'list',
1513
1515
  warningCount: 0,
1516
+ webSearchEnabled: true,
1514
1517
  width: 0,
1515
1518
  x: 0,
1516
1519
  y: 0
@@ -3285,7 +3288,8 @@ const getOpenAiTools = tools => {
3285
3288
  };
3286
3289
  });
3287
3290
  };
3288
- const getOpenAiParams = (input, modelId, stream, includeObfuscation, tools, previousResponseId) => {
3291
+ const getOpenAiParams = (input, modelId, stream, includeObfuscation, tools, webSearchEnabled, previousResponseId) => {
3292
+ const openAiTools = getOpenAiTools(tools);
3289
3293
  return {
3290
3294
  input,
3291
3295
  model: modelId,
@@ -3301,7 +3305,9 @@ const getOpenAiParams = (input, modelId, stream, includeObfuscation, tools, prev
3301
3305
  previous_response_id: previousResponseId
3302
3306
  } : {}),
3303
3307
  tool_choice: 'auto',
3304
- tools: getOpenAiTools(tools)
3308
+ tools: webSearchEnabled ? [...openAiTools, {
3309
+ type: 'web_search'
3310
+ }] : openAiTools
3305
3311
  };
3306
3312
  };
3307
3313
  const getStreamChunkText = content => {
@@ -3319,6 +3325,47 @@ const getStreamChunkText = content => {
3319
3325
  return typeof text === 'string' ? text : '';
3320
3326
  }).join('');
3321
3327
  };
3328
+ const getShortToolErrorMessage = error => {
3329
+ const trimmed = error.trim().replace(/^Error:\s*/, '');
3330
+ const firstLine = trimmed.split('\n')[0];
3331
+ if (firstLine.length <= 80) {
3332
+ return firstLine;
3333
+ }
3334
+ return `${firstLine.slice(0, 77)}...`;
3335
+ };
3336
+ const getToolCallExecutionStatus = content => {
3337
+ let parsed;
3338
+ try {
3339
+ parsed = JSON.parse(content);
3340
+ } catch {
3341
+ return {
3342
+ errorMessage: 'Invalid tool output',
3343
+ status: 'error'
3344
+ };
3345
+ }
3346
+ if (!parsed || typeof parsed !== 'object') {
3347
+ return {
3348
+ errorMessage: 'Invalid tool output',
3349
+ status: 'error'
3350
+ };
3351
+ }
3352
+ const rawError = Reflect.get(parsed, 'error');
3353
+ if (typeof rawError !== 'string' || !rawError.trim()) {
3354
+ return {
3355
+ status: 'success'
3356
+ };
3357
+ }
3358
+ const errorMessage = getShortToolErrorMessage(rawError);
3359
+ if (/not[\s_-]?found|enoent/i.test(errorMessage)) {
3360
+ return {
3361
+ status: 'not-found'
3362
+ };
3363
+ }
3364
+ return {
3365
+ errorMessage,
3366
+ status: 'error'
3367
+ };
3368
+ };
3322
3369
  const getResponseOutputText = parsed => {
3323
3370
  if (!parsed || typeof parsed !== 'object') {
3324
3371
  return '';
@@ -3455,7 +3502,31 @@ const updateToolCallAccumulator = (accumulator, chunk) => {
3455
3502
  toolCalls
3456
3503
  };
3457
3504
  };
3458
- const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished) => {
3505
+ const getResponseFunctionCallsFromStreamingAccumulator = toolCallAccumulator => {
3506
+ return Object.entries(toolCallAccumulator).toSorted((a, b) => a[0].localeCompare(b[0])).map(entry => entry[1]).filter(toolCall => typeof toolCall.id === 'string' && !!toolCall.id && !!toolCall.name).map(toolCall => ({
3507
+ arguments: toolCall.arguments,
3508
+ callId: toolCall.id,
3509
+ name: toolCall.name
3510
+ }));
3511
+ };
3512
+ const getStreamingToolCallKey = value => {
3513
+ if (!value || typeof value !== 'object') {
3514
+ return undefined;
3515
+ }
3516
+ const outputIndex = Reflect.get(value, 'output_index');
3517
+ if (typeof outputIndex === 'number') {
3518
+ return String(outputIndex);
3519
+ }
3520
+ if (typeof outputIndex === 'string' && outputIndex) {
3521
+ return outputIndex;
3522
+ }
3523
+ const itemId = Reflect.get(value, 'item_id');
3524
+ if (typeof itemId === 'string' && itemId) {
3525
+ return itemId;
3526
+ }
3527
+ return undefined;
3528
+ };
3529
+ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDataEvent) => {
3459
3530
  if (!response.body) {
3460
3531
  return {
3461
3532
  details: 'request-failed',
@@ -3467,22 +3538,14 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3467
3538
  let remainder = '';
3468
3539
  let text = '';
3469
3540
  let done = false;
3470
- let finishedNotified = false;
3471
3541
  let toolCallAccumulator = {};
3472
- const notifyFinished = async () => {
3473
- if (finishedNotified) {
3474
- return;
3475
- }
3476
- finishedNotified = true;
3477
- if (onEventStreamFinished) {
3478
- await onEventStreamFinished();
3479
- }
3480
- };
3542
+ let responseId;
3543
+ let completedResponseFunctionCalls = [];
3481
3544
  const emitToolCallAccumulator = async () => {
3482
3545
  if (!onToolCallsChunk) {
3483
3546
  return;
3484
3547
  }
3485
- const toolCalls = Object.entries(toolCallAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => !!toolCall.name);
3548
+ const toolCalls = Object.entries(toolCallAccumulator).toSorted((a, b) => a[0].localeCompare(b[0])).map(entry => entry[1]).filter(toolCall => !!toolCall.name);
3486
3549
  if (toolCalls.length === 0) {
3487
3550
  return;
3488
3551
  }
@@ -3497,7 +3560,25 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3497
3560
  }
3498
3561
  const eventType = Reflect.get(parsed, 'type');
3499
3562
  if (eventType === 'response.completed') {
3500
- await notifyFinished();
3563
+ const response = Reflect.get(parsed, 'response');
3564
+ if (response && typeof response === 'object') {
3565
+ const parsedResponseId = Reflect.get(response, 'id');
3566
+ if (typeof parsedResponseId === 'string' && parsedResponseId) {
3567
+ responseId = parsedResponseId;
3568
+ }
3569
+ completedResponseFunctionCalls = getResponseFunctionCalls(response);
3570
+ }
3571
+ return;
3572
+ }
3573
+ if (eventType === 'response.created' || eventType === 'response.in_progress') {
3574
+ const response = Reflect.get(parsed, 'response');
3575
+ if (!response || typeof response !== 'object') {
3576
+ return;
3577
+ }
3578
+ const parsedResponseId = Reflect.get(response, 'id');
3579
+ if (typeof parsedResponseId === 'string' && parsedResponseId) {
3580
+ responseId = parsedResponseId;
3581
+ }
3501
3582
  return;
3502
3583
  }
3503
3584
  if (eventType === 'response.output_text.delta') {
@@ -3512,9 +3593,9 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3512
3593
  return;
3513
3594
  }
3514
3595
  if (eventType === 'response.output_item.added') {
3515
- const outputIndex = Reflect.get(parsed, 'output_index');
3596
+ const toolCallKey = getStreamingToolCallKey(parsed);
3516
3597
  const item = Reflect.get(parsed, 'item');
3517
- if (typeof outputIndex !== 'number' || !item || typeof item !== 'object') {
3598
+ if (!toolCallKey || !item || typeof item !== 'object') {
3518
3599
  return;
3519
3600
  }
3520
3601
  const itemType = Reflect.get(item, 'type');
@@ -3533,17 +3614,49 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3533
3614
  };
3534
3615
  toolCallAccumulator = {
3535
3616
  ...toolCallAccumulator,
3536
- [outputIndex]: next
3617
+ [toolCallKey]: next
3618
+ };
3619
+ await emitToolCallAccumulator();
3620
+ return;
3621
+ }
3622
+ if (eventType === 'response.output_item.done') {
3623
+ const toolCallKey = getStreamingToolCallKey(parsed);
3624
+ const item = Reflect.get(parsed, 'item');
3625
+ if (!toolCallKey || !item || typeof item !== 'object') {
3626
+ return;
3627
+ }
3628
+ const itemType = Reflect.get(item, 'type');
3629
+ if (itemType !== 'function_call') {
3630
+ return;
3631
+ }
3632
+ const callId = Reflect.get(item, 'call_id');
3633
+ const name = Reflect.get(item, 'name');
3634
+ const rawArguments = Reflect.get(item, 'arguments');
3635
+ const current = toolCallAccumulator[toolCallKey] || {
3636
+ arguments: '',
3637
+ name: ''
3638
+ };
3639
+ toolCallAccumulator = {
3640
+ ...toolCallAccumulator,
3641
+ [toolCallKey]: {
3642
+ arguments: typeof rawArguments === 'string' ? rawArguments : current.arguments,
3643
+ ...(typeof callId === 'string' ? {
3644
+ id: callId
3645
+ } : current.id ? {
3646
+ id: current.id
3647
+ } : {}),
3648
+ name: typeof name === 'string' && name ? name : current.name
3649
+ }
3537
3650
  };
3538
3651
  await emitToolCallAccumulator();
3539
3652
  return;
3540
3653
  }
3541
3654
  if (eventType === 'response.function_call_arguments.delta' || eventType === 'response.function_call_arguments.done') {
3542
- const outputIndex = Reflect.get(parsed, 'output_index');
3543
- if (typeof outputIndex !== 'number') {
3655
+ const toolCallKey = getStreamingToolCallKey(parsed);
3656
+ if (!toolCallKey) {
3544
3657
  return;
3545
3658
  }
3546
- const current = toolCallAccumulator[outputIndex] || {
3659
+ const current = toolCallAccumulator[toolCallKey] || {
3547
3660
  arguments: '',
3548
3661
  name: ''
3549
3662
  };
@@ -3562,7 +3675,7 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3562
3675
  };
3563
3676
  toolCallAccumulator = {
3564
3677
  ...toolCallAccumulator,
3565
- [outputIndex]: next
3678
+ [toolCallKey]: next
3566
3679
  };
3567
3680
  await emitToolCallAccumulator();
3568
3681
  return;
@@ -3622,7 +3735,6 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3622
3735
  }
3623
3736
  for (const line of dataLines) {
3624
3737
  if (line === '[DONE]') {
3625
- await notifyFinished();
3626
3738
  done = true;
3627
3739
  break;
3628
3740
  }
@@ -3640,7 +3752,6 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3640
3752
  const dataLines = parseSseEvent(remainder);
3641
3753
  for (const line of dataLines) {
3642
3754
  if (line === '[DONE]') {
3643
- await notifyFinished();
3644
3755
  continue;
3645
3756
  }
3646
3757
  let parsed;
@@ -3652,8 +3763,12 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3652
3763
  await handleParsedStreamEvent(parsed);
3653
3764
  }
3654
3765
  }
3655
- await notifyFinished();
3766
+ const responseFunctionCalls = completedResponseFunctionCalls.length > 0 ? completedResponseFunctionCalls : getResponseFunctionCallsFromStreamingAccumulator(toolCallAccumulator);
3656
3767
  return {
3768
+ ...(responseId ? {
3769
+ responseId
3770
+ } : {}),
3771
+ responseFunctionCalls,
3657
3772
  text,
3658
3773
  type: 'success'
3659
3774
  };
@@ -3694,7 +3809,8 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3694
3809
  onEventStreamFinished,
3695
3810
  onTextChunk,
3696
3811
  onToolCallsChunk,
3697
- stream
3812
+ stream,
3813
+ webSearchEnabled = false
3698
3814
  } = options ?? {
3699
3815
  stream: false
3700
3816
  };
@@ -3709,7 +3825,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3709
3825
  let response;
3710
3826
  try {
3711
3827
  response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
3712
- body: JSON.stringify(getOpenAiParams(openAiInput, modelId, stream, includeObfuscation, tools, previousResponseId)),
3828
+ body: JSON.stringify(getOpenAiParams(openAiInput, modelId, stream, includeObfuscation, tools, webSearchEnabled, previousResponseId)),
3713
3829
  headers: {
3714
3830
  Authorization: `Bearer ${openApiApiKey}`,
3715
3831
  'Content-Type': 'application/json',
@@ -3745,7 +3861,51 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3745
3861
  };
3746
3862
  }
3747
3863
  if (stream) {
3748
- return parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished);
3864
+ const streamResult = await parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent);
3865
+ if (streamResult.type !== 'success') {
3866
+ return streamResult;
3867
+ }
3868
+ if (streamResult.responseId) {
3869
+ previousResponseId = streamResult.responseId;
3870
+ }
3871
+ if (streamResult.responseFunctionCalls.length > 0) {
3872
+ openAiInput.length = 0;
3873
+ const executedToolCalls = [];
3874
+ for (const toolCall of streamResult.responseFunctionCalls) {
3875
+ const content = await executeChatTool(toolCall.name, toolCall.arguments, {
3876
+ assetDir,
3877
+ platform
3878
+ });
3879
+ const executionStatus = getToolCallExecutionStatus(content);
3880
+ executedToolCalls.push({
3881
+ arguments: toolCall.arguments,
3882
+ ...(executionStatus.errorMessage ? {
3883
+ errorMessage: executionStatus.errorMessage
3884
+ } : {}),
3885
+ id: toolCall.callId,
3886
+ name: toolCall.name,
3887
+ ...(executionStatus.status ? {
3888
+ status: executionStatus.status
3889
+ } : {})
3890
+ });
3891
+ openAiInput.push({
3892
+ call_id: toolCall.callId,
3893
+ output: content,
3894
+ type: 'function_call_output'
3895
+ });
3896
+ }
3897
+ if (onToolCallsChunk && executedToolCalls.length > 0) {
3898
+ await onToolCallsChunk(executedToolCalls);
3899
+ }
3900
+ continue;
3901
+ }
3902
+ if (onEventStreamFinished) {
3903
+ await onEventStreamFinished();
3904
+ }
3905
+ return {
3906
+ text: streamResult.text,
3907
+ type: 'success'
3908
+ };
3749
3909
  }
3750
3910
  let parsed;
3751
3911
  try {
@@ -3769,17 +3929,33 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3769
3929
  const responseFunctionCalls = getResponseFunctionCalls(parsed);
3770
3930
  if (responseFunctionCalls.length > 0) {
3771
3931
  openAiInput.length = 0;
3932
+ const executedToolCalls = [];
3772
3933
  for (const toolCall of responseFunctionCalls) {
3773
3934
  const content = await executeChatTool(toolCall.name, toolCall.arguments, {
3774
3935
  assetDir,
3775
3936
  platform
3776
3937
  });
3938
+ const executionStatus = getToolCallExecutionStatus(content);
3939
+ executedToolCalls.push({
3940
+ arguments: toolCall.arguments,
3941
+ ...(executionStatus.errorMessage ? {
3942
+ errorMessage: executionStatus.errorMessage
3943
+ } : {}),
3944
+ id: toolCall.callId,
3945
+ name: toolCall.name,
3946
+ ...(executionStatus.status ? {
3947
+ status: executionStatus.status
3948
+ } : {})
3949
+ });
3777
3950
  openAiInput.push({
3778
3951
  call_id: toolCall.callId,
3779
3952
  output: content,
3780
3953
  type: 'function_call_output'
3781
3954
  });
3782
3955
  }
3956
+ if (onToolCallsChunk && executedToolCalls.length > 0) {
3957
+ await onToolCallsChunk(executedToolCalls);
3958
+ }
3783
3959
  continue;
3784
3960
  }
3785
3961
  const outputText = getResponseOutputText(parsed);
@@ -3808,6 +3984,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3808
3984
  const toolCalls = Reflect.get(message, 'tool_calls');
3809
3985
  if (Array.isArray(toolCalls) && toolCalls.length > 0) {
3810
3986
  openAiInput.length = 0;
3987
+ const executedToolCalls = [];
3811
3988
  for (const toolCall of toolCalls) {
3812
3989
  if (!toolCall || typeof toolCall !== 'object') {
3813
3990
  continue;
@@ -3823,12 +4000,29 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3823
4000
  assetDir,
3824
4001
  platform
3825
4002
  }) : '{}';
4003
+ if (typeof name === 'string') {
4004
+ const executionStatus = getToolCallExecutionStatus(content);
4005
+ executedToolCalls.push({
4006
+ arguments: typeof rawArguments === 'string' ? rawArguments : '',
4007
+ ...(executionStatus.errorMessage ? {
4008
+ errorMessage: executionStatus.errorMessage
4009
+ } : {}),
4010
+ id,
4011
+ name,
4012
+ ...(executionStatus.status ? {
4013
+ status: executionStatus.status
4014
+ } : {})
4015
+ });
4016
+ }
3826
4017
  openAiInput.push({
3827
4018
  call_id: id,
3828
4019
  output: content,
3829
4020
  type: 'function_call_output'
3830
4021
  });
3831
4022
  }
4023
+ if (onToolCallsChunk && executedToolCalls.length > 0) {
4024
+ await onToolCallsChunk(executedToolCalls);
4025
+ }
3832
4026
  continue;
3833
4027
  }
3834
4028
  const content = Reflect.get(message, 'content');
@@ -4218,7 +4412,8 @@ const getAiResponse = async ({
4218
4412
  selectedModelId,
4219
4413
  streamingEnabled = false,
4220
4414
  useMockApi,
4221
- userText
4415
+ userText,
4416
+ webSearchEnabled = false
4222
4417
  }) => {
4223
4418
  let text = '';
4224
4419
  const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
@@ -4249,7 +4444,8 @@ const getAiResponse = async ({
4249
4444
  ...(onToolCallsChunk ? {
4250
4445
  onToolCallsChunk
4251
4446
  } : {}),
4252
- stream: streamingEnabled
4447
+ stream: streamingEnabled,
4448
+ webSearchEnabled
4253
4449
  });
4254
4450
  if (result.type === 'success') {
4255
4451
  const {
@@ -4576,10 +4772,53 @@ const appendMessageToSelectedSession = (sessions, selectedSessionId, message) =>
4576
4772
  };
4577
4773
  });
4578
4774
  };
4775
+ const hasLegacyStreamingToolCalls = parsed => {
4776
+ if (!parsed || typeof parsed !== 'object') {
4777
+ return false;
4778
+ }
4779
+ const choices = Reflect.get(parsed, 'choices');
4780
+ if (!Array.isArray(choices) || choices.length === 0) {
4781
+ return false;
4782
+ }
4783
+ const firstChoice = choices[0];
4784
+ if (!firstChoice || typeof firstChoice !== 'object') {
4785
+ return false;
4786
+ }
4787
+ const delta = Reflect.get(firstChoice, 'delta');
4788
+ if (!delta || typeof delta !== 'object') {
4789
+ return false;
4790
+ }
4791
+ const toolCalls = Reflect.get(delta, 'tool_calls');
4792
+ return Array.isArray(toolCalls) && toolCalls.length > 0;
4793
+ };
4794
+ const isStreamingFunctionCallEvent = parsed => {
4795
+ if (hasLegacyStreamingToolCalls(parsed)) {
4796
+ return true;
4797
+ }
4798
+ if (!parsed || typeof parsed !== 'object') {
4799
+ return false;
4800
+ }
4801
+ const type = Reflect.get(parsed, 'type');
4802
+ if (type === 'response.function_call_arguments.delta' || type === 'response.function_call_arguments.done') {
4803
+ return true;
4804
+ }
4805
+ if (type !== 'response.output_item.added' && type !== 'response.output_item.done') {
4806
+ return false;
4807
+ }
4808
+ const item = Reflect.get(parsed, 'item');
4809
+ if (!item || typeof item !== 'object') {
4810
+ return false;
4811
+ }
4812
+ return Reflect.get(item, 'type') === 'function_call';
4813
+ };
4814
+ const getSseEventType = value => {
4815
+ return value && typeof value === 'object' && Reflect.get(value, 'type') === 'response.completed' ? 'sse-response-completed' : 'sse-response-part';
4816
+ };
4579
4817
  const handleSubmit = async state => {
4580
4818
  const {
4581
4819
  assetDir,
4582
4820
  composerValue,
4821
+ emitStreamingFunctionCallEvents,
4583
4822
  mockAiResponseDelay,
4584
4823
  mockApiCommandId,
4585
4824
  models,
@@ -4595,7 +4834,8 @@ const handleSubmit = async state => {
4595
4834
  sessions,
4596
4835
  streamingEnabled,
4597
4836
  useMockApi,
4598
- viewMode
4837
+ viewMode,
4838
+ webSearchEnabled
4599
4839
  } = state;
4600
4840
  const userText = composerValue.trim();
4601
4841
  if (!userText) {
@@ -4705,10 +4945,14 @@ const handleSubmit = async state => {
4705
4945
  mockApiCommandId,
4706
4946
  models,
4707
4947
  onDataEvent: async value => {
4948
+ if (!emitStreamingFunctionCallEvents && isStreamingFunctionCallEvent(value)) {
4949
+ return;
4950
+ }
4951
+ const sseEventType = getSseEventType(value);
4708
4952
  await appendChatViewEvent({
4709
4953
  sessionId: optimisticState.selectedSessionId,
4710
4954
  timestamp: new Date().toISOString(),
4711
- type: 'sse-response-part',
4955
+ type: sseEventType,
4712
4956
  value
4713
4957
  });
4714
4958
  },
@@ -4735,7 +4979,8 @@ const handleSubmit = async state => {
4735
4979
  selectedModelId,
4736
4980
  streamingEnabled,
4737
4981
  useMockApi,
4738
- userText
4982
+ userText,
4983
+ webSearchEnabled
4739
4984
  });
4740
4985
  const {
4741
4986
  latestState
@@ -4916,6 +5161,13 @@ const handleClickNew = async state => {
4916
5161
  return createSession(state);
4917
5162
  };
4918
5163
 
5164
+ const handleClickReadFile = async uri => {
5165
+ if (!uri) {
5166
+ return;
5167
+ }
5168
+ await invoke('Main.openUri', uri);
5169
+ };
5170
+
4919
5171
  const handleClickSessionDebug = async state => {
4920
5172
  await invoke('Main.openUri', `chat-debug://${state.selectedSessionId}`);
4921
5173
  return state;
@@ -5049,6 +5301,11 @@ const handleKeyDown = async (state, key, shiftKey) => {
5049
5301
  return handleSubmit(submitState);
5050
5302
  };
5051
5303
 
5304
+ const handleMessagesContextMenu = async state => {
5305
+ await invoke('ContextMenu.show', 1234);
5306
+ return state;
5307
+ };
5308
+
5052
5309
  const handleModelChange = async (state, value) => {
5053
5310
  return {
5054
5311
  ...state,
@@ -5186,6 +5443,15 @@ const getSavedViewMode = savedState => {
5186
5443
  return viewMode;
5187
5444
  };
5188
5445
 
5446
+ const loadEmitStreamingFunctionCallEvents = async () => {
5447
+ try {
5448
+ const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
5449
+ return typeof savedEmitStreamingFunctionCallEvents === 'boolean' ? savedEmitStreamingFunctionCallEvents : false;
5450
+ } catch {
5451
+ return false;
5452
+ }
5453
+ };
5454
+
5189
5455
  const loadOpenApiApiKey = async () => {
5190
5456
  try {
5191
5457
  const savedOpenApiKey = await get('secrets.openApiKey');
@@ -5233,9 +5499,11 @@ const loadStreamingEnabled = async () => {
5233
5499
  const loadPreferences = async () => {
5234
5500
  const openApiApiKey = await loadOpenApiApiKey();
5235
5501
  const openRouterApiKey = await loadOpenRouterApiKey();
5502
+ const emitStreamingFunctionCallEvents = await loadEmitStreamingFunctionCallEvents();
5236
5503
  const streamingEnabled = await loadStreamingEnabled();
5237
5504
  const passIncludeObfuscation = await loadPassIncludeObfuscation();
5238
5505
  return {
5506
+ emitStreamingFunctionCallEvents,
5239
5507
  openApiApiKey,
5240
5508
  openRouterApiKey,
5241
5509
  passIncludeObfuscation,
@@ -5270,6 +5538,7 @@ const loadContent = async (state, savedState) => {
5270
5538
  const savedSelectedModelId = getSavedSelectedModelId(savedState);
5271
5539
  const savedViewMode = getSavedViewMode(savedState);
5272
5540
  const {
5541
+ emitStreamingFunctionCallEvents,
5273
5542
  openApiApiKey,
5274
5543
  openRouterApiKey,
5275
5544
  passIncludeObfuscation,
@@ -5302,6 +5571,7 @@ const loadContent = async (state, savedState) => {
5302
5571
  return {
5303
5572
  ...state,
5304
5573
  chatListScrollTop,
5574
+ emitStreamingFunctionCallEvents,
5305
5575
  initial: false,
5306
5576
  messagesScrollTop,
5307
5577
  openApiApiKey,
@@ -5378,6 +5648,34 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
5378
5648
  --ChatMessageFontSize: ${chatMessageFontSize}px;
5379
5649
  --ChatMessageLineHeight: ${chatMessageLineHeight}px;
5380
5650
  --ChatMessageFontFamily: ${chatMessageFontFamily};
5651
+ }
5652
+
5653
+ .ChatToolCalls {
5654
+ position: relative;
5655
+ border: 1px solid var(--vscode-editorWidget-border);
5656
+ border-radius: 4px;
5657
+ margin-bottom: 8px;
5658
+ padding: 10px 8px 6px;
5659
+ background: var(--vscode-editorWidget-background);
5660
+ }
5661
+
5662
+ .ChatToolCallsLabel {
5663
+ position: absolute;
5664
+ top: -8px;
5665
+ left: 8px;
5666
+ padding: 0 4px;
5667
+ border-radius: 3px;
5668
+ background: var(--vscode-editor-background);
5669
+ color: var(--vscode-descriptionForeground);
5670
+ font-size: 10px;
5671
+ line-height: 14px;
5672
+ text-transform: lowercase;
5673
+ letter-spacing: 0.02em;
5674
+ }
5675
+
5676
+ .ChatToolCallReadFileLink {
5677
+ color: var(--vscode-textLink-foreground);
5678
+ text-decoration: underline;
5381
5679
  }`;
5382
5680
  };
5383
5681
 
@@ -5433,6 +5731,7 @@ const ChatHeader = 'ChatHeader';
5433
5731
  const Button = 'Button';
5434
5732
  const ButtonPrimary = 'ButtonPrimary';
5435
5733
  const ButtonSecondary = 'ButtonSecondary';
5734
+ const FileIcon = 'FileIcon';
5436
5735
  const IconButton = 'IconButton';
5437
5736
  const InputBox = 'InputBox';
5438
5737
  const Label = 'Label';
@@ -5444,6 +5743,9 @@ const ChatListItemLabel = 'ChatListItemLabel';
5444
5743
  const Markdown = 'Markdown';
5445
5744
  const Message = 'Message';
5446
5745
  const ChatMessageContent = 'ChatMessageContent';
5746
+ const ChatToolCalls = 'ChatToolCalls';
5747
+ const ChatToolCallsLabel = 'ChatToolCallsLabel';
5748
+ const ChatToolCallReadFileLink = 'ChatToolCallReadFileLink';
5447
5749
  const ChatOrderedList = 'ChatOrderedList';
5448
5750
  const ChatOrderedListItem = 'ChatOrderedListItem';
5449
5751
  const MessageUser = 'MessageUser';
@@ -5473,6 +5775,8 @@ const HandleModelChange = 20;
5473
5775
  const HandleChatListScroll = 21;
5474
5776
  const HandleMessagesScroll = 22;
5475
5777
  const HandleClickSessionDebug = 23;
5778
+ const HandleClickReadFile = 24;
5779
+ const HandleMessagesContextMenu = 25;
5476
5780
 
5477
5781
  const getModelLabel = model => {
5478
5782
  if (model.provider === 'openRouter') {
@@ -5787,13 +6091,108 @@ const getToolCallArgumentPreview = rawArguments => {
5787
6091
  return rawArguments;
5788
6092
  };
5789
6093
 
6094
+ const RE_QUERY_OR_HASH = /[?#].*$/;
6095
+ const RE_TRAILING_SLASH = /\/$/;
6096
+ const getFileNameFromUri = uri => {
6097
+ const stripped = uri.replace(RE_QUERY_OR_HASH, '').replace(RE_TRAILING_SLASH, '');
6098
+ const slashIndex = Math.max(stripped.lastIndexOf('/'), stripped.lastIndexOf('\\\\'));
6099
+ const fileName = slashIndex === -1 ? stripped : stripped.slice(slashIndex + 1);
6100
+ return fileName || uri;
6101
+ };
6102
+
6103
+ const getReadFileTarget = rawArguments => {
6104
+ let parsed;
6105
+ try {
6106
+ parsed = JSON.parse(rawArguments);
6107
+ } catch {
6108
+ return undefined;
6109
+ }
6110
+ if (!parsed || typeof parsed !== 'object') {
6111
+ return undefined;
6112
+ }
6113
+ const uri = Reflect.get(parsed, 'uri');
6114
+ const path = Reflect.get(parsed, 'path');
6115
+ const uriValue = typeof uri === 'string' ? uri : '';
6116
+ const pathValue = typeof path === 'string' ? path : '';
6117
+ const title = uriValue || pathValue;
6118
+ if (!title) {
6119
+ return undefined;
6120
+ }
6121
+ // `read_file` tool calls usually provide a relative `path`; pass it through so UI clicks can open the file.
6122
+ const clickableUri = uriValue || pathValue;
6123
+ return {
6124
+ clickableUri,
6125
+ title
6126
+ };
6127
+ };
6128
+
6129
+ const getToolCallStatusLabel$1 = toolCall => {
6130
+ if (toolCall.status === 'not-found') {
6131
+ return ' (not-found)';
6132
+ }
6133
+ if (toolCall.status === 'error') {
6134
+ if (toolCall.errorMessage) {
6135
+ return ` (error: ${toolCall.errorMessage})`;
6136
+ }
6137
+ return ' (error)';
6138
+ }
6139
+ return '';
6140
+ };
6141
+
6142
+ const getToolCallReadFileVirtualDom = toolCall => {
6143
+ const target = getReadFileTarget(toolCall.arguments);
6144
+ if (!target) {
6145
+ return [];
6146
+ }
6147
+ const fileName = getFileNameFromUri(target.title);
6148
+ const toolNameLabel = `${toolCall.name} `;
6149
+ const statusLabel = getToolCallStatusLabel$1(toolCall);
6150
+ const fileNameClickableProps = target.clickableUri ? {
6151
+ 'data-uri': target.clickableUri,
6152
+ onClick: HandleClickReadFile
6153
+ } : {};
6154
+ return [{
6155
+ childCount: statusLabel ? 4 : 3,
6156
+ className: ChatOrderedListItem,
6157
+ title: target.title,
6158
+ type: Li
6159
+ }, {
6160
+ childCount: 0,
6161
+ className: FileIcon,
6162
+ type: Div
6163
+ }, text(toolNameLabel), {
6164
+ childCount: 1,
6165
+ className: ChatToolCallReadFileLink,
6166
+ ...fileNameClickableProps,
6167
+ type: Span
6168
+ }, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
6169
+ };
6170
+
6171
+ const getToolCallStatusLabel = toolCall => {
6172
+ if (toolCall.status === 'not-found') {
6173
+ return ' (not-found)';
6174
+ }
6175
+ if (toolCall.status === 'error') {
6176
+ if (toolCall.errorMessage) {
6177
+ return ` (error: ${toolCall.errorMessage})`;
6178
+ }
6179
+ return ' (error)';
6180
+ }
6181
+ return '';
6182
+ };
5790
6183
  const getToolCallDom = toolCall => {
6184
+ if (toolCall.name === 'read_file') {
6185
+ const virtualDom = getToolCallReadFileVirtualDom(toolCall);
6186
+ if (virtualDom.length > 0) {
6187
+ return virtualDom;
6188
+ }
6189
+ }
5791
6190
  const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
5792
- const label = `${toolCall.name} ${argumentPreview}`;
6191
+ const label = `${toolCall.name} ${argumentPreview}${getToolCallStatusLabel(toolCall)}`;
5793
6192
  return [{
5794
6193
  childCount: 1,
5795
- className: Markdown,
5796
- type: P
6194
+ className: ChatOrderedListItem,
6195
+ type: Li
5797
6196
  }, text(label)];
5798
6197
  };
5799
6198
 
@@ -5802,9 +6201,17 @@ const getToolCallsDom = message => {
5802
6201
  return [];
5803
6202
  }
5804
6203
  return [{
5805
- childCount: message.toolCalls.length,
5806
- className: 'ToolCallsContainer',
6204
+ childCount: 2,
6205
+ className: ChatToolCalls,
5807
6206
  type: Div$1
6207
+ }, {
6208
+ childCount: 1,
6209
+ className: ChatToolCallsLabel,
6210
+ type: Div$1
6211
+ }, text('tools'), {
6212
+ childCount: message.toolCalls.length,
6213
+ className: ChatOrderedList,
6214
+ type: Ol$1
5808
6215
  }, ...message.toolCalls.flatMap(getToolCallDom)];
5809
6216
  };
5810
6217
 
@@ -5907,7 +6314,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
5907
6314
  childCount: extraChildCount,
5908
6315
  className: ChatMessageContent,
5909
6316
  type: Div
5910
- }, ...messageDom, ...toolCallsDom, ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
6317
+ }, ...toolCallsDom, ...messageDom, ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
5911
6318
  };
5912
6319
 
5913
6320
  const getEmptyMessagesDom = () => {
@@ -5925,6 +6332,7 @@ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = ''
5925
6332
  return [{
5926
6333
  childCount: messages.length,
5927
6334
  className: 'ChatMessages',
6335
+ onContextMenu: HandleMessagesContextMenu,
5928
6336
  onScroll: HandleMessagesScroll,
5929
6337
  scrollTop: messagesScrollTop,
5930
6338
  type: Div
@@ -6139,6 +6547,9 @@ const renderEventListeners = () => {
6139
6547
  }, {
6140
6548
  name: HandleClick,
6141
6549
  params: ['handleClick', TargetName, 'event.target.dataset.id']
6550
+ }, {
6551
+ name: HandleClickReadFile,
6552
+ params: ['handleClickReadFile', 'event.target.dataset.uri']
6142
6553
  }, {
6143
6554
  name: HandleClickDelete,
6144
6555
  params: ['handleClickDelete', 'event.target.dataset.id']
@@ -6172,6 +6583,10 @@ const renderEventListeners = () => {
6172
6583
  }, {
6173
6584
  name: HandleMessagesScroll,
6174
6585
  params: ['handleMessagesScroll', 'event.target.scrollTop']
6586
+ }, {
6587
+ name: HandleMessagesContextMenu,
6588
+ params: ['handleMessagesContextMenu'],
6589
+ preventDefault: true
6175
6590
  }, {
6176
6591
  name: HandleFocus,
6177
6592
  params: ['handleInputFocus', TargetName]
@@ -6264,6 +6679,13 @@ const setChatList = state => {
6264
6679
  };
6265
6680
  };
6266
6681
 
6682
+ const setEmitStreamingFunctionCallEvents = (state, emitStreamingFunctionCallEvents) => {
6683
+ return {
6684
+ ...state,
6685
+ emitStreamingFunctionCallEvents
6686
+ };
6687
+ };
6688
+
6267
6689
  const setStreamingEnabled = (state, streamingEnabled) => {
6268
6690
  return {
6269
6691
  ...state,
@@ -6302,11 +6724,13 @@ const commandMap = {
6302
6724
  'Chat.handleClickDelete': wrapCommand(handleClickDelete),
6303
6725
  'Chat.handleClickList': wrapCommand(handleClickList),
6304
6726
  'Chat.handleClickNew': wrapCommand(handleClickNew),
6727
+ 'Chat.handleClickReadFile': handleClickReadFile,
6305
6728
  'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
6306
6729
  'Chat.handleClickSettings': handleClickSettings,
6307
6730
  'Chat.handleInput': wrapCommand(handleInput),
6308
6731
  'Chat.handleInputFocus': wrapCommand(handleInputFocus),
6309
6732
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
6733
+ 'Chat.handleMessagesContextMenu': wrapCommand(handleMessagesContextMenu),
6310
6734
  'Chat.handleMessagesScroll': wrapCommand(handleMessagesScroll),
6311
6735
  'Chat.handleModelChange': wrapCommand(handleModelChange),
6312
6736
  'Chat.handleSubmit': wrapCommand(handleSubmit),
@@ -6325,6 +6749,7 @@ const commandMap = {
6325
6749
  'Chat.resize': wrapCommand(resize),
6326
6750
  'Chat.saveState': wrapGetter(saveState),
6327
6751
  'Chat.setChatList': wrapCommand(setChatList),
6752
+ 'Chat.setEmitStreamingFunctionCallEvents': wrapCommand(setEmitStreamingFunctionCallEvents),
6328
6753
  'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
6329
6754
  'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
6330
6755
  'Chat.terminate': terminate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",