From 1b0957df7bf9d40838d496791f8f1fe6f97a7548 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Thu, 5 Oct 2023 20:51:47 +0200 Subject: [PATCH 1/2] Workflow summarizer example --- .../workflow-summarize-openai/.env.template | 2 + examples/workflow-summarize-openai/machine.ts | 95 +++++++++ .../workflow-summarize-openai/package.json | 16 ++ .../workflow-summarize-openai/pnpm-lock.yaml | 188 ++++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 examples/workflow-summarize-openai/.env.template create mode 100644 examples/workflow-summarize-openai/machine.ts create mode 100644 examples/workflow-summarize-openai/package.json create mode 100644 examples/workflow-summarize-openai/pnpm-lock.yaml diff --git a/examples/workflow-summarize-openai/.env.template b/examples/workflow-summarize-openai/.env.template new file mode 100644 index 0000000000..f1ebd355ef --- /dev/null +++ b/examples/workflow-summarize-openai/.env.template @@ -0,0 +1,2 @@ +OPENAI_ORGANIZATION="..." +OPENAI_API_KEY="..." diff --git a/examples/workflow-summarize-openai/machine.ts b/examples/workflow-summarize-openai/machine.ts new file mode 100644 index 0000000000..953275e845 --- /dev/null +++ b/examples/workflow-summarize-openai/machine.ts @@ -0,0 +1,95 @@ +import { assign, createMachine, fromPromise } from 'xstate'; +import OpenAI from 'openai'; + +const openai = new OpenAI({ + organization: process.env.OPENAI_ORGANIZATION, + apiKey: process.env.OPENAI_API_KEY +}); + +const createChatCompletion = fromPromise( + ({ + input + }: { + input: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming; + }) => openai.chat.completions.create(input) +); + +const createSlackPost = fromPromise( + async ({ + input + }: { + input: { + channel: string; + }; + }) => { + // TODO: add Slack integration here + } +); + +export const machine = createMachine({ + id: 'openai-summarizer', + types: { + input: {} as { + text: string; + }, + context: {} as { + text: string; + summary: string | null; + }, + actors: {} as + | { + src: 'OpenAI.createChatCompletion'; + logic: typeof createChatCompletion; + } + | { + src: 'Slack.postMessage'; + logic: typeof createSlackPost; + } + }, + context: ({ input }) => ({ + text: input.text, + summary: null + }), + initial: 'Generating summary', + states: { + 'Generating summary': { + invoke: { + src: 'OpenAI.createChatCompletion', + input: ({ context }) => ({ + model: 'gpt-3.5-turbo-16k', + messages: [ + { + role: 'user', + content: `Summarize the following text with the most unique and helpful points, into a numbered list of key points and takeaways: \n ${context.text}` + } + ] + }), + onDone: { + target: 'Posting to Slack', + actions: assign({ + summary: ({ event }) => event.output.choices[0].message.content + }) + }, + onError: { + target: 'error' + } + } + }, + 'Posting to Slack': { + invoke: { + src: 'Slack.postMessage', + input: ({ context }) => ({ + channel: '', + text: context.summary + }) + }, + onDone: { target: 'final' } + }, + error: { + // ... + }, + done: { + type: 'final' + } + } +}); diff --git a/examples/workflow-summarize-openai/package.json b/examples/workflow-summarize-openai/package.json new file mode 100644 index 0000000000..58637bfad0 --- /dev/null +++ b/examples/workflow-summarize-openai/package.json @@ -0,0 +1,16 @@ +{ + "name": "workflow-summarize-openai", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "openai": "^4.11.1", + "xstate": "5.0.0-beta.33" + } +} diff --git a/examples/workflow-summarize-openai/pnpm-lock.yaml b/examples/workflow-summarize-openai/pnpm-lock.yaml new file mode 100644 index 0000000000..a241f19ba7 --- /dev/null +++ b/examples/workflow-summarize-openai/pnpm-lock.yaml @@ -0,0 +1,188 @@ +lockfileVersion: '6.0' + +dependencies: + openai: + specifier: ^4.11.1 + version: 4.11.1 + xstate: + specifier: 5.0.0-beta.33 + version: 5.0.0-beta.33 + +packages: + + /@types/node-fetch@2.6.6: + resolution: {integrity: sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==} + dependencies: + '@types/node': 18.18.3 + form-data: 4.0.0 + dev: false + + /@types/node@18.18.3: + resolution: {integrity: sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==} + dev: false + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /base-64@0.1.0: + resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} + dev: false + + /charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + dev: false + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + dev: false + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /digest-fetch@1.3.0: + resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==} + dependencies: + base-64: 0.1.0 + md5: 2.3.0 + dev: false + + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + + /is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: false + + /md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + dev: false + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /openai@4.11.1: + resolution: {integrity: sha512-GU0HQWbejXuVAQlDjxIE8pohqnjptFDIm32aPlNT1H9ucMz1VJJD0DaTJRQsagNaJ97awWjjVLEG7zCM6sm4SA==} + hasBin: true + dependencies: + '@types/node': 18.18.3 + '@types/node-fetch': 2.6.6 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + digest-fetch: 1.3.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + + /xstate@5.0.0-beta.33: + resolution: {integrity: sha512-zHwbY2d2GGrsIySUCybrlq6YAPGM20yKpvliroDqfSbwa255Z1d7RYLkbbxiLx8SnEwDpWVple7JTXkjOw3JLA==} + dev: false From a5cefe9ef609e99a0bcba814814d1b2e08da2d92 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Sat, 7 Oct 2023 21:10:34 -0400 Subject: [PATCH 2/2] Slack linear workflow --- examples/workflow-slack-linear/.env.template | 2 + examples/workflow-slack-linear/machine.ts | 125 ++++++++++++++++++ examples/workflow-slack-linear/package.json | 16 +++ examples/workflow-slack-linear/pnpm-lock.yaml | 79 +++++++++++ 4 files changed, 222 insertions(+) create mode 100644 examples/workflow-slack-linear/.env.template create mode 100644 examples/workflow-slack-linear/machine.ts create mode 100644 examples/workflow-slack-linear/package.json create mode 100644 examples/workflow-slack-linear/pnpm-lock.yaml diff --git a/examples/workflow-slack-linear/.env.template b/examples/workflow-slack-linear/.env.template new file mode 100644 index 0000000000..f1ebd355ef --- /dev/null +++ b/examples/workflow-slack-linear/.env.template @@ -0,0 +1,2 @@ +OPENAI_ORGANIZATION="..." +OPENAI_API_KEY="..." diff --git a/examples/workflow-slack-linear/machine.ts b/examples/workflow-slack-linear/machine.ts new file mode 100644 index 0000000000..f04f22fb5e --- /dev/null +++ b/examples/workflow-slack-linear/machine.ts @@ -0,0 +1,125 @@ +import { assign, createMachine, fromPromise } from 'xstate'; + +const createSlackPost = fromPromise( + async ({ + input + }: { + input: { + channel: string; + }; + }) => { + // TODO: add Slack integration here + } +); + +export const machine = createMachine({ + id: 'linear-issues-daily-slack-alert', + types: { + input: {} as { + text: string; + }, + context: {} as { + issues: { nodes: any[] } | null; + }, + actors: {} as + | { + src: 'Linear.getIssues'; + logic: any; + } + | { + src: 'Slack.postMessage'; + logic: typeof createSlackPost; + } + | { + src: 'cron'; + logic: any; + }, + events: {} as { type: 'cron' } + }, + context: ({ input }) => ({ + issues: null + }), + invoke: { + src: 'cron', + input: { + cron: '0 9 * * 1,2,3,4,5' + } + }, + initial: 'Get in-progress issues', + states: { + idle: { + on: { + cron: { target: 'Get in-progress issues' } + } + }, + 'Get in-progress issues': { + invoke: { + src: 'Linear.getIssues', + input: ({ context }) => ({ + first: 20, + filter: { + team: { + id: { + // To get your Team id from within Linear, hit CMD+K and "Copy model UUID" + eq: '' + } + }, + assignee: { + email: { + eq: '' + } + }, + state: { + name: { + eq: 'In Progress' + } + } + } + }), + onDone: { + actions: assign({ + issues: ({ event }) => event.output + }), + target: 'Post to Slack' + } + } + }, + 'Post to Slack': { + invoke: { + src: 'Slack.postMessage', + input: ({ context }) => ({ + channel: process.env.SLACK_CHANNEL_ID!, + // Include text for notifications and blocks to get a rich Slack message in the channel + text: `You have ${ + context.issues!.nodes.length + } 'In Progress' issues in Linear!`, + // Create rich Slack messages with the Block Kit builder https://app.slack.com/block-kit-builder/ + blocks: context.issues!.nodes.flatMap((issue) => [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `⏳ *${issue.title}*` + }, + accessory: { + type: 'button', + text: { + type: 'plain_text', + text: 'View issue', + emoji: true + }, + value: 'click_me_123', + url: issue.url, + action_id: 'button-action' + } + }, + { + type: 'divider' + } + ]) + }) + }, + onDone: { target: 'idle' } + } + } +}); diff --git a/examples/workflow-slack-linear/package.json b/examples/workflow-slack-linear/package.json new file mode 100644 index 0000000000..497469f9ed --- /dev/null +++ b/examples/workflow-slack-linear/package.json @@ -0,0 +1,16 @@ +{ + "name": "workflow-slack-linear", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@linear/sdk": "^8.0.0", + "xstate": "5.0.0-beta.33" + } +} diff --git a/examples/workflow-slack-linear/pnpm-lock.yaml b/examples/workflow-slack-linear/pnpm-lock.yaml new file mode 100644 index 0000000000..d8c1d44c16 --- /dev/null +++ b/examples/workflow-slack-linear/pnpm-lock.yaml @@ -0,0 +1,79 @@ +lockfileVersion: '6.0' + +dependencies: + '@linear/sdk': + specifier: ^8.0.0 + version: 8.0.0 + xstate: + specifier: 5.0.0-beta.33 + version: 5.0.0-beta.33 + +packages: + + /@graphql-typed-document-node/core@3.2.0(graphql@15.8.0): + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + graphql: 15.8.0 + dev: false + + /@linear/sdk@8.0.0: + resolution: {integrity: sha512-crNMJuQVIrouUuRrmmWCZgReQ44rthFPpFOOzqJxHmZreS3xhGF3x2JHVfxz3m17vQwum2LBCGsMBEESyiwm5A==} + engines: {node: '>=12.x', yarn: 1.x} + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@15.8.0) + graphql: 15.8.0 + isomorphic-unfetch: 3.1.0 + transitivePeerDependencies: + - encoding + dev: false + + /graphql@15.8.0: + resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} + engines: {node: '>= 10.x'} + dev: false + + /isomorphic-unfetch@3.1.0: + resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} + dependencies: + node-fetch: 2.7.0 + unfetch: 4.2.0 + transitivePeerDependencies: + - encoding + dev: false + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /unfetch@4.2.0: + resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + + /xstate@5.0.0-beta.33: + resolution: {integrity: sha512-zHwbY2d2GGrsIySUCybrlq6YAPGM20yKpvliroDqfSbwa255Z1d7RYLkbbxiLx8SnEwDpWVple7JTXkjOw3JLA==} + dev: false