@lvce-editor/chat-view 3.7.0 → 3.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.
@@ -2792,109 +2792,450 @@ const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). P
2792
2792
  const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];
2793
2793
  const openRouterTooManyRequestsReasons = ['Wait a short time and retry your request.', 'Reduce request frequency to avoid rate limits.', 'Use a different model if this one is saturated.'];
2794
2794
 
2795
- const delay = async ms => {
2796
- await new Promise(resolve => setTimeout(resolve, ms));
2795
+ const executeGetWorkspaceUriTool = async (_args, _options) => {
2796
+ try {
2797
+ const workspaceUri = await getWorkspacePath();
2798
+ return JSON.stringify({
2799
+ workspaceUri
2800
+ });
2801
+ } catch (error) {
2802
+ return JSON.stringify({
2803
+ error: String(error)
2804
+ });
2805
+ }
2797
2806
  };
2798
2807
 
2799
- const getMockAiResponse = async (userMessage, delayInMs) => {
2800
- await delay(delayInMs);
2801
- return `Mock AI response: I received "${userMessage}".`;
2808
+ const isAbsoluteUri$1 = value => {
2809
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
2810
+ };
2811
+ const executeListFilesTool = async (args, _options) => {
2812
+ const uri = typeof args.uri === 'string' ? args.uri : '';
2813
+ if (!uri || !isAbsoluteUri$1(uri)) {
2814
+ return JSON.stringify({
2815
+ error: 'Invalid argument: uri must be an absolute URI.'
2816
+ });
2817
+ }
2818
+ try {
2819
+ const entries = await invoke('FileSystem.readDirWithFileTypes', uri);
2820
+ return JSON.stringify({
2821
+ entries,
2822
+ uri
2823
+ });
2824
+ } catch (error) {
2825
+ return JSON.stringify({
2826
+ error: String(error),
2827
+ uri
2828
+ });
2829
+ }
2802
2830
  };
2803
2831
 
2804
- let queue = [];
2805
- let waiters = [];
2806
- let finished = false;
2807
- let errorResult;
2808
- const reset$1 = () => {
2809
- queue = [];
2810
- waiters = [];
2811
- finished = false;
2812
- errorResult = undefined;
2832
+ const isPathTraversalAttempt = path => {
2833
+ if (!path) {
2834
+ return false;
2835
+ }
2836
+ if (path.startsWith('/') || path.startsWith('\\')) {
2837
+ return true;
2838
+ }
2839
+ if (path.startsWith('file://')) {
2840
+ return true;
2841
+ }
2842
+ if (/^[a-zA-Z]:[\\/]/.test(path)) {
2843
+ return true;
2844
+ }
2845
+ const segments = path.split(/[\\/]/);
2846
+ return segments.includes('..');
2813
2847
  };
2814
- const setHttpErrorResponse = (statusCode, body) => {
2815
- const rawError = body && typeof body === 'object' ? Reflect.get(body, 'error') : undefined;
2816
- const errorCode = rawError && typeof rawError === 'object' ? Reflect.get(rawError, 'code') : undefined;
2817
- const errorMessage = rawError && typeof rawError === 'object' ? Reflect.get(rawError, 'message') : undefined;
2818
- const errorType = rawError && typeof rawError === 'object' ? Reflect.get(rawError, 'type') : undefined;
2819
- errorResult = {
2820
- details: 'http-error',
2821
- ...(typeof errorCode === 'string' ? {
2822
- errorCode
2823
- } : {}),
2824
- ...(typeof errorMessage === 'string' ? {
2825
- errorMessage
2826
- } : {}),
2827
- ...(typeof errorType === 'string' ? {
2828
- errorType
2829
- } : {}),
2830
- statusCode,
2831
- type: 'error'
2832
- };
2848
+
2849
+ const normalizeRelativePath = path => {
2850
+ const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
2851
+ if (segments.length === 0) {
2852
+ return '.';
2853
+ }
2854
+ return segments.join('/');
2833
2855
  };
2834
- const takeErrorResponse = () => {
2835
- const error = errorResult;
2836
- errorResult = undefined;
2837
- return error;
2856
+
2857
+ const isAbsoluteUri = value => {
2858
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
2838
2859
  };
2839
- const pushChunk = chunk => {
2840
- if (waiters.length > 0) {
2841
- const resolve = waiters.shift();
2842
- resolve?.(chunk);
2843
- return;
2860
+ const executeReadFileTool = async (args, _options) => {
2861
+ const uri = typeof args.uri === 'string' ? args.uri : '';
2862
+ if (uri) {
2863
+ if (!isAbsoluteUri(uri)) {
2864
+ return JSON.stringify({
2865
+ error: 'Invalid argument: uri must be an absolute URI.'
2866
+ });
2867
+ }
2868
+ try {
2869
+ const content = await readFile(uri);
2870
+ return JSON.stringify({
2871
+ content,
2872
+ uri
2873
+ });
2874
+ } catch (error) {
2875
+ return JSON.stringify({
2876
+ error: String(error),
2877
+ uri
2878
+ });
2879
+ }
2844
2880
  }
2845
- queue.push(chunk);
2846
- };
2847
- const finish = () => {
2848
- finished = true;
2849
- if (waiters.length === 0) {
2850
- return;
2881
+ const filePath = typeof args.path === 'string' ? args.path : '';
2882
+ if (!filePath || isPathTraversalAttempt(filePath)) {
2883
+ return JSON.stringify({
2884
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
2885
+ });
2851
2886
  }
2852
- const activeWaiters = waiters;
2853
- waiters = [];
2854
- for (const resolve of activeWaiters) {
2855
- resolve(undefined);
2887
+ const normalizedPath = normalizeRelativePath(filePath);
2888
+ try {
2889
+ const content = await readFile(normalizedPath);
2890
+ return JSON.stringify({
2891
+ content,
2892
+ path: normalizedPath
2893
+ });
2894
+ } catch (error) {
2895
+ return JSON.stringify({
2896
+ error: String(error),
2897
+ path: normalizedPath
2898
+ });
2856
2899
  }
2857
2900
  };
2858
- const readNextChunk = async () => {
2859
- if (queue.length > 0) {
2860
- return queue.shift();
2901
+
2902
+ const maxPayloadLength = 40_000;
2903
+ const executeRenderHtmlTool = async (args, _options) => {
2904
+ const html = typeof args.html === 'string' ? args.html : '';
2905
+ const css = typeof args.css === 'string' ? args.css : '';
2906
+ const title = typeof args.title === 'string' ? args.title : '';
2907
+ if (!html) {
2908
+ return JSON.stringify({
2909
+ error: 'Missing required argument: html'
2910
+ });
2861
2911
  }
2862
- if (finished) {
2863
- return undefined;
2912
+ if (html.length > maxPayloadLength || css.length > maxPayloadLength) {
2913
+ return JSON.stringify({
2914
+ error: 'Payload too large: keep html/css under 40,000 characters each.'
2915
+ });
2864
2916
  }
2865
- const {
2866
- promise,
2867
- resolve
2868
- } = Promise.withResolvers();
2869
- waiters.push(resolve);
2870
- return promise;
2917
+ return JSON.stringify({
2918
+ css,
2919
+ html,
2920
+ ok: true,
2921
+ title
2922
+ });
2871
2923
  };
2872
2924
 
2873
- const parseSseDataLines = eventChunk => {
2874
- const lines = eventChunk.split('\n');
2875
- const dataLines = [];
2876
- for (const line of lines) {
2877
- if (!line.startsWith('data:')) {
2878
- continue;
2879
- }
2880
- dataLines.push(line.slice(5).trimStart());
2881
- }
2882
- return dataLines;
2925
+ const activateByEvent = (event, assetDir, platform) => {
2926
+ // @ts-ignore
2927
+ return activateByEvent$1(event, assetDir, platform);
2883
2928
  };
2884
- const emitToolCalls = async (toolCallAccumulator, onToolCallsChunk) => {
2885
- if (!onToolCallsChunk) {
2886
- return;
2929
+
2930
+ const executeProvider = async ({
2931
+ assetDir,
2932
+ event,
2933
+ method,
2934
+ noProviderFoundMessage,
2935
+ params,
2936
+ platform
2937
+ }) => {
2938
+ await activateByEvent(event, assetDir, platform);
2939
+ // @ts-ignore
2940
+ const result = invoke$1(method, ...params);
2941
+ return result;
2942
+ };
2943
+
2944
+ const OnFileSystem = 'onFileSystem';
2945
+
2946
+ const executeFileSystemCommand = async (method, params, options) => {
2947
+ return executeProvider({
2948
+ assetDir: options.assetDir,
2949
+ event: OnFileSystem,
2950
+ method,
2951
+ noProviderFoundMessage: 'No file system provider found',
2952
+ params,
2953
+ platform: options.platform
2954
+ });
2955
+ };
2956
+
2957
+ const CommandExecute = 'ExtensionHostCommand.executeCommand';
2958
+ const FileSystemWriteFile = 'ExtensionHostFileSystem.writeFile';
2959
+
2960
+ const executeWriteFileTool = async (args, options) => {
2961
+ const filePath = typeof args.path === 'string' ? args.path : '';
2962
+ const content = typeof args.content === 'string' ? args.content : '';
2963
+ if (!filePath || isPathTraversalAttempt(filePath)) {
2964
+ return JSON.stringify({
2965
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
2966
+ });
2887
2967
  }
2888
- const toolCalls = Object.entries(toolCallAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => !!toolCall.name);
2889
- if (toolCalls.length === 0) {
2890
- return;
2968
+ const normalizedPath = normalizeRelativePath(filePath);
2969
+ try {
2970
+ await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
2971
+ return JSON.stringify({
2972
+ ok: true,
2973
+ path: normalizedPath
2974
+ });
2975
+ } catch (error) {
2976
+ return JSON.stringify({
2977
+ error: String(error),
2978
+ path: normalizedPath
2979
+ });
2891
2980
  }
2892
- await onToolCallsChunk(toolCalls);
2893
2981
  };
2894
- const getMockOpenApiAssistantText = async (stream, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished) => {
2895
- const error = takeErrorResponse();
2896
- if (error) {
2897
- return error;
2982
+
2983
+ const parseToolArguments = rawArguments => {
2984
+ if (typeof rawArguments !== 'string') {
2985
+ return {};
2986
+ }
2987
+ try {
2988
+ const parsed = JSON.parse(rawArguments);
2989
+ if (!parsed || typeof parsed !== 'object') {
2990
+ return {};
2991
+ }
2992
+ return parsed;
2993
+ } catch {
2994
+ return {};
2995
+ }
2996
+ };
2997
+
2998
+ const executeChatTool = async (name, rawArguments, options) => {
2999
+ const args = parseToolArguments(rawArguments);
3000
+ if (name === 'read_file') {
3001
+ return executeReadFileTool(args);
3002
+ }
3003
+ if (name === 'write_file') {
3004
+ return executeWriteFileTool(args, options);
3005
+ }
3006
+ if (name === 'list_files') {
3007
+ return executeListFilesTool(args);
3008
+ }
3009
+ if (name === 'getWorkspaceUri') {
3010
+ return executeGetWorkspaceUriTool();
3011
+ }
3012
+ if (name === 'render_html') {
3013
+ return executeRenderHtmlTool(args);
3014
+ }
3015
+ return JSON.stringify({
3016
+ error: `Unknown tool: ${name}`
3017
+ });
3018
+ };
3019
+
3020
+ const getReadFileTool = () => {
3021
+ return {
3022
+ function: {
3023
+ description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI.',
3024
+ name: 'read_file',
3025
+ parameters: {
3026
+ additionalProperties: false,
3027
+ properties: {
3028
+ uri: {
3029
+ description: 'Absolute file URI within the workspace (for example: file:///workspace/src/index.ts).',
3030
+ type: 'string'
3031
+ }
3032
+ },
3033
+ required: ['uri'],
3034
+ type: 'object'
3035
+ }
3036
+ },
3037
+ type: 'function'
3038
+ };
3039
+ };
3040
+ const getWriteFileTool = () => {
3041
+ return {
3042
+ function: {
3043
+ description: 'Write UTF-8 text content to a file inside the currently open workspace folder.',
3044
+ name: 'write_file',
3045
+ parameters: {
3046
+ additionalProperties: false,
3047
+ properties: {
3048
+ content: {
3049
+ description: 'New UTF-8 text content to write to the file.',
3050
+ type: 'string'
3051
+ },
3052
+ path: {
3053
+ description: 'Relative file path within the workspace (for example: src/index.ts).',
3054
+ type: 'string'
3055
+ }
3056
+ },
3057
+ required: ['path', 'content'],
3058
+ type: 'object'
3059
+ }
3060
+ },
3061
+ type: 'function'
3062
+ };
3063
+ };
3064
+ const getListFilesTool = () => {
3065
+ return {
3066
+ function: {
3067
+ description: 'List direct children (files and folders) for a folder URI inside the currently open workspace folder. Only pass an absolute URI.',
3068
+ name: 'list_files',
3069
+ parameters: {
3070
+ additionalProperties: false,
3071
+ properties: {
3072
+ uri: {
3073
+ description: 'Absolute folder URI within the workspace (for example: file:///workspace/src).',
3074
+ type: 'string'
3075
+ }
3076
+ },
3077
+ required: ['uri'],
3078
+ type: 'object'
3079
+ }
3080
+ },
3081
+ type: 'function'
3082
+ };
3083
+ };
3084
+ const getGetWorkspaceUriTool = () => {
3085
+ return {
3086
+ function: {
3087
+ description: 'Get the URI of the currently open workspace folder.',
3088
+ name: 'getWorkspaceUri',
3089
+ parameters: {
3090
+ additionalProperties: false,
3091
+ properties: {},
3092
+ type: 'object'
3093
+ }
3094
+ },
3095
+ type: 'function'
3096
+ };
3097
+ };
3098
+ const getRenderHtmlTool = () => {
3099
+ return {
3100
+ function: {
3101
+ description: 'Render custom HTML and optional CSS directly in the chat tool call list using native chat UI rendering. Use this for structured cards, tables, and small dashboards. After calling this tool, do not repeat the same HTML, data table, or long content again as plain text unless the user explicitly asks for a text-only version.',
3102
+ name: 'render_html',
3103
+ parameters: {
3104
+ additionalProperties: false,
3105
+ properties: {
3106
+ css: {
3107
+ description: 'Optional CSS string applied inside the preview document.',
3108
+ type: 'string'
3109
+ },
3110
+ html: {
3111
+ description: 'HTML string to render in the preview document.',
3112
+ type: 'string'
3113
+ },
3114
+ title: {
3115
+ description: 'Optional short title for the preview.',
3116
+ type: 'string'
3117
+ }
3118
+ },
3119
+ required: ['html'],
3120
+ type: 'object'
3121
+ }
3122
+ },
3123
+ type: 'function'
3124
+ };
3125
+ };
3126
+ const getBasicChatTools = () => {
3127
+ return [getReadFileTool(), getWriteFileTool(), getListFilesTool(), getGetWorkspaceUriTool(), getRenderHtmlTool()];
3128
+ };
3129
+
3130
+ const getClientRequestIdHeader = () => {
3131
+ return {
3132
+ 'x-client-request-id': crypto.randomUUID()
3133
+ };
3134
+ };
3135
+
3136
+ const delay = async ms => {
3137
+ await new Promise(resolve => setTimeout(resolve, ms));
3138
+ };
3139
+
3140
+ const getMockAiResponse = async (userMessage, delayInMs) => {
3141
+ await delay(delayInMs);
3142
+ return `Mock AI response: I received "${userMessage}".`;
3143
+ };
3144
+
3145
+ let queue = [];
3146
+ let waiters = [];
3147
+ let finished = false;
3148
+ let errorResult;
3149
+ const reset$2 = () => {
3150
+ queue = [];
3151
+ waiters = [];
3152
+ finished = false;
3153
+ errorResult = undefined;
3154
+ };
3155
+ const setHttpErrorResponse = (statusCode, body) => {
3156
+ const rawError = body && typeof body === 'object' ? Reflect.get(body, 'error') : undefined;
3157
+ const errorCode = rawError && typeof rawError === 'object' ? Reflect.get(rawError, 'code') : undefined;
3158
+ const errorMessage = rawError && typeof rawError === 'object' ? Reflect.get(rawError, 'message') : undefined;
3159
+ const errorType = rawError && typeof rawError === 'object' ? Reflect.get(rawError, 'type') : undefined;
3160
+ errorResult = {
3161
+ details: 'http-error',
3162
+ ...(typeof errorCode === 'string' ? {
3163
+ errorCode
3164
+ } : {}),
3165
+ ...(typeof errorMessage === 'string' ? {
3166
+ errorMessage
3167
+ } : {}),
3168
+ ...(typeof errorType === 'string' ? {
3169
+ errorType
3170
+ } : {}),
3171
+ statusCode,
3172
+ type: 'error'
3173
+ };
3174
+ };
3175
+ const takeErrorResponse = () => {
3176
+ const error = errorResult;
3177
+ errorResult = undefined;
3178
+ return error;
3179
+ };
3180
+ const pushChunk = chunk => {
3181
+ if (waiters.length > 0) {
3182
+ const resolve = waiters.shift();
3183
+ resolve?.(chunk);
3184
+ return;
3185
+ }
3186
+ queue.push(chunk);
3187
+ };
3188
+ const finish = () => {
3189
+ finished = true;
3190
+ if (waiters.length === 0) {
3191
+ return;
3192
+ }
3193
+ const activeWaiters = waiters;
3194
+ waiters = [];
3195
+ for (const resolve of activeWaiters) {
3196
+ resolve(undefined);
3197
+ }
3198
+ };
3199
+ const readNextChunk = async () => {
3200
+ if (queue.length > 0) {
3201
+ return queue.shift();
3202
+ }
3203
+ if (finished) {
3204
+ return undefined;
3205
+ }
3206
+ const {
3207
+ promise,
3208
+ resolve
3209
+ } = Promise.withResolvers();
3210
+ waiters.push(resolve);
3211
+ return promise;
3212
+ };
3213
+
3214
+ const parseSseDataLines = eventChunk => {
3215
+ const lines = eventChunk.split('\n');
3216
+ const dataLines = [];
3217
+ for (const line of lines) {
3218
+ if (!line.startsWith('data:')) {
3219
+ continue;
3220
+ }
3221
+ dataLines.push(line.slice(5).trimStart());
3222
+ }
3223
+ return dataLines;
3224
+ };
3225
+ const emitToolCalls = async (toolCallAccumulator, onToolCallsChunk) => {
3226
+ if (!onToolCallsChunk) {
3227
+ return;
3228
+ }
3229
+ const toolCalls = Object.entries(toolCallAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => !!toolCall.name);
3230
+ if (toolCalls.length === 0) {
3231
+ return;
3232
+ }
3233
+ await onToolCallsChunk(toolCalls);
3234
+ };
3235
+ const getMockOpenApiAssistantText = async (stream, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished) => {
3236
+ const error = takeErrorResponse();
3237
+ if (error) {
3238
+ return error;
2898
3239
  }
2899
3240
  let text = '';
2900
3241
  let remainder = '';
@@ -3036,479 +3377,138 @@ const getMockOpenApiAssistantText = async (stream, onTextChunk, onToolCallsChunk
3036
3377
  };
3037
3378
  };
3038
3379
 
3039
- const activateByEvent = (event, assetDir, platform) => {
3040
- // @ts-ignore
3041
- return activateByEvent$1(event, assetDir, platform);
3042
- };
3043
-
3044
- const executeProvider = async ({
3045
- assetDir,
3046
- event,
3047
- method,
3048
- noProviderFoundMessage,
3049
- params,
3050
- platform
3051
- }) => {
3052
- await activateByEvent(event, assetDir, platform);
3053
- // @ts-ignore
3054
- const result = invoke$1(method, ...params);
3055
- return result;
3056
- };
3057
-
3058
- const CommandExecute = 'ExtensionHostCommand.executeCommand';
3059
- const FileSystemWriteFile = 'ExtensionHostFileSystem.writeFile';
3060
-
3061
3380
  const normalizeLimitInfo = value => {
3062
3381
  if (!value || typeof value !== 'object') {
3063
3382
  return undefined;
3064
3383
  }
3065
- const limitRemaining = Reflect.get(value, 'limitRemaining');
3066
- const limitReset = Reflect.get(value, 'limitReset');
3067
- const retryAfter = Reflect.get(value, 'retryAfter');
3068
- const usage = Reflect.get(value, 'usage');
3069
- const usageDaily = Reflect.get(value, 'usageDaily');
3070
- const normalized = {
3071
- ...(typeof limitRemaining === 'number' || limitRemaining === null ? {
3072
- limitRemaining
3073
- } : {}),
3074
- ...(typeof limitReset === 'string' || limitReset === null ? {
3075
- limitReset
3076
- } : {}),
3077
- ...(typeof retryAfter === 'string' || retryAfter === null ? {
3078
- retryAfter
3079
- } : {}),
3080
- ...(typeof usage === 'number' ? {
3081
- usage
3082
- } : {}),
3083
- ...(typeof usageDaily === 'number' ? {
3084
- usageDaily
3085
- } : {})
3086
- };
3087
- const hasDetails = typeof limitRemaining === 'number' || limitRemaining === null || typeof limitReset === 'string' || limitReset === null || typeof retryAfter === 'string' || retryAfter === null || typeof usage === 'number' || typeof usageDaily === 'number';
3088
- return hasDetails ? normalized : undefined;
3089
- };
3090
-
3091
- const normalizeMockResult = value => {
3092
- if (typeof value === 'string') {
3093
- return {
3094
- text: value,
3095
- type: 'success'
3096
- };
3097
- }
3098
- if (!value || typeof value !== 'object') {
3099
- return {
3100
- details: 'request-failed',
3101
- type: 'error'
3102
- };
3103
- }
3104
- const type = Reflect.get(value, 'type');
3105
- if (type === 'success') {
3106
- const text = Reflect.get(value, 'text');
3107
- if (typeof text === 'string') {
3108
- return {
3109
- text,
3110
- type: 'success'
3111
- };
3112
- }
3113
- return {
3114
- details: 'request-failed',
3115
- type: 'error'
3116
- };
3117
- }
3118
- if (type === 'error') {
3119
- const details = Reflect.get(value, 'details');
3120
- if (details === 'request-failed' || details === 'too-many-requests' || details === 'http-error') {
3121
- const rawMessage = Reflect.get(value, 'rawMessage');
3122
- const statusCode = Reflect.get(value, 'statusCode');
3123
- const limitInfo = normalizeLimitInfo(Reflect.get(value, 'limitInfo'));
3124
- return {
3125
- details,
3126
- ...(limitInfo ? {
3127
- limitInfo
3128
- } : {}),
3129
- ...(typeof rawMessage === 'string' ? {
3130
- rawMessage
3131
- } : {}),
3132
- ...(typeof statusCode === 'number' ? {
3133
- statusCode
3134
- } : {}),
3135
- type: 'error'
3136
- };
3137
- }
3138
- }
3139
- const text = Reflect.get(value, 'text');
3140
- if (typeof text === 'string') {
3141
- return {
3142
- text,
3143
- type: 'success'
3144
- };
3145
- }
3146
- return {
3147
- details: 'request-failed',
3148
- type: 'error'
3149
- };
3150
- };
3151
-
3152
- const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform) => {
3153
- if (!mockApiCommandId) {
3154
- return {
3155
- details: 'request-failed',
3156
- type: 'error'
3157
- };
3158
- }
3159
- try {
3160
- const result = await executeProvider({
3161
- assetDir,
3162
- event: `onCommand:${mockApiCommandId}`,
3163
- method: CommandExecute,
3164
- noProviderFoundMessage: 'No mock api command found',
3165
- params: [mockApiCommandId, {
3166
- messages,
3167
- modelId,
3168
- openRouterApiBaseUrl,
3169
- openRouterApiKey
3170
- }],
3171
- platform
3172
- });
3173
- return normalizeMockResult(result);
3174
- } catch {
3175
- return {
3176
- details: 'request-failed',
3177
- type: 'error'
3178
- };
3179
- }
3180
- };
3181
-
3182
- const makeApiRequest = async options => {
3183
- return invoke$2('ChatNetwork.makeApiRequest', options);
3184
- };
3185
- const makeStreamingApiRequest = async options => {
3186
- return invoke$2('ChatNetwork.makeStreamingApiRequest', options);
3187
- };
3188
-
3189
- const executeGetWorkspaceUriTool = async (_args, _options) => {
3190
- try {
3191
- const workspaceUri = await getWorkspacePath();
3192
- return JSON.stringify({
3193
- workspaceUri
3194
- });
3195
- } catch (error) {
3196
- return JSON.stringify({
3197
- error: String(error)
3198
- });
3199
- }
3200
- };
3201
-
3202
- const isAbsoluteUri$1 = value => {
3203
- return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
3204
- };
3205
- const executeListFilesTool = async (args, _options) => {
3206
- const uri = typeof args.uri === 'string' ? args.uri : '';
3207
- if (!uri || !isAbsoluteUri$1(uri)) {
3208
- return JSON.stringify({
3209
- error: 'Invalid argument: uri must be an absolute URI.'
3210
- });
3211
- }
3212
- try {
3213
- const entries = await invoke('FileSystem.readDirWithFileTypes', uri);
3214
- return JSON.stringify({
3215
- entries,
3216
- uri
3217
- });
3218
- } catch (error) {
3219
- return JSON.stringify({
3220
- error: String(error),
3221
- uri
3222
- });
3223
- }
3224
- };
3225
-
3226
- const isPathTraversalAttempt = path => {
3227
- if (!path) {
3228
- return false;
3229
- }
3230
- if (path.startsWith('/') || path.startsWith('\\')) {
3231
- return true;
3232
- }
3233
- if (path.startsWith('file://')) {
3234
- return true;
3235
- }
3236
- if (/^[a-zA-Z]:[\\/]/.test(path)) {
3237
- return true;
3238
- }
3239
- const segments = path.split(/[\\/]/);
3240
- return segments.includes('..');
3241
- };
3242
-
3243
- const normalizeRelativePath = path => {
3244
- const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
3245
- if (segments.length === 0) {
3246
- return '.';
3247
- }
3248
- return segments.join('/');
3249
- };
3250
-
3251
- const isAbsoluteUri = value => {
3252
- return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
3253
- };
3254
- const executeReadFileTool = async (args, _options) => {
3255
- const uri = typeof args.uri === 'string' ? args.uri : '';
3256
- if (uri) {
3257
- if (!isAbsoluteUri(uri)) {
3258
- return JSON.stringify({
3259
- error: 'Invalid argument: uri must be an absolute URI.'
3260
- });
3261
- }
3262
- try {
3263
- const content = await readFile(uri);
3264
- return JSON.stringify({
3265
- content,
3266
- uri
3267
- });
3268
- } catch (error) {
3269
- return JSON.stringify({
3270
- error: String(error),
3271
- uri
3272
- });
3273
- }
3274
- }
3275
- const filePath = typeof args.path === 'string' ? args.path : '';
3276
- if (!filePath || isPathTraversalAttempt(filePath)) {
3277
- return JSON.stringify({
3278
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3279
- });
3280
- }
3281
- const normalizedPath = normalizeRelativePath(filePath);
3282
- try {
3283
- const content = await readFile(normalizedPath);
3284
- return JSON.stringify({
3285
- content,
3286
- path: normalizedPath
3287
- });
3288
- } catch (error) {
3289
- return JSON.stringify({
3290
- error: String(error),
3291
- path: normalizedPath
3292
- });
3293
- }
3294
- };
3295
-
3296
- const maxPayloadLength = 40_000;
3297
- const executeRenderHtmlTool = async (args, _options) => {
3298
- const html = typeof args.html === 'string' ? args.html : '';
3299
- const css = typeof args.css === 'string' ? args.css : '';
3300
- const title = typeof args.title === 'string' ? args.title : '';
3301
- if (!html) {
3302
- return JSON.stringify({
3303
- error: 'Missing required argument: html'
3304
- });
3305
- }
3306
- if (html.length > maxPayloadLength || css.length > maxPayloadLength) {
3307
- return JSON.stringify({
3308
- error: 'Payload too large: keep html/css under 40,000 characters each.'
3309
- });
3310
- }
3311
- return JSON.stringify({
3312
- css,
3313
- html,
3314
- ok: true,
3315
- title
3316
- });
3317
- };
3318
-
3319
- const OnFileSystem = 'onFileSystem';
3320
-
3321
- const executeFileSystemCommand = async (method, params, options) => {
3322
- return executeProvider({
3323
- assetDir: options.assetDir,
3324
- event: OnFileSystem,
3325
- method,
3326
- noProviderFoundMessage: 'No file system provider found',
3327
- params,
3328
- platform: options.platform
3329
- });
3330
- };
3331
-
3332
- const executeWriteFileTool = async (args, options) => {
3333
- const filePath = typeof args.path === 'string' ? args.path : '';
3334
- const content = typeof args.content === 'string' ? args.content : '';
3335
- if (!filePath || isPathTraversalAttempt(filePath)) {
3336
- return JSON.stringify({
3337
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3338
- });
3339
- }
3340
- const normalizedPath = normalizeRelativePath(filePath);
3341
- try {
3342
- await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
3343
- return JSON.stringify({
3344
- ok: true,
3345
- path: normalizedPath
3346
- });
3347
- } catch (error) {
3348
- return JSON.stringify({
3349
- error: String(error),
3350
- path: normalizedPath
3351
- });
3352
- }
3353
- };
3354
-
3355
- const parseToolArguments = rawArguments => {
3356
- if (typeof rawArguments !== 'string') {
3357
- return {};
3358
- }
3359
- try {
3360
- const parsed = JSON.parse(rawArguments);
3361
- if (!parsed || typeof parsed !== 'object') {
3362
- return {};
3363
- }
3364
- return parsed;
3365
- } catch {
3366
- return {};
3367
- }
3384
+ const limitRemaining = Reflect.get(value, 'limitRemaining');
3385
+ const limitReset = Reflect.get(value, 'limitReset');
3386
+ const retryAfter = Reflect.get(value, 'retryAfter');
3387
+ const usage = Reflect.get(value, 'usage');
3388
+ const usageDaily = Reflect.get(value, 'usageDaily');
3389
+ const normalized = {
3390
+ ...(typeof limitRemaining === 'number' || limitRemaining === null ? {
3391
+ limitRemaining
3392
+ } : {}),
3393
+ ...(typeof limitReset === 'string' || limitReset === null ? {
3394
+ limitReset
3395
+ } : {}),
3396
+ ...(typeof retryAfter === 'string' || retryAfter === null ? {
3397
+ retryAfter
3398
+ } : {}),
3399
+ ...(typeof usage === 'number' ? {
3400
+ usage
3401
+ } : {}),
3402
+ ...(typeof usageDaily === 'number' ? {
3403
+ usageDaily
3404
+ } : {})
3405
+ };
3406
+ const hasDetails = typeof limitRemaining === 'number' || limitRemaining === null || typeof limitReset === 'string' || limitReset === null || typeof retryAfter === 'string' || retryAfter === null || typeof usage === 'number' || typeof usageDaily === 'number';
3407
+ return hasDetails ? normalized : undefined;
3368
3408
  };
3369
3409
 
3370
- const executeChatTool = async (name, rawArguments, options) => {
3371
- const args = parseToolArguments(rawArguments);
3372
- if (name === 'read_file') {
3373
- return executeReadFileTool(args);
3410
+ const normalizeMockResult = value => {
3411
+ if (typeof value === 'string') {
3412
+ return {
3413
+ text: value,
3414
+ type: 'success'
3415
+ };
3374
3416
  }
3375
- if (name === 'write_file') {
3376
- return executeWriteFileTool(args, options);
3417
+ if (!value || typeof value !== 'object') {
3418
+ return {
3419
+ details: 'request-failed',
3420
+ type: 'error'
3421
+ };
3377
3422
  }
3378
- if (name === 'list_files') {
3379
- return executeListFilesTool(args);
3423
+ const type = Reflect.get(value, 'type');
3424
+ if (type === 'success') {
3425
+ const text = Reflect.get(value, 'text');
3426
+ if (typeof text === 'string') {
3427
+ return {
3428
+ text,
3429
+ type: 'success'
3430
+ };
3431
+ }
3432
+ return {
3433
+ details: 'request-failed',
3434
+ type: 'error'
3435
+ };
3380
3436
  }
3381
- if (name === 'getWorkspaceUri') {
3382
- return executeGetWorkspaceUriTool();
3437
+ if (type === 'error') {
3438
+ const details = Reflect.get(value, 'details');
3439
+ if (details === 'request-failed' || details === 'too-many-requests' || details === 'http-error') {
3440
+ const rawMessage = Reflect.get(value, 'rawMessage');
3441
+ const statusCode = Reflect.get(value, 'statusCode');
3442
+ const limitInfo = normalizeLimitInfo(Reflect.get(value, 'limitInfo'));
3443
+ return {
3444
+ details,
3445
+ ...(limitInfo ? {
3446
+ limitInfo
3447
+ } : {}),
3448
+ ...(typeof rawMessage === 'string' ? {
3449
+ rawMessage
3450
+ } : {}),
3451
+ ...(typeof statusCode === 'number' ? {
3452
+ statusCode
3453
+ } : {}),
3454
+ type: 'error'
3455
+ };
3456
+ }
3383
3457
  }
3384
- if (name === 'render_html') {
3385
- return executeRenderHtmlTool(args);
3458
+ const text = Reflect.get(value, 'text');
3459
+ if (typeof text === 'string') {
3460
+ return {
3461
+ text,
3462
+ type: 'success'
3463
+ };
3386
3464
  }
3387
- return JSON.stringify({
3388
- error: `Unknown tool: ${name}`
3389
- });
3390
- };
3391
-
3392
- const getReadFileTool = () => {
3393
- return {
3394
- function: {
3395
- description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI.',
3396
- name: 'read_file',
3397
- parameters: {
3398
- additionalProperties: false,
3399
- properties: {
3400
- uri: {
3401
- description: 'Absolute file URI within the workspace (for example: file:///workspace/src/index.ts).',
3402
- type: 'string'
3403
- }
3404
- },
3405
- required: ['uri'],
3406
- type: 'object'
3407
- }
3408
- },
3409
- type: 'function'
3410
- };
3411
- };
3412
- const getWriteFileTool = () => {
3413
- return {
3414
- function: {
3415
- description: 'Write UTF-8 text content to a file inside the currently open workspace folder.',
3416
- name: 'write_file',
3417
- parameters: {
3418
- additionalProperties: false,
3419
- properties: {
3420
- content: {
3421
- description: 'New UTF-8 text content to write to the file.',
3422
- type: 'string'
3423
- },
3424
- path: {
3425
- description: 'Relative file path within the workspace (for example: src/index.ts).',
3426
- type: 'string'
3427
- }
3428
- },
3429
- required: ['path', 'content'],
3430
- type: 'object'
3431
- }
3432
- },
3433
- type: 'function'
3434
- };
3435
- };
3436
- const getListFilesTool = () => {
3437
- return {
3438
- function: {
3439
- description: 'List direct children (files and folders) for a folder URI inside the currently open workspace folder. Only pass an absolute URI.',
3440
- name: 'list_files',
3441
- parameters: {
3442
- additionalProperties: false,
3443
- properties: {
3444
- uri: {
3445
- description: 'Absolute folder URI within the workspace (for example: file:///workspace/src).',
3446
- type: 'string'
3447
- }
3448
- },
3449
- required: ['uri'],
3450
- type: 'object'
3451
- }
3452
- },
3453
- type: 'function'
3454
- };
3455
- };
3456
- const getGetWorkspaceUriTool = () => {
3457
- return {
3458
- function: {
3459
- description: 'Get the URI of the currently open workspace folder.',
3460
- name: 'getWorkspaceUri',
3461
- parameters: {
3462
- additionalProperties: false,
3463
- properties: {},
3464
- type: 'object'
3465
- }
3466
- },
3467
- type: 'function'
3468
- };
3469
- };
3470
- const getRenderHtmlTool = () => {
3471
3465
  return {
3472
- function: {
3473
- description: 'Render custom HTML and optional CSS directly in the chat tool call list using native chat UI rendering. Use this for structured cards, tables, and small dashboards. After calling this tool, do not repeat the same HTML, data table, or long content again as plain text unless the user explicitly asks for a text-only version.',
3474
- name: 'render_html',
3475
- parameters: {
3476
- additionalProperties: false,
3477
- properties: {
3478
- css: {
3479
- description: 'Optional CSS string applied inside the preview document.',
3480
- type: 'string'
3481
- },
3482
- html: {
3483
- description: 'HTML string to render in the preview document.',
3484
- type: 'string'
3485
- },
3486
- title: {
3487
- description: 'Optional short title for the preview.',
3488
- type: 'string'
3489
- }
3490
- },
3491
- required: ['html'],
3492
- type: 'object'
3493
- }
3494
- },
3495
- type: 'function'
3466
+ details: 'request-failed',
3467
+ type: 'error'
3496
3468
  };
3497
3469
  };
3498
- const getBasicChatTools = () => {
3499
- return [getReadFileTool(), getWriteFileTool(), getListFilesTool(), getGetWorkspaceUriTool(), getRenderHtmlTool()];
3500
- };
3501
3470
 
3502
- const getClientRequestIdHeader = () => {
3503
- return {
3504
- 'x-client-request-id': crypto.randomUUID()
3505
- };
3471
+ const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform) => {
3472
+ if (!mockApiCommandId) {
3473
+ return {
3474
+ details: 'request-failed',
3475
+ type: 'error'
3476
+ };
3477
+ }
3478
+ try {
3479
+ const result = await executeProvider({
3480
+ assetDir,
3481
+ event: `onCommand:${mockApiCommandId}`,
3482
+ method: CommandExecute,
3483
+ noProviderFoundMessage: 'No mock api command found',
3484
+ params: [mockApiCommandId, {
3485
+ messages,
3486
+ modelId,
3487
+ openRouterApiBaseUrl,
3488
+ openRouterApiKey
3489
+ }],
3490
+ platform
3491
+ });
3492
+ return normalizeMockResult(result);
3493
+ } catch {
3494
+ return {
3495
+ details: 'request-failed',
3496
+ type: 'error'
3497
+ };
3498
+ }
3506
3499
  };
3507
3500
 
3508
3501
  const getOpenApiApiEndpoint = openApiApiBaseUrl => {
3509
3502
  return `${openApiApiBaseUrl}/responses`;
3510
3503
  };
3511
3504
 
3505
+ const makeApiRequest = async options => {
3506
+ return invoke$2('ChatNetwork.makeApiRequest', options);
3507
+ };
3508
+ const makeStreamingApiRequest = async options => {
3509
+ return invoke$2('ChatNetwork.makeStreamingApiRequest', options);
3510
+ };
3511
+
3512
3512
  const getTextContent = content => {
3513
3513
  if (typeof content === 'string') {
3514
3514
  return content;
@@ -4933,6 +4933,17 @@ const isOpenRouterModel = (selectedModelId, models) => {
4933
4933
  return selectedModelId.toLowerCase().startsWith('openrouter/');
4934
4934
  };
4935
4935
 
4936
+ let requests = [];
4937
+ const reset$1 = () => {
4938
+ requests = [];
4939
+ };
4940
+ const capture = request => {
4941
+ requests = [...requests, request];
4942
+ };
4943
+ const getAll = () => {
4944
+ return requests;
4945
+ };
4946
+
4936
4947
  const getAiResponse = async ({
4937
4948
  assetDir,
4938
4949
  messageId,
@@ -4962,6 +4973,22 @@ const getAiResponse = async ({
4962
4973
  const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
4963
4974
  if (usesOpenApiModel) {
4964
4975
  if (useMockApi) {
4976
+ const openAiInput = messages.map(message => ({
4977
+ content: message.text,
4978
+ role: message.role
4979
+ }));
4980
+ const modelId = getOpenApiModelId(selectedModelId);
4981
+ const headers = {
4982
+ Authorization: `Bearer ${openApiApiKey}`,
4983
+ 'Content-Type': 'application/json',
4984
+ ...getClientRequestIdHeader()
4985
+ };
4986
+ capture({
4987
+ headers,
4988
+ method: 'POST',
4989
+ payload: getOpenAiParams(openAiInput, modelId, streamingEnabled, passIncludeObfuscation, getBasicChatTools(), webSearchEnabled),
4990
+ url: getOpenApiApiEndpoint(openApiApiBaseUrl)
4991
+ });
4965
4992
  const result = await getMockOpenApiAssistantText(streamingEnabled, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished);
4966
4993
  if (result.type === 'success') {
4967
4994
  const {
@@ -6484,6 +6511,15 @@ const loadContent = async (state, savedState) => {
6484
6511
  };
6485
6512
  };
6486
6513
 
6514
+ const mockOpenApiRequestGetAll = _state => {
6515
+ return getAll();
6516
+ };
6517
+
6518
+ const mockOpenApiRequestReset = state => {
6519
+ reset$1();
6520
+ return state;
6521
+ };
6522
+
6487
6523
  const mockOpenApiSetHttpErrorResponse = (state, statusCode, body) => {
6488
6524
  setHttpErrorResponse(statusCode, body);
6489
6525
  return state;
@@ -6500,7 +6536,7 @@ const mockOpenApiStreamPushChunk = (state, chunk) => {
6500
6536
  };
6501
6537
 
6502
6538
  const mockOpenApiStreamReset = state => {
6503
- reset$1();
6539
+ reset$2();
6504
6540
  return state;
6505
6541
  };
6506
6542
 
@@ -6539,7 +6575,7 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
6539
6575
  };
6540
6576
 
6541
6577
  const registerMockResponse = (state, mockResponse) => {
6542
- reset$1();
6578
+ reset$2();
6543
6579
  pushChunk(mockResponse.text);
6544
6580
  finish();
6545
6581
  return state;
@@ -8282,6 +8318,8 @@ const commandMap = {
8282
8318
  'Chat.initialize': initialize,
8283
8319
  'Chat.loadContent': wrapCommand(loadContent),
8284
8320
  'Chat.loadContent2': wrapCommand(loadContent),
8321
+ 'Chat.mockOpenApiRequestGetAll': wrapGetter(mockOpenApiRequestGetAll),
8322
+ 'Chat.mockOpenApiRequestReset': wrapCommand(mockOpenApiRequestReset),
8285
8323
  'Chat.mockOpenApiSetHttpErrorResponse': wrapCommand(mockOpenApiSetHttpErrorResponse),
8286
8324
  'Chat.mockOpenApiStreamFinish': wrapCommand(mockOpenApiStreamFinish),
8287
8325
  'Chat.mockOpenApiStreamPushChunk': wrapCommand(mockOpenApiStreamPushChunk),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",