diff --git a/package.json b/package.json index 3bf8786e..4b61a367 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "eslint-plugin-header": "^3.1.1", "indefinite": "*", "lodash": "^4.17.21", + "openai": "^4.30.0", "react": "*", "react-dom": "*", "react-draggable": "^4.4.6", diff --git a/public/index.html b/public/index.html index 6aaef3c2..6d93b9a9 100644 --- a/public/index.html +++ b/public/index.html @@ -4,7 +4,7 @@ + content="default-src 'self' https://api.openai.com/v1/chat/completions; img-src 'self' * data:; font-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; media-src 'none' ; frame-src 'none';" /> diff --git a/src/components/application/ApplicationInfo/index.tsx b/src/components/application/ApplicationInfo/index.tsx index 9871d82c..e8f18116 100644 --- a/src/components/application/ApplicationInfo/index.tsx +++ b/src/components/application/ApplicationInfo/index.tsx @@ -35,6 +35,7 @@ const ApplicationInfo: FC = ({ const [editMode, setEditMode] = useState(!applicationInfo.name && !applicationInfo.description ); const [content, setContent] = useState(''); const [name, setName] = useState(''); + const [openAIKey, setOpenAIKey] = useState(''); const [securityCategory, setSecurityCategory] = useState('CCCS Medium'); const [checkedIaaS, setCheckedIaaS] = useState(false); const [checkedPaaS, setCheckedPaaS] = useState(false); @@ -54,6 +55,7 @@ const ApplicationInfo: FC = ({ ...prev, description: content, name, + openAIKey, securityCategory: securityCategory, useIaaS: checkedIaaS, usePaaS: checkedPaaS, @@ -67,11 +69,12 @@ const ApplicationInfo: FC = ({ setEditMode(false); }, [checkedApplication, checkedCompute, checkedData, checkedIaaS, checkedNetwork, checkedPaaS, checkedSaaS, - checkedStorage, content, name, securityCategory, setApplicationInfo]); + checkedStorage, content, name, openAIKey, securityCategory, setApplicationInfo]); const handleEdit = useCallback(() => { setContent(applicationInfo.description || ''); setName(applicationInfo.name || ''); + setOpenAIKey(applicationInfo.openAIKey || ''); setSecurityCategory(applicationInfo.securityCategory || 'CCCS Medium'); setCheckedIaaS(applicationInfo.useIaaS || false); setCheckedPaaS(applicationInfo.usePaaS || false); @@ -201,6 +204,18 @@ const ApplicationInfo: FC = ({ Network + + + setOpenAIKey(event.detail.value) + } + validateData={ApplicationInfoSchema.shape.openAIKey.safeParse} + placeholder='Enter OpenAI API key' + /> + ) : ( diff --git a/src/components/generic/Flow/Nodes/ProcessNode.tsx b/src/components/generic/Flow/Nodes/ProcessNode.tsx index 0ee9fd57..418d14e7 100644 --- a/src/components/generic/Flow/Nodes/ProcessNode.tsx +++ b/src/components/generic/Flow/Nodes/ProcessNode.tsx @@ -26,7 +26,6 @@ export default memo(({ data, selected }: { data: any; selected: boolean }) => { id="top-source" position={Position.Top} style={{ background: '#555' }} - onConnect={(params) => console.log('handle onConnect', params)} isConnectable={true} /> { id="left-source" position={Position.Left} style={{ background: '#555' }} - onConnect={(params) => console.log('handle onConnect', params)} isConnectable={true} /> diff --git a/src/components/generic/Flow/Threats/ThreatList.tsx b/src/components/generic/Flow/Threats/ThreatList.tsx index 86d29658..9e4a57bb 100644 --- a/src/components/generic/Flow/Threats/ThreatList.tsx +++ b/src/components/generic/Flow/Threats/ThreatList.tsx @@ -26,7 +26,8 @@ import { PropertyFilter, } from '@cloudscape-design/components'; import { columnDefinitions, getMatchesCountText, paginationLabels, collectionPreferencesProps, filteringConstants, filteringProperties } from './table-config'; -import { useThreatsContext } from '../../../../contexts/ThreatsContext'; +import { useThreatsContext, useApplicationInfoContext } from '../../../../contexts'; +import OpenAIModal from '../../OpenAIModal'; function EmptyState({ title, subtitle, action }) { return ( @@ -42,9 +43,12 @@ function EmptyState({ title, subtitle, action }) { ); } -export default memo(({ threats, component, changeHandler }: { threats: any; component: any; changeHandler: any } ) => { +export default memo(({ threats, component, changeHandler, flow }: { threats: any; component: any; changeHandler: any; flow: any }) => { const [data, setData] = useState((component && component.data) || {}); + const [AIModalVisable, setAIModalVisable] = useState(false); + + const { applicationInfo } = useApplicationInfoContext(); useEffect(() => { setData((component && component.data) || {}); @@ -97,7 +101,7 @@ export default memo(({ threats, component, changeHandler }: { threats: any; comp subtitle="" action={ } /> @@ -117,48 +121,54 @@ export default memo(({ threats, component, changeHandler }: { threats: any; comp } return ( - - Threats - - } - columnDefinitions={columnDefinitions} - visibleColumns={preferences.visibleContent} - items={items} - trackBy="id" - selectedItems={data.threats} - onSelectionChange={(e) => updateData('threats', e.detail.selectedItems, [])} - stickyHeader - resizableColumns - wrapLines - stripedRows - pagination={ - - } - preferences={ - setPreferences(detail)} - /> - } - filter={ - - } - /> + <> +
setAIModalVisable(true)}>Generate : null) + } + counter={ + data.threats?.length + ? `(${data.threats.length}/${threats.length})` + : `(${threats.length})` + } + > + Threats + + } + columnDefinitions={columnDefinitions} + visibleColumns={preferences.visibleContent} + items={items} + trackBy="id" + selectedItems={data.threats} + onSelectionChange={(e) => updateData('threats', e.detail.selectedItems, [])} + stickyHeader + resizableColumns + wrapLines + stripedRows + pagination={ + + } + preferences={ + setPreferences(detail)} + /> + } + filter={ + + } + /> + + ); }); \ No newline at end of file diff --git a/src/components/generic/Flow/index.tsx b/src/components/generic/Flow/index.tsx index 9d8d40b6..09f949b4 100644 --- a/src/components/generic/Flow/index.tsx +++ b/src/components/generic/Flow/index.tsx @@ -22,6 +22,8 @@ import SpaceBetween from '@cloudscape-design/components/space-between'; import { v4 } from 'uuid'; import 'reactflow/dist/style.css'; +import { LOCAL_STORAGE_KEY_DATA_FLOW_DIAGRAM } from '../../../configs/localStorageKeys'; + import ActorNode from './Nodes/ActorNode'; import DatastoreNode from './Nodes/DatastoreNode'; import ProcessNode from './Nodes/ProcessNode'; @@ -37,7 +39,6 @@ import ZIndexChanger from './Nodes/ZIndexChanger'; import ThreatList from './Threats/ThreatList'; import { useThreatsContext } from '../../../contexts'; -import { useWorkspacesContext } from '../../../contexts/WorkspacesContext'; const edgeTypes = { biDirectional: BiDirectionalEdge, @@ -63,9 +64,6 @@ namespace s { } function Flow() { - const { currentWorkspace } = useWorkspacesContext(); - const flowKey = `dataflow-diagram-${currentWorkspace?.id}`; - const { zoomTo, getZoom, setViewport } = useReactFlow(); // Save and restore state @@ -75,15 +73,15 @@ function Flow() { const onSave = useCallback(() => { if (rfInstance) { const flow = rfInstance.toObject(); - localStorage.setItem(flowKey, JSON.stringify(flow)); + localStorage.setItem(LOCAL_STORAGE_KEY_DATA_FLOW_DIAGRAM, JSON.stringify(flow)); setSaveState(true); } - }, [rfInstance, flowKey]); + }, [rfInstance]); const onInit = async (instance) => { setRfInstance(instance); const restoreFlow = async () => { - const flow = JSON.parse(localStorage.getItem(flowKey) as string); + const flow = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY_DATA_FLOW_DIAGRAM) as string); if (flow) { const { x = 0, y = 0, zoom = 1 } = flow.viewport; @@ -275,7 +273,8 @@ function Flow() { + changeHandler={setNodeDataValue} + flow={rfInstance?.toObject()}/> ); } diff --git a/src/components/generic/OpenAIModal/index.tsx b/src/components/generic/OpenAIModal/index.tsx new file mode 100644 index 00000000..b0a41f52 --- /dev/null +++ b/src/components/generic/OpenAIModal/index.tsx @@ -0,0 +1,111 @@ +import { memo, useEffect, useState } from 'react'; +import { + Modal, + Header, + Alert, + Box, + Table, + SpaceBetween, + Button, +} from '@cloudscape-design/components'; + +import OpenAI from 'openai'; +import { v4 } from 'uuid'; + +import { useThreatsContext } from '../../../contexts/ThreatsContext/context'; + +const prompt = () => { + return `You are an assitant helping to generate threat statements based on the STRIDE threat modeling framework. Threat statements are structured in a using a specific grammar. The grammar we are using is as follows: [threat source] [prerequisites] can [threat action] which leads to [threat impact], negatively impacting [impacted assets]. We are also using a data flow diagram to help generate these statements. I will provide you with information about the impacted asset in a json format, which is described either as an actor, a process, a datastore, or an edge linking any of those elements. I will also provide you with additional information about the impacted asset such as data features, technology features, security features, and tags. Based on this information and the general context of the diagram, you will generate a threat statement. Please ensure the statement follows the grammar provided. You do not need to include [] in the statement. Ensure that the threat statement you return makes logical sense in the context of threat modeling. Please always return one threat statement in a JSON array of objects that follow this structure: {threatSource: '', prerequisites: '', threatAction: '', threatImpact: '', impactedGoal: [], impactedAssets: [], statement: '', displayedStatement: []}. For example, assume you generate a threat statement as follows: + + A threat actor with access to logs can access sensitive data that was not excluded or redacted, which leads to the ability to modify data, resulting in reduced confidentiality of file upload, file listing, upload status, uploaded files and authentication tokens + + You would return: + + [{"statement":"An external threat actor who is authenticated and authorized can flood the legitimate user's internet link, which leads to unnecessary and excessive costs, resulting in reduced availability of billing","threatSource":"external threat actor","prerequisites":"who is authenticated and authorized","displayedStatement":["An ",{"type":"span","content":"external threat actor","tooltip":"threat source"}," ",{"type":"span","content":"who is authenticated and authorized","tooltip":"prerequisites"}," can ",{"type":"b","content":"flood the legitimate user's internet link","tooltip":"threat action"},", which leads to ",{"type":"span","content":"unnecessary and excessive costs","tooltip":"threat impact"},", resulting in reduced ",{"type":"span","content":"availability","tooltip":"impacted goal"}," of ",{"type":"span","content":"billing","tooltip":"impacted assets"}],"threatAction":"flood the legitimate user's internet link","threatImpact":"unnecessary and excessive costs","impactedAssets":["billing"],"impactedGoal":["availability"]}] + + ONLY RETURN VALID JSON THAT IS PARSEABLE BY A JAVASCRIPT JSON PARSER. DO NOT INCLUDE \`\`\`json TAGS IN YOUR RESPONSE. DO NOT INCLUDE ANYTHING OTHER THAN THE JSON ARRAY OF OBJECTS. + `; +}; + +export default memo(({ visible, setVisible, data, flow, apiKey }: { visible: boolean; setVisible: any; data: any; flow: any; apiKey: any }) => { + + const [loading, setLoading] = useState(false); + const [threats, setThreats] = useState([]); + const [error, setError] = useState(null); + const [selectedItems, setSelectedItems] = useState<{ statement: string }[]>([]); + + const { saveStatement } = useThreatsContext(); + + useEffect(() => { + if (visible) { + const openai = new OpenAI({ apiKey: apiKey, dangerouslyAllowBrowser: true }); + setError(null); + setLoading(true); + openai.chat.completions.create({ + messages: [ + { role: 'system', content: prompt() }, + { role: 'assistant', content: 'Generate a threat statement for the impacted asset.' }, + { role: 'user', content: `Here is the information about the impacted asset in json format: ${JSON.stringify(data)} Here is the data flow diagram in json format: ${JSON.stringify(flow)}` }, + ], + model: 'gpt-4o', + }).then((response) => { + let content = response.choices[0].message.content || '[]'; + setThreats(JSON.parse(content).map((item) => ({ id: v4(), ...item })) || []); + setLoading(false); + return response; + }).catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + }, [visible, apiKey, data, flow]); + + const saveSuggestedThreats = () => { + selectedItems.forEach((item) => { + let s = { + id: v4(), + numericId: -1, + ...item, + }; + saveStatement(s); + }); + setVisible(false); + }; + + + return AI generated threat statements} + visible={visible} + onDismiss={() => setVisible(false)} + size='large' + footer={ + + + + + + } + > + {(error ? + {error} + : +
item.statement || '-' }]} + enableKeyboardNavigation + items={threats} + loading={loading} + loadingText='Loading statements...' + sortingDisabled + trackBy="id" + variant='embedded' + selectedItems={selectedItems} + onSelectionChange={({ detail }) => setSelectedItems(detail.selectedItems)} + selectionType="multi" + empty={} + wrapLines /> + )} + ; +}); diff --git a/src/configs/localStorageKeys.ts b/src/configs/localStorageKeys.ts index dad08d7d..db2fa04e 100644 --- a/src/configs/localStorageKeys.ts +++ b/src/configs/localStorageKeys.ts @@ -35,5 +35,7 @@ export const LOCAL_STORAGE_KEY_ARCHIECTURE_INFO = 'ThreatStatementGenerator.Arch export const LOCAL_STORAGE_KEY_DATAFLOW_INFO = 'ThreatStatementGenerator.DataflowInfo'; export const LOCAL_STORAGE_KEY_DIAGRAM_INFO = 'ThreatStatementGenerator.DiagramInfo'; +export const LOCAL_STORAGE_KEY_DATA_FLOW_DIAGRAM = 'ThreatStatementGenerator.DataFlowDiagram'; + export const LOCAL_STORAGE_KEY_WORKSPACE_LIST_MIGRATION = 'ThreatStatementGenerator.workspaceListMigration'; export const LOCAL_STORAGE_KEY_THREATS_LIST_MIGRATION = 'ThreatStatementGenerator.threatListMigration'; diff --git a/src/contexts/ApplicationContext/context.ts b/src/contexts/ApplicationContext/context.ts index 2fda1250..00a2be85 100644 --- a/src/contexts/ApplicationContext/context.ts +++ b/src/contexts/ApplicationContext/context.ts @@ -27,6 +27,7 @@ export interface ApplicationInfoContextApi { const initialState: ApplicationInfoContextApi = { applicationInfo: { description: '', + openAIKey: '', securityCategory: 'CCCS Medium', useIaaS: false, usePaaS: false, diff --git a/src/customTypes/application.ts b/src/customTypes/application.ts index 0387fb02..27f1d729 100644 --- a/src/customTypes/application.ts +++ b/src/customTypes/application.ts @@ -23,6 +23,7 @@ export const ApplicationInfoSchema = z.object({ */ name: z.string().max(SINGLE_FIELD_INPUT_MAX_LENGTH).optional(), description: z.string().max(FREE_TEXT_INPUT_MAX_LENGTH).optional(), + openAIKey: z.string().max(SINGLE_FIELD_INPUT_MAX_LENGTH).optional(), securityCategory: z.string().optional(), useIaaS: z.boolean().optional(), usePaaS: z.boolean().optional(), diff --git a/yarn.lock b/yarn.lock index db0f810d..2e022dcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4811,6 +4811,14 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== +"@types/node-fetch@^2.6.4": + version "2.6.11" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" + integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -4830,6 +4838,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== +"@types/node@^18.11.18": + version "18.19.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.26.tgz#18991279d0a0e53675285e8cf4a0823766349729" + integrity sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw== + dependencies: + undici-types "~5.26.4" + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -5978,6 +5993,11 @@ balanced-match@^1.0.0, balanced-match@^1.0.2: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== + base64-js@^1.0.2, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -6388,6 +6408,11 @@ character-reference-invalid@^2.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + check-types@^11.2.3: version "11.2.3" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.3.tgz#1ffdf68faae4e941fce252840b1787b8edc93b71" @@ -6858,6 +6883,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -7631,6 +7661,14 @@ diff@^5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +digest-fetch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661" + integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== + dependencies: + base-64 "^0.1.0" + md5 "^2.3.0" + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -8776,6 +8814,11 @@ fork-ts-checker-webpack-plugin@^6.5.0: semver "^7.3.2" tapable "^1.0.0" +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + form-data-encoder@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" @@ -8790,11 +8833,28 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + format@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -9815,6 +9875,11 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -11641,6 +11706,15 @@ marky@^1.2.2: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdast-util-definitions@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7" @@ -13067,7 +13141,12 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12: +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -13443,6 +13522,21 @@ open@^8.0.9, open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +openai@^4.30.0: + version "4.30.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.30.0.tgz#fafa30cc8d2a8570f258f0a8609ec4a3080022fa" + integrity sha512-jOaT4u7DpWzvuOuSxw5skuBbSuagw91Vd4IU/zP9qdgu6C19AAq4wzx+24e59wdEmsAG58enOUNf6t5V2WwN9g== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + web-streams-polyfill "^3.2.1" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -17273,6 +17367,16 @@ web-namespaces@^2.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +web-streams-polyfill@^3.2.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + web-vitals@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-3.5.2.tgz#5bb58461bbc173c3f00c2ddff8bfe6e680999ca9"