diff --git a/components/script/messages.tsx b/components/script/messages.tsx index c0bcaf90..96f4f73a 100644 --- a/components/script/messages.tsx +++ b/components/script/messages.tsx @@ -39,6 +39,10 @@ const Message = ({ noAvatar?: boolean; restart?: () => void; }) => { + if (message === undefined) { + return null; + } + switch (message.type) { case MessageType.User: return ( diff --git a/components/script/messages/confirmForm.tsx b/components/script/messages/confirmForm.tsx index 52d30ad4..e712b7da 100644 --- a/components/script/messages/confirmForm.tsx +++ b/components/script/messages/confirmForm.tsx @@ -46,7 +46,7 @@ const ConfirmForm = ({ {message} {command && ( - + {command.startsWith('Running') ? command.replace('Running', '').replace(/`/g, '') : command.replace(/`/g, '')} diff --git a/components/script/useChatSocket.tsx b/components/script/useChatSocket.tsx index 7211125d..db9799b5 100644 --- a/components/script/useChatSocket.tsx +++ b/components/script/useChatSocket.tsx @@ -33,6 +33,8 @@ const useChatSocket = (isEmpty?: boolean) => { const latestAgentMessageIndex = useRef(-1); const trustedRef = useRef>({}); const trustedRepoPrefixesRef = useRef([...initiallyTrustedRepos]); + // trustedOpenAPIRef contains a mapping of OpenAPI run tools to OpenAPI operation names that have been trusted. + const trustedOpenAPIRef = useRef>>({}); // update the refs as the state changes useEffect(() => { @@ -201,7 +203,9 @@ const useChatSocket = (isEmpty?: boolean) => { } let confirmMessage = `Proceed with calling the ${frame.tool.name} tool?`; - if (frame.displayText) { + if (frame.tool.instructions?.startsWith('#!sys.openapi')) { + confirmMessage = `Proceed with running the following API operation (or allow all runs of this operation)?`; + } else if (frame.displayText) { const tool = frame.tool?.name?.replace('sys.', ''); confirmMessage = frame.tool?.source?.repo ? `Proceed with running the following (or allow all calls from the **${trimRepo(frame.tool?.source.repo!.Root)}** repo)?` @@ -278,6 +282,28 @@ const useChatSocket = (isEmpty?: boolean) => { const alreadyAllowed = (frame: CallFrame): boolean => { if (!frame.tool) return false; + if (frame.tool.instructions?.startsWith('#!sys.openapi')) { + // If the tool is an OpenAPI tool to list operations or get the schema for an operation, allow it. + const instructions = frame.tool.instructions.split(' '); + if ( + instructions.length > 2 && + (instructions[1] == 'list' || instructions[1] == 'get-schema') + ) { + return true; + } + + // If the tool is an OpenAPI tool to run an operation, check if it has been trusted. + if (!frame.tool.name) { + return false; + } + + const operation = JSON.parse(frame.input as string)['operation']; + return ( + trustedOpenAPIRef.current[frame.tool.name] && + trustedOpenAPIRef.current[frame.tool.name][operation] + ); + } + // Check if the tool has already been allowed if (frame.tool.name && trustedRef.current[frame.tool.name]) return true; @@ -304,6 +330,22 @@ const useChatSocket = (isEmpty?: boolean) => { const addTrustedFor = (frame: CallFrame) => { if (!frame.tool) return () => {}; + if ( + frame.tool.instructions && + frame.tool.name && + frame.tool.instructions.startsWith('#!sys.openapi') + ) { + return () => { + const toolName = frame.tool?.name ?? ''; // Not possible for this to be null, but I have to satisfy the type checker. + const operation = JSON.parse(frame.input as string)['operation']; + + if (!trustedOpenAPIRef.current[toolName]) { + trustedOpenAPIRef.current[toolName] = {}; + } + trustedOpenAPIRef.current[toolName][operation] = true; + }; + } + return frame.tool.source?.repo ? () => { const repo = frame.tool!.source?.repo!.Root;