@lvce-editor/chat-view 1.16.0 → 1.17.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.
@@ -1045,6 +1045,9 @@ const {
1045
1045
  invokeAndTransfer,
1046
1046
  set: set$1
1047
1047
  } = create$2(RendererWorker);
1048
+ const readFile = async uri => {
1049
+ return invoke('FileSystem.readFile', uri);
1050
+ };
1048
1051
  const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
1049
1052
  const command = 'HandleMessagePort.handleMessagePort2';
1050
1053
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
@@ -1309,6 +1312,10 @@ const getDefaultModels = () => {
1309
1312
  id: defaultModelId,
1310
1313
  name: 'test',
1311
1314
  provider: 'test'
1315
+ }, {
1316
+ id: 'openapi/gpt-5-mini',
1317
+ name: 'GPT-5 Mini',
1318
+ provider: 'openApi'
1312
1319
  }, {
1313
1320
  id: 'openapi/gpt-4o-mini',
1314
1321
  name: 'GPT-4o Mini',
@@ -2205,6 +2212,7 @@ const executeProvider = async ({
2205
2212
  };
2206
2213
 
2207
2214
  const CommandExecute = 'ExtensionHostCommand.executeCommand';
2215
+ const FileSystemWriteFile = 'ExtensionHostFileSystem.writeFile';
2208
2216
 
2209
2217
  const normalizeLimitInfo = value => {
2210
2218
  if (!value || typeof value !== 'object') {
@@ -2310,6 +2318,183 @@ const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBa
2310
2318
  }
2311
2319
  };
2312
2320
 
2321
+ const OnFileSystem = 'onFileSystem';
2322
+
2323
+ const isPathTraversalAttempt = path => {
2324
+ if (!path) {
2325
+ return false;
2326
+ }
2327
+ if (path.startsWith('/') || path.startsWith('\\')) {
2328
+ return true;
2329
+ }
2330
+ if (path.startsWith('file://')) {
2331
+ return true;
2332
+ }
2333
+ if (/^[a-zA-Z]:[\\/]/.test(path)) {
2334
+ return true;
2335
+ }
2336
+ const segments = path.split(/[\\/]/);
2337
+ return segments.includes('..');
2338
+ };
2339
+ const normalizeRelativePath = path => {
2340
+ const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
2341
+ if (segments.length === 0) {
2342
+ return '.';
2343
+ }
2344
+ return segments.join('/');
2345
+ };
2346
+ const parseToolArguments = rawArguments => {
2347
+ if (typeof rawArguments !== 'string') {
2348
+ return {};
2349
+ }
2350
+ try {
2351
+ const parsed = JSON.parse(rawArguments);
2352
+ if (!parsed || typeof parsed !== 'object') {
2353
+ return {};
2354
+ }
2355
+ return parsed;
2356
+ } catch {
2357
+ return {};
2358
+ }
2359
+ };
2360
+ const executeFileSystemCommand = async (method, params, options) => {
2361
+ return executeProvider({
2362
+ assetDir: options.assetDir,
2363
+ event: OnFileSystem,
2364
+ method,
2365
+ noProviderFoundMessage: 'No file system provider found',
2366
+ params,
2367
+ platform: options.platform
2368
+ });
2369
+ };
2370
+ const getBasicChatTools = () => {
2371
+ return [{
2372
+ function: {
2373
+ description: 'Read UTF-8 text content from a file inside the currently open workspace folder.',
2374
+ name: 'read_file',
2375
+ parameters: {
2376
+ additionalProperties: false,
2377
+ properties: {
2378
+ path: {
2379
+ description: 'Relative file path within the workspace (for example: src/index.ts).',
2380
+ type: 'string'
2381
+ }
2382
+ },
2383
+ required: ['path'],
2384
+ type: 'object'
2385
+ }
2386
+ },
2387
+ type: 'function'
2388
+ }, {
2389
+ function: {
2390
+ description: 'Write UTF-8 text content to a file inside the currently open workspace folder.',
2391
+ name: 'write_file',
2392
+ parameters: {
2393
+ additionalProperties: false,
2394
+ properties: {
2395
+ content: {
2396
+ description: 'New UTF-8 text content to write to the file.',
2397
+ type: 'string'
2398
+ },
2399
+ path: {
2400
+ description: 'Relative file path within the workspace (for example: src/index.ts).',
2401
+ type: 'string'
2402
+ }
2403
+ },
2404
+ required: ['path', 'content'],
2405
+ type: 'object'
2406
+ }
2407
+ },
2408
+ type: 'function'
2409
+ }, {
2410
+ function: {
2411
+ description: 'List direct children (files and folders) for a folder inside the currently open workspace folder.',
2412
+ name: 'list_files',
2413
+ parameters: {
2414
+ additionalProperties: false,
2415
+ properties: {
2416
+ path: {
2417
+ description: 'Relative folder path within the workspace. Use "." for the workspace root.',
2418
+ type: 'string'
2419
+ }
2420
+ },
2421
+ type: 'object'
2422
+ }
2423
+ },
2424
+ type: 'function'
2425
+ }];
2426
+ };
2427
+ const executeChatTool = async (name, rawArguments, options) => {
2428
+ const args = parseToolArguments(rawArguments);
2429
+ if (name === 'read_file') {
2430
+ const filePath = typeof args.path === 'string' ? args.path : '';
2431
+ if (!filePath || isPathTraversalAttempt(filePath)) {
2432
+ return JSON.stringify({
2433
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
2434
+ });
2435
+ }
2436
+ const normalizedPath = normalizeRelativePath(filePath);
2437
+ try {
2438
+ const content = await readFile(normalizedPath);
2439
+ return JSON.stringify({
2440
+ content,
2441
+ path: normalizedPath
2442
+ });
2443
+ } catch (error) {
2444
+ return JSON.stringify({
2445
+ error: String(error),
2446
+ path: normalizedPath
2447
+ });
2448
+ }
2449
+ }
2450
+ if (name === 'write_file') {
2451
+ const filePath = typeof args.path === 'string' ? args.path : '';
2452
+ const content = typeof args.content === 'string' ? args.content : '';
2453
+ if (!filePath || isPathTraversalAttempt(filePath)) {
2454
+ return JSON.stringify({
2455
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
2456
+ });
2457
+ }
2458
+ const normalizedPath = normalizeRelativePath(filePath);
2459
+ try {
2460
+ await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
2461
+ return JSON.stringify({
2462
+ ok: true,
2463
+ path: normalizedPath
2464
+ });
2465
+ } catch (error) {
2466
+ return JSON.stringify({
2467
+ error: String(error),
2468
+ path: normalizedPath
2469
+ });
2470
+ }
2471
+ }
2472
+ if (name === 'list_files') {
2473
+ const folderPath = typeof args.path === 'string' && args.path ? args.path : '.';
2474
+ if (isPathTraversalAttempt(folderPath)) {
2475
+ return JSON.stringify({
2476
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
2477
+ });
2478
+ }
2479
+ const normalizedPath = normalizeRelativePath(folderPath);
2480
+ try {
2481
+ const entries = await invoke('FileSystem.readDirWithFileTypes', normalizedPath);
2482
+ return JSON.stringify({
2483
+ entries,
2484
+ path: normalizedPath
2485
+ });
2486
+ } catch (error) {
2487
+ return JSON.stringify({
2488
+ error: String(error),
2489
+ path: normalizedPath
2490
+ });
2491
+ }
2492
+ }
2493
+ return JSON.stringify({
2494
+ error: `Unknown tool: ${name}`
2495
+ });
2496
+ };
2497
+
2313
2498
  const getOpenApiApiEndpoint = openApiApiBaseUrl => {
2314
2499
  return `${openApiApiBaseUrl}/chat/completions`;
2315
2500
  };
@@ -2358,84 +2543,121 @@ const getOpenApiErrorDetails = async response => {
2358
2543
  errorType: typeof errorType === 'string' ? errorType : undefined
2359
2544
  };
2360
2545
  };
2361
- const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl) => {
2362
- let response;
2363
- try {
2364
- response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
2365
- body: JSON.stringify({
2366
- messages: messages.map(message => ({
2367
- content: message.text,
2368
- role: message.role
2369
- })),
2370
- model: modelId
2371
- }),
2372
- headers: {
2373
- Authorization: `Bearer ${openApiApiKey}`,
2374
- 'Content-Type': 'application/json'
2375
- },
2376
- method: 'POST'
2377
- });
2378
- } catch {
2379
- return {
2380
- details: 'request-failed',
2381
- type: 'error'
2382
- };
2383
- }
2384
- if (!response.ok) {
2385
- const {
2386
- errorCode,
2387
- errorMessage,
2388
- errorType
2389
- } = await getOpenApiErrorDetails(response);
2390
- return {
2391
- details: 'http-error',
2392
- errorCode,
2393
- errorMessage,
2394
- errorType,
2395
- statusCode: response.status,
2396
- type: 'error'
2397
- };
2398
- }
2399
- let parsed;
2400
- try {
2401
- parsed = await response.json();
2402
- } catch {
2403
- return {
2404
- details: 'request-failed',
2405
- type: 'error'
2406
- };
2407
- }
2408
- if (!parsed || typeof parsed !== 'object') {
2409
- return {
2410
- text: '',
2411
- type: 'success'
2412
- };
2413
- }
2414
- const choices = Reflect.get(parsed, 'choices');
2415
- if (!Array.isArray(choices)) {
2416
- return {
2417
- text: '',
2418
- type: 'success'
2419
- };
2420
- }
2421
- const firstChoice = choices[0];
2422
- if (!firstChoice || typeof firstChoice !== 'object') {
2423
- return {
2424
- text: '',
2425
- type: 'success'
2426
- };
2427
- }
2428
- const message = Reflect.get(firstChoice, 'message');
2429
- if (!message || typeof message !== 'object') {
2546
+ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform) => {
2547
+ const completionMessages = messages.map(message => ({
2548
+ content: message.text,
2549
+ role: message.role
2550
+ }));
2551
+ const tools = getBasicChatTools();
2552
+ const maxToolIterations = 4;
2553
+ for (let i = 0; i <= maxToolIterations; i++) {
2554
+ let response;
2555
+ try {
2556
+ response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
2557
+ body: JSON.stringify({
2558
+ messages: completionMessages,
2559
+ model: modelId,
2560
+ tool_choice: 'auto',
2561
+ tools
2562
+ }),
2563
+ headers: {
2564
+ Authorization: `Bearer ${openApiApiKey}`,
2565
+ 'Content-Type': 'application/json'
2566
+ },
2567
+ method: 'POST'
2568
+ });
2569
+ } catch {
2570
+ return {
2571
+ details: 'request-failed',
2572
+ type: 'error'
2573
+ };
2574
+ }
2575
+ if (!response.ok) {
2576
+ const {
2577
+ errorCode,
2578
+ errorMessage,
2579
+ errorType
2580
+ } = await getOpenApiErrorDetails(response);
2581
+ return {
2582
+ details: 'http-error',
2583
+ errorCode,
2584
+ errorMessage,
2585
+ errorType,
2586
+ statusCode: response.status,
2587
+ type: 'error'
2588
+ };
2589
+ }
2590
+ let parsed;
2591
+ try {
2592
+ parsed = await response.json();
2593
+ } catch {
2594
+ return {
2595
+ details: 'request-failed',
2596
+ type: 'error'
2597
+ };
2598
+ }
2599
+ if (!parsed || typeof parsed !== 'object') {
2600
+ return {
2601
+ text: '',
2602
+ type: 'success'
2603
+ };
2604
+ }
2605
+ const choices = Reflect.get(parsed, 'choices');
2606
+ if (!Array.isArray(choices)) {
2607
+ return {
2608
+ text: '',
2609
+ type: 'success'
2610
+ };
2611
+ }
2612
+ const firstChoice = choices[0];
2613
+ if (!firstChoice || typeof firstChoice !== 'object') {
2614
+ return {
2615
+ text: '',
2616
+ type: 'success'
2617
+ };
2618
+ }
2619
+ const message = Reflect.get(firstChoice, 'message');
2620
+ if (!message || typeof message !== 'object') {
2621
+ return {
2622
+ text: '',
2623
+ type: 'success'
2624
+ };
2625
+ }
2626
+ const toolCalls = Reflect.get(message, 'tool_calls');
2627
+ if (Array.isArray(toolCalls) && toolCalls.length > 0) {
2628
+ completionMessages.push(message);
2629
+ for (const toolCall of toolCalls) {
2630
+ if (!toolCall || typeof toolCall !== 'object') {
2631
+ continue;
2632
+ }
2633
+ const id = Reflect.get(toolCall, 'id');
2634
+ const toolFunction = Reflect.get(toolCall, 'function');
2635
+ if (typeof id !== 'string' || !toolFunction || typeof toolFunction !== 'object') {
2636
+ continue;
2637
+ }
2638
+ const name = Reflect.get(toolFunction, 'name');
2639
+ const rawArguments = Reflect.get(toolFunction, 'arguments');
2640
+ const content = typeof name === 'string' ? await executeChatTool(name, rawArguments, {
2641
+ assetDir,
2642
+ platform
2643
+ }) : '{}';
2644
+ completionMessages.push({
2645
+ content,
2646
+ role: 'tool',
2647
+ tool_call_id: id
2648
+ });
2649
+ }
2650
+ continue;
2651
+ }
2652
+ const content = Reflect.get(message, 'content');
2430
2653
  return {
2431
- text: '',
2654
+ text: getTextContent(content),
2432
2655
  type: 'success'
2433
2656
  };
2434
2657
  }
2435
- const content = Reflect.get(message, 'content');
2436
2658
  return {
2437
- text: getTextContent(content),
2438
- type: 'success'
2659
+ details: 'request-failed',
2660
+ type: 'error'
2439
2661
  };
2440
2662
  };
2441
2663
 
@@ -2575,91 +2797,128 @@ const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) =>
2575
2797
  }
2576
2798
  return normalizedLimitInfo;
2577
2799
  };
2578
- const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
2579
- let response;
2580
- try {
2581
- response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
2582
- body: JSON.stringify({
2583
- messages: messages.map(message => ({
2584
- content: message.text,
2585
- role: message.role
2586
- })),
2587
- model: modelId
2588
- }),
2589
- headers: {
2590
- Authorization: `Bearer ${openRouterApiKey}`,
2591
- 'Content-Type': 'application/json'
2592
- },
2593
- method: 'POST'
2594
- });
2595
- } catch {
2596
- return {
2597
- details: 'request-failed',
2598
- type: 'error'
2599
- };
2600
- }
2601
- if (!response.ok) {
2602
- if (response.status === 429) {
2603
- const retryAfter = response.headers?.get?.('retry-after') ?? null;
2604
- const rawMessage = await getOpenRouterRaw429Message(response);
2605
- const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl);
2800
+ const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform) => {
2801
+ const completionMessages = messages.map(message => ({
2802
+ content: message.text,
2803
+ role: message.role
2804
+ }));
2805
+ const tools = getBasicChatTools();
2806
+ const maxToolIterations = 4;
2807
+ for (let i = 0; i <= maxToolIterations; i++) {
2808
+ let response;
2809
+ try {
2810
+ response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
2811
+ body: JSON.stringify({
2812
+ messages: completionMessages,
2813
+ model: modelId,
2814
+ tool_choice: 'auto',
2815
+ tools
2816
+ }),
2817
+ headers: {
2818
+ Authorization: `Bearer ${openRouterApiKey}`,
2819
+ 'Content-Type': 'application/json'
2820
+ },
2821
+ method: 'POST'
2822
+ });
2823
+ } catch {
2606
2824
  return {
2607
- details: 'too-many-requests',
2608
- limitInfo: limitInfo || retryAfter ? {
2609
- ...limitInfo,
2610
- retryAfter
2611
- } : undefined,
2612
- rawMessage,
2613
- statusCode: 429,
2825
+ details: 'request-failed',
2614
2826
  type: 'error'
2615
2827
  };
2616
2828
  }
2829
+ if (!response.ok) {
2830
+ if (response.status === 429) {
2831
+ const retryAfter = response.headers?.get?.('retry-after') ?? null;
2832
+ const rawMessage = await getOpenRouterRaw429Message(response);
2833
+ const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl);
2834
+ return {
2835
+ details: 'too-many-requests',
2836
+ limitInfo: limitInfo || retryAfter ? {
2837
+ ...limitInfo,
2838
+ retryAfter
2839
+ } : undefined,
2840
+ rawMessage,
2841
+ statusCode: 429,
2842
+ type: 'error'
2843
+ };
2844
+ }
2845
+ return {
2846
+ details: 'http-error',
2847
+ statusCode: response.status,
2848
+ type: 'error'
2849
+ };
2850
+ }
2851
+ let parsed;
2852
+ try {
2853
+ parsed = await response.json();
2854
+ } catch {
2855
+ return {
2856
+ details: 'request-failed',
2857
+ type: 'error'
2858
+ };
2859
+ }
2860
+ if (!parsed || typeof parsed !== 'object') {
2861
+ return {
2862
+ text: '',
2863
+ type: 'success'
2864
+ };
2865
+ }
2866
+ const choices = Reflect.get(parsed, 'choices');
2867
+ if (!Array.isArray(choices)) {
2868
+ return {
2869
+ text: '',
2870
+ type: 'success'
2871
+ };
2872
+ }
2873
+ const firstChoice = choices[0];
2874
+ if (!firstChoice || typeof firstChoice !== 'object') {
2875
+ return {
2876
+ text: '',
2877
+ type: 'success'
2878
+ };
2879
+ }
2880
+ const message = Reflect.get(firstChoice, 'message');
2881
+ if (!message || typeof message !== 'object') {
2882
+ return {
2883
+ text: '',
2884
+ type: 'success'
2885
+ };
2886
+ }
2887
+ const toolCalls = Reflect.get(message, 'tool_calls');
2888
+ if (Array.isArray(toolCalls) && toolCalls.length > 0) {
2889
+ completionMessages.push(message);
2890
+ for (const toolCall of toolCalls) {
2891
+ if (!toolCall || typeof toolCall !== 'object') {
2892
+ continue;
2893
+ }
2894
+ const id = Reflect.get(toolCall, 'id');
2895
+ const toolFunction = Reflect.get(toolCall, 'function');
2896
+ if (typeof id !== 'string' || !toolFunction || typeof toolFunction !== 'object') {
2897
+ continue;
2898
+ }
2899
+ const name = Reflect.get(toolFunction, 'name');
2900
+ const rawArguments = Reflect.get(toolFunction, 'arguments');
2901
+ const content = typeof name === 'string' ? await executeChatTool(name, rawArguments, {
2902
+ assetDir,
2903
+ platform
2904
+ }) : '{}';
2905
+ completionMessages.push({
2906
+ content,
2907
+ role: 'tool',
2908
+ tool_call_id: id
2909
+ });
2910
+ }
2911
+ continue;
2912
+ }
2913
+ const content = Reflect.get(message, 'content');
2617
2914
  return {
2618
- details: 'http-error',
2619
- statusCode: response.status,
2620
- type: 'error'
2621
- };
2622
- }
2623
- let parsed;
2624
- try {
2625
- parsed = await response.json();
2626
- } catch {
2627
- return {
2628
- details: 'request-failed',
2629
- type: 'error'
2630
- };
2631
- }
2632
- if (!parsed || typeof parsed !== 'object') {
2633
- return {
2634
- text: '',
2635
- type: 'success'
2636
- };
2637
- }
2638
- const choices = Reflect.get(parsed, 'choices');
2639
- if (!Array.isArray(choices)) {
2640
- return {
2641
- text: '',
2642
- type: 'success'
2643
- };
2644
- }
2645
- const firstChoice = choices[0];
2646
- if (!firstChoice || typeof firstChoice !== 'object') {
2647
- return {
2648
- text: '',
2649
- type: 'success'
2650
- };
2651
- }
2652
- const message = Reflect.get(firstChoice, 'message');
2653
- if (!message || typeof message !== 'object') {
2654
- return {
2655
- text: '',
2915
+ text: getTextContent(content),
2656
2916
  type: 'success'
2657
2917
  };
2658
2918
  }
2659
- const content = Reflect.get(message, 'content');
2660
2919
  return {
2661
- text: getTextContent(content),
2662
- type: 'success'
2920
+ details: 'request-failed',
2921
+ type: 'error'
2663
2922
  };
2664
2923
  };
2665
2924
 
@@ -2756,7 +3015,7 @@ const getAiResponse = async ({
2756
3015
  const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
2757
3016
  if (usesOpenApiModel) {
2758
3017
  if (openApiApiKey) {
2759
- const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl);
3018
+ const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl, assetDir, platform);
2760
3019
  if (result.type === 'success') {
2761
3020
  const {
2762
3021
  text: assistantText
@@ -2781,7 +3040,7 @@ const getAiResponse = async ({
2781
3040
  text = getOpenRouterErrorMessage(result);
2782
3041
  }
2783
3042
  } else if (openRouterApiKey) {
2784
- const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl);
3043
+ const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform);
2785
3044
  if (result.type === 'success') {
2786
3045
  const {
2787
3046
  text: assistantText
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",