diff --git a/package.json b/package.json index 53cd9c8..8988a99 100644 --- a/package.json +++ b/package.json @@ -482,15 +482,15 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@hookform/resolvers": "^3.9.1", + "@hookform/resolvers": "^3.10.0", "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "@langchain/anthropic": "^0.3.11", - "@langchain/community": "^0.3.20", - "@langchain/core": "0.3.26", - "@langchain/langgraph": "^0.2.36", + "@langchain/community": "^0.3.22", + "@langchain/core": "0.3.27", + "@langchain/langgraph": "^0.2.39", "@langchain/openai": "^0.3.16", "@langchain/textsplitters": "^0.1.0", - "@lexical/react": "^0.22.0", + "@lexical/react": "^0.23.0", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-checkbox": "^1.1.3", @@ -512,13 +512,13 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.6", "@radix-ui/react-visually-hidden": "^1.1.1", - "@tanstack/react-query": "^5.62.11", + "@tanstack/react-query": "^5.62.16", "@tomjs/vite-plugin-vscode": "^3.2.1", - "@types/diff": "^6.0.0", + "@types/diff": "^7.0.0", "@types/fs-extra": "^11.0.4", "@types/global-agent": "^2.1.3", - "@types/node": "^22.10.2", - "@types/react": "^19.0.2", + "@types/node": "^22.10.5", + "@types/react": "^19.0.3", "@types/react-dom": "^19.0.2", "@types/shell-quote": "^1.7.5", "@types/turndown": "^5.0.5", @@ -542,19 +542,19 @@ "commitizen": "^4.3.1", "cpy": "10.1.0", "diff": "^7.0.0", - "es-toolkit": "^1.30.1", + "es-toolkit": "^1.31.0", "eslint": "^8.57.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-alias": "^1.1.2", - "eslint-import-resolver-typescript": "^3.6.3", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-json": "^4.0.1", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react-compiler": "latest", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unused-imports": "^3.2.0", + "eslint-plugin-unused-imports": "^4.1.4", "esno": "^4.8.0", "eventemitter3": "^5.0.1", "execa": "^9.5.2", @@ -571,8 +571,8 @@ "inquirer": "^9.3.4", "js-tiktoken": "^1.0.16", "knip": "^5.41.1", - "langchain": "^0.3.8", - "lexical": "^0.22.0", + "langchain": "^0.3.10", + "lexical": "^0.23.0", "lint-staged": "^15.3.0", "lowdb": "^7.0.1", "lucide-react": "^0.469.0", @@ -580,15 +580,15 @@ "minimatch": "^10.0.1", "next-themes": "^0.4.4", "p-limit": "^6.2.0", - "pnpm": "^9.15.2", + "pnpm": "^9.15.3", "postcss": "^8.4.49", "prettier": "^3.4.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-error-boundary": "^5.0.0", "react-hook-form": "^7.54.2", - "react-intersection-observer": "^9.14.0", - "react-markdown": "^9.0.1", + "react-intersection-observer": "^9.14.1", + "react-markdown": "^9.0.3", "react-resizable-panels": "^2.1.7", "react-router": "^7.1.1", "react-router-dom": "^7.1.1", @@ -602,7 +602,7 @@ "rimraf": "^6.0.1", "scroll-into-view-if-needed": "^3.1.0", "shell-quote": "^1.8.2", - "shiki": "^1.24.4", + "shiki": "^1.26.1", "simple-git": "^3.27.0", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", @@ -621,25 +621,25 @@ "unified": "^11.0.5", "use-immer": "^0.11.0", "use-resize-observer": "^9.1.0", - "uuid": "^11.0.3", + "uuid": "^11.0.4", "vaul": "^1.1.2", "vectordb": "^0.14.1", - "vite": "^6.0.6", + "vite": "^6.0.7", "vite-plugin-pages": "^0.32.4", "vite-plugin-svgr": "^4.3.0", "vite-tsconfig-paths": "^5.1.4", "vitest": "^2.1.8", "web-tree-sitter": "^0.24.6", "zod": "^3.24.1", - "zustand": "^5.0.2" + "zustand": "^5.0.3" }, "pnpm": { "overrides": { - "@langchain/core": "0.3.26", + "@langchain/core": "0.3.27", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", - "lexical": "^0.22.0", - "shiki": "^1.24.4" + "lexical": "^0.23.0", + "shiki": "^1.26.1" } }, "commitlint": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54304c0..173a773 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,11 +5,11 @@ settings: excludeLinksFromLockfile: false overrides: - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 '@types/react': ^19.0.2 '@types/react-dom': ^19.0.2 - lexical: ^0.22.0 - shiki: ^1.24.4 + lexical: ^0.23.0 + shiki: ^1.26.1 importers: @@ -20,13 +20,13 @@ importers: version: 0.1.9 '@commitlint/cli': specifier: ^19.6.1 - version: 19.6.1(@types/node@22.10.2)(typescript@5.7.2) + version: 19.6.1(@types/node@22.10.5)(typescript@5.7.2) '@commitlint/config-conventional': specifier: ^19.6.0 version: 19.6.0 '@commitlint/cz-commitlint': specifier: ^19.6.1 - version: 19.6.1(@types/node@22.10.2)(commitizen@4.3.1(@types/node@22.10.2)(typescript@5.7.2))(inquirer@9.3.4)(typescript@5.7.2) + version: 19.6.1(@types/node@22.10.5)(commitizen@4.3.1(@types/node@22.10.5)(typescript@5.7.2))(inquirer@9.3.4)(typescript@5.7.2) '@cyntler/react-doc-viewer': specifier: ^1.17.0 version: 1.17.0(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -40,32 +40,32 @@ importers: specifier: ^3.2.2 version: 3.2.2(react@19.0.0) '@hookform/resolvers': - specifier: ^3.9.1 - version: 3.9.1(react-hook-form@7.54.2(react@19.0.0)) + specifier: ^3.10.0 + version: 3.10.0(react-hook-form@7.54.2(react@19.0.0)) '@ianvs/prettier-plugin-sort-imports': specifier: ^4.4.0 version: 4.4.0(@vue/compiler-sfc@3.4.36)(prettier@3.4.2) '@langchain/anthropic': specifier: ^0.3.11 - version: 0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + version: 0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) '@langchain/community': - specifier: ^0.3.20 - version: 0.3.20(@browserbasehq/sdk@2.0.0)(@browserbasehq/stagehand@1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(openai@4.77.0(zod@3.24.1))(zod@3.24.1))(@ibm-cloud/watsonx-ai@1.1.1)(@langchain/anthropic@0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(@xenova/transformers@2.17.2)(axios@1.7.4)(cheerio@1.0.0)(fast-xml-parser@4.4.1)(ibm-cloud-sdk-core@5.1.0)(ignore@7.0.0)(jsonwebtoken@9.0.2)(lodash@4.17.21)(openai@4.77.0(zod@3.24.1))(playwright@1.49.1)(ws@8.18.0) + specifier: ^0.3.22 + version: 0.3.22(@browserbasehq/sdk@2.0.0)(@browserbasehq/stagehand@1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(openai@4.77.0(zod@3.24.1))(zod@3.24.1))(@ibm-cloud/watsonx-ai@1.1.1)(@langchain/anthropic@0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(fast-xml-parser@4.4.1)(ibm-cloud-sdk-core@5.1.0)(ignore@7.0.0)(jsonwebtoken@9.0.2)(lodash@4.17.21)(openai@4.77.0(zod@3.24.1))(playwright@1.49.1)(ws@8.18.0) '@langchain/core': - specifier: 0.3.26 - version: 0.3.26(openai@4.77.0(zod@3.24.1)) + specifier: 0.3.27 + version: 0.3.27(openai@4.77.0(zod@3.24.1)) '@langchain/langgraph': - specifier: ^0.2.36 - version: 0.2.36(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + specifier: ^0.2.39 + version: 0.2.39(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) '@langchain/openai': specifier: ^0.3.16 - version: 0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + version: 0.3.16(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) '@langchain/textsplitters': specifier: ^0.1.0 - version: 0.1.0(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + version: 0.1.0(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) '@lexical/react': - specifier: ^0.22.0 - version: 0.22.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.18) + specifier: ^0.23.0 + version: 0.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.18) '@radix-ui/react-accordion': specifier: ^1.2.2 version: 1.2.2(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -130,14 +130,14 @@ importers: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/react-query': - specifier: ^5.62.11 - version: 5.62.11(react@19.0.0) + specifier: ^5.62.16 + version: 5.62.16(react@19.0.0) '@tomjs/vite-plugin-vscode': specifier: ^3.2.1 - version: 3.2.1(@swc/core@1.7.10)(postcss@8.4.49)(typescript@5.7.2)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) + version: 3.2.1(@swc/core@1.7.10)(postcss@8.4.49)(typescript@5.7.2)(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) '@types/diff': - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^7.0.0 + version: 7.0.0 '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 @@ -145,8 +145,8 @@ importers: specifier: ^2.1.3 version: 2.1.3 '@types/node': - specifier: ^22.10.2 - version: 22.10.2 + specifier: ^22.10.5 + version: 22.10.5 '@types/react': specifier: ^19.0.2 version: 19.0.2 @@ -173,7 +173,7 @@ importers: version: 7.18.0(eslint@8.57.0)(typescript@5.7.2) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) + version: 4.3.4(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) '@vscode/vsce': specifier: ^3.2.1 version: 3.2.1 @@ -212,7 +212,7 @@ importers: version: 4.2.5 commitizen: specifier: ^4.3.1 - version: 4.3.1(@types/node@22.10.2)(typescript@5.7.2) + version: 4.3.1(@types/node@22.10.5)(typescript@5.7.2) cpy: specifier: 10.1.0 version: 10.1.0 @@ -220,29 +220,29 @@ importers: specifier: ^7.0.0 version: 7.0.0 es-toolkit: - specifier: ^1.30.1 - version: 1.30.1 + specifier: ^1.31.0 + version: 1.31.0 eslint: specifier: ^8.57.0 version: 8.57.0 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.35.0(eslint@8.57.0))(eslint@8.57.0) + version: 19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.35.0(eslint@8.57.0))(eslint@8.57.0) eslint-config-airbnb-typescript: specifier: ^18.0.0 - version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0) + version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint@8.57.0) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.0) eslint-import-resolver-alias: specifier: ^1.1.2 - version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)) + version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)) eslint-import-resolver-typescript: - specifier: ^3.6.3 - version: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0) + specifier: ^3.7.0 + version: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) eslint-plugin-json: specifier: ^4.0.1 version: 4.0.1 @@ -251,13 +251,13 @@ importers: version: 5.2.1(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.4.2) eslint-plugin-react-compiler: specifier: latest - version: 19.0.0-beta-b2e8e9c-20241220(eslint@8.57.0) + version: 19.0.0-beta-63e3235-20250105(eslint@8.57.0) eslint-plugin-simple-import-sort: specifier: ^12.1.1 version: 12.1.1(eslint@8.57.0) eslint-plugin-unused-imports: - specifier: ^3.2.0 - version: 3.2.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0) + specifier: ^4.1.4 + version: 4.1.4(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0) esno: specifier: ^4.8.0 version: 4.8.0 @@ -305,13 +305,13 @@ importers: version: 1.0.16 knip: specifier: ^5.41.1 - version: 5.41.1(@types/node@22.10.2)(typescript@5.7.2) + version: 5.41.1(@types/node@22.10.5)(typescript@5.7.2) langchain: - specifier: ^0.3.8 - version: 0.3.8(@langchain/anthropic@0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(openai@4.77.0(zod@3.24.1)) + specifier: ^0.3.10 + version: 0.3.10(@langchain/anthropic@0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(openai@4.77.0(zod@3.24.1)) lexical: - specifier: ^0.22.0 - version: 0.22.0 + specifier: ^0.23.0 + version: 0.23.0 lint-staged: specifier: ^15.3.0 version: 15.3.0 @@ -334,8 +334,8 @@ importers: specifier: ^6.2.0 version: 6.2.0 pnpm: - specifier: ^9.15.2 - version: 9.15.2 + specifier: ^9.15.3 + version: 9.15.3 postcss: specifier: ^8.4.49 version: 8.4.49 @@ -355,11 +355,11 @@ importers: specifier: ^7.54.2 version: 7.54.2(react@19.0.0) react-intersection-observer: - specifier: ^9.14.0 - version: 9.14.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^9.14.1 + version: 9.14.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-markdown: - specifier: ^9.0.1 - version: 9.0.1(@types/react@19.0.2)(react@19.0.0) + specifier: ^9.0.3 + version: 9.0.3(@types/react@19.0.2)(react@19.0.0) react-resizable-panels: specifier: ^2.1.7 version: 2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -400,8 +400,8 @@ importers: specifier: ^1.8.2 version: 1.8.2 shiki: - specifier: ^1.24.4 - version: 1.24.4 + specifier: ^1.26.1 + version: 1.26.1 simple-git: specifier: ^3.27.0 version: 3.27.0 @@ -457,8 +457,8 @@ importers: specifier: ^9.1.0 version: 9.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) uuid: - specifier: ^11.0.3 - version: 11.0.3 + specifier: ^11.0.4 + version: 11.0.4 vaul: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -466,20 +466,20 @@ importers: specifier: ^0.14.1 version: 0.14.1(@apache-arrow/ts@14.0.2)(apache-arrow@18.1.0) vite: - specifier: ^6.0.6 - version: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) vite-plugin-pages: specifier: ^0.32.4 - version: 0.32.4(@vue/compiler-sfc@3.4.36)(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) + version: 0.32.4(@vue/compiler-sfc@3.4.36)(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.3.0(rollup@4.24.3)(typescript@5.7.2)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) + version: 4.3.0(rollup@4.24.3)(typescript@5.7.2)(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.7.2)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) + version: 5.1.4(typescript@5.7.2)(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)) vitest: specifier: ^2.1.8 - version: 2.1.8(@types/node@22.10.2)(less@4.2.0) + version: 2.1.8(@types/node@22.10.5)(less@4.2.0) web-tree-sitter: specifier: ^0.24.6 version: 0.24.6 @@ -487,8 +487,8 @@ importers: specifier: ^3.24.1 version: 3.24.1 zustand: - specifier: ^5.0.2 - version: 5.0.2(@types/react@19.0.2)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)) + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.0.2)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)) website: dependencies: @@ -507,7 +507,7 @@ importers: version: 1.1.44 '@nolebase/vitepress-plugin-inline-link-preview': specifier: ^2.4.0 - version: 2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) + version: 2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) '@unocss/preset-icons': specifier: ^0.61.9 version: 0.61.9 @@ -525,10 +525,10 @@ importers: version: 4.0.0 unocss: specifier: ^0.61.9 - version: 0.61.9(postcss@8.4.49)(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0)) + version: 0.61.9(postcss@8.4.49)(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0)) vitepress: specifier: 1.3.2 - version: 1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) + version: 1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) zod: specifier: 3.23.8 version: 3.23.8 @@ -1851,8 +1851,8 @@ packages: '@floating-ui/utils@0.2.7': resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==} - '@hookform/resolvers@3.9.1': - resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==} + '@hookform/resolvers@3.10.0': + resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: react-hook-form: ^7.0.0 @@ -2089,10 +2089,10 @@ packages: resolution: {integrity: sha512-rYjDZjMwVQ+cYeJd9IoSESdkkG8fc0m3siGRYKNy6qgYMnqCz8sUPKBanXwbZAs6wvspPCGgNK9WONfaCeX97A==} engines: {node: '>=18'} peerDependencies: - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 - '@langchain/community@0.3.20': - resolution: {integrity: sha512-5XmguFWVrfYJ8s9kHPAmC1bTGfdVOKqzWVCoTolSXMVMDaVn+LVvCJxEedO01kU1y2AS4pUl5MDI9wssKS1Ehg==} + '@langchain/community@0.3.22': + resolution: {integrity: sha512-RGfmG3sfJhVpdFHyPwdQhrzGXdGoCoBh6zWB79/4WaMVU0q0Uo1Y/NeeslSxp778sWEeJkRcXZC73dR8lwOFjw==} engines: {node: '>=18'} peerDependencies: '@arcjet/redact': ^v1.0.0-alpha.23 @@ -2123,9 +2123,10 @@ packages: '@google-cloud/storage': ^6.10.1 || ^7.7.0 '@gradientai/nodejs-sdk': ^1.2.0 '@huggingface/inference': ^2.6.4 + '@huggingface/transformers': ^3.2.3 '@ibm-cloud/watsonx-ai': '*' '@lancedb/lancedb': ^0.12.0 - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 '@layerup/layerup-security': ^1.5.12 '@libsql/client': ^0.14.0 '@mendable/firecrawl-js': ^1.4.3 @@ -2152,11 +2153,10 @@ packages: '@upstash/ratelimit': ^1.1.3 || ^2.0.3 '@upstash/redis': ^1.20.6 '@upstash/vector': ^1.1.1 - '@vercel/kv': ^0.2.3 - '@vercel/postgres': ^0.5.0 + '@vercel/kv': '*' + '@vercel/postgres': '*' '@writerai/writer-sdk': ^0.40.2 '@xata.io/client': ^0.28.0 - '@xenova/transformers': ^2.17.2 '@zilliz/milvus2-sdk-node': '>=2.3.5' apify-client: ^2.7.1 assemblyai: ^4.6.0 @@ -2218,6 +2218,7 @@ packages: voy-search: 0.6.2 weaviate-ts-client: '*' web-auth-library: ^1.0.3 + word-extractor: '*' ws: ^8.14.2 youtube-transcript: ^1.0.6 youtubei.js: ^9.1.0 @@ -2276,6 +2277,8 @@ packages: optional: true '@huggingface/inference': optional: true + '@huggingface/transformers': + optional: true '@lancedb/lancedb': optional: true '@layerup/layerup-security': @@ -2338,8 +2341,6 @@ packages: optional: true '@xata.io/client': optional: true - '@xenova/transformers': - optional: true '@zilliz/milvus2-sdk-node': optional: true apify-client: @@ -2458,6 +2459,8 @@ packages: optional: true web-auth-library: optional: true + word-extractor: + optional: true ws: optional: true youtube-transcript: @@ -2465,105 +2468,105 @@ packages: youtubei.js: optional: true - '@langchain/core@0.3.26': - resolution: {integrity: sha512-6RUQHEp8wv+JwtYIIEBYBzbLlcAQZFc7EDOgAM0ukExjh9HiXoJzoWpgMRRCrr/koIbtwXPJUqBprZK1I1CXHQ==} + '@langchain/core@0.3.27': + resolution: {integrity: sha512-jtJKbJWB1NPU1YvtrExOB2rumvUFgkJwlWGxyjSIV9A6zcLVmUbcZGV8fCSuXgl5bbzOIQLJ1xcLYQmbW9TkTg==} engines: {node: '>=18'} '@langchain/langgraph-checkpoint@0.0.13': resolution: {integrity: sha512-amdmBcNT8a9xP2VwcEWxqArng4gtRDcnVyVI4DsQIo1Aaz8e8+hH17zSwrUF3pt1pIYztngIfYnBOim31mtKMg==} engines: {node: '>=18'} peerDependencies: - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 - '@langchain/langgraph-sdk@0.0.23': - resolution: {integrity: sha512-4LfwMN1PdawJ9I3dXxQHUb1NoJaZo5SklQbAamrS6fLrUU9fSoYkPu1mYQp3uJjKtXRYOnuoP0egYyQPoKuiXQ==} + '@langchain/langgraph-sdk@0.0.33': + resolution: {integrity: sha512-l/hRbI6roLzplBXy2VyDUwqY1TkK7RcjPmrMUuVdvCCH4LTwLfIXh/G1kHatNiN7VUTskw0FkfBbgq6gtj0ang==} - '@langchain/langgraph@0.2.36': - resolution: {integrity: sha512-zxk7ZCVxP0/Ut9785EiXCS7BE7sXd8cu943mcZUF2aNFUaQRTBbbiKpNdR3nb1+xO/B+HVktrJT2VFdkAywnng==} + '@langchain/langgraph@0.2.39': + resolution: {integrity: sha512-zoQT5LViPlB5hRS7RNwixcAonUBAHcW+IzVkGR/4vcKoE49z5rPBdZsWjJ6b1YIV1K2bdSDJWl5KSEHilvnR1Q==} engines: {node: '>=18'} peerDependencies: - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 '@langchain/openai@0.3.16': resolution: {integrity: sha512-Om9HRlTeI0Ou6D4pfxbWHop4WGfkCdV/7v1W/+Jr7NSf0BNoA9jk5GqGms8ZtOYSGgPvizDu3i0TrM3B4cN4NA==} engines: {node: '>=18'} peerDependencies: - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 '@langchain/textsplitters@0.1.0': resolution: {integrity: sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==} engines: {node: '>=18'} peerDependencies: - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 - '@lexical/clipboard@0.22.0': - resolution: {integrity: sha512-nG3MX7MYmaf4f4Ia0YaYaJIS1QriuUxNaLHYeAqOPqYZLH1CUnXslCiak6OyQ4dt/QozKKvGM5vpZN2QWvs0Xw==} + '@lexical/clipboard@0.23.0': + resolution: {integrity: sha512-+MEdOajIXFp/5Q3dS3tj3PD3E6SCzf91E2AkNfN3oeeogDf04WG4e5Gx8NuXSGzpEZ8Rog28QDP6xQ8fCzwaTg==} - '@lexical/code@0.22.0': - resolution: {integrity: sha512-5HQGcv+R7+uOxFGGqGxE5Mj2LcYwxniHejri7s3OJTSeFrGxAXwP2Kxb+5FSvWsyg265snuxZlNNfKVozgJPhA==} + '@lexical/code@0.23.0': + resolution: {integrity: sha512-vpiIUq+SaEO/566TlbX4USpO1X+jSCPxSf9cZlPTorr4N55h7RGgsUORASapoKMCxbeYxlwKRrTdrThzZWJptw==} - '@lexical/devtools-core@0.22.0': - resolution: {integrity: sha512-PFdaN2c+O/eLqNcWzuLBz0AaiLPHs9+iMHUZDQMgAWcKS6uiEBA61VS+ZNZn8zj8RZZvUtxgmZ9wQFFURklfww==} + '@lexical/devtools-core@0.23.0': + resolution: {integrity: sha512-QYtcLvx8INI1AoWEYZmmti0pP5DlgRZoR4Xh2dWLXyA/+VoLtykbXB83UgsjUVFbKU7XAiKEWFCyORqfJaxY+w==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/dragon@0.22.0': - resolution: {integrity: sha512-68b1EiFbYRqPEhbrFPhfTNe/hSTCFtwBmcIP5cGgEef+lL5XqCxc6NK2g2DK4Dx26DSGwXxTuRG8NuuxFN+CiA==} + '@lexical/dragon@0.23.0': + resolution: {integrity: sha512-EIwnH8eZIkYTyb4rY9cPKrzPv7a4t9cip6JBeTsysGB3k2K3nTaWCW4k89kUZ4Jy4olB+d7FDLRjEUMwV7MoDg==} - '@lexical/hashtag@0.22.0': - resolution: {integrity: sha512-W6Wrz0+ClezZQavjCjYso+LzRP/rWNUIM6cLwnf7oFXsmIXeaLHXecIbRwZ+SChnj4eUlSlnlknMVi3bJKaOjw==} + '@lexical/hashtag@0.23.0': + resolution: {integrity: sha512-rVIR02vIy8eGo0Py0mXqK4JjH4g9ZcegUVxKNDvlf7Ad+DdQvKNlOT5iaDDpPts8OXQvZz7GNe7HxoDnvJEZsQ==} - '@lexical/history@0.22.0': - resolution: {integrity: sha512-4e/xL7CtYvsv8X4iGH1tPL8J6jthDpzh0a1WWZfL9ipeOYN9Btctq6o1Obdth5DfbMR7ELq7PSN2FK6+jZ4EQQ==} + '@lexical/history@0.23.0': + resolution: {integrity: sha512-s76kbrGYw/duLjN3OpPiYtpzl1F9ddbTbFL7KxWG6FHhAXXPF5caY9Ajg+OB6327r2jSxUbZSautd5zbwFxbWA==} - '@lexical/html@0.22.0': - resolution: {integrity: sha512-ED2Xik95a4kPg9vbU5pT/sA5hvEyGnuVxg0XvMx/6Gaa7OIhYDiZvSD4DCE+eVfn0xZMnIbX94Ygf4//71kCYw==} + '@lexical/html@0.23.0': + resolution: {integrity: sha512-kHCmjATl88CeaeJoWbycHT1XQjwYgscjZSmgSmOahRvCsBee4lJ/h+cuMLVDj9gj21IAnzYd8Gx+EHka/yECgA==} - '@lexical/link@0.22.0': - resolution: {integrity: sha512-mtlsOYW6sgbgvv1ecPt6/9CdlgP0MhVyrF8iu16JpngphVXu/Lz2BSh7TCfY0xho/H2OIu++9lUGn87cK1Zplw==} + '@lexical/link@0.23.0': + resolution: {integrity: sha512-jD+T4jmzD93ULZTuK69Hhw+ZYdM/PhsZpgiHYIEBiLCoO69n9eeIBD6YAx/UP34C0/xOi8RDsqVjdyUaXLPzvA==} - '@lexical/list@0.22.0': - resolution: {integrity: sha512-DZLzMg1/H+nclV8BNqZe2qk/bz1ogCr7Huzwab5o8jTjpsDJaqyUZasj8wRgBzyLu9jxMQngkarL47nnk+zrbA==} + '@lexical/list@0.23.0': + resolution: {integrity: sha512-YcvnyqER400XWYtjruIRs1ggMKqQbBupejMx2SHrXRzL/7dByHtmfGL6Bzn/1Y3BRWBYSFHy2LFs+OCFuChEIw==} - '@lexical/mark@0.22.0': - resolution: {integrity: sha512-d8Jb9xqhHwUrN2uBs0aIjvA/NgeAZCI5Zorynqq5ihBVSytrol09JyRt4t7SXRH4sCpDMDLMWfTjm820RQFq8g==} + '@lexical/mark@0.23.0': + resolution: {integrity: sha512-INx+vyty0eTaCqCNhzO8uxtV8QWItgCfzT5I/c33Hzy2oklXTO80kvTIkYVWMk2oFW64wNOzU49lY39iCp2nzQ==} - '@lexical/markdown@0.22.0': - resolution: {integrity: sha512-FJNfegbgry4dFwyzjdK6sasYHsVgiYy2WrVmVKYR/EDBxa0MARnVO7lRRXA37WcqMKi5N8dE/dABmjlmzuYzHA==} + '@lexical/markdown@0.23.0': + resolution: {integrity: sha512-X+vj7WMowkCFVV4+NAQ8af0WaqjnjaFeLLJSXUE5P5HfPHExZKTe8CmCXKctILXpplcur3yG1gd4keu1OeOPkA==} - '@lexical/offset@0.22.0': - resolution: {integrity: sha512-DgfO0q1+PzLzmMgRxTC7+y4whL6btlNSfv7f47TBAMBoj5tKdkGyMrru0xjoeYHVsMiSNgqMY2HTXU5YokKUOg==} + '@lexical/offset@0.23.0': + resolution: {integrity: sha512-N8iqYmhjZbZubO2UP9NrbopMKzrTX0RTuompciJpQxrRkE43zFlkclp55dFczTXx0F5JdWstPju0Yz3mvkLr8w==} - '@lexical/overflow@0.22.0': - resolution: {integrity: sha512-LPj27ChVsOUW7Gv28z9m7NfpPQs7e3OooYL1WzU3R8NYDj/U32YxQXpUDcxHKwBPo8j8NVEna2d3UAG1txz6BA==} + '@lexical/overflow@0.23.0': + resolution: {integrity: sha512-wQif5rWxEcUksei14/lzNhKT1XrFbOlGCupCGFeAGR+MIvEQhWdd8AXdA3vsVECMBK8tpGC5IkiHJ2JwPEoXtA==} - '@lexical/plain-text@0.22.0': - resolution: {integrity: sha512-MBlX4PfLN6I/As8uuRnL/17B+lIjmtNSIH90NjykRwBC8dtWr4gzSjm9OGTee6HctxnbQNb1BGLM7W1hLtiKAw==} + '@lexical/plain-text@0.23.0': + resolution: {integrity: sha512-JIiptGKlVGNrlZoJ2WFWZXElsYv0IDowICQfhw/wpl36ClmJ4dRuwQ/9HtOi709XPKAyk0f3m1psl0ytg63Oig==} - '@lexical/react@0.22.0': - resolution: {integrity: sha512-kHeAX6QFsDcdih2kBQVnIATOQsDzpMp2VLYlSxatF8JvCTaMQ3quH/CGHLnUAauZDxM2LC/eV+Z4nrUOP3Alyg==} + '@lexical/react@0.23.0': + resolution: {integrity: sha512-c3yqISdiLxE2hB7V6M4wSJN/7uXoFmt4OjJAWKSHL2GmMydmZfdK7lWXb9Ky9rfVsxA6xVlIkx8+Jcma5jnVbg==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/rich-text@0.22.0': - resolution: {integrity: sha512-ILN/tht6emxbVK12Hp86y50fk0MGHMHg6Ivt/5OncoDPXfLMicuJRgLgLxLl5dCf7UVQOfa7z8eKEWnE83M5Mg==} + '@lexical/rich-text@0.23.0': + resolution: {integrity: sha512-X5f+as0dItxo5GGwwExHo7cGgG1erf/02mqhFNbMvOnl+VJVOvy3c+wp2W3JEWRDTaLdqxaw/m4LrfN6m79cEg==} - '@lexical/selection@0.22.0': - resolution: {integrity: sha512-DdRh8bHijQGV1OxeaywulaLRxgYtn1B7miS0I4pP04KkRXVxrlWRP5dGzQoMDghTh+W1QD9GiUSAyz4L9R3zxQ==} + '@lexical/selection@0.23.0': + resolution: {integrity: sha512-ypyLRkzRiVA8JIlIZu58FepkBxl8ilysigjJefyMEuFUS8/F3d9nujznWi6BhplWmBCd/lNzFjvLvmsvYAK1XQ==} - '@lexical/table@0.22.0': - resolution: {integrity: sha512-Kx4N4kgYKTpcYtYYbp3hfG6We6ss3vWlhJ/f/QuUo18+w8c0a7aaCo4wTjNNUnSuXSFc1E02JlaTTVoI1pVfDw==} + '@lexical/table@0.23.0': + resolution: {integrity: sha512-R8WHuyrefQyrwMkIGuj/2CnQDf5f3yljHABy77URvoBjmVONEM/vqQ9ZLCtDP4fIaxhdf2Fq3Agt6e3tMNs/vQ==} - '@lexical/text@0.22.0': - resolution: {integrity: sha512-eLQTH11FmTW8VBOcb3hKc6H4S4A2cXFDOXHE3lCA+ErLXOnwlj7tKm52eWyddPTlkNmZ3C8mIvfVPcS4Jxw20Q==} + '@lexical/text@0.23.0': + resolution: {integrity: sha512-t+ZbP0sIKCtJwSrtCR3WZvmn5c+5JRa/ddu051PIfqHVU1Ox+Y3AXAPYgK76vMki7Zfldda2KqglnSgOX4p/Zw==} - '@lexical/utils@0.22.0': - resolution: {integrity: sha512-mQoNS4e0+lqkj3lPKE2u5vdQmdtgsqcLhzNJMhpCmFvJ896l5zKXOFQoFmF8BFz/v7w92FT7dpT5gd0lcIryyg==} + '@lexical/utils@0.23.0': + resolution: {integrity: sha512-vhcwR7ymkvXGrnoANxiBR55UlNwR4KcRNTzbbKgtQRdo+ATXbX6/KROVPJ6nkvYah+f6fcqw9Crj7RtzSOYhiQ==} - '@lexical/yjs@0.22.0': - resolution: {integrity: sha512-JrQO03HIIfOURpCpSZgZoTH8WBFumhvl9ysP1ZAqh9f2bLZp7qxi/jFgwOorz0NdM11Qb1dJprNNPaqNnS4BKA==} + '@lexical/yjs@0.23.0': + resolution: {integrity: sha512-xitE7eCIPQpQvgyPqT4XwRzfgNy6WkCUrjSSuxahpd9ezgZi2lsBPVU1ZSVFjna2yUfFPfHvGiVBJ1TyKIZoWw==} peerDependencies: yjs: '>=13.5.22' @@ -3402,23 +3405,29 @@ packages: '@shikijs/core@1.10.3': resolution: {integrity: sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==} - '@shikijs/core@1.24.4': - resolution: {integrity: sha512-jjLsld+xEEGYlxAXDyGwWsKJ1sw5Pc1pnp4ai2ORpjx2UX08YYTC0NNqQYO1PaghYaR+PvgMOGuvzw2he9sk0Q==} + '@shikijs/core@1.26.1': + resolution: {integrity: sha512-yeo7sG+WZQblKPclUOKRPwkv1PyoHYkJ4gP9DzhFJbTdueKR7wYTI1vfF/bFi1NTgc545yG/DzvVhZgueVOXMA==} + + '@shikijs/engine-javascript@1.26.1': + resolution: {integrity: sha512-CRhA0b8CaSLxS0E9A4Bzcb3LKBNpykfo9F85ozlNyArxjo2NkijtiwrJZ6eHa+NT5I9Kox2IXVdjUsP4dilsmw==} + + '@shikijs/engine-oniguruma@1.26.1': + resolution: {integrity: sha512-F5XuxN1HljLuvfXv7d+mlTkV7XukC1cawdtOo+7pKgPD83CAB1Sf8uHqP3PK0u7njFH0ZhoXE1r+0JzEgAQ+kg==} - '@shikijs/engine-javascript@1.24.4': - resolution: {integrity: sha512-TClaQOLvo9WEMJv6GoUsykQ6QdynuKszuORFWCke8qvi6PeLm7FcD9+7y45UenysxEWYpDL5KJaVXTngTE+2BA==} + '@shikijs/langs@1.26.1': + resolution: {integrity: sha512-oz/TQiIqZejEIZbGtn68hbJijAOTtYH4TMMSWkWYozwqdpKR3EXgILneQy26WItmJjp3xVspHdiUxUCws4gtuw==} - '@shikijs/engine-oniguruma@1.24.4': - resolution: {integrity: sha512-Do2ry6flp2HWdvpj2XOwwa0ljZBRy15HKZITzPcNIBOGSeprnA8gOooA/bLsSPuy8aJBa+Q/r34dMmC3KNL/zw==} + '@shikijs/themes@1.26.1': + resolution: {integrity: sha512-JDxVn+z+wgLCiUhBGx2OQrLCkKZQGzNH3nAxFir4PjUcYiyD8Jdms9izyxIogYmSwmoPTatFTdzyrRKbKlSfPA==} '@shikijs/transformers@1.10.3': resolution: {integrity: sha512-MNjsyye2WHVdxfZUSr5frS97sLGe6G1T+1P41QjyBFJehZphMcr4aBlRLmq6OSPBslYe9byQPVvt/LJCOfxw8Q==} - '@shikijs/types@1.24.4': - resolution: {integrity: sha512-0r0XU7Eaow0PuDxuWC1bVqmWCgm3XqizIaT7SM42K03vc69LGooT0U8ccSR44xP/hGlNx4FKhtYpV+BU6aaKAA==} + '@shikijs/types@1.26.1': + resolution: {integrity: sha512-d4B00TKKAMaHuFYgRf3L0gwtvqpW4hVdVwKcZYbBfAAQXspgkbWqnFfuFl3MDH6gLbsubOcr+prcnsqah3ny7Q==} - '@shikijs/vscode-textmate@9.3.1': - resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==} + '@shikijs/vscode-textmate@10.0.1': + resolution: {integrity: sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==} '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} @@ -3578,11 +3587,11 @@ packages: '@swc/types@0.1.12': resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} - '@tanstack/query-core@5.62.9': - resolution: {integrity: sha512-lwePd8hNYhyQ4nM/iRQ+Wz2cDtspGeZZHFZmCzHJ7mfKXt+9S301fULiY2IR2byJYY6Z03T427E5PoVfMexHjw==} + '@tanstack/query-core@5.62.16': + resolution: {integrity: sha512-9Sgft7Qavcd+sN0V25xVyo0nfmcZXBuODy3FVG7BMWTg1HMLm8wwG5tNlLlmSic1u7l1v786oavn+STiFaPH2g==} - '@tanstack/react-query@5.62.11': - resolution: {integrity: sha512-Xb1nw0cYMdtFmwkvH9+y5yYFhXvLRCnXoqlzSw7UkqtCVFq3cG8q+rHZ2Yz1XrC+/ysUaTqbLKJqk95mCgC1oQ==} + '@tanstack/react-query@5.62.16': + resolution: {integrity: sha512-XJIZNj65d2IdvU8VBESmrPakfIm6FSdHDzrS1dPrAwmq3ZX+9riMh/ZfbNQHAWnhrgmq7KoXpgZSRyXnqMYT9A==} peerDependencies: react: ^18 || ^19 @@ -3731,8 +3740,8 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/diff@6.0.0': - resolution: {integrity: sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==} + '@types/diff@7.0.0': + resolution: {integrity: sha512-sVpkpbnTJL9CYoDf4U+tHaQLe5HiTaHWY7m9FuYA7oMCHwC9ie0Vh9eIGapyzYrU3+pILlSY2fAc4elfw5m4dg==} '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} @@ -3809,8 +3818,8 @@ packages: '@types/node@20.3.0': resolution: {integrity: sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==} - '@types/node@22.10.2': - resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/pad-left@2.1.1': resolution: {integrity: sha512-Xd22WCRBydkGSApl5Bw0PhAOHKSVjNL3E3AwzKaps96IMraPqy5BvZIsBVK6JLwdybUzjHnuWVwpDd0JjTfHXA==} @@ -5427,8 +5436,8 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} - es-toolkit@1.30.1: - resolution: {integrity: sha512-ZXflqanzH8BpHkDhFa10bBf6ONDCe84EPUm7SSICGzuuROSluT2ynTPtwn9PcRelMtorCRozSknI/U0MNYp0Uw==} + es-toolkit@1.31.0: + resolution: {integrity: sha512-vwS0lv/tzjM2/t4aZZRAgN9I9TP0MSkWuvt6By+hEXfG/uLs8yg2S1/ayRXH/x3pinbLgVJYT+eppueg3cM6tg==} es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -5517,8 +5526,8 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - eslint-import-resolver-typescript@3.6.3: - resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} + eslint-import-resolver-typescript@3.7.0: + resolution: {integrity: sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -5551,27 +5560,6 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-module-utils@2.8.1: - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - eslint-plugin-import@2.31.0: resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} engines: {node: '>=4'} @@ -5606,8 +5594,8 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-react-compiler@19.0.0-beta-b2e8e9c-20241220: - resolution: {integrity: sha512-STVaOQyivSBv0un6/ujYOPntKcCaD0qXIG8siBEs9QcWmQ7q3J3ozuAE86SlSc7ElIZgPoL9HoSN3EONS47nqQ==} + eslint-plugin-react-compiler@19.0.0-beta-63e3235-20250105: + resolution: {integrity: sha512-Smts5x+u+rRopr0926jCXFPkS8D8hFJexDvTW41V0Xu/xHgd4pnGWiJQRBsvTEARzOdJ6NdlmYs4n+O4Thn2iA==} engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} peerDependencies: eslint: '>=7' @@ -5629,20 +5617,15 @@ packages: peerDependencies: eslint: '>=5.0.0' - eslint-plugin-unused-imports@3.2.0: - resolution: {integrity: sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} peerDependencies: - '@typescript-eslint/eslint-plugin': 6 - 7 - eslint: '8' + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true - eslint-rule-composer@0.3.0: - resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} - engines: {node: '>=4.0.0'} - eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6304,9 +6287,6 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inline-style-parser@0.2.3: - resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} - inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -6690,16 +6670,18 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - langchain@0.3.8: - resolution: {integrity: sha512-EiAHFgBdThuXFmIx9j81wjdPItpRsw0Ck4r5dyhB74gyhehRGna/UK2CTqeKVnIUM/f4g4JbxUgAU4voXljDMw==} + langchain@0.3.10: + resolution: {integrity: sha512-dZXdhs81NU/PS2WfECCLJszx4to3ELK7qTMbumD0rAKx3mQb0sqr8M9MiVCcPgTZ1J1pzoDr5yCSdsmm9UsNXA==} engines: {node: '>=18'} peerDependencies: '@langchain/anthropic': '*' '@langchain/aws': '*' + '@langchain/cerebras': '*' '@langchain/cohere': '*' - '@langchain/core': 0.3.26 + '@langchain/core': 0.3.27 '@langchain/google-genai': '*' '@langchain/google-vertexai': '*' + '@langchain/google-vertexai-web': '*' '@langchain/groq': '*' '@langchain/mistralai': '*' '@langchain/ollama': '*' @@ -6713,12 +6695,16 @@ packages: optional: true '@langchain/aws': optional: true + '@langchain/cerebras': + optional: true '@langchain/cohere': optional: true '@langchain/google-genai': optional: true '@langchain/google-vertexai': optional: true + '@langchain/google-vertexai-web': + optional: true '@langchain/groq': optional: true '@langchain/mistralai': @@ -6774,8 +6760,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lexical@0.22.0: - resolution: {integrity: sha512-EquENoJZdhwAzsZ+Dz8dGZprlpPY1zez6Gk9yhGkPzmIVPRIGY4aEAmfKQCep1dZgkUUQB8Flr0xK0+u5TrFhw==} + lexical@0.23.0: + resolution: {integrity: sha512-xkRJqPrdcAkUKP9NiJcmOayKpvou9C8H9y2O8fIWM9tW0KAJub1gkuw9q9VexwvqgCZbf2ep2ufFwC1rY7caSw==} lib0@0.2.97: resolution: {integrity: sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg==} @@ -7439,8 +7425,8 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - oniguruma-to-es@0.8.1: - resolution: {integrity: sha512-dekySTEvCxCj0IgKcA2uUCO/e4ArsqpucDPcX26w9ajx+DvMWLc5eZeJaRQkd7oC/+rwif5gnT900tA34uN9Zw==} + oniguruma-to-es@0.10.0: + resolution: {integrity: sha512-zapyOUOCJxt+xhiNRPPMtfJkHGsZ98HHB9qJEkdT8BGytO/+kpe4m1Ngf0MzbzTmhacn11w9yGeDP6tzDhnCdg==} onnx-proto@4.0.4: resolution: {integrity: sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==} @@ -7708,8 +7694,8 @@ packages: engines: {node: '>=18'} hasBin: true - pnpm@9.15.2: - resolution: {integrity: sha512-k+V7ASbw33TOa/8paAOUwLpU7Ecka5zzIfASHY2bsD91CnBfJO3DwRgIU6/XwsO5QZbQo9U9PgadnieT7xHzIQ==} + pnpm@9.15.3: + resolution: {integrity: sha512-H3m8JFpm6wsHxdTYMTEkB3RkLKqobvfQQ0q0fA0W9msE4h4MCG62HmLHfvxNf37Aca+tN5avZIkvrmZQkXOJOg==} engines: {node: '>=18.12'} hasBin: true @@ -7889,8 +7875,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-intersection-observer@9.14.0: - resolution: {integrity: sha512-AYqlmDZn85VUmlODwYym9y5OlqY2cFyIu41dkN0GJWvhdbd19Mh16mz5IH6fO1gp5V4FfQOO4m0zGc04Tj13rQ==} + react-intersection-observer@9.14.1: + resolution: {integrity: sha512-k1xIUn3sCQi3ugNeF64FJb3zwve5mcetvAUR9JazXeOmtap4IP2evN8rs+yf6SQ7F1QydsOGiqTmt+lySKZ9uA==} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -7901,8 +7887,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-markdown@9.0.1: - resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==} + react-markdown@9.0.3: + resolution: {integrity: sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==} peerDependencies: '@types/react': ^19.0.2 react: '>=18' @@ -8046,14 +8032,14 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regex-recursion@5.0.0: - resolution: {integrity: sha512-UwyOqeobrCCqTXPcsSqH4gDhOjD5cI/b8kjngWgSZbxYh5yVjAwTjO5+hAuPRNiuR70+5RlWSs+U9PVcVcW9Lw==} + regex-recursion@5.1.1: + resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} regex-utilities@2.3.0: resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - regex@5.0.2: - resolution: {integrity: sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==} + regex@5.1.1: + resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} @@ -8303,8 +8289,8 @@ packages: resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} engines: {node: '>= 0.4'} - shiki@1.24.4: - resolution: {integrity: sha512-aVGSFAOAr1v26Hh/+GBIsRVDWJ583XYV7CuNURKRWh9gpGv4OdbisZGq96B9arMYTZhTQkmRF5BrShOSTvNqhw==} + shiki@1.26.1: + resolution: {integrity: sha512-Gqg6DSTk3wYqaZ5OaYtzjcdxcBvX5kCy24yvRJEgjT5U+WHlmqCThLuBUx0juyxQBi+6ug53IGeuQS07DWwpcw==} side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} @@ -8416,6 +8402,9 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stable-hash@0.0.4: + resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} @@ -8546,9 +8535,6 @@ packages: style-to-js@1.1.16: resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} - style-to-object@1.0.6: - resolution: {integrity: sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==} - style-to-object@1.0.8: resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} @@ -9039,8 +9025,8 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - uuid@11.0.3: - resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + uuid@11.0.4: + resolution: {integrity: sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==} hasBin: true uuid@8.3.2: @@ -9173,8 +9159,8 @@ packages: terser: optional: true - vite@6.0.6: - resolution: {integrity: sha512-NSjmUuckPmDU18bHz7QZ+bTYhRR0iA72cs2QAxCqDpafJ0S6qetco0LB3WW2OxlMHS0JmAv+yZ/R3uPmMyGTjQ==} + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -9502,8 +9488,8 @@ packages: zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} - zustand@5.0.2: - resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': ^19.0.2 @@ -10188,11 +10174,11 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@commitlint/cli@19.6.1(@types/node@22.10.2)(typescript@5.7.2)': + '@commitlint/cli@19.6.1(@types/node@22.10.5)(typescript@5.7.2)': dependencies: '@commitlint/format': 19.5.0 '@commitlint/lint': 19.6.0 - '@commitlint/load': 19.6.1(@types/node@22.10.2)(typescript@5.7.2) + '@commitlint/load': 19.6.1(@types/node@22.10.5)(typescript@5.7.2) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 tinyexec: 0.3.1 @@ -10211,13 +10197,13 @@ snapshots: '@commitlint/types': 19.5.0 ajv: 8.16.0 - '@commitlint/cz-commitlint@19.6.1(@types/node@22.10.2)(commitizen@4.3.1(@types/node@22.10.2)(typescript@5.7.2))(inquirer@9.3.4)(typescript@5.7.2)': + '@commitlint/cz-commitlint@19.6.1(@types/node@22.10.5)(commitizen@4.3.1(@types/node@22.10.5)(typescript@5.7.2))(inquirer@9.3.4)(typescript@5.7.2)': dependencies: '@commitlint/ensure': 19.5.0 - '@commitlint/load': 19.6.1(@types/node@22.10.2)(typescript@5.7.2) + '@commitlint/load': 19.6.1(@types/node@22.10.5)(typescript@5.7.2) '@commitlint/types': 19.5.0 chalk: 5.4.1 - commitizen: 4.3.1(@types/node@22.10.2)(typescript@5.7.2) + commitizen: 4.3.1(@types/node@22.10.5)(typescript@5.7.2) inquirer: 9.3.4 lodash.isplainobject: 4.0.6 word-wrap: 1.2.5 @@ -10253,7 +10239,7 @@ snapshots: '@commitlint/rules': 19.6.0 '@commitlint/types': 19.5.0 - '@commitlint/load@19.5.0(@types/node@22.10.2)(typescript@5.7.2)': + '@commitlint/load@19.5.0(@types/node@22.10.5)(typescript@5.7.2)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 @@ -10261,7 +10247,7 @@ snapshots: '@commitlint/types': 19.5.0 chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.7.2) - cosmiconfig-typescript-loader: 5.0.0(@types/node@22.10.2)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) + cosmiconfig-typescript-loader: 5.0.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -10270,7 +10256,7 @@ snapshots: - typescript optional: true - '@commitlint/load@19.6.1(@types/node@22.10.2)(typescript@5.7.2)': + '@commitlint/load@19.6.1(@types/node@22.10.5)(typescript@5.7.2)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 @@ -10278,7 +10264,7 @@ snapshots: '@commitlint/types': 19.5.0 chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.7.2) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.2)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -10805,7 +10791,7 @@ snapshots: '@floating-ui/utils@0.2.7': {} - '@hookform/resolvers@3.9.1(react-hook-form@7.54.2(react@19.0.0))': + '@hookform/resolvers@3.10.0(react-hook-form@7.54.2(react@19.0.0))': dependencies: react-hook-form: 7.54.2(react@19.0.0) @@ -11024,28 +11010,28 @@ snapshots: '@lancedb/vectordb-win32-x64-msvc@0.14.1': optional: true - '@langchain/anthropic@0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + '@langchain/anthropic@0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))': dependencies: '@anthropic-ai/sdk': 0.32.1 - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) fast-xml-parser: 4.4.1 zod: 3.24.1 zod-to-json-schema: 3.22.5(zod@3.24.1) transitivePeerDependencies: - encoding - '@langchain/community@0.3.20(@browserbasehq/sdk@2.0.0)(@browserbasehq/stagehand@1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(openai@4.77.0(zod@3.24.1))(zod@3.24.1))(@ibm-cloud/watsonx-ai@1.1.1)(@langchain/anthropic@0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(@xenova/transformers@2.17.2)(axios@1.7.4)(cheerio@1.0.0)(fast-xml-parser@4.4.1)(ibm-cloud-sdk-core@5.1.0)(ignore@7.0.0)(jsonwebtoken@9.0.2)(lodash@4.17.21)(openai@4.77.0(zod@3.24.1))(playwright@1.49.1)(ws@8.18.0)': + '@langchain/community@0.3.22(@browserbasehq/sdk@2.0.0)(@browserbasehq/stagehand@1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(openai@4.77.0(zod@3.24.1))(zod@3.24.1))(@ibm-cloud/watsonx-ai@1.1.1)(@langchain/anthropic@0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(fast-xml-parser@4.4.1)(ibm-cloud-sdk-core@5.1.0)(ignore@7.0.0)(jsonwebtoken@9.0.2)(lodash@4.17.21)(openai@4.77.0(zod@3.24.1))(playwright@1.49.1)(ws@8.18.0)': dependencies: '@browserbasehq/stagehand': 1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(openai@4.77.0(zod@3.24.1))(zod@3.24.1) '@ibm-cloud/watsonx-ai': 1.1.1 - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) - '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) + '@langchain/openai': 0.3.16(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) binary-extensions: 2.2.0 expr-eval: 2.0.2 flat: 5.0.2 ibm-cloud-sdk-core: 5.1.0 js-yaml: 4.1.0 - langchain: 0.3.8(@langchain/anthropic@0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(openai@4.77.0(zod@3.24.1)) + langchain: 0.3.10(@langchain/anthropic@0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(openai@4.77.0(zod@3.24.1)) langsmith: 0.2.13(openai@4.77.0(zod@3.24.1)) openai: 4.77.0(zod@3.24.1) uuid: 10.0.0 @@ -11053,7 +11039,6 @@ snapshots: zod-to-json-schema: 3.24.1(zod@3.24.1) optionalDependencies: '@browserbasehq/sdk': 2.0.0 - '@xenova/transformers': 2.17.2 cheerio: 1.0.0 fast-xml-parser: 4.4.1 ignore: 7.0.0 @@ -11064,9 +11049,11 @@ snapshots: transitivePeerDependencies: - '@langchain/anthropic' - '@langchain/aws' + - '@langchain/cerebras' - '@langchain/cohere' - '@langchain/google-genai' - '@langchain/google-vertexai' + - '@langchain/google-vertexai-web' - '@langchain/groq' - '@langchain/mistralai' - '@langchain/ollama' @@ -11075,7 +11062,7 @@ snapshots: - handlebars - peggy - '@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))': + '@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))': dependencies: '@cfworker/json-schema': 4.0.3 ansi-styles: 5.2.0 @@ -11088,33 +11075,33 @@ snapshots: p-retry: 4.6.2 uuid: 10.0.0 zod: 3.24.1 - zod-to-json-schema: 3.22.5(zod@3.24.1) + zod-to-json-schema: 3.24.1(zod@3.24.1) transitivePeerDependencies: - openai - '@langchain/langgraph-checkpoint@0.0.13(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + '@langchain/langgraph-checkpoint@0.0.13(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))': dependencies: - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) uuid: 10.0.0 - '@langchain/langgraph-sdk@0.0.23': + '@langchain/langgraph-sdk@0.0.33': dependencies: '@types/json-schema': 7.0.15 p-queue: 6.6.2 p-retry: 4.6.2 uuid: 9.0.1 - '@langchain/langgraph@0.2.36(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + '@langchain/langgraph@0.2.39(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))': dependencies: - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) - '@langchain/langgraph-checkpoint': 0.0.13(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) - '@langchain/langgraph-sdk': 0.0.23 + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) + '@langchain/langgraph-checkpoint': 0.0.13(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) + '@langchain/langgraph-sdk': 0.0.33 uuid: 10.0.0 zod: 3.24.1 - '@langchain/openai@0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + '@langchain/openai@0.3.16(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))': dependencies: - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) js-tiktoken: 1.0.16 openai: 4.77.0(zod@3.24.1) zod: 3.24.1 @@ -11122,156 +11109,156 @@ snapshots: transitivePeerDependencies: - encoding - '@langchain/textsplitters@0.1.0(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + '@langchain/textsplitters@0.1.0(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))': dependencies: - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) js-tiktoken: 1.0.16 - '@lexical/clipboard@0.22.0': + '@lexical/clipboard@0.23.0': dependencies: - '@lexical/html': 0.22.0 - '@lexical/list': 0.22.0 - '@lexical/selection': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/html': 0.23.0 + '@lexical/list': 0.23.0 + '@lexical/selection': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/code@0.22.0': + '@lexical/code@0.23.0': dependencies: - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 prismjs: 1.29.0 - '@lexical/devtools-core@0.22.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@lexical/devtools-core@0.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@lexical/html': 0.22.0 - '@lexical/link': 0.22.0 - '@lexical/mark': 0.22.0 - '@lexical/table': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/html': 0.23.0 + '@lexical/link': 0.23.0 + '@lexical/mark': 0.23.0 + '@lexical/table': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@lexical/dragon@0.22.0': + '@lexical/dragon@0.23.0': dependencies: - lexical: 0.22.0 + lexical: 0.23.0 - '@lexical/hashtag@0.22.0': + '@lexical/hashtag@0.23.0': dependencies: - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/history@0.22.0': + '@lexical/history@0.23.0': dependencies: - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/html@0.22.0': + '@lexical/html@0.23.0': dependencies: - '@lexical/selection': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/selection': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/link@0.22.0': + '@lexical/link@0.23.0': dependencies: - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/list@0.22.0': + '@lexical/list@0.23.0': dependencies: - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/mark@0.22.0': + '@lexical/mark@0.23.0': dependencies: - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/markdown@0.22.0': + '@lexical/markdown@0.23.0': dependencies: - '@lexical/code': 0.22.0 - '@lexical/link': 0.22.0 - '@lexical/list': 0.22.0 - '@lexical/rich-text': 0.22.0 - '@lexical/text': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/code': 0.23.0 + '@lexical/link': 0.23.0 + '@lexical/list': 0.23.0 + '@lexical/rich-text': 0.23.0 + '@lexical/text': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/offset@0.22.0': + '@lexical/offset@0.23.0': dependencies: - lexical: 0.22.0 + lexical: 0.23.0 - '@lexical/overflow@0.22.0': + '@lexical/overflow@0.23.0': dependencies: - lexical: 0.22.0 + lexical: 0.23.0 - '@lexical/plain-text@0.22.0': + '@lexical/plain-text@0.23.0': dependencies: - '@lexical/clipboard': 0.22.0 - '@lexical/selection': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/clipboard': 0.23.0 + '@lexical/selection': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/react@0.22.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.18)': + '@lexical/react@0.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.18)': dependencies: - '@lexical/clipboard': 0.22.0 - '@lexical/code': 0.22.0 - '@lexical/devtools-core': 0.22.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@lexical/dragon': 0.22.0 - '@lexical/hashtag': 0.22.0 - '@lexical/history': 0.22.0 - '@lexical/link': 0.22.0 - '@lexical/list': 0.22.0 - '@lexical/mark': 0.22.0 - '@lexical/markdown': 0.22.0 - '@lexical/overflow': 0.22.0 - '@lexical/plain-text': 0.22.0 - '@lexical/rich-text': 0.22.0 - '@lexical/selection': 0.22.0 - '@lexical/table': 0.22.0 - '@lexical/text': 0.22.0 - '@lexical/utils': 0.22.0 - '@lexical/yjs': 0.22.0(yjs@13.6.18) - lexical: 0.22.0 + '@lexical/clipboard': 0.23.0 + '@lexical/code': 0.23.0 + '@lexical/devtools-core': 0.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@lexical/dragon': 0.23.0 + '@lexical/hashtag': 0.23.0 + '@lexical/history': 0.23.0 + '@lexical/link': 0.23.0 + '@lexical/list': 0.23.0 + '@lexical/mark': 0.23.0 + '@lexical/markdown': 0.23.0 + '@lexical/overflow': 0.23.0 + '@lexical/plain-text': 0.23.0 + '@lexical/rich-text': 0.23.0 + '@lexical/selection': 0.23.0 + '@lexical/table': 0.23.0 + '@lexical/text': 0.23.0 + '@lexical/utils': 0.23.0 + '@lexical/yjs': 0.23.0(yjs@13.6.18) + lexical: 0.23.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) react-error-boundary: 3.1.4(react@19.0.0) transitivePeerDependencies: - yjs - '@lexical/rich-text@0.22.0': + '@lexical/rich-text@0.23.0': dependencies: - '@lexical/clipboard': 0.22.0 - '@lexical/selection': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/clipboard': 0.23.0 + '@lexical/selection': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/selection@0.22.0': + '@lexical/selection@0.23.0': dependencies: - lexical: 0.22.0 + lexical: 0.23.0 - '@lexical/table@0.22.0': + '@lexical/table@0.23.0': dependencies: - '@lexical/clipboard': 0.22.0 - '@lexical/utils': 0.22.0 - lexical: 0.22.0 + '@lexical/clipboard': 0.23.0 + '@lexical/utils': 0.23.0 + lexical: 0.23.0 - '@lexical/text@0.22.0': + '@lexical/text@0.23.0': dependencies: - lexical: 0.22.0 + lexical: 0.23.0 - '@lexical/utils@0.22.0': + '@lexical/utils@0.23.0': dependencies: - '@lexical/list': 0.22.0 - '@lexical/selection': 0.22.0 - '@lexical/table': 0.22.0 - lexical: 0.22.0 + '@lexical/list': 0.23.0 + '@lexical/selection': 0.23.0 + '@lexical/table': 0.23.0 + lexical: 0.23.0 - '@lexical/yjs@0.22.0(yjs@13.6.18)': + '@lexical/yjs@0.23.0(yjs@13.6.18)': dependencies: - '@lexical/offset': 0.22.0 - '@lexical/selection': 0.22.0 - lexical: 0.22.0 + '@lexical/offset': 0.23.0 + '@lexical/selection': 0.23.0 + lexical: 0.23.0 yjs: 13.6.18 '@mapbox/node-pre-gyp@1.0.11': @@ -11314,11 +11301,11 @@ snapshots: dependencies: markdown-it: 14.1.0 - '@nolebase/ui@2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2)': + '@nolebase/ui@2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2)': dependencies: '@iconify-json/octicon': 1.1.56 less: 4.2.0 - vitepress: 1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) + vitepress: 1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) vue: 3.4.36(typescript@5.7.2) transitivePeerDependencies: - '@algolia/client-search' @@ -11348,17 +11335,17 @@ snapshots: - typescript - universal-cookie - '@nolebase/vitepress-plugin-inline-link-preview@2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2)': + '@nolebase/vitepress-plugin-inline-link-preview@2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2)': dependencies: '@iconify-json/icon-park-outline': 1.1.15 '@iconify-json/octicon': 1.1.56 '@iconify-json/svg-spinners': 1.1.2 '@nolebase/markdown-it-element-transform': 2.4.0(markdown-it@14.1.0) - '@nolebase/ui': 2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) + '@nolebase/ui': 2.4.0(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) less: 4.2.0 markdown-it: 14.1.0 markdown-it-attrs: 4.1.6(markdown-it@14.1.0) - vitepress: 1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) + vitepress: 1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2) transitivePeerDependencies: - '@algolia/client-search' - '@types/node' @@ -12134,36 +12121,44 @@ snapshots: dependencies: '@types/hast': 3.0.4 - '@shikijs/core@1.24.4': + '@shikijs/core@1.26.1': dependencies: - '@shikijs/engine-javascript': 1.24.4 - '@shikijs/engine-oniguruma': 1.24.4 - '@shikijs/types': 1.24.4 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/engine-javascript': 1.26.1 + '@shikijs/engine-oniguruma': 1.26.1 + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 '@types/hast': 3.0.4 hast-util-to-html: 9.0.4 - '@shikijs/engine-javascript@1.24.4': + '@shikijs/engine-javascript@1.26.1': + dependencies: + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 + oniguruma-to-es: 0.10.0 + + '@shikijs/engine-oniguruma@1.26.1': + dependencies: + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 + + '@shikijs/langs@1.26.1': dependencies: - '@shikijs/types': 1.24.4 - '@shikijs/vscode-textmate': 9.3.1 - oniguruma-to-es: 0.8.1 + '@shikijs/types': 1.26.1 - '@shikijs/engine-oniguruma@1.24.4': + '@shikijs/themes@1.26.1': dependencies: - '@shikijs/types': 1.24.4 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/types': 1.26.1 '@shikijs/transformers@1.10.3': dependencies: - shiki: 1.24.4 + shiki: 1.26.1 - '@shikijs/types@1.24.4': + '@shikijs/types@1.26.1': dependencies: - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/vscode-textmate': 10.0.1 '@types/hast': 3.0.4 - '@shikijs/vscode-textmate@9.3.1': {} + '@shikijs/vscode-textmate@10.0.1': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -12304,18 +12299,18 @@ snapshots: '@swc/counter': 0.1.3 optional: true - '@tanstack/query-core@5.62.9': {} + '@tanstack/query-core@5.62.16': {} - '@tanstack/react-query@5.62.11(react@19.0.0)': + '@tanstack/react-query@5.62.16(react@19.0.0)': dependencies: - '@tanstack/query-core': 5.62.9 + '@tanstack/query-core': 5.62.16 react: 19.0.0 '@tokenizer/token@0.3.0': {} '@tomjs/node@2.2.3': {} - '@tomjs/vite-plugin-vscode@3.2.1(@swc/core@1.7.10)(postcss@8.4.49)(typescript@5.7.2)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1))': + '@tomjs/vite-plugin-vscode@3.2.1(@swc/core@1.7.10)(postcss@8.4.49)(typescript@5.7.2)(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1))': dependencies: '@tomjs/node': 2.2.3 dayjs: 1.11.12 @@ -12325,7 +12320,7 @@ snapshots: lodash.merge: 4.6.2 node-html-parser: 6.1.13 tsup: 7.2.0(@swc/core@1.7.10)(postcss@8.4.49)(typescript@5.7.2) - vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) transitivePeerDependencies: - '@swc/core' - postcss @@ -12364,7 +12359,7 @@ snapshots: '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 '@types/cookie@0.4.1': {} @@ -12372,7 +12367,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 '@types/d3-array@3.2.1': {} @@ -12495,7 +12490,7 @@ snapshots: dependencies: '@types/ms': 0.7.34 - '@types/diff@6.0.0': {} + '@types/diff@7.0.0': {} '@types/eslint@8.56.10': dependencies: @@ -12514,7 +12509,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.10.2 + '@types/node': 22.10.5 '@types/geojson@7946.0.14': {} @@ -12532,7 +12527,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 '@types/katex@0.16.7': {} @@ -12557,7 +12552,7 @@ snapshots: '@types/node-fetch@2.6.11': dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 form-data: 4.0.0 '@types/node@10.14.22': {} @@ -12572,7 +12567,7 @@ snapshots: '@types/node@20.3.0': {} - '@types/node@22.10.2': + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -12580,7 +12575,7 @@ snapshots: '@types/papaparse@5.3.15': dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 '@types/react-dom@19.0.2(@types/react@19.0.2)': dependencies: @@ -12698,13 +12693,13 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@unocss/astro@0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0))': + '@unocss/astro@0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0))': dependencies: '@unocss/core': 0.61.9 '@unocss/reset': 0.61.9 - '@unocss/vite': 0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0)) + '@unocss/vite': 0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0)) optionalDependencies: - vite: 5.3.5(@types/node@22.10.2)(less@4.2.0) + vite: 5.3.5(@types/node@22.10.5)(less@4.2.0) transitivePeerDependencies: - rollup - supports-color @@ -12841,7 +12836,7 @@ snapshots: dependencies: '@unocss/core': 0.61.9 - '@unocss/vite@0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0))': + '@unocss/vite@0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0))': dependencies: '@ampproject/remapping': 2.3.0 '@rollup/pluginutils': 5.1.0(rollup@4.24.3) @@ -12853,25 +12848,25 @@ snapshots: chokidar: 3.6.0 fast-glob: 3.3.2 magic-string: 0.30.11 - vite: 5.3.5(@types/node@22.10.2)(less@4.2.0) + vite: 5.3.5(@types/node@22.10.5)(less@4.2.0) transitivePeerDependencies: - rollup - supports-color - '@vitejs/plugin-react@4.3.4(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1))': + '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.0.5(vite@5.3.5(@types/node@22.10.2)(less@4.2.0))(vue@3.4.36(typescript@5.7.2))': + '@vitejs/plugin-vue@5.0.5(vite@5.3.5(@types/node@22.10.5)(less@4.2.0))(vue@3.4.36(typescript@5.7.2))': dependencies: - vite: 5.3.5(@types/node@22.10.2)(less@4.2.0) + vite: 5.3.5(@types/node@22.10.5)(less@4.2.0) vue: 3.4.36(typescript@5.7.2) '@vitest/expect@2.1.8': @@ -12881,13 +12876,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.2)(less@4.2.0))': + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.5)(less@4.2.0))': dependencies: '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: - vite: 5.4.11(@types/node@22.10.2)(less@4.2.0) + vite: 5.4.11(@types/node@22.10.5)(less@4.2.0) '@vitest/pretty-format@2.1.8': dependencies: @@ -13742,10 +13737,10 @@ snapshots: has-own-prop: 2.0.0 repeat-string: 1.6.1 - commitizen@4.3.1(@types/node@22.10.2)(typescript@5.7.2): + commitizen@4.3.1(@types/node@22.10.5)(typescript@5.7.2): dependencies: cachedir: 2.3.0 - cz-conventional-changelog: 3.3.0(@types/node@22.10.2)(typescript@5.7.2) + cz-conventional-changelog: 3.3.0(@types/node@22.10.5)(typescript@5.7.2) dedent: 0.7.0 detect-indent: 6.1.0 find-node-modules: 2.1.3 @@ -13834,17 +13829,17 @@ snapshots: dependencies: layout-base: 2.0.1 - cosmiconfig-typescript-loader@5.0.0(@types/node@22.10.2)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): + cosmiconfig-typescript-loader@5.0.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 cosmiconfig: 9.0.0(typescript@5.7.2) jiti: 1.21.6 typescript: 5.7.2 optional: true - cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.2)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): + cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): dependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 cosmiconfig: 9.0.0(typescript@5.7.2) jiti: 2.4.2 typescript: 5.7.2 @@ -13938,16 +13933,16 @@ snapshots: cytoscape@3.30.2: {} - cz-conventional-changelog@3.3.0(@types/node@22.10.2)(typescript@5.7.2): + cz-conventional-changelog@3.3.0(@types/node@22.10.5)(typescript@5.7.2): dependencies: chalk: 2.4.2 - commitizen: 4.3.1(@types/node@22.10.2)(typescript@5.7.2) + commitizen: 4.3.1(@types/node@22.10.5)(typescript@5.7.2) conventional-commit-types: 3.0.0 lodash.map: 4.6.0 longest: 2.0.1 word-wrap: 1.2.5 optionalDependencies: - '@commitlint/load': 19.5.0(@types/node@22.10.2)(typescript@5.7.2) + '@commitlint/load': 19.5.0(@types/node@22.10.5)(typescript@5.7.2) transitivePeerDependencies: - '@types/node' - typescript @@ -14383,7 +14378,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.10.2 + '@types/node': 22.10.5 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -14530,7 +14525,7 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - es-toolkit@1.30.1: {} + es-toolkit@1.31.0: {} es6-error@4.1.1: {} @@ -14677,29 +14672,29 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 - eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0): + eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2) '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - eslint-plugin-import - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.35.0(eslint@8.57.0))(eslint@8.57.0): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.35.0(eslint@8.57.0))(eslint@8.57.0): dependencies: eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -14710,9 +14705,9 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)): dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) eslint-import-resolver-node@0.3.9: dependencies: @@ -14722,47 +14717,34 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.3.6 + debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-bun-module: 1.1.0 is-glob: 4.0.3 + stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.7.2) - eslint: 8.57.0 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -14773,7 +14755,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -14826,7 +14808,7 @@ snapshots: '@types/eslint': 8.56.10 eslint-config-prettier: 9.1.0(eslint@8.57.0) - eslint-plugin-react-compiler@19.0.0-beta-b2e8e9c-20241220(eslint@8.57.0): + eslint-plugin-react-compiler@19.0.0-beta-63e3235-20250105(eslint@8.57.0): dependencies: '@babel/core': 7.25.2 '@babel/parser': 7.25.3 @@ -14868,15 +14850,12 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-unused-imports@3.2.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0): dependencies: eslint: 8.57.0 - eslint-rule-composer: 0.3.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2) - eslint-rule-composer@0.3.0: {} - eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 @@ -15489,7 +15468,7 @@ snapshots: hast-util-to-jsx-runtime@2.3.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/hast': 3.0.4 '@types/unist': 3.0.2 comma-separated-tokens: 2.0.3 @@ -15501,7 +15480,7 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 6.5.0 space-separated-tokens: 2.0.2 - style-to-object: 1.0.6 + style-to-object: 1.0.8 unist-util-position: 5.0.0 vfile-message: 4.0.2 transitivePeerDependencies: @@ -15699,8 +15678,6 @@ snapshots: ini@4.1.1: {} - inline-style-parser@0.2.3: {} - inline-style-parser@0.2.4: {} inline-style-prefixer@7.0.1: @@ -16060,11 +16037,11 @@ snapshots: khroma@2.1.0: {} - knip@5.41.1(@types/node@22.10.2)(typescript@5.7.2): + knip@5.41.1(@types/node@22.10.5)(typescript@5.7.2): dependencies: '@nodelib/fs.walk': 1.2.8 '@snyk/github-codeowners': 1.1.0 - '@types/node': 22.10.2 + '@types/node': 22.10.5 easy-table: 1.2.0 enhanced-resolve: 5.17.1 fast-glob: 3.3.2 @@ -16083,11 +16060,11 @@ snapshots: kolorist@1.8.0: {} - langchain@0.3.8(@langchain/anthropic@0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(openai@4.77.0(zod@3.24.1)): + langchain@0.3.10(@langchain/anthropic@0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))))(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1)))(axios@1.7.4)(cheerio@1.0.0)(openai@4.77.0(zod@3.24.1)): dependencies: - '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) - '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) - '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + '@langchain/core': 0.3.27(openai@4.77.0(zod@3.24.1)) + '@langchain/openai': 0.3.16(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) + '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) js-tiktoken: 1.0.16 js-yaml: 4.1.0 jsonpointer: 5.0.1 @@ -16099,7 +16076,7 @@ snapshots: zod: 3.24.1 zod-to-json-schema: 3.24.1(zod@3.24.1) optionalDependencies: - '@langchain/anthropic': 0.3.11(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + '@langchain/anthropic': 0.3.11(@langchain/core@0.3.27(openai@4.77.0(zod@3.24.1))) axios: 1.7.4(debug@4.4.0) cheerio: 1.0.0 transitivePeerDependencies: @@ -16156,7 +16133,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lexical@0.22.0: {} + lexical@0.23.0: {} lib0@0.2.97: dependencies: @@ -17034,11 +17011,11 @@ snapshots: dependencies: mimic-function: 5.0.1 - oniguruma-to-es@0.8.1: + oniguruma-to-es@0.10.0: dependencies: emoji-regex-xs: 1.0.0 - regex: 5.0.2 - regex-recursion: 5.0.0 + regex: 5.1.1 + regex-recursion: 5.1.1 onnx-proto@4.0.4: dependencies: @@ -17304,7 +17281,7 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - pnpm@9.15.2: {} + pnpm@9.15.3: {} points-on-curve@0.2.0: {} @@ -17419,7 +17396,7 @@ snapshots: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/long': 4.0.2 - '@types/node': 22.10.2 + '@types/node': 22.10.5 long: 4.0.0 proxy-from-env@1.1.0: {} @@ -17481,7 +17458,7 @@ snapshots: dependencies: react: 19.0.0 - react-intersection-observer@9.14.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-intersection-observer@9.14.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 optionalDependencies: @@ -17489,7 +17466,7 @@ snapshots: react-is@16.13.1: {} - react-markdown@9.0.1(@types/react@19.0.2)(react@19.0.0): + react-markdown@9.0.3(@types/react@19.0.2)(react@19.0.0): dependencies: '@types/hast': 3.0.4 '@types/react': 19.0.2 @@ -17574,7 +17551,7 @@ snapshots: html-react-parser: 5.2.2(@types/react@19.0.2)(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - shiki: 1.24.4 + shiki: 1.26.1 transitivePeerDependencies: - '@types/react' @@ -17664,13 +17641,14 @@ snapshots: regenerator-runtime@0.14.1: {} - regex-recursion@5.0.0: + regex-recursion@5.1.1: dependencies: + regex: 5.1.1 regex-utilities: 2.3.0 regex-utilities@2.3.0: {} - regex@5.0.2: + regex@5.1.1: dependencies: regex-utilities: 2.3.0 @@ -18012,13 +17990,15 @@ snapshots: shell-quote@1.8.2: {} - shiki@1.24.4: + shiki@1.26.1: dependencies: - '@shikijs/core': 1.24.4 - '@shikijs/engine-javascript': 1.24.4 - '@shikijs/engine-oniguruma': 1.24.4 - '@shikijs/types': 1.24.4 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/core': 1.26.1 + '@shikijs/engine-javascript': 1.26.1 + '@shikijs/engine-oniguruma': 1.26.1 + '@shikijs/langs': 1.26.1 + '@shikijs/themes': 1.26.1 + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 '@types/hast': 3.0.4 side-channel@1.0.6: @@ -18157,6 +18137,8 @@ snapshots: sprintf-js@1.1.3: {} + stable-hash@0.0.4: {} + stack-generator@2.0.10: dependencies: stackframe: 1.3.4 @@ -18304,10 +18286,6 @@ snapshots: dependencies: style-to-object: 1.0.8 - style-to-object@1.0.6: - dependencies: - inline-style-parser: 0.2.3 - style-to-object@1.0.8: dependencies: inline-style-parser: 0.2.4 @@ -18779,9 +18757,9 @@ snapshots: universalify@2.0.1: {} - unocss@0.61.9(postcss@8.4.49)(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0)): + unocss@0.61.9(postcss@8.4.49)(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0)): dependencies: - '@unocss/astro': 0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0)) + '@unocss/astro': 0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0)) '@unocss/cli': 0.61.9(rollup@4.24.3) '@unocss/core': 0.61.9 '@unocss/extractor-arbitrary-variants': 0.61.9 @@ -18800,9 +18778,9 @@ snapshots: '@unocss/transformer-compile-class': 0.61.9 '@unocss/transformer-directives': 0.61.9 '@unocss/transformer-variant-group': 0.61.9 - '@unocss/vite': 0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.2)(less@4.2.0)) + '@unocss/vite': 0.61.9(rollup@4.24.3)(vite@5.3.5(@types/node@22.10.5)(less@4.2.0)) optionalDependencies: - vite: 5.3.5(@types/node@22.10.2)(less@4.2.0) + vite: 5.3.5(@types/node@22.10.5)(less@4.2.0) transitivePeerDependencies: - postcss - rollup @@ -18865,7 +18843,7 @@ snapshots: uuid@10.0.0: {} - uuid@11.0.3: {} + uuid@11.0.4: {} uuid@8.3.2: {} @@ -18914,13 +18892,13 @@ snapshots: '@types/unist': 3.0.2 vfile-message: 4.0.2 - vite-node@2.1.8(@types/node@22.10.2)(less@4.2.0): + vite-node@2.1.8(@types/node@22.10.5)(less@4.2.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.5.4 pathe: 1.1.2 - vite: 5.4.11(@types/node@22.10.2)(less@4.2.0) + vite: 5.4.11(@types/node@22.10.5)(less@4.2.0) transitivePeerDependencies: - '@types/node' - less @@ -18932,7 +18910,7 @@ snapshots: - supports-color - terser - vite-plugin-pages@0.32.4(@vue/compiler-sfc@3.4.36)(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)): + vite-plugin-pages@0.32.4(@vue/compiler-sfc@3.4.36)(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)): dependencies: '@types/debug': 4.1.12 debug: 4.3.7 @@ -18942,7 +18920,7 @@ snapshots: json5: 2.2.3 local-pkg: 0.5.1 picocolors: 1.1.1 - vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) yaml: 2.6.1 optionalDependencies: '@vue/compiler-sfc': 3.4.36 @@ -18950,69 +18928,69 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-svgr@4.3.0(rollup@4.24.3)(typescript@5.7.2)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)): + vite-plugin-svgr@4.3.0(rollup@4.24.3)(typescript@5.7.2)(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)): dependencies: '@rollup/pluginutils': 5.1.3(rollup@4.24.3) '@svgr/core': 8.1.0(typescript@5.7.2) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.2)) - vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.7.2)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)): + vite-tsconfig-paths@5.1.4(typescript@5.7.2)(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.1.1(typescript@5.7.2) optionalDependencies: - vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1) transitivePeerDependencies: - supports-color - typescript - vite@5.3.5(@types/node@22.10.2)(less@4.2.0): + vite@5.3.5(@types/node@22.10.5)(less@4.2.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.19.0 optionalDependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 fsevents: 2.3.3 less: 4.2.0 - vite@5.4.11(@types/node@22.10.2)(less@4.2.0): + vite@5.4.11(@types/node@22.10.5)(less@4.2.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.24.3 optionalDependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 fsevents: 2.3.3 less: 4.2.0 - vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1): + vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(tsx@4.19.1)(yaml@2.6.1): dependencies: esbuild: 0.24.2 postcss: 8.4.49 rollup: 4.24.3 optionalDependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 fsevents: 2.3.3 jiti: 2.4.2 less: 4.2.0 tsx: 4.19.1 yaml: 2.6.1 - vitepress@1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.2)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2): + vitepress@1.3.2(@algolia/client-search@4.24.0)(@types/node@22.10.5)(@types/react@19.0.2)(axios@1.7.7)(less@4.2.0)(postcss@8.4.49)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0)(typescript@5.7.2): dependencies: '@docsearch/css': 3.6.1 '@docsearch/js': 3.6.1(@algolia/client-search@4.24.0)(@types/react@19.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.15.0) '@shikijs/core': 1.10.3 '@shikijs/transformers': 1.10.3 '@types/markdown-it': 14.1.1 - '@vitejs/plugin-vue': 5.0.5(vite@5.3.5(@types/node@22.10.2)(less@4.2.0))(vue@3.4.36(typescript@5.7.2)) + '@vitejs/plugin-vue': 5.0.5(vite@5.3.5(@types/node@22.10.5)(less@4.2.0))(vue@3.4.36(typescript@5.7.2)) '@vue/devtools-api': 7.3.6 '@vue/shared': 3.4.35 '@vueuse/core': 10.11.0(vue@3.4.36(typescript@5.7.2)) @@ -19020,8 +18998,8 @@ snapshots: focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 7.0.1 - shiki: 1.24.4 - vite: 5.3.5(@types/node@22.10.2)(less@4.2.0) + shiki: 1.26.1 + vite: 5.3.5(@types/node@22.10.5)(less@4.2.0) vue: 3.4.36(typescript@5.7.2) optionalDependencies: postcss: 8.4.49 @@ -19052,10 +19030,10 @@ snapshots: - typescript - universal-cookie - vitest@2.1.8(@types/node@22.10.2)(less@4.2.0): + vitest@2.1.8(@types/node@22.10.5)(less@4.2.0): dependencies: '@vitest/expect': 2.1.8 - '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.2)(less@4.2.0)) + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.5)(less@4.2.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.8 '@vitest/snapshot': 2.1.8 @@ -19071,11 +19049,11 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.10.2)(less@4.2.0) - vite-node: 2.1.8(@types/node@22.10.2)(less@4.2.0) + vite: 5.4.11(@types/node@22.10.5)(less@4.2.0) + vite-node: 2.1.8(@types/node@22.10.5)(less@4.2.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.10.2 + '@types/node': 22.10.5 transitivePeerDependencies: - less - lightningcss @@ -19322,7 +19300,7 @@ snapshots: zod@3.24.1: {} - zustand@5.0.2(@types/react@19.0.2)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)): + zustand@5.0.3(@types/react@19.0.2)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)): optionalDependencies: '@types/react': 19.0.2 immer: 10.1.1 diff --git a/src/extension/actions/agent-actions.ts b/src/extension/actions/agent-actions.ts new file mode 100644 index 0000000..379e4cf --- /dev/null +++ b/src/extension/actions/agent-actions.ts @@ -0,0 +1,247 @@ +import { ServerPluginRegister } from '@extension/registers/server-plugin-register' +import { runAction } from '@extension/state' +import { ServerActionCollection } from '@shared/actions/server-action-collection' +import type { ActionContext } from '@shared/actions/types' +import type { + ChatContext, + Conversation, + ConversationAction +} from '@shared/entities' +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' +import { produce } from 'immer' +import type { DraftFunction } from 'use-immer' + +export class AgentActionsCollection extends ServerActionCollection { + readonly categoryName = 'agent' + + private getAgentServerUtilsProviderMap(): Record< + string, + AgentServerUtilsProvider + > { + const agentServerPluginRegister = + this.registerManager.getRegister(ServerPluginRegister) + const agentServerUtilsProviderMap = + agentServerPluginRegister?.agentServerPluginRegistry?.providerManagers.serverUtils.getIdProviderMap() + + if (!agentServerUtilsProviderMap) { + throw new Error('AgentServerUtilsProviders not found') + } + + return agentServerUtilsProviderMap + } + + private getAgentServerUtilsProvider( + agentName: string | undefined + ): AgentServerUtilsProvider { + if (!agentName) { + throw new Error('Agent name not found') + } + + const agentServerUtilsProviderMap = this.getAgentServerUtilsProviderMap() + const provider = agentServerUtilsProviderMap[agentName] + + if (!provider) { + throw new Error(`AgentServerUtilsProvider not found for ${agentName}`) + } + + return provider + } + + private async handleAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ConversationAction + autoRefresh?: boolean + }>, + handlerType: + | 'onAcceptAction' + | 'onRejectAction' + | 'onStartAction' + | 'onRestartAction' + ) { + const { action, autoRefresh = true } = context.actionParams + const provider = this.getAgentServerUtilsProvider(action.agent?.name) + + await provider[handlerType]?.(context) + + if (autoRefresh) { + await this.refreshChatSession() + } + } + + private async handleMultipleActions( + context: ActionContext<{ + chatContext: ChatContext + actionItems: { + conversation: Conversation + action: ConversationAction + }[] + autoRefresh?: boolean + }>, + handlerType: 'acceptAction' | 'rejectAction' + ) { + const { + chatContext, + actionItems, + autoRefresh = true + } = context.actionParams + + for (const actionItem of actionItems) { + await this[handlerType]({ + ...context, + actionParams: { + chatContext, + conversation: actionItem.conversation, + action: actionItem.action, + autoRefresh: false + } + }) + } + + if (autoRefresh) { + await this.refreshChatSession() + } + } + + private async refreshChatSession() { + await runAction(this.registerManager).client.chat.refreshCurrentChatSession( + { + actionParams: {} + } + ) + } + + async acceptAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ConversationAction + autoRefresh?: boolean + }> + ) { + await this.handleAction(context, 'onAcceptAction') + } + + async rejectAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ConversationAction + autoRefresh?: boolean + }> + ) { + await this.handleAction(context, 'onRejectAction') + } + + async acceptMultipleActions( + context: ActionContext<{ + chatContext: ChatContext + actionItems: { + conversation: Conversation + action: ConversationAction + }[] + autoRefresh?: boolean + }> + ) { + await this.handleMultipleActions(context, 'acceptAction') + } + + async rejectMultipleActions( + context: ActionContext<{ + chatContext: ChatContext + actionItems: { + conversation: Conversation + action: ConversationAction + }[] + autoRefresh?: boolean + }> + ) { + await this.handleMultipleActions(context, 'rejectAction') + } + + async startAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ConversationAction + autoRefresh?: boolean + }> + ) { + await this.handleAction(context, 'onStartAction') + } + + async restartAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ConversationAction + autoRefresh?: boolean + }> + ) { + await this.handleAction(context, 'onRestartAction') + } + + async updateCurrentAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ConversationAction + updater: ConversationAction | DraftFunction + }> + ) { + const { + chatContext: oldChatContext, + conversation, + action, + updater + } = context.actionParams + + const chatContext = await runAction( + this.registerManager + ).server.chatSession.getChatContext({ + actionParams: { + sessionId: oldChatContext.id + } + }) + + if (!chatContext) throw new Error('Chat context not found') + + const conversationIndex = chatContext.conversations.findIndex( + c => c.id === conversation.id + ) + + if (conversationIndex === -1) throw new Error('Conversation not found') + + const actionIndex = chatContext.conversations[ + conversationIndex + ]!.actions.findIndex(a => a.id === action.id) + + if (actionIndex === -1) throw new Error('Action not found') + + const currentChatContext = await runAction( + this.registerManager + ).server.chatSession.getChatContext({ + actionParams: { + sessionId: chatContext.id + } + }) + + const newChatContext = produce(currentChatContext, draft => { + const action = + draft!.conversations[conversationIndex]!.actions[actionIndex]! + if (typeof updater === 'function') { + updater(action) + } else { + draft!.conversations[conversationIndex]!.actions[actionIndex] = updater + } + }) + + await runAction().server.chatSession.updateSession({ + ...context, + actionParams: { + chatContext: newChatContext! + } + }) + } +} diff --git a/src/extension/actions/apply-actions.ts b/src/extension/actions/apply-actions.ts index 2b640d6..1357a8a 100644 --- a/src/extension/actions/apply-actions.ts +++ b/src/extension/actions/apply-actions.ts @@ -1,6 +1,7 @@ import { ModelProviderFactory } from '@extension/ai/model-providers/helpers/factory' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { InlineDiffRegister } from '@extension/registers/inline-diff-register' +import { TaskEntity } from '@extension/registers/inline-diff-register/task-entity' import type { InlineDiffTask } from '@extension/registers/inline-diff-register/types' import { HumanMessage, SystemMessage } from '@langchain/core/messages' import { ServerActionCollection } from '@shared/actions/server-action-collection' @@ -16,7 +17,7 @@ export class ApplyActionsCollection extends ServerActionCollection { ?.inlineDiffProvider } - async *applyCode( + async *createAndStartApplyCodeTask( context: ActionContext<{ path: string code: string @@ -26,10 +27,12 @@ export class ApplyActionsCollection extends ServerActionCollection { ): AsyncGenerator { const { abortController, actionParams } = context const { path, code, selectionRange, cleanLast } = actionParams - if (!path || !code || !this.inlineDiffProvider) return + if (!path || !code || !this.inlineDiffProvider) + throw new Error('createApplyCodeTask: Invalid parameters') - const originalCode = await VsCodeFS.readFileOrOpenDocumentContent(path) - const taskId = path + const fullPath = await VsCodeFS.getFullPath(path, false) + const originalCode = await VsCodeFS.readFileOrOpenDocumentContent(fullPath) + const taskId = fullPath if (cleanLast) { await this.inlineDiffProvider.resetAndCleanHistory(taskId) @@ -46,7 +49,7 @@ export class ApplyActionsCollection extends ServerActionCollection { new SystemMessage( ` You are a code editor assistant. You are given a file path and a code snippet. You need to apply the code snippet to the file at the given path. -The file path is ${path}. +The file path is ${fullPath}. The original code is: ${originalCode} @@ -62,8 +65,8 @@ Don't reply with anything except the code. const uri = vscode.window.visibleTextEditors.find( - editor => editor.document.uri.toString() === path - )?.document.uri || vscode.Uri.file(path) + editor => editor.document.uri.toString() === fullPath + )?.document.uri || vscode.Uri.file(fullPath) const document = await vscode.workspace.openTextDocument(uri) const fullRange = new vscode.Range( 0, @@ -73,7 +76,7 @@ Don't reply with anything except the code. ) const finalSelectionRange = selectionRange || fullRange - await this.inlineDiffProvider.createTask( + yield await this.inlineDiffProvider.createTask( taskId, uri, finalSelectionRange, @@ -81,17 +84,70 @@ Don't reply with anything except the code. abortController ) - yield* this.inlineDiffProvider.startStreamTask(taskId, buildAiStream) + yield* await this.inlineDiffProvider.startStreamTask(taskId, buildAiStream) } - async interruptApplyCode( + async acceptApplyCodeTask( + context: ActionContext<{ + task: InlineDiffTask + }> + ): Promise { + const { actionParams } = context + const task = TaskEntity.fromJson({ + ...actionParams.task, + abortController: context.abortController + }) + + if (!this.inlineDiffProvider) + throw new Error('acceptApplyCodeTask: inlineDiffProvider not found') + + return this.inlineDiffProvider.acceptAll(task) + } + + async rejectApplyCodeTask( + context: ActionContext<{ + task: InlineDiffTask + }> + ): Promise { + const { actionParams } = context + const task = TaskEntity.fromJson({ + ...actionParams.task, + abortController: context.abortController + }) + + if (!this.inlineDiffProvider) + throw new Error('rejectApplyCodeTask: inlineDiffProvider not found') + + return this.inlineDiffProvider.rejectAll(task) + } + + async abortAndCleanApplyCodeTaskByPath( context: ActionContext<{ path: string }> ): Promise { const { actionParams } = context const { path } = actionParams - if (!path || !this.inlineDiffProvider) return + if (!path || !this.inlineDiffProvider) + throw new Error('abortAndCleanApplyCodeTaskByPath: Invalid parameters') - const taskId = path + const taskId = await VsCodeFS.getFullPath(path, false) await this.inlineDiffProvider.resetAndCleanHistory(taskId) } + + async abortAndCleanApplyCodeTask( + context: ActionContext<{ + task: InlineDiffTask + }> + ): Promise { + const { actionParams } = context + const task = TaskEntity.fromJson({ + ...actionParams.task, + abortController: context.abortController + }) + if (!this.inlineDiffProvider) + throw new Error( + 'abortAndCleanApplyCodeTask: inlineDiffProvider not found' + ) + + await this.inlineDiffProvider.resetAndCleanHistory(task.id) + } } diff --git a/src/extension/actions/file-actions.ts b/src/extension/actions/file-actions.ts index 60dc6cf..3775068 100644 --- a/src/extension/actions/file-actions.ts +++ b/src/extension/actions/file-actions.ts @@ -13,7 +13,10 @@ import { logger } from '@extension/logger' import { getWorkspaceFolder } from '@extension/utils' import { ServerActionCollection } from '@shared/actions/server-action-collection' import type { ActionContext } from '@shared/actions/types' -import type { EditorError, TreeInfo } from '@shared/plugins/fs-plugin/types' +import type { + EditorError, + TreeInfo +} from '@shared/plugins/mentions/fs-mention-plugin/types' import * as vscode from 'vscode' export class FileActionsCollection extends ServerActionCollection { @@ -94,24 +97,7 @@ export class FileActionsCollection extends ServerActionCollection { ): Promise { const { actionParams } = context const { path: filePath, returnNullIfNotExists } = actionParams - try { - const workspaceFolder = getWorkspaceFolder() - const absolutePath = path.isAbsolute(filePath) - ? filePath - : path.join(workspaceFolder.uri.fsPath, filePath) - const stat = await VsCodeFS.stat(absolutePath) - - if ( - returnNullIfNotExists && - stat.type !== vscode.FileType.File && - stat.type !== vscode.FileType.Directory - ) - return null - - return absolutePath - } catch { - return null - } + return await VsCodeFS.getFullPath(filePath, returnNullIfNotExists ?? false) } async getFileInfoForMessage( @@ -124,8 +110,16 @@ export class FileActionsCollection extends ServerActionCollection { const { actionParams } = context const { relativePath, startLine, endLine } = actionParams try { - const workspaceFolder = getWorkspaceFolder() - const fullPath = path.join(workspaceFolder.uri.fsPath, relativePath) + const fullPath = await this.getFullPath({ + ...context, + actionParams: { + path: relativePath, + returnNullIfNotExists: true + } + }) + + if (!fullPath) return null + const fileInfo = await VsCodeFS.stat(fullPath) if (!fileInfo || fileInfo.type !== vscode.FileType.File) return null diff --git a/src/extension/actions/git-actions.ts b/src/extension/actions/git-actions.ts index 25c55fe..af80656 100644 --- a/src/extension/actions/git-actions.ts +++ b/src/extension/actions/git-actions.ts @@ -3,7 +3,10 @@ import type { RegisterManager } from '@extension/registers/register-manager' import { getWorkspaceFolder } from '@extension/utils' import { ServerActionCollection } from '@shared/actions/server-action-collection' import type { ActionContext } from '@shared/actions/types' -import type { GitCommit, GitDiff } from '@shared/plugins/git-plugin/types' +import type { + GitCommit, + GitDiff +} from '@shared/plugins/mentions/git-mention-plugin/types' import { settledPromiseResults } from '@shared/utils/common' import simpleGit, { SimpleGit } from 'simple-git' diff --git a/src/extension/actions/index.ts b/src/extension/actions/index.ts index 7fe71b9..b25a182 100644 --- a/src/extension/actions/index.ts +++ b/src/extension/actions/index.ts @@ -1,5 +1,6 @@ import type { ServerActionCollection } from '@shared/actions/server-action-collection' +import { AgentActionsCollection } from './agent-actions' import { AIModelActionsCollection } from './ai-model-actions' import { AIProviderActionsCollection } from './ai-provider-actions' import { ApplyActionsCollection } from './apply-actions' @@ -29,6 +30,7 @@ export const serverActionCollections = [ AIProviderActionsCollection, AIModelActionsCollection, MentionActionsCollection, + AgentActionsCollection, PromptSnippetActionsCollection ] as const satisfies (typeof ServerActionCollection)[] diff --git a/src/extension/actions/mention-actions.ts b/src/extension/actions/mention-actions.ts index 30dbf12..f064bd2 100644 --- a/src/extension/actions/mention-actions.ts +++ b/src/extension/actions/mention-actions.ts @@ -4,30 +4,30 @@ import { ServerActionCollection } from '@shared/actions/server-action-collection import type { ActionContext } from '@shared/actions/types' import type { Conversation, Mention } from '@shared/entities' import type { - RefreshMentionFn, - ServerUtilsProvider -} from '@shared/plugins/base/server/create-provider-manager' + MentionServerUtilsProvider, + RefreshMentionFn +} from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { settledPromiseResults, tryParseJSON } from '@shared/utils/common' export class MentionActionsCollection extends ServerActionCollection { readonly categoryName = 'mention' - private getServerUtilsProviders(): ServerUtilsProvider[] { - const serverPluginRegister = + private getMentionServerUtilsProviders(): MentionServerUtilsProvider[] { + const mentionServerPluginRegister = this.registerManager.getRegister(ServerPluginRegister) - const serverUtilsProviders = - serverPluginRegister?.serverPluginRegistry?.providerManagers.serverUtils.getValues() + const mentionServerUtilsProviders = + mentionServerPluginRegister?.mentionServerPluginRegistry?.providerManagers.serverUtils.getValues() - if (!serverUtilsProviders) { - throw new Error('ServerUtilsProviders not found') + if (!mentionServerUtilsProviders) { + throw new Error('MentionServerUtilsProviders not found') } - return serverUtilsProviders + return mentionServerUtilsProviders } private async createCompositeRefreshFunction(): Promise { // Get mention utils providers - const serverUtilsProviders = this.getServerUtilsProviders() + const mentionServerUtilsProviders = this.getMentionServerUtilsProviders() // Get controller register const actionRegister = this.registerManager.getRegister(ActionRegister) @@ -37,7 +37,7 @@ export class MentionActionsCollection extends ServerActionCollection { // Create refresh functions from all providers const refreshFunctions = await settledPromiseResults( - serverUtilsProviders.map( + mentionServerUtilsProviders.map( async provider => await provider.createRefreshMentionFn(actionRegister) ) ) diff --git a/src/extension/actions/terminal-actions.ts b/src/extension/actions/terminal-actions.ts index 37fdb74..49b0196 100644 --- a/src/extension/actions/terminal-actions.ts +++ b/src/extension/actions/terminal-actions.ts @@ -17,4 +17,18 @@ export class TerminalActionsCollection extends ServerActionCollection { ): Promise { return this.terminalWatcher?.getAllTerminalInfos() || [] } + + async runTerminalCommand( + context: ActionContext<{ + command: string + isBackground: boolean + }> + ) { + // TODO: Implement this + return { + output: '', + exitCode: 0, + terminalInfo: null + } + } } diff --git a/src/extension/chat/strategies/base/base-agent.ts b/src/extension/chat/strategies/base/base-agent.ts index 29f09a9..96a3d48 100644 --- a/src/extension/chat/strategies/base/base-agent.ts +++ b/src/extension/chat/strategies/base/base-agent.ts @@ -27,8 +27,6 @@ export abstract class BaseAgent< abstract name: string - abstract logTitle: string - abstract description: string constructor( @@ -39,7 +37,7 @@ export abstract class BaseAgent< abstract execute(input: z.infer): Promise> // Create the Langchain tool - public async createTool(): Promise { + async createTool(): Promise { return new DynamicStructuredTool({ name: this.name, description: this.description, diff --git a/src/extension/chat/strategies/base/base-node.ts b/src/extension/chat/strategies/base/base-node.ts index 6330ed3..f93357d 100644 --- a/src/extension/chat/strategies/base/base-node.ts +++ b/src/extension/chat/strategies/base/base-node.ts @@ -5,9 +5,10 @@ import type { } from '@extension/chat/strategies/base/base-state' import type { BaseStrategyOptions } from '@extension/chat/strategies/base/base-strategy' import { findCurrentToolsCallParams } from '@extension/chat/utils/find-current-tools-call-params' +import { logger } from '@extension/logger' import type { ToolMessage } from '@langchain/core/messages' import type { DynamicStructuredTool } from '@langchain/core/tools' -import type { Agent, Conversation, ConversationLog } from '@shared/entities' +import type { Agent, Conversation } from '@shared/entities' import type { ZodObjectAny } from '@shared/types/common' import { settledPromiseResults } from '@shared/utils/common' import { produce } from 'immer' @@ -35,7 +36,6 @@ export type AgentsConfig = { type ExecuteAgentToolResult = { agents: Agent, GetAgentOutput>[] - logs: ConversationLog[] } type BuildAgentConfig = ( @@ -114,6 +114,18 @@ export abstract class BaseNode< ...overrideAgentContext } + if ( + !finalAgentContext.createToolOptions || + !finalAgentContext.state || + !finalAgentContext.strategyOptions + ) { + logger.error( + 'Agent context is missing required properties', + finalAgentContext + ) + throw new Error('Agent context is missing required properties') + } + const agentInstance = new agentConfig.agentClass(finalAgentContext) const tool = await agentInstance.createTool() return { tool, agentConfig, agentInstance } @@ -127,8 +139,7 @@ export abstract class BaseNode< const { agentClass: AgentClass, agentContext, processAgentOutput } = props const results: ExecuteAgentToolResult = { - agents: [], - logs: [] + agents: [] } const { tool, agentConfig, agentInstance } = @@ -149,7 +160,7 @@ export abstract class BaseNode< const agentOutput = JSON.parse(toolMessage?.lc_kwargs.content) const agent: Agent, GetAgentOutput> = { - id: uuidv4(), + id: toolCall.id || uuidv4(), name: tool.name, input: toolCall.args, output: processAgentOutput @@ -157,10 +168,7 @@ export abstract class BaseNode< : agentOutput } - const log = this.createAgentLog(agentInstance.logTitle, agent.id) - results.agents.push(agent) - results.logs.push(log) }) await settledPromiseResults(toolCallsPromises) @@ -172,27 +180,27 @@ export abstract class BaseNode< conversation: Conversation, agents: Agent, GetAgentOutput>[] ) { - conversation.agents = produce(conversation.agents, draft => { + conversation.thinkAgents = produce(conversation.thinkAgents, draft => { draft.push(...agents) }) } - protected addLogsToConversation( - conversation: Conversation, - logs: ConversationLog[] + protected addAgentsToLastHumanAndNewConversation( + state: State, + agents: Agent, GetAgentOutput>[] ) { - conversation.logs = produce(conversation.logs, draft => { - draft.push(...logs) - }) - } + const lastHumanConversation = [...state.chatContext.conversations] + .reverse() + .find(conversation => conversation.role === 'human') + + if (lastHumanConversation) { + this.addAgentsToConversation(lastHumanConversation, agents) + } + + const newConversation = state.newConversations.at(-1)! - // Helper method to create agent log - protected createAgentLog(title: string, agentId: string): ConversationLog { - return { - id: uuidv4(), - createdAt: Date.now(), - title, - agentId + if (newConversation) { + this.addAgentsToConversation(newConversation, agents) } } @@ -230,7 +238,9 @@ export const createToolsFromNodes = async < ( await Promise.all( props.nodeClasses.map(async NodeClass => { - const nodeInstance = new NodeClass(props.strategyOptions) + const nodeInstance = new NodeClass({ + strategyOptions: props.strategyOptions + } as BaseNodeContext) return await nodeInstance.createTools(props.state) }) ) @@ -245,6 +255,8 @@ export const createGraphNodeFromNodes = async < }) => await Promise.all( props.nodeClasses.map(NodeClass => - new NodeClass(props.strategyOptions).createGraphNode() + new NodeClass({ + strategyOptions: props.strategyOptions + } as BaseNodeContext).createGraphNode() ) ) diff --git a/src/extension/chat/strategies/chat-strategy/chat-workflow.ts b/src/extension/chat/strategies/chat-strategy/chat-workflow.ts index 018c99e..f3f2ddc 100644 --- a/src/extension/chat/strategies/chat-strategy/chat-workflow.ts +++ b/src/extension/chat/strategies/chat-strategy/chat-workflow.ts @@ -19,7 +19,7 @@ const createSmartRoute = export const createChatWorkflow = async (options: BaseStrategyOptions) => { const chatStrategyProvider = options.registerManager .getRegister(ServerPluginRegister) - ?.serverPluginRegistry?.providerManagers.chatStrategy.mergeAll() + ?.mentionServerPluginRegistry?.providerManagers.chatStrategy.mergeAll() const toolNodes = (await chatStrategyProvider?.buildLanggraphToolNodes?.(options)) || [] diff --git a/src/extension/chat/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts b/src/extension/chat/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts index fd496b4..b3b293e 100644 --- a/src/extension/chat/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts +++ b/src/extension/chat/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts @@ -23,7 +23,7 @@ export class ChatMessagesConstructor { private getChatStrategyProvider() { return this.registerManager .getRegister(ServerPluginRegister) - ?.serverPluginRegistry?.providerManagers.chatStrategy.mergeAll() + ?.mentionServerPluginRegistry?.providerManagers.chatStrategy.mergeAll() } constructor(options: ChatMessagesConstructorOptions) { diff --git a/src/extension/chat/strategies/chat-strategy/messages-constructors/conversation-message-constructor.ts b/src/extension/chat/strategies/chat-strategy/messages-constructors/conversation-message-constructor.ts index f8ab697..578ed24 100644 --- a/src/extension/chat/strategies/chat-strategy/messages-constructors/conversation-message-constructor.ts +++ b/src/extension/chat/strategies/chat-strategy/messages-constructors/conversation-message-constructor.ts @@ -3,15 +3,15 @@ import { HumanMessage } from '@langchain/core/messages' import type { ChatContext, Conversation, - LangchainMessage, - LangchainMessageContents + ConversationContents, + LangchainMessage } from '@shared/entities' -import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionChatStrategyProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' interface ConversationMessageConstructorOptions { chatContext: ChatContext conversation: Conversation - chatStrategyProvider: ChatStrategyProvider + chatStrategyProvider: MentionChatStrategyProvider } export class ConversationMessageConstructor { @@ -19,7 +19,7 @@ export class ConversationMessageConstructor { private conversation: Conversation - private chatStrategyProvider: ChatStrategyProvider + private chatStrategyProvider: MentionChatStrategyProvider constructor(options: ConversationMessageConstructorOptions) { this.chatContext = options.chatContext @@ -80,30 +80,29 @@ ${prompt} this.chatContext )) || [] - const imageContents: LangchainMessageContents = + const imageContents: ConversationContents = imageUrls.map(url => ({ type: 'image_url', image_url: { url } })) || [] let isEnhanced = false - const enhancedContents: LangchainMessageContents = - this.conversation.contents - .map(content => { - if (content.type === 'text' && !isEnhanced) { - isEnhanced = true - return { - ...content, - text: ` + const enhancedContents: ConversationContents = this.conversation.contents + .map(content => { + if (content.type === 'text' && !isEnhanced) { + isEnhanced = true + return { + ...content, + text: ` ${prompt} ${content.text} ${endPrompt} ` - } } - return content - }) - .concat(...imageContents) + } + return content + }) + .concat(...imageContents) return new HumanMessage({ content: enhancedContents }) } diff --git a/src/extension/chat/strategies/chat-strategy/nodes/agent-node.ts b/src/extension/chat/strategies/chat-strategy/nodes/agent-node.ts index 1dd3f28..96cbc16 100644 --- a/src/extension/chat/strategies/chat-strategy/nodes/agent-node.ts +++ b/src/extension/chat/strategies/chat-strategy/nodes/agent-node.ts @@ -3,8 +3,8 @@ import { getToolCallsFromMessage } from '@extension/chat/utils/get-tool-calls-fr import { ServerPluginRegister } from '@extension/registers/server-plugin-register' import type { AIMessageChunk } from '@langchain/core/messages' import { type LangchainTool } from '@shared/entities' -import { convertToLangchainMessageContents } from '@shared/utils/convert-to-langchain-message-contents' -import { mergeLangchainMessageContents } from '@shared/utils/merge-langchain-message-contents' +import { mergeConversationContents } from '@shared/utils/chat-context-helper/common/merge-conversation-contents' +import { parseAsConversationContents } from '@shared/utils/chat-context-helper/common/parse-as-conversation-contents' import { produce } from 'immer' import { BaseNode } from '../../base/base-node' @@ -23,7 +23,7 @@ export class AgentNode extends BaseNode { const aiModel = await modelProvider.createLangChainModel() const chatStrategyProvider = this.context.strategyOptions.registerManager .getRegister(ServerPluginRegister) - ?.serverPluginRegistry?.providerManagers.chatStrategy.mergeAll() + ?.mentionServerPluginRegistry?.providerManagers.chatStrategy.mergeAll() const tools = [ ...((await chatStrategyProvider?.buildAgentTools?.( @@ -59,13 +59,13 @@ export class AgentNode extends BaseNode { } const toolCalls = getToolCallsFromMessage(message) - const contents = convertToLangchainMessageContents(message.content) + const contents = parseAsConversationContents(message.content) if (!toolCalls.length && contents.length) { // no tool calls shouldContinue = false newConversations = produce(newConversations, draft => { - draft.at(-1)!.contents = mergeLangchainMessageContents([ + draft.at(-1)!.contents = mergeConversationContents([ ...draft.at(-1)!.contents, ...contents ]) diff --git a/src/extension/chat/strategies/chat-strategy/nodes/generate-node.ts b/src/extension/chat/strategies/chat-strategy/nodes/generate-node.ts index ec38166..e4f82fa 100644 --- a/src/extension/chat/strategies/chat-strategy/nodes/generate-node.ts +++ b/src/extension/chat/strategies/chat-strategy/nodes/generate-node.ts @@ -1,7 +1,7 @@ import { ModelProviderFactory } from '@extension/ai/model-providers/helpers/factory' import type { AIMessageChunk } from '@langchain/core/messages' -import { convertToLangchainMessageContents } from '@shared/utils/convert-to-langchain-message-contents' -import { mergeLangchainMessageContents } from '@shared/utils/merge-langchain-message-contents' +import { mergeConversationContents } from '@shared/utils/chat-context-helper/common/merge-conversation-contents' +import { parseAsConversationContents } from '@shared/utils/chat-context-helper/common/parse-as-conversation-contents' import { produce } from 'immer' import { BaseNode } from '../../base/base-node' @@ -41,11 +41,11 @@ export class GenerateNode extends BaseNode { message = message.concat(chunk) } - const contents = convertToLangchainMessageContents(message.content) + const contents = parseAsConversationContents(message.content) if (contents.length) { newConversations = produce(state.newConversations, draft => { - draft.at(-1)!.contents = mergeLangchainMessageContents([ + draft.at(-1)!.contents = mergeConversationContents([ ...draft.at(-1)!.contents, ...contents ]) diff --git a/src/extension/chat/utils/conversation-utils.ts b/src/extension/chat/utils/conversation-utils.ts index 9483e13..b9dee2c 100644 --- a/src/extension/chat/utils/conversation-utils.ts +++ b/src/extension/chat/utils/conversation-utils.ts @@ -1,7 +1,7 @@ import type { RegisterManager } from '@extension/registers/register-manager' import { ServerPluginRegister } from '@extension/registers/server-plugin-register' -import type { Agent, ChatContext, Conversation } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { ChatContext, Conversation } from '@shared/entities' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' /** * Process all conversations in chatContext and collect AI agents for each human conversation @@ -14,26 +14,19 @@ export const processConversationsForCreateMessage = ( const { conversations } = chatContext const serverPluginRegister = registerManager.getRegister(ServerPluginRegister) const serverUtilsProviders = - serverPluginRegister?.serverPluginRegistry?.providerManagers.serverUtils.getValues() + serverPluginRegister?.mentionServerPluginRegistry?.providerManagers.serverUtils.getValues() if (!serverUtilsProviders) { throw new Error('ServerUtilsProviders not found') } - const updatedConversations = conversations.map( - (currentConversation, index) => { - const updatedAgentsConversations = updateConversationAgents( - chatContext, - currentConversation, - index - ) - const updatedConversation = updateConversationByProviders( - serverUtilsProviders, - updatedAgentsConversations - ) - return updatedConversation - } - ) + const updatedConversations = conversations.map(currentConversation => { + const updatedConversation = updateConversationByProviders( + serverUtilsProviders, + currentConversation + ) + return updatedConversation + }) return { ...chatContext, @@ -41,33 +34,8 @@ export const processConversationsForCreateMessage = ( } } -const updateConversationAgents = ( - chatContext: ChatContext, - currentConversation: Conversation, - index: number -): Conversation => { - const { conversations } = chatContext - if (currentConversation.role !== 'human') return currentConversation - - // Collect AI agents until next human message - const aiAgents: Agent[] = [] - let i = index + 1 - - while (i < conversations.length && conversations[i]!.role !== 'human') { - if (conversations[i]!.role === 'ai') { - aiAgents.push(...(conversations[i]!.agents || [])) - } - i++ - } - - return { - ...currentConversation, - agents: aiAgents - } -} - const updateConversationByProviders = ( - serverUtilsProviders: ServerUtilsProvider[], + serverUtilsProviders: MentionServerUtilsProvider[], conversation: Conversation ): Conversation => { let latestConversation = conversation diff --git a/src/extension/chat/utils/message-builder.ts b/src/extension/chat/utils/message-builder.ts index 9de7520..670c69c 100644 --- a/src/extension/chat/utils/message-builder.ts +++ b/src/extension/chat/utils/message-builder.ts @@ -4,15 +4,12 @@ import { SystemMessage, type MessageType } from '@langchain/core/messages' -import type { - LangchainMessage, - LangchainMessageContents -} from '@shared/entities' +import type { ConversationContents, LangchainMessage } from '@shared/entities' export class MessageBuilder { static createMessage( type: MessageType, - messageContents: LangchainMessageContents + messageContents: ConversationContents ): LangchainMessage | null { switch (type) { case 'human': diff --git a/src/extension/chat/vectordb/base-indexer.ts b/src/extension/chat/vectordb/base-indexer.ts index e49ffb1..9af33a7 100644 --- a/src/extension/chat/vectordb/base-indexer.ts +++ b/src/extension/chat/vectordb/base-indexer.ts @@ -1,6 +1,6 @@ -import crypto from 'crypto' import { EmbeddingManager } from '@extension/ai/embeddings/embedding-manager' import type { BaseEmbeddings } from '@extension/ai/embeddings/types' +import { getFileHash } from '@extension/file-utils/get-file-hash' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { logger } from '@extension/logger' import { @@ -138,8 +138,7 @@ export abstract class BaseIndexer { } async generateFileHash(filePath: string): Promise { - const content = await VsCodeFS.readFile(filePath) - return crypto.createHash('sha256').update(content, 'utf-8').digest('hex') + return await getFileHash(filePath) } async isValidRow(row: T): Promise { diff --git a/src/extension/file-utils/generate-tree.ts b/src/extension/file-utils/generate-tree.ts index ec1c1b8..b9fce6d 100644 --- a/src/extension/file-utils/generate-tree.ts +++ b/src/extension/file-utils/generate-tree.ts @@ -1,7 +1,7 @@ import path from 'path' import { logger } from '@extension/logger' import { getWorkspaceFolder } from '@extension/utils' -import type { TreeInfo } from '@shared/plugins/fs-plugin/types' +import type { TreeInfo } from '@shared/plugins/mentions/fs-mention-plugin/types' import * as vscode from 'vscode' import { traverseFileOrFolders, type FsItemInfo } from './traverse-fs' diff --git a/src/extension/file-utils/get-file-hash.ts b/src/extension/file-utils/get-file-hash.ts new file mode 100644 index 0000000..a1088f3 --- /dev/null +++ b/src/extension/file-utils/get-file-hash.ts @@ -0,0 +1,8 @@ +import crypto from 'crypto' + +import { VsCodeFS } from './vscode-fs' + +export const getFileHash = async (filePath: string) => { + const content = await VsCodeFS.readFile(filePath) + return crypto.createHash('sha256').update(content, 'utf-8').digest('hex') +} diff --git a/src/extension/file-utils/vscode-fs.ts b/src/extension/file-utils/vscode-fs.ts index 58c4986..e04c1d0 100644 --- a/src/extension/file-utils/vscode-fs.ts +++ b/src/extension/file-utils/vscode-fs.ts @@ -1,4 +1,6 @@ /* eslint-disable unused-imports/no-unused-vars */ +import path from 'path' +import { getWorkspaceFolder } from '@extension/utils' import JSONC from 'comment-json' import * as vscode from 'vscode' @@ -100,9 +102,10 @@ export class VsCodeFS { static async exists(path: string): Promise { try { - await this.stat(path) + const uri = vscode.Uri.file(path) + await this.fs.stat(uri) return true - } catch { + } catch (error) { return false } } @@ -114,4 +117,25 @@ export class VsCodeFS { static async readJsonFile(filePath: string): Promise { return JSONC.parse(await this.readFile(filePath, 'utf8')) as T } + + static async getFullPath( + filePath: string, + returnNullIfNotExists: T + ): Promise { + let absolutePath: string = filePath + try { + const workspaceFolder = getWorkspaceFolder() + absolutePath = path.isAbsolute(filePath) + ? filePath + : path.join(workspaceFolder.uri.fsPath, filePath) + const isExists = await VsCodeFS.exists(absolutePath) + + if (returnNullIfNotExists && !isExists) return null as any + + return absolutePath + } catch { + if (returnNullIfNotExists) return null as any + return absolutePath + } + } } diff --git a/src/extension/registers/inline-diff-register/decoration-manager.ts b/src/extension/registers/inline-diff-register/decoration-manager.ts index 3180c58..9394cdb 100644 --- a/src/extension/registers/inline-diff-register/decoration-manager.ts +++ b/src/extension/registers/inline-diff-register/decoration-manager.ts @@ -5,7 +5,7 @@ import type { DiffProcessor } from './diff-processor' import { InlineDiffTask, type DiffBlockWithRange } from './types' export class DecorationManager { - private diffDecorationTypes: { + diffDecorationTypes: { add: vscode.TextEditorDecorationType remove: vscode.TextEditorDecorationType scanning: vscode.TextEditorDecorationType diff --git a/src/extension/registers/inline-diff-register/diff-processor.ts b/src/extension/registers/inline-diff-register/diff-processor.ts index c841057..8951b55 100644 --- a/src/extension/registers/inline-diff-register/diff-processor.ts +++ b/src/extension/registers/inline-diff-register/diff-processor.ts @@ -174,6 +174,8 @@ export class DiffProcessor { task.waitForReviewDiffBlockIds = task.diffBlocks .filter(block => block.type !== 'no-change') .map(block => block.id) + task.originalWaitForReviewDiffBlockIdCount = + task.waitForReviewDiffBlockIds.length } async buildDiffContent( diff --git a/src/extension/registers/inline-diff-register/history-manager.ts b/src/extension/registers/inline-diff-register/history-manager.ts index 3f0098f..9af7aee 100644 --- a/src/extension/registers/inline-diff-register/history-manager.ts +++ b/src/extension/registers/inline-diff-register/history-manager.ts @@ -1,9 +1,16 @@ import type { DiffAction, DiffEdit } from './types' export class HistoryManager { - private actions: DiffAction[] = [] + actions: DiffAction[] = [] - private position: number = -1 + position: number = -1 + + constructor(options?: { actions: DiffAction[]; position: number }) { + if (options) { + this.actions = options.actions + this.position = options.position + } + } push(action: DiffAction) { this.position++ diff --git a/src/extension/registers/inline-diff-register/index.ts b/src/extension/registers/inline-diff-register/index.ts index 73701d7..0edbb49 100644 --- a/src/extension/registers/inline-diff-register/index.ts +++ b/src/extension/registers/inline-diff-register/index.ts @@ -9,7 +9,7 @@ import { InlineDiffProvider } from './inline-diff-provider' export class InlineDiffRegister extends BaseRegister { private disposables: vscode.Disposable[] = [] - public inlineDiffProvider!: InlineDiffProvider + inlineDiffProvider!: InlineDiffProvider constructor( protected context: vscode.ExtensionContext, diff --git a/src/extension/registers/inline-diff-register/inline-diff-provider.ts b/src/extension/registers/inline-diff-register/inline-diff-provider.ts index 9bc11d1..03690e0 100644 --- a/src/extension/registers/inline-diff-register/inline-diff-provider.ts +++ b/src/extension/registers/inline-diff-register/inline-diff-provider.ts @@ -35,10 +35,6 @@ export class InlineDiffProvider implements vscode.CodeLensProvider { return this.codeLensEventEmitter.event } - get onDidChangeTaskState(): vscode.Event { - return this.taskManager.onDidChangeTaskState - } - async createTask( taskId: string, fileUri: vscode.Uri, @@ -65,19 +61,19 @@ export class InlineDiffProvider implements vscode.CodeLensProvider { abortController: AbortController ) => Promise> ): AsyncGenerator { - yield* this.taskManager.startStreamTask(taskId, buildAiStream) + yield* await this.taskManager.startStreamTask(taskId, buildAiStream) } async provideCodeLenses( document: vscode.TextDocument ): Promise { const codeLenses: vscode.CodeLens[] = [] - for (const task of this.taskManager.getAllTasks()) { if ( document.uri.toString() !== task.originalFileUri.toString() || - task.state === InlineDiffTaskState.Finished || - task.state === InlineDiffTaskState.Rejected + [InlineDiffTaskState.Accepted, InlineDiffTaskState.Rejected].includes( + task.state + ) ) { continue } @@ -105,7 +101,7 @@ export class InlineDiffProvider implements vscode.CodeLensProvider { continue } - if (task.state === InlineDiffTaskState.Pending) { + if (task.state === InlineDiffTaskState.Generating) { codeLenses.push( new vscode.CodeLens(topRange, { title: '$(sync~spin) Aide is working...', @@ -115,7 +111,7 @@ export class InlineDiffProvider implements vscode.CodeLensProvider { continue } - if (task.state === InlineDiffTaskState.Applying) { + if (task.state === InlineDiffTaskState.Reviewing) { codeLenses.push( new vscode.CodeLens(topRange, { title: '$(check) Accept All', @@ -195,7 +191,8 @@ export class InlineDiffProvider implements vscode.CodeLensProvider { blocks: DiffBlock[], actionId = uuidv4() ) { - await this.taskManager.acceptDiffs(task, blocks, actionId) + const finalTask = this.taskManager.getTask(task.id) || task + await this.taskManager.acceptDiffs(finalTask, blocks, actionId) } async rejectDiffs( @@ -203,17 +200,20 @@ export class InlineDiffProvider implements vscode.CodeLensProvider { blocks: DiffBlock[], actionId = uuidv4() ) { - await this.taskManager.rejectDiffs(task, blocks, actionId) + const finalTask = this.taskManager.getTask(task.id) || task + await this.taskManager.rejectDiffs(finalTask, blocks, actionId) } async acceptAll(task: InlineDiffTask, actionId = uuidv4()) { await this.acceptDiffs(task, task.diffBlocks, actionId) - this.taskManager.updateTaskState(task, InlineDiffTaskState.Finished) + this.taskManager.updateTaskState(task, InlineDiffTaskState.Accepted) + return task } async rejectAll(task: InlineDiffTask, actionId = uuidv4()) { await this.rejectDiffs(task, task.diffBlocks, actionId) this.taskManager.updateTaskState(task, InlineDiffTaskState.Rejected) + return task } async resetAndCleanHistory(taskId: string) { diff --git a/src/extension/registers/inline-diff-register/task-entity.ts b/src/extension/registers/inline-diff-register/task-entity.ts new file mode 100644 index 0000000..67ae393 --- /dev/null +++ b/src/extension/registers/inline-diff-register/task-entity.ts @@ -0,0 +1,86 @@ +import { + convertRangeJsonToVSCodeRange, + convertUriJsonToVSCodeUri, + type VSCodeRangeJson, + type VSCodeUriJson +} from '@extension/utils' +import { BaseEntity } from '@shared/entities' +import { v4 as uuidv4 } from 'uuid' +import * as vscode from 'vscode' + +import { HistoryManager } from './history-manager' +import { + InlineDiffTaskState, + type DiffAction, + type InlineDiffTask +} from './types' + +export interface TaskEntityJsonData + extends Omit< + InlineDiffTask, + 'selectionRange' | 'originalFileUri' | 'history' | 'error' + > { + selectionRange: VSCodeRangeJson + originalFileUri: VSCodeUriJson + error?: string + history: { + actions: DiffAction[] + position: number + } +} + +export class TaskEntity extends BaseEntity { + protected getDefaults(override?: Partial) { + return { + id: uuidv4(), + state: InlineDiffTaskState.Idle, + selectionRange: new vscode.Range(0, 0, 0, 0), + selectionContent: '', + contentAfterSelection: '', + replacementContent: '', + originalFileUri: vscode.Uri.file(''), + diffBlocks: [], + lastKnownDocumentVersion: 0, + waitForReviewDiffBlockIds: [], + originalWaitForReviewDiffBlockIdCount: 0, + history: new HistoryManager(), + ...override + } + } + + static toJson(task: InlineDiffTask): TaskEntityJsonData { + return JSON.parse(JSON.stringify(task)) + } + + static fromJson( + taskJsonData: TaskEntityJsonData | InlineDiffTask + ): InlineDiffTask { + const task: InlineDiffTask = new TaskEntity({ + ...taskJsonData, + selectionRange: convertRangeJsonToVSCodeRange( + taskJsonData.selectionRange + ), + originalFileUri: convertUriJsonToVSCodeUri(taskJsonData.originalFileUri), + error: + taskJsonData.error instanceof Error + ? taskJsonData.error + : typeof taskJsonData.error === 'string' + ? new Error(taskJsonData.error) + : undefined, + history: + taskJsonData.history instanceof HistoryManager + ? taskJsonData.history + : new HistoryManager({ + ...taskJsonData.history + }), + abortController: + taskJsonData.abortController instanceof AbortController + ? taskJsonData.abortController + : taskJsonData.abortController + ? new AbortController() + : undefined + }).entity + + return task + } +} diff --git a/src/extension/registers/inline-diff-register/task-manager.ts b/src/extension/registers/inline-diff-register/task-manager.ts index 940480f..0521c0b 100644 --- a/src/extension/registers/inline-diff-register/task-manager.ts +++ b/src/extension/registers/inline-diff-register/task-manager.ts @@ -12,7 +12,7 @@ import * as vscode from 'vscode' import { DecorationManager } from './decoration-manager' import { DiffProcessor } from './diff-processor' -import { HistoryManager } from './history-manager' +import { TaskEntity } from './task-entity' import { InlineDiffTask, InlineDiffTaskState, @@ -26,18 +26,29 @@ export class TaskManager { private tasks: Map = new Map() - private taskStateChangeEmitter = new vscode.EventEmitter() - codeLensChangeEmitter = new vscode.EventEmitter() - get onDidChangeTaskState(): vscode.Event { - return this.taskStateChangeEmitter.event - } - constructor( private diffProcessor: DiffProcessor, private decorationManager: DecorationManager - ) {} + ) { + this.disposes.push( + vscode.window.onDidChangeActiveTextEditor(async editor => { + if (!editor) return + + const tasksInFile = Array.from(this.tasks.values()).filter( + task => + task.originalFileUri.toString() === editor.document.uri.toString() + ) + + for (const task of tasksInFile) { + const blocksWithRange = + await this.diffProcessor.getDiffBlocksWithDisplayRange(task) + await this.updateDecorationsAndCodeLenses(task, blocksWithRange) + } + }) + ) + } async createTask( taskId: string, @@ -55,7 +66,7 @@ export class TaskManager { ) ) - const task: InlineDiffTask = { + const task = new TaskEntity({ id: taskId, state: InlineDiffTaskState.Idle, selectionRange: selection, @@ -63,23 +74,36 @@ export class TaskManager { contentAfterSelection, replacementContent, originalFileUri: fileUri, - diffBlocks: [], abortController, - lastKnownDocumentVersion: document.version, - waitForReviewDiffBlockIds: [], - history: new HistoryManager() - } - + lastKnownDocumentVersion: document.version + }).entity this.tasks.set(task.id, task) return task } + resetTask(taskId: string) { + const task = this.getTask(taskId) + if (!task) return + + this.updateTaskState(task, InlineDiffTaskState.Idle) + task.replacementContent = '' + task.diffBlocks = [] + task.abortController?.abort() + task.error = undefined + task.waitForReviewDiffBlockIds = [] + task.originalWaitForReviewDiffBlockIdCount = 0 + task.history.clear() + + this.tasks.set(taskId, task) + return task + } + async startTask(taskId: string) { const task = this.getTask(taskId) if (!task) throw new Error('Task not found') try { - this.updateTaskState(task, InlineDiffTaskState.Applying) + this.updateTaskState(task, InlineDiffTaskState.Reviewing) await this.diffProcessor.computeDiff(task) const blocksWithRange = @@ -101,7 +125,7 @@ export class TaskManager { if (!task) throw new Error('Task not found') try { - this.updateTaskState(task, InlineDiffTaskState.Pending) + this.updateTaskState(task, InlineDiffTaskState.Generating) if (!task.abortController) { task.abortController = new AbortController() @@ -111,8 +135,9 @@ export class TaskManager { let accumulatedContent = '' await this.setupDocumentChangeListener(task) + const streamChunks = await this.processStreamChunks(aiStream) - for await (const chunk of this.processStreamChunks(aiStream)) { + for await (const chunk of streamChunks) { accumulatedContent += chunk task.replacementContent = this.getGeneratedFullLinesContent( removeCodeBlockStartSyntax(accumulatedContent) @@ -131,7 +156,7 @@ export class TaskManager { removeCodeBlockEndSyntax(task.replacementContent) ) - this.updateTaskState(task, InlineDiffTaskState.Applying) + this.updateTaskState(task, InlineDiffTaskState.Reviewing) await this.diffProcessor.computeDiff(task) const editor = await this.diffProcessor.getEditor(task) const blocksWithRange = @@ -143,8 +168,6 @@ export class TaskManager { } catch (error) { this.handleTaskError(task, error) yield task - } finally { - task.abortController?.abort() } } @@ -171,27 +194,18 @@ export class TaskManager { updateTaskState(task: InlineDiffTask, state: InlineDiffTaskState) { task.state = state - this.taskStateChangeEmitter.fire(task) } private handleTaskError(task: InlineDiffTask, error: unknown) { task.state = InlineDiffTaskState.Error task.error = error instanceof Error ? error : new Error(String(error)) - this.taskStateChangeEmitter.fire(task) logger.error('Error in task', error) } async resetAndCleanHistory(taskId: string) { - const task = this.getTask(taskId) + const task = this.resetTask(taskId) if (!task) return - task.abortController?.abort() - task.history.clear() - task.waitForReviewDiffBlockIds = [] - task.diffBlocks = [] - task.replacementContent = '' - - this.updateTaskState(task, InlineDiffTaskState.Idle) const blocksWithRange = await this.diffProcessor.getDiffBlocksWithDisplayRange(task) await this.applyToDocumentAndRefresh(task, blocksWithRange) @@ -272,7 +286,7 @@ export class TaskManager { await this.applyToDocumentAndRefresh(task, blocksWithRange) if (task.waitForReviewDiffBlockIds.length === 0) { - this.updateTaskState(task, InlineDiffTaskState.Finished) + this.updateTaskState(task, InlineDiffTaskState.Accepted) const editor = await this.diffProcessor.getEditor(task) await editor.document.save() } @@ -379,7 +393,7 @@ export class TaskManager { const blocksWithRange = await this.diffProcessor.getDiffBlocksWithDisplayRange(task) await this.applyToDocumentAndRefresh(task, blocksWithRange) - await this.updateDecorationsAndCodeLenses(task, blocksWithRange) + // await this.updateDecorationsAndCodeLenses(task, blocksWithRange) if (task.waitForReviewDiffBlockIds.length === 0) { const allAccepted = task.history @@ -389,14 +403,14 @@ export class TaskManager { this.updateTaskState( task, allAccepted - ? InlineDiffTaskState.Finished + ? InlineDiffTaskState.Accepted : InlineDiffTaskState.Rejected ) } else if ( - task.state === InlineDiffTaskState.Finished || + task.state === InlineDiffTaskState.Accepted || task.state === InlineDiffTaskState.Rejected ) { - this.updateTaskState(task, InlineDiffTaskState.Applying) + this.updateTaskState(task, InlineDiffTaskState.Reviewing) } } catch (error) { logger.error('Error handling undo/redo', error) @@ -404,7 +418,6 @@ export class TaskManager { } dispose() { - this.taskStateChangeEmitter.dispose() this.tasks.clear() this.diffProcessor.dispose() this.decorationManager.dispose() diff --git a/src/extension/registers/inline-diff-register/types.ts b/src/extension/registers/inline-diff-register/types.ts index b9ec811..e8200e6 100644 --- a/src/extension/registers/inline-diff-register/types.ts +++ b/src/extension/registers/inline-diff-register/types.ts @@ -4,11 +4,11 @@ import type { HistoryManager } from './history-manager' export enum InlineDiffTaskState { Idle = 'Idle', - Applying = 'Applying', + Generating = 'Generating', + Reviewing = 'Reviewing', + Accepted = 'Accepted', Rejected = 'Rejected', - Finished = 'Finished', - Error = 'Error', - Pending = 'Pending' + Error = 'Error' } export interface DiffBlock { @@ -50,5 +50,6 @@ export interface InlineDiffTask { error?: Error lastKnownDocumentVersion: number waitForReviewDiffBlockIds: string[] + originalWaitForReviewDiffBlockIdCount: number history: HistoryManager } diff --git a/src/extension/registers/server-plugin-register.ts b/src/extension/registers/server-plugin-register.ts index ace1441..5592a94 100644 --- a/src/extension/registers/server-plugin-register.ts +++ b/src/extension/registers/server-plugin-register.ts @@ -1,13 +1,17 @@ import type { CommandManager } from '@extension/commands/command-manager' -import { createServerPlugins } from '@shared/plugins/base/server/create-server-plugins' -import { ServerPluginRegistry } from '@shared/plugins/base/server/server-plugin-registry' +import { AgentServerPluginRegistry } from '@shared/plugins/agents/_base/server/agent-server-plugin-registry' +import { createAgentServerPlugins } from '@shared/plugins/agents/_base/server/agent-server-plugins' +import { MentionServerPluginRegistry } from '@shared/plugins/mentions/_base/server/mention-server-plugin-registry' +import { createMentionServerPlugins } from '@shared/plugins/mentions/_base/server/mention-server-plugins' import * as vscode from 'vscode' import { BaseRegister } from './base-register' import type { RegisterManager } from './register-manager' export class ServerPluginRegister extends BaseRegister { - serverPluginRegistry!: ServerPluginRegistry + mentionServerPluginRegistry!: MentionServerPluginRegistry + + agentServerPluginRegistry!: AgentServerPluginRegistry constructor( protected context: vscode.ExtensionContext, @@ -18,17 +22,25 @@ export class ServerPluginRegister extends BaseRegister { } async register(): Promise { - const serverPluginRegistry = new ServerPluginRegistry() - const plugins = createServerPlugins() + const mentionServerPluginRegistry = new MentionServerPluginRegistry() + const mentionPlugins = createMentionServerPlugins() + const agentServerPluginRegistry = new AgentServerPluginRegistry() + const agentPlugins = createAgentServerPlugins() - await Promise.allSettled( - plugins.map(plugin => serverPluginRegistry.loadPlugin(plugin)) - ) + await Promise.allSettled([ + ...mentionPlugins.map(plugin => + mentionServerPluginRegistry.loadPlugin(plugin) + ), + ...agentPlugins.map(plugin => + agentServerPluginRegistry.loadPlugin(plugin) + ) + ]) - this.serverPluginRegistry = serverPluginRegistry + this.mentionServerPluginRegistry = mentionServerPluginRegistry + this.agentServerPluginRegistry = agentServerPluginRegistry } async dispose(): Promise { - await this.serverPluginRegistry.unloadAllPlugins() + await this.mentionServerPluginRegistry.unloadAllPlugins() } } diff --git a/src/extension/utils.ts b/src/extension/utils.ts index f651aa9..f8a8b73 100644 --- a/src/extension/utils.ts +++ b/src/extension/utils.ts @@ -187,3 +187,45 @@ export const showQuickPickWithCustomInput = async ( quickPick.show() }) } + +export type VSCodeRangeJson = [ + { + line: number + character: number + }, + { + line: number + character: number + } +] + +export const convertRangeJsonToVSCodeRange = ( + range: VSCodeRangeJson | vscode.Range +): vscode.Range => { + if (Array.isArray(range)) { + return new vscode.Range( + range[0].line, + range[0].character, + range[1].line, + range[1].character + ) + } + return range +} + +export type VSCodeUriJson = { + $mid: number + external: string + path: string + scheme: string +} + +export const convertUriJsonToVSCodeUri = ( + uri: VSCodeUriJson | vscode.Uri +): vscode.Uri => { + if (uri instanceof vscode.Uri) return uri + return vscode.Uri.from({ + scheme: uri.scheme, + path: uri.path + }) +} diff --git a/src/shared/entities/ai-model-entity.ts b/src/shared/entities/ai-model-entity.ts index 908017e..dd1d3c5 100644 --- a/src/shared/entities/ai-model-entity.ts +++ b/src/shared/entities/ai-model-entity.ts @@ -16,7 +16,7 @@ export interface AIModel extends IBaseEntity { } export class AIModelEntity extends BaseEntity { - protected getDefaults(data?: Partial): AIModel { + protected getDefaults(override?: Partial): AIModel { return { id: uuidv4(), providerOrBaseUrl: AIProviderType.OpenAI, @@ -27,7 +27,7 @@ export class AIModelEntity extends BaseEntity { audioInputSupport: 'unknown', audioOutputSupport: 'unknown', toolsCallSupport: 'unknown', - ...data + ...override } } } diff --git a/src/shared/entities/ai-provider-entity/base.ts b/src/shared/entities/ai-provider-entity/base.ts index 37be192..e5ebb7d 100644 --- a/src/shared/entities/ai-provider-entity/base.ts +++ b/src/shared/entities/ai-provider-entity/base.ts @@ -39,8 +39,8 @@ export abstract class AIProviderEntity< abstract type: AIProviderType abstract getProviderConfig(): AIProviderConfig - protected getDefaults(data?: Partial): T { - const type = data?.type ?? this.type + protected getDefaults(override?: Partial): T { + const type = override?.type ?? this.type return { id: uuidv4(), name: '', @@ -50,7 +50,7 @@ export abstract class AIProviderEntity< allowRealTimeModels: true, realTimeModels: [], manualModels: [], - ...data + ...override } as unknown as T } diff --git a/src/shared/entities/base-entity.ts b/src/shared/entities/base-entity.ts index 889250a..51a4938 100644 --- a/src/shared/entities/base-entity.ts +++ b/src/shared/entities/base-entity.ts @@ -8,9 +8,9 @@ export interface IBaseEntity { export abstract class BaseEntity { entity: T - constructor(data?: Partial) { - this.entity = { ...this.getDefaults(data || {}) } + constructor(override?: Partial) { + this.entity = { ...this.getDefaults(override || {}) } } - protected abstract getDefaults(data?: Partial): T + protected abstract getDefaults(override?: Partial): T } diff --git a/src/shared/entities/chat-context-entity.ts b/src/shared/entities/chat-context-entity.ts index c3b32a6..98aedc3 100644 --- a/src/shared/entities/chat-context-entity.ts +++ b/src/shared/entities/chat-context-entity.ts @@ -13,7 +13,7 @@ export interface ChatContext extends IBaseEntity { } export class ChatContextEntity extends BaseEntity { - getDefaults(data?: Partial): ChatContext { + getDefaults(override?: Partial): ChatContext { const now = Date.now() return { id: uuidv4(), @@ -24,7 +24,7 @@ export class ChatContextEntity extends BaseEntity { settings: { explicitContext: '总是用中文回复' }, - ...data + ...override } } diff --git a/src/shared/entities/chat-session-entity.ts b/src/shared/entities/chat-session-entity.ts index fa9fe48..fb46387 100644 --- a/src/shared/entities/chat-session-entity.ts +++ b/src/shared/entities/chat-session-entity.ts @@ -11,7 +11,7 @@ export interface ChatSession extends IBaseEntity { } export class ChatSessionEntity extends BaseEntity { - protected getDefaults(data?: Partial): ChatSession { + protected getDefaults(override?: Partial): ChatSession { const now = Date.now() return { id: uuidv4(), @@ -19,7 +19,7 @@ export class ChatSessionEntity extends BaseEntity { createdAt: now, updatedAt: now, title: 'New Chat', - ...data + ...override } } } diff --git a/src/shared/entities/conversation-entity.ts b/src/shared/entities/conversation-entity.ts index dced2bf..6a81eba 100644 --- a/src/shared/entities/conversation-entity.ts +++ b/src/shared/entities/conversation-entity.ts @@ -11,7 +11,6 @@ import type { } from '@langchain/core/messages' import type { RunnableToolLike } from '@langchain/core/runnables' import type { StructuredToolInterface } from '@langchain/core/tools' -import type { PluginState } from '@shared/plugins/base/types' import { v4 as uuidv4 } from 'uuid' import { BaseEntity, type IBaseEntity } from './base-entity' @@ -30,32 +29,30 @@ export interface ConversationState { export interface Conversation extends IBaseEntity { createdAt: number role: MessageType - contents: LangchainMessageContents + contents: ConversationContents richText?: string // JSON stringified - pluginStates: Record mentions: Mention[] - agents: Agent[] - logs: ConversationLog[] + thinkAgents: Agent[] // tools calls + actions: ConversationAction[] state: ConversationState } export class ConversationEntity extends BaseEntity { - protected getDefaults(data?: Partial): Conversation { + protected getDefaults(override?: Partial): Conversation { return { id: uuidv4(), createdAt: Date.now(), role: 'human', contents: [], - pluginStates: {}, mentions: [], - agents: [], - logs: [], + thinkAgents: [], + actions: [], state: { selectedFilesFromFileSelector: [], currentFilesFromVSCode: [], selectedImagesFromOutsideUrl: [] }, - ...data + ...override } } } @@ -67,17 +64,19 @@ export interface Mention { export interface Agent { id: string - name: string + name: string // also is agent plugin id input: Input output: Output } -export type ConversationLog = { +export interface ConversationAction< + State extends Record = Record, + AgentType extends Agent = Agent +> { id: string - createdAt: number - title: string - content?: string - agentId?: string + state: State + weight: number + agent?: AgentType } export type LangchainMessage = @@ -88,18 +87,28 @@ export type LangchainMessage = | FunctionMessage | ToolMessage -export type LangchainMessageContents = ( - | { - type: 'text' - text: string - } - | { - type: 'image_url' - image_url: { - url: string - detail?: ImageDetail - } - } +export type ConversationTextContent = { + type: 'text' + text: string +} + +export type ConversationImageContent = { + type: 'image_url' + image_url: { + url: string + detail?: ImageDetail + } +} + +export type ConversationActionContent = { + type: 'action' + actionId: string +} + +export type ConversationContents = ( + | ConversationTextContent + | ConversationImageContent + | ConversationActionContent )[] export type LangchainTool = StructuredToolInterface | RunnableToolLike diff --git a/src/shared/entities/doc-site-entity.ts b/src/shared/entities/doc-site-entity.ts index d805ed1..7bc5d00 100644 --- a/src/shared/entities/doc-site-entity.ts +++ b/src/shared/entities/doc-site-entity.ts @@ -10,14 +10,14 @@ export interface DocSite extends IBaseEntity { } export class DocSiteEntity extends BaseEntity { - protected getDefaults(data?: Partial): DocSite { + protected getDefaults(override?: Partial): DocSite { return { id: uuidv4(), name: '', url: '', isCrawled: false, isIndexed: false, - ...data + ...override } } } diff --git a/src/shared/entities/prompt-snippet-entity.ts b/src/shared/entities/prompt-snippet-entity.ts index 3ec192e..fdf0ef0 100644 --- a/src/shared/entities/prompt-snippet-entity.ts +++ b/src/shared/entities/prompt-snippet-entity.ts @@ -3,8 +3,8 @@ import { v4 as uuidv4 } from 'uuid' import { BaseEntity, type IBaseEntity } from './base-entity' import { ConversationEntity, + type ConversationContents, type ConversationState, - type LangchainMessageContents, type Mention } from './conversation-entity' @@ -12,14 +12,14 @@ export interface PromptSnippet extends IBaseEntity { title: string createdAt: number updatedAt: number - contents: LangchainMessageContents + contents: ConversationContents richText?: string // JSON stringified mentions: Mention[] state: ConversationState } export class PromptSnippetEntity extends BaseEntity { - protected getDefaults(data?: Partial): PromptSnippet { + protected getDefaults(override?: Partial): PromptSnippet { const conversationEntity = new ConversationEntity().entity const { contents, mentions, state } = conversationEntity const now = Date.now() @@ -32,7 +32,7 @@ export class PromptSnippetEntity extends BaseEntity { contents, mentions, state, - ...data + ...override } } } diff --git a/src/shared/entities/setting-entity/entity.ts b/src/shared/entities/setting-entity/entity.ts index eaa9e43..e5977fb 100644 --- a/src/shared/entities/setting-entity/entity.ts +++ b/src/shared/entities/setting-entity/entity.ts @@ -10,13 +10,13 @@ export interface Settings extends IBaseEntity { } export class SettingsEntity extends BaseEntity { - protected getDefaults(data?: Partial): Settings { + protected getDefaults(override?: Partial): Settings { return { id: uuidv4(), key: 'unknown' as SettingKey, value: 'unknown', updatedAt: Date.now(), - ...data + ...override } } } diff --git a/src/shared/plugins/base/deep-merge-providers.ts b/src/shared/plugins/_shared/deep-merge-providers.ts similarity index 100% rename from src/shared/plugins/base/deep-merge-providers.ts rename to src/shared/plugins/_shared/deep-merge-providers.ts diff --git a/src/shared/plugins/fs-plugin/server/merge-code-snippets.ts b/src/shared/plugins/_shared/merge-code-snippets.ts similarity index 96% rename from src/shared/plugins/fs-plugin/server/merge-code-snippets.ts rename to src/shared/plugins/_shared/merge-code-snippets.ts index 09045e0..15af367 100644 --- a/src/shared/plugins/fs-plugin/server/merge-code-snippets.ts +++ b/src/shared/plugins/_shared/merge-code-snippets.ts @@ -1,5 +1,5 @@ import { VsCodeFS } from '@extension/file-utils/vscode-fs' -import type { CodeSnippet } from '@shared/plugins/fs-plugin/types' +import type { CodeSnippet } from '@shared/plugins/agents/codebase-search-agent-plugin/types' export type MergeCodeSnippetsMode = 'default' | 'expanded' diff --git a/src/shared/plugins/_shared/provider-manager.ts b/src/shared/plugins/_shared/provider-manager.ts new file mode 100644 index 0000000..14cbd91 --- /dev/null +++ b/src/shared/plugins/_shared/provider-manager.ts @@ -0,0 +1,49 @@ +import { deepMergeProviders } from './deep-merge-providers' + +export class ProviderUtils { + static getValues = (idProvidersMap: Record T>): T[] => + Object.values(idProvidersMap).map(provider => provider?.()) + + static getValuesMap = ( + idProvidersMap: Record T> + ): Record => + Object.fromEntries( + Object.entries(idProvidersMap).map(([id, provider]) => [id, provider?.()]) + ) + + static mergeAll = ( + idProvidersMap: Record T> + ): T | undefined => { + const allValues = ProviderUtils.getValues(idProvidersMap) + return deepMergeProviders(allValues) + } +} + +export class ProviderManager { + protected idProvidersMap = {} as Record T> + + register(pluginId: Id, provider: () => T): void { + this.idProvidersMap[pluginId] = provider + } + + unregister(pluginId: Id): void { + delete this.idProvidersMap[pluginId] + } + + getValues(): T[] { + return ProviderUtils.getValues(this.idProvidersMap) + } + + getIdProviderMap(): Record { + return Object.fromEntries( + Object.entries<() => T>(this.idProvidersMap).map(([id, provider]) => [ + id, + provider?.() + ]) + ) as Record + } + + mergeAll(): T | undefined { + return ProviderUtils.mergeAll(this.idProvidersMap) + } +} diff --git a/src/shared/plugins/base/strategies.ts b/src/shared/plugins/_shared/strategies.ts similarity index 100% rename from src/shared/plugins/base/strategies.ts rename to src/shared/plugins/_shared/strategies.ts diff --git a/src/shared/plugins/agents/_base/client/agent-client-plugin-context.tsx b/src/shared/plugins/agents/_base/client/agent-client-plugin-context.tsx new file mode 100644 index 0000000..bba7f0d --- /dev/null +++ b/src/shared/plugins/agents/_base/client/agent-client-plugin-context.tsx @@ -0,0 +1,101 @@ +import React, { createContext, useContext, useRef } from 'react' +import { ProviderUtils } from '@shared/plugins/_shared/provider-manager' + +import type { AgentPluginId } from '../types' +import type { AgentClientPluginProviderMap } from './agent-client-plugin-types' + +type ProviderKey = keyof AgentClientPluginProviderMap + +type IdProviderMap = Record< + AgentPluginId, + () => AgentClientPluginProviderMap[ProviderKey] +> + +export interface AgentClientPluginContextValue { + registerProvider: ( + pluginId: AgentPluginId, + providerKey: K, + provider: () => AgentClientPluginProviderMap[K] + ) => void + getProviders: ( + providerKey: K + ) => AgentClientPluginProviderMap[K][] + getIdProviderMap: ( + providerKey: K + ) => Record + mergeProviders: ( + providerKey: K + ) => AgentClientPluginProviderMap[K] | undefined +} + +const AgentClientPluginContext = + createContext(null) + +export const AgentClientPluginProvider: React.FC = ({ + children +}) => { + const providerKeyInfoMapRef = useRef({} as Record) + + const registerProvider = ( + pluginId: AgentPluginId, + providerKey: K, + provider: () => AgentClientPluginProviderMap[K] + ) => { + if (!providerKeyInfoMapRef.current[providerKey]) { + providerKeyInfoMapRef.current[providerKey] = {} as IdProviderMap + } + providerKeyInfoMapRef.current[providerKey]![pluginId] = provider + } + + const getProviders = ( + providerKey: K + ): AgentClientPluginProviderMap[K][] => { + const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || + {}) as Record AgentClientPluginProviderMap[K]> + + return ProviderUtils.getValues(idProviderMap) + } + + const getIdProviderMap = ( + providerKey: K + ): Record => { + const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || + {}) as Record AgentClientPluginProviderMap[K]> + + return ProviderUtils.getValuesMap(idProviderMap) + } + + const mergeProviders = ( + providerKey: K + ): AgentClientPluginProviderMap[K] | undefined => { + const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || + {}) as Record AgentClientPluginProviderMap[K]> + + const result = ProviderUtils.mergeAll(idProviderMap) + + return result + } + + return ( + + {children} + + ) +} + +export const useAgentPlugin = () => { + const context = useContext(AgentClientPluginContext) + if (!context) { + throw new Error( + 'useAgentPlugin must be used within AgentClientPluginProvider' + ) + } + return context +} diff --git a/src/shared/plugins/agents/_base/client/agent-client-plugin-types.ts b/src/shared/plugins/agents/_base/client/agent-client-plugin-types.ts new file mode 100644 index 0000000..6f30b7c --- /dev/null +++ b/src/shared/plugins/agents/_base/client/agent-client-plugin-types.ts @@ -0,0 +1,36 @@ +import type { Agent, Conversation, ConversationAction } from '@shared/entities' +import type { SFC } from '@shared/types/common' +import type { Updater } from 'use-immer' + +export type CustomRenderThinkItemProps = { + agent: AgentType +} + +export type CustomRenderMessageActionItemProps< + ActionType extends ConversationAction = ConversationAction +> = { + conversationAction: ActionType + setConversationAction: Updater + conversation: Conversation + setConversation: Updater +} + +export type CustomRenderFloatingActionItemProps< + ActionType extends ConversationAction = ConversationAction +> = CustomRenderMessageActionItemProps + +export type IsSameAction< + ActionType extends ConversationAction = ConversationAction +> = (actionA: ActionType, actionB: ActionType) => boolean + +export type IsCompletedAction< + ActionType extends ConversationAction = ConversationAction +> = (action: ActionType) => boolean + +export type AgentClientPluginProviderMap = { + CustomRenderThinkItem: SFC + CustomRenderMessageActionItem: SFC + CustomRenderFloatingActionItem: SFC + isSameAction: IsSameAction + isCompletedAction: IsCompletedAction +} diff --git a/src/shared/plugins/agents/_base/client/agent-client-plugins.ts b/src/shared/plugins/agents/_base/client/agent-client-plugins.ts new file mode 100644 index 0000000..b7f6012 --- /dev/null +++ b/src/shared/plugins/agents/_base/client/agent-client-plugins.ts @@ -0,0 +1,19 @@ +import { CodebaseSearchAgentClientPlugin } from '@shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-client-plugin' +import { EditFileAgentClientPlugin } from '@shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-client-plugin' +import { ReadFilesAgentClientPlugin } from '@shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-client-plugin' +import { WebVisitAgentClientPlugin } from '@shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-client-plugin' + +import { WebSearchAgentClientPlugin } from '../../web-search-agent-plugin/client/web-search-agent-client-plugin' +import type { AgentClientPlugin } from './create-agent-client-plugin' + +export const createAgentClientPlugins = (): AgentClientPlugin[] => { + const plugins: AgentClientPlugin[] = [ + CodebaseSearchAgentClientPlugin, + ReadFilesAgentClientPlugin, + WebVisitAgentClientPlugin, + WebSearchAgentClientPlugin, + EditFileAgentClientPlugin + ] + + return plugins +} diff --git a/src/shared/plugins/agents/_base/client/create-agent-client-plugin.ts b/src/shared/plugins/agents/_base/client/create-agent-client-plugin.ts new file mode 100644 index 0000000..c99aff7 --- /dev/null +++ b/src/shared/plugins/agents/_base/client/create-agent-client-plugin.ts @@ -0,0 +1,37 @@ +import { AgentPluginId } from '../types' +import { useAgentPlugin } from './agent-client-plugin-context' +import type { AgentClientPluginProviderMap } from './agent-client-plugin-types' + +export interface AgentClientPlugin { + id: AgentPluginId + version: string + usePlugin: () => void +} + +export type AgentClientPluginSetupProps = { + registerProvider: ( + providerKey: K, + provider: () => AgentClientPluginProviderMap[K] + ) => void +} + +export const createAgentClientPlugin = (options: { + id: AgentPluginId + version: string + setup: (context: AgentClientPluginSetupProps) => void +}): AgentClientPlugin => ({ + id: options.id, + version: options.version, + usePlugin() { + const { registerProvider } = useAgentPlugin() + + options.setup({ + registerProvider: (key, provider) => + registerProvider( + options.id, + key as keyof AgentClientPluginProviderMap, + provider as () => AgentClientPluginProviderMap[keyof AgentClientPluginProviderMap] + ) + }) + } +}) diff --git a/src/shared/plugins/agents/_base/server/agent-server-plugin-context.ts b/src/shared/plugins/agents/_base/server/agent-server-plugin-context.ts new file mode 100644 index 0000000..7d4ecdb --- /dev/null +++ b/src/shared/plugins/agents/_base/server/agent-server-plugin-context.ts @@ -0,0 +1,48 @@ +import type { AgentPluginId } from '../types' +import type { AgentServerPluginRegistry } from './agent-server-plugin-registry' +import type { createAgentProviderManagers } from './create-agent-provider-manager' + +export interface AgentServerPlugin { + id: AgentPluginId + version: string + dependencies?: AgentPluginId[] + activate(context: AgentServerPluginContext): Promise + deactivate?(): void +} + +interface AgentServerPluginContextOptions { + pluginId: AgentPluginId + registry: AgentServerPluginRegistry +} + +// eslint-disable-next-line unused-imports/no-unused-vars +export class AgentServerPluginContext { + private pluginId: AgentPluginId + + private registry: AgentServerPluginRegistry + + constructor(options: AgentServerPluginContextOptions) { + const { pluginId, registry } = options + this.pluginId = pluginId + this.registry = registry + } + + registerCommand(command: string, callback: (...args: any[]) => void): void { + this.registry.registerCommand(command, callback) + } + + executeCommand(command: string, ...args: any[]): void { + this.registry.executeCommand(command, ...args) + } + + registerProvider< + K extends keyof AgentServerPluginRegistry['providerManagers'] + >( + key: K, + provider: Parameters< + ReturnType[K]['register'] + >[1] + ): void { + this.registry.providerManagers[key].register(this.pluginId, provider as any) + } +} diff --git a/src/shared/plugins/base/server/server-plugin-registry.ts b/src/shared/plugins/agents/_base/server/agent-server-plugin-registry.ts similarity index 60% rename from src/shared/plugins/base/server/server-plugin-registry.ts rename to src/shared/plugins/agents/_base/server/agent-server-plugin-registry.ts index c7b0e6d..4463610 100644 --- a/src/shared/plugins/base/server/server-plugin-registry.ts +++ b/src/shared/plugins/agents/_base/server/agent-server-plugin-registry.ts @@ -1,37 +1,41 @@ import { logger } from '@extension/logger' -import type { PluginId } from '../types' -import { createProviderManagers } from './create-provider-manager' -import { ServerPluginContext, type ServerPlugin } from './server-plugin-context' +import type { AgentPluginId } from '../types' +import { + AgentServerPluginContext, + type AgentServerPlugin +} from './agent-server-plugin-context' +import { createAgentProviderManagers } from './create-agent-provider-manager' -export class ServerPluginRegistry { - private plugins: Map = new Map() +export class AgentServerPluginRegistry { + private plugins: Map = new Map() - private pluginContexts: Map = new Map() + private pluginContexts: Map = + new Map() private commands: Map void> = new Map() - providerManagers = createProviderManagers() + providerManagers = createAgentProviderManagers() - private checkDependencies(plugin: ServerPlugin): boolean { + private checkDependencies(plugin: AgentServerPlugin): boolean { return ( !plugin.dependencies || plugin.dependencies.every(depId => this.plugins.has(depId)) ) } - async loadPlugin(_plugin: ServerPlugin): Promise { - let currentPluginId: PluginId | null = null + async loadPlugin(_plugin: AgentServerPlugin): Promise { + let currentPluginId: AgentPluginId | null = null try { - const plugin = _plugin as ServerPlugin + const plugin = _plugin as AgentServerPlugin currentPluginId = plugin.id if (!this.checkDependencies(plugin)) throw new Error(`Dependencies not met for plugin ${currentPluginId}`) this.plugins.set(currentPluginId, plugin) - const context = new ServerPluginContext({ + const context = new AgentServerPluginContext({ registry: this, pluginId: currentPluginId }) @@ -44,7 +48,7 @@ export class ServerPluginRegistry { } } - private handleError(error: Error, pluginId: PluginId | null): void { + private handleError(error: Error, pluginId: AgentPluginId | null): void { logger.error(`Error in plugin ${pluginId}:`, error) } @@ -57,11 +61,13 @@ export class ServerPluginRegistry { if (callback) callback(...args) } - getPlugin(pluginId: PluginId): T | undefined { + getPlugin( + pluginId: AgentPluginId + ): T | undefined { return this.plugins.get(pluginId) as T } - async unloadPlugin(pluginId: PluginId): Promise { + async unloadPlugin(pluginId: AgentPluginId): Promise { const plugin = this.plugins.get(pluginId) if (plugin?.deactivate) { await plugin.deactivate() diff --git a/src/shared/plugins/agents/_base/server/agent-server-plugins.ts b/src/shared/plugins/agents/_base/server/agent-server-plugins.ts new file mode 100644 index 0000000..b18eb12 --- /dev/null +++ b/src/shared/plugins/agents/_base/server/agent-server-plugins.ts @@ -0,0 +1,19 @@ +import { CodebaseSearchAgentServerPlugin } from '@shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-plugin' +import { EditFileAgentServerPlugin } from '@shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-plugin' +import { ReadFilesAgentServerPlugin } from '@shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-plugin' +import { WebVisitAgentServerPlugin } from '@shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-plugin' + +import { WebSearchAgentServerPlugin } from '../../web-search-agent-plugin/server/web-search-agent-server-plugin' +import type { AgentServerPlugin } from './agent-server-plugin-context' + +export const createAgentServerPlugins = (): AgentServerPlugin[] => { + const plugins: AgentServerPlugin[] = [ + new CodebaseSearchAgentServerPlugin(), + new ReadFilesAgentServerPlugin(), + new WebVisitAgentServerPlugin(), + new WebSearchAgentServerPlugin(), + new EditFileAgentServerPlugin() + ] + + return plugins +} diff --git a/src/shared/plugins/agents/_base/server/create-agent-provider-manager.ts b/src/shared/plugins/agents/_base/server/create-agent-provider-manager.ts new file mode 100644 index 0000000..b5ee449 --- /dev/null +++ b/src/shared/plugins/agents/_base/server/create-agent-provider-manager.ts @@ -0,0 +1,50 @@ +import type { BaseAgent } from '@extension/chat/strategies/base' +import type { ActionContext } from '@shared/actions/types' +import type { + ChatContext, + Conversation, + ConversationAction +} from '@shared/entities' +import { ProviderManager } from '@shared/plugins/_shared/provider-manager' + +import type { AgentPluginId } from '../types' + +export interface AgentServerUtilsProvider< + AgentType extends BaseAgent = BaseAgent, + ActionType extends ConversationAction = ConversationAction +> { + getAgentClass: () => new (...args: any[]) => AgentType + onStartAction?: ( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ActionType + }> + ) => Promise + onRestartAction?: ( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ActionType + }> + ) => Promise + onAcceptAction?: ( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ActionType + }> + ) => Promise + onRejectAction?: ( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: ActionType + }> + ) => Promise +} + +export const createAgentProviderManagers = () => + ({ + serverUtils: new ProviderManager() + }) as const satisfies Record> diff --git a/src/shared/plugins/agents/_base/types.ts b/src/shared/plugins/agents/_base/types.ts new file mode 100644 index 0000000..ccc1d04 --- /dev/null +++ b/src/shared/plugins/agents/_base/types.ts @@ -0,0 +1,16 @@ +export enum AgentPluginId { + CodebaseSearch = 'codebaseSearch', + DocRetriever = 'docRetriever', + FsVisit = 'fsVisit', + WebSearch = 'webSearch', + WebVisit = 'webVisit', + ReadFiles = 'readFiles', + RunTerminalCmd = 'runTerminalCmd', + ListDir = 'listDir', + GrepSearch = 'grepSearch', + EditFile = 'editFile', + FileSearch = 'fileSearch', + DeleteFiles = 'deleteFiles', + Reapply = 'reapply', + ParallelApply = 'parallelApply' +} diff --git a/src/shared/plugins/agents/_for-migrate/agent-names.ts b/src/shared/plugins/agents/_for-migrate/agent-names.ts new file mode 100644 index 0000000..c1a8413 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/agent-names.ts @@ -0,0 +1,14 @@ +export const codebaseSearchAgentName = 'codebaseSearch' +export const docRetrieverAgentName = 'docRetriever' +export const fsVisitAgentName = 'fsVisit' +export const webSearchAgentName = 'webSearch' +export const webVisitAgentName = 'webVisit' +export const readFilesAgentName = 'readFiles' +export const runTerminalCmdAgentName = 'runTerminalCmd' +export const listDirAgentName = 'listDir' +export const grepSearchAgentName = 'grepSearch' +export const editFileAgentName = 'editFile' +export const fileSearchAgentName = 'fileSearch' +export const deleteFilesAgentName = 'deleteFiles' +export const reapplyAgentName = 'reapply' +export const parallelApplyAgentName = 'parallelApply' diff --git a/src/shared/plugins/agents/_for-migrate/codebase-search-agent.ts b/src/shared/plugins/agents/_for-migrate/codebase-search-agent.ts new file mode 100644 index 0000000..010c1bd --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/codebase-search-agent.ts @@ -0,0 +1,95 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { CodebaseWatcherRegister } from '@extension/registers/codebase-watcher-register' +import { mergeCodeSnippets } from '@shared/plugins/_shared/merge-code-snippets' +import type { CodeSnippet } from '@shared/plugins/agents/codebase-search-agent-plugin/types' +import { z } from 'zod' + +import { codebaseSearchAgentName } from './agent-names' + +export class CodebaseSearchAgent extends BaseAgent< + BaseGraphState, + { enableCodebaseAgent: boolean } +> { + static name = codebaseSearchAgentName + + name = CodebaseSearchAgent.name + + logTitle = 'Search Codebase' + + description = `Find snippets of code from the codebase most relevant to the search query. +This is a semantic search tool, so the query should ask for something semantically matching what is needed. +If it makes sense to only search in particular directories, please specify them in the targetDirectories field. +Unless there is a clear reason to use your own search query, please just reuse the user's exact query with their wording. +Their exact wording/phrasing can often be helpful for the semantic search query. Keeping the same exact question format can also be helpful.` + + inputSchema = z.object({ + query: z + .string() + .describe( + "The search query to find relevant code. You should reuse the user's exact query/most recent message with their wording unless there is a clear reason not to." + ), + targetDirectories: z + .array(z.string()) + .optional() + .describe('Glob patterns for directories to search over'), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + codeSnippets: z.array( + z.object({ + fileHash: z.string(), + relativePath: z.string(), + fullPath: z.string(), + startLine: z.number(), + startCharacter: z.number(), + endLine: z.number(), + endCharacter: z.number(), + code: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const { enableCodebaseAgent } = this.context.createToolOptions + + if (!enableCodebaseAgent) { + return { codeSnippets: [] } + } + + const indexer = this.context.strategyOptions.registerManager.getRegister( + CodebaseWatcherRegister + )?.indexer + + if (!indexer) { + return { codeSnippets: [] } + } + + const searchResults = await indexer.searchSimilarRow(input.query) + + // Filter results by target directories if specified + const filteredResults = input.targetDirectories + ? searchResults.filter(row => + input.targetDirectories?.some(dir => row.relativePath.includes(dir)) + ) + : searchResults + + const searchCodeSnippets = filteredResults.slice(0, 8).map(row => { + // eslint-disable-next-line unused-imports/no-unused-vars + const { embedding, ...others } = row + return { ...others, code: '' } + }) + + const codeSnippets = await mergeCodeSnippets(searchCodeSnippets, { + mode: 'expanded' + }) + + return { codeSnippets } + } +} diff --git a/src/shared/plugins/agents/_for-migrate/delete-files-agent.ts b/src/shared/plugins/agents/_for-migrate/delete-files-agent.ts new file mode 100644 index 0000000..35456b2 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/delete-files-agent.ts @@ -0,0 +1,62 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { runAction } from '@extension/state' +import { settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import { deleteFilesAgentName } from './agent-names' + +export class DeleteFilesAgent extends BaseAgent { + static name = deleteFilesAgentName + + name = DeleteFilesAgent.name + + logTitle = 'Delete Files' + + description = `Deletes files at the specified paths. The operation will fail gracefully if: + - The file doesn't exist + - The operation is rejected for security reasons + - The file cannot be deleted` + + inputSchema = z.object({ + targetFilesRelativePaths: z + .array(z.string()) + .describe( + 'The paths of the files to delete, relative to the workspace root.' + ), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + success: z.boolean(), + error: z.string().optional() + }) + + async execute(input: z.infer) { + const results = await settledPromiseResults( + input.targetFilesRelativePaths.map(async targetFileRelativePath => { + const fullPath = await runAction( + this.context.strategyOptions.registerManager + ).server.file.getFullPath({ + actionParams: { + path: targetFileRelativePath, + returnNullIfNotExists: false + } + }) + // TODO: add task action to delete file + + return { + fullPath, + relativePath: targetFileRelativePath + } + }) + ) + + console.log('results', results) + } +} diff --git a/src/shared/plugins/agents/doc-retriever-agent.ts b/src/shared/plugins/agents/_for-migrate/doc-retriever-agent.ts similarity index 97% rename from src/shared/plugins/agents/doc-retriever-agent.ts rename to src/shared/plugins/agents/_for-migrate/doc-retriever-agent.ts index 9f38471..162c319 100644 --- a/src/shared/plugins/agents/doc-retriever-agent.ts +++ b/src/shared/plugins/agents/_for-migrate/doc-retriever-agent.ts @@ -4,10 +4,10 @@ import { DocCrawler } from '@extension/chat/utils/doc-crawler' import { DocIndexer } from '@extension/chat/vectordb/doc-indexer' import { aidePaths } from '@extension/file-utils/paths' import { docSitesDB } from '@extension/lowdb/doc-sites-db' +import type { DocInfo } from '@shared/plugins/agents/doc-retriever-agent-plugin/types' import { removeDuplicates, settledPromiseResults } from '@shared/utils/common' import { z } from 'zod' -import type { DocInfo } from '../doc-plugin/types' import { docRetrieverAgentName } from './agent-names' export class DocRetrieverAgent extends BaseAgent< diff --git a/src/shared/plugins/agents/_for-migrate/edit-file-agent.ts b/src/shared/plugins/agents/_for-migrate/edit-file-agent.ts new file mode 100644 index 0000000..54742ce --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/edit-file-agent.ts @@ -0,0 +1,75 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { runAction } from '@extension/state' +import { z } from 'zod' + +import { editFileAgentName } from './agent-names' + +export class EditFileAgent extends BaseAgent { + static name = editFileAgentName + + name = EditFileAgent.name + + logTitle = 'Edit File' + + description = `Use this tool to propose an edit to an existing file. +This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write. +When writing the edit, you should specify each edit in sequence, with the special comment \`// ... existing code ...\` to represent unchanged code in between edited lines. + +For example: +\`\`\` +// ... existing code ... +FIRST_EDIT +// ... existing code ... +SECOND_EDIT +// ... existing code ... +THIRD_EDIT +// ... existing code ... +\`\`\` + +You should bias towards repeating as few lines of the original file as possible to convey the change. +But, each edit should contain sufficient context of unchanged lines around the code you're editing to resolve ambiguity. +DO NOT omit spans of pre-existing code without using the \`// ... existing code ...\` comment to indicate its absence. +Make sure it is clear what the edit should be.` + + inputSchema = z.object({ + targetFilePath: z + .string() + .describe( + 'The target file path to modify. Always specify the target file as the first argument and use the relative path in the workspace of the file to edit' + ), + instructions: z + .string() + .describe( + 'A single sentence instruction describing what you are going to do for the sketched edit. This is used to assist the less intelligent model in applying the edit. Please use the first person to describe what you are going to do.' + ), + codeEdit: z + .string() + .describe( + "Specify ONLY the precise lines of code that you wish to edit. **NEVER specify or write out unchanged code**. Instead, represent all unchanged code using the comment of the language you're editing in - example: `// ... existing code ...`" + ), + blocking: z + .boolean() + .describe( + 'Whether this tool call should block the client from making further edits to the file until this call is complete. If true, the client will not be able to make further edits to the file until this call is complete.' + ) + }) + + outputSchema = z.object({ + success: z.boolean(), + error: z.string().optional() + }) + + async execute(input: z.infer) { + const fullPath = await runAction( + this.context.strategyOptions.registerManager + ).server.file.getFullPath({ + actionParams: { + path: input.targetFilePath + } + }) + console.log('fullPath', fullPath) + + // TODO: add task action to edit file + } +} diff --git a/src/shared/plugins/agents/_for-migrate/file-search-agent.ts b/src/shared/plugins/agents/_for-migrate/file-search-agent.ts new file mode 100644 index 0000000..39799a3 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/file-search-agent.ts @@ -0,0 +1,73 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { createShouldIgnore } from '@extension/file-utils/ignore-patterns' +import { + traverseFileOrFolders, + type FileInfo +} from '@extension/file-utils/traverse-fs' +import { getWorkspaceFolder } from '@extension/utils' +import { z } from 'zod' + +import { fileSearchAgentName } from './agent-names' + +export class FileSearchAgent extends BaseAgent { + static name = fileSearchAgentName + + name = FileSearchAgent.name + + logTitle = 'File Search' + + description = `Fast file search based on fuzzy matching against file path. Use if you know part of the file path but don't know where it's located exactly. Response will be capped to 10 results. Make your query more specific if need to filter results further.` + + inputSchema = z.object({ + query: z.string().describe('Fuzzy filename to search for'), + explanation: z + .string() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + files: z.array( + z.object({ + type: z.literal('file'), + relativePath: z.string(), + fullPath: z.string(), + content: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const workspaceFolder = getWorkspaceFolder() + const workspacePath = workspaceFolder.uri.fsPath + + // Create ignore function based on workspace settings + const shouldIgnore = await createShouldIgnore(workspacePath) + + // Convert query to regex pattern for fuzzy matching + const queryPattern = input.query + .split('') + .map(c => `[^/]*${c.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}`) + .join('') + const regex = new RegExp(queryPattern, 'i') + + const items = await traverseFileOrFolders({ + type: 'file', + filesOrFolders: ['./'], + isGetFileContent: false, + workspacePath, + customShouldIgnore: filePath => { + if (shouldIgnore(filePath)) return true + return !regex.test(filePath) + }, + itemCallback: item => item + }) + + // Return top 10 results + return { + files: items.slice(0, 10) + } + } +} diff --git a/src/shared/plugins/agents/fs-visit-agent.ts b/src/shared/plugins/agents/_for-migrate/fs-visit-agent.ts similarity index 100% rename from src/shared/plugins/agents/fs-visit-agent.ts rename to src/shared/plugins/agents/_for-migrate/fs-visit-agent.ts diff --git a/src/shared/plugins/agents/_for-migrate/grep-search-agent.ts b/src/shared/plugins/agents/_for-migrate/grep-search-agent.ts new file mode 100644 index 0000000..7f3c4f4 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/grep-search-agent.ts @@ -0,0 +1,116 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { createShouldIgnore } from '@extension/file-utils/ignore-patterns' +import { VsCodeFS } from '@extension/file-utils/vscode-fs' +import { getWorkspaceFolder } from '@extension/utils' +import { glob } from 'glob' +import { z } from 'zod' + +import { grepSearchAgentName } from './agent-names' + +export class GrepSearchAgent extends BaseAgent { + static name = grepSearchAgentName + + name = GrepSearchAgent.name + + logTitle = 'Grep Search' + + description = `Fast text-based regex search that finds exact pattern matches within files or directories, utilizing the ripgrep command for efficient searching. +Results will be formatted in the style of ripgrep and can include line numbers and content. +To avoid overwhelming output, the results are capped at 50 matches. +Use the include or exclude patterns to filter the search scope by file type or specific paths. + +This is best for finding exact text matches or regex patterns. +More precise than semantic search for finding specific strings or patterns. +This is preferred over semantic search when we know the exact symbol/function name/etc. to search in some set of directories/file types.` + + inputSchema = z.object({ + query: z.string().describe('The regex pattern to search for'), + caseSensitive: z + .boolean() + .optional() + .describe('Whether the search should be case sensitive'), + includePattern: z + .string() + .optional() + .describe( + 'Glob pattern for files to include (e.g. "*.ts" for TypeScript files)' + ), + excludePattern: z + .string() + .optional() + .describe('Glob pattern for files to exclude'), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + matches: z.array( + z.object({ + relativePath: z.string(), + lineNumber: z.number(), + content: z.string() + }) + ) + }) + + async execute(input: z.infer) { + const workspaceFolder = getWorkspaceFolder() + const workspacePath = workspaceFolder.uri.fsPath + + // Create ignore function based on workspace settings + const shouldIgnore = await createShouldIgnore(workspacePath) + + // Get all files based on include/exclude patterns + const files = await glob(input.includePattern || '**/*', { + cwd: workspacePath, + nodir: true, + absolute: true, + follow: false, + dot: true, + ignore: { + ignored: p => { + if (input.excludePattern) { + const mm = new RegExp(input.excludePattern) + if (mm.test(p.relative())) return true + } + return shouldIgnore(p.fullpath()) + } + } + }) + + const matches: Array<{ + relativePath: string + lineNumber: number + content: string + }> = [] + + // Search through each file + for (const file of files) { + const content = await VsCodeFS.readFileOrOpenDocumentContent(file) + const lines = content.split('\n') + const regex = new RegExp(input.query, input.caseSensitive ? 'g' : 'gi') + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]! + if (regex.test(line)) { + matches.push({ + relativePath: file.slice(workspacePath.length + 1), + lineNumber: i + 1, + content: line.trim() + }) + + if (matches.length >= 50) break + } + } + + if (matches.length >= 50) break + } + + return { matches } + } +} diff --git a/src/shared/plugins/agents/_for-migrate/list-dir-agent.ts b/src/shared/plugins/agents/_for-migrate/list-dir-agent.ts new file mode 100644 index 0000000..6ba1f14 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/list-dir-agent.ts @@ -0,0 +1,55 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { traverseFileOrFolders } from '@extension/file-utils/traverse-fs' +import { getWorkspaceFolder } from '@extension/utils' +import { z } from 'zod' + +import { listDirAgentName } from './agent-names' + +export class ListDirAgent extends BaseAgent { + static name = listDirAgentName + + name = ListDirAgent.name + + logTitle = 'List Directory' + + description = `List the contents of a directory. The quick tool to use for discovery, before using more targeted tools like semantic search or file reading. Useful to try to understand the file structure before diving deeper into specific files. Can be used to explore the codebase.` + + inputSchema = z.object({ + relativePath: z + .string() + .describe( + 'Path to list contents of, relative to the workspace root. Ex: "./" is the root of the workspace' + ), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + items: z.array( + z.object({ + type: z.enum(['file', 'folder']), + relativePath: z.string(), + fullPath: z.string() + }) + ) + }) + + async execute(input: z.infer) { + const workspacePath = getWorkspaceFolder().uri.fsPath + + const items = await traverseFileOrFolders({ + type: 'fileOrFolder', + filesOrFolders: [input.relativePath], + isGetFileContent: false, + workspacePath, + itemCallback: item => item + }) + + return { items } + } +} diff --git a/src/shared/plugins/agents/_for-migrate/parallel-apply-agent.ts b/src/shared/plugins/agents/_for-migrate/parallel-apply-agent.ts new file mode 100644 index 0000000..bd25420 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/parallel-apply-agent.ts @@ -0,0 +1,108 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { runAction } from '@extension/state' +import { z } from 'zod' + +import { parallelApplyAgentName } from './agent-names' + +export class ParallelApplyAgent extends BaseAgent { + static name = parallelApplyAgentName + + name = ParallelApplyAgent.name + + logTitle = 'Parallel Apply' + + description = `When there are multiple locations that can be edited in parallel, with a similar type of edit, use this tool to sketch out a plan for the edits. +You should start with the editPlan which describes what the edits will be. +Then, write out the files that will be edited with the editRegions argument. +You shouldn't edit more than 50 files at a time.` + + inputSchema = z.object({ + editPlan: z + .string() + .describe( + 'A detailed description of the parallel edits to be applied.\n' + + 'They should be specified in a way where a model just seeing one of the files and this plan would be able to apply the edits to any of the files.\n' + + 'It should be in the first person, describing what you will do on another iteration, after seeing the file.' + ), + editRegions: z.array( + z.object({ + relativeWorkspacePath: z + .string() + .describe('The path to the file to edit.'), + startLine: z + .number() + .optional() + .describe( + 'The start line of the region to edit. 1-indexed and inclusive.' + ), + endLine: z + .number() + .optional() + .describe( + 'The end line of the region to edit. 1-indexed and inclusive.' + ) + }) + ) + }) + + outputSchema = z.object({ + success: z.boolean(), + error: z.string().optional(), + results: z.array( + z.object({ + filePath: z.string(), + success: z.boolean(), + error: z.string().optional() + }) + ) + }) + + async execute(input: z.infer) { + if (input.editRegions.length > 50) { + return { + success: false, + error: 'Cannot edit more than 50 files at once', + results: [] + } + } + + const results = await Promise.all( + input.editRegions.map(async region => { + try { + const fullPath = await runAction( + this.context.strategyOptions.registerManager + ).server.file.getFullPath({ + actionParams: { + path: region.relativeWorkspacePath, + returnNullIfNotExists: false + } + }) + + console.log('Processing file:', fullPath) + + // TODO: add task action to apply parallel edits + + return { + filePath: region.relativeWorkspacePath, + success: true + } + } catch (error) { + return { + filePath: region.relativeWorkspacePath, + success: false, + error: error instanceof Error ? error.message : String(error) + } + } + }) + ) + + const hasErrors = results.some(result => !result.success) + + return { + success: !hasErrors, + error: hasErrors ? 'Some files failed to process' : undefined, + results + } + } +} diff --git a/src/shared/plugins/agents/_for-migrate/read-files-agent.ts b/src/shared/plugins/agents/_for-migrate/read-files-agent.ts new file mode 100644 index 0000000..78ad6e2 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/read-files-agent.ts @@ -0,0 +1,125 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { VsCodeFS } from '@extension/file-utils/vscode-fs' +import { logger } from '@extension/logger' +import { runAction } from '@extension/state' +import { settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import { readFilesAgentName } from './agent-names' + +export class ReadFilesAgent extends BaseAgent { + static name = readFilesAgentName + + name = ReadFilesAgent.name + + logTitle = 'Read Files' + + description = `Read the contents of multiple files (and the outline). + +When using this tool to gather information, it's your responsibility to ensure you have the COMPLETE context. Each time you call this command you should: +1) Assess if contents viewed are sufficient to proceed with the task. +2) Take note of lines not shown. +3) If file contents viewed are insufficient, and you suspect they may be in lines not shown, proactively call the tool again to view those lines. +4) When in doubt, call this tool again to gather more information. Partial file views may miss critical dependencies, imports, or functionality. + +If reading a range of lines is not enough, you may choose to read the entire file. +Reading entire files is often wasteful and slow, especially for large files (i.e. more than a few hundred lines). So you should use this option sparingly. +Reading the entire file is not allowed in most cases. You are only allowed to read the entire file if it has been edited or manually attached to the conversation by the user.` + + inputSchema = z.object({ + files: z.array( + z.object({ + relativePath: z + .string() + .describe( + 'The path of the file to read, relative to the workspace root.' + ), + shouldReadEntireFile: z + .boolean() + .describe('Whether to read the entire file. Defaults to false.'), + startLineOneIndexed: z + .number() + .describe( + 'The one-indexed line number to start reading from (inclusive).' + ), + endLineOneIndexedInclusive: z + .number() + .describe( + 'The one-indexed line number to end reading at (inclusive).' + ) + }) + ), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + content: z.string(), + relativePath: z.string(), + fullPath: z.string(), + startLine: z.number(), + endLine: z.number() + }) + + async execute(input: z.infer) { + const results = await settledPromiseResults( + input.files.map(async inputFile => { + const fullPath = await runAction( + this.context.strategyOptions.registerManager + ).server.file.getFullPath({ + actionParams: { + path: inputFile.relativePath, + returnNullIfNotExists: false + } + }) + + let fileContent = '' + + try { + if (fullPath) { + fileContent = await VsCodeFS.readFileOrOpenDocumentContent( + fullPath, + 'utf-8' + ) + } + } catch (e) { + logger.error('Failed to read file', { + fullPath, + error: e + }) + } + + if (inputFile.shouldReadEntireFile) { + return { + content: fileContent, + relativePath: inputFile.relativePath, + fullPath, + startLine: 1, + endLine: fileContent.split('\n').length + } + } + + const lines = fileContent.split('\n') + const selectedLines = lines.slice( + inputFile.startLineOneIndexed - 1, + inputFile.endLineOneIndexedInclusive + ) + + return { + content: selectedLines.join('\n'), + relativePath: inputFile.relativePath, + fullPath, + startLine: inputFile.startLineOneIndexed, + endLine: inputFile.endLineOneIndexedInclusive + } + }) + ) + + return results + } +} diff --git a/src/shared/plugins/agents/_for-migrate/reapply-agent.ts b/src/shared/plugins/agents/_for-migrate/reapply-agent.ts new file mode 100644 index 0000000..39835e2 --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/reapply-agent.ts @@ -0,0 +1,43 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { runAction } from '@extension/state' +import { z } from 'zod' + +import { reapplyAgentName } from './agent-names' + +export class ReapplyAgent extends BaseAgent { + static name = reapplyAgentName + + name = ReapplyAgent.name + + logTitle = 'Reapply Edit' + + description = `Calls a smarter model to apply the last edit to the specified file. +Use this tool immediately after the result of an editFile tool call ONLY IF the diff is not what you expected, indicating the model applying the changes was not smart enough to follow your instructions.` + + inputSchema = z.object({ + targetFilePath: z + .string() + .describe('The relative path to the file to reapply the last edit to.') + }) + + outputSchema = z.object({ + success: z.boolean(), + error: z.string().optional() + }) + + async execute(input: z.infer) { + const fullPath = await runAction( + this.context.strategyOptions.registerManager + ).server.file.getFullPath({ + actionParams: { + path: input.targetFilePath, + returnNullIfNotExists: false + } + }) + + console.log('fullPath', fullPath) + + // TODO: add task action to reapply edit + } +} diff --git a/src/shared/plugins/agents/_for-migrate/run-terminal-cmd-agent.ts b/src/shared/plugins/agents/_for-migrate/run-terminal-cmd-agent.ts new file mode 100644 index 0000000..0ced7ec --- /dev/null +++ b/src/shared/plugins/agents/_for-migrate/run-terminal-cmd-agent.ts @@ -0,0 +1,68 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { runAction } from '@extension/state' +import { z } from 'zod' + +import { runTerminalCmdAgentName } from './agent-names' + +export class RunTerminalCmdAgent extends BaseAgent { + static name = runTerminalCmdAgentName + + name = RunTerminalCmdAgent.name + + logTitle = 'Run Terminal Command' + + description = `PROPOSE a command to run on behalf of the user. +If you have this tool, note that you DO have the ability to run commands directly on the USER's system. + +Adhere to these rules: +1. Based on the contents of the conversation, you will be told if you are in the same shell as a previous step or a new shell. +2. If in a new shell, you should \`cd\` to the right directory and do necessary setup in addition to running the command. +3. If in the same shell, the state will persist, no need to do things like \`cd\` to the same directory. +4. For ANY commands that would use a pager, you should append \` | cat\` to the command (or whatever is appropriate). You MUST do this for: git, less, head, tail, more, etc. +5. For commands that are long running/expected to run indefinitely until interruption, please run them in the background. To run jobs in the background, set \`isBackground\` to true rather than changing the details of the command. +6. Dont include any newlines in the command.` + + inputSchema = z.object({ + command: z.string().describe('The terminal command to execute'), + isBackground: z + .boolean() + .describe('Whether the command should be run in the background'), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this command needs to be run and how it contributes to the goal.' + ), + requireUserApproval: z + .boolean() + .describe( + "Whether the user must approve the command before it is executed. Only set this to true if the command is safe and if it matches the user's requirements for commands that should be executed automatically." + ) + }) + + outputSchema = z.object({ + output: z.string(), + exitCode: z.number(), + command: z.string() + }) + + async execute(input: z.infer) { + const result = await runAction( + this.context.strategyOptions.registerManager + ).server.terminal.runTerminalCommand({ + actionParams: { + command: input.command, + isBackground: input.isBackground + } + }) + + // TODO: add task action to run terminal command + + return { + output: result.output, + exitCode: result.exitCode, + command: input.command + } + } +} diff --git a/src/shared/plugins/agents/web-search-agent.ts b/src/shared/plugins/agents/_for-migrate/web-search-agent.ts similarity index 100% rename from src/shared/plugins/agents/web-search-agent.ts rename to src/shared/plugins/agents/_for-migrate/web-search-agent.ts diff --git a/src/shared/plugins/agents/web-visit-agent.ts b/src/shared/plugins/agents/_for-migrate/web-visit-agent.ts similarity index 100% rename from src/shared/plugins/agents/web-visit-agent.ts rename to src/shared/plugins/agents/_for-migrate/web-visit-agent.ts diff --git a/src/shared/plugins/agents/agent-names.ts b/src/shared/plugins/agents/agent-names.ts deleted file mode 100644 index d06efd7..0000000 --- a/src/shared/plugins/agents/agent-names.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const codebaseSearchAgentName = 'codebaseSearch' -export const docRetrieverAgentName = 'docRetriever' -export const fsVisitAgentName = 'fsVisit' -export const webSearchAgentName = 'webSearch' -export const webVisitAgentName = 'webVisit' diff --git a/src/shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-client-plugin.tsx b/src/shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-client-plugin.tsx new file mode 100644 index 0000000..950bd6f --- /dev/null +++ b/src/shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-client-plugin.tsx @@ -0,0 +1,19 @@ +import { createAgentClientPlugin } from '@shared/plugins/agents/_base/client/create-agent-client-plugin' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { CodebaseSearchAgentThinkItem } from './codebase-search-agent-think-item' + +export const CodebaseSearchAgentClientPlugin = createAgentClientPlugin({ + id: AgentPluginId.CodebaseSearch, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider( + 'CustomRenderThinkItem', + () => CodebaseSearchAgentThinkItem + ) + } +}) diff --git a/src/shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-think-item.tsx b/src/shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-think-item.tsx new file mode 100644 index 0000000..f43feae --- /dev/null +++ b/src/shared/plugins/agents/codebase-search-agent-plugin/client/codebase-search-agent-think-item.tsx @@ -0,0 +1,63 @@ +import type { FC } from 'react' +import type { GetAgent } from '@extension/chat/strategies/base' +import type { FileInfo } from '@extension/file-utils/traverse-fs' +import type { CustomRenderThinkItemProps } from '@shared/plugins/agents/_base/client/agent-client-plugin-types' +import type { CodebaseSearchAgent } from '@shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent' +import type { SFC } from '@shared/types/common' +import { ChatThinkItem } from '@webview/components/chat/messages/roles/chat-thinks' +import { FileIcon } from '@webview/components/file-icon' +import { TruncateStart } from '@webview/components/truncate-start' +import { api } from '@webview/network/actions-api' +import { cn } from '@webview/utils/common' +import { getFileNameFromPath } from '@webview/utils/path' + +import type { CodeSnippet } from '../types' + +export const CodebaseSearchAgentThinkItem: SFC< + CustomRenderThinkItemProps> +> = ({ agent }) => ( + +
+ {agent.output.codeSnippets?.map((snippet, index) => ( + + ))} +
+
+) + +interface FileSnippetItemProps { + file: CodeSnippet | FileInfo +} + +export const FileSnippetItem: FC = ({ file }) => { + const openFileInEditor = async () => { + const fileFullPath = file.fullPath + + if (!fileFullPath) return + await api.actions().server.file.openFileInEditor({ + actionParams: { + path: fileFullPath, + startLine: 'startLine' in file ? file.startLine : undefined + } + }) + } + + const fileName = getFileNameFromPath(file.relativePath) + + return ( +
+
+ + {fileName} +
+ + {file.relativePath} + +
+ ) +} diff --git a/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-plugin.ts b/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-plugin.ts new file mode 100644 index 0000000..a45414c --- /dev/null +++ b/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-plugin.ts @@ -0,0 +1,29 @@ +import type { + AgentServerPlugin, + AgentServerPluginContext +} from '@shared/plugins/agents/_base/server/agent-server-plugin-context' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { CodebaseSearchAgentServerUtilsProvider } from './codebase-search-agent-server-utils-provider' + +export class CodebaseSearchAgentServerPlugin implements AgentServerPlugin { + id = AgentPluginId.CodebaseSearch + + version: string = pkg.version + + private context: AgentServerPluginContext | null = null + + async activate(context: AgentServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'serverUtils', + () => new CodebaseSearchAgentServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-utils-provider.ts b/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-utils-provider.ts new file mode 100644 index 0000000..7e6586c --- /dev/null +++ b/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent-server-utils-provider.ts @@ -0,0 +1,11 @@ +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' + +import { CodebaseSearchAgent } from './codebase-search-agent' + +export class CodebaseSearchAgentServerUtilsProvider + implements AgentServerUtilsProvider +{ + getAgentClass() { + return CodebaseSearchAgent + } +} diff --git a/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent.ts b/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent.ts new file mode 100644 index 0000000..322ae6c --- /dev/null +++ b/src/shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent.ts @@ -0,0 +1,113 @@ +import { BaseAgent } from '@extension/chat/strategies/base' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { CodebaseWatcherRegister } from '@extension/registers/codebase-watcher-register' +import { mergeCodeSnippets } from '@shared/plugins/_shared/merge-code-snippets' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import type { CodeSnippet } from '../types' + +export class CodebaseSearchAgent extends BaseAgent< + BaseGraphState, + { enableCodebaseAgent: boolean } +> { + static name = AgentPluginId.CodebaseSearch + + name = CodebaseSearchAgent.name + + description = `Find snippets of code from the codebase most relevant to the search query. +This is a semantic search tool, so the query should ask for something semantically matching what is needed. +If it makes sense to only search in particular directories, please specify them in the targetDirectories field. +If you don't know the file tree structure, please don't specify targetDirectories. +If the user's native language is not English, use both the English query. +Unless there is a clear reason to use your own search query, please just reuse the user's exact query with their wording. +Their exact wording/phrasing can often be helpful for the semantic search query. Keeping the same exact question format can also be helpful.` + + inputSchema = z.object({ + query: z + .string() + .describe( + "The search query to find relevant code. You should reuse the user's exact query/most recent message with their wording unless there is a clear reason not to." + ), + englishQuery: z + .string() + .optional() + .describe( + 'The English-translated query to find relevant code. If the user is not English, this is the query to use.' + ), + targetDirectories: z + .array(z.string()) + .optional() + .describe('Glob patterns for directories to search over'), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + codeSnippets: z.array( + z.object({ + fileHash: z.string(), + relativePath: z.string(), + fullPath: z.string(), + startLine: z.number(), + startCharacter: z.number(), + endLine: z.number(), + endCharacter: z.number(), + code: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const { enableCodebaseAgent } = this.context.createToolOptions + + if (!enableCodebaseAgent) { + return { codeSnippets: [] } + } + + const indexer = this.context.strategyOptions.registerManager.getRegister( + CodebaseWatcherRegister + )?.indexer + + if (!indexer) { + return { codeSnippets: [] } + } + + const searchResults = ( + await settledPromiseResults([ + indexer.searchSimilarRow(input.query), + input.englishQuery + ? indexer.searchSimilarRow(input.englishQuery) + : Promise.resolve([]) + ]) + ) + .flat() + .filter(Boolean) + + // Filter results by target directories if specified + const filteredResults = input.targetDirectories + ? searchResults.filter(row => + input.targetDirectories?.some(dir => row.relativePath.includes(dir)) + ) + : searchResults + + const searchCodeSnippets = filteredResults.map(row => { + // eslint-disable-next-line unused-imports/no-unused-vars + const { embedding, ...others } = row + return { ...others, code: '' } + }) + + const codeSnippets = ( + await mergeCodeSnippets(searchCodeSnippets, { + mode: 'expanded' + }) + ).slice(0, 8) + + return { codeSnippets } + } +} diff --git a/src/shared/plugins/agents/codebase-search-agent-plugin/types.ts b/src/shared/plugins/agents/codebase-search-agent-plugin/types.ts new file mode 100644 index 0000000..ebbe6ce --- /dev/null +++ b/src/shared/plugins/agents/codebase-search-agent-plugin/types.ts @@ -0,0 +1,10 @@ +export interface CodeSnippet { + fileHash: string + relativePath: string + fullPath: string + startLine: number + startCharacter: number + endLine: number + endCharacter: number + code: string +} diff --git a/src/shared/plugins/agents/codebase-search-agent.ts b/src/shared/plugins/agents/codebase-search-agent.ts deleted file mode 100644 index 80b8f97..0000000 --- a/src/shared/plugins/agents/codebase-search-agent.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { BaseAgent } from '@extension/chat/strategies/base/base-agent' -import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' -import { CodebaseWatcherRegister } from '@extension/registers/codebase-watcher-register' -import { settledPromiseResults } from '@shared/utils/common' -import { z } from 'zod' - -import { mergeCodeSnippets } from '../fs-plugin/server/merge-code-snippets' -import type { CodeSnippet } from '../fs-plugin/types' -import { codebaseSearchAgentName } from './agent-names' - -export class CodebaseSearchAgent extends BaseAgent< - BaseGraphState, - { enableCodebaseAgent: boolean } -> { - static name = codebaseSearchAgentName - - name = CodebaseSearchAgent.name - - logTitle = 'Search Codebase' - - description = 'Search for relevant code in the codebase.' - - inputSchema = z.object({ - queryParts: z - .array(z.string()) - .describe('List of search terms to find relevant code in the codebase') - }) - - outputSchema = z.object({ - codeSnippets: z.array( - z.object({ - fileHash: z.string(), - relativePath: z.string(), - fullPath: z.string(), - startLine: z.number(), - startCharacter: z.number(), - endLine: z.number(), - endCharacter: z.number(), - code: z.string() - }) satisfies z.ZodType - ) - }) - - async execute(input: z.infer) { - const { enableCodebaseAgent } = this.context.createToolOptions - - if (!enableCodebaseAgent) { - return { codeSnippets: [] } - } - - const indexer = this.context.strategyOptions.registerManager.getRegister( - CodebaseWatcherRegister - )?.indexer - - if (!indexer) { - return { codeSnippets: [] } - } - - const searchResults = await settledPromiseResults( - input.queryParts?.map(query => indexer.searchSimilarRow(query)) || [] - ) - - const searchCodeSnippets = searchResults - .flat() - .slice(0, 8) - .map(row => { - // eslint-disable-next-line unused-imports/no-unused-vars - const { embedding, ...others } = row - return { ...others, code: '' } - }) - - const codeSnippets = await mergeCodeSnippets(searchCodeSnippets, { - mode: 'expanded' - }) - - return { codeSnippets } - } -} diff --git a/src/shared/plugins/agents/doc-retriever-agent-plugin/client/doc-retriever-agent-client-plugin.tsx b/src/shared/plugins/agents/doc-retriever-agent-plugin/client/doc-retriever-agent-client-plugin.tsx new file mode 100644 index 0000000..16456ae --- /dev/null +++ b/src/shared/plugins/agents/doc-retriever-agent-plugin/client/doc-retriever-agent-client-plugin.tsx @@ -0,0 +1,16 @@ +import { createAgentClientPlugin } from '@shared/plugins/agents/_base/client/create-agent-client-plugin' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { DocRetrieverAgentThinkItem } from './doc-retriever-agent-think-item' + +export const DocRetrieverAgentClientPlugin = createAgentClientPlugin({ + id: AgentPluginId.DocRetriever, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider('CustomRenderThinkItem', () => DocRetrieverAgentThinkItem) + } +}) diff --git a/src/shared/plugins/doc-plugin/client/doc-log-preview.tsx b/src/shared/plugins/agents/doc-retriever-agent-plugin/client/doc-retriever-agent-think-item.tsx similarity index 60% rename from src/shared/plugins/doc-plugin/client/doc-log-preview.tsx rename to src/shared/plugins/agents/doc-retriever-agent-plugin/client/doc-retriever-agent-think-item.tsx index f58e3ce..3e6a8e4 100644 --- a/src/shared/plugins/doc-plugin/client/doc-log-preview.tsx +++ b/src/shared/plugins/agents/doc-retriever-agent-plugin/client/doc-retriever-agent-think-item.tsx @@ -1,54 +1,39 @@ -import { FC, type ReactNode } from 'react' -import { FileTextIcon } from '@radix-ui/react-icons' -import { docRetrieverAgentName } from '@shared/plugins/agents/agent-names' -import type { DocRetrieverAgent } from '@shared/plugins/agents/doc-retriever-agent' -import type { CustomRenderLogPreviewProps } from '@shared/plugins/base/client/client-plugin-types' -import type { GetAgent } from '@shared/plugins/base/strategies' +import type { FC } from 'react' +import type { GetAgent } from '@shared/plugins/_shared/strategies' +import type { CustomRenderThinkItemProps } from '@shared/plugins/agents/_base/client/agent-client-plugin-types' import type { SFC } from '@shared/types/common' -import { ChatLogPreview } from '@webview/components/chat/messages/roles/chat-log-preview' +import { ChatThinkItem } from '@webview/components/chat/messages/roles/chat-thinks' import type { PreviewContent } from '@webview/components/content-preview' import { ContentPreviewPopover } from '@webview/components/content-preview-popover' import { api } from '@webview/network/actions-api' import { cn } from '@webview/utils/common' +import { FileTextIcon } from 'lucide-react' +import type { DocRetrieverAgent } from '../server/doc-retriever-agent' import type { DocInfo } from '../types' -export const DocLogPreview: SFC = props => { - const { log } = props - const { agent } = log - - const renderWrapper = (children: ReactNode) => ( - -
{children}
-
- ) - - if (!agent) return null - - switch (agent.name) { - case docRetrieverAgentName: - return renderWrapper( - (agent as GetAgent).output.relevantDocs?.map( - (doc, index) => ( - - ) - ) - ) - default: - return null - } -} +export const DocRetrieverAgentThinkItem: SFC< + CustomRenderThinkItemProps> +> = ({ agent }) => ( + +
+ {agent.output.relevantDocs?.map((doc, index) => ( + + ))} +
+
+) interface DocItemProps { doc: DocInfo className?: string } -const DocItem: FC = ({ doc, className }) => { +export const DocItem: FC = ({ doc, className }) => { const previewContent: PreviewContent = { type: 'markdown', content: doc.content diff --git a/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent-server-plugin.ts b/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent-server-plugin.ts new file mode 100644 index 0000000..185e391 --- /dev/null +++ b/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent-server-plugin.ts @@ -0,0 +1,29 @@ +import type { + AgentServerPlugin, + AgentServerPluginContext +} from '@shared/plugins/agents/_base/server/agent-server-plugin-context' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { DocRetrieverAgentServerUtilsProvider } from './doc-retriever-agent-server-utils-provider' + +export class DocRetrieverAgentServerPlugin implements AgentServerPlugin { + id = AgentPluginId.DocRetriever + + version: string = pkg.version + + private context: AgentServerPluginContext | null = null + + async activate(context: AgentServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'serverUtils', + () => new DocRetrieverAgentServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent-server-utils-provider.ts b/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent-server-utils-provider.ts new file mode 100644 index 0000000..e95bacb --- /dev/null +++ b/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent-server-utils-provider.ts @@ -0,0 +1,11 @@ +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' + +import { DocRetrieverAgent } from './doc-retriever-agent' + +export class DocRetrieverAgentServerUtilsProvider + implements AgentServerUtilsProvider +{ + getAgentClass() { + return DocRetrieverAgent + } +} diff --git a/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent.ts b/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent.ts new file mode 100644 index 0000000..61fa5d3 --- /dev/null +++ b/src/shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent.ts @@ -0,0 +1,94 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { DocCrawler } from '@extension/chat/utils/doc-crawler' +import { DocIndexer } from '@extension/chat/vectordb/doc-indexer' +import { aidePaths } from '@extension/file-utils/paths' +import { docSitesDB } from '@extension/lowdb/doc-sites-db' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { removeDuplicates, settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import type { DocInfo } from '../types' + +export class DocRetrieverAgent extends BaseAgent< + BaseGraphState, + { allowSearchDocSiteNames: string[] } +> { + static name = AgentPluginId.DocRetriever + + name = DocRetrieverAgent.name + + description = + 'Search for relevant information in specified documentation sites.' + + inputSchema = z.object({ + queryParts: z + .array( + z.object({ + siteName: z + .string() + .describe('The name of the documentation site to search'), + keywords: z + .array(z.string()) + .describe( + 'List of keywords to search for in the specified doc site' + ) + }) + ) + .describe( + "The AI should break down the user's query into multiple parts, each targeting a specific doc site with relevant keywords. This allows for a more comprehensive search across multiple documentation sources." + ) + }) + + outputSchema = z.object({ + relevantDocs: z.array( + z.object({ + content: z.string(), + path: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const { allowSearchDocSiteNames } = this.context.createToolOptions + const docSites = await docSitesDB.getAll() + + const docPromises = input.queryParts.map(async ({ siteName, keywords }) => { + const docSite = docSites.find(site => site.name === siteName) + + if (!docSite?.isIndexed || !allowSearchDocSiteNames.includes(siteName)) { + return [] + } + + const docIndexer = new DocIndexer( + DocCrawler.getDocCrawlerFolderPath(docSite.url), + aidePaths.getGlobalLanceDbPath() + ) + + await docIndexer.initialize() + + const searchResults = await settledPromiseResults( + keywords.map(keyword => docIndexer.searchSimilarRow(keyword)) + ) + + const searchRows = removeDuplicates( + searchResults.flatMap(result => result), + ['fullPath'] + ).slice(0, 3) + + const docInfoResults = await settledPromiseResults( + searchRows.map(async row => ({ + content: await docIndexer.getRowFileContent(row), + path: docSite.url + })) + ) + + return docInfoResults + }) + + const results = await settledPromiseResults(docPromises) + return { + relevantDocs: results.flatMap(result => result) + } + } +} diff --git a/src/shared/plugins/agents/doc-retriever-agent-plugin/types.ts b/src/shared/plugins/agents/doc-retriever-agent-plugin/types.ts new file mode 100644 index 0000000..8d54e96 --- /dev/null +++ b/src/shared/plugins/agents/doc-retriever-agent-plugin/types.ts @@ -0,0 +1,4 @@ +export interface DocInfo { + content: string + path: string // file path or url +} diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-client-plugin.tsx b/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-client-plugin.tsx new file mode 100644 index 0000000..199d99c --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-client-plugin.tsx @@ -0,0 +1,45 @@ +import { InlineDiffTaskState } from '@extension/registers/inline-diff-register/types' +import { createAgentClientPlugin } from '@shared/plugins/agents/_base/client/create-agent-client-plugin' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import type { + IsCompletedAction, + IsSameAction +} from '../../_base/client/agent-client-plugin-types' +import type { EditFileAction } from '../types' +import { EditFileAgentFloatingActionItem } from './edit-file-agent-floating-action-item' +import { EditFileAgentMessageActionItem } from './edit-file-agent-message-action-item' + +export const EditFileAgentClientPlugin = createAgentClientPlugin({ + id: AgentPluginId.EditFile, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider( + 'CustomRenderMessageActionItem', + () => EditFileAgentMessageActionItem + ) + + registerProvider( + 'CustomRenderFloatingActionItem', + () => EditFileAgentFloatingActionItem + ) + + registerProvider('isSameAction', () => isSameAction) + registerProvider('isCompletedAction', () => isCompletedAction) + } +}) + +const isSameAction: IsSameAction = (actionA, actionB) => { + const filePathA = actionA.agent?.input.targetFilePath + const filePathB = actionB.agent?.input.targetFilePath + return filePathA === filePathB +} + +const isCompletedAction: IsCompletedAction = action => + ![InlineDiffTaskState.Reviewing].includes( + action.state.inlineDiffTask?.state as InlineDiffTaskState + ) diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-floating-action-item.tsx b/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-floating-action-item.tsx new file mode 100644 index 0000000..281b800 --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-floating-action-item.tsx @@ -0,0 +1,186 @@ +/* eslint-disable unused-imports/no-unused-vars */ +import { InlineDiffTaskState } from '@extension/registers/inline-diff-register/types' +import { Cross2Icon, DotFilledIcon, ReloadIcon } from '@radix-ui/react-icons' +import type { SFC } from '@shared/types/common' +import { ButtonWithTooltip } from '@webview/components/button-with-tooltip' +import { FileIcon } from '@webview/components/file-icon' +import { useChatContext } from '@webview/contexts/chat-context' +import { useSessionActionContext } from '@webview/contexts/conversation-action-context/session-action-context' +import { useFileInfoForMessage } from '@webview/hooks/api/use-file-info-for-message' +import { api } from '@webview/network/actions-api' +import { cn } from '@webview/utils/common' +import { getFileNameFromPath } from '@webview/utils/path' +import { CheckIcon, PlayIcon } from 'lucide-react' + +import type { CustomRenderFloatingActionItemProps } from '../../_base/client/agent-client-plugin-types' +import type { EditFileAction } from '../types' + +export const EditFileAgentFloatingActionItem: SFC< + CustomRenderFloatingActionItemProps +> = props => { + const { conversationAction, conversation } = props + + const { context } = useChatContext() + const { + startActionMutation, + restartActionMutation, + acceptActionMutation, + rejectActionMutation + } = useSessionActionContext() + const { data: fileInfo } = useFileInfoForMessage({ + relativePath: conversationAction.agent?.input.targetFilePath + }) + + const { inlineDiffTask } = conversationAction.state + + const openFileInEditor = async () => { + const fileFullPath = fileInfo?.fullPath + + if (!fileFullPath) return + await api.actions().server.file.openFileInEditor({ + actionParams: { + path: fileFullPath + } + }) + } + + const renderActionButton = () => { + if (inlineDiffTask?.state === InlineDiffTaskState.Idle || !inlineDiffTask) { + return ( + + startActionMutation.mutate({ + chatContext: context, + conversation, + action: conversationAction + }) + } + > + + + ) + } + + if (inlineDiffTask?.state === InlineDiffTaskState.Reviewing) { + return ( + <> + {/* reject */} + + rejectActionMutation.mutate({ + chatContext: context, + conversation, + action: conversationAction + }) + } + > + + + + {/* accept */} + + acceptActionMutation.mutate({ + chatContext: context, + conversation, + action: conversationAction + }) + } + > + + + + ) + } + + return ( + + restartActionMutation.mutate({ + chatContext: context, + conversation, + action: conversationAction + }) + } + > + + + ) + } + + if (!fileInfo) return null + + return ( +
+
+ {/* title */} + + {getFileNameFromPath(fileInfo.relativePath)} + + {inlineDiffTask && + ![InlineDiffTaskState.Rejected, InlineDiffTaskState.Error].includes( + inlineDiffTask.state + ) ? ( + + ({inlineDiffTask.waitForReviewDiffBlockIds.length}/ + {inlineDiffTask.originalWaitForReviewDiffBlockIdCount}) + + ) : null} + + {/* status */} + {inlineDiffTask?.state === InlineDiffTaskState.Generating && ( + +
+ + )} + + {inlineDiffTask?.state === InlineDiffTaskState.Reviewing && ( + + + + )} + + {inlineDiffTask?.state === InlineDiffTaskState.Accepted && ( + + + + )} + + {inlineDiffTask?.state === InlineDiffTaskState.Rejected && ( + + + + )} +
+ + {/* actions */} +
e.stopPropagation()} + > + {renderActionButton()} +
+
+ ) +} diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-message-action-item.tsx b/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-message-action-item.tsx new file mode 100644 index 0000000..258f38c --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/client/edit-file-agent-message-action-item.tsx @@ -0,0 +1,96 @@ +/* eslint-disable unused-imports/no-unused-vars */ +import { InlineDiffTaskState } from '@extension/registers/inline-diff-register/types' +import type { SFC } from '@shared/types/common' +import { ButtonWithTooltip } from '@webview/components/button-with-tooltip' +import { FileIcon } from '@webview/components/file-icon' +import { + CollapsibleBlock, + type CollapsibleBlockStatus +} from '@webview/components/ui/collapsible-block' +import { Highlighter } from '@webview/components/ui/highlighter' +import { useGetFullPath } from '@webview/hooks/api/use-get-full-path' +import { api } from '@webview/network/actions-api' +import { getFileNameFromPath } from '@webview/utils/path' +import { getShikiLanguage } from '@webview/utils/shiki' +import { CopyIcon } from 'lucide-react' +import { toast } from 'sonner' + +import type { CustomRenderMessageActionItemProps } from '../../_base/client/agent-client-plugin-types' +import type { EditFileAction } from '../types' + +export const EditFileAgentMessageActionItem: SFC< + CustomRenderMessageActionItemProps +> = ({ conversationAction, setConversationAction }) => { + const { targetFilePath, codeEdit } = conversationAction.agent!.input + + const { data: fullPath } = useGetFullPath({ + path: targetFilePath, + returnNullIfNotExists: false + }) + + const shikiLang = getShikiLanguage({ + unknownLang: 'typescript', + path: targetFilePath + }) + + const copyToClipboard = () => { + navigator.clipboard.writeText(codeEdit) + toast.success('Code copied to clipboard') + } + + const openFileInEditor = async () => { + if (!fullPath) return + await api.actions().server.file.openFileInEditor({ + actionParams: { + path: fullPath + } + }) + } + + const renderActions = () => ( + <> + + + + + ) + + const renderFileName = () => + targetFilePath ? ( +
+ + {getFileNameFromPath(targetFilePath)} +
+ ) : null + + const stateMap: Record = { + [InlineDiffTaskState.Idle]: 'idle', + [InlineDiffTaskState.Generating]: 'loading', + [InlineDiffTaskState.Reviewing]: 'waiting', + [InlineDiffTaskState.Accepted]: 'success', + [InlineDiffTaskState.Rejected]: 'error', + [InlineDiffTaskState.Error]: 'error' + } + + return ( + openFileInEditor()} + > + + + ) +} diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-plugin.ts b/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-plugin.ts new file mode 100644 index 0000000..8aa9f63 --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-plugin.ts @@ -0,0 +1,18 @@ +import type { AgentServerPlugin } from '@shared/plugins/agents/_base/server/agent-server-plugin-context' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { EditFileAgentServerUtilsProvider } from './edit-file-agent-server-utils-provider' + +export class EditFileAgentServerPlugin implements AgentServerPlugin { + id = AgentPluginId.EditFile + + version = pkg.version + + async activate(context: any) { + context.registerProvider( + 'serverUtils', + () => new EditFileAgentServerUtilsProvider() + ) + } +} diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-utils-provider.ts b/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-utils-provider.ts new file mode 100644 index 0000000..8a3a128 --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent-server-utils-provider.ts @@ -0,0 +1,136 @@ +import { InlineDiffTaskState } from '@extension/registers/inline-diff-register/types' +import { runAction } from '@extension/state' +import type { ActionContext } from '@shared/actions/types' +import type { ChatContext, Conversation } from '@shared/entities' +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' +import { getErrorMsg } from '@shared/utils/common' +import { cloneDeep } from 'es-toolkit' +import type { WritableDraft } from 'immer' +import type { Writeable } from 'zod' + +import type { EditFileAction } from '../types' +import { EditFileAgent } from './edit-file-agent' + +export class EditFileAgentServerUtilsProvider + implements AgentServerUtilsProvider +{ + getAgentClass() { + return EditFileAgent + } + + async onStartAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: EditFileAction + }> + ) { + const { agent } = context.actionParams.action + const streamTask = + await runAction().server.apply.createAndStartApplyCodeTask({ + ...context, + actionParams: { + path: agent?.input.targetFilePath || '', + code: agent?.input.codeEdit || '', + cleanLast: true + } + }) + + let lastTaskState: InlineDiffTaskState | undefined + for await (const task of streamTask) { + if (lastTaskState !== task.state) { + await runAction().server.agent.updateCurrentAction({ + ...context, + actionParams: { + ...context.actionParams, + updater: _draft => { + const draft = _draft as WritableDraft + draft.state.inlineDiffTask = cloneDeep(task) + } + } + }) + } + lastTaskState = task.state + + if (task.state === InlineDiffTaskState.Error) { + throw new Error(`Failed to apply code: ${getErrorMsg(task.error)}`) + } + } + } + + async onRestartAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: EditFileAction + }> + ) { + const { inlineDiffTask } = context.actionParams.action.state + if (!inlineDiffTask) throw new Error('Inline diff task not found') + + await runAction().server.apply.abortAndCleanApplyCodeTask({ + ...context, + abortController: new AbortController(), + actionParams: { + task: inlineDiffTask + } + }) + await this.onStartAction(context) + } + + async onAcceptAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: EditFileAction + }> + ) { + const { inlineDiffTask } = context.actionParams.action.state + if (!inlineDiffTask) throw new Error('Inline diff task not found') + const acceptedTask = await runAction().server.apply.acceptApplyCodeTask({ + ...context, + actionParams: { + task: inlineDiffTask + } + }) + + await runAction().server.agent.updateCurrentAction({ + ...context, + actionParams: { + ...context.actionParams, + updater: _draft => { + const draft = _draft as Writeable + draft.state.inlineDiffTask = acceptedTask + } + } + }) + } + + async onRejectAction( + context: ActionContext<{ + chatContext: ChatContext + conversation: Conversation + action: EditFileAction + }> + ) { + const { inlineDiffTask } = context.actionParams.action.state + if (!inlineDiffTask) throw new Error('Inline diff task not found') + const rejectedTask = await runAction().server.apply.rejectApplyCodeTask({ + ...context, + actionParams: { + task: inlineDiffTask + } + }) + + await runAction().server.agent.updateCurrentAction({ + ...context, + actionParams: { + ...context.actionParams, + updater: _draft => { + const draft = _draft as Writeable + draft.state.inlineDiffTask = rejectedTask + } + } + }) + } +} diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent.ts b/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent.ts new file mode 100644 index 0000000..cfe0953 --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/server/edit-file-agent.ts @@ -0,0 +1,67 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { VsCodeFS } from '@extension/file-utils/vscode-fs' +import { z } from 'zod' + +import { AgentPluginId } from '../../_base/types' + +export class EditFileAgent extends BaseAgent { + static name = AgentPluginId.EditFile + + name = EditFileAgent.name + + description = `Use this tool to propose an edit to an existing file. +This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write. +When writing the edit, you should specify each edit in sequence, with the special comment \`// ... existing code ...\` to represent unchanged code in between edited lines. + +For example: +\`\`\` +// ... existing code ... +FIRST_EDIT +// ... existing code ... +SECOND_EDIT +// ... existing code ... +THIRD_EDIT +// ... existing code ... +\`\`\` + +You should bias towards repeating as few lines of the original file as possible to convey the change. +But, each edit should contain sufficient context of unchanged lines around the code you're editing to resolve ambiguity. +DO NOT omit spans of pre-existing code without using the \`// ... existing code ...\` comment to indicate its absence. +Make sure it is clear what the edit should be.` + + inputSchema = z.object({ + targetFilePath: z + .string() + .describe( + 'The target file path to modify. Always specify the target file as the first argument and use the relative path in the workspace of the file to edit' + ), + instructions: z + .string() + .describe( + 'A single sentence instruction describing what you are going to do for the sketched edit. This is used to assist the less intelligent model in applying the edit. Please use the first person to describe what you are going to do.' + ), + codeEdit: z + .string() + .describe( + "Specify ONLY the precise lines of code that you wish to edit. **NEVER specify or write out unchanged code**. Instead, represent all unchanged code using the comment of the language you're editing in - example: `// ... existing code ...`" + ), + blocking: z + .boolean() + .describe( + 'Whether this tool call should block the client from making further edits to the file until this call is complete. If true, the client will not be able to make further edits to the file until this call is complete.' + ) + }) + + outputSchema = z.object({ + success: z.boolean(), + error: z.string().optional() + }) + + async execute(input: z.infer) { + const fullPath = await VsCodeFS.getFullPath(input.targetFilePath, false) + console.log('fullPath', fullPath) + + // TODO: add task action to edit file + } +} diff --git a/src/shared/plugins/agents/edit-file-agent-plugin/types.ts b/src/shared/plugins/agents/edit-file-agent-plugin/types.ts new file mode 100644 index 0000000..22caea7 --- /dev/null +++ b/src/shared/plugins/agents/edit-file-agent-plugin/types.ts @@ -0,0 +1,14 @@ +import type { GetAgent } from '@extension/chat/strategies/base' +import type { InlineDiffTask } from '@extension/registers/inline-diff-register/types' +import type { ConversationAction } from '@shared/entities' + +import type { EditFileAgent } from './server/edit-file-agent' + +export interface EditFileAgentState { + inlineDiffTask: InlineDiffTask | null +} + +export type EditFileAction = ConversationAction< + EditFileAgentState, + GetAgent +> diff --git a/src/shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-client-plugin.tsx b/src/shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-client-plugin.tsx new file mode 100644 index 0000000..b4f36fe --- /dev/null +++ b/src/shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-client-plugin.tsx @@ -0,0 +1,16 @@ +import { createAgentClientPlugin } from '@shared/plugins/agents/_base/client/create-agent-client-plugin' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { ReadFilesAgentThinkItem } from './read-files-agent-think-item' + +export const ReadFilesAgentClientPlugin = createAgentClientPlugin({ + id: AgentPluginId.ReadFiles, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider('CustomRenderThinkItem', () => ReadFilesAgentThinkItem) + } +}) diff --git a/src/shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-think-item.tsx b/src/shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-think-item.tsx new file mode 100644 index 0000000..acc1c5a --- /dev/null +++ b/src/shared/plugins/agents/read-files-agent-plugin/client/read-files-agent-think-item.tsx @@ -0,0 +1,19 @@ +import type { GetAgent } from '@extension/chat/strategies/base' +import type { CustomRenderThinkItemProps } from '@shared/plugins/agents/_base/client/agent-client-plugin-types' +import type { SFC } from '@shared/types/common' +import { ChatThinkItem } from '@webview/components/chat/messages/roles/chat-thinks' + +import { FileSnippetItem } from '../../codebase-search-agent-plugin/client/codebase-search-agent-think-item' +import type { ReadFilesAgent } from '../server/read-files-agent' + +export const ReadFilesAgentThinkItem: SFC< + CustomRenderThinkItemProps> +> = ({ agent }) => ( + +
+ {agent.output.codeSnippets.map((snippet, index) => ( + + ))} +
+
+) diff --git a/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-plugin.ts b/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-plugin.ts new file mode 100644 index 0000000..699d9a8 --- /dev/null +++ b/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-plugin.ts @@ -0,0 +1,29 @@ +import type { + AgentServerPlugin, + AgentServerPluginContext +} from '@shared/plugins/agents/_base/server/agent-server-plugin-context' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { ReadFilesAgentServerUtilsProvider } from './read-files-agent-server-utils-provider' + +export class ReadFilesAgentServerPlugin implements AgentServerPlugin { + id = AgentPluginId.ReadFiles + + version: string = pkg.version + + private context: AgentServerPluginContext | null = null + + async activate(context: AgentServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'serverUtils', + () => new ReadFilesAgentServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-utils-provider.ts b/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-utils-provider.ts new file mode 100644 index 0000000..3f751a3 --- /dev/null +++ b/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent-server-utils-provider.ts @@ -0,0 +1,11 @@ +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' + +import { ReadFilesAgent } from './read-files-agent' + +export class ReadFilesAgentServerUtilsProvider + implements AgentServerUtilsProvider +{ + getAgentClass() { + return ReadFilesAgent + } +} diff --git a/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent.ts b/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent.ts new file mode 100644 index 0000000..8ac205e --- /dev/null +++ b/src/shared/plugins/agents/read-files-agent-plugin/server/read-files-agent.ts @@ -0,0 +1,120 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { getFileHash } from '@extension/file-utils/get-file-hash' +import { VsCodeFS } from '@extension/file-utils/vscode-fs' +import { settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import { AgentPluginId } from '../../_base/types' +import type { CodeSnippet } from '../../codebase-search-agent-plugin/types' + +export class ReadFilesAgent extends BaseAgent { + static name = AgentPluginId.ReadFiles + + name = ReadFilesAgent.name + + description = `Read the contents of multiple files (and the outline). + +When using this tool to gather information, it's your responsibility to ensure you have the COMPLETE context. Each time you call this command you should: +1) Assess if contents viewed are sufficient to proceed with the task. +2) Take note of lines not shown. +3) If file contents viewed are insufficient, and you suspect they may be in lines not shown, proactively call the tool again to view those lines. +4) When in doubt, call this tool again to gather more information. Partial file views may miss critical dependencies, imports, or functionality. + +If reading a range of lines is not enough, you may choose to read the entire file. +Reading entire files is often wasteful and slow, especially for large files (i.e. more than a few hundred lines). So you should use this option sparingly. +Reading the entire file is not allowed in most cases. You are only allowed to read the entire file if it has been edited or manually attached to the conversation by the user.` + + inputSchema = z.object({ + files: z.array( + z.object({ + relativePath: z + .string() + .describe( + 'The path of the file to read, relative to the workspace root.' + ), + shouldReadEntireFile: z + .boolean() + .describe('Whether to read the entire file. Defaults to false.'), + startLineOneIndexed: z + .number() + .describe( + 'The one-indexed line number to start reading from (inclusive).' + ), + endLineOneIndexedInclusive: z + .number() + .describe( + 'The one-indexed line number to end reading at (inclusive).' + ) + }) + ), + explanation: z + .string() + .optional() + .describe( + 'One sentence explanation as to why this tool is being used, and how it contributes to the goal.' + ) + }) + + outputSchema = z.object({ + codeSnippets: z.array( + z.object({ + fileHash: z.string(), + relativePath: z.string(), + fullPath: z.string(), + startLine: z.number(), + startCharacter: z.number(), + endLine: z.number(), + endCharacter: z.number(), + code: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const codeSnippets = await settledPromiseResults( + input.files.map(async inputFile => { + const fullPath = await VsCodeFS.getFullPath( + inputFile.relativePath, + false + ) + + if (!fullPath) + throw new Error(`File not found:${inputFile.relativePath}`) + + const fileContent = await VsCodeFS.readFileOrOpenDocumentContent( + fullPath, + 'utf-8' + ) + + const lines = fileContent.split('\n') + const startLine = inputFile.shouldReadEntireFile + ? 1 + : inputFile.startLineOneIndexed + const endLine = inputFile.shouldReadEntireFile + ? lines.length + : inputFile.endLineOneIndexedInclusive + + const selectedLines = lines.slice(startLine - 1, endLine) + const code = selectedLines.join('\n') + + const fileHash = await getFileHash(fullPath) + + return { + fileHash, + relativePath: inputFile.relativePath, + fullPath, + startLine, + startCharacter: 0, + endLine, + endCharacter: selectedLines[selectedLines.length - 1]?.length ?? 0, + code + } satisfies CodeSnippet + }) + ) + + return { + codeSnippets + } + } +} diff --git a/src/shared/plugins/agents/web-search-agent-plugin/client/web-search-agent-client-plugin.tsx b/src/shared/plugins/agents/web-search-agent-plugin/client/web-search-agent-client-plugin.tsx new file mode 100644 index 0000000..5ad2990 --- /dev/null +++ b/src/shared/plugins/agents/web-search-agent-plugin/client/web-search-agent-client-plugin.tsx @@ -0,0 +1,16 @@ +import { createAgentClientPlugin } from '@shared/plugins/agents/_base/client/create-agent-client-plugin' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { WebSearchAgentThinkItem } from './web-search-agent-think-item' + +export const WebSearchAgentClientPlugin = createAgentClientPlugin({ + id: AgentPluginId.WebSearch, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider('CustomRenderThinkItem', () => WebSearchAgentThinkItem) + } +}) diff --git a/src/shared/plugins/agents/web-search-agent-plugin/client/web-search-agent-think-item.tsx b/src/shared/plugins/agents/web-search-agent-plugin/client/web-search-agent-think-item.tsx new file mode 100644 index 0000000..fabdab2 --- /dev/null +++ b/src/shared/plugins/agents/web-search-agent-plugin/client/web-search-agent-think-item.tsx @@ -0,0 +1,24 @@ +import type { GetAgent } from '@extension/chat/strategies/base' +import type { CustomRenderThinkItemProps } from '@shared/plugins/agents/_base/client/agent-client-plugin-types' +import type { SFC } from '@shared/types/common' +import { ChatThinkItem } from '@webview/components/chat/messages/roles/chat-thinks' +import { cn } from '@webview/utils/common' + +import { WebContentInfoItem } from '../../web-visit-agent-plugin/client/web-visit-agent-think-item' +import type { WebSearchAgent } from '../server/web-search-agent' + +export const WebSearchAgentThinkItem: SFC< + CustomRenderThinkItemProps> +> = ({ agent }) => ( + +
+ {agent.output.searchResults?.map((searchResult, index) => ( + + ))} +
+
+) diff --git a/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent-server-plugin.ts b/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent-server-plugin.ts new file mode 100644 index 0000000..524e950 --- /dev/null +++ b/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent-server-plugin.ts @@ -0,0 +1,29 @@ +import type { + AgentServerPlugin, + AgentServerPluginContext +} from '@shared/plugins/agents/_base/server/agent-server-plugin-context' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { WebSearchAgentServerUtilsProvider } from './web-search-agent-server-utils-provider' + +export class WebSearchAgentServerPlugin implements AgentServerPlugin { + id = AgentPluginId.WebSearch + + version: string = pkg.version + + private context: AgentServerPluginContext | null = null + + async activate(context: AgentServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'serverUtils', + () => new WebSearchAgentServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent-server-utils-provider.ts b/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent-server-utils-provider.ts new file mode 100644 index 0000000..1ee2b96 --- /dev/null +++ b/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent-server-utils-provider.ts @@ -0,0 +1,11 @@ +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' + +import { WebSearchAgent } from './web-search-agent' + +export class WebSearchAgentServerUtilsProvider + implements AgentServerUtilsProvider +{ + getAgentClass() { + return WebSearchAgent + } +} diff --git a/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent.ts b/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent.ts new file mode 100644 index 0000000..8682cee --- /dev/null +++ b/src/shared/plugins/agents/web-search-agent-plugin/server/web-search-agent.ts @@ -0,0 +1,153 @@ +import { ModelProviderFactory } from '@extension/ai/model-providers/helpers/factory' +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { ChatMessagesConstructor } from '@extension/chat/strategies/chat-strategy/messages-constructors/chat-messages-constructor' +import { searxngSearch } from '@extension/chat/utils/searxng-search' +import { logger } from '@extension/logger' +import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio' +import type { Document } from '@langchain/core/documents' +import { HumanMessage } from '@langchain/core/messages' +import { settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import { AgentPluginId } from '../../_base/types' +import type { WebContentInfo } from '../../web-visit-agent-plugin/types' + +const MAX_CONTENT_LENGTH = 16 * 1000 + +export class WebSearchAgent extends BaseAgent< + BaseGraphState, + { enableWebSearchAgent: boolean } +> { + static name = AgentPluginId.WebSearch + + name = WebSearchAgent.name + + description = + 'IMPORTANT: Proactively use this web search tool whenever you:\n' + + '1. Need to verify or update your knowledge about recent developments, versions, or current facts\n' + + '2. Are unsure about specific technical details or best practices\n' + + '3. Need real-world examples or implementation details\n' + + '4. Encounter questions about:\n' + + ' - Current events or recent updates\n' + + ' - Latest software versions or features\n' + + ' - Modern best practices or trends\n' + + ' - Specific technical implementations\n' + + '5. Want to provide evidence-based recommendations\n\n' + + 'DO NOT rely solely on your training data when users ask about:\n' + + '- Recent technologies or updates\n' + + '- Current best practices\n' + + '- Specific implementation details\n' + + '- Version-specific features or APIs\n' + + 'Instead, use this tool to get up-to-date information.' + + inputSchema = z.object({ + keywords: z.string().describe('Keywords to search web') + }) + + outputSchema = z.object({ + relevantContent: z.string(), + searchResults: z.array( + z.object({ + content: z.string(), + url: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const { enableWebSearchAgent } = this.context.createToolOptions + + if (!enableWebSearchAgent) { + return { relevantContent: '', webSearchResults: [] } + } + + const searxngSearchResult = await searxngSearch(input.keywords, { + abortController: this.context.state.abortController + }) + const urls = searxngSearchResult.results.map(result => result.url) + + const docsLoadResult = await settledPromiseResults( + urls.map(url => new CheerioWebBaseLoader(url).load()) + ) + + const docs: Document>[] = docsLoadResult.flat() + + const docsContent = docs + .map(doc => doc.pageContent) + .join('\n') + .slice(0, MAX_CONTENT_LENGTH) + + if (!docsContent) { + logger.warn('No content found in web search results', { + keywords: input.keywords, + docs + }) + return { relevantContent: '', webSearchResults: [] } + } + + const chatMessagesConstructor = new ChatMessagesConstructor({ + ...this.context.strategyOptions, + chatContext: this.context.state.chatContext + }) + const messagesFromChatContext = + await chatMessagesConstructor.constructMessages() + + const modelProvider = + await ModelProviderFactory.getModelProviderForChatContext( + this.context.state.chatContext + ) + const aiModel = await modelProvider.createLangChainModel() + + const response = await aiModel + .bind({ signal: this.context.state.abortController?.signal }) + .invoke([ + ...messagesFromChatContext.slice(-2), + new HumanMessage({ + content: ` +You are an expert information analyst. Your task is to process web search results and create a high-quality, focused summary that will be used in a subsequent AI conversation. Follow these critical guidelines: + +1. RELEVANCE & FOCUS +- Identify and extract ONLY information that directly addresses the user's query +- Eliminate tangential or loosely related content +- Preserve technical details and specific examples when relevant + +2. INFORMATION QUALITY +- Prioritize factual, verifiable information +- Include specific technical details, numbers, or metrics when present +- Maintain technical accuracy in specialized topics + +3. STRUCTURE & CLARITY +- Present information in a logical, well-structured format +- Use clear, precise language +- Preserve important technical terms and concepts + +4. BALANCED PERSPECTIVE +- Include multiple viewpoints when present +- Note any significant disagreements or contradictions +- Indicate if information seems incomplete or uncertain + +5. CONTEXT PRESERVATION +- Maintain crucial context that affects meaning +- Include relevant dates or version information for technical content +- Preserve attribution for significant claims or findings + +Here's the content to analyze: + +""" +${docsContent} +""" + +Provide a focused, technical summary that will serve as high-quality context for the next phase of AI conversation.` + }) + ]) + + return { + relevantContent: + typeof response.content === 'string' + ? response.content + : JSON.stringify(response.content), + webSearchResults: searxngSearchResult.results + } + } +} diff --git a/src/shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-client-plugin.tsx b/src/shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-client-plugin.tsx new file mode 100644 index 0000000..516b6c4 --- /dev/null +++ b/src/shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-client-plugin.tsx @@ -0,0 +1,16 @@ +import { createAgentClientPlugin } from '@shared/plugins/agents/_base/client/create-agent-client-plugin' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { WebVisitAgentThinkItem } from './web-visit-agent-think-item' + +export const WebVisitAgentClientPlugin = createAgentClientPlugin({ + id: AgentPluginId.WebVisit, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider('CustomRenderThinkItem', () => WebVisitAgentThinkItem) + } +}) diff --git a/src/shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-think-item.tsx b/src/shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-think-item.tsx new file mode 100644 index 0000000..599a27e --- /dev/null +++ b/src/shared/plugins/agents/web-visit-agent-plugin/client/web-visit-agent-think-item.tsx @@ -0,0 +1,80 @@ +import type { GetAgent } from '@extension/chat/strategies/base' +import type { CustomRenderThinkItemProps } from '@shared/plugins/agents/_base/client/agent-client-plugin-types' +import type { SFC } from '@shared/types/common' +import { ChatThinkItem } from '@webview/components/chat/messages/roles/chat-thinks' +import { ContentPreviewPopover } from '@webview/components/content-preview-popover' +import { cn } from '@webview/utils/common' +import { GlobeIcon } from 'lucide-react' + +import type { WebVisitAgent } from '../server/web-visit-agent' +import type { WebContentInfo } from '../types' + +export const WebVisitAgentThinkItem: SFC< + CustomRenderThinkItemProps> +> = ({ agent }) => ( + +
+ {agent.output.visitResults?.map((visitResult, index) => ( + + ))} +
+
+) + +interface WebContentInfoItemProps { + contentInfo: WebContentInfo + className?: string +} + +export const WebContentInfoItem: React.FC = ({ + contentInfo, + className +}) => { + const previewContent = { + type: 'markdown' as const, + content: contentInfo.content + } + + const handleOpenUrl = (e: React.MouseEvent) => { + e.stopPropagation() + if (!contentInfo.url) return + window.open(contentInfo.url, '_blank') + } + + return ( + +
+
+
+ +
+
+ +
+ {/* Content Preview */} +
+ {contentInfo.content} +
+ + {/* URL */} +
+ {contentInfo.url} +
+
+
+
+ ) +} diff --git a/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-plugin.ts b/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-plugin.ts new file mode 100644 index 0000000..d3e377c --- /dev/null +++ b/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-plugin.ts @@ -0,0 +1,29 @@ +import type { + AgentServerPlugin, + AgentServerPluginContext +} from '@shared/plugins/agents/_base/server/agent-server-plugin-context' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { WebVisitAgentServerUtilsProvider } from './web-visit-agent-server-utils-provider' + +export class WebVisitAgentServerPlugin implements AgentServerPlugin { + id = AgentPluginId.WebVisit + + version: string = pkg.version + + private context: AgentServerPluginContext | null = null + + async activate(context: AgentServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'serverUtils', + () => new WebVisitAgentServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-utils-provider.ts b/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-utils-provider.ts new file mode 100644 index 0000000..4adc2fe --- /dev/null +++ b/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent-server-utils-provider.ts @@ -0,0 +1,11 @@ +import type { AgentServerUtilsProvider } from '@shared/plugins/agents/_base/server/create-agent-provider-manager' + +import { WebVisitAgent } from './web-visit-agent' + +export class WebVisitAgentServerUtilsProvider + implements AgentServerUtilsProvider +{ + getAgentClass() { + return WebVisitAgent + } +} diff --git a/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent.ts b/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent.ts new file mode 100644 index 0000000..a1c2568 --- /dev/null +++ b/src/shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent.ts @@ -0,0 +1,61 @@ +import { BaseAgent } from '@extension/chat/strategies/base/base-agent' +import type { BaseGraphState } from '@extension/chat/strategies/base/base-state' +import { DocCrawler } from '@extension/chat/utils/doc-crawler' +import { settledPromiseResults } from '@shared/utils/common' +import { z } from 'zod' + +import { AgentPluginId } from '../../_base/types' +import type { WebContentInfo } from '../types' + +export class WebVisitAgent extends BaseAgent< + BaseGraphState, + { enableWebVisitAgent: boolean } +> { + static name = AgentPluginId.WebVisit + + name = WebVisitAgent.name + + description = + 'A tool for visiting and extracting content from web pages. Use this tool when you need to:\n' + + '1. Analyze specific webpage content in detail\n' + + '2. Extract information from known URLs\n' + + '3. Compare content across multiple web pages\n' + + '4. Verify or fact-check information from web sources\n' + + 'Note: Only use this for specific URLs you want to analyze, not for general web searches.' + + inputSchema = z.object({ + urls: z + .array(z.string().url()) + .describe( + 'An array of URLs to visit and retrieve content from. Each URL should be a valid web address.' + ) + }) + + outputSchema = z.object({ + visitResults: z.array( + z.object({ + content: z.string(), + url: z.string() + }) satisfies z.ZodType + ) + }) + + async execute(input: z.infer) { + const { enableWebVisitAgent } = this.context.createToolOptions + + if (!enableWebVisitAgent) { + return { visitResults: [] } + } + + const docCrawler = new DocCrawler(input.urls[0]!) + const visitResults = await settledPromiseResults( + input.urls.map(async url => ({ + url, + content: + (await docCrawler.getPageContent(url)) || 'Failed to retrieve content' + })) + ) + + return { visitResults } + } +} diff --git a/src/shared/plugins/agents/web-visit-agent-plugin/types.ts b/src/shared/plugins/agents/web-visit-agent-plugin/types.ts new file mode 100644 index 0000000..1d6917d --- /dev/null +++ b/src/shared/plugins/agents/web-visit-agent-plugin/types.ts @@ -0,0 +1,4 @@ +export interface WebContentInfo { + content: string + url: string +} diff --git a/src/shared/plugins/base/client/client-plugin-context.tsx b/src/shared/plugins/base/client/client-plugin-context.tsx deleted file mode 100644 index 7281bee..0000000 --- a/src/shared/plugins/base/client/client-plugin-context.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { createContext, useContext, useRef } from 'react' -import { useCallbackRef } from '@webview/hooks/use-callback-ref' -import { useImmer } from 'use-immer' - -import { ProviderUtils } from '../provider-manager' -import { PluginId, PluginState } from '../types' -import type { ClientPluginProviderMap } from './client-plugin-types' - -type ProviderKey = keyof ClientPluginProviderMap - -type IdProviderMap = Record< - PluginId, - () => ClientPluginProviderMap[ProviderKey] -> - -export interface ClientPluginContextValue { - state: Record - getState: () => Record - setState: ( - pluginId: PluginId, - updater: PluginState | ((draft: PluginState) => void) - ) => void - registerProvider: ( - pluginId: PluginId, - providerKey: K, - provider: () => ClientPluginProviderMap[K] - ) => void - getProviders: ( - providerKey: K - ) => ClientPluginProviderMap[K][] - mergeProviders: ( - providerKey: K - ) => ClientPluginProviderMap[K] | undefined -} - -const ClientPluginContext = createContext(null) - -export const ClientPluginProvider: React.FC = ({ - children -}) => { - const [state, setState] = useImmer>( - {} as Record - ) - const providerKeyInfoMapRef = useRef({} as Record) - - const setPluginState = ( - pluginId: PluginId, - updater: PluginState | ((draft: PluginState) => void) - ) => { - setState(draft => { - if (!draft[pluginId]) { - draft[pluginId] = {} - } - if (typeof updater === 'function') { - updater(draft[pluginId]) - } else { - draft[pluginId] = updater - } - }) - } - - const registerProvider = ( - pluginId: PluginId, - providerKey: K, - provider: () => ClientPluginProviderMap[K] - ) => { - if (!providerKeyInfoMapRef.current[providerKey]) { - providerKeyInfoMapRef.current[providerKey] = {} as IdProviderMap - } - providerKeyInfoMapRef.current[providerKey]![pluginId] = provider - } - - const getProviders = ( - providerKey: K - ): ClientPluginProviderMap[K][] => { - const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || - {}) as Record ClientPluginProviderMap[K]> - - return ProviderUtils.getValues(idProviderMap) - } - - const mergeProviders = ( - providerKey: K - ): ClientPluginProviderMap[K] | undefined => { - const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || - {}) as Record ClientPluginProviderMap[K]> - - const result = ProviderUtils.mergeAll(idProviderMap) - - return result - } - - const getState = useCallbackRef(() => state) - - return ( - - {children} - - ) -} - -export const usePlugin = () => { - const context = useContext(ClientPluginContext) - if (!context) { - throw new Error('usePlugin must be used within ClientPluginProvider') - } - return context -} diff --git a/src/shared/plugins/base/client/client-plugin-types.ts b/src/shared/plugins/base/client/client-plugin-types.ts deleted file mode 100644 index 03420f7..0000000 --- a/src/shared/plugins/base/client/client-plugin-types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { SFC } from '@shared/types/common' -import type { MentionOption } from '@webview/types/chat' - -import type { LogWithAgent } from '../base-to-state' - -export type UseMentionOptionsReturns = MentionOption[] - -export type CustomRenderLogPreviewProps = { - log: LogWithAgent -} - -export type ClientPluginProviderMap = { - useMentionOptions: () => UseMentionOptionsReturns - CustomRenderLogPreview: SFC -} diff --git a/src/shared/plugins/base/client/create-client-plugins.ts b/src/shared/plugins/base/client/create-client-plugins.ts deleted file mode 100644 index 8cee2bc..0000000 --- a/src/shared/plugins/base/client/create-client-plugins.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DocClientPlugin } from '@shared/plugins/doc-plugin/client/doc-client-plugin' -import { FsClientPlugin } from '@shared/plugins/fs-plugin/client/fs-client-plugin' -import { GitClientPlugin } from '@shared/plugins/git-plugin/client/git-client-plugin' -import { PromptSnippetClientPlugin } from '@shared/plugins/prompt-snippet-plugin/client/prompt-snippet-client-plugin' -import { TerminalClientPlugin } from '@shared/plugins/terminal-plugin/client/terminal-client-plugin' -import { WebClientPlugin } from '@shared/plugins/web-plugin/client/web-client-plugin' - -import type { ClientPlugin } from './use-client-plugin' - -export const createClientPlugins = (): ClientPlugin[] => { - const plugins: ClientPlugin[] = [ - FsClientPlugin, - DocClientPlugin, - WebClientPlugin, - GitClientPlugin, - TerminalClientPlugin, - PromptSnippetClientPlugin - ] - - return plugins -} diff --git a/src/shared/plugins/base/client/use-client-plugin.ts b/src/shared/plugins/base/client/use-client-plugin.ts deleted file mode 100644 index 8b1b612..0000000 --- a/src/shared/plugins/base/client/use-client-plugin.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect } from 'react' - -import { PluginId, PluginState } from '../types' -import { usePlugin } from './client-plugin-context' -import type { ClientPluginProviderMap } from './client-plugin-types' - -export interface ClientPlugin { - id: PluginId - version: string - getInitialState: () => State - usePlugin: () => void -} - -export type SetupProps = { - state: State - setState: (updater: (draft: State) => void) => void - registerProvider: ( - providerKey: K, - provider: () => ClientPluginProviderMap[K] - ) => void -} - -export const createClientPlugin = (options: { - id: PluginId - version: string - getInitialState: () => State - setup: (context: SetupProps) => void -}): ClientPlugin => ({ - id: options.id, - version: options.version, - getInitialState: options.getInitialState, - usePlugin() { - const { state, setState, registerProvider } = usePlugin() - - useEffect(() => { - setState(options.id, options.getInitialState()) - }, []) - - const pluginState = (state[options.id] || - options.getInitialState()) as State - - options.setup({ - state: pluginState, - setState: updater => setState(options.id, updater), - registerProvider: (key, provider) => - registerProvider( - options.id, - key as keyof ClientPluginProviderMap, - provider as () => ClientPluginProviderMap[keyof ClientPluginProviderMap] - ) - }) - } -}) diff --git a/src/shared/plugins/base/provider-manager.ts b/src/shared/plugins/base/provider-manager.ts deleted file mode 100644 index 79c45d6..0000000 --- a/src/shared/plugins/base/provider-manager.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { deepMergeProviders } from './deep-merge-providers' -import { PluginId } from './types' - -export class ProviderUtils { - static getValues = (idProvidersMap: Record T>): T[] => - Object.values(idProvidersMap).map(provider => provider?.()) - - static mergeAll = ( - idProvidersMap: Record T> - ): T | undefined => { - const allValues = ProviderUtils.getValues(idProvidersMap) - return deepMergeProviders(allValues) - } -} - -export class ProviderManager { - protected idProvidersMap = {} as Record T> - - register(pluginId: PluginId, provider: () => T): void { - this.idProvidersMap[pluginId] = provider - } - - unregister(pluginId: PluginId): void { - delete this.idProvidersMap[pluginId] - } - - getValues(): T[] { - return ProviderUtils.getValues(this.idProvidersMap) - } - - mergeAll(): T | undefined { - return ProviderUtils.mergeAll(this.idProvidersMap) - } -} diff --git a/src/shared/plugins/base/server/create-server-plugins.ts b/src/shared/plugins/base/server/create-server-plugins.ts deleted file mode 100644 index a7415e6..0000000 --- a/src/shared/plugins/base/server/create-server-plugins.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DocServerPlugin } from '@shared/plugins/doc-plugin/server/doc-server-plugin' -import { FsServerPlugin } from '@shared/plugins/fs-plugin/server/fs-server-plugin' -import { GitServerPlugin } from '@shared/plugins/git-plugin/server/git-server-plugin' -import { PromptSnippetServerPlugin } from '@shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-plugin' -import { TerminalServerPlugin } from '@shared/plugins/terminal-plugin/server/terminal-server-plugin' -import { WebServerPlugin } from '@shared/plugins/web-plugin/server/web-server-plugin' - -import type { ServerPlugin } from './server-plugin-context' - -export const createServerPlugins = (): ServerPlugin[] => { - const plugins: ServerPlugin[] = [ - new FsServerPlugin(), - new DocServerPlugin(), - new WebServerPlugin(), - new GitServerPlugin(), - new TerminalServerPlugin(), - new PromptSnippetServerPlugin() - ] - - return plugins -} diff --git a/src/shared/plugins/base/server/server-plugin-context.ts b/src/shared/plugins/base/server/server-plugin-context.ts deleted file mode 100644 index f17f931..0000000 --- a/src/shared/plugins/base/server/server-plugin-context.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { PluginId, PluginState } from '../types' -import type { createProviderManagers } from './create-provider-manager' -import type { ServerPluginRegistry } from './server-plugin-registry' - -export interface ServerPlugin { - id: PluginId - version: string - dependencies?: PluginId[] - activate(context: ServerPluginContext): Promise - deactivate?(): void -} - -interface ServerPluginContextOptions { - pluginId: PluginId - registry: ServerPluginRegistry -} - -// eslint-disable-next-line unused-imports/no-unused-vars -export class ServerPluginContext { - private pluginId: PluginId - - private registry: ServerPluginRegistry - - constructor(options: ServerPluginContextOptions) { - const { pluginId, registry } = options - this.pluginId = pluginId - this.registry = registry - } - - registerCommand(command: string, callback: (...args: any[]) => void): void { - this.registry.registerCommand(command, callback) - } - - executeCommand(command: string, ...args: any[]): void { - this.registry.executeCommand(command, ...args) - } - - registerProvider( - key: K, - provider: Parameters< - ReturnType[K]['register'] - >[1] - ): void { - this.registry.providerManagers[key].register(this.pluginId, provider as any) - } -} diff --git a/src/shared/plugins/base/types.ts b/src/shared/plugins/base/types.ts deleted file mode 100644 index 3151ec7..0000000 --- a/src/shared/plugins/base/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export enum PluginId { - Fs = 'fsPlugin', - Web = 'webPlugin', - Doc = 'docPlugin', - Git = 'gitPlugin', - Terminal = 'terminalPlugin', - PromptSnippet = 'promptSnippetPlugin' -} - -export type PluginState = Record - -export type ValidRecipeReturnType = - | State - | void - | undefined - | (State extends undefined ? any : never) diff --git a/src/shared/plugins/doc-plugin/server/doc-server-plugin.ts b/src/shared/plugins/doc-plugin/server/doc-server-plugin.ts deleted file mode 100644 index ec16c87..0000000 --- a/src/shared/plugins/doc-plugin/server/doc-server-plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { - ServerPlugin, - ServerPluginContext -} from '@shared/plugins/base/server/server-plugin-context' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import type { DocPluginState } from '../types' -import { DocChatStrategyProvider } from './chat-strategy/doc-chat-strategy-provider' -import { DocServerUtilsProvider } from './doc-server-utils-provider' - -export class DocServerPlugin implements ServerPlugin { - id = PluginId.Doc - - version: string = pkg.version - - private context: ServerPluginContext | null = null - - async activate(context: ServerPluginContext): Promise { - this.context = context - - this.context.registerProvider( - 'chatStrategy', - () => new DocChatStrategyProvider() - ) - - this.context.registerProvider( - 'serverUtils', - () => new DocServerUtilsProvider() - ) - } - - deactivate(): void { - this.context = null - } -} diff --git a/src/shared/plugins/doc-plugin/types.ts b/src/shared/plugins/doc-plugin/types.ts deleted file mode 100644 index b5f0adf..0000000 --- a/src/shared/plugins/doc-plugin/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Mention } from '@shared/entities' - -import { PluginId } from '../base/types' - -export enum DocMentionType { - Docs = `${PluginId.Doc}#docs`, - Doc = `${PluginId.Doc}#doc`, - DocSetting = `${PluginId.Doc}#doc-setting` -} - -export type DocMention = Mention - -export interface DocInfo { - content: string - path: string // file path or url -} - -export interface DocPluginState {} diff --git a/src/shared/plugins/fs-plugin/client/fs-log-preview.tsx b/src/shared/plugins/fs-plugin/client/fs-log-preview.tsx deleted file mode 100644 index 96f9d4c..0000000 --- a/src/shared/plugins/fs-plugin/client/fs-log-preview.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { FC, type ReactNode } from 'react' -import { - codebaseSearchAgentName, - fsVisitAgentName -} from '@shared/plugins/agents/agent-names' -import type { CodebaseSearchAgent } from '@shared/plugins/agents/codebase-search-agent' -import type { FsVisitAgent } from '@shared/plugins/agents/fs-visit-agent' -import type { CustomRenderLogPreviewProps } from '@shared/plugins/base/client/client-plugin-types' -import type { GetAgent } from '@shared/plugins/base/strategies' -import type { SFC } from '@shared/types/common' -import { ChatLogPreview } from '@webview/components/chat/messages/roles/chat-log-preview' -import { FileIcon } from '@webview/components/file-icon' -import { TruncateStart } from '@webview/components/truncate-start' -import { api } from '@webview/network/actions-api' -import type { FileInfo } from '@webview/types/chat' -import { cn } from '@webview/utils/common' -import { getFileNameFromPath } from '@webview/utils/path' - -import type { CodeSnippet } from '../types' - -export const FsLogPreview: SFC = props => { - const { log } = props - const { agent } = log - - const renderWrapper = (children: ReactNode) => ( - -
{children}
-
- ) - - if (!agent) return null - - switch (agent.name) { - case codebaseSearchAgentName: - return renderWrapper( -
- {(agent as GetAgent).output.codeSnippets?.map( - (snippet, index) => - )} -
- ) - case fsVisitAgentName: - return renderWrapper( -
- {(agent as GetAgent).output.files?.map( - (file, index) => - )} -
- ) - default: - return null - } -} - -interface FileSnippetItemProps { - file: CodeSnippet | FileInfo -} - -const FileSnippetItem: FC = ({ file }) => { - const openFileInEditor = async () => { - const fileFullPath = file.fullPath - - if (!fileFullPath) return - await api.actions().server.file.openFileInEditor({ - actionParams: { - path: fileFullPath, - startLine: 'startLine' in file ? file.startLine : undefined - } - }) - } - - const fileName = getFileNameFromPath(file.relativePath) - - return ( -
-
- - {fileName} -
- - {file.relativePath} - -
- ) -} diff --git a/src/shared/plugins/fs-plugin/fs-to-state.ts b/src/shared/plugins/fs-plugin/fs-to-state.ts deleted file mode 100644 index 81d75e1..0000000 --- a/src/shared/plugins/fs-plugin/fs-to-state.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CodebaseSearchAgent } from '../agents/codebase-search-agent' -import { FsVisitAgent } from '../agents/fs-visit-agent' -import { BaseToState } from '../base/base-to-state' -import { FsMentionType, type FsMention } from './types' - -export class FsToState extends BaseToState { - toMentionsState() { - return { - selectedFiles: this.getMentionDataByType(FsMentionType.File), - selectedFolders: this.getMentionDataByType(FsMentionType.Folder), - selectedTrees: this.getMentionDataByType(FsMentionType.Tree), - codeChunks: this.getMentionDataByType(FsMentionType.Code), - enableCodebaseAgent: this.isMentionExit(FsMentionType.Codebase), - editorErrors: this.getMentionDataByType(FsMentionType.Errors).flat() - } - } - - toAgentsState() { - return { - codeSnippets: this.getAgentOutputsByKey< - CodebaseSearchAgent, - 'codeSnippets' - >('codebaseSearch', 'codeSnippets').flat(), - visitedFiles: this.getAgentOutputsByKey( - 'fsVisit', - 'files' - ).flat() - } - } -} diff --git a/src/shared/plugins/fs-plugin/server/fs-server-plugin.ts b/src/shared/plugins/fs-plugin/server/fs-server-plugin.ts deleted file mode 100644 index 1aa58ef..0000000 --- a/src/shared/plugins/fs-plugin/server/fs-server-plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { - ServerPlugin, - ServerPluginContext -} from '@shared/plugins/base/server/server-plugin-context' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import type { FsPluginState } from '../types' -import { FsChatStrategyProvider } from './chat-strategy/fs-chat-strategy-provider' -import { FsServerUtilsProvider } from './fs-server-utils-provider' - -export class FsServerPlugin implements ServerPlugin { - id = PluginId.Fs - - version: string = pkg.version - - private context: ServerPluginContext | null = null - - async activate(context: ServerPluginContext): Promise { - this.context = context - - this.context.registerProvider( - 'chatStrategy', - () => new FsChatStrategyProvider() - ) - - this.context.registerProvider( - 'serverUtils', - () => new FsServerUtilsProvider() - ) - } - - deactivate(): void { - this.context = null - } -} diff --git a/src/shared/plugins/git-plugin/server/git-server-plugin.ts b/src/shared/plugins/git-plugin/server/git-server-plugin.ts deleted file mode 100644 index a286a63..0000000 --- a/src/shared/plugins/git-plugin/server/git-server-plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { - ServerPlugin, - ServerPluginContext -} from '@shared/plugins/base/server/server-plugin-context' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import type { GitPluginState } from '../types' -import { GitChatStrategyProvider } from './chat-strategy/git-chat-strategy-provider' -import { GitServerUtilsProvider } from './git-server-utils-provider' - -export class GitServerPlugin implements ServerPlugin { - id = PluginId.Git - - version: string = pkg.version - - private context: ServerPluginContext | null = null - - async activate(context: ServerPluginContext): Promise { - this.context = context - - this.context.registerProvider( - 'chatStrategy', - () => new GitChatStrategyProvider() - ) - - this.context.registerProvider( - 'serverUtils', - () => new GitServerUtilsProvider() - ) - } - - deactivate(): void { - this.context = null - } -} diff --git a/src/shared/plugins/base/base-to-state.ts b/src/shared/plugins/mentions/_base/base-to-state.ts similarity index 71% rename from src/shared/plugins/base/base-to-state.ts rename to src/shared/plugins/mentions/_base/base-to-state.ts index b5cb725..54b32ae 100644 --- a/src/shared/plugins/base/base-to-state.ts +++ b/src/shared/plugins/mentions/_base/base-to-state.ts @@ -2,16 +2,7 @@ import type { BaseAgent, GetAgentOutput } from '@extension/chat/strategies/base/base-agent' -import type { - Agent, - Conversation, - ConversationLog, - Mention -} from '@shared/entities' - -export type LogWithAgent = ConversationLog & { - agent?: A -} +import type { Agent, Conversation, Mention } from '@shared/entities' export abstract class BaseToState { mentions?: M[] @@ -22,7 +13,7 @@ export abstract class BaseToState { constructor(conversation?: Conversation) { this.mentions = (conversation?.mentions || []) as M[] - this.agents = (conversation?.agents || []) as Agent[] + this.agents = (conversation?.thinkAgents || []) as Agent[] this.conversation = conversation } @@ -30,10 +21,6 @@ export abstract class BaseToState { abstract toAgentsState(): unknown - toLogWithAgent(): LogWithAgent[] { - return toLogWithAgent(this.conversation) - } - getMentionDataByType( type: T ): Extract['data'][] { @@ -89,28 +76,6 @@ export abstract class BaseToState { } } -export const toLogWithAgent = ( - conversation: Conversation | undefined -): LogWithAgent[] => { - if (!conversation) return [] - - const idAgentMap = new Map() - conversation.agents?.forEach(agent => { - idAgentMap.set(agent.id, agent) - }) - - return ( - conversation.logs?.map(log => { - if (!log.agentId) return log - - return { - ...log, - agent: idAgentMap.get(log.agentId) - } - }) || [] - ) -} - export type GetMentionState> = ReturnType< T['toMentionsState'] > diff --git a/src/shared/plugins/mentions/_base/client/create-mention-client-plugin.ts b/src/shared/plugins/mentions/_base/client/create-mention-client-plugin.ts new file mode 100644 index 0000000..8a0761c --- /dev/null +++ b/src/shared/plugins/mentions/_base/client/create-mention-client-plugin.ts @@ -0,0 +1,37 @@ +import { MentionPluginId } from '../types' +import { useMentionPlugin } from './mention-client-plugin-context' +import type { MentionClientPluginProviderMap } from './mention-client-plugin-types' + +export interface MentionClientPlugin { + id: MentionPluginId + version: string + usePlugin: () => void +} + +export type MentionClientPluginSetupProps = { + registerProvider: ( + providerKey: K, + provider: () => MentionClientPluginProviderMap[K] + ) => void +} + +export const createMentionClientPlugin = (options: { + id: MentionPluginId + version: string + setup: (context: MentionClientPluginSetupProps) => void +}): MentionClientPlugin => ({ + id: options.id, + version: options.version, + usePlugin() { + const { registerProvider } = useMentionPlugin() + + options.setup({ + registerProvider: (key, provider) => + registerProvider( + options.id, + key as keyof MentionClientPluginProviderMap, + provider as () => MentionClientPluginProviderMap[keyof MentionClientPluginProviderMap] + ) + }) + } +}) diff --git a/src/shared/plugins/mentions/_base/client/mention-client-plugin-context.tsx b/src/shared/plugins/mentions/_base/client/mention-client-plugin-context.tsx new file mode 100644 index 0000000..4e3496a --- /dev/null +++ b/src/shared/plugins/mentions/_base/client/mention-client-plugin-context.tsx @@ -0,0 +1,88 @@ +import React, { createContext, useContext, useRef } from 'react' +import { ProviderUtils } from '@shared/plugins/_shared/provider-manager' + +import { MentionPluginId } from '../types' +import type { MentionClientPluginProviderMap } from './mention-client-plugin-types' + +type ProviderKey = keyof MentionClientPluginProviderMap + +type IdProviderMap = Record< + MentionPluginId, + () => MentionClientPluginProviderMap[ProviderKey] +> + +export interface MentionClientPluginContextValue { + registerProvider: ( + pluginId: MentionPluginId, + providerKey: K, + provider: () => MentionClientPluginProviderMap[K] + ) => void + getProviders: ( + providerKey: K + ) => MentionClientPluginProviderMap[K][] + mergeProviders: ( + providerKey: K + ) => MentionClientPluginProviderMap[K] | undefined +} + +const MentionClientPluginContext = + createContext(null) + +export const MentionClientPluginProvider: React.FC = ({ + children +}) => { + const providerKeyInfoMapRef = useRef({} as Record) + + const registerProvider = ( + pluginId: MentionPluginId, + providerKey: K, + provider: () => MentionClientPluginProviderMap[K] + ) => { + if (!providerKeyInfoMapRef.current[providerKey]) { + providerKeyInfoMapRef.current[providerKey] = {} as IdProviderMap + } + providerKeyInfoMapRef.current[providerKey]![pluginId] = provider + } + + const getProviders = ( + providerKey: K + ): MentionClientPluginProviderMap[K][] => { + const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || + {}) as Record MentionClientPluginProviderMap[K]> + + return ProviderUtils.getValues(idProviderMap) + } + + const mergeProviders = ( + providerKey: K + ): MentionClientPluginProviderMap[K] | undefined => { + const idProviderMap = (providerKeyInfoMapRef.current[providerKey] || + {}) as Record MentionClientPluginProviderMap[K]> + + const result = ProviderUtils.mergeAll(idProviderMap) + + return result + } + + return ( + + {children} + + ) +} + +export const useMentionPlugin = () => { + const context = useContext(MentionClientPluginContext) + if (!context) { + throw new Error( + 'useMentionPlugin must be used within MentionClientPluginProvider' + ) + } + return context +} diff --git a/src/shared/plugins/mentions/_base/client/mention-client-plugin-types.ts b/src/shared/plugins/mentions/_base/client/mention-client-plugin-types.ts new file mode 100644 index 0000000..68bf916 --- /dev/null +++ b/src/shared/plugins/mentions/_base/client/mention-client-plugin-types.ts @@ -0,0 +1,12 @@ +import type { Agent } from '@shared/entities' +import type { MentionOption } from '@webview/types/chat' + +export type UseMentionOptionsReturns = MentionOption[] + +export type CustomRenderLogPreviewProps = { + agent: Agent +} + +export type MentionClientPluginProviderMap = { + useMentionOptions: () => UseMentionOptionsReturns +} diff --git a/src/shared/plugins/mentions/_base/client/mention-client-plugins.ts b/src/shared/plugins/mentions/_base/client/mention-client-plugins.ts new file mode 100644 index 0000000..56f80bb --- /dev/null +++ b/src/shared/plugins/mentions/_base/client/mention-client-plugins.ts @@ -0,0 +1,21 @@ +import { DocMentionClientPlugin } from '@shared/plugins/mentions/doc-mention-plugin/client/doc-mention-client-plugin' +import { FsMentionClientPlugin } from '@shared/plugins/mentions/fs-mention-plugin/client/fs-mention-client-plugin' +import { GitMentionClientPlugin } from '@shared/plugins/mentions/git-mention-plugin/client/git-mention-client-plugin' +import { PromptSnippetMentionClientPlugin } from '@shared/plugins/mentions/prompt-snippet-mention-plugin/client/prompt-snippet-mention-client-plugin' +import { TerminalMentionClientPlugin } from '@shared/plugins/mentions/terminal-mention-plugin/client/terminal-mention-client-plugin' +import { WebMentionClientPlugin } from '@shared/plugins/mentions/web-mention-plugin/client/web-mention-client-plugin' + +import type { MentionClientPlugin } from './create-mention-client-plugin' + +export const createMentionClientPlugins = (): MentionClientPlugin[] => { + const plugins: MentionClientPlugin[] = [ + FsMentionClientPlugin, + DocMentionClientPlugin, + WebMentionClientPlugin, + GitMentionClientPlugin, + TerminalMentionClientPlugin, + PromptSnippetMentionClientPlugin + ] + + return plugins +} diff --git a/src/shared/plugins/base/server/create-provider-manager.ts b/src/shared/plugins/mentions/_base/server/create-mention-provider-manager.ts similarity index 68% rename from src/shared/plugins/base/server/create-provider-manager.ts rename to src/shared/plugins/mentions/_base/server/create-mention-provider-manager.ts index 68acd96..acf5f5a 100644 --- a/src/shared/plugins/base/server/create-provider-manager.ts +++ b/src/shared/plugins/mentions/_base/server/create-mention-provider-manager.ts @@ -1,15 +1,16 @@ import type { ActionRegister } from '@extension/registers/action-register' import type { StructuredTool } from '@langchain/core/tools' import type { ChatContext, Conversation, Mention } from '@shared/entities' +import { ProviderManager } from '@shared/plugins/_shared/provider-manager' import type { BaseStrategyOptions, ChatGraphNode, ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' -import { ProviderManager } from '../provider-manager' +import type { MentionPluginId } from '../types' -export interface ChatStrategyProvider { +export interface MentionChatStrategyProvider { buildSystemMessagePrompt?: (chatContext: ChatContext) => Promise buildContextMessagePrompt?: ( conversation: Conversation, @@ -38,7 +39,7 @@ export interface ChatStrategyProvider { export type RefreshMentionFn = (mention: Mention) => Mention -export interface ServerUtilsProvider { +export interface MentionServerUtilsProvider { createRefreshMentionFn: ( actionRegister: ActionRegister ) => Promise @@ -47,8 +48,14 @@ export interface ServerUtilsProvider { ) => Conversation } -export const createProviderManagers = () => +export const createMentionProviderManagers = () => ({ - chatStrategy: new ProviderManager(), - serverUtils: new ProviderManager() - }) as const satisfies Record> + chatStrategy: new ProviderManager< + MentionPluginId, + MentionChatStrategyProvider + >(), + serverUtils: new ProviderManager< + MentionPluginId, + MentionServerUtilsProvider + >() + }) as const satisfies Record> diff --git a/src/shared/plugins/mentions/_base/server/mention-server-plugin-context.ts b/src/shared/plugins/mentions/_base/server/mention-server-plugin-context.ts new file mode 100644 index 0000000..b9bb792 --- /dev/null +++ b/src/shared/plugins/mentions/_base/server/mention-server-plugin-context.ts @@ -0,0 +1,48 @@ +import type { MentionPluginId } from '../types' +import type { createMentionProviderManagers } from './create-mention-provider-manager' +import type { MentionServerPluginRegistry } from './mention-server-plugin-registry' + +export interface MentionServerPlugin { + id: MentionPluginId + version: string + dependencies?: MentionPluginId[] + activate(context: MentionServerPluginContext): Promise + deactivate?(): void +} + +interface MentionServerPluginContextOptions { + pluginId: MentionPluginId + registry: MentionServerPluginRegistry +} + +// eslint-disable-next-line unused-imports/no-unused-vars +export class MentionServerPluginContext { + private pluginId: MentionPluginId + + private registry: MentionServerPluginRegistry + + constructor(options: MentionServerPluginContextOptions) { + const { pluginId, registry } = options + this.pluginId = pluginId + this.registry = registry + } + + registerCommand(command: string, callback: (...args: any[]) => void): void { + this.registry.registerCommand(command, callback) + } + + executeCommand(command: string, ...args: any[]): void { + this.registry.executeCommand(command, ...args) + } + + registerProvider< + K extends keyof MentionServerPluginRegistry['providerManagers'] + >( + key: K, + provider: Parameters< + ReturnType[K]['register'] + >[1] + ): void { + this.registry.providerManagers[key].register(this.pluginId, provider as any) + } +} diff --git a/src/shared/plugins/mentions/_base/server/mention-server-plugin-registry.ts b/src/shared/plugins/mentions/_base/server/mention-server-plugin-registry.ts new file mode 100644 index 0000000..bc26dad --- /dev/null +++ b/src/shared/plugins/mentions/_base/server/mention-server-plugin-registry.ts @@ -0,0 +1,86 @@ +import { logger } from '@extension/logger' + +import type { MentionPluginId } from '../types' +import { createMentionProviderManagers } from './create-mention-provider-manager' +import { + MentionServerPluginContext, + type MentionServerPlugin +} from './mention-server-plugin-context' + +export class MentionServerPluginRegistry { + private plugins: Map = new Map() + + private pluginContexts: Map = + new Map() + + private commands: Map void> = new Map() + + providerManagers = createMentionProviderManagers() + + private checkDependencies(plugin: MentionServerPlugin): boolean { + return ( + !plugin.dependencies || + plugin.dependencies.every(depId => this.plugins.has(depId)) + ) + } + + async loadPlugin(_plugin: MentionServerPlugin): Promise { + let currentPluginId: MentionPluginId | null = null + + try { + const plugin = _plugin as MentionServerPlugin + currentPluginId = plugin.id + + if (!this.checkDependencies(plugin)) + throw new Error(`Dependencies not met for plugin ${currentPluginId}`) + + this.plugins.set(currentPluginId, plugin) + const context = new MentionServerPluginContext({ + registry: this, + pluginId: currentPluginId + }) + this.pluginContexts.set(currentPluginId, context) + await plugin.activate(context) + } catch (error: any) { + this.handleError(error, currentPluginId) + } finally { + currentPluginId = null + } + } + + private handleError(error: Error, pluginId: MentionPluginId | null): void { + logger.error(`Error in plugin ${pluginId}:`, error) + } + + registerCommand(command: string, callback: (...args: any[]) => void): void { + this.commands.set(command, callback) + } + + executeCommand(command: string, ...args: any[]): void { + const callback = this.commands.get(command) + if (callback) callback(...args) + } + + getPlugin( + pluginId: MentionPluginId + ): T | undefined { + return this.plugins.get(pluginId) as T + } + + async unloadPlugin(pluginId: MentionPluginId): Promise { + const plugin = this.plugins.get(pluginId) + if (plugin?.deactivate) { + await plugin.deactivate() + } + this.plugins.delete(pluginId) + Object.values(this.providerManagers).forEach(manager => + manager.unregister(pluginId) + ) + } + + async unloadAllPlugins(): Promise { + for (const pluginId of this.plugins.keys()) { + await this.unloadPlugin(pluginId) + } + } +} diff --git a/src/shared/plugins/mentions/_base/server/mention-server-plugins.ts b/src/shared/plugins/mentions/_base/server/mention-server-plugins.ts new file mode 100644 index 0000000..be7af5f --- /dev/null +++ b/src/shared/plugins/mentions/_base/server/mention-server-plugins.ts @@ -0,0 +1,21 @@ +import { DocMentionServerPlugin } from '@shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-plugin' +import { FsMentionServerPlugin } from '@shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-plugin' +import { GitMentionServerPlugin } from '@shared/plugins/mentions/git-mention-plugin/server/git-mention-server-plugin' +import { PromptSnippetMentionServerPlugin } from '@shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-plugin' +import { TerminalMentionServerPlugin } from '@shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-plugin' +import { WebMentionServerPlugin } from '@shared/plugins/mentions/web-mention-plugin/server/web-mention-server-plugin' + +import type { MentionServerPlugin } from './mention-server-plugin-context' + +export const createMentionServerPlugins = (): MentionServerPlugin[] => { + const plugins: MentionServerPlugin[] = [ + new FsMentionServerPlugin(), + new DocMentionServerPlugin(), + new WebMentionServerPlugin(), + new GitMentionServerPlugin(), + new TerminalMentionServerPlugin(), + new PromptSnippetMentionServerPlugin() + ] + + return plugins +} diff --git a/src/shared/plugins/mentions/_base/types.ts b/src/shared/plugins/mentions/_base/types.ts new file mode 100644 index 0000000..7ffdc14 --- /dev/null +++ b/src/shared/plugins/mentions/_base/types.ts @@ -0,0 +1,8 @@ +export enum MentionPluginId { + Fs = 'fsMentionPlugin', + Web = 'webMentionPlugin', + Doc = 'docMentionPlugin', + Git = 'gitMentionPlugin', + Terminal = 'terminalMentionPlugin', + PromptSnippet = 'promptSnippetMentionPlugin' +} diff --git a/src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx b/src/shared/plugins/mentions/doc-mention-plugin/client/doc-mention-client-plugin.tsx similarity index 77% rename from src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx rename to src/shared/plugins/mentions/doc-mention-plugin/client/doc-mention-client-plugin.tsx index 2acd229..26aac98 100644 --- a/src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx +++ b/src/shared/plugins/mentions/doc-mention-plugin/client/doc-mention-client-plugin.tsx @@ -1,37 +1,31 @@ import { GearIcon, IdCardIcon } from '@radix-ui/react-icons' -import type { UseMentionOptionsReturns } from '@shared/plugins/base/client/client-plugin-types' import { - createClientPlugin, - type SetupProps -} from '@shared/plugins/base/client/use-client-plugin' -import { PluginId } from '@shared/plugins/base/types' + createMentionClientPlugin, + type MentionClientPluginSetupProps +} from '@shared/plugins/mentions/_base/client/create-mention-client-plugin' +import type { UseMentionOptionsReturns } from '@shared/plugins/mentions/_base/client/mention-client-plugin-types' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' import { pkg } from '@shared/utils/pkg' import { useQuery } from '@tanstack/react-query' import { api } from '@webview/network/actions-api' import { type MentionOption } from '@webview/types/chat' import { useNavigate } from 'react-router' -import { DocMentionType, type DocPluginState } from '../types' -import { DocLogPreview } from './doc-log-preview' +import { DocMentionType } from '../types' -export const DocClientPlugin = createClientPlugin({ - id: PluginId.Doc, +export const DocMentionClientPlugin = createMentionClientPlugin({ + id: MentionPluginId.Doc, version: pkg.version, - getInitialState() { - return {} - }, - setup(props) { const { registerProvider } = props registerProvider('useMentionOptions', () => createUseMentionOptions(props)) - registerProvider('CustomRenderLogPreview', () => DocLogPreview) } }) const createUseMentionOptions = - (props: SetupProps) => (): UseMentionOptionsReturns => { + (props: MentionClientPluginSetupProps) => (): UseMentionOptionsReturns => { const navigate = useNavigate() const { data: docSites = [] } = useQuery({ diff --git a/src/shared/plugins/doc-plugin/doc-to-state.ts b/src/shared/plugins/mentions/doc-mention-plugin/doc-to-state.ts similarity index 56% rename from src/shared/plugins/doc-plugin/doc-to-state.ts rename to src/shared/plugins/mentions/doc-mention-plugin/doc-to-state.ts index 0e58dd6..564c3d4 100644 --- a/src/shared/plugins/doc-plugin/doc-to-state.ts +++ b/src/shared/plugins/mentions/doc-mention-plugin/doc-to-state.ts @@ -1,5 +1,7 @@ -import { DocRetrieverAgent } from '../agents/doc-retriever-agent' -import { BaseToState } from '../base/base-to-state' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import type { DocRetrieverAgent } from '@shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent' + +import { BaseToState } from '../_base/base-to-state' import { DocMentionType, type DocMention } from './types' export class DocToState extends BaseToState { @@ -14,7 +16,7 @@ export class DocToState extends BaseToState { relevantDocs: this.getAgentOutputsByKey< DocRetrieverAgent, 'relevantDocs' - >(DocRetrieverAgent.name, 'relevantDocs').flat() + >(AgentPluginId.DocRetriever, 'relevantDocs').flat() } } } diff --git a/src/shared/plugins/doc-plugin/server/chat-strategy/doc-chat-strategy-provider.ts b/src/shared/plugins/mentions/doc-mention-plugin/server/chat-strategy/doc-mention-chat-strategy-provider.ts similarity index 86% rename from src/shared/plugins/doc-plugin/server/chat-strategy/doc-chat-strategy-provider.ts rename to src/shared/plugins/mentions/doc-mention-plugin/server/chat-strategy/doc-mention-chat-strategy-provider.ts index f6904c5..1a571f8 100644 --- a/src/shared/plugins/doc-plugin/server/chat-strategy/doc-chat-strategy-provider.ts +++ b/src/shared/plugins/mentions/doc-mention-plugin/server/chat-strategy/doc-mention-chat-strategy-provider.ts @@ -1,19 +1,19 @@ import type { StructuredTool } from '@langchain/core/tools' import type { Conversation } from '@shared/entities' -import type { - GetAgentState, - GetMentionState -} from '@shared/plugins/base/base-to-state' -import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' import { createGraphNodeFromNodes, createToolsFromNodes -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' import type { BaseStrategyOptions, ChatGraphNode, ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import type { + GetAgentState, + GetMentionState +} from '@shared/plugins/mentions/_base/base-to-state' +import type { MentionChatStrategyProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { DocToState } from '../../doc-to-state' import { DocRetrieverNode } from './doc-retriever-node' @@ -24,7 +24,9 @@ interface ConversationWithStateProps { agentState: GetAgentState } -export class DocChatStrategyProvider implements ChatStrategyProvider { +export class DocMentionChatStrategyProvider + implements MentionChatStrategyProvider +{ private createConversationWithStateProps( conversation: Conversation ): ConversationWithStateProps { diff --git a/src/shared/plugins/doc-plugin/server/chat-strategy/doc-retriever-node.ts b/src/shared/plugins/mentions/doc-mention-plugin/server/chat-strategy/doc-retriever-node.ts similarity index 83% rename from src/shared/plugins/doc-plugin/server/chat-strategy/doc-retriever-node.ts rename to src/shared/plugins/mentions/doc-mention-plugin/server/chat-strategy/doc-retriever-node.ts index 5705b91..dd2be6c 100644 --- a/src/shared/plugins/doc-plugin/server/chat-strategy/doc-retriever-node.ts +++ b/src/shared/plugins/mentions/doc-mention-plugin/server/chat-strategy/doc-retriever-node.ts @@ -1,10 +1,10 @@ -import { DocRetrieverAgent } from '@shared/plugins/agents/doc-retriever-agent' import { BaseNode, ChatGraphState, dispatchBaseGraphState, type BaseStrategyOptions -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import { DocRetrieverAgent } from '@shared/plugins/agents/doc-retriever-agent-plugin/server/doc-retriever-agent' import { DocToState } from '../../doc-to-state' @@ -37,9 +37,7 @@ export class DocRetrieverNode extends BaseNode< if (!toolCallsResults.agents.length) return {} - const newConversation = state.newConversations.at(-1)! - this.addAgentsToConversation(newConversation, toolCallsResults.agents) - this.addLogsToConversation(newConversation, toolCallsResults.logs) + this.addAgentsToLastHumanAndNewConversation(state, toolCallsResults.agents) dispatchBaseGraphState({ chatContext: state.chatContext, diff --git a/src/shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-plugin.ts b/src/shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-plugin.ts new file mode 100644 index 0000000..68a0d06 --- /dev/null +++ b/src/shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-plugin.ts @@ -0,0 +1,35 @@ +import type { + MentionServerPlugin, + MentionServerPluginContext +} from '@shared/plugins/mentions/_base/server/mention-server-plugin-context' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { DocMentionChatStrategyProvider } from './chat-strategy/doc-mention-chat-strategy-provider' +import { DocMentionServerUtilsProvider } from './doc-mention-server-utils-provider' + +export class DocMentionServerPlugin implements MentionServerPlugin { + id = MentionPluginId.Doc + + version: string = pkg.version + + private context: MentionServerPluginContext | null = null + + async activate(context: MentionServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'chatStrategy', + () => new DocMentionChatStrategyProvider() + ) + + this.context.registerProvider( + 'serverUtils', + () => new DocMentionServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/doc-plugin/server/doc-server-utils-provider.ts b/src/shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-utils-provider.ts similarity index 80% rename from src/shared/plugins/doc-plugin/server/doc-server-utils-provider.ts rename to src/shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-utils-provider.ts index 45e2b58..6faf2ce 100644 --- a/src/shared/plugins/doc-plugin/server/doc-server-utils-provider.ts +++ b/src/shared/plugins/mentions/doc-mention-plugin/server/doc-mention-server-utils-provider.ts @@ -1,10 +1,12 @@ import type { ActionRegister } from '@extension/registers/action-register' import type { Mention } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { DocMentionType } from '../types' -export class DocServerUtilsProvider implements ServerUtilsProvider { +export class DocMentionServerUtilsProvider + implements MentionServerUtilsProvider +{ async createRefreshMentionFn(actionRegister: ActionRegister) { const docSites = await actionRegister.actions().server.doc.getDocSites({ actionParams: {} diff --git a/src/shared/plugins/mentions/doc-mention-plugin/types.ts b/src/shared/plugins/mentions/doc-mention-plugin/types.ts new file mode 100644 index 0000000..8b9ad08 --- /dev/null +++ b/src/shared/plugins/mentions/doc-mention-plugin/types.ts @@ -0,0 +1,11 @@ +import type { Mention } from '@shared/entities' + +import { MentionPluginId } from '../_base/types' + +export enum DocMentionType { + Docs = `${MentionPluginId.Doc}#docs`, + Doc = `${MentionPluginId.Doc}#doc`, + DocSetting = `${MentionPluginId.Doc}#doc-setting` +} + +export type DocMention = Mention diff --git a/src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx b/src/shared/plugins/mentions/fs-mention-plugin/client/fs-mention-client-plugin.tsx similarity index 91% rename from src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx rename to src/shared/plugins/mentions/fs-mention-plugin/client/fs-mention-client-plugin.tsx index 00c5338..c0ae83c 100644 --- a/src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx +++ b/src/shared/plugins/mentions/fs-mention-plugin/client/fs-mention-client-plugin.tsx @@ -4,12 +4,12 @@ import { CubeIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons' -import type { UseMentionOptionsReturns } from '@shared/plugins/base/client/client-plugin-types' import { - createClientPlugin, - type SetupProps -} from '@shared/plugins/base/client/use-client-plugin' -import { PluginId } from '@shared/plugins/base/types' + createMentionClientPlugin, + type MentionClientPluginSetupProps +} from '@shared/plugins/mentions/_base/client/create-mention-client-plugin' +import type { UseMentionOptionsReturns } from '@shared/plugins/mentions/_base/client/mention-client-plugin-types' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' import { pkg } from '@shared/utils/pkg' import { useQuery } from '@tanstack/react-query' import { FileIcon as FileIcon2 } from '@webview/components/file-icon' @@ -18,30 +18,24 @@ import { SearchSortStrategy, type MentionOption } from '@webview/types/chat' import { getFileNameFromPath } from '@webview/utils/path' import { ChevronRightIcon, FileIcon, FolderTreeIcon } from 'lucide-react' -import { FsMentionType, type FsPluginState, type TreeInfo } from '../types' -import { FsLogPreview } from './fs-log-preview' +import { FsMentionType, type TreeInfo } from '../types' import { MentionFilePreview } from './mention-file-preview' import { MentionFolderPreview } from './mention-folder-preview' import { MentionTreePreview } from './mention-tree-preview' -export const FsClientPlugin = createClientPlugin({ - id: PluginId.Fs, +export const FsMentionClientPlugin = createMentionClientPlugin({ + id: MentionPluginId.Fs, version: pkg.version, - getInitialState() { - return {} - }, - setup(props) { const { registerProvider } = props registerProvider('useMentionOptions', () => createUseMentionOptions(props)) - registerProvider('CustomRenderLogPreview', () => FsLogPreview) } }) const createUseMentionOptions = - (props: SetupProps) => (): UseMentionOptionsReturns => { + (props: MentionClientPluginSetupProps) => (): UseMentionOptionsReturns => { const { data: files = [] } = useQuery({ queryKey: ['realtime', 'files'], queryFn: () => diff --git a/src/shared/plugins/fs-plugin/client/mention-file-preview.tsx b/src/shared/plugins/mentions/fs-mention-plugin/client/mention-file-preview.tsx similarity index 100% rename from src/shared/plugins/fs-plugin/client/mention-file-preview.tsx rename to src/shared/plugins/mentions/fs-mention-plugin/client/mention-file-preview.tsx diff --git a/src/shared/plugins/fs-plugin/client/mention-folder-preview.tsx b/src/shared/plugins/mentions/fs-mention-plugin/client/mention-folder-preview.tsx similarity index 100% rename from src/shared/plugins/fs-plugin/client/mention-folder-preview.tsx rename to src/shared/plugins/mentions/fs-mention-plugin/client/mention-folder-preview.tsx diff --git a/src/shared/plugins/fs-plugin/client/mention-tree-preview.tsx b/src/shared/plugins/mentions/fs-mention-plugin/client/mention-tree-preview.tsx similarity index 100% rename from src/shared/plugins/fs-plugin/client/mention-tree-preview.tsx rename to src/shared/plugins/mentions/fs-mention-plugin/client/mention-tree-preview.tsx diff --git a/src/shared/plugins/mentions/fs-mention-plugin/fs-to-state.ts b/src/shared/plugins/mentions/fs-mention-plugin/fs-to-state.ts new file mode 100644 index 0000000..0caba82 --- /dev/null +++ b/src/shared/plugins/mentions/fs-mention-plugin/fs-to-state.ts @@ -0,0 +1,34 @@ +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import type { CodebaseSearchAgent } from '@shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent' +import type { ReadFilesAgent } from '@shared/plugins/agents/read-files-agent-plugin/server/read-files-agent' + +import { BaseToState } from '../_base/base-to-state' +import { FsMentionType, type FsMention } from './types' + +export class FsToState extends BaseToState { + toMentionsState() { + return { + selectedFiles: this.getMentionDataByType(FsMentionType.File), + selectedFolders: this.getMentionDataByType(FsMentionType.Folder), + selectedTrees: this.getMentionDataByType(FsMentionType.Tree), + codeChunks: this.getMentionDataByType(FsMentionType.Code), + enableCodebaseAgent: this.isMentionExit(FsMentionType.Codebase), + editorErrors: this.getMentionDataByType(FsMentionType.Errors).flat() + } + } + + toAgentsState() { + return { + codeSnippets: [ + ...(this.getAgentOutputsByKey( + AgentPluginId.CodebaseSearch, + 'codeSnippets' + ).flat() || []), + ...(this.getAgentOutputsByKey( + AgentPluginId.ReadFiles, + 'codeSnippets' + ).flat() || []) + ] + } + } +} diff --git a/src/shared/plugins/fs-plugin/server/chat-strategy/codebase-search-node.ts b/src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/codebase-search-node.ts similarity index 82% rename from src/shared/plugins/fs-plugin/server/chat-strategy/codebase-search-node.ts rename to src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/codebase-search-node.ts index 535ae12..7885c3c 100644 --- a/src/shared/plugins/fs-plugin/server/chat-strategy/codebase-search-node.ts +++ b/src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/codebase-search-node.ts @@ -1,9 +1,9 @@ -import { CodebaseSearchAgent } from '@shared/plugins/agents/codebase-search-agent' import { BaseNode, dispatchBaseGraphState, type ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import { CodebaseSearchAgent } from '@shared/plugins/agents/codebase-search-agent-plugin/server/codebase-search-agent' import { FsToState } from '../../fs-to-state' @@ -33,9 +33,7 @@ export class CodebaseSearchNode extends BaseNode { if (!toolCallsResults.agents.length) return {} - const newConversation = state.newConversations.at(-1)! - this.addAgentsToConversation(newConversation, toolCallsResults.agents) - this.addLogsToConversation(newConversation, toolCallsResults.logs) + this.addAgentsToLastHumanAndNewConversation(state, toolCallsResults.agents) dispatchBaseGraphState({ chatContext: state.chatContext, diff --git a/src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts b/src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/fs-mention-chat-strategy-provider.ts similarity index 95% rename from src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts rename to src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/fs-mention-chat-strategy-provider.ts index 40e6d92..53476ba 100644 --- a/src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts +++ b/src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/fs-mention-chat-strategy-provider.ts @@ -14,25 +14,25 @@ import { logger } from '@extension/logger' import { getWorkspaceFolder } from '@extension/utils' import type { StructuredTool } from '@langchain/core/tools' import type { ChatContext, Conversation } from '@shared/entities' -import type { - GetAgentState, - GetMentionState -} from '@shared/plugins/base/base-to-state' -import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' +import { mergeCodeSnippets } from '@shared/plugins/_shared/merge-code-snippets' import { createGraphNodeFromNodes, createToolsFromNodes, type BaseStrategyOptions, type ChatGraphNode, type ChatGraphState -} from '@shared/plugins/base/strategies' -import { mergeCodeSnippets } from '@shared/plugins/fs-plugin/server/merge-code-snippets' +} from '@shared/plugins/_shared/strategies' +import type { + GetAgentState, + GetMentionState +} from '@shared/plugins/mentions/_base/base-to-state' +import type { MentionChatStrategyProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { removeDuplicates } from '@shared/utils/common' import { FsToState } from '../../fs-to-state' import { type EditorError } from '../../types' import { CodebaseSearchNode } from './codebase-search-node' -import { FsVisitNode } from './fs-visit-node' +import { ReadFilesNode } from './read-files-node' interface BuildFilePromptsResult { selectedFilesPrompt: string @@ -47,7 +47,9 @@ interface ConversationWithStateProps { agentState: GetAgentState } -export class FsChatStrategyProvider implements ChatStrategyProvider { +export class FsMentionChatStrategyProvider + implements MentionChatStrategyProvider +{ private createConversationWithStateProps( conversation: Conversation ): ConversationWithStateProps { @@ -129,7 +131,7 @@ ${codeChunksPrompt}` state: ChatGraphState ): Promise { return await createToolsFromNodes({ - nodeClasses: [CodebaseSearchNode, FsVisitNode], + nodeClasses: [CodebaseSearchNode, ReadFilesNode], strategyOptions, state }) @@ -139,7 +141,7 @@ ${codeChunksPrompt}` strategyOptions: BaseStrategyOptions ): Promise { return await createGraphNodeFromNodes({ - nodeClasses: [CodebaseSearchNode, FsVisitNode], + nodeClasses: [CodebaseSearchNode, ReadFilesNode], strategyOptions }) } diff --git a/src/shared/plugins/fs-plugin/server/chat-strategy/fs-visit-node.ts b/src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/read-files-node.ts similarity index 58% rename from src/shared/plugins/fs-plugin/server/chat-strategy/fs-visit-node.ts rename to src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/read-files-node.ts index 58e2120..a4ec2c0 100644 --- a/src/shared/plugins/fs-plugin/server/chat-strategy/fs-visit-node.ts +++ b/src/shared/plugins/mentions/fs-mention-plugin/server/chat-strategy/read-files-node.ts @@ -1,15 +1,15 @@ -import { FsVisitAgent } from '@shared/plugins/agents/fs-visit-agent' import { BaseNode, dispatchBaseGraphState, type ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import { ReadFilesAgent } from '@shared/plugins/agents/read-files-agent-plugin/server/read-files-agent' -export class FsVisitNode extends BaseNode { +export class ReadFilesNode extends BaseNode { onInit() { - this.registerAgentConfig(FsVisitAgent.name, state => + this.registerAgentConfig(ReadFilesAgent.name, state => this.createAgentConfig({ - agentClass: FsVisitAgent, + agentClass: ReadFilesAgent, agentContext: { state, strategyOptions: this.context.strategyOptions, @@ -21,14 +21,12 @@ export class FsVisitNode extends BaseNode { async execute(state: ChatGraphState) { const toolCallsResults = await this.executeAgentTool(state, { - agentClass: FsVisitAgent + agentClass: ReadFilesAgent }) if (!toolCallsResults.agents.length) return {} - const newConversation = state.newConversations.at(-1)! - this.addAgentsToConversation(newConversation, toolCallsResults.agents) - this.addLogsToConversation(newConversation, toolCallsResults.logs) + this.addAgentsToLastHumanAndNewConversation(state, toolCallsResults.agents) dispatchBaseGraphState({ chatContext: state.chatContext, diff --git a/src/shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-plugin.ts b/src/shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-plugin.ts new file mode 100644 index 0000000..13120fb --- /dev/null +++ b/src/shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-plugin.ts @@ -0,0 +1,35 @@ +import type { + MentionServerPlugin, + MentionServerPluginContext +} from '@shared/plugins/mentions/_base/server/mention-server-plugin-context' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { FsMentionChatStrategyProvider } from './chat-strategy/fs-mention-chat-strategy-provider' +import { FsMentionServerUtilsProvider } from './fs-mention-server-utils-provider' + +export class FsMentionServerPlugin implements MentionServerPlugin { + id = MentionPluginId.Fs + + version: string = pkg.version + + private context: MentionServerPluginContext | null = null + + async activate(context: MentionServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'chatStrategy', + () => new FsMentionChatStrategyProvider() + ) + + this.context.registerProvider( + 'serverUtils', + () => new FsMentionServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/fs-plugin/server/fs-server-utils-provider.ts b/src/shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-utils-provider.ts similarity index 91% rename from src/shared/plugins/fs-plugin/server/fs-server-utils-provider.ts rename to src/shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-utils-provider.ts index 8353fcf..b82136e 100644 --- a/src/shared/plugins/fs-plugin/server/fs-server-utils-provider.ts +++ b/src/shared/plugins/mentions/fs-mention-plugin/server/fs-mention-server-utils-provider.ts @@ -1,11 +1,13 @@ import type { FileInfo, FolderInfo } from '@extension/file-utils/traverse-fs' import type { ActionRegister } from '@extension/registers/action-register' import type { Mention } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { FsMentionType, type FsMention, type TreeInfo } from '../types' -export class FsServerUtilsProvider implements ServerUtilsProvider { +export class FsMentionServerUtilsProvider + implements MentionServerUtilsProvider +{ async createRefreshMentionFn(actionRegister: ActionRegister) { const files = await actionRegister .actions() diff --git a/src/shared/plugins/fs-plugin/types.ts b/src/shared/plugins/mentions/fs-mention-plugin/types.ts similarity index 68% rename from src/shared/plugins/fs-plugin/types.ts rename to src/shared/plugins/mentions/fs-mention-plugin/types.ts index c0396b0..38c9433 100644 --- a/src/shared/plugins/fs-plugin/types.ts +++ b/src/shared/plugins/mentions/fs-mention-plugin/types.ts @@ -1,18 +1,19 @@ import type { FileInfo, FolderInfo } from '@extension/file-utils/traverse-fs' import type { Mention } from '@shared/entities' +import type { CodeSnippet } from '@shared/plugins/agents/codebase-search-agent-plugin/types' -import { PluginId } from '../base/types' +import { MentionPluginId } from '../_base/types' export enum FsMentionType { - Files = `${PluginId.Fs}#files`, - File = `${PluginId.Fs}#file`, - Folders = `${PluginId.Fs}#folders`, - Folder = `${PluginId.Fs}#folder`, - Trees = `${PluginId.Fs}#trees`, - Tree = `${PluginId.Fs}#tree`, - Code = `${PluginId.Fs}#code`, - Codebase = `${PluginId.Fs}#codebase`, - Errors = `${PluginId.Fs}#errors` + Files = `${MentionPluginId.Fs}#files`, + File = `${MentionPluginId.Fs}#file`, + Folders = `${MentionPluginId.Fs}#folders`, + Folder = `${MentionPluginId.Fs}#folder`, + Trees = `${MentionPluginId.Fs}#trees`, + Tree = `${MentionPluginId.Fs}#tree`, + Code = `${MentionPluginId.Fs}#code`, + Codebase = `${MentionPluginId.Fs}#codebase`, + Errors = `${MentionPluginId.Fs}#errors` } export type FileMention = Mention @@ -30,17 +31,6 @@ export type FsMention = | CodebaseMention | ErrorMention -export interface CodeSnippet { - fileHash: string - relativePath: string - fullPath: string - startLine: number - startCharacter: number - endLine: number - endCharacter: number - code: string -} - export interface CodeChunk { code: string language: string @@ -66,5 +56,3 @@ export interface TreeInfo { treeString: string // markdown tree string, for user reading listString: string // markdown list string, for ai reading } - -export interface FsPluginState {} diff --git a/src/shared/plugins/git-plugin/client/git-client-plugin.tsx b/src/shared/plugins/mentions/git-mention-plugin/client/git-mention-client-plugin.tsx similarity index 81% rename from src/shared/plugins/git-plugin/client/git-client-plugin.tsx rename to src/shared/plugins/mentions/git-mention-plugin/client/git-mention-client-plugin.tsx index 04c3b8e..db9fd8c 100644 --- a/src/shared/plugins/git-plugin/client/git-client-plugin.tsx +++ b/src/shared/plugins/mentions/git-mention-plugin/client/git-mention-client-plugin.tsx @@ -1,25 +1,21 @@ import { CommitIcon, MaskOffIcon, TransformIcon } from '@radix-ui/react-icons' -import type { UseMentionOptionsReturns } from '@shared/plugins/base/client/client-plugin-types' import { - createClientPlugin, - type SetupProps -} from '@shared/plugins/base/client/use-client-plugin' -import { PluginId } from '@shared/plugins/base/types' + createMentionClientPlugin, + type MentionClientPluginSetupProps +} from '@shared/plugins/mentions/_base/client/create-mention-client-plugin' +import type { UseMentionOptionsReturns } from '@shared/plugins/mentions/_base/client/mention-client-plugin-types' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' import { pkg } from '@shared/utils/pkg' import { useQuery } from '@tanstack/react-query' import { api } from '@webview/network/actions-api' import { type MentionOption } from '@webview/types/chat' -import { GitCommit, GitMentionType, GitPluginState } from '../types' +import { GitCommit, GitMentionType } from '../types' -export const GitClientPlugin = createClientPlugin({ - id: PluginId.Git, +export const GitMentionClientPlugin = createMentionClientPlugin({ + id: MentionPluginId.Git, version: pkg.version, - getInitialState() { - return {} - }, - setup(props) { const { registerProvider } = props @@ -28,7 +24,7 @@ export const GitClientPlugin = createClientPlugin({ }) const createUseMentionOptions = - (props: SetupProps) => (): UseMentionOptionsReturns => { + (props: MentionClientPluginSetupProps) => (): UseMentionOptionsReturns => { const { data: gitCommits = [] } = useQuery({ queryKey: ['realtime', 'git-commits'], queryFn: () => diff --git a/src/shared/plugins/git-plugin/git-to-state.ts b/src/shared/plugins/mentions/git-mention-plugin/git-to-state.ts similarity index 89% rename from src/shared/plugins/git-plugin/git-to-state.ts rename to src/shared/plugins/mentions/git-mention-plugin/git-to-state.ts index e5038a8..42f7805 100644 --- a/src/shared/plugins/git-plugin/git-to-state.ts +++ b/src/shared/plugins/mentions/git-mention-plugin/git-to-state.ts @@ -1,4 +1,4 @@ -import { BaseToState } from '../base/base-to-state' +import { BaseToState } from '../_base/base-to-state' import { GitMentionType, type GitMention } from './types' export class GitToState extends BaseToState { diff --git a/src/shared/plugins/git-plugin/server/chat-strategy/git-chat-strategy-provider.ts b/src/shared/plugins/mentions/git-mention-plugin/server/chat-strategy/git-mention-chat-strategy-provider.ts similarity index 91% rename from src/shared/plugins/git-plugin/server/chat-strategy/git-chat-strategy-provider.ts rename to src/shared/plugins/mentions/git-mention-plugin/server/chat-strategy/git-mention-chat-strategy-provider.ts index b0b8bda..dd2b61f 100644 --- a/src/shared/plugins/git-plugin/server/chat-strategy/git-chat-strategy-provider.ts +++ b/src/shared/plugins/mentions/git-mention-plugin/server/chat-strategy/git-mention-chat-strategy-provider.ts @@ -2,8 +2,8 @@ import type { Conversation } from '@shared/entities' import type { GetAgentState, GetMentionState -} from '@shared/plugins/base/base-to-state' -import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' +} from '@shared/plugins/mentions/_base/base-to-state' +import type { MentionChatStrategyProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { removeDuplicates } from '@shared/utils/common' import { GitToState } from '../../git-to-state' @@ -15,7 +15,9 @@ interface ConversationWithStateProps { agentState: GetAgentState } -export class GitChatStrategyProvider implements ChatStrategyProvider { +export class GitMentionChatStrategyProvider + implements MentionChatStrategyProvider +{ private createConversationWithStateProps( conversation: Conversation ): ConversationWithStateProps { diff --git a/src/shared/plugins/mentions/git-mention-plugin/server/git-mention-server-plugin.ts b/src/shared/plugins/mentions/git-mention-plugin/server/git-mention-server-plugin.ts new file mode 100644 index 0000000..5c80d56 --- /dev/null +++ b/src/shared/plugins/mentions/git-mention-plugin/server/git-mention-server-plugin.ts @@ -0,0 +1,35 @@ +import type { + MentionServerPlugin, + MentionServerPluginContext +} from '@shared/plugins/mentions/_base/server/mention-server-plugin-context' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { GitMentionChatStrategyProvider } from './chat-strategy/git-mention-chat-strategy-provider' +import { GitMentionServerUtilsProvider } from './git-mention-server-utils-provider' + +export class GitMentionServerPlugin implements MentionServerPlugin { + id = MentionPluginId.Git + + version: string = pkg.version + + private context: MentionServerPluginContext | null = null + + async activate(context: MentionServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'chatStrategy', + () => new GitMentionChatStrategyProvider() + ) + + this.context.registerProvider( + 'serverUtils', + () => new GitMentionServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/git-plugin/server/git-server-utils-provider.ts b/src/shared/plugins/mentions/git-mention-plugin/server/git-mention-server-utils-provider.ts similarity index 82% rename from src/shared/plugins/git-plugin/server/git-server-utils-provider.ts rename to src/shared/plugins/mentions/git-mention-plugin/server/git-mention-server-utils-provider.ts index 1d2e000..c4c6a9c 100644 --- a/src/shared/plugins/git-plugin/server/git-server-utils-provider.ts +++ b/src/shared/plugins/mentions/git-mention-plugin/server/git-mention-server-utils-provider.ts @@ -1,10 +1,12 @@ import type { ActionRegister } from '@extension/registers/action-register' import type { Mention } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { GitMentionType } from '../types' -export class GitServerUtilsProvider implements ServerUtilsProvider { +export class GitMentionServerUtilsProvider + implements MentionServerUtilsProvider +{ async createRefreshMentionFn(actionRegister: ActionRegister) { const commits = await actionRegister .actions() diff --git a/src/shared/plugins/git-plugin/types.ts b/src/shared/plugins/mentions/git-mention-plugin/types.ts similarity index 82% rename from src/shared/plugins/git-plugin/types.ts rename to src/shared/plugins/mentions/git-mention-plugin/types.ts index 642e8a9..52b3d46 100644 --- a/src/shared/plugins/git-plugin/types.ts +++ b/src/shared/plugins/mentions/git-mention-plugin/types.ts @@ -1,12 +1,12 @@ import type { Mention } from '@shared/entities' -import { PluginId } from '../base/types' +import { MentionPluginId } from '../_base/types' export enum GitMentionType { - Git = `${PluginId.Git}#git`, - GitCommit = `${PluginId.Git}#git-commit`, - GitDiff = `${PluginId.Git}#git-diff`, - GitPR = `${PluginId.Git}#git-pr` + Git = `${MentionPluginId.Git}#git`, + GitCommit = `${MentionPluginId.Git}#git-commit`, + GitDiff = `${MentionPluginId.Git}#git-diff`, + GitPR = `${MentionPluginId.Git}#git-pr` } export type GitCommitMention = Mention @@ -60,5 +60,3 @@ export interface GitCommit { author: string date: string } - -export interface GitPluginState {} diff --git a/src/shared/plugins/prompt-snippet-plugin/client/mention-prompt-snippet-preview.tsx b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/client/mention-prompt-snippet-preview.tsx similarity index 93% rename from src/shared/plugins/prompt-snippet-plugin/client/mention-prompt-snippet-preview.tsx rename to src/shared/plugins/mentions/prompt-snippet-mention-plugin/client/mention-prompt-snippet-preview.tsx index b29faa9..e900a88 100644 --- a/src/shared/plugins/prompt-snippet-plugin/client/mention-prompt-snippet-preview.tsx +++ b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/client/mention-prompt-snippet-preview.tsx @@ -8,7 +8,7 @@ import type { SFC } from '@shared/types/common' import { ChatInput, ChatInputMode, - type ChatInputRef + type ChatInputEditorRef } from '@webview/components/chat/editor/chat-input' import { ChatProviders } from '@webview/contexts/providers' import type { MentionOption } from '@webview/types/chat' @@ -33,7 +33,7 @@ export const MentionPromptSnippetPreview: SFC< const PreviewPromptSnippet: React.FC<{ promptSnippet: PromptSnippet }> = ({ promptSnippet }) => { - const chatInputRef = useRef(null) + const editorRef = useRef(null) const [context, setContext] = useImmer( new ChatContextEntity({ conversations: [ @@ -68,13 +68,13 @@ const PreviewPromptSnippet: React.FC<{ promptSnippet: PromptSnippet }> = ({ }) setTimeout(() => { - chatInputRef.current?.reInitializeEditor() + editorRef.current?.reInitializeEditor() }, 0) }, [promptSnippet]) return ( ({ - id: PluginId.PromptSnippet, - version: pkg.version, - - getInitialState() { - return {} - }, +export const PromptSnippetMentionClientPlugin = createMentionClientPlugin({ + id: MentionPluginId.PromptSnippet, + version: pkg.version, - setup(props) { - const { registerProvider } = props + setup(props) { + const { registerProvider } = props - registerProvider('useMentionOptions', () => - createUseMentionOptions(props) - ) - } - }) + registerProvider('useMentionOptions', () => createUseMentionOptions(props)) + } +}) const createUseMentionOptions = - (props: SetupProps) => - (): UseMentionOptionsReturns => { + (props: MentionClientPluginSetupProps) => (): UseMentionOptionsReturns => { const navigate = useNavigate() const { data: snippets = [] } = useQuery({ queryKey: ['realtime', 'promptSnippets'], @@ -51,9 +40,7 @@ const createUseMentionOptions = const snippetMentionOptions: MentionOption[] = snippets.map(snippet => { const label = snippet.title - const textContent = getAllTextFromLangchainMessageContents( - snippet.contents - ) + const textContent = getAllTextFromConversationContents(snippet.contents) return { id: `${PromptSnippetMentionType.PromptSnippet}#${snippet.id}`, diff --git a/src/shared/plugins/prompt-snippet-plugin/prompt-snippet-to-state.ts b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/prompt-snippet-to-state.ts similarity index 86% rename from src/shared/plugins/prompt-snippet-plugin/prompt-snippet-to-state.ts rename to src/shared/plugins/mentions/prompt-snippet-mention-plugin/prompt-snippet-to-state.ts index 1a72d24..46e16e9 100644 --- a/src/shared/plugins/prompt-snippet-plugin/prompt-snippet-to-state.ts +++ b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/prompt-snippet-to-state.ts @@ -1,4 +1,4 @@ -import { BaseToState } from '../base/base-to-state' +import { BaseToState } from '../_base/base-to-state' import { PromptSnippetMentionType, type PromptSnippetMention } from './types' export class PromptSnippetToState extends BaseToState { diff --git a/src/shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-plugin.ts b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-plugin.ts new file mode 100644 index 0000000..19d623c --- /dev/null +++ b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-plugin.ts @@ -0,0 +1,29 @@ +import type { + MentionServerPlugin, + MentionServerPluginContext +} from '@shared/plugins/mentions/_base/server/mention-server-plugin-context' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { PromptSnippetMentionServerUtilsProvider } from './prompt-snippet-mention-server-utils-provider' + +export class PromptSnippetMentionServerPlugin implements MentionServerPlugin { + id = MentionPluginId.PromptSnippet + + version: string = pkg.version + + private context: MentionServerPluginContext | null = null + + async activate(context: MentionServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'serverUtils', + () => new PromptSnippetMentionServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-utils-provider.ts b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-utils-provider.ts similarity index 88% rename from src/shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-utils-provider.ts rename to src/shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-utils-provider.ts index c779277..a716318 100644 --- a/src/shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-utils-provider.ts +++ b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/server/prompt-snippet-mention-server-utils-provider.ts @@ -2,17 +2,19 @@ import type { ActionRegister } from '@extension/registers/action-register' import { ConversationEntity, type Conversation, - type LangchainMessageContents, + type ConversationContents, type Mention, type PromptSnippet } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' +import { mergeConversationContents } from '@shared/utils/chat-context-helper/common/merge-conversation-contents' import { removeDuplicates } from '@shared/utils/common' -import { mergeLangchainMessageContents } from '@shared/utils/merge-langchain-message-contents' import { PromptSnippetMentionType, type PromptSnippetMention } from '../types' -export class PromptSnippetServerUtilsProvider implements ServerUtilsProvider { +export class PromptSnippetMentionServerUtilsProvider + implements MentionServerUtilsProvider +{ async createRefreshMentionFn(actionRegister: ActionRegister) { const snippets = await actionRegister .actions() @@ -48,7 +50,7 @@ export class PromptSnippetServerUtilsProvider implements ServerUtilsProvider { private getMergedSnippetsInfo(relativeMentions: Mention[]) { const result = { mentions: [] as Mention[], - contents: [] as LangchainMessageContents, + contents: [] as ConversationContents, state: new ConversationEntity().entity.state } @@ -99,7 +101,7 @@ export class PromptSnippetServerUtilsProvider implements ServerUtilsProvider { ...conversation, mentions: [...mentions, ...conversation.mentions], contents: [ - ...mergeLangchainMessageContents(contents), + ...mergeConversationContents(contents), ...conversation.contents ], state: { diff --git a/src/shared/plugins/prompt-snippet-plugin/types.ts b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/types.ts similarity index 83% rename from src/shared/plugins/prompt-snippet-plugin/types.ts rename to src/shared/plugins/mentions/prompt-snippet-mention-plugin/types.ts index ba6a206..6c5b66b 100644 --- a/src/shared/plugins/prompt-snippet-plugin/types.ts +++ b/src/shared/plugins/mentions/prompt-snippet-mention-plugin/types.ts @@ -1,9 +1,5 @@ import type { PromptSnippet } from '@shared/entities' -export interface PromptSnippetPluginState { - snippets?: PromptSnippet[] -} - export enum PromptSnippetMentionType { PromptSnippet = 'promptSnippet', PromptSnippets = 'promptSnippets', diff --git a/src/shared/plugins/terminal-plugin/client/mention-terminal-preview.tsx b/src/shared/plugins/mentions/terminal-mention-plugin/client/mention-terminal-preview.tsx similarity index 100% rename from src/shared/plugins/terminal-plugin/client/mention-terminal-preview.tsx rename to src/shared/plugins/mentions/terminal-mention-plugin/client/mention-terminal-preview.tsx diff --git a/src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx b/src/shared/plugins/mentions/terminal-mention-plugin/client/terminal-mention-client-plugin.tsx similarity index 75% rename from src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx rename to src/shared/plugins/mentions/terminal-mention-plugin/client/terminal-mention-client-plugin.tsx index a48cc49..aaab772 100644 --- a/src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx +++ b/src/shared/plugins/mentions/terminal-mention-plugin/client/terminal-mention-client-plugin.tsx @@ -1,30 +1,22 @@ -import type { UseMentionOptionsReturns } from '@shared/plugins/base/client/client-plugin-types' import { - createClientPlugin, - type SetupProps -} from '@shared/plugins/base/client/use-client-plugin' -import { PluginId } from '@shared/plugins/base/types' + createMentionClientPlugin, + type MentionClientPluginSetupProps +} from '@shared/plugins/mentions/_base/client/create-mention-client-plugin' +import type { UseMentionOptionsReturns } from '@shared/plugins/mentions/_base/client/mention-client-plugin-types' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' import { pkg } from '@shared/utils/pkg' import { useQuery } from '@tanstack/react-query' import { api } from '@webview/network/actions-api' import { type MentionOption } from '@webview/types/chat' import { SquareTerminalIcon } from 'lucide-react' -import { - TerminalInfo, - TerminalMentionType, - TerminalPluginState -} from '../types' +import { TerminalInfo, TerminalMentionType } from '../types' import { MentionTerminalPreview } from './mention-terminal-preview' -export const TerminalClientPlugin = createClientPlugin({ - id: PluginId.Terminal, +export const TerminalMentionClientPlugin = createMentionClientPlugin({ + id: MentionPluginId.Terminal, version: pkg.version, - getInitialState() { - return {} - }, - setup(props) { const { registerProvider } = props @@ -33,7 +25,7 @@ export const TerminalClientPlugin = createClientPlugin({ }) const createUseMentionOptions = - (props: SetupProps) => (): UseMentionOptionsReturns => { + (props: MentionClientPluginSetupProps) => (): UseMentionOptionsReturns => { const { data: terminals = [] } = useQuery({ queryKey: ['realtime', 'terminals'], queryFn: () => diff --git a/src/shared/plugins/terminal-plugin/server/chat-strategy/terminal-chat-strategy-provider.ts b/src/shared/plugins/mentions/terminal-mention-plugin/server/chat-strategy/terminal-mention-chat-strategy-provider.ts similarity index 83% rename from src/shared/plugins/terminal-plugin/server/chat-strategy/terminal-chat-strategy-provider.ts rename to src/shared/plugins/mentions/terminal-mention-plugin/server/chat-strategy/terminal-mention-chat-strategy-provider.ts index 620ac55..bc2ecd4 100644 --- a/src/shared/plugins/terminal-plugin/server/chat-strategy/terminal-chat-strategy-provider.ts +++ b/src/shared/plugins/mentions/terminal-mention-plugin/server/chat-strategy/terminal-mention-chat-strategy-provider.ts @@ -2,10 +2,10 @@ import type { Conversation } from '@shared/entities' import type { GetAgentState, GetMentionState -} from '@shared/plugins/base/base-to-state' -import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' +} from '@shared/plugins/mentions/_base/base-to-state' +import type { MentionChatStrategyProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' -import { TerminalToState } from '../../terminal-mentions-to-state' +import { TerminalToState } from '../../terminal-to-state' import type { TerminalCommand } from '../../types' interface ConversationWithStateProps { @@ -14,7 +14,9 @@ interface ConversationWithStateProps { agentState: GetAgentState } -export class TerminalChatStrategyProvider implements ChatStrategyProvider { +export class TerminalMentionChatStrategyProvider + implements MentionChatStrategyProvider +{ private createConversationWithStateProps( conversation: Conversation ): ConversationWithStateProps { diff --git a/src/shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-plugin.ts b/src/shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-plugin.ts new file mode 100644 index 0000000..fa9b657 --- /dev/null +++ b/src/shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-plugin.ts @@ -0,0 +1,35 @@ +import type { + MentionServerPlugin, + MentionServerPluginContext +} from '@shared/plugins/mentions/_base/server/mention-server-plugin-context' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { TerminalMentionChatStrategyProvider } from './chat-strategy/terminal-mention-chat-strategy-provider' +import { TerminalMentionServerUtilsProvider } from './terminal-mention-server-utils-provider' + +export class TerminalMentionServerPlugin implements MentionServerPlugin { + id = MentionPluginId.Terminal + + version: string = pkg.version + + private context: MentionServerPluginContext | null = null + + async activate(context: MentionServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'chatStrategy', + () => new TerminalMentionChatStrategyProvider() + ) + + this.context.registerProvider( + 'serverUtils', + () => new TerminalMentionServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/terminal-plugin/server/terminal-server-utils-provider.ts b/src/shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-utils-provider.ts similarity index 81% rename from src/shared/plugins/terminal-plugin/server/terminal-server-utils-provider.ts rename to src/shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-utils-provider.ts index 810182c..37b40d6 100644 --- a/src/shared/plugins/terminal-plugin/server/terminal-server-utils-provider.ts +++ b/src/shared/plugins/mentions/terminal-mention-plugin/server/terminal-mention-server-utils-provider.ts @@ -1,10 +1,12 @@ import type { ActionRegister } from '@extension/registers/action-register' import type { Mention } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { TerminalMentionType } from '../types' -export class TerminalServerUtilsProvider implements ServerUtilsProvider { +export class TerminalMentionServerUtilsProvider + implements MentionServerUtilsProvider +{ async createRefreshMentionFn(actionRegister: ActionRegister) { const terminals = await actionRegister .actions() diff --git a/src/shared/plugins/terminal-plugin/terminal-mentions-to-state.ts b/src/shared/plugins/mentions/terminal-mention-plugin/terminal-to-state.ts similarity index 85% rename from src/shared/plugins/terminal-plugin/terminal-mentions-to-state.ts rename to src/shared/plugins/mentions/terminal-mention-plugin/terminal-to-state.ts index a368eea..0dd279d 100644 --- a/src/shared/plugins/terminal-plugin/terminal-mentions-to-state.ts +++ b/src/shared/plugins/mentions/terminal-mention-plugin/terminal-to-state.ts @@ -1,4 +1,4 @@ -import { BaseToState } from '../base/base-to-state' +import { BaseToState } from '../_base/base-to-state' import { TerminalMentionType, type TerminalMention } from './types' export class TerminalToState extends BaseToState { diff --git a/src/shared/plugins/terminal-plugin/types.ts b/src/shared/plugins/mentions/terminal-mention-plugin/types.ts similarity index 67% rename from src/shared/plugins/terminal-plugin/types.ts rename to src/shared/plugins/mentions/terminal-mention-plugin/types.ts index ab596de..c8f3e5d 100644 --- a/src/shared/plugins/terminal-plugin/types.ts +++ b/src/shared/plugins/mentions/terminal-mention-plugin/types.ts @@ -1,7 +1,7 @@ import type { TerminalInfo } from '@extension/registers/terminal-watcher-register' import type { Mention } from '@shared/entities' -import { PluginId } from '../base/types' +import { MentionPluginId } from '../_base/types' export type { TerminalInfo, @@ -9,13 +9,11 @@ export type { } from '@extension/registers/terminal-watcher-register' export enum TerminalMentionType { - Terminals = `${PluginId.Terminal}#terminals`, - Terminal = `${PluginId.Terminal}#terminal` + Terminals = `${MentionPluginId.Terminal}#terminals`, + Terminal = `${MentionPluginId.Terminal}#terminal` } export type TerminalMention = Mention< TerminalMentionType.Terminal, TerminalInfo > - -export interface TerminalPluginState {} diff --git a/src/shared/plugins/mentions/web-mention-plugin/client/web-mention-client-plugin.tsx b/src/shared/plugins/mentions/web-mention-plugin/client/web-mention-client-plugin.tsx new file mode 100644 index 0000000..0eb76f2 --- /dev/null +++ b/src/shared/plugins/mentions/web-mention-plugin/client/web-mention-client-plugin.tsx @@ -0,0 +1,37 @@ +import { GlobeIcon } from '@radix-ui/react-icons' +import { + createMentionClientPlugin, + type MentionClientPluginSetupProps +} from '@shared/plugins/mentions/_base/client/create-mention-client-plugin' +import type { UseMentionOptionsReturns } from '@shared/plugins/mentions/_base/client/mention-client-plugin-types' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { WebMentionType } from '../types' + +export const WebMentionClientPlugin = createMentionClientPlugin({ + id: MentionPluginId.Web, + version: pkg.version, + + setup(props) { + const { registerProvider } = props + + registerProvider('useMentionOptions', () => createUseMentionOptions(props)) + } +}) + +const createUseMentionOptions = + (props: MentionClientPluginSetupProps) => (): UseMentionOptionsReturns => [ + { + id: WebMentionType.Web, + type: WebMentionType.Web, + label: 'Web', + data: true, + topLevelSort: 3, + searchKeywords: ['web', 'search'], + itemLayoutProps: { + icon: , + label: 'Web' + } + } + ] diff --git a/src/shared/plugins/web-plugin/server/chat-strategy/web-chat-strategy-provider.ts b/src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-mention-chat-strategy-provider.ts similarity index 87% rename from src/shared/plugins/web-plugin/server/chat-strategy/web-chat-strategy-provider.ts rename to src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-mention-chat-strategy-provider.ts index c8c9c3d..d9494b7 100644 --- a/src/shared/plugins/web-plugin/server/chat-strategy/web-chat-strategy-provider.ts +++ b/src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-mention-chat-strategy-provider.ts @@ -1,19 +1,19 @@ import type { StructuredTool } from '@langchain/core/tools' import type { Conversation } from '@shared/entities' -import type { - GetAgentState, - GetMentionState -} from '@shared/plugins/base/base-to-state' -import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' import { createGraphNodeFromNodes, createToolsFromNodes -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' import type { BaseStrategyOptions, ChatGraphNode, ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import type { + GetAgentState, + GetMentionState +} from '@shared/plugins/mentions/_base/base-to-state' +import type { MentionChatStrategyProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { removeDuplicates } from '@shared/utils/common' import { WebToState } from '../../web-to-state' @@ -26,7 +26,9 @@ interface ConversationWithStateProps { agentState: GetAgentState } -export class WebChatStrategyProvider implements ChatStrategyProvider { +export class WebMentionChatStrategyProvider + implements MentionChatStrategyProvider +{ private createConversationWithStateProps( conversation: Conversation ): ConversationWithStateProps { diff --git a/src/shared/plugins/web-plugin/server/chat-strategy/web-search-node.ts b/src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-search-node.ts similarity index 82% rename from src/shared/plugins/web-plugin/server/chat-strategy/web-search-node.ts rename to src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-search-node.ts index 3384137..28c4c41 100644 --- a/src/shared/plugins/web-plugin/server/chat-strategy/web-search-node.ts +++ b/src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-search-node.ts @@ -1,9 +1,9 @@ -import { WebSearchAgent } from '@shared/plugins/agents/web-search-agent' import { BaseNode, dispatchBaseGraphState, type ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import { WebSearchAgent } from '@shared/plugins/agents/web-search-agent-plugin/server/web-search-agent' import { WebToState } from '../../web-to-state' @@ -33,10 +33,7 @@ export class WebSearchNode extends BaseNode { if (!toolCallsResults.agents.length) return {} - const newConversation = state.newConversations.at(-1)! - - this.addAgentsToConversation(newConversation, toolCallsResults.agents) - this.addLogsToConversation(newConversation, toolCallsResults.logs) + this.addAgentsToLastHumanAndNewConversation(state, toolCallsResults.agents) dispatchBaseGraphState({ chatContext: state.chatContext, diff --git a/src/shared/plugins/web-plugin/server/chat-strategy/web-visit-node.ts b/src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-visit-node.ts similarity index 82% rename from src/shared/plugins/web-plugin/server/chat-strategy/web-visit-node.ts rename to src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-visit-node.ts index f1062b5..661cba0 100644 --- a/src/shared/plugins/web-plugin/server/chat-strategy/web-visit-node.ts +++ b/src/shared/plugins/mentions/web-mention-plugin/server/chat-strategy/web-visit-node.ts @@ -1,9 +1,9 @@ -import { WebVisitAgent } from '@shared/plugins/agents/web-visit-agent' import { BaseNode, dispatchBaseGraphState, type ChatGraphState -} from '@shared/plugins/base/strategies' +} from '@shared/plugins/_shared/strategies' +import { WebVisitAgent } from '@shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent' import { WebToState } from '../../web-to-state' @@ -33,10 +33,7 @@ export class WebVisitNode extends BaseNode { if (!toolCallsResults.agents.length) return {} - const newConversation = state.newConversations.at(-1)! - - this.addAgentsToConversation(newConversation, toolCallsResults.agents) - this.addLogsToConversation(newConversation, toolCallsResults.logs) + this.addAgentsToLastHumanAndNewConversation(state, toolCallsResults.agents) dispatchBaseGraphState({ chatContext: state.chatContext, diff --git a/src/shared/plugins/mentions/web-mention-plugin/server/web-mention-server-plugin.ts b/src/shared/plugins/mentions/web-mention-plugin/server/web-mention-server-plugin.ts new file mode 100644 index 0000000..5f6e66c --- /dev/null +++ b/src/shared/plugins/mentions/web-mention-plugin/server/web-mention-server-plugin.ts @@ -0,0 +1,35 @@ +import type { + MentionServerPlugin, + MentionServerPluginContext +} from '@shared/plugins/mentions/_base/server/mention-server-plugin-context' +import { MentionPluginId } from '@shared/plugins/mentions/_base/types' +import { pkg } from '@shared/utils/pkg' + +import { WebMentionChatStrategyProvider } from './chat-strategy/web-mention-chat-strategy-provider' +import { WebMentionServerUtilsProvider } from './web-mention-server-utils-provider' + +export class WebMentionServerPlugin implements MentionServerPlugin { + id = MentionPluginId.Web + + version: string = pkg.version + + private context: MentionServerPluginContext | null = null + + async activate(context: MentionServerPluginContext): Promise { + this.context = context + + this.context.registerProvider( + 'chatStrategy', + () => new WebMentionChatStrategyProvider() + ) + + this.context.registerProvider( + 'serverUtils', + () => new WebMentionServerUtilsProvider() + ) + } + + deactivate(): void { + this.context = null + } +} diff --git a/src/shared/plugins/web-plugin/server/web-server-utils-provider.ts b/src/shared/plugins/mentions/web-mention-plugin/server/web-mention-server-utils-provider.ts similarity index 74% rename from src/shared/plugins/web-plugin/server/web-server-utils-provider.ts rename to src/shared/plugins/mentions/web-mention-plugin/server/web-mention-server-utils-provider.ts index ac2d67b..5fc97a2 100644 --- a/src/shared/plugins/web-plugin/server/web-server-utils-provider.ts +++ b/src/shared/plugins/mentions/web-mention-plugin/server/web-mention-server-utils-provider.ts @@ -1,10 +1,12 @@ import type { ActionRegister } from '@extension/registers/action-register' import type { Mention } from '@shared/entities' -import type { ServerUtilsProvider } from '@shared/plugins/base/server/create-provider-manager' +import type { MentionServerUtilsProvider } from '@shared/plugins/mentions/_base/server/create-mention-provider-manager' import { WebMentionType } from '../types' -export class WebServerUtilsProvider implements ServerUtilsProvider { +export class WebMentionServerUtilsProvider + implements MentionServerUtilsProvider +{ // eslint-disable-next-line unused-imports/no-unused-vars async createRefreshMentionFn(actionRegister: ActionRegister) { return (_mention: Mention) => { diff --git a/src/shared/plugins/mentions/web-mention-plugin/types.ts b/src/shared/plugins/mentions/web-mention-plugin/types.ts new file mode 100644 index 0000000..b9fd71a --- /dev/null +++ b/src/shared/plugins/mentions/web-mention-plugin/types.ts @@ -0,0 +1,9 @@ +import type { Mention } from '@shared/entities' + +import { MentionPluginId } from '../_base/types' + +export enum WebMentionType { + Web = `${MentionPluginId.Web}#web` +} + +export type WebMention = Mention diff --git a/src/shared/plugins/mentions/web-mention-plugin/web-to-state.ts b/src/shared/plugins/mentions/web-mention-plugin/web-to-state.ts new file mode 100644 index 0000000..a1f7402 --- /dev/null +++ b/src/shared/plugins/mentions/web-mention-plugin/web-to-state.ts @@ -0,0 +1,28 @@ +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import { WebSearchAgent } from '@shared/plugins/agents/web-search-agent-plugin/server/web-search-agent' +import type { WebVisitAgent } from '@shared/plugins/agents/web-visit-agent-plugin/server/web-visit-agent' + +import { BaseToState } from '../_base/base-to-state' +import { WebMentionType, type WebMention } from './types' + +export class WebToState extends BaseToState { + toMentionsState() { + return { + enableWebSearchAgent: this.isMentionExit(WebMentionType.Web), + enableWebVisitAgent: this.isMentionExit(WebMentionType.Web) + } + } + + toAgentsState() { + return { + webSearchRelevantContent: this.getAgentOutputsByKey< + WebSearchAgent, + 'relevantContent' + >(AgentPluginId.WebSearch, 'relevantContent').flat(), + webVisitContents: this.getAgentOutputsByKey< + WebVisitAgent, + 'visitResults' + >(AgentPluginId.WebVisit, 'visitResults').flat() + } + } +} diff --git a/src/shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-plugin.ts b/src/shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-plugin.ts deleted file mode 100644 index ac51a1c..0000000 --- a/src/shared/plugins/prompt-snippet-plugin/server/prompt-snippet-server-plugin.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { - ServerPlugin, - ServerPluginContext -} from '@shared/plugins/base/server/server-plugin-context' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import type { PromptSnippetPluginState } from '../types' -import { PromptSnippetServerUtilsProvider } from './prompt-snippet-server-utils-provider' - -export class PromptSnippetServerPlugin - implements ServerPlugin -{ - id = PluginId.PromptSnippet - - version: string = pkg.version - - private context: ServerPluginContext | null = null - - async activate( - context: ServerPluginContext - ): Promise { - this.context = context - - this.context.registerProvider( - 'serverUtils', - () => new PromptSnippetServerUtilsProvider() - ) - } - - deactivate(): void { - this.context = null - } -} diff --git a/src/shared/plugins/terminal-plugin/server/terminal-server-plugin.ts b/src/shared/plugins/terminal-plugin/server/terminal-server-plugin.ts deleted file mode 100644 index e11e97f..0000000 --- a/src/shared/plugins/terminal-plugin/server/terminal-server-plugin.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { - ServerPlugin, - ServerPluginContext -} from '@shared/plugins/base/server/server-plugin-context' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import type { TerminalPluginState } from '../types' -import { TerminalChatStrategyProvider } from './chat-strategy/terminal-chat-strategy-provider' -import { TerminalServerUtilsProvider } from './terminal-server-utils-provider' - -export class TerminalServerPlugin implements ServerPlugin { - id = PluginId.Terminal - - version: string = pkg.version - - private context: ServerPluginContext | null = null - - async activate( - context: ServerPluginContext - ): Promise { - this.context = context - - this.context.registerProvider( - 'chatStrategy', - () => new TerminalChatStrategyProvider() - ) - - this.context.registerProvider( - 'serverUtils', - () => new TerminalServerUtilsProvider() - ) - } - - deactivate(): void { - this.context = null - } -} diff --git a/src/shared/plugins/web-plugin/client/web-client-plugin.tsx b/src/shared/plugins/web-plugin/client/web-client-plugin.tsx deleted file mode 100644 index 3a0abac..0000000 --- a/src/shared/plugins/web-plugin/client/web-client-plugin.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { GlobeIcon } from '@radix-ui/react-icons' -import type { UseMentionOptionsReturns } from '@shared/plugins/base/client/client-plugin-types' -import { - createClientPlugin, - type SetupProps -} from '@shared/plugins/base/client/use-client-plugin' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import { WebMentionType, WebPluginState } from '../types' -import { WebLogPreview } from './web-log-preview' - -export const WebClientPlugin = createClientPlugin({ - id: PluginId.Web, - version: pkg.version, - - getInitialState() { - return {} - }, - - setup(props) { - const { registerProvider } = props - - registerProvider('useMentionOptions', () => createUseMentionOptions(props)) - registerProvider('CustomRenderLogPreview', () => WebLogPreview) - } -}) - -const createUseMentionOptions = - (props: SetupProps) => (): UseMentionOptionsReturns => [ - { - id: WebMentionType.Web, - type: WebMentionType.Web, - label: 'Web', - data: true, - topLevelSort: 3, - searchKeywords: ['web', 'search'], - itemLayoutProps: { - icon: , - label: 'Web' - } - } - ] diff --git a/src/shared/plugins/web-plugin/client/web-log-preview.tsx b/src/shared/plugins/web-plugin/client/web-log-preview.tsx deleted file mode 100644 index 1b782bb..0000000 --- a/src/shared/plugins/web-plugin/client/web-log-preview.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { FC, type ReactNode } from 'react' -import { GlobeIcon } from '@radix-ui/react-icons' -import { - webSearchAgentName, - webVisitAgentName -} from '@shared/plugins/agents/agent-names' -import type { WebSearchAgent } from '@shared/plugins/agents/web-search-agent' -import type { WebVisitAgent } from '@shared/plugins/agents/web-visit-agent' -import type { CustomRenderLogPreviewProps } from '@shared/plugins/base/client/client-plugin-types' -import type { GetAgent } from '@shared/plugins/base/strategies' -import type { SFC } from '@shared/types/common' -import { ChatLogPreview } from '@webview/components/chat/messages/roles/chat-log-preview' -import type { PreviewContent } from '@webview/components/content-preview' -import { ContentPreviewPopover } from '@webview/components/content-preview-popover' -import { cn } from '@webview/utils/common' - -import type { WebDocInfo } from '../types' - -export const WebLogPreview: SFC = props => { - const { log } = props - const { agent } = log - - const renderWrapper = (children: ReactNode) => ( - -
{children}
-
- ) - - if (!agent) return null - - switch (agent.name) { - case webSearchAgentName: - return renderWrapper( - (agent as GetAgent).output.webSearchResults?.map( - (doc, index) => ( - - ) - ) - ) - case webVisitAgentName: - return renderWrapper( - (agent as GetAgent).output.contents?.map( - (doc, index) => ( - - ) - ) - ) - default: - return null - } -} - -interface WebDocItemProps { - doc: WebDocInfo - className?: string -} - -const WebDocItem: FC = ({ doc, className }) => { - const previewContent: PreviewContent = { - type: 'markdown', - content: doc.content - } - - const handleOpenUrl = (e: React.MouseEvent) => { - e.stopPropagation() - window.open(doc.url, '_blank') - } - - return ( - -
-
-
- -
-
- -
- {/* Content Preview */} -
- {doc.content} -
- - {/* URL */} -
- {doc.url} -
-
-
-
- ) -} diff --git a/src/shared/plugins/web-plugin/server/web-server-plugin.ts b/src/shared/plugins/web-plugin/server/web-server-plugin.ts deleted file mode 100644 index 65790cf..0000000 --- a/src/shared/plugins/web-plugin/server/web-server-plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { - ServerPlugin, - ServerPluginContext -} from '@shared/plugins/base/server/server-plugin-context' -import { PluginId } from '@shared/plugins/base/types' -import { pkg } from '@shared/utils/pkg' - -import type { WebPluginState } from '../types' -import { WebChatStrategyProvider } from './chat-strategy/web-chat-strategy-provider' -import { WebServerUtilsProvider } from './web-server-utils-provider' - -export class WebServerPlugin implements ServerPlugin { - id = PluginId.Web - - version: string = pkg.version - - private context: ServerPluginContext | null = null - - async activate(context: ServerPluginContext): Promise { - this.context = context - - this.context.registerProvider( - 'chatStrategy', - () => new WebChatStrategyProvider() - ) - - this.context.registerProvider( - 'serverUtils', - () => new WebServerUtilsProvider() - ) - } - - deactivate(): void { - this.context = null - } -} diff --git a/src/shared/plugins/web-plugin/types.ts b/src/shared/plugins/web-plugin/types.ts deleted file mode 100644 index 4133c84..0000000 --- a/src/shared/plugins/web-plugin/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Mention } from '@shared/entities' - -import { PluginId } from '../base/types' - -export enum WebMentionType { - Web = `${PluginId.Web}#web` -} - -export type WebMention = Mention - -export interface WebDocInfo { - content: string - url: string -} - -export interface WebPluginState {} diff --git a/src/shared/plugins/web-plugin/web-to-state.ts b/src/shared/plugins/web-plugin/web-to-state.ts deleted file mode 100644 index 62fb82e..0000000 --- a/src/shared/plugins/web-plugin/web-to-state.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { WebSearchAgent } from '../agents/web-search-agent' -import type { WebVisitAgent } from '../agents/web-visit-agent' -import { BaseToState } from '../base/base-to-state' -import { WebMentionType, type WebMention } from './types' - -export class WebToState extends BaseToState { - toMentionsState() { - return { - enableWebSearchAgent: this.isMentionExit(WebMentionType.Web), - enableWebVisitAgent: this.isMentionExit(WebMentionType.Web) - } - } - - toAgentsState() { - return { - webSearchRelevantContent: this.getAgentOutputsByKey< - WebSearchAgent, - 'relevantContent' - >(WebSearchAgent.name, 'relevantContent').flat(), - webVisitContents: this.getAgentOutputsByKey( - 'webVisit', - 'contents' - ).flat() - } - } -} diff --git a/src/extension/chat/utils/convert-langchain-message-to-conversation.ts b/src/shared/utils/chat-context-helper/common/convert-langchain-message-to-conversation.ts similarity index 69% rename from src/extension/chat/utils/convert-langchain-message-to-conversation.ts rename to src/shared/utils/chat-context-helper/common/convert-langchain-message-to-conversation.ts index 31d2d25..6e3f8c9 100644 --- a/src/extension/chat/utils/convert-langchain-message-to-conversation.ts +++ b/src/shared/utils/chat-context-helper/common/convert-langchain-message-to-conversation.ts @@ -2,10 +2,10 @@ import type { MessageType } from '@langchain/core/messages' import { ConversationEntity, type Conversation, - type LangchainMessage, - type LangchainMessageContents + type ConversationContents, + type LangchainMessage } from '@shared/entities' -import { convertToLangchainMessageContents } from '@shared/utils/convert-to-langchain-message-contents' +import { parseAsConversationContents } from '@shared/utils/chat-context-helper/common/parse-as-conversation-contents' export const convertLangchainMessageToConversation = ( message: LangchainMessage, @@ -16,7 +16,7 @@ export const convertLangchainMessageToConversation = ( ...initConversation, role: messageType }).entity - const contents: LangchainMessageContents = convertToLangchainMessageContents( + const contents: ConversationContents = parseAsConversationContents( message.content ) diff --git a/src/shared/utils/chat-context-helper/common/get-all-text-from-conversation-contents.ts b/src/shared/utils/chat-context-helper/common/get-all-text-from-conversation-contents.ts new file mode 100644 index 0000000..a816403 --- /dev/null +++ b/src/shared/utils/chat-context-helper/common/get-all-text-from-conversation-contents.ts @@ -0,0 +1,13 @@ +import type { ConversationContents } from '@shared/entities' + +export const getAllTextFromConversationContents = ( + contents: ConversationContents +): string => + contents + .map(content => { + if (content.type === 'text') { + return content.text + } + return '' + }) + .join('') diff --git a/src/shared/utils/merge-langchain-message-contents.ts b/src/shared/utils/chat-context-helper/common/merge-conversation-contents.ts similarity index 63% rename from src/shared/utils/merge-langchain-message-contents.ts rename to src/shared/utils/chat-context-helper/common/merge-conversation-contents.ts index 57b7881..6081369 100644 --- a/src/shared/utils/merge-langchain-message-contents.ts +++ b/src/shared/utils/chat-context-helper/common/merge-conversation-contents.ts @@ -1,9 +1,9 @@ -import type { LangchainMessageContents } from '@shared/entities' +import type { ConversationContents } from '@shared/entities' -export const mergeLangchainMessageContents = ( - contents: LangchainMessageContents -): LangchainMessageContents => { - const finalContents: LangchainMessageContents = [] +export const mergeConversationContents = ( + contents: ConversationContents +): ConversationContents => { + const finalContents: ConversationContents = [] contents.forEach(content => { const lastContent = finalContents.at(-1) diff --git a/src/shared/utils/chat-context-helper/common/parse-as-conversation-contents.ts b/src/shared/utils/chat-context-helper/common/parse-as-conversation-contents.ts new file mode 100644 index 0000000..56f4312 --- /dev/null +++ b/src/shared/utils/chat-context-helper/common/parse-as-conversation-contents.ts @@ -0,0 +1,78 @@ +import { type MessageContentComplex } from '@langchain/core/messages' +import type { ConversationContents, LangchainMessage } from '@shared/entities' +import { hasOwnProperty } from '@shared/utils/common' + +export const parseAsConversationContents = ( + content: + | string + | MessageContentComplex[] + | MessageContentComplex + | LangchainMessage + | undefined + | null +): ConversationContents => { + const defaultContent: ConversationContents = [] + + if (!content) { + return defaultContent + } + + if (Array.isArray(content)) { + return content + .map(item => { + const itemResult = parseAsConversationContents(item) + return itemResult + }) + .flat() + } + + if (typeof content === 'string') { + return [ + { + type: 'text', + text: content + } + ] + } + + if (typeof content === 'object' && content !== null) { + if (hasOwnProperty(content, 'type')) { + if (content.type === 'text') { + return [ + { + type: 'text', + text: content.text + } + ] + } + + if (content.type === 'image_url') { + return [ + { + type: 'image_url', + image_url: { + url: + typeof content.image_url === 'string' + ? content.image_url + : content.image_url?.url, + detail: content?.image_url?.detail || undefined + } + } + ] + } + + if (content.type === 'action') { + return [ + { + type: 'action', + actionId: content.actionId + } + ] + } + } else if (hasOwnProperty(content, 'getType')) { + return parseAsConversationContents(content.content) + } + } + + return defaultContent +} diff --git a/src/shared/utils/common.ts b/src/shared/utils/common.ts index 1ce66c8..b1c3422 100644 --- a/src/shared/utils/common.ts +++ b/src/shared/utils/common.ts @@ -106,3 +106,6 @@ export const signalToController = (signal: AbortSignal) => { return controller } + +export const hasOwnProperty = (obj: unknown, prop: string): obj is T => + Object.prototype.hasOwnProperty.call(obj, prop) diff --git a/src/shared/utils/convert-to-langchain-message-contents.ts b/src/shared/utils/convert-to-langchain-message-contents.ts deleted file mode 100644 index 77bb5cc..0000000 --- a/src/shared/utils/convert-to-langchain-message-contents.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { MessageContentComplex } from '@langchain/core/messages' -import type { LangchainMessageContents } from '@shared/entities' - -export const convertToLangchainMessageContents = ( - content: - | string - | MessageContentComplex[] - | MessageContentComplex - | undefined - | null -): LangchainMessageContents => { - const defaultContent: LangchainMessageContents = [] - - if (!content) { - return defaultContent - } - - if (typeof content === 'string') { - return [ - { - type: 'text', - text: content - } - ] - } - - if (Object.prototype.hasOwnProperty.call(content, 'type')) { - const _content = content as MessageContentComplex - - if (_content.type === 'text') { - return [ - { - type: 'text', - text: _content.text - } - ] - } - - if (_content.type === 'image_url') { - return [ - { - type: 'image_url', - image_url: { - url: - typeof _content.image_url === 'string' - ? _content.image_url - : _content.image_url?.url, - detail: _content?.image_url?.detail || undefined - } - } - ] - } - } - - if (Array.isArray(content)) { - return content - .map(item => { - const itemResult = convertToLangchainMessageContents(item) - return itemResult - }) - .flat() - } - - return defaultContent -} diff --git a/src/shared/utils/get-all-text-from-langchain-message-contents.ts b/src/shared/utils/get-all-text-from-langchain-message-contents.ts deleted file mode 100644 index 8e8ca5b..0000000 --- a/src/shared/utils/get-all-text-from-langchain-message-contents.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { LangchainMessageContents } from '@shared/entities' - -export const getAllTextFromLangchainMessageContents = ( - contents: LangchainMessageContents -): string => - contents - .map(content => { - if (content.type === 'text') { - return content.text - } - return '' - }) - .join('') diff --git a/src/webview/actions/chat-actions.ts b/src/webview/actions/chat-actions.ts index 6f4ad91..524e40f 100644 --- a/src/webview/actions/chat-actions.ts +++ b/src/webview/actions/chat-actions.ts @@ -23,11 +23,15 @@ export class ChatActionsCollection extends ClientActionCollection { refreshChatSessions(context: ActionContext<{}>) { emitter.emit('chat.refreshChatSessions', context) } + + refreshCurrentChatSession(context: ActionContext<{}>) { + emitter.emit('chat.refreshCurrentChatSession', context) + } } export const useChatActions = () => { const navigate = useNavigate() - const { refreshChatSessions } = useChatContext() + const { refreshChatSessions, refreshCurrentChatSession } = useChatContext() const location = useLocation() const { pathname } = location @@ -50,4 +54,8 @@ export const useChatActions = () => { useOn('chat.refreshChatSessions', async context => { await refreshChatSessions() }) + + useOn('chat.refreshCurrentChatSession', async context => { + await refreshCurrentChatSession() + }) } diff --git a/src/webview/components/chat/chat-ui.tsx b/src/webview/components/chat/chat-ui.tsx index ebaad59..7f0a60d 100644 --- a/src/webview/components/chat/chat-ui.tsx +++ b/src/webview/components/chat/chat-ui.tsx @@ -6,6 +6,7 @@ import { useChatContext } from '@webview/contexts/chat-context' import { useGlobalSearch } from '@webview/contexts/global-search-context' import { useChatState } from '@webview/hooks/chat/use-chat-state' import { useSendMessage } from '@webview/hooks/chat/use-send-message' +import { useElementSize } from '@webview/hooks/use-element-size' import { logger } from '@webview/utils/logger' import { useNavigate } from 'react-router' import { useKey } from 'react-use' @@ -20,7 +21,7 @@ import { SelectValue } from '../ui/select' import { SidebarLayout } from '../ui/sidebar/sidebar-layout' -import { ChatInput, type ChatInputRef } from './editor/chat-input' +import { ChatInput, type ChatInputEditorRef } from './editor/chat-input' import { ChatMessages } from './messages/chat-messages' import { ChatSidebar } from './sidebar/chat-sidebar' @@ -39,13 +40,15 @@ export const ChatUI: FC = () => { newConversation, setNewConversation, deleteConversation, - historiesConversationsWithUIState, newConversationUIState, toggleConversationEditMode } = useChatState() - const chatInputRef = useRef(null) + const editorRef = useRef(null) + const editorWrapperRef = useRef(null) + const editorWrapperSize = useElementSize(editorWrapperRef) const { openSearch } = useGlobalSearch() const { sendMessage, cancelSending, isSending } = useSendMessage() + const showActionCollapsible = true useKey( event => (event.metaKey || event.ctrlKey) && event.key === 'Delete', @@ -60,9 +63,9 @@ export const ChatUI: FC = () => { const handleSend = async (conversation: Conversation) => { try { await sendMessage(conversation) - chatInputRef.current?.clearInput() - chatInputRef.current?.reInitializeEditor() - chatInputRef.current?.focusOnEditor() + editorRef.current?.clearInput() + editorRef.current?.reInitializeEditor() + editorRef.current?.focusOnEditor() } catch (error) { if (isAbortError(error)) return logger.error('Failed to send message:', error) @@ -163,19 +166,19 @@ export const ChatUI: FC = () => { >
{isSending && ( -
+
{
)} + = ({ + defaultExpanded = false, + className = '' +}) => { + const { context, setContext } = useChatContext() + const [isExpanded, setIsExpanded] = useState(defaultExpanded) + const isSameAction = useAgentPluginIsSameAction() + const isCompletedAction = useAgentPluginIsCompletedAction() + const { acceptMultipleActionsMutation, rejectMultipleActionsMutation } = + useSessionActionContext() + const uniqActionInfos = useMemo(() => { + // Use Map to store unique actions with their conversations + const actionMap = new Map< + string, + { + action: ConversationAction + actionIndex: number + conversation: Conversation + conversationIndex: number + isCompleted: boolean + } + >() + + // Iterate through conversations and their actions + context.conversations.forEach((conversation, conversationIndex) => { + conversation.actions.forEach((action, actionIndex) => { + let shouldAdd = true + + // Check against existing actions + for (const [id, existing] of actionMap.entries()) { + if (isSameAction(action, existing.action)) { + if (action.weight > existing.action.weight) { + actionMap.delete(id) + } else { + shouldAdd = false + } + break + } + } + + if (shouldAdd) { + actionMap.set(action.id, { + action, + actionIndex, + conversation, + conversationIndex, + isCompleted: isCompletedAction(action) + }) + } + }) + }) + + return Array.from(actionMap.values()) + }, [context.conversations, isSameAction]) + + const unCompletedActionInfos = uniqActionInfos.filter( + action => !action.isCompleted + ) + + const handleAcceptAll = () => { + acceptMultipleActionsMutation.mutate({ + chatContext: context, + actionItems: unCompletedActionInfos.map(actionInfo => ({ + conversation: actionInfo.conversation, + action: actionInfo.action + })) + }) + } + + const handleRejectAll = () => { + rejectMultipleActionsMutation.mutate({ + chatContext: context, + actionItems: unCompletedActionInfos.map(actionInfo => ({ + conversation: actionInfo.conversation, + action: actionInfo.action + })) + }) + } + + if (!uniqActionInfos.length) return null + + return ( +
+
+ {/* title */} +
setIsExpanded(!isExpanded)} + > + + + Actions ({uniqActionInfos.length}) + +
+ + {/* actions */} +
+ {unCompletedActionInfos.length > 0 && ( + <> + + + + )} +
+
+ + +
+ {uniqActionInfos.map( + ({ action, conversation, actionIndex, conversationIndex }) => ( + { + setContext(draft => { + if (typeof updater === 'function') { + updater( + draft.conversations[conversationIndex]!.actions[ + actionIndex + ]! + ) + } else { + draft.conversations[conversationIndex]!.actions[ + actionIndex + ] = updater + } + }) + }} + conversation={conversation} + setConversation={updater => { + setContext(draft => { + if (typeof updater === 'function') { + updater(draft.conversations[conversationIndex]!) + } else { + draft.conversations[conversationIndex] = updater + } + }) + }} + /> + ) + )} +
+
+
+
+ ) +} diff --git a/src/webview/components/chat/editor/chat-input.tsx b/src/webview/components/chat/editor/chat-input.tsx index 131e3ad..51c74de 100644 --- a/src/webview/components/chat/editor/chat-input.tsx +++ b/src/webview/components/chat/editor/chat-input.tsx @@ -5,19 +5,17 @@ import { type Conversation, type ImageInfo } from '@shared/entities' -import { PluginId } from '@shared/plugins/base/types' +import { getAllTextFromConversationContents } from '@shared/utils/chat-context-helper/common/get-all-text-from-conversation-contents' +import { mergeConversationContents } from '@shared/utils/chat-context-helper/common/merge-conversation-contents' +import { parseAsConversationContents } from '@shared/utils/chat-context-helper/common/parse-as-conversation-contents' import { removeDuplicates, tryParseJSON, tryStringifyJSON } from '@shared/utils/common' -import { convertToLangchainMessageContents } from '@shared/utils/convert-to-langchain-message-contents' -import { getAllTextFromLangchainMessageContents } from '@shared/utils/get-all-text-from-langchain-message-contents' -import { mergeLangchainMessageContents } from '@shared/utils/merge-langchain-message-contents' import { ButtonWithTooltip } from '@webview/components/button-with-tooltip' import { FileIcon } from '@webview/components/file-icon' import { BorderBeam } from '@webview/components/ui/border-beam' -import { usePlugin, WithPluginProvider } from '@webview/contexts/plugin-context' import { useCallbackRef } from '@webview/hooks/use-callback-ref' import { api } from '@webview/network/actions-api' import { type FileInfo } from '@webview/types/chat' @@ -34,6 +32,7 @@ import { import { type Updater } from 'use-immer' import { ContextSelector } from '../selectors/context-selector' +import { ActionCollapsible } from './action-collapsible' import { ChatEditor, type ChatEditorRef } from './chat-editor' import { FileAttachments, @@ -47,8 +46,11 @@ export enum ChatInputMode { } export interface ChatInputProps { - ref?: React.Ref + ref?: React.Ref className?: string + editorWrapperRef?: React.Ref + editorWrapperClassName?: string + editorRef?: React.Ref editorClassName?: string mode?: ChatInputMode onExitEditMode?: () => void @@ -60,17 +62,22 @@ export interface ChatInputProps { setConversation: Updater sendButtonDisabled: boolean hideModelSelector?: boolean + showActionCollapsible?: boolean onSend?: (conversation: Conversation) => void + showBlurBg?: boolean } -export interface ChatInputRef extends ChatEditorRef { +export interface ChatInputEditorRef extends ChatEditorRef { reInitializeEditor: () => void clearInput: () => void } -const _ChatInput: FC = ({ +export const ChatInput: FC = ({ ref, className, + editorWrapperRef, + editorWrapperClassName, + editorRef, editorClassName, mode = ChatInputMode.Default, onExitEditMode, @@ -82,17 +89,11 @@ const _ChatInput: FC = ({ setConversation, sendButtonDisabled, hideModelSelector = false, - onSend + onSend, + showActionCollapsible = false, + showBlurBg = false }) => { - const editorRef = useRef(null) - const { setState: setPluginState, getState: getPluginState } = usePlugin() - - // sync conversation plugin states with plugin registry - useEffect(() => { - Object.entries(conversation.pluginStates).forEach(([pluginId, state]) => { - setPluginState(pluginId as PluginId, state) - }) - }, [setPluginState, conversation.pluginStates]) + const innerEditorRef = useRef(null) const handleEditorChange = async (editorState: EditorState) => { const newRichText = tryStringifyJSON(editorState.toJSON()) || '' @@ -100,13 +101,12 @@ const _ChatInput: FC = ({ setConversation(draft => { if (draft.richText !== newRichText) { draft.richText = newRichText - draft.contents = mergeLangchainMessageContents( - convertToLangchainMessageContents( + draft.contents = mergeConversationContents( + parseAsConversationContents( editorState.read(() => $getRoot().getTextContent()) ) ) } - draft.pluginStates = getPluginState() }) } @@ -128,7 +128,7 @@ const _ChatInput: FC = ({ const root = $getRoot() const paragraph = $createParagraphNode() const text = $createTextNode( - getAllTextFromLangchainMessageContents(contents) + getAllTextFromConversationContents(contents) ) paragraph.append(text) root.clear() @@ -144,7 +144,7 @@ const _ChatInput: FC = ({ const getConversation = useCallbackRef(() => conversation) const handleSend = async () => { if (sendButtonDisabled || !onSend) return - const editorState = editorRef.current?.editor.getEditorState() + const editorState = innerEditorRef.current?.editor.getEditorState() if (editorState) { await handleEditorChange(editorState) @@ -161,16 +161,16 @@ const _ChatInput: FC = ({ onSend(newConversation) } - const focusOnEditor = () => editorRef.current?.focusOnEditor() + const focusOnEditor = () => innerEditorRef.current?.focusOnEditor() useEffect(() => { - editorRef.current?.editor.setEditable( + innerEditorRef.current?.editor.setEditable( ![ChatInputMode.MessageReadonly].includes(mode) ) }, [mode]) const reInitializeEditor = () => { - initialEditorState(editorRef.current?.editor) + initialEditorState(innerEditorRef.current?.editor) } const clearInput = () => { @@ -181,19 +181,20 @@ const _ChatInput: FC = ({ } const handleRef = (node: ChatEditorRef | null) => { - editorRef.current = node - if (typeof ref === 'function') { - ref({ + innerEditorRef.current = node + if (typeof editorRef === 'function') { + editorRef({ ...node, reInitializeEditor, clearInput - } as ChatInputRef) - } else if (ref) { - ref.current = { + } as ChatInputEditorRef) + } else if (editorRef) { + // eslint-disable-next-line react-compiler/react-compiler + editorRef.current = { ...node, reInitializeEditor, clearInput - } as ChatInputRef + } as ChatInputEditorRef } } @@ -217,117 +218,124 @@ const _ChatInput: FC = ({ return ( - + {showActionCollapsible && ( + )} - > - - - - - {![ChatInputMode.MessageReadonly].includes(mode) && ( - - { - editorRef.current?.insertSpaceAndAt() - }} - onFocusOnEditor={focusOnEditor} - showExitEditModeButton={mode === ChatInputMode.MessageEdit} - onExitEditMode={onExitEditMode} - /> - {onSend && ( - - ⌘↩ Send - - )} - - )} - + + + + + {![ChatInputMode.MessageReadonly].includes(mode) && ( + + { + innerEditorRef.current?.insertSpaceAndAt() + }} + onFocusOnEditor={focusOnEditor} + showExitEditModeButton={mode === ChatInputMode.MessageEdit} + onExitEditMode={onExitEditMode} + /> + {onSend && ( + + ⌘↩ Send + + )} + + )} + + + {borderAnimation && } + {showBlurBg && ( +
+ )} - {borderAnimation && } - +
) } -export const ChatInput = WithPluginProvider(_ChatInput) - interface AnimatedFileAttachmentsProps { mode: ChatInputMode conversation: Conversation diff --git a/src/webview/components/chat/messages/chat-messages.tsx b/src/webview/components/chat/messages/chat-messages.tsx index 4453fc9..e331742 100644 --- a/src/webview/components/chat/messages/chat-messages.tsx +++ b/src/webview/components/chat/messages/chat-messages.tsx @@ -5,12 +5,15 @@ import React, { type FC, type RefObject } from 'react' -import { getAllTextFromLangchainMessageContents } from '@shared/utils/get-all-text-from-langchain-message-contents' +import type { Conversation } from '@shared/entities' +import { getAllTextFromConversationContents } from '@shared/utils/chat-context-helper/common/get-all-text-from-conversation-contents' import { AnimatedList } from '@webview/components/ui/animated-list' import { ScrollArea } from '@webview/components/ui/scroll-area' -import type { ConversationWithUIState } from '@webview/types/chat' +import { useChatContext } from '@webview/contexts/chat-context' +import { useChatState } from '@webview/hooks/chat/use-chat-state' import { cn, copyToClipboard } from '@webview/utils/common' import scrollIntoView from 'scroll-into-view-if-needed' +import type { Updater } from 'use-immer' import { ChatAIMessage, type ChatAIMessageProps } from './roles/chat-ai-message' import { @@ -26,8 +29,6 @@ import { interface ChatMessagesProps extends Pick< InnerMessageProps, - | 'context' - | 'setContext' | 'onEditModeChange' | 'onSend' | 'className' @@ -35,16 +36,13 @@ interface ChatMessagesProps | 'onDelete' | 'onRegenerate' > { - conversationsWithUIState: ConversationWithUIState[] autoScrollToBottom?: boolean disableAnimation?: boolean + blankBottomHeight?: number } export const ChatMessages: React.FC = props => { const { - conversationsWithUIState, - context, - setContext, onSend, onEditModeChange, className, @@ -52,15 +50,16 @@ export const ChatMessages: React.FC = props => { onDelete, onRegenerate, autoScrollToBottom = true, - disableAnimation = false + disableAnimation = false, + blankBottomHeight = 0 } = props const containerRef = useRef(null) const scrollContentRef = useRef(null) const endOfMessagesRef = useRef(null) const prevConversationIdRef = useRef(undefined) - - const lastConversationId = conversationsWithUIState.at(-1)?.id + const { historiesConversationsWithUIState } = useChatState() + const lastConversationId = historiesConversationsWithUIState.at(-1)?.id useEffect(() => { if (!containerRef.current) return @@ -83,37 +82,11 @@ export const ChatMessages: React.FC = props => { prevConversationIdRef.current = lastConversationId }, [lastConversationId, autoScrollToBottom]) - // const handleSetConversation: Updater = ( - // conversationOrUpdater: Conversation | DraftFunction - // ) => { - // setContext((draft: ChatContext) => { - // const index = draft.conversations.findIndex(c => { - // if (typeof conversationOrUpdater === 'function') { - // // Create a temporary draft to test the updater - // const tempC = produce(c, conversationOrUpdater) - // // Compare IDs to find the right conversation - // return tempC.id === c.id && tempC !== c - // } - // return c.id === conversationOrUpdater.id - // }) - - // if (index !== -1) { - // if (typeof conversationOrUpdater === 'function') { - // // Apply the updater function to the found conversation - // conversationOrUpdater(draft.conversations[index]!) - // } else { - // // Replace the conversation with the new one - // draft.conversations[index] = conversationOrUpdater - // } - // } - // }) - // } - return ( = props => { {/* Chat messages */} - {conversationsWithUIState.map(conversationWithUIState => { + {historiesConversationsWithUIState.map(conversationWithUIState => { const { uiState, ...conversation } = conversationWithUIState return ( = props => { onEditModeChange={onEditModeChange} onDelete={onDelete} onRegenerate={onRegenerate} + scrollContentBottomBlankHeight={blankBottomHeight} /> ) })}
+
) } interface InnerMessageProps - extends ChatAIMessageProps, + extends Omit, Omit, Pick { className?: string style?: CSSProperties + scrollContentBottomBlankHeight?: number scrollContentRef: RefObject } @@ -169,8 +146,6 @@ const InnerMessage: FC = props => { const { conversation, onEditModeChange, - context, - setContext, onSend, isLoading, isEditMode, @@ -179,21 +154,34 @@ const InnerMessage: FC = props => { style, scrollContentRef, onDelete, - onRegenerate + onRegenerate, + scrollContentBottomBlankHeight } = props + const { setContext } = useChatContext() const isAiMessage = conversation.role === 'ai' const isHumanMessage = conversation.role === 'human' + const setConversation: Updater = updater => { + setContext(draft => { + const index = draft.conversations.findIndex(c => c.id === conversation.id) + if (index !== -1) { + if (typeof updater === 'function') { + updater(draft.conversations[index]!) + } else { + draft.conversations[index] = updater + } + } + }) + } + const handleCopy = () => { if (isHumanMessage) { messageRef.current?.copy?.() } if (isAiMessage) { - copyToClipboard( - getAllTextFromLangchainMessageContents(conversation.contents) - ) + copyToClipboard(getAllTextFromConversationContents(conversation.contents)) } } @@ -210,6 +198,7 @@ const InnerMessage: FC = props => { onCopy={handleCopy} onDelete={onDelete} onRegenerate={isAiMessage ? onRegenerate : undefined} + scrollContentBottomBlankHeight={scrollContentBottomBlankHeight} /> ) @@ -230,6 +219,7 @@ const InnerMessage: FC = props => { conversation={conversation} isLoading={isLoading} isEditMode={isEditMode} + setConversation={setConversation} // onEditModeChange={onEditModeChange} /> {renderMessageToolbar()} @@ -240,8 +230,6 @@ const InnerMessage: FC = props => { <> { - language: string isLoading?: boolean - fileContent: string - fileInfo: FileInfo | null | undefined + originalContent: string + enableActionController?: boolean + children: React.ReactNode } export const FileBlock: FC = ({ - language, - fileContent, - fileInfo, defaultExpanded, isLoading = false, + originalContent, + enableActionController = false, + children, ...rest }) => { + const { content, shikiLang, markdownLang, fileInfo, fileContent } = + useChildrenInfo(children) + const copyToClipboard = () => { navigator.clipboard.writeText(fileContent) toast.success('Code copied to clipboard') @@ -83,15 +89,23 @@ export const FileBlock: FC = ({ ) : null return ( - - - + <> + {enableActionController && ( + + )} + + + + ) } @@ -115,7 +129,7 @@ export const useApplyActions = ({ text: 'Stopping...' } } - if (applyStatus === InlineDiffTaskState.Finished) { + if (applyStatus === InlineDiffTaskState.Accepted) { return { onClick: () => reapplyCode(), icon: , diff --git a/src/webview/components/chat/messages/markdown/code/block/helpers/action-controller.tsx b/src/webview/components/chat/messages/markdown/code/block/helpers/action-controller.tsx new file mode 100644 index 0000000..e8ffeaa --- /dev/null +++ b/src/webview/components/chat/messages/markdown/code/block/helpers/action-controller.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from 'react' +import { AgentPluginId } from '@shared/plugins/agents/_base/types' +import type { EditFileAction } from '@shared/plugins/agents/edit-file-agent-plugin/types' +import { useMarkdownActionContext } from '@webview/contexts/conversation-action-context/markdown-action-context' +import { api } from '@webview/network/actions-api' +import { useDebounce } from 'react-use' +import { v4 as uuidv4 } from 'uuid' + +interface ActionControllerProps { + originalContent: string + fileRelativePath: string +} + +export const ActionController: React.FC = ({ + originalContent, + fileRelativePath +}) => { + const [debouncedOriginalContent, setDebouncedOriginalContent] = useState('') + + useDebounce( + () => { + setDebouncedOriginalContent(originalContent) + }, + 1000, + [originalContent] + ) + + const { addAction } = useMarkdownActionContext() + + useEffect(() => { + // add file edit action + if (!debouncedOriginalContent || !fileRelativePath) return + + addAction({ + currentContent: debouncedOriginalContent, + action: { + state: { + inlineDiffTask: null + }, + agent: { + id: uuidv4(), + name: AgentPluginId.EditFile, + input: { + blocking: false, + codeEdit: debouncedOriginalContent, + instructions: 'Edit the file by composer', + targetFilePath: fileRelativePath + }, + output: { + success: true + } + } + }, + onRemoveSameAction: oldAction => { + const oldInlineDiffTask = oldAction.state.inlineDiffTask + if (oldInlineDiffTask) { + api.actions().server.apply.abortAndCleanApplyCodeTask({ + actionParams: { + task: oldInlineDiffTask + } + }) + } + } + }) + }, [debouncedOriginalContent, fileRelativePath]) + + return null +} diff --git a/src/webview/components/chat/messages/markdown/code/block/helpers/types.ts b/src/webview/components/chat/messages/markdown/code/block/helpers/types.ts index 07ed5a2..4c5497b 100644 --- a/src/webview/components/chat/messages/markdown/code/block/helpers/types.ts +++ b/src/webview/components/chat/messages/markdown/code/block/helpers/types.ts @@ -1,6 +1,5 @@ export interface BaseCodeBlockProps extends Omit, 'content'> { - content: string defaultExpanded?: boolean className?: string style?: React.CSSProperties diff --git a/src/webview/components/chat/messages/markdown/code/block/helpers/use-children-info.ts b/src/webview/components/chat/messages/markdown/code/block/helpers/use-children-info.ts index 93d4f56..0201f31 100644 --- a/src/webview/components/chat/messages/markdown/code/block/helpers/use-children-info.ts +++ b/src/webview/components/chat/messages/markdown/code/block/helpers/use-children-info.ts @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { useFileInfoForMessage } from '@webview/hooks/api/use-file-info-for-message' import { getShikiLanguage } from '@webview/utils/shiki' @@ -6,19 +7,19 @@ import { getContentInfoFromChildren, getRangeFromCode } from './utils' export const FALLBACK_LANG = 'typescript' export const useChildrenInfo = (children: any) => { - const contentInfo = getContentInfoFromChildren(children) - const { content, className } = contentInfo - const markdownLangLineStr = - className?.replace('language-', '') || FALLBACK_LANG - const [markdownLang, relativePath] = markdownLangLineStr.split(':') + const { content, markdownLang, relativePath } = + getContentInfoFromChildren(children) const shikiLang = getShikiLanguage({ unknownLang: markdownLang, path: relativePath }) - const { startLine, endLine } = getRangeFromCode(content) - const { data: fileInfo } = useFileInfoForMessage({ + const { startLine, endLine } = useMemo( + () => getRangeFromCode(content), + [content] + ) + const { data: fileInfo, isLoading } = useFileInfoForMessage({ relativePath, startLine, endLine @@ -27,5 +28,12 @@ export const useChildrenInfo = (children: any) => { const fileContent = startLine === undefined && content ? content : fileInfo?.content || '' - return { content, fileContent, shikiLang, markdownLang, fileInfo } + return { + content, + fileContent, + shikiLang, + markdownLang, + fileInfo, + isLoading + } } diff --git a/src/webview/components/chat/messages/markdown/code/block/helpers/utils.ts b/src/webview/components/chat/messages/markdown/code/block/helpers/utils.ts index 1edc827..bd581ba 100644 --- a/src/webview/components/chat/messages/markdown/code/block/helpers/utils.ts +++ b/src/webview/components/chat/messages/markdown/code/block/helpers/utils.ts @@ -44,7 +44,7 @@ export const getRangeFromCode = ( return { startLine, endLine } } -export const getContentInfoFromChildren = ( +const _getContentInfoFromChildren = ( _children: any ): { content: string; className: string } => { const defaultResult = { content: '', className: '' } @@ -57,11 +57,33 @@ export const getContentInfoFromChildren = ( if (typeof _children === 'object' && 'props' in _children) { const { children, className } = _children.props - const result = getContentInfoFromChildren(children) + const result = _getContentInfoFromChildren(children) return { content: result.content, className: result.className || className } } const children = Array.isArray(_children) ? _children[0] : _children - return getContentInfoFromChildren(children) + return _getContentInfoFromChildren(children) +} + +export const FALLBACK_LANG = 'typescript' +export const getContentInfoFromChildren = ( + children: React.ReactNode +): { + content: string + className: string + markdownLang: string + relativePath: string | undefined +} => { + const { content, className } = _getContentInfoFromChildren(children) + const markdownLangLineStr = + className?.replace('language-', '') || FALLBACK_LANG + const [markdownLang, relativePath] = markdownLangLineStr.split(':') + + return { + content, + className, + markdownLang: markdownLang || FALLBACK_LANG, + relativePath + } } diff --git a/src/webview/components/chat/messages/markdown/code/block/index.tsx b/src/webview/components/chat/messages/markdown/code/block/index.tsx index 789da99..e69abdc 100644 --- a/src/webview/components/chat/messages/markdown/code/block/index.tsx +++ b/src/webview/components/chat/messages/markdown/code/block/index.tsx @@ -3,28 +3,28 @@ import { cn } from '@webview/utils/common' import { FileBlock } from './file-block' import type { BaseCodeBlockProps } from './helpers/types' -import { useChildrenInfo } from './helpers/use-children-info' +import { getContentInfoFromChildren } from './helpers/utils' import { MermaidBlock } from './mermaid-block' export interface CodeBlockProps extends Omit, Omit, 'content'> { children?: ReactNode + enableActionController?: boolean } export const CodeBlock: FC = ({ children, className, defaultExpanded = true, + enableActionController = false, ...rest }) => { - const { content, shikiLang, markdownLang, fileInfo, fileContent } = - useChildrenInfo(children) + const { content, markdownLang } = getContentInfoFromChildren(children) if (!content) return null const baseCodeBlockProps: BaseCodeBlockProps = { - content, className: cn('overflow-hidden my-2', className), defaultExpanded, ...rest @@ -32,15 +32,16 @@ export const CodeBlock: FC = ({ // Render Mermaid if enabled and language is mermaid if (markdownLang === 'mermaid') { - return + return } return ( + originalContent={content} + enableActionController={enableActionController} + > + {children} + ) } diff --git a/src/webview/components/chat/messages/markdown/code/block/mermaid-block/index.tsx b/src/webview/components/chat/messages/markdown/code/block/mermaid-block/index.tsx index 155ab01..f9726e6 100644 --- a/src/webview/components/chat/messages/markdown/code/block/mermaid-block/index.tsx +++ b/src/webview/components/chat/messages/markdown/code/block/mermaid-block/index.tsx @@ -1,14 +1,17 @@ +/* eslint-disable unused-imports/no-unused-vars */ import { useEffect, useState, type FC } from 'react' import { CopyIcon } from '@radix-ui/react-icons' import { Button } from '@webview/components/ui/button' +import { CollapsibleBlock } from '@webview/components/ui/collapsible-block' import mermaid from 'mermaid' import { useTheme } from 'next-themes' import { toast } from 'sonner' -import { CollapsibleBlock } from '../helpers/collapsible-block' import type { BaseCodeBlockProps } from '../helpers/types' -export interface MermaidBlockProps extends BaseCodeBlockProps {} +export interface MermaidBlockProps extends BaseCodeBlockProps { + content: string +} export const MermaidBlock: FC = ({ content, @@ -39,7 +42,7 @@ export const MermaidBlock: FC = ({ return ( = ({ children, className, style, + fontSize, headerMultiple, lineHeight, marginMultiple, allowHtml, enableLatex, - enableImageGallery, - variant = 'normal' + variant = 'normal', + + enableActionController = false }) => { const content = enableLatex ? fixMarkdownBold(escapeMhchem(escapeBrackets(children))) @@ -96,20 +98,22 @@ export const Markdown: FC = ({ data-code-type="markdown" style={customStyle} > - + ) => , - img: enableImageGallery - ? (props: ComponentProps<'img'>) => - ( - - ) as ReactNode - : undefined, - pre: (props: ComponentProps<'pre'>) => , + img: (props: ComponentProps<'img'>) => ( + + ), + pre: (props: ComponentProps<'pre'>) => ( + + ), code: (props: ComponentProps<'code'>) => , video: (props: ComponentProps<'video'>) => (
) } - -export const ChatAIMessage = WithPluginProvider(_ChatAIMessage) diff --git a/src/webview/components/chat/messages/roles/chat-human-message.tsx b/src/webview/components/chat/messages/roles/chat-human-message.tsx index 520dcc8..ec1464e 100644 --- a/src/webview/components/chat/messages/roles/chat-human-message.tsx +++ b/src/webview/components/chat/messages/roles/chat-human-message.tsx @@ -10,10 +10,11 @@ import type { Conversation } from '@shared/entities' import { ChatInput, ChatInputMode, - type ChatInputProps, - type ChatInputRef + type ChatInputEditorRef, + type ChatInputProps } from '@webview/components/chat/editor/chat-input' import { BorderBeam } from '@webview/components/ui/border-beam' +import { useChatContext } from '@webview/contexts/chat-context' import { useConversation } from '@webview/hooks/chat/use-conversation' import type { ConversationUIState } from '@webview/types/chat' import { cn } from '@webview/utils/common' @@ -24,10 +25,7 @@ export interface ChatHumanMessageRef extends HTMLDivElement { } export interface ChatHumanMessageProps - extends Pick< - ChatInputProps, - 'context' | 'setContext' | 'conversation' | 'onSend' - >, + extends Pick, ConversationUIState { ref?: Ref className?: string @@ -42,20 +40,19 @@ export const ChatHumanMessage: FC = props => { isEditMode = false, sendButtonDisabled, onEditModeChange, - context, - setContext, conversation: initialConversation, onSend, className, style } = props - const chatInputRef = useRef(null) - const divRef = useRef(null) + const { context, setContext } = useChatContext() + const editorRef = useRef(null) + const containerRef = useRef(null) useImperativeHandle(ref, () => - Object.assign(divRef.current!, { - copy: () => chatInputRef.current?.copyWithFormatting() + Object.assign(containerRef.current!, { + copy: () => editorRef.current?.copyWithFormatting() }) ) @@ -68,13 +65,13 @@ export const ChatHumanMessage: FC = props => { if (isEditMode) { // i don't know why this is needed setTimeout(() => { - chatInputRef.current?.focusOnEditor(true) + editorRef.current?.focusOnEditor(true) }, 0) } }, [isEditMode]) return ( -
+
= props => { style={{ willChange: 'auto' }} > = ({ log, className, children }) => ( +}> = ({ title, content, className, children }) => ( -
-
-

{log.title}

+
+
+

{title}

- {log.content && ( + {content && (
- {log.content} + {content}
)} @@ -52,41 +52,19 @@ export const ChatLogPreview: FC<{ ) -export const ChatAIMessageLogPreview: FC<{ conversation: Conversation }> = ({ - conversation -}) => { - const logs = toLogWithAgent(conversation) - - const customRenderLogPreview = usePluginCustomRenderLogPreview() - - if (logs.length === 0) return null - - return ( -
- {logs.map((log, index) => ( -
- {log.agent && customRenderLogPreview ? ( - customRenderLogPreview({ log }) - ) : ( - - )} -
- ))} -
- ) -} - -export const ChatAIMessageLogAccordion: FC<{ +export const ChatThinks: FC<{ conversation: Conversation isLoading: boolean }> = ({ conversation, isLoading }) => { - if (!conversation.logs.length) return null + const { thinkAgents: agents } = conversation + + if (agents.length === 0) return null const getAccordionTriggerTitle = () => { if (!isLoading) return 'Thought' - if (conversation.logs.length > 0) - return conversation.logs.at(-1)?.title || 'Thinking...' + if (conversation.thinkAgents.length > 0) + return conversation.thinkAgents.at(-1)?.name || 'Thinking...' return 'Thinking...' } @@ -111,7 +89,13 @@ export const ChatAIMessageLogAccordion: FC<{ - +
+ {agents.map((agent, index) => ( +
+ {agent ? : null} +
+ ))} +
) diff --git a/src/webview/components/chat/messages/toolbars/base-toolbar.tsx b/src/webview/components/chat/messages/toolbars/base-toolbar.tsx index 076fbb8..54b5c07 100644 --- a/src/webview/components/chat/messages/toolbars/base-toolbar.tsx +++ b/src/webview/components/chat/messages/toolbars/base-toolbar.tsx @@ -8,6 +8,7 @@ export interface BaseToolbarProps { scrollContentRef: React.RefObject className?: string bottomOffset?: number + scrollContentBottomBlankHeight?: number buildChildren: (props: { isFloating: boolean }) => React.ReactNode } @@ -15,7 +16,8 @@ export const BaseToolbar: FC = ({ messageRef, scrollContentRef, className, - bottomOffset = 16, + bottomOffset = 20, + scrollContentBottomBlankHeight = 0, buildChildren }) => { const [isFloating, setIsFloating] = useState(false) @@ -29,18 +31,20 @@ export const BaseToolbar: FC = ({ const messageRect = messageRef.current.getBoundingClientRect() const containerRect = scrollContentRef.current.getBoundingClientRect() + const containerBottom = + containerRect.bottom - scrollContentBottomBlankHeight if (messageRect.height === 0 || containerRect.height === 0) return - const isPartiallyOutBottom = messageRect.bottom > containerRect.bottom - const isTopVisible = messageRect.top < containerRect.bottom + const isPartiallyOutBottom = messageRect.bottom > containerBottom + const isTopVisible = messageRect.top < containerBottom const shouldFloat = isPartiallyOutBottom && isTopVisible setIsFloating(shouldFloat) if (shouldFloat) { - const bottom = window.innerHeight - containerRect.bottom + bottomOffset + const bottom = window.innerHeight - containerBottom + bottomOffset const left = containerRect.left + containerRect.width / 2 setFloatingPosition({ bottom, left }) @@ -91,16 +95,14 @@ export const BaseToolbar: FC = ({ } }, [checkShouldFloat]) - const toolbarContent = ( -
- {buildChildren({ isFloating })} -
- ) - return ( <> {/* Static version - always rendered */} -
{toolbarContent}
+
+
+ {buildChildren({ isFloating: false })} +
+
{/* Fixed version - rendered when floating */} {isFloating && ( @@ -111,7 +113,7 @@ export const BaseToolbar: FC = ({ left: floatingPosition.left, transform: 'translateX(-50%)' }} - className="z-40 relative shadow-lg" + className="z-[1] relative shadow-lg" > {/* blur overlay */}
= ({ backdropFilter: 'blur(4px)' }} /> - {toolbarContent} +
+ {buildChildren({ isFloating: true })} +
)} diff --git a/src/webview/components/content-preview.tsx b/src/webview/components/content-preview.tsx index c7ecff5..1c3050f 100644 --- a/src/webview/components/content-preview.tsx +++ b/src/webview/components/content-preview.tsx @@ -7,7 +7,7 @@ import { getExtFromPath } from '@webview/utils/path' import { getShikiLanguageFromPath } from '@webview/utils/shiki' import { Markdown } from './chat/messages/markdown' -import { Highlighter } from './chat/messages/markdown/code/block/helpers/highlighter' +import { Highlighter } from './ui/highlighter' export type PreviewContent = | { type: 'text'; content: string } diff --git a/src/webview/components/settings/custom-renders/prompt-snippet-management/prompt-snippet-card.tsx b/src/webview/components/settings/custom-renders/prompt-snippet-management/prompt-snippet-card.tsx index 1c1f108..59934af 100644 --- a/src/webview/components/settings/custom-renders/prompt-snippet-management/prompt-snippet-card.tsx +++ b/src/webview/components/settings/custom-renders/prompt-snippet-management/prompt-snippet-card.tsx @@ -1,6 +1,6 @@ import type { PromptSnippetWithSaveType } from '@extension/actions/prompt-snippet-actions' import { Pencil2Icon, TrashIcon } from '@radix-ui/react-icons' -import { getAllTextFromLangchainMessageContents } from '@shared/utils/get-all-text-from-langchain-message-contents' +import { getAllTextFromConversationContents } from '@shared/utils/chat-context-helper/common/get-all-text-from-conversation-contents' import { AlertAction } from '@webview/components/ui/alert-action' import { Button } from '@webview/components/ui/button' import { Checkbox } from '@webview/components/ui/checkbox' @@ -62,7 +62,7 @@ export const PromptSnippetCard = ({
- {getAllTextFromLangchainMessageContents(snippet.contents)} + {getAllTextFromConversationContents(snippet.contents)}
) diff --git a/src/webview/components/chat/messages/markdown/code/block/helpers/collapsible-block.tsx b/src/webview/components/ui/collapsible-block.tsx similarity index 54% rename from src/webview/components/chat/messages/markdown/code/block/helpers/collapsible-block.tsx rename to src/webview/components/ui/collapsible-block.tsx index cd555ef..87fe557 100644 --- a/src/webview/components/chat/messages/markdown/code/block/helpers/collapsible-block.tsx +++ b/src/webview/components/ui/collapsible-block.tsx @@ -1,24 +1,42 @@ import React, { ReactNode, useState } from 'react' -import { ChevronDownIcon, ChevronUpIcon, CodeIcon } from '@radix-ui/react-icons' +import { + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, + CodeIcon, + Cross2Icon, + DotFilledIcon +} from '@radix-ui/react-icons' import { Button } from '@webview/components/ui/button' import { AnimatePresence, motion } from 'framer-motion' +export type CollapsibleBlockStatus = + | 'idle' + | 'loading' + | 'waiting' + | 'success' + | 'error' + export interface CollapsibleBlockProps { children: ReactNode title: ReactNode - actions?: ReactNode + actionSlot?: ReactNode + statusSlot?: ReactNode defaultExpanded?: boolean - isLoading?: boolean + status?: CollapsibleBlockStatus className?: string + onClickTitle?: () => void } export const CollapsibleBlock: React.FC = ({ children, title, - actions, + actionSlot: actions, + statusSlot, defaultExpanded = false, - isLoading, - className = '' + status = 'idle', + className = '', + onClickTitle }) => { const [isExpanded, setIsExpanded] = useState(defaultExpanded) @@ -27,12 +45,74 @@ export const CollapsibleBlock: React.FC = ({
setIsExpanded(!isExpanded)} + onClick={() => + onClickTitle ? onClickTitle() : setIsExpanded(!isExpanded) + } > {typeof title === 'string' ? : null} {title} + + {statusSlot ? ( +
{ + e.stopPropagation() + }} + > + {statusSlot} +
+ ) : null} + + {status === 'loading' && ( + + )} + + {status === 'waiting' && ( + + )} + + {status === 'success' && ( + + )} + + {status === 'error' && ( + + )}
-
+ +
{actions} - - {isLoading && ( - - )}
diff --git a/src/webview/components/chat/messages/markdown/code/block/helpers/highlighter.tsx b/src/webview/components/ui/highlighter.tsx similarity index 73% rename from src/webview/components/chat/messages/markdown/code/block/helpers/highlighter.tsx rename to src/webview/components/ui/highlighter.tsx index a9113b9..eb575d2 100644 --- a/src/webview/components/chat/messages/markdown/code/block/helpers/highlighter.tsx +++ b/src/webview/components/ui/highlighter.tsx @@ -2,18 +2,18 @@ import type { FC } from 'react' import { useShikiHighlighter } from '@webview/hooks/use-shiki-highlighter' import parse from 'html-react-parser' -import type { BaseCodeBlockProps } from './types' - -export interface HighlighterProps - extends Omit { +export interface HighlighterProps { language: string + content: string + style?: React.CSSProperties + className?: string } export const Highlighter: FC = ({ style, - className, language, - content + content, + className }) => { const { highlightedCode } = useShikiHighlighter({ code: content, diff --git a/src/webview/components/ui/shine-border.tsx b/src/webview/components/ui/shine-border.tsx index ee38f56..8a24572 100644 --- a/src/webview/components/ui/shine-border.tsx +++ b/src/webview/components/ui/shine-border.tsx @@ -50,7 +50,7 @@ export const ShineBorder = ({ } as React.CSSProperties } className={cn( - 'relative grid w-fit place-items-center rounded-[--border-radius] overflow-hidden', + 'relative z-0 grid w-fit place-items-center rounded-[--border-radius] overflow-hidden', className )} > diff --git a/src/webview/components/ui/split-accordion.tsx b/src/webview/components/ui/split-accordion.tsx index 54bee29..854928d 100644 --- a/src/webview/components/ui/split-accordion.tsx +++ b/src/webview/components/ui/split-accordion.tsx @@ -77,7 +77,7 @@ export const SplitAccordionTrigger: React.FC = ({