2
0

♻️ Migrate from got to ky (#1416)

Closes #1415
This commit is contained in:
Baptiste Arnaud
2024-04-05 09:01:16 +02:00
committed by GitHub
parent ccc7101dd3
commit d96f384e02
59 changed files with 990 additions and 628 deletions

1
.eslintignore Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -21,7 +21,7 @@ const injectViewerUrlIfVercelPreview = (val) => {
process.env.VERCEL_BUILDER_PROJECT_NAME,
process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME
)
if (process.env.NEXT_PUBLIC_CHAT_API_URL.includes('{{pr_id}}'))
if (process.env.NEXT_PUBLIC_CHAT_API_URL?.includes('{{pr_id}}'))
process.env.NEXT_PUBLIC_CHAT_API_URL =
process.env.NEXT_PUBLIC_CHAT_API_URL.replace(
'{{pr_id}}',
@@ -56,12 +56,14 @@ const nextConfig = {
if (nextRuntime === 'edge') {
config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false
config.resolve.alias['qrcode'] = false
return config
}
// These packages are imports from the integrations definition files that can be ignored for the client.
config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false
config.resolve.alias['openai'] = false
config.resolve.alias['qrcode'] = false
return config
},
headers: async () => {

View File

@@ -67,7 +67,7 @@
"framer-motion": "10.3.0",
"google-auth-library": "8.9.0",
"google-spreadsheet": "4.1.1",
"got": "12.6.0",
"ky": "1.2.3",
"immer": "10.0.2",
"jsonwebtoken": "9.0.1",
"libphonenumber-js": "1.10.37",

View File

@@ -32,7 +32,6 @@ export const getResultExample = authenticatedProcedure
})
)
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
console.log('user', user)
const typebot = (await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: {

View File

@@ -17,7 +17,7 @@ export const convertVariablesForTestToVariables = (
) as Variable
return { ...variable, value: parseVariableValue(variableForTest.value) }
}, {}),
]
].filter((v) => v.value)
}
const parseVariableValue = (value: string | undefined): string | string[] => {

View File

@@ -1,6 +1,5 @@
import { Variable, HttpResponse } from '@typebot.io/schemas'
import { sendRequest } from '@typebot.io/lib'
import { env } from '@typebot.io/env'
export const executeWebhook = (
typebotId: string,
@@ -8,7 +7,7 @@ export const executeWebhook = (
{ blockId }: { blockId: string }
) =>
sendRequest<HttpResponse>({
url: `${env.NEXT_PUBLIC_VIEWER_URL[0]}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook`,
url: `/api/typebots/${typebotId}/blocks/${blockId}/testWebhook`,
method: 'POST',
body: {
variables,

View File

@@ -5,7 +5,7 @@ import { z } from 'zod'
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
import got from 'got'
import ky from 'ky'
export const listProjects = authenticatedProcedure
.input(
@@ -58,7 +58,7 @@ export const listProjects = authenticatedProcedure
const url = 'https://api.zemantic.ai/v1/projects'
try {
const response = await got
const response = await ky
.get(url, {
headers: {
Authorization: `Bearer ${data.apiKey}`,

View File

@@ -3,7 +3,7 @@ import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import { customDomainSchema } from '@typebot.io/schemas/features/customDomains'
import got, { HTTPError } from 'got'
import ky, { HTTPError } from 'ky'
import { env } from '@typebot.io/env'
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
import { trackEvents } from '@typebot.io/telemetry/trackEvents'
@@ -61,12 +61,12 @@ export const createCustomDomain = authenticatedProcedure
try {
await createDomainOnVercel(name)
} catch (err) {
console.log(err)
if (err instanceof HTTPError && err.response.statusCode !== 409)
if (err instanceof HTTPError && err.response.status !== 409) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to create custom domain on Vercel',
})
}
}
const customDomain = await prisma.customDomain.create({
@@ -91,8 +91,12 @@ export const createCustomDomain = authenticatedProcedure
})
const createDomainOnVercel = (name: string) =>
got.post({
url: `https://api.vercel.com/v10/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains?teamId=${env.VERCEL_TEAM_ID}`,
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
json: { name },
})
ky.post(
`https://api.vercel.com/v10/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains?teamId=${env.VERCEL_TEAM_ID}`,
{
headers: {
authorization: `Bearer ${env.VERCEL_TOKEN}`,
},
json: { name },
}
)

View File

@@ -2,7 +2,7 @@ import prisma from '@typebot.io/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import got from 'got'
import ky from 'ky'
import { env } from '@typebot.io/env'
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
@@ -63,7 +63,9 @@ export const deleteCustomDomain = authenticatedProcedure
})
const deleteDomainOnVercel = (name: string) =>
got.delete({
url: `https://api.vercel.com/v9/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
})
ky.delete(
`https://api.vercel.com/v9/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
{
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
}
)

View File

@@ -377,6 +377,7 @@ const SystemUserToken = ({
<ListItem>Copy and paste the generated token:</ListItem>
<TextInput
isRequired
type="password"
label="System User Token"
defaultValue={initialToken}
onChange={(val) => setToken(val.trim())}

View File

@@ -1,6 +1,6 @@
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { z } from 'zod'
import got from 'got'
import ky from 'ky'
import prisma from '@typebot.io/lib/prisma'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { TRPCError } from '@trpc/server'
@@ -22,16 +22,13 @@ export const getPhoneNumber = authenticatedProcedure
code: 'NOT_FOUND',
message: 'Credentials not found',
})
const { display_phone_number } = (await got(
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}`,
{
const { display_phone_number } = await ky
.get(`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}`, {
headers: {
Authorization: `Bearer ${credentials.systemUserAccessToken}`,
},
}
).json()) as {
display_phone_number: string
}
})
.json<{ display_phone_number: string }>()
const formattedPhoneNumber = `${
display_phone_number.startsWith('+') ? '' : '+'

View File

@@ -1,6 +1,6 @@
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { z } from 'zod'
import got from 'got'
import ky from 'ky'
import { TRPCError } from '@trpc/server'
import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp'
import prisma from '@typebot.io/lib/prisma'
@@ -28,21 +28,23 @@ export const getSystemTokenInfo = authenticatedProcedure
})
const {
data: { expires_at, scopes, app_id, application },
} = (await got(
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/debug_token?input_token=${credentials.systemUserAccessToken}`,
{
headers: {
Authorization: `Bearer ${credentials.systemUserAccessToken}`,
},
}
).json()) as {
data: {
app_id: string
application: string
expires_at: number
scopes: string[]
}
}
} = await ky
.get(
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/debug_token?input_token=${credentials.systemUserAccessToken}`,
{
headers: {
Authorization: `Bearer ${credentials.systemUserAccessToken}`,
},
}
)
.json<{
data: {
app_id: string
application: string
expires_at: number
scopes: string[]
}
}>()
return {
appId: app_id,

View File

@@ -3,7 +3,7 @@ import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { startSession } from '@typebot.io/bot-engine/startSession'
import { env } from '@typebot.io/env'
import { HTTPError } from 'got'
import { HTTPError } from 'ky'
import prisma from '@typebot.io/lib/prisma'
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
@@ -169,7 +169,7 @@ export const startWhatsAppPreview = authenticatedProcedure
},
})
} catch (err) {
if (err instanceof HTTPError) console.log(err.response.body)
if (err instanceof HTTPError) console.log(await err.response.text())
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Request to Meta to send preview message failed',

View File

@@ -16,7 +16,7 @@ import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitat
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis/nodejs'
import got from 'got'
import ky from 'ky'
import { env } from '@typebot.io/env'
import * as Sentry from '@sentry/nextjs'
import { getIp } from '@typebot.io/lib/getIp'
@@ -164,10 +164,12 @@ export const getAuthOptions = ({
if (!account) return false
const isNewUser = !('createdAt' in user && isDefined(user.createdAt))
if (isNewUser && user.email) {
const { body } = await got.get(
'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf'
)
const disposableEmailDomains = body.split('\n')
const data = await ky
.get(
'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf'
)
.text()
const disposableEmailDomains = data.split('\n')
if (disposableEmailDomains.includes(user.email.split('@')[1]))
return false
}

View File

@@ -5,7 +5,7 @@ import {
methodNotAllowed,
notAuthenticated,
} from '@typebot.io/lib/api'
import { got } from 'got'
import ky from 'ky'
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
import { env } from '@typebot.io/env'
@@ -31,9 +31,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
const deleteDomainOnVercel = (name: string) =>
got.delete({
url: `https://api.vercel.com/v8/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
})
ky.delete(
`https://api.vercel.com/v8/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
{
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
}
)
export default handler

View File

@@ -0,0 +1,114 @@
import {
Typebot,
Variable,
HttpRequest,
Block,
AnswerInSessionState,
} from '@typebot.io/schemas'
import { NextApiRequest, NextApiResponse } from 'next'
import { byId } from '@typebot.io/lib'
import { isWebhookBlock } from '@typebot.io/schemas/helpers'
import { methodNotAllowed, notFound } from '@typebot.io/lib/api'
import prisma from '@typebot.io/lib/prisma'
import { getBlockById } from '@typebot.io/schemas/helpers'
import {
executeWebhook,
parseWebhookAttributes,
} from '@typebot.io/bot-engine/blocks/integrations/webhook/executeWebhookBlock'
import { fetchLinkedChildTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedChildTypebots'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import { saveLog } from '@typebot.io/bot-engine/logs/saveLog'
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
const user = await getAuthenticatedUser(req, res)
const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string
const resultId = req.query.resultId as string | undefined
const { variables } = (
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
) as {
variables: Variable[]
}
const typebot = (await prisma.typebot.findUnique({
where: { id: typebotId },
include: { webhooks: true },
})) as unknown as (Typebot & { webhooks: HttpRequest[] }) | null
if (!typebot) return notFound(res)
const block = typebot.groups
.flatMap<Block>((g) => g.blocks)
.find(byId(blockId))
if (!block || !isWebhookBlock(block))
return notFound(res, 'Webhook block not found')
const webhookId = 'webhookId' in block ? block.webhookId : undefined
const webhook =
block.options?.webhook ??
typebot.webhooks.find((w) => {
if ('id' in w) return w.id === webhookId
return false
})
if (!webhook)
return res
.status(404)
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
const { group } = getBlockById(blockId, typebot.groups)
const linkedTypebots = await fetchLinkedChildTypebots({
isPreview: !('typebotId' in typebot),
typebots: [typebot],
userId: user?.id,
})([])
const answers = arrayify(
await parseSampleResult(typebot, linkedTypebots)(group.id, variables)
)
const parsedWebhook = await parseWebhookAttributes({
webhook,
isCustomBody: block.options?.isCustomBody,
typebot: {
...typebot,
variables: typebot.variables.map((v) => {
const matchingVariable = variables.find(byId(v.id))
if (!matchingVariable) return v
return { ...v, value: matchingVariable.value }
}),
},
answers,
})
if (!parsedWebhook)
return res.status(500).send({
statusCode: 500,
data: { message: `Couldn't parse webhook attributes` },
})
const { response, logs } = await executeWebhook(parsedWebhook, {
timeout: block.options?.timeout,
})
if (resultId)
await Promise.all(
logs?.map((log) =>
saveLog({
message: log.description,
details: log.details,
status: log.status as 'error' | 'success' | 'info',
resultId,
})
) ?? []
)
return res.status(200).send(response)
}
return methodNotAllowed(res)
}
const arrayify = (
obj: Record<string, string | boolean | undefined>
): AnswerInSessionState[] =>
Object.entries(obj)
.map(([key, value]) => ({ key, value: value?.toString() }))
.filter((a) => a.value) as AnswerInSessionState[]
export default handler

View File

@@ -9,7 +9,7 @@ Here is the `sendMessage` action of the Telegram block:
```ts
import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth'
import { got } from 'got'
import ky from 'ky'
export const sendMessage = createAction({
auth,
@@ -27,14 +27,14 @@ export const sendMessage = createAction({
run: {
server: async ({ credentials: { token }, options: { chatId, text } }) => {
try {
await got.post(`https://api.telegram.org/bot${token}/sendMessage`, {
await ky.post(`https://api.telegram.org/bot${token}/sendMessage`, {
json: {
chat_id: chatId,
text,
},
})
} catch (error) {
console.log('ERROR', error.response.body)
console.log('ERROR', await error.response.text())
}
},
},

View File

@@ -13,8 +13,6 @@ An action can do one of the following things:
The most common action is to execute a function on the server. This is done by simply declaring that function in the action block.
If you need to use an external package that is not compatible with web browser environment, you will have to dynamically import it in the server function. You can find an example of this in the [Anthropic's Create Chat Message action](https://github.com/baptisteArno/typebot.io/blob/main/packages/forge/blocks/anthropic/actions/createChatMessage.tsx)
Example:
```ts

View File

@@ -17,7 +17,7 @@ const injectViewerUrlIfVercelPreview = (val) => {
)
return
process.env.NEXT_PUBLIC_VIEWER_URL = `https://${process.env.VERCEL_BRANCH_URL}`
if (process.env.NEXT_PUBLIC_CHAT_API_URL.includes('{{pr_id}}'))
if (process.env.NEXT_PUBLIC_CHAT_API_URL?.includes('{{pr_id}}'))
process.env.NEXT_PUBLIC_CHAT_API_URL =
process.env.NEXT_PUBLIC_CHAT_API_URL.replace(
'{{pr_id}}',
@@ -56,12 +56,14 @@ const nextConfig = {
if (nextRuntime === 'edge') {
config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false
config.resolve.alias['qrcode'] = false
return config
}
// These packages are imports from the integrations definition files that can be ignored for the client.
config.resolve.alias['minio'] = false
config.resolve.alias['got'] = false
config.resolve.alias['openai'] = false
config.resolve.alias['qrcode'] = false
return config
},
async redirects() {

View File

@@ -26,6 +26,7 @@
"cors": "2.8.5",
"google-spreadsheet": "4.1.1",
"got": "12.6.0",
"ky": "1.2.3",
"next": "14.1.0",
"nextjs-cors": "2.1.2",
"nodemailer": "6.9.8",

View File

@@ -1,45 +1,35 @@
import {
PublicTypebot,
ResultValues,
Typebot,
Variable,
HttpRequest,
HttpResponse,
Block,
PublicTypebot,
AnswerInSessionState,
} from '@typebot.io/schemas'
import { NextApiRequest, NextApiResponse } from 'next'
import got, { Method, Headers, HTTPError } from 'got'
import { byId, isEmpty, isNotDefined, omit } from '@typebot.io/lib'
import { byId } from '@typebot.io/lib'
import { isWebhookBlock } from '@typebot.io/schemas/helpers'
import { parseAnswers } from '@typebot.io/results/parseAnswers'
import { initMiddleware, methodNotAllowed, notFound } from '@typebot.io/lib/api'
import { stringify } from 'qs'
import Cors from 'cors'
import prisma from '@typebot.io/lib/prisma'
import { fetchLinkedTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedTypebots'
import { getPreviouslyLinkedTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/getPreviouslyLinkedTypebots'
import { parseVariables } from '@typebot.io/variables/parseVariables'
import { saveErrorLog } from '@typebot.io/bot-engine/logs/saveErrorLog'
import { saveSuccessLog } from '@typebot.io/bot-engine/logs/saveSuccessLog'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import {
HttpMethod,
defaultTimeout,
defaultWebhookAttributes,
maxTimeout,
} from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
import { getBlockById } from '@typebot.io/schemas/helpers'
import {
convertKeyValueTableToObject,
longReqTimeoutWhitelist,
executeWebhook,
parseWebhookAttributes,
} from '@typebot.io/bot-engine/blocks/integrations/webhook/executeWebhookBlock'
import { env } from '@typebot.io/env'
import { fetchLinkedParentTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedParentTypebots'
import { fetchLinkedChildTypebots } from '@typebot.io/bot-engine/blocks/logic/typebotLink/fetchLinkedChildTypebots'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import { saveLog } from '@typebot.io/bot-engine/logs/saveLog'
import { authenticateUser } from '@/helpers/authenticateUser'
const cors = initMiddleware(Cors())
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res)
if (req.method === 'POST') {
const user = await authenticateUser(req)
const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string
const resultId = req.query.resultId as string | undefined
@@ -72,232 +62,81 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
.status(404)
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
const { group } = getBlockById(blockId, typebot.groups)
const result = await executeWebhook(typebot)({
webhook,
variables,
groupId: group.id,
resultValues,
resultId,
const linkedTypebotsParents = (await fetchLinkedParentTypebots({
isPreview: !('typebotId' in typebot),
parentTypebotIds,
userId: user?.id,
})) as (Typebot | PublicTypebot)[]
const linkedTypebotsChildren = await fetchLinkedChildTypebots({
isPreview: !('typebotId' in typebot),
typebots: [typebot],
userId: user?.id,
})([])
const linkedTypebots = [...linkedTypebotsParents, ...linkedTypebotsChildren]
const answers = resultValues
? resultValues.answers.map((answer) => ({
key:
(answer.variableId
? typebot.variables.find(
(variable) => variable.id === answer.variableId
)?.name
: typebot.groups.find((group) =>
group.blocks.find((block) => block.id === answer.blockId)
)?.title) ?? '',
value: answer.content,
}))
: arrayify(
await parseSampleResult(typebot, linkedTypebots)(group.id, variables)
)
const parsedWebhook = await parseWebhookAttributes({
webhook,
isCustomBody: block.options?.isCustomBody,
typebot: {
...typebot,
variables: typebot.variables.map((v) => {
const matchingVariable = variables.find(byId(v.id))
if (!matchingVariable) return v
return { ...v, value: matchingVariable.value }
}),
},
answers,
})
if (!parsedWebhook)
return res.status(500).send({
statusCode: 500,
data: { message: `Couldn't parse webhook attributes` },
})
const { response, logs } = await executeWebhook(parsedWebhook, {
timeout: block.options?.timeout,
})
return res.status(200).send(result)
if (resultId)
await Promise.all(
logs?.map((log) =>
saveLog({
message: log.description,
details: log.details,
status: log.status as 'error' | 'success' | 'info',
resultId,
})
) ?? []
)
return res.status(200).send(response)
}
return methodNotAllowed(res)
}
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
export const executeWebhook =
(typebot: Typebot) =>
async ({
webhook,
variables,
groupId,
resultValues,
resultId,
parentTypebotIds = [],
isCustomBody,
timeout,
}: {
webhook: HttpRequest
variables: Variable[]
groupId: string
resultValues?: ResultValues
resultId?: string
parentTypebotIds: string[]
isCustomBody?: boolean
timeout?: number
}): Promise<HttpResponse> => {
if (!webhook.url)
return {
statusCode: 400,
data: { message: `Webhook doesn't have url or method` },
}
const basicAuth: { username?: string; password?: string } = {}
const basicAuthHeaderIdx =
webhook.headers?.findIndex(
(h) =>
h.key?.toLowerCase() === 'authorization' &&
h.value?.toLowerCase()?.includes('basic')
) ?? -1
const isUsernamePasswordBasicAuth =
basicAuthHeaderIdx !== -1 &&
webhook.headers?.[basicAuthHeaderIdx].value?.includes(':')
if (isUsernamePasswordBasicAuth) {
const [username, password] =
webhook.headers?.[basicAuthHeaderIdx].value?.slice(6).split(':') ?? []
basicAuth.username = username
basicAuth.password = password
webhook.headers?.splice(basicAuthHeaderIdx, 1)
}
const headers = convertKeyValueTableToObject(webhook.headers, variables) as
| Headers
| undefined
const queryParams = stringify(
convertKeyValueTableToObject(webhook.queryParams, variables)
)
const contentType = headers ? headers['Content-Type'] : undefined
const linkedTypebotsParents = (await fetchLinkedTypebots({
isPreview: !('typebotId' in typebot),
typebotIds: parentTypebotIds,
})) as (Typebot | PublicTypebot)[]
const linkedTypebotsChildren = await getPreviouslyLinkedTypebots({
isPreview: !('typebotId' in typebot),
typebots: [typebot],
})([])
const bodyContent = await getBodyContent(typebot, [
...linkedTypebotsParents,
...linkedTypebotsChildren,
])({
body: webhook.body,
isCustomBody,
resultValues,
groupId,
variables,
})
const { data: body, isJson } =
bodyContent && webhook.method !== HttpMethod.GET
? safeJsonParse(
parseVariables(variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
: { data: undefined, isJson: false }
const url = parseVariables(variables)(
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
)
const isLongRequest = longReqTimeoutWhitelist.some((whiteListedUrl) =>
url?.includes(whiteListedUrl)
)
const request = {
url,
method: (webhook.method ?? defaultWebhookAttributes.method) as Method,
headers: headers ?? {},
...basicAuth,
json:
!contentType?.includes('x-www-form-urlencoded') && body && isJson
? body
: undefined,
form:
contentType?.includes('x-www-form-urlencoded') && body
? body
: undefined,
body: body && !isJson ? body : undefined,
timeout: {
response: isNotDefined(env.CHAT_API_TIMEOUT)
? undefined
: timeout && timeout !== defaultTimeout
? Math.min(timeout, maxTimeout) * 1000
: isLongRequest
? maxTimeout * 1000
: defaultTimeout * 1000,
},
}
try {
const response = await got(request.url, omit(request, 'url'))
await saveSuccessLog({
resultId,
message: 'Webhook successfuly executed.',
details: {
statusCode: response.statusCode,
request,
response: safeJsonParse(response.body).data,
},
})
return {
statusCode: response.statusCode,
data: safeJsonParse(response.body).data,
}
} catch (error) {
if (error instanceof HTTPError) {
const response = {
statusCode: error.response.statusCode,
data: safeJsonParse(error.response.body as string).data,
}
await saveErrorLog({
resultId,
message: 'Webhook returned an error',
details: {
request,
response,
},
})
return response
}
const response = {
statusCode: 500,
data: { message: `Error from Typebot server: ${error}` },
}
console.error(error)
await saveErrorLog({
resultId,
message: 'Webhook failed to execute',
details: {
request,
response,
},
})
return response
}
}
const getBodyContent =
(
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
async ({
body,
resultValues,
groupId,
variables,
isCustomBody,
}: {
body?: string | null
resultValues?: ResultValues
groupId: string
variables: Variable[]
isCustomBody?: boolean
}): Promise<string | undefined> => {
return body === '{{state}}' || isEmpty(body) || isCustomBody !== true
? JSON.stringify(
resultValues
? parseAnswers({
answers: resultValues.answers.map((answer) => ({
key:
(answer.variableId
? typebot.variables.find(
(variable) => variable.id === answer.variableId
)?.name
: typebot.groups.find((group) =>
group.blocks.find(
(block) => block.id === answer.blockId
)
)?.title) ?? '',
value: answer.content,
})),
variables: resultValues.variables,
})
: await parseSampleResult(typebot, linkedTypebots)(
groupId,
variables
)
)
: body ?? undefined
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const safeJsonParse = (json: string): { data: any; isJson: boolean } => {
try {
return { data: JSON.parse(json), isJson: true }
} catch (err) {
return { data: json, isJson: false }
}
}
const arrayify = (
obj: Record<string, string | boolean | undefined>
): AnswerInSessionState[] =>
Object.entries(obj)
.map(([key, value]) => ({ key, value: value?.toString() }))
.filter((a) => a.value) as AnswerInSessionState[]
export default handler

View File

@@ -13,7 +13,6 @@ type Props = {
sessionId: string
}
export const continueChat = async ({ origin, sessionId, message }: Props) => {
console.log('test')
const session = await getSession(sessionId)
if (!session) {

View File

@@ -1,7 +1,7 @@
import { isNotEmpty } from '@typebot.io/lib/utils'
import { ContinueChatResponse } from '@typebot.io/schemas'
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
import { HTTPError } from 'got'
import { HTTPError } from 'ky'
import { ClientOptions, OpenAI } from 'openai'
type Props = Pick<
@@ -55,9 +55,9 @@ export const executeChatCompletionOpenAIRequest = async ({
} catch (error) {
if (error instanceof HTTPError) {
if (
(error.response.statusCode === 503 ||
error.response.statusCode === 500 ||
error.response.statusCode === 403) &&
(error.response.status === 503 ||
error.response.status === 500 ||
error.response.status === 403) &&
!isRetrying
) {
console.log('OpenAI API error - 503, retrying in 3 seconds')
@@ -73,7 +73,7 @@ export const executeChatCompletionOpenAIRequest = async ({
isRetrying: true,
})
}
if (error.response.statusCode === 400) {
if (error.response.status === 400) {
const log = {
status: 'info',
description:
@@ -93,8 +93,8 @@ export const executeChatCompletionOpenAIRequest = async ({
}
logs.push({
status: 'error',
description: `OpenAI API error - ${error.response.statusCode}`,
details: error.response.body,
description: `OpenAI API error - ${error.response.status}`,
details: await error.response.text(),
})
return { logs }
}

View File

@@ -11,10 +11,11 @@ import {
ChatLog,
ExecutableHttpRequest,
AnswerInSessionState,
TypebotInSession,
} from '@typebot.io/schemas'
import { stringify } from 'qs'
import { isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib'
import got, { Method, HTTPError, OptionsInit } from 'got'
import ky, { HTTPError, Options } from 'ky'
import { resumeWebhookExecution } from './resumeWebhookExecution'
import { ExecuteIntegrationResponse } from '../../../types'
import { parseVariables } from '@typebot.io/variables/parseVariables'
@@ -60,9 +61,11 @@ export const executeWebhookBlock = async (
})) as HttpRequest | null)
: null)
if (!webhook) return { outgoingEdgeId: block.outgoingEdgeId }
const parsedWebhook = await parseWebhookAttributes(state)({
const parsedWebhook = await parseWebhookAttributes({
webhook,
isCustomBody: block.options?.isCustomBody,
typebot: state.typebotsQueue[0].typebot,
answers: state.typebotsQueue[0].answers,
})
if (!parsedWebhook) {
logs.push({
@@ -104,69 +107,69 @@ export const executeWebhookBlock = async (
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
const parseWebhookAttributes =
(state: SessionState) =>
async ({
webhook,
isCustomBody,
}: {
webhook: HttpRequest
isCustomBody?: boolean
}): Promise<ParsedWebhook | undefined> => {
if (!webhook.url) return
const { typebot } = state.typebotsQueue[0]
const basicAuth: { username?: string; password?: string } = {}
const basicAuthHeaderIdx = webhook.headers?.findIndex(
(h) =>
h.key?.toLowerCase() === 'authorization' &&
h.value?.toLowerCase()?.includes('basic')
)
const isUsernamePasswordBasicAuth =
basicAuthHeaderIdx !== -1 &&
isDefined(basicAuthHeaderIdx) &&
webhook.headers?.at(basicAuthHeaderIdx)?.value?.includes(':')
if (isUsernamePasswordBasicAuth) {
const [username, password] =
webhook.headers?.at(basicAuthHeaderIdx)?.value?.slice(6).split(':') ??
[]
basicAuth.username = username
basicAuth.password = password
webhook.headers?.splice(basicAuthHeaderIdx, 1)
}
const headers = convertKeyValueTableToObject(
webhook.headers,
typebot.variables
) as ExecutableHttpRequest['headers'] | undefined
const queryParams = stringify(
convertKeyValueTableToObject(webhook.queryParams, typebot.variables)
)
const bodyContent = await getBodyContent({
body: webhook.body,
answers: state.typebotsQueue[0].answers,
variables: typebot.variables,
isCustomBody,
})
const method = webhook.method ?? defaultWebhookAttributes.method
const { data: body, isJson } =
bodyContent && method !== HttpMethod.GET
? safeJsonParse(
parseVariables(typebot.variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
: { data: undefined, isJson: false }
return {
url: parseVariables(typebot.variables)(
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
),
basicAuth,
method,
headers,
body,
isJson,
}
export const parseWebhookAttributes = async ({
webhook,
isCustomBody,
typebot,
answers,
}: {
webhook: HttpRequest
isCustomBody?: boolean
typebot: TypebotInSession
answers: AnswerInSessionState[]
}): Promise<ParsedWebhook | undefined> => {
if (!webhook.url) return
const basicAuth: { username?: string; password?: string } = {}
const basicAuthHeaderIdx = webhook.headers?.findIndex(
(h) =>
h.key?.toLowerCase() === 'authorization' &&
h.value?.toLowerCase()?.includes('basic')
)
const isUsernamePasswordBasicAuth =
basicAuthHeaderIdx !== -1 &&
isDefined(basicAuthHeaderIdx) &&
webhook.headers?.at(basicAuthHeaderIdx)?.value?.includes(':')
if (isUsernamePasswordBasicAuth) {
const [username, password] =
webhook.headers?.at(basicAuthHeaderIdx)?.value?.slice(6).split(':') ?? []
basicAuth.username = username
basicAuth.password = password
webhook.headers?.splice(basicAuthHeaderIdx, 1)
}
const headers = convertKeyValueTableToObject(
webhook.headers,
typebot.variables
) as ExecutableHttpRequest['headers'] | undefined
const queryParams = stringify(
convertKeyValueTableToObject(webhook.queryParams, typebot.variables)
)
const bodyContent = await getBodyContent({
body: webhook.body,
answers,
variables: typebot.variables,
isCustomBody,
})
const method = webhook.method ?? defaultWebhookAttributes.method
const { data: body, isJson } =
bodyContent && method !== HttpMethod.GET
? safeJsonParse(
parseVariables(typebot.variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
: { data: undefined, isJson: false }
return {
url: parseVariables(typebot.variables)(
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
),
basicAuth,
method,
headers,
body,
isJson,
}
}
export const executeWebhook = async (
webhook: ParsedWebhook,
@@ -177,7 +180,8 @@ export const executeWebhook = async (
startTimeShouldBeUpdated?: boolean
}> => {
const logs: ChatLog[] = []
const { headers, url, method, basicAuth, body, isJson } = webhook
const { headers, url, method, basicAuth, isJson } = webhook
const contentType = headers ? headers['Content-Type'] : undefined
const isLongRequest = params.disableRequestTimeout
@@ -186,59 +190,60 @@ export const executeWebhook = async (
url?.includes(whiteListedUrl)
)
const isFormData = contentType?.includes('x-www-form-urlencoded')
let body = webhook.body
if (isFormData && isJson) body = parseFormDataBody(body as object)
const request = {
url,
method: method as Method,
method,
headers: headers ?? {},
...(basicAuth ?? {}),
json:
!contentType?.includes('x-www-form-urlencoded') && body && isJson
? body
: undefined,
form:
contentType?.includes('x-www-form-urlencoded') && body ? body : undefined,
body: body && !isJson ? (body as string) : undefined,
timeout: {
response: isNotDefined(env.CHAT_API_TIMEOUT)
? undefined
: params.timeout && params.timeout !== defaultTimeout
? Math.min(params.timeout, maxTimeout) * 1000
: isLongRequest
? maxTimeout * 1000
: defaultTimeout * 1000,
},
} satisfies OptionsInit
json: !isFormData && body && isJson ? body : undefined,
body: (isFormData && body ? body : undefined) as any,
timeout: isNotDefined(env.CHAT_API_TIMEOUT)
? undefined
: params.timeout && params.timeout !== defaultTimeout
? Math.min(params.timeout, maxTimeout) * 1000
: isLongRequest
? maxTimeout * 1000
: defaultTimeout * 1000,
} satisfies Options & { url: string; body: any }
try {
const response = await got(request.url, omit(request, 'url'))
const response = await ky(request.url, omit(request, 'url'))
const body = await response.text()
logs.push({
status: 'success',
description: webhookSuccessDescription,
details: {
statusCode: response.statusCode,
response: safeJsonParse(response.body).data,
statusCode: response.status,
response: safeJsonParse(body).data,
request,
},
})
return {
response: {
statusCode: response.statusCode,
data: safeJsonParse(response.body).data,
statusCode: response.status,
data: safeJsonParse(body).data,
},
logs,
startTimeShouldBeUpdated: true,
}
} catch (error) {
if (error instanceof HTTPError) {
const responseBody = await error.response.text()
const response = {
statusCode: error.response.statusCode,
data: safeJsonParse(error.response.body as string).data,
statusCode: error.response.status,
data: safeJsonParse(responseBody).data,
}
logs.push({
status: 'error',
description: webhookErrorDescription,
details: {
statusCode: error.response.statusCode,
statusCode: error.response.status,
request,
response,
},
@@ -257,7 +262,7 @@ export const executeWebhook = async (
}
logs.push({
status: 'error',
description: `Webhook request timed out. (${request.timeout.response}ms)`,
description: `Webhook request timed out. (${request.timeout}ms)`,
details: {
response,
request,
@@ -320,10 +325,18 @@ export const convertKeyValueTableToObject = (
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const safeJsonParse = (json: string): { data: any; isJson: boolean } => {
const safeJsonParse = (json: unknown): { data: any; isJson: boolean } => {
try {
return { data: JSON.parse(json), isJson: true }
return { data: JSON.parse(json as string), isJson: true }
} catch (err) {
return { data: json, isJson: false }
}
}
const parseFormDataBody = (body: object) => {
const searchParams = new URLSearchParams()
Object.entries(body as object).forEach(([key, value]) => {
searchParams.set(key, value)
})
return searchParams
}

View File

@@ -4,7 +4,7 @@ import {
ZemanticAiCredentials,
ZemanticAiResponse,
} from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
import got from 'got'
import ky from 'ky'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { byId, isDefined, isEmpty } from '@typebot.io/lib'
import { ExecuteIntegrationResponse } from '../../../types'
@@ -51,7 +51,7 @@ export const executeZemanticAiBlock = async (
})
try {
const res: ZemanticAiResponse = await got
const res: ZemanticAiResponse = await ky
.post(URL, {
headers: {
Authorization: `Bearer ${apiKey}`,

View File

@@ -11,12 +11,12 @@ import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/consta
type Props = {
typebots: Pick<PublicTypebot, 'groups'>[]
user?: User
userId: string | undefined
isPreview?: boolean
}
export const getPreviouslyLinkedTypebots =
({ typebots, user, isPreview }: Props) =>
export const fetchLinkedChildTypebots =
({ typebots, userId, isPreview }: Props) =>
async (
capturedLinkedBots: (Typebot | PublicTypebot)[]
): Promise<(Typebot | PublicTypebot)[]> => {
@@ -40,13 +40,13 @@ export const getPreviouslyLinkedTypebots =
.filter(isDefined)
if (linkedTypebotIds.length === 0) return capturedLinkedBots
const linkedTypebots = (await fetchLinkedTypebots({
user,
userId,
typebotIds: linkedTypebotIds,
isPreview,
})) as (Typebot | PublicTypebot)[]
return getPreviouslyLinkedTypebots({
return fetchLinkedChildTypebots({
typebots: linkedTypebots,
user,
userId,
isPreview,
})([...capturedLinkedBots, ...linkedTypebots])
}

View File

@@ -0,0 +1,20 @@
import { fetchLinkedTypebots } from './fetchLinkedTypebots'
type Props = {
parentTypebotIds: string[]
userId: string | undefined
isPreview?: boolean
}
export const fetchLinkedParentTypebots = ({
parentTypebotIds,
isPreview,
userId,
}: Props) =>
parentTypebotIds.length > 0
? fetchLinkedTypebots({
typebotIds: parentTypebotIds,
isPreview,
userId,
})
: []

View File

@@ -1,20 +1,19 @@
import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
type Props = {
isPreview?: boolean
typebotIds: string[]
user?: User
userId: string | undefined
}
export const fetchLinkedTypebots = async ({
user,
userId,
isPreview,
typebotIds,
}: Props) => {
if (!user || !isPreview)
if (!userId || !isPreview)
return prisma.publicTypebot.findMany({
where: { id: { in: typebotIds } },
where: { typebotId: { in: typebotIds } },
})
const linkedTypebots = await prisma.typebot.findMany({
where: { id: { in: typebotIds } },
@@ -39,7 +38,7 @@ export const fetchLinkedTypebots = async ({
return linkedTypebots.filter(
(typebot) =>
typebot.collaborators.some(
(collaborator) => collaborator.userId === user.id
) || typebot.workspace.members.some((member) => member.userId === user.id)
(collaborator) => collaborator.userId === userId
) || typebot.workspace.members.some((member) => member.userId === userId)
)
}

View File

@@ -111,7 +111,6 @@ export const executeGroup = async (
logs,
visitedEdges,
}
console.log('yes')
const executionResponse = (
isLogicBlock(block)
? await executeLogic(newSessionState)(block)

View File

@@ -16,7 +16,6 @@ import { env } from '@typebot.io/env'
export const executeIntegration =
(state: SessionState) =>
async (block: IntegrationBlock): Promise<ExecuteIntegrationResponse> => {
console.log('HI')
switch (block.type) {
case IntegrationBlockType.GOOGLE_SHEETS:
return {

View File

@@ -27,7 +27,6 @@ export const executeForgedBlock = async (
const blockDef = forgedBlocks[block.type]
if (!blockDef) return { outgoingEdgeId: block.outgoingEdgeId }
const action = blockDef.actions.find((a) => a.name === block.options.action)
console.log('test', action)
const noCredentialsError = {
status: 'error',
description: 'Credentials not provided for integration',

View File

@@ -14,6 +14,7 @@
"@typebot.io/env": "workspace:*",
"@typebot.io/lib": "workspace:*",
"@typebot.io/prisma": "workspace:*",
"@typebot.io/results": "workspace:*",
"@typebot.io/schemas": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
"@typebot.io/variables": "workspace:*",
@@ -24,19 +25,18 @@
"date-fns-tz": "2.0.0",
"google-auth-library": "8.9.0",
"google-spreadsheet": "4.1.1",
"got": "12.6.0",
"ky": "^1.1.3",
"ky": "1.2.3",
"libphonenumber-js": "1.10.37",
"node-html-parser": "6.1.5",
"nodemailer": "6.9.8",
"openai": "4.28.4",
"qs": "6.11.2",
"stripe": "12.13.0",
"@typebot.io/results": "workspace:*"
"stripe": "12.13.0"
},
"devDependencies": {
"@typebot.io/forge": "workspace:*",
"@typebot.io/forge-repository": "workspace:*",
"@types/node": "^20.12.3",
"@types/nodemailer": "6.4.14",
"@types/qs": "6.9.7"
}

View File

@@ -1,5 +1,5 @@
import { env } from '@typebot.io/env'
import got from 'got'
import ky from 'ky'
type Props = {
mediaId: string
@@ -10,21 +10,24 @@ export const downloadMedia = async ({
mediaId,
systemUserAccessToken,
}: Props): Promise<{ file: Buffer; mimeType: string }> => {
const { body } = await got.get({
url: `${env.WHATSAPP_CLOUD_API_URL}/v17.0/${mediaId}`,
headers: {
Authorization: `Bearer ${systemUserAccessToken}`,
},
})
const parsedBody = JSON.parse(body) as { url: string; mime_type: string }
return {
file: await got(parsedBody.url, {
const { url, mime_type } = await ky
.get(`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${mediaId}`, {
headers: {
Authorization: `Bearer ${systemUserAccessToken}`,
},
}).buffer(),
mimeType: parsedBody.mime_type,
})
.json<{ url: string; mime_type: string }>()
return {
file: Buffer.from(
await ky
.get(url, {
headers: {
Authorization: `Bearer ${systemUserAccessToken}`,
},
})
.arrayBuffer()
),
mimeType: mime_type,
}
}

View File

@@ -10,7 +10,7 @@ import {
import { convertMessageToWhatsAppMessage } from './convertMessageToWhatsAppMessage'
import { sendWhatsAppMessage } from './sendWhatsAppMessage'
import * as Sentry from '@sentry/nextjs'
import { HTTPError } from 'got'
import { HTTPError } from 'ky'
import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage'
import { isNotDefined } from '@typebot.io/lib/utils'
import { computeTypingDuration } from '../computeTypingDuration'
@@ -141,7 +141,7 @@ export const sendChatReplyToWhatsApp = async ({
Sentry.captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body)
console.log('HTTPError', err.response.status, await err.response.text())
}
}
@@ -172,7 +172,11 @@ export const sendChatReplyToWhatsApp = async ({
Sentry.captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body)
console.log(
'HTTPError',
err.response.status,
await err.response.text()
)
}
}
}
@@ -253,7 +257,11 @@ const executeClientSideAction =
Sentry.captureException(err, { extra: { message } })
console.log('Failed to send message:', JSON.stringify(message, null, 2))
if (err instanceof HTTPError)
console.log('HTTPError', err.response.statusCode, err.response.body)
console.log(
'HTTPError',
err.response.status,
await err.response.text()
)
}
}
}

View File

@@ -3,7 +3,7 @@ import {
WhatsAppSendingMessage,
} from '@typebot.io/schemas/features/whatsapp'
import { env } from '@typebot.io/env'
import got from 'got'
import ky from 'ky'
type Props = {
to: string
@@ -16,7 +16,7 @@ export const sendWhatsAppMessage = async ({
message,
credentials,
}: Props) =>
got.post(
ky.post(
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}/messages`,
{
headers: {

View File

@@ -1,5 +1,6 @@
import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth'
import { Anthropic } from '@anthropic-ai/sdk'
import { AnthropicStream } from 'ai'
import { anthropicModels, defaultAnthropicOptions } from '../constants'
import { parseChatMessages } from '../helpers/parseChatMessages'
@@ -103,8 +104,6 @@ export const createChatMessage = createAction({
responseMapping?.map((res) => res.variableId).filter(isDefined) ?? [],
run: {
server: async ({ credentials: { apiKey }, options, variables, logs }) => {
const { Anthropic } = await import('@anthropic-ai/sdk')
const client = new Anthropic({
apiKey: apiKey,
})
@@ -150,8 +149,6 @@ export const createChatMessage = createAction({
(res) => res.item === 'Message Content' || !res.item
)?.variableId,
run: async ({ credentials: { apiKey }, options, variables }) => {
const { Anthropic } = await import('@anthropic-ai/sdk')
const client = new Anthropic({
apiKey: apiKey,
})

View File

@@ -1,6 +1,6 @@
import { createAction, option } from '@typebot.io/forge'
import { isDefined, isEmpty } from '@typebot.io/lib'
import { HTTPError, got } from 'got'
import ky, { HTTPError } from 'ky'
import { apiBaseUrl } from '../constants'
import { auth } from '../auth'
import { ChatNodeResponse } from '../types'
@@ -40,7 +40,7 @@ export const sendMessage = createAction({
logs,
}) => {
try {
const res: ChatNodeResponse = await got
const res: ChatNodeResponse = await ky
.post(apiBaseUrl + botId, {
headers: {
Authorization: `Bearer ${apiKey}`,
@@ -66,7 +66,7 @@ export const sendMessage = createAction({
return logs.add({
status: 'error',
description: error.message,
details: error.response.body,
details: await error.response.text(),
})
console.error(error)
}

View File

@@ -11,6 +11,6 @@
"@typebot.io/tsconfig": "workspace:*",
"@types/react": "18.2.15",
"typescript": "5.3.2",
"got": "12.6.0"
"ky": "1.2.3"
}
}

View File

@@ -1,9 +1,9 @@
import { createAction, option } from '@typebot.io/forge'
import { isDefined, isEmpty, isNotEmpty } from '@typebot.io/lib'
import { HTTPError, got } from 'got'
import { auth } from '../auth'
import { defaultBaseUrl } from '../constants'
import { Chunk } from '../types'
import ky from 'ky'
export const createChatMessage = createAction({
auth,
@@ -44,13 +44,15 @@ export const createChatMessage = createAction({
logs,
}) => {
try {
const stream = got.post(
const response = await ky(
(apiEndpoint ?? defaultBaseUrl) + '/v1/chat-messages',
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
json: {
body: JSON.stringify({
inputs:
inputs?.reduce((acc, { key, value }) => {
if (isEmpty(key) || isEmpty(value)) return acc
@@ -64,56 +66,70 @@ export const createChatMessage = createAction({
conversation_id,
user,
files: [],
},
isStream: true,
}),
}
)
const reader = response.body?.getReader()
if (!reader)
return logs.add({
status: 'error',
description: 'Failed to read response stream',
})
const { answer, conversationId, totalTokens } = await new Promise<{
answer: string
conversationId: string | undefined
totalTokens: number | undefined
}>((resolve, reject) => {
}>(async (resolve, reject) => {
let jsonChunk = ''
let answer = ''
let conversationId: string | undefined
let totalTokens: number | undefined
stream.on('data', (chunk) => {
const lines = chunk.toString().split('\n') as string[]
lines
.filter((line) => line.length > 0 && line !== '\n')
.forEach((line) => {
jsonChunk += line
if (jsonChunk.startsWith('event: ')) {
try {
while (true) {
const { value, done } = await reader.read()
if (done) {
resolve({ answer, conversationId, totalTokens })
return
}
const chunk = new TextDecoder().decode(value)
const lines = chunk.toString().split('\n') as string[]
lines
.filter((line) => line.length > 0 && line !== '\n')
.forEach((line) => {
jsonChunk += line
if (jsonChunk.startsWith('event: ')) {
jsonChunk = ''
return
}
if (
!jsonChunk.startsWith('data: ') ||
!jsonChunk.endsWith('}')
)
return
const data = JSON.parse(jsonChunk.slice(6)) as Chunk
jsonChunk = ''
return
}
if (!jsonChunk.startsWith('data: ') || !jsonChunk.endsWith('}'))
return
const data = JSON.parse(jsonChunk.slice(6)) as Chunk
jsonChunk = ''
if (
data.event === 'message' ||
data.event === 'agent_message'
) {
answer += data.answer
}
if (data.event === 'message_end') {
totalTokens = data.metadata.usage.total_tokens
conversationId = data.conversation_id
}
})
})
stream.on('end', () => {
resolve({ answer, conversationId, totalTokens })
})
stream.on('error', (error) => {
reject(error)
})
if (
data.event === 'message' ||
data.event === 'agent_message'
) {
answer += data.answer
}
if (data.event === 'message_end') {
totalTokens = data.metadata.usage.total_tokens
conversationId = data.conversation_id
}
})
}
} catch (e) {
reject(e)
}
})
responseMapping?.forEach((mapping) => {
@@ -130,12 +146,10 @@ export const createChatMessage = createAction({
variables.set(mapping.variableId, totalTokens)
})
} catch (error) {
if (error instanceof HTTPError)
return logs.add({
status: 'error',
description: error.message,
details: error.response.body,
})
logs.add({
status: 'error',
description: 'Failed to create chat message',
})
console.error(error)
}
},

View File

@@ -10,7 +10,7 @@
"@typebot.io/lib": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
"@types/react": "18.2.15",
"got": "12.6.0",
"ky": "1.2.3",
"typescript": "5.3.2"
}
}

View File

@@ -2,7 +2,7 @@ import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth'
import { baseUrl } from '../constants'
import { ModelsResponse, VoicesResponse } from '../type'
import got, { HTTPError } from 'got'
import got, { HTTPError } from 'ky'
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
import { createId } from '@typebot.io/lib/createId'
@@ -93,10 +93,10 @@ export const convertTextToSpeech = createAction({
text: options.text,
},
})
.buffer()
.arrayBuffer()
const url = await uploadFileToBucket({
file: response,
file: Buffer.from(response),
key: `tmp/elevenlabs/audio/${createId() + createId()}.mp3`,
mimeType: 'audio/mpeg',
})
@@ -107,7 +107,7 @@ export const convertTextToSpeech = createAction({
return logs.add({
status: 'error',
description: err.message,
details: err.response.body,
details: await err.response.text(),
})
}
}

View File

@@ -13,7 +13,7 @@
"typescript": "5.3.2"
},
"dependencies": {
"got": "12.6.0",
"ky": "1.2.3",
"@typebot.io/lib": "workspace:*"
}
}

View File

@@ -3,6 +3,8 @@ import { isDefined } from '@typebot.io/lib'
import { auth } from '../auth'
import { parseMessages } from '../helpers/parseMessages'
import { OpenAIStream } from 'ai'
// @ts-ignore
import MistralClient from '../helpers/client'
const nativeMessageContentSchema = {
content: option.string.layout({
@@ -95,15 +97,17 @@ export const createChatCompletion = createAction({
id: 'fetchModels',
dependencies: [],
fetch: async ({ credentials }) => {
const MistralClient = (await import('@mistralai/mistralai')).default
const client = new MistralClient(credentials.apiKey)
const listModelsResponse = await client.listModels()
const listModelsResponse: any = await client.listModels()
return (
listModelsResponse.data
.sort((a, b) => b.created - a.created)
.map((model) => model.id) ?? []
.sort(
(a: { created: number }, b: { created: number }) =>
b.created - a.created
)
.map((model: { id: any }) => model.id) ?? []
)
},
},
@@ -111,10 +115,9 @@ export const createChatCompletion = createAction({
run: {
server: async ({ credentials: { apiKey }, options, variables, logs }) => {
if (!options.model) return logs.add('No model selected')
const MistralClient = (await import('@mistralai/mistralai')).default
const client = new MistralClient(apiKey)
const response = await client.chat({
const response: any = await client.chat({
model: options.model,
messages: parseMessages({ options, variables }),
})
@@ -132,15 +135,13 @@ export const createChatCompletion = createAction({
)?.variableId,
run: async ({ credentials: { apiKey }, options, variables }) => {
if (!options.model) return
const MistralClient = (await import('@mistralai/mistralai')).default
const client = new MistralClient(apiKey)
const response = client.chatStream({
const response: any = client.chatStream({
model: options.model,
messages: parseMessages({ options, variables }),
})
// @ts-ignore https://github.com/vercel/ai/issues/936
return OpenAIStream(response)
},
},

View File

@@ -0,0 +1,341 @@
// Taken from https://github.com/mistralai/client-js/blob/main/src/client.js
// Lib seems not actively maintained, and we need this patch: https://github.com/mistralai/client-js/pull/42
let isNode = false
let fetch
const VERSION = '0.0.3'
const RETRY_STATUS_CODES = [429, 500, 502, 503, 504]
const ENDPOINT = 'https://api.mistral.ai'
/**
* Initialize fetch
* @return {Promise<void>}
*/
async function initializeFetch() {
if (typeof globalThis.fetch === 'undefined')
throw new Error('No fetch implementation found')
if (typeof window === 'undefined') {
isNode = true
}
fetch = globalThis.fetch
}
initializeFetch()
/**
* MistralAPIError
* @return {MistralAPIError}
* @extends {Error}
*/
class MistralAPIError extends Error {
/**
* A simple error class for Mistral API errors
* @param {*} message
*/
constructor(message) {
super(message)
this.name = 'MistralAPIError'
}
}
/**
* MistralClient
* @return {MistralClient}
*/
class MistralClient {
/**
* A simple and lightweight client for the Mistral API
* @param {*} apiKey can be set as an environment variable MISTRAL_API_KEY,
* or provided in this parameter
* @param {*} endpoint defaults to https://api.mistral.ai
* @param {*} maxRetries defaults to 5
* @param {*} timeout defaults to 120 seconds
*/
constructor(
apiKey = process.env.MISTRAL_API_KEY,
endpoint = ENDPOINT,
maxRetries = 5,
timeout = 120
) {
this.endpoint = endpoint
this.apiKey = apiKey
this.maxRetries = maxRetries
this.timeout = timeout
if (this.endpoint.indexOf('inference.azure.com')) {
this.modelDefault = 'mistral'
}
}
/**
*
* @param {*} method
* @param {*} path
* @param {*} request
* @return {Promise<*>}
*/
_request = async function (method, path, request) {
const url = `${this.endpoint}/${path}`
const options = {
method: method,
headers: {
'User-Agent': `mistral-client-js/${VERSION}`,
Accept: request?.stream ? 'text/event-stream' : 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body: method !== 'get' ? JSON.stringify(request) : null,
timeout: this.timeout * 1000,
}
for (let attempts = 0; attempts < this.maxRetries; attempts++) {
try {
const response = await fetch(url, options)
if (response.ok) {
if (request?.stream) {
if (isNode) {
return response.body
} else {
const reader = response.body.getReader()
// Chrome does not support async iterators yet, so polyfill it
const asyncIterator = async function* () {
try {
while (true) {
// Read from the stream
const { done, value } = await reader.read()
// Exit if we're done
if (done) return
// Else yield the chunk
yield value
}
} finally {
reader.releaseLock()
}
}
return asyncIterator()
}
}
return await response.json()
} else if (RETRY_STATUS_CODES.includes(response.status)) {
console.debug(
`Retrying request on response status: ${response.status}`,
`Response: ${await response.text()}`,
`Attempt: ${attempts + 1}`
)
// eslint-disable-next-line max-len
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, attempts + 1) * 500)
)
} else {
throw new MistralAPIError(
`HTTP error! status: ${response.status} ` +
`Response: \n${await response.text()}`
)
}
} catch (error) {
console.error(`Request failed: ${error.message}`)
if (error.name === 'MistralAPIError') {
throw error
}
if (attempts === this.maxRetries - 1) throw error
// eslint-disable-next-line max-len
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, attempts + 1) * 500)
)
}
}
throw new Error('Max retries reached')
}
/**
* Creates a chat completion request
* @param {*} model
* @param {*} messages
* @param {*} tools
* @param {*} temperature
* @param {*} maxTokens
* @param {*} topP
* @param {*} randomSeed
* @param {*} stream
* @param {*} safeMode deprecated use safePrompt instead
* @param {*} safePrompt
* @param {*} toolChoice
* @param {*} responseFormat
* @return {Promise<Object>}
*/
_makeChatCompletionRequest = function (
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
stream,
safeMode,
safePrompt,
toolChoice,
responseFormat
) {
// if modelDefault and model are undefined, throw an error
if (!model && !this.modelDefault) {
throw new MistralAPIError('You must provide a model name')
}
return {
model: model ?? this.modelDefault,
messages: messages,
tools: tools ?? undefined,
temperature: temperature ?? undefined,
max_tokens: maxTokens ?? undefined,
top_p: topP ?? undefined,
random_seed: randomSeed ?? undefined,
stream: stream ?? undefined,
safe_prompt: (safeMode || safePrompt) ?? undefined,
tool_choice: toolChoice ?? undefined,
response_format: responseFormat ?? undefined,
}
}
/**
* Returns a list of the available models
* @return {Promise<Object>}
*/
listModels = async function () {
const response = await this._request('get', 'v1/models')
return response
}
/**
* A chat endpoint without streaming
* @param {*} model the name of the model to chat with, e.g. mistral-tiny
* @param {*} messages an array of messages to chat with, e.g.
* [{role: 'user', content: 'What is the best French cheese?'}]
* @param {*} tools a list of tools to use.
* @param {*} temperature the temperature to use for sampling, e.g. 0.5
* @param {*} maxTokens the maximum number of tokens to generate, e.g. 100
* @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9
* @param {*} randomSeed the random seed to use for sampling, e.g. 42
* @param {*} safeMode deprecated use safePrompt instead
* @param {*} safePrompt whether to use safe mode, e.g. true
* @param {*} toolChoice the tool to use, e.g. 'auto'
* @param {*} responseFormat the format of the response, e.g. 'json_format'
* @return {Promise<Object>}
*/
chat = async function ({
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
safeMode,
safePrompt,
toolChoice,
responseFormat,
}) {
const request = this._makeChatCompletionRequest(
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
false,
safeMode,
safePrompt,
toolChoice,
responseFormat
)
const response = await this._request('post', 'v1/chat/completions', request)
return response
}
/**
* A chat endpoint that streams responses.
* @param {*} model the name of the model to chat with, e.g. mistral-tiny
* @param {*} messages an array of messages to chat with, e.g.
* [{role: 'user', content: 'What is the best French cheese?'}]
* @param {*} tools a list of tools to use.
* @param {*} temperature the temperature to use for sampling, e.g. 0.5
* @param {*} maxTokens the maximum number of tokens to generate, e.g. 100
* @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9
* @param {*} randomSeed the random seed to use for sampling, e.g. 42
* @param {*} safeMode deprecated use safePrompt instead
* @param {*} safePrompt whether to use safe mode, e.g. true
* @param {*} toolChoice the tool to use, e.g. 'auto'
* @param {*} responseFormat the format of the response, e.g. 'json_format'
* @return {Promise<Object>}
*/
chatStream = async function* ({
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
safeMode,
safePrompt,
toolChoice,
responseFormat,
}) {
const request = this._makeChatCompletionRequest(
model,
messages,
tools,
temperature,
maxTokens,
topP,
randomSeed,
true,
safeMode,
safePrompt,
toolChoice,
responseFormat
)
const response = await this._request('post', 'v1/chat/completions', request)
let buffer = ''
const decoder = new TextDecoder()
for await (const chunk of response) {
buffer += decoder.decode(chunk, { stream: true })
let firstNewline
while ((firstNewline = buffer.indexOf('\n')) !== -1) {
const chunkLine = buffer.substring(0, firstNewline)
buffer = buffer.substring(firstNewline + 1)
if (chunkLine.startsWith('data:')) {
const json = chunkLine.substring(6).trim()
if (json !== '[DONE]') {
yield JSON.parse(json)
}
}
}
}
}
/**
* An embeddings endpoint that returns embeddings for a single,
* or batch of inputs
* @param {*} model The embedding model to use, e.g. mistral-embed
* @param {*} input The input to embed,
* e.g. ['What is the best French cheese?']
* @return {Promise<Object>}
*/
embeddings = async function ({ model, input }) {
const request = {
model: model,
input: input,
}
const response = await this._request('post', 'v1/embeddings', request)
return response
}
}
export default MistralClient

View File

@@ -9,11 +9,11 @@
"@typebot.io/forge": "workspace:*",
"@typebot.io/lib": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
"@types/node": "^20.12.4",
"@types/react": "18.2.15",
"typescript": "5.3.2"
},
"dependencies": {
"@mistralai/mistralai": "0.1.3",
"ai": "3.0.12"
}
}

View File

@@ -1,6 +1,6 @@
{
"extends": "@typebot.io/tsconfig/base.json",
"include": ["**/*.ts", "**/*.tsx"],
"include": ["**/*.ts", "**/*.tsx", "helpers/client.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
"lib": ["ESNext", "DOM"],

View File

@@ -6,7 +6,7 @@ import { getChatCompletionStreamVarId } from '@typebot.io/openai-block/shared/ge
import { runChatCompletion } from '@typebot.io/openai-block/shared/runChatCompletion'
import { runChatCompletionStream } from '@typebot.io/openai-block/shared/runChatCompletionStream'
import { defaultOpenRouterOptions } from '../constants'
import { got } from 'got'
import ky from 'ky'
import { ModelsResponse } from '../types'
export const createChatCompletion = createAction({
@@ -42,7 +42,7 @@ export const createChatCompletion = createAction({
id: 'fetchModels',
dependencies: [],
fetch: async () => {
const response = await got
const response = await ky
.get(defaultOpenRouterOptions.baseUrl + '/models')
.json<ModelsResponse>()

View File

@@ -12,6 +12,6 @@
"typescript": "5.3.2",
"@typebot.io/lib": "workspace:*",
"@typebot.io/openai-block": "workspace:*",
"got": "12.6.0"
"ky": "1.2.3"
}
}

View File

@@ -1,4 +1,5 @@
import { createAction, option } from '@typebot.io/forge'
import { toBuffer as generateQrCodeBuffer } from 'qrcode'
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
import { createId } from '@typebot.io/lib/createId'
@@ -28,8 +29,6 @@ export const generateQrCode = createAction({
'QR code image URL is not specified. Please select a variable to save the generated QR code image.'
)
const generateQrCodeBuffer = (await import('qrcode')).toBuffer
const url = await uploadFileToBucket({
file: await generateQrCodeBuffer(options.data),
key: `tmp/qrcodes/${createId() + createId()}.png`,

View File

@@ -1,7 +1,7 @@
import { createAction, option } from '@typebot.io/forge'
import { isDefined } from '@typebot.io/lib'
import { ZemanticAiResponse } from '../types'
import { got } from 'got'
import ky from 'ky'
import { apiBaseUrl } from '../constants'
import { auth } from '../auth'
import { baseOptions } from '../baseOptions'
@@ -63,7 +63,7 @@ export const searchDocuments = createAction({
},
variables,
}) => {
const res: ZemanticAiResponse = await got
const res = await ky
.post(apiBaseUrl, {
headers: {
Authorization: `Bearer ${apiKey}`,
@@ -79,7 +79,7 @@ export const searchDocuments = createAction({
},
},
})
.json()
.json<ZemanticAiResponse>()
responseMapping?.forEach((mapping) => {
if (!mapping.variableId || !mapping.item) return

View File

@@ -8,6 +8,7 @@ export const auth = {
label: 'API key',
isRequired: true,
placeholder: 'ze...',
inputType: 'password',
helperText:
'You can generate an API key [here](https://zemantic.ai/dashboard/settings).',
isDebounceDisabled: true,

View File

@@ -1,6 +1,6 @@
import { createBlock } from '@typebot.io/forge'
import { ZemanticAiLogo } from './logo'
import { got } from 'got'
import ky from 'ky'
import { searchDocuments } from './actions/searchDocuments'
import { auth } from './auth'
import { baseOptions } from './baseOptions'
@@ -19,7 +19,7 @@ export const zemanticAiBlock = createBlock({
fetch: async ({ credentials: { apiKey } }) => {
const url = 'https://api.zemantic.ai/v1/projects'
const response = await got
const response = await ky
.get(url, {
headers: {
Authorization: `Bearer ${apiKey}`,

View File

@@ -11,6 +11,6 @@
"@types/react": "18.2.15",
"typescript": "5.3.2",
"@typebot.io/lib": "workspace:*",
"got": "12.6.0"
"ky": "1.2.3"
}
}

View File

@@ -38,7 +38,7 @@
"@udecode/plate-paragraph": "30.5.3",
"escape-html": "1.0.3",
"google-auth-library": "8.9.0",
"got": "12.6.0",
"ky": "1.2.3",
"minio": "7.1.3",
"posthog-node": "3.1.1",
"remark-parse": "11.0.0",

View File

@@ -1,8 +1,7 @@
import { PrismaClient } from '@typebot.io/prisma'
import { promptAndSetEnvironment } from './utils'
import got, { HTTPError } from 'got'
import ky, { HTTPError } from 'ky'
import { confirm, text, isCancel } from '@clack/prompts'
import { writeFileSync } from 'fs'
const insertUsersInBrevoList = async () => {
await promptAndSetEnvironment('production')
@@ -44,7 +43,7 @@ const insertUsersInBrevoList = async () => {
}
try {
await got.post('https://api.brevo.com/v3/contacts/import', {
await ky.post('https://api.brevo.com/v3/contacts/import', {
headers: {
'api-key': process.env.BREVO_API_KEY,
},
@@ -58,7 +57,7 @@ const insertUsersInBrevoList = async () => {
})
} catch (err) {
if (err instanceof HTTPError) {
console.log(err.response.body)
console.log(await err.response.text())
return
}
console.log(err)

View File

@@ -42,7 +42,7 @@
"@types/papaparse": "5.3.7",
"@types/prompts": "2.4.4",
"deep-object-diff": "1.1.9",
"got": "12.6.0",
"ky": "1.2.3",
"prompts": "2.4.2",
"stripe": "12.13.0",
"tsx": "3.12.7",

View File

@@ -8,7 +8,7 @@
"license": "ISC",
"dependencies": {
"@typebot.io/schemas": "workspace:*",
"got": "12.6.0",
"ky": "1.2.3",
"posthog-node": "3.1.1",
"@typebot.io/env": "workspace:*"
},

View File

@@ -1,7 +1,7 @@
import { env } from '@typebot.io/env'
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
import { PostHog } from 'posthog-node'
import got from 'got'
import ky from 'ky'
export const trackEvents = async (events: TelemetryEvent[]) => {
if (!env.NEXT_PUBLIC_POSTHOG_KEY) return
@@ -17,7 +17,7 @@ export const trackEvents = async (events: TelemetryEvent[]) => {
})
if (env.USER_CREATED_WEBHOOK_URL) {
try {
await got.post(env.USER_CREATED_WEBHOOK_URL, {
await ky.post(env.USER_CREATED_WEBHOOK_URL, {
json: {
email: event.data.email,
name: event.data.name ? event.data.name.split(' ')[0] : undefined,

186
pnpm-lock.yaml generated
View File

@@ -191,15 +191,15 @@ importers:
google-spreadsheet:
specifier: 4.1.1
version: 4.1.1(google-auth-library@8.9.0)
got:
specifier: 12.6.0
version: 12.6.0
immer:
specifier: 10.0.2
version: 10.0.2
jsonwebtoken:
specifier: 9.0.1
version: 9.0.1
ky:
specifier: 1.2.3
version: 1.2.3
libphonenumber-js:
specifier: 1.10.37
version: 1.10.37
@@ -593,6 +593,9 @@ importers:
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
next:
specifier: 14.1.0
version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
@@ -781,12 +784,9 @@ importers:
google-spreadsheet:
specifier: 4.1.1
version: 4.1.1(google-auth-library@8.9.0)
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: ^1.1.3
version: 1.2.0
specifier: 1.2.3
version: 1.2.3
libphonenumber-js:
specifier: 1.10.37
version: 1.10.37
@@ -812,6 +812,9 @@ importers:
'@typebot.io/forge-repository':
specifier: workspace:*
version: link:../forge/repository
'@types/node':
specifier: ^20.12.3
version: 20.12.3
'@types/nodemailer':
specifier: 6.4.14
version: 6.4.14
@@ -1334,9 +1337,9 @@ importers:
'@types/react':
specifier: 18.2.15
version: 18.2.15
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
typescript:
specifier: 5.3.2
version: 5.3.2
@@ -1355,9 +1358,9 @@ importers:
'@types/react':
specifier: 18.2.15
version: 18.2.15
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
typescript:
specifier: 5.3.2
version: 5.3.2
@@ -1367,9 +1370,9 @@ importers:
'@typebot.io/lib':
specifier: workspace:*
version: link:../../../lib
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
devDependencies:
'@typebot.io/forge':
specifier: workspace:*
@@ -1386,9 +1389,6 @@ importers:
packages/forge/blocks/mistral:
dependencies:
'@mistralai/mistralai':
specifier: 0.1.3
version: 0.1.3
ai:
specifier: 3.0.12
version: 3.0.12(react@18.2.0)(solid-js@1.7.8)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4)
@@ -1402,6 +1402,9 @@ importers:
'@typebot.io/tsconfig':
specifier: workspace:*
version: link:../../../tsconfig
'@types/node':
specifier: ^20.12.4
version: 20.12.4
'@types/react':
specifier: 18.2.15
version: 18.2.15
@@ -1426,9 +1429,9 @@ importers:
'@types/react':
specifier: 18.2.15
version: 18.2.15
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
typescript:
specifier: 5.3.2
version: 5.3.2
@@ -1524,9 +1527,9 @@ importers:
'@types/react':
specifier: 18.2.15
version: 18.2.15
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
typescript:
specifier: 5.3.2
version: 5.3.2
@@ -1645,9 +1648,9 @@ importers:
google-auth-library:
specifier: 8.9.0
version: 8.9.0
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
minio:
specifier: 7.1.3
version: 7.1.3
@@ -1884,9 +1887,9 @@ importers:
deep-object-diff:
specifier: 1.1.9
version: 1.1.9
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
prompts:
specifier: 2.4.2
version: 2.4.2
@@ -1911,9 +1914,9 @@ importers:
'@typebot.io/schemas':
specifier: workspace:*
version: link:../schemas
got:
specifier: 12.6.0
version: 12.6.0
ky:
specifier: 1.2.3
version: 1.2.3
posthog-node:
specifier: 3.1.1
version: 3.1.1
@@ -6838,7 +6841,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
@@ -6859,14 +6862,14 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.11.26)
jest-config: 29.7.0(@types/node@20.12.3)
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -6894,7 +6897,7 @@ packages:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
jest-mock: 29.7.0
dev: true
@@ -6921,7 +6924,7 @@ packages:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 20.11.26
'@types/node': 20.12.3
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -6954,7 +6957,7 @@ packages:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25
'@types/node': 20.11.26
'@types/node': 20.12.3
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@@ -7042,7 +7045,7 @@ packages:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 20.11.26
'@types/node': 20.12.3
'@types/yargs': 17.0.32
chalk: 4.1.2
dev: true
@@ -7461,14 +7464,6 @@ packages:
zod-to-json-schema: 3.22.4(zod@3.22.4)
dev: true
/@mistralai/mistralai@0.1.3:
resolution: {integrity: sha512-WUHxC2xdeqX9PTXJEqdiNY54vT2ir72WSJrZTTBKRnkfhX6zIfCYA24faRlWjUB5WTpn+wfdGsTMl3ArijlXFA==}
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
dev: false
/@next/env@14.0.5-canary.46:
resolution: {integrity: sha512-dvNzrArTfe3VY1VIscpb3E2e7SZ1qwFe82WGzpOVbxilT3JcsnVGYF/uq8Jj1qKWPI5C/aePNXwA97JRNAXpRQ==}
dev: false
@@ -7827,7 +7822,7 @@ packages:
engines: {node: '>=16'}
hasBin: true
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
playwright-core: 1.36.0
optionalDependencies:
fsevents: 2.3.2
@@ -9453,7 +9448,7 @@ packages:
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies:
'@types/connect': 3.4.38
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: false
/@types/canvas-confetti@1.6.0:
@@ -9463,13 +9458,13 @@ packages:
/@types/cli-progress@3.11.5:
resolution: {integrity: sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/connect@3.4.38:
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: false
/@types/content-type@1.1.8:
@@ -9482,7 +9477,7 @@ packages:
/@types/cors@2.8.13:
resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
/@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -9528,7 +9523,7 @@ packages:
/@types/express-serve-static-core@4.17.43:
resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
'@types/qs': 6.9.7
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
@@ -9546,7 +9541,7 @@ packages:
/@types/graceful-fs@4.1.9:
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/hast@2.3.10:
@@ -9602,7 +9597,7 @@ packages:
/@types/jsdom@20.0.1:
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
'@types/tough-cookie': 4.0.5
parse5: 7.1.2
dev: true
@@ -9617,7 +9612,7 @@ packages:
/@types/jsonwebtoken@9.0.2:
resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/katex@0.16.7:
@@ -9655,7 +9650,7 @@ packages:
/@types/micro@7.3.7:
resolution: {integrity: sha512-MFsX7eCj0Tg3TtphOQvANNvNtFpya+s/rYOCdV6o+DFjOQPFi2EVRbBALjbbgZTXUaJP1Q281MJiJOD40d0UxQ==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/mime@1.3.5:
@@ -9676,7 +9671,7 @@ packages:
/@types/node-fetch@2.6.11:
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
form-data: 4.0.0
dev: false
@@ -9687,6 +9682,18 @@ packages:
resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/node@20.12.3:
resolution: {integrity: sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==}
dependencies:
undici-types: 5.26.5
/@types/node@20.12.4:
resolution: {integrity: sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/node@20.4.2:
resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==}
@@ -9698,13 +9705,13 @@ packages:
/@types/nodemailer@6.4.14:
resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/nodemailer@6.4.8:
resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/normalize-package-data@2.4.4:
@@ -9717,7 +9724,7 @@ packages:
/@types/papaparse@5.3.7:
resolution: {integrity: sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/parse-json@4.0.2:
@@ -9735,7 +9742,7 @@ packages:
/@types/prompts@2.4.4:
resolution: {integrity: sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
kleur: 3.0.3
dev: true
@@ -9745,7 +9752,7 @@ packages:
/@types/qrcode@1.5.5:
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: true
/@types/qs@6.9.7:
@@ -9801,7 +9808,7 @@ packages:
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
dependencies:
'@types/mime': 1.3.5
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: false
/@types/serve-static@1.15.5:
@@ -9809,7 +9816,7 @@ packages:
dependencies:
'@types/http-errors': 2.0.4
'@types/mime': 3.0.4
'@types/node': 20.11.26
'@types/node': 20.12.3
dev: false
/@types/stack-utils@2.0.3:
@@ -9841,7 +9848,7 @@ packages:
/@types/webpack@5.28.5(@swc/core@1.3.101)(esbuild@0.19.11):
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
tapable: 2.2.1
webpack: 5.90.3(@swc/core@1.3.101)(esbuild@0.19.11)
transitivePeerDependencies:
@@ -12457,7 +12464,7 @@ packages:
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.11.26)
jest-config: 29.7.0(@types/node@20.12.3)
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@@ -13170,7 +13177,7 @@ packages:
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.13
'@types/node': 20.11.26
'@types/node': 20.12.3
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.4.2
@@ -15928,7 +15935,7 @@ packages:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
chalk: 4.1.2
co: 4.6.0
dedent: 1.5.1
@@ -15966,7 +15973,7 @@ packages:
create-jest: 29.7.0
exit: 0.1.2
import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.11.26)
jest-config: 29.7.0(@types/node@20.12.3)
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@@ -15977,7 +15984,7 @@ packages:
- ts-node
dev: true
/jest-config@29.7.0(@types/node@20.11.26):
/jest-config@29.7.0(@types/node@20.12.3):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@@ -15992,7 +15999,7 @@ packages:
'@babel/core': 7.22.9
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
babel-jest: 29.7.0(@babel/core@7.22.9)
chalk: 4.1.2
ci-info: 3.9.0
@@ -16058,7 +16065,7 @@ packages:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/jsdom': 20.0.1
'@types/node': 20.11.26
'@types/node': 20.12.3
jest-mock: 29.7.0
jest-util: 29.7.0
jsdom: 20.0.3
@@ -16075,7 +16082,7 @@ packages:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
jest-mock: 29.7.0
jest-util: 29.7.0
dev: true
@@ -16091,7 +16098,7 @@ packages:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 20.11.26
'@types/node': 20.12.3
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -16142,7 +16149,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
jest-util: 29.7.0
dev: true
@@ -16197,7 +16204,7 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@@ -16228,7 +16235,7 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
chalk: 4.1.2
cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2
@@ -16280,7 +16287,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -16305,7 +16312,7 @@ packages:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.26
'@types/node': 20.12.3
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@@ -16317,7 +16324,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
merge-stream: 2.0.0
supports-color: 8.1.1
dev: false
@@ -16326,7 +16333,7 @@ packages:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -16658,10 +16665,9 @@ packages:
engines: {node: '>=18'}
dev: false
/ky@1.2.0:
resolution: {integrity: sha512-dnPW+T78MuJ9tLAiF/apJV7bP7RRRCARXQxsCmsWiKLXqGtMBOgDVOFRYzCAfNe/OrRyFyor5ESgvvC+QWEqOA==}
/ky@1.2.3:
resolution: {integrity: sha512-2IM3VssHfG2zYz2FsHRUqIp8chhLc9uxDMcK2THxgFfv8pQhnMfN8L0ul+iW4RdBl5AglF8ooPIflRm3yNH0IA==}
engines: {node: '>=18'}
dev: false
/language-subtag-registry@0.3.22:
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
@@ -18540,7 +18546,7 @@ packages:
engines: {node: '>=14'}
dependencies:
'@types/express': 4.17.21
'@types/node': 20.11.26
'@types/node': 20.12.3
accepts: 1.3.8
content-disposition: 0.5.4
depd: 1.1.2
@@ -21400,7 +21406,7 @@ packages:
resolution: {integrity: sha512-mn7CxL71FCRWkQp33jcJ7+xfRF7HGzPYZlq2c87U+6kxL1qd7f/N3S1g1E5uaSWe83V5v3jN/IiWqg9y8+kWRw==}
engines: {node: '>=12.*'}
dependencies:
'@types/node': 20.11.26
'@types/node': 20.12.3
qs: 6.11.2
/strnum@1.0.5: