diff --git a/legacy/examples/human-feedback-select.ts b/legacy/examples/human-feedback-select.ts new file mode 100644 index 00000000..60c4677c --- /dev/null +++ b/legacy/examples/human-feedback-select.ts @@ -0,0 +1,32 @@ +import 'dotenv/config' +import { OpenAIClient } from 'openai-fetch' +import { z } from 'zod' + +import { Agentic, HumanFeedbackSelect } from '../src' + +async function main() { + const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) + const ai = new Agentic({ openai }) + + const jokes = ai + .gpt3(`Tell me {{num}} jokes about {{topic}}`) + .input( + z.object({ + topic: z.string(), + num: z.number().int().default(5).optional() + }) + ) + .output(z.array(z.string())) + .modelParams({ temperature: 0.9 }) + + const feedback = new HumanFeedbackSelect(z.string()) + let out = await jokes.call({ topic: 'statisticians' }) + let hf = await feedback.call(out) + while (!hf.accepted) { + out = await jokes.call({ topic: 'statisticians' }) + hf = await feedback.call(out) + } + console.log(hf.results) +} + +main() diff --git a/legacy/examples/human-feedback.ts b/legacy/examples/human-feedback.ts new file mode 100644 index 00000000..d9646303 --- /dev/null +++ b/legacy/examples/human-feedback.ts @@ -0,0 +1,33 @@ +import 'dotenv/config' +import { OpenAIClient } from 'openai-fetch' +import { z } from 'zod' + +import { Agentic, HumanFeedbackSingle } from '../src' + +async function main() { + const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) + const ai = new Agentic({ openai }) + + const topicFacts = ai + .gpt3(`Give me {{numFacts}} random facts about {{topic}}`) + .input( + z.object({ + topic: z.string(), + numFacts: z.number().int().default(5).optional() + }) + ) + .output(z.object({ facts: z.array(z.string()) })) + .modelParams({ temperature: 0.9 }) + + const feedback = new HumanFeedbackSingle(topicFacts.outputSchema) + + let out = await topicFacts.call({ topic: 'cats' }) + let hf = await feedback.call(out) + while (!hf.accepted) { + out = await topicFacts.call({ topic: 'cats' }) + hf = await feedback.call(out) + } + console.log(hf.result) +} + +main() diff --git a/legacy/package.json b/legacy/package.json index 5aee3a6c..fa87d135 100644 --- a/legacy/package.json +++ b/legacy/package.json @@ -39,6 +39,9 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.4.3", + "@inquirer/checkbox": "^1.3.0", + "@inquirer/editor": "^1.1.0", + "@inquirer/select": "^1.2.0", "handlebars": "^4.7.7", "js-tiktoken": "^1.0.6", "jsonrepair": "^3.1.0", diff --git a/legacy/pnpm-lock.yaml b/legacy/pnpm-lock.yaml index 2f3fda0a..5a9ae174 100644 --- a/legacy/pnpm-lock.yaml +++ b/legacy/pnpm-lock.yaml @@ -8,6 +8,15 @@ dependencies: '@anthropic-ai/sdk': specifier: ^0.4.3 version: 0.4.3 + '@inquirer/checkbox': + specifier: ^1.3.0 + version: 1.3.0 + '@inquirer/editor': + specifier: ^1.1.0 + version: 1.1.0 + '@inquirer/select': + specifier: ^1.2.0 + version: 1.2.0 handlebars: specifier: ^4.7.7 version: 4.7.7 @@ -534,6 +543,60 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@inquirer/checkbox@1.3.0: + resolution: {integrity: sha512-kfYE5BH7vO0j2IwSgxzDmKPzQm/OpLnIZEEbOetYM+k4+YKTbSqeqCu7VZl3d8/rtotgJQc7gb8u2pIVeJh3Mg==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 2.0.0 + '@inquirer/type': 1.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/core@2.0.0: + resolution: {integrity: sha512-NnLGihYWEFVdFIoEDPwGO0jB5phuNcxTUHSNq5geyiOVQOnWNuX9x2rhPPeiikE/5fNXIBmqojD0+PiD9whtXw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/type': 1.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.0 + cli-width: 4.0.0 + figures: 3.2.0 + mute-stream: 1.0.0 + run-async: 3.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + + /@inquirer/editor@1.1.0: + resolution: {integrity: sha512-ll6NfzutAuzRwNELERhECZCnAIzb2DdnRaNwtJ3Gfy6MrQBdYpFzGLwDyxB8+yf2iJoMOmsKWnzGbmvWNvSUhw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 2.0.0 + '@inquirer/type': 1.1.0 + chalk: 4.1.2 + external-editor: 3.1.0 + dev: false + + /@inquirer/select@1.2.0: + resolution: {integrity: sha512-2CqhtE40GFmRXDFzJeMvSowKcO2/yvIzgSpL44+Hl/SAO/1FJgmHNAFGBuqX0RbohYPnSpF8eftgiy16fA3RJw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 2.0.0 + '@inquirer/type': 1.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/type@1.1.0: + resolution: {integrity: sha512-XMaorygt2o/mXinZg/OOz6d3JKuV3o4jRc/3KDiVPeKLLkjiO4iJErbLKtKn+Od2ZC2lbiFQkrIuloVpEubisA==} + engines: {node: '>=14.18.0'} + dev: false + /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} dev: true @@ -865,12 +928,10 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.21.3 - dev: true /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} @@ -889,7 +950,6 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -1128,13 +1188,16 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /chalk@5.2.0: resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -1187,6 +1250,11 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-spinners@2.9.0: + resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} + engines: {node: '>=6'} + dev: false + /cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -1203,6 +1271,11 @@ packages: string-width: 5.1.2 dev: true + /cli-width@4.0.0: + resolution: {integrity: sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==} + engines: {node: '>= 12'} + dev: false + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1235,7 +1308,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -1243,7 +1315,6 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -1433,7 +1504,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1541,7 +1611,6 @@ packages: /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} @@ -1706,6 +1775,15 @@ packages: engines: {node: '>=12.0.0'} dev: true + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1739,6 +1817,13 @@ packages: reusify: 1.0.4 dev: true + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: false + /figures@5.0.0: resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} engines: {node: '>=14'} @@ -1982,7 +2067,6 @@ packages: /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true /has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} @@ -2050,6 +2134,13 @@ packages: hasBin: true dev: true + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /ignore-by-default@2.1.0: resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} engines: {node: '>=10 <11 || >=12 <13 || >=14'} @@ -2188,7 +2279,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -2678,6 +2768,11 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: false + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -2846,6 +2941,11 @@ packages: word-wrap: 1.2.3 dev: true + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: false + /p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} @@ -3247,6 +3347,11 @@ packages: fsevents: 2.3.2 dev: true + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: false + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -3267,6 +3372,10 @@ packages: is-regex: 1.1.4 dev: true + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + /semver@5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -3469,7 +3578,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -3519,7 +3627,6 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} @@ -3591,7 +3698,6 @@ packages: engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - dev: true /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} @@ -3629,6 +3735,13 @@ packages: engines: {node: '>=4'} dev: true + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: false + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -3760,7 +3873,6 @@ packages: /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: true /type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} @@ -3903,7 +4015,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} diff --git a/legacy/src/index.ts b/legacy/src/index.ts index 45bc925a..0378c345 100644 --- a/legacy/src/index.ts +++ b/legacy/src/index.ts @@ -7,3 +7,4 @@ export * from './tokenizer' export * from './services/metaphor' export * from './tools/metaphor' +export * from './tools/feedback' diff --git a/legacy/src/tools/feedback.ts b/legacy/src/tools/feedback.ts new file mode 100644 index 00000000..a76e2a5c --- /dev/null +++ b/legacy/src/tools/feedback.ts @@ -0,0 +1,232 @@ +import checkbox from '@inquirer/checkbox' +import editor from '@inquirer/editor' +import select from '@inquirer/select' +import { ZodTypeAny, z } from 'zod' + +import * as types from '../types' +import { BaseTask } from '../task' + +/** + * Actions the user can take in the feedback selection prompt. + */ +const UserActions = { + Accept: 'accept', + Edit: 'edit', + Decline: 'decline', + Select: 'select', + Exit: 'exit' +} as const + +type UserActions = (typeof UserActions)[keyof typeof UserActions] + +/** + * Messages to display to the user for each action. + */ +const UserActionMessages: Record = { + [UserActions.Accept]: 'Accept inputs', + [UserActions.Edit]: 'Edit (open in editor)', + [UserActions.Decline]: 'Decline', + [UserActions.Select]: 'Select inputs to keep', + [UserActions.Exit]: 'Exit' +} + +/** + * Prompt the user to select one of a list of options. + */ +async function askUser( + message: string, + choices: UserActions[] +): Promise { + return await select({ + message, + choices: choices.map((choice) => ({ + name: UserActionMessages[choice], + value: choice + })) + }) +} + +/** + * Output schema when prompting the user to accept, edit, or decline a single input. + */ +export const FeedbackSingleOutputSchema = (result: T) => + z.object({ + result: result, + accepted: z.boolean() + }) + +/** + * Prompt the user to accept, edit, or decline a single input. + */ +export class HumanFeedbackSingle extends BaseTask< + ZodTypeAny, + ZodTypeAny +> { + private choiceSchema: T + + constructor(choiceSchema: T) { + super() + this.choiceSchema = choiceSchema + } + + public get inputSchema() { + return this.choiceSchema + } + + public get outputSchema() { + return FeedbackSingleOutputSchema(this.choiceSchema) + } + + private actionHandlers = { + [UserActions.Accept]: ( + input: types.ParsedData + ) => ({ result: input, accepted: true }), + [UserActions.Edit]: async ( + input: types.ParsedData + ) => { + const editedInput = await editor({ + message: 'Edit the input:', + default: JSON.stringify(input) + }) + return this.outputSchema.parse({ + result: JSON.parse(editedInput), + accepted: true + }) + }, + [UserActions.Decline]: () => ({ result: null, accepted: false }), + [UserActions.Exit]: () => { + throw new Error('Exiting...') + } + } + + /** + * Prompts the user to give feedback for the given input and handles their response. + */ + public async call( + input: types.ParsedData + ): Promise> { + try { + input = this.inputSchema.parse(input) + const msg = [ + 'The following input was generated:', + JSON.stringify(input, null, 2), + 'What would you like to do?' + ].join('\n') + const feedback = await askUser(msg, [ + UserActions.Accept, + UserActions.Edit, + UserActions.Decline, + UserActions.Exit + ]) + const handler = this.actionHandlers[feedback] + if (!handler) { + throw new Error(`Unexpected feedback: ${feedback}`) + } + return handler(input) + } catch (err) { + console.error('Error parsing input:', err) + throw err + } + } +} + +/** + * Output schema when prompting the user to accept, select from, edit, or decline a list of inputs. + */ +export const FeedbackSelectOutputSchema = (result: T) => + z.object({ + results: z.array(result), + accepted: z.boolean() + }) + +/** + * Prompt the user to accept, select from, edit, or decline a list of inputs. + */ +export class HumanFeedbackSelect extends BaseTask< + ZodTypeAny, + ZodTypeAny +> { + private choiceSchema: T + + constructor(choiceSchema: T) { + super() + this.choiceSchema = choiceSchema + } + + public get inputSchema() { + return z.array(this.choiceSchema) + } + + public get outputSchema() { + return FeedbackSelectOutputSchema(this.choiceSchema) + } + + private actionHandlers = { + [UserActions.Accept]: ( + input: types.ParsedData + ) => ({ results: input, accepted: true }), + [UserActions.Edit]: async ( + input: types.ParsedData + ) => { + const editedInput = await editor({ + message: 'Edit the input:', + default: JSON.stringify(input, null, 2) + }) + return this.outputSchema.parse({ + results: JSON.parse(editedInput), + accepted: true + }) + }, + [UserActions.Select]: async ( + input: types.ParsedData + ) => { + const choices = input.map((completion) => ({ + name: completion, + value: completion + })) + const chosen = await checkbox({ + message: 'Pick items to keep:', + choices, + pageSize: choices.length + }) + return { results: chosen.length === 0 ? [] : chosen, accepted: true } + }, + [UserActions.Decline]: () => ({ results: [], accepted: false }), + [UserActions.Exit]: () => { + throw new Error('Exiting...') + } + } + + /** + * Prompts the user to give feedback for the given list of inputs and handles their response. + */ + public async call( + input: types.ParsedData + ): Promise> { + try { + input = this.inputSchema.parse(input) + const message = [ + 'The following inputs were generated:', + ...input.map( + (choice, index) => `${index + 1}. ${JSON.stringify(choice, null, 2)}` + ), + 'What would you like to do?' + ].join('\n') + const feedback = await askUser(message, [ + UserActions.Accept, + UserActions.Select, + UserActions.Edit, + UserActions.Decline, + UserActions.Exit + ]) + const handler = this.actionHandlers[feedback] + if (!handler) { + throw new Error(`Unexpected feedback: ${feedback}`) + } + return handler(input) + } catch (err) { + console.error('Error parsing input:', err) + throw err + } + } +}