@lvce-editor/chat-view 2.6.0 → 2.7.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.
@@ -1078,7 +1078,7 @@ const {
1078
1078
  set: set$3
1079
1079
  } = create$2(6002);
1080
1080
 
1081
- const Div$1 = 4;
1081
+ const Ol$1 = 49;
1082
1082
 
1083
1083
  const ClientX = 'event.clientX';
1084
1084
  const ClientY = 'event.clientY';
@@ -1469,6 +1469,7 @@ const createDefaultState = () => {
1469
1469
  composerHeight: composerLineHeight + 8,
1470
1470
  composerLineHeight,
1471
1471
  composerValue: '',
1472
+ emitStreamingFunctionCallEvents: false,
1472
1473
  errorCount: 0,
1473
1474
  focus: 'composer',
1474
1475
  focused: false,
@@ -3455,7 +3456,14 @@ const updateToolCallAccumulator = (accumulator, chunk) => {
3455
3456
  toolCalls
3456
3457
  };
3457
3458
  };
3458
- const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished) => {
3459
+ const getResponseFunctionCallsFromStreamingAccumulator = toolCallAccumulator => {
3460
+ return Object.entries(toolCallAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => typeof toolCall.id === 'string' && !!toolCall.id && !!toolCall.name).map(toolCall => ({
3461
+ arguments: toolCall.arguments,
3462
+ callId: toolCall.id,
3463
+ name: toolCall.name
3464
+ }));
3465
+ };
3466
+ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDataEvent) => {
3459
3467
  if (!response.body) {
3460
3468
  return {
3461
3469
  details: 'request-failed',
@@ -3467,17 +3475,9 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3467
3475
  let remainder = '';
3468
3476
  let text = '';
3469
3477
  let done = false;
3470
- let finishedNotified = false;
3471
3478
  let toolCallAccumulator = {};
3472
- const notifyFinished = async () => {
3473
- if (finishedNotified) {
3474
- return;
3475
- }
3476
- finishedNotified = true;
3477
- if (onEventStreamFinished) {
3478
- await onEventStreamFinished();
3479
- }
3480
- };
3479
+ let responseId;
3480
+ let completedResponseFunctionCalls = [];
3481
3481
  const emitToolCallAccumulator = async () => {
3482
3482
  if (!onToolCallsChunk) {
3483
3483
  return;
@@ -3497,7 +3497,25 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3497
3497
  }
3498
3498
  const eventType = Reflect.get(parsed, 'type');
3499
3499
  if (eventType === 'response.completed') {
3500
- await notifyFinished();
3500
+ const response = Reflect.get(parsed, 'response');
3501
+ if (response && typeof response === 'object') {
3502
+ const parsedResponseId = Reflect.get(response, 'id');
3503
+ if (typeof parsedResponseId === 'string' && parsedResponseId) {
3504
+ responseId = parsedResponseId;
3505
+ }
3506
+ completedResponseFunctionCalls = getResponseFunctionCalls(response);
3507
+ }
3508
+ return;
3509
+ }
3510
+ if (eventType === 'response.created' || eventType === 'response.in_progress') {
3511
+ const response = Reflect.get(parsed, 'response');
3512
+ if (!response || typeof response !== 'object') {
3513
+ return;
3514
+ }
3515
+ const parsedResponseId = Reflect.get(response, 'id');
3516
+ if (typeof parsedResponseId === 'string' && parsedResponseId) {
3517
+ responseId = parsedResponseId;
3518
+ }
3501
3519
  return;
3502
3520
  }
3503
3521
  if (eventType === 'response.output_text.delta') {
@@ -3538,6 +3556,38 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3538
3556
  await emitToolCallAccumulator();
3539
3557
  return;
3540
3558
  }
3559
+ if (eventType === 'response.output_item.done') {
3560
+ const outputIndex = Reflect.get(parsed, 'output_index');
3561
+ const item = Reflect.get(parsed, 'item');
3562
+ if (typeof outputIndex !== 'number' || !item || typeof item !== 'object') {
3563
+ return;
3564
+ }
3565
+ const itemType = Reflect.get(item, 'type');
3566
+ if (itemType !== 'function_call') {
3567
+ return;
3568
+ }
3569
+ const callId = Reflect.get(item, 'call_id');
3570
+ const name = Reflect.get(item, 'name');
3571
+ const rawArguments = Reflect.get(item, 'arguments');
3572
+ const current = toolCallAccumulator[outputIndex] || {
3573
+ arguments: '',
3574
+ name: ''
3575
+ };
3576
+ toolCallAccumulator = {
3577
+ ...toolCallAccumulator,
3578
+ [outputIndex]: {
3579
+ arguments: typeof rawArguments === 'string' ? rawArguments : current.arguments,
3580
+ ...(typeof callId === 'string' ? {
3581
+ id: callId
3582
+ } : current.id ? {
3583
+ id: current.id
3584
+ } : {}),
3585
+ name: typeof name === 'string' && name ? name : current.name
3586
+ }
3587
+ };
3588
+ await emitToolCallAccumulator();
3589
+ return;
3590
+ }
3541
3591
  if (eventType === 'response.function_call_arguments.delta' || eventType === 'response.function_call_arguments.done') {
3542
3592
  const outputIndex = Reflect.get(parsed, 'output_index');
3543
3593
  if (typeof outputIndex !== 'number') {
@@ -3622,7 +3672,6 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3622
3672
  }
3623
3673
  for (const line of dataLines) {
3624
3674
  if (line === '[DONE]') {
3625
- await notifyFinished();
3626
3675
  done = true;
3627
3676
  break;
3628
3677
  }
@@ -3640,7 +3689,6 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3640
3689
  const dataLines = parseSseEvent(remainder);
3641
3690
  for (const line of dataLines) {
3642
3691
  if (line === '[DONE]') {
3643
- await notifyFinished();
3644
3692
  continue;
3645
3693
  }
3646
3694
  let parsed;
@@ -3652,8 +3700,12 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
3652
3700
  await handleParsedStreamEvent(parsed);
3653
3701
  }
3654
3702
  }
3655
- await notifyFinished();
3703
+ const responseFunctionCalls = completedResponseFunctionCalls.length > 0 ? completedResponseFunctionCalls : getResponseFunctionCallsFromStreamingAccumulator(toolCallAccumulator);
3656
3704
  return {
3705
+ ...(responseId ? {
3706
+ responseId
3707
+ } : {}),
3708
+ responseFunctionCalls,
3657
3709
  text,
3658
3710
  type: 'success'
3659
3711
  };
@@ -3745,7 +3797,20 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
3745
3797
  };
3746
3798
  }
3747
3799
  if (stream) {
3748
- return parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished);
3800
+ const streamResult = await parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent);
3801
+ if (streamResult.type !== 'success') {
3802
+ return streamResult;
3803
+ }
3804
+ if (streamResult.responseId) {
3805
+ previousResponseId = streamResult.responseId;
3806
+ }
3807
+ if (onEventStreamFinished) {
3808
+ await onEventStreamFinished();
3809
+ }
3810
+ return {
3811
+ text: streamResult.text,
3812
+ type: 'success'
3813
+ };
3749
3814
  }
3750
3815
  let parsed;
3751
3816
  try {
@@ -4576,10 +4641,53 @@ const appendMessageToSelectedSession = (sessions, selectedSessionId, message) =>
4576
4641
  };
4577
4642
  });
4578
4643
  };
4644
+ const hasLegacyStreamingToolCalls = parsed => {
4645
+ if (!parsed || typeof parsed !== 'object') {
4646
+ return false;
4647
+ }
4648
+ const choices = Reflect.get(parsed, 'choices');
4649
+ if (!Array.isArray(choices) || choices.length === 0) {
4650
+ return false;
4651
+ }
4652
+ const firstChoice = choices[0];
4653
+ if (!firstChoice || typeof firstChoice !== 'object') {
4654
+ return false;
4655
+ }
4656
+ const delta = Reflect.get(firstChoice, 'delta');
4657
+ if (!delta || typeof delta !== 'object') {
4658
+ return false;
4659
+ }
4660
+ const toolCalls = Reflect.get(delta, 'tool_calls');
4661
+ return Array.isArray(toolCalls) && toolCalls.length > 0;
4662
+ };
4663
+ const isStreamingFunctionCallEvent = parsed => {
4664
+ if (hasLegacyStreamingToolCalls(parsed)) {
4665
+ return true;
4666
+ }
4667
+ if (!parsed || typeof parsed !== 'object') {
4668
+ return false;
4669
+ }
4670
+ const type = Reflect.get(parsed, 'type');
4671
+ if (type === 'response.function_call_arguments.delta' || type === 'response.function_call_arguments.done') {
4672
+ return true;
4673
+ }
4674
+ if (type !== 'response.output_item.added' && type !== 'response.output_item.done') {
4675
+ return false;
4676
+ }
4677
+ const item = Reflect.get(parsed, 'item');
4678
+ if (!item || typeof item !== 'object') {
4679
+ return false;
4680
+ }
4681
+ return Reflect.get(item, 'type') === 'function_call';
4682
+ };
4683
+ const getSseEventType = value => {
4684
+ return value && typeof value === 'object' && Reflect.get(value, 'type') === 'response.completed' ? 'sse-response-completed' : 'sse-response-part';
4685
+ };
4579
4686
  const handleSubmit = async state => {
4580
4687
  const {
4581
4688
  assetDir,
4582
4689
  composerValue,
4690
+ emitStreamingFunctionCallEvents,
4583
4691
  mockAiResponseDelay,
4584
4692
  mockApiCommandId,
4585
4693
  models,
@@ -4705,10 +4813,14 @@ const handleSubmit = async state => {
4705
4813
  mockApiCommandId,
4706
4814
  models,
4707
4815
  onDataEvent: async value => {
4816
+ if (!emitStreamingFunctionCallEvents && isStreamingFunctionCallEvent(value)) {
4817
+ return;
4818
+ }
4819
+ const sseEventType = getSseEventType(value);
4708
4820
  await appendChatViewEvent({
4709
4821
  sessionId: optimisticState.selectedSessionId,
4710
4822
  timestamp: new Date().toISOString(),
4711
- type: 'sse-response-part',
4823
+ type: sseEventType,
4712
4824
  value
4713
4825
  });
4714
4826
  },
@@ -4916,6 +5028,13 @@ const handleClickNew = async state => {
4916
5028
  return createSession(state);
4917
5029
  };
4918
5030
 
5031
+ const handleClickReadFile = async uri => {
5032
+ if (!uri) {
5033
+ return;
5034
+ }
5035
+ await invoke('Main.openUri', uri);
5036
+ };
5037
+
4919
5038
  const handleClickSessionDebug = async state => {
4920
5039
  await invoke('Main.openUri', `chat-debug://${state.selectedSessionId}`);
4921
5040
  return state;
@@ -5186,6 +5305,15 @@ const getSavedViewMode = savedState => {
5186
5305
  return viewMode;
5187
5306
  };
5188
5307
 
5308
+ const loadEmitStreamingFunctionCallEvents = async () => {
5309
+ try {
5310
+ const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
5311
+ return typeof savedEmitStreamingFunctionCallEvents === 'boolean' ? savedEmitStreamingFunctionCallEvents : false;
5312
+ } catch {
5313
+ return false;
5314
+ }
5315
+ };
5316
+
5189
5317
  const loadOpenApiApiKey = async () => {
5190
5318
  try {
5191
5319
  const savedOpenApiKey = await get('secrets.openApiKey');
@@ -5233,9 +5361,11 @@ const loadStreamingEnabled = async () => {
5233
5361
  const loadPreferences = async () => {
5234
5362
  const openApiApiKey = await loadOpenApiApiKey();
5235
5363
  const openRouterApiKey = await loadOpenRouterApiKey();
5364
+ const emitStreamingFunctionCallEvents = await loadEmitStreamingFunctionCallEvents();
5236
5365
  const streamingEnabled = await loadStreamingEnabled();
5237
5366
  const passIncludeObfuscation = await loadPassIncludeObfuscation();
5238
5367
  return {
5368
+ emitStreamingFunctionCallEvents,
5239
5369
  openApiApiKey,
5240
5370
  openRouterApiKey,
5241
5371
  passIncludeObfuscation,
@@ -5270,6 +5400,7 @@ const loadContent = async (state, savedState) => {
5270
5400
  const savedSelectedModelId = getSavedSelectedModelId(savedState);
5271
5401
  const savedViewMode = getSavedViewMode(savedState);
5272
5402
  const {
5403
+ emitStreamingFunctionCallEvents,
5273
5404
  openApiApiKey,
5274
5405
  openRouterApiKey,
5275
5406
  passIncludeObfuscation,
@@ -5302,6 +5433,7 @@ const loadContent = async (state, savedState) => {
5302
5433
  return {
5303
5434
  ...state,
5304
5435
  chatListScrollTop,
5436
+ emitStreamingFunctionCallEvents,
5305
5437
  initial: false,
5306
5438
  messagesScrollTop,
5307
5439
  openApiApiKey,
@@ -5433,6 +5565,7 @@ const ChatHeader = 'ChatHeader';
5433
5565
  const Button = 'Button';
5434
5566
  const ButtonPrimary = 'ButtonPrimary';
5435
5567
  const ButtonSecondary = 'ButtonSecondary';
5568
+ const FileIcon = 'FileIcon';
5436
5569
  const IconButton = 'IconButton';
5437
5570
  const InputBox = 'InputBox';
5438
5571
  const Label = 'Label';
@@ -5473,6 +5606,7 @@ const HandleModelChange = 20;
5473
5606
  const HandleChatListScroll = 21;
5474
5607
  const HandleMessagesScroll = 22;
5475
5608
  const HandleClickSessionDebug = 23;
5609
+ const HandleClickReadFile = 24;
5476
5610
 
5477
5611
  const getModelLabel = model => {
5478
5612
  if (model.provider === 'openRouter') {
@@ -5787,13 +5921,80 @@ const getToolCallArgumentPreview = rawArguments => {
5787
5921
  return rawArguments;
5788
5922
  };
5789
5923
 
5924
+ const getFileNameFromUri = uri => {
5925
+ const stripped = uri.replace(/[?#].*$/, '').replace(/\/$/, '');
5926
+ const slashIndex = Math.max(stripped.lastIndexOf('/'), stripped.lastIndexOf('\\\\'));
5927
+ const fileName = slashIndex === -1 ? stripped : stripped.slice(slashIndex + 1);
5928
+ return fileName || uri;
5929
+ };
5930
+
5931
+ const getReadFileTarget = rawArguments => {
5932
+ let parsed;
5933
+ try {
5934
+ parsed = JSON.parse(rawArguments);
5935
+ } catch {
5936
+ return undefined;
5937
+ }
5938
+ if (!parsed || typeof parsed !== 'object') {
5939
+ return undefined;
5940
+ }
5941
+ const uri = Reflect.get(parsed, 'uri');
5942
+ const path = Reflect.get(parsed, 'path');
5943
+ const uriValue = typeof uri === 'string' ? uri : '';
5944
+ const pathValue = typeof path === 'string' ? path : '';
5945
+ const title = uriValue || pathValue;
5946
+ if (!title) {
5947
+ return undefined;
5948
+ }
5949
+ const clickableUri = uriValue || (pathValue.startsWith('file://') ? pathValue : '');
5950
+ return {
5951
+ clickableUri,
5952
+ title
5953
+ };
5954
+ };
5955
+
5956
+ const getToolCallReadFileVirtualDom = toolCall => {
5957
+ const target = getReadFileTarget(toolCall.arguments);
5958
+ if (!target) {
5959
+ return [];
5960
+ }
5961
+ const fileName = getFileNameFromUri(target.title);
5962
+ const clickableProps = target.clickableUri ? {
5963
+ 'data-uri': target.clickableUri,
5964
+ onClick: HandleClickReadFile
5965
+ } : {};
5966
+ return [{
5967
+ childCount: 2,
5968
+ className: ChatOrderedListItem,
5969
+ ...clickableProps,
5970
+ title: target.title,
5971
+ type: Li
5972
+ }, {
5973
+ childCount: 0,
5974
+ className: FileIcon,
5975
+ ...clickableProps,
5976
+ type: Div
5977
+ }, {
5978
+ childCount: 1,
5979
+ ...clickableProps,
5980
+ style: 'color: var(--vscode-textLink-foreground); text-decoration: underline;',
5981
+ type: Span
5982
+ }, text(fileName)];
5983
+ };
5984
+
5790
5985
  const getToolCallDom = toolCall => {
5986
+ if (toolCall.name === 'read_file') {
5987
+ const virtualDom = getToolCallReadFileVirtualDom(toolCall);
5988
+ if (virtualDom.length > 0) {
5989
+ return virtualDom;
5990
+ }
5991
+ }
5791
5992
  const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
5792
5993
  const label = `${toolCall.name} ${argumentPreview}`;
5793
5994
  return [{
5794
5995
  childCount: 1,
5795
- className: Markdown,
5796
- type: P
5996
+ className: ChatOrderedListItem,
5997
+ type: Li
5797
5998
  }, text(label)];
5798
5999
  };
5799
6000
 
@@ -5803,8 +6004,8 @@ const getToolCallsDom = message => {
5803
6004
  }
5804
6005
  return [{
5805
6006
  childCount: message.toolCalls.length,
5806
- className: 'ToolCallsContainer',
5807
- type: Div$1
6007
+ className: ChatOrderedList,
6008
+ type: Ol$1
5808
6009
  }, ...message.toolCalls.flatMap(getToolCallDom)];
5809
6010
  };
5810
6011
 
@@ -5907,7 +6108,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
5907
6108
  childCount: extraChildCount,
5908
6109
  className: ChatMessageContent,
5909
6110
  type: Div
5910
- }, ...messageDom, ...toolCallsDom, ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
6111
+ }, ...toolCallsDom, ...messageDom, ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
5911
6112
  };
5912
6113
 
5913
6114
  const getEmptyMessagesDom = () => {
@@ -6139,6 +6340,9 @@ const renderEventListeners = () => {
6139
6340
  }, {
6140
6341
  name: HandleClick,
6141
6342
  params: ['handleClick', TargetName, 'event.target.dataset.id']
6343
+ }, {
6344
+ name: HandleClickReadFile,
6345
+ params: ['handleClickReadFile', 'event.target.dataset.uri']
6142
6346
  }, {
6143
6347
  name: HandleClickDelete,
6144
6348
  params: ['handleClickDelete', 'event.target.dataset.id']
@@ -6264,6 +6468,13 @@ const setChatList = state => {
6264
6468
  };
6265
6469
  };
6266
6470
 
6471
+ const setEmitStreamingFunctionCallEvents = (state, emitStreamingFunctionCallEvents) => {
6472
+ return {
6473
+ ...state,
6474
+ emitStreamingFunctionCallEvents
6475
+ };
6476
+ };
6477
+
6267
6478
  const setStreamingEnabled = (state, streamingEnabled) => {
6268
6479
  return {
6269
6480
  ...state,
@@ -6302,6 +6513,7 @@ const commandMap = {
6302
6513
  'Chat.handleClickDelete': wrapCommand(handleClickDelete),
6303
6514
  'Chat.handleClickList': wrapCommand(handleClickList),
6304
6515
  'Chat.handleClickNew': wrapCommand(handleClickNew),
6516
+ 'Chat.handleClickReadFile': handleClickReadFile,
6305
6517
  'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
6306
6518
  'Chat.handleClickSettings': handleClickSettings,
6307
6519
  'Chat.handleInput': wrapCommand(handleInput),
@@ -6325,6 +6537,7 @@ const commandMap = {
6325
6537
  'Chat.resize': wrapCommand(resize),
6326
6538
  'Chat.saveState': wrapGetter(saveState),
6327
6539
  'Chat.setChatList': wrapCommand(setChatList),
6540
+ 'Chat.setEmitStreamingFunctionCallEvents': wrapCommand(setEmitStreamingFunctionCallEvents),
6328
6541
  'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
6329
6542
  'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
6330
6543
  '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.7.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",