🐛 (openai) Fix ask assistant not correctly referencing uploaded f… (#1469)
…iles Closes #1468, closes #1467, closes #1211
This commit is contained in:
@@ -3,7 +3,7 @@ import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks
|
||||
import { OpenAI } from 'openai'
|
||||
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
||||
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
||||
import { ReadOnlyVariableStore } from '@typebot.io/forge'
|
||||
import { VariableStore } from '@typebot.io/forge'
|
||||
import {
|
||||
ParseVariablesOptions,
|
||||
parseVariables,
|
||||
@@ -13,6 +13,9 @@ import { getCredentials } from '../queries/getCredentials'
|
||||
import { getSession } from '../queries/getSession'
|
||||
import { getBlockById } from '@typebot.io/schemas/helpers'
|
||||
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
||||
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
||||
import { updateSession } from '../queries/updateSession'
|
||||
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
|
||||
|
||||
type Props = {
|
||||
sessionId: string
|
||||
@@ -92,7 +95,8 @@ export const getMessageStream = async ({ sessionId, messages }: Props) => {
|
||||
credentials.data,
|
||||
credentials.iv
|
||||
)
|
||||
const variables: ReadOnlyVariableStore = {
|
||||
|
||||
const variables: VariableStore = {
|
||||
list: () => session.state.typebotsQueue[0].typebot.variables,
|
||||
get: (id: string) => {
|
||||
const variable = session.state.typebotsQueue[0].typebot.variables.find(
|
||||
@@ -105,10 +109,25 @@ export const getMessageStream = async ({ sessionId, messages }: Props) => {
|
||||
session.state.typebotsQueue[0].typebot.variables,
|
||||
params
|
||||
)(text),
|
||||
set: async (id: string, value: unknown) => {
|
||||
const variable = session.state.typebotsQueue[0].typebot.variables.find(
|
||||
(variable) => variable.id === id
|
||||
)
|
||||
if (!variable) return
|
||||
await updateSession({
|
||||
id: session.id,
|
||||
state: updateVariablesInSession(session.state)([
|
||||
{ ...variable, value },
|
||||
]),
|
||||
isReplying: undefined,
|
||||
})
|
||||
},
|
||||
}
|
||||
const stream = await action.run.stream.run({
|
||||
credentials: decryptedCredentials,
|
||||
options: block.options,
|
||||
options: deepParseVariables(
|
||||
session.state.typebotsQueue[0].typebot.variables
|
||||
)(block.options),
|
||||
variables,
|
||||
})
|
||||
if (!stream) return { status: 500, message: 'Could not create stream' }
|
||||
|
||||
@@ -89,9 +89,7 @@ export const createChatCompletionOpenAI = async (
|
||||
isNextBubbleMessageWithAssistantMessage(typebot)(
|
||||
blockId,
|
||||
assistantMessageVariableName
|
||||
) &&
|
||||
(!process.env.VERCEL_ENV ||
|
||||
(isPlaneteScale() && credentials && isCredentialsV2(credentials)))
|
||||
)
|
||||
) {
|
||||
return {
|
||||
clientSideActions: [
|
||||
@@ -102,7 +100,6 @@ export const createChatCompletionOpenAI = async (
|
||||
content?: string
|
||||
role: (typeof chatCompletionMessageRoles)[number]
|
||||
}[],
|
||||
runtime: process.env.VERCEL_ENV ? 'edge' : 'nodejs',
|
||||
},
|
||||
expectsDedicatedReply: true,
|
||||
},
|
||||
|
||||
@@ -58,9 +58,7 @@ export const executeForgedBlock = async (
|
||||
action.run.stream.getStreamVariableId(block.options)
|
||||
) &&
|
||||
state.isStreamEnabled &&
|
||||
!state.whatsApp &&
|
||||
(!process.env.VERCEL_ENV ||
|
||||
(isPlaneteScale() && credentials && isCredentialsV2(credentials)))
|
||||
!state.whatsApp
|
||||
) {
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
@@ -69,7 +67,6 @@ export const executeForgedBlock = async (
|
||||
type: 'stream',
|
||||
expectsDedicatedReply: true,
|
||||
stream: true,
|
||||
runtime: process.env.VERCEL_ENV ? 'edge' : 'nodejs',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@typebot.io/variables": "workspace:*",
|
||||
"@udecode/plate-common": "30.4.5",
|
||||
"ai": "3.0.12",
|
||||
"ai": "3.0.31",
|
||||
"chrono-node": "2.7.5",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns-tz": "2.0.0",
|
||||
@@ -29,7 +29,7 @@
|
||||
"libphonenumber-js": "1.10.37",
|
||||
"node-html-parser": "6.1.5",
|
||||
"nodemailer": "6.9.8",
|
||||
"openai": "4.28.4",
|
||||
"openai": "4.38.3",
|
||||
"qs": "6.11.2",
|
||||
"stripe": "12.13.0"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SessionState } from '@typebot.io/schemas'
|
||||
type Props = {
|
||||
id: string
|
||||
state: SessionState
|
||||
isReplying: boolean
|
||||
isReplying: boolean | undefined
|
||||
}
|
||||
|
||||
export const updateSession = ({
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"scripts": {
|
||||
"build": "pnpm tsc --noEmit && tsup",
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -40,7 +40,7 @@
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"tsup": "6.5.0",
|
||||
"typebot-js": "workspace:*",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/env": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"jest-environment-jsdom": "29.4.1",
|
||||
"prettier": "2.8.3",
|
||||
"ts-jest": "29.0.5",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@typebot.io/tsconfig": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.2.77",
|
||||
"version": "0.2.78",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -46,6 +46,6 @@
|
||||
"rollup-plugin-postcss": "4.0.2",
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,8 +165,8 @@ export const ChatChunk = (props: Props) => {
|
||||
class="flex flex-col flex-1 gap-2"
|
||||
style={{
|
||||
'max-width':
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultHostAvatarIsEnabled
|
||||
props.theme.chat?.guestAvatar?.isEnabled ??
|
||||
defaultGuestAvatarIsEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
|
||||
@@ -27,7 +27,7 @@ export const StreamingBubble = (props: Props) => {
|
||||
.map((block, index) => {
|
||||
if (index % 2 === 0) {
|
||||
return block.split('\n\n').map((line) =>
|
||||
domPurify.sanitize(marked.parse(line), {
|
||||
domPurify.sanitize(marked.parse(line.replace(/【.+】/g, '')), {
|
||||
ADD_ATTR: ['target'],
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ClientSideActionContext } from '@/types'
|
||||
import { readDataStream } from '@/utils/ai/readDataStream'
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import { isNotEmpty } from '@typebot.io/lib/utils'
|
||||
import { createUniqueId } from 'solid-js'
|
||||
@@ -7,22 +8,16 @@ let abortController: AbortController | null = null
|
||||
const secondsToWaitBeforeRetries = 3
|
||||
const maxRetryAttempts = 3
|
||||
|
||||
const edgeRuntimePath = '/api/integrations/openai/streamer'
|
||||
const nodejsRuntimePath = (sessionId: string) =>
|
||||
`/api/v1/sessions/${sessionId}/streamMessage`
|
||||
|
||||
export const streamChat =
|
||||
(context: ClientSideActionContext & { retryAttempt?: number }) =>
|
||||
async ({
|
||||
messages,
|
||||
runtime,
|
||||
onMessageStream,
|
||||
}: {
|
||||
messages?: {
|
||||
content?: string | undefined
|
||||
role?: 'system' | 'user' | 'assistant' | undefined
|
||||
}[]
|
||||
runtime: 'edge' | 'nodejs'
|
||||
onMessageStream?: (props: { id: string; message: string }) => void
|
||||
}): Promise<{ message?: string; error?: object }> => {
|
||||
try {
|
||||
@@ -31,12 +26,8 @@ export const streamChat =
|
||||
const apiHost = context.apiHost
|
||||
|
||||
const res = await fetch(
|
||||
isNotEmpty(apiHost)
|
||||
? apiHost
|
||||
: guessApiHost() +
|
||||
(runtime === 'edge'
|
||||
? edgeRuntimePath
|
||||
: nodejsRuntimePath(context.sessionId)),
|
||||
(isNotEmpty(apiHost) ? apiHost : guessApiHost()) +
|
||||
`/api/v2/sessions/${context.sessionId}/streamMessage`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -44,7 +35,6 @@ export const streamChat =
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages,
|
||||
sessionId: runtime === 'edge' ? context.sessionId : undefined,
|
||||
}),
|
||||
signal: abortController.signal,
|
||||
}
|
||||
@@ -61,7 +51,7 @@ export const streamChat =
|
||||
return streamChat({
|
||||
...context,
|
||||
retryAttempt: (context.retryAttempt ?? 0) + 1,
|
||||
})({ messages, onMessageStream, runtime })
|
||||
})({ messages, onMessageStream })
|
||||
}
|
||||
return {
|
||||
error: (await res.json()) || 'Failed to fetch the chat response.',
|
||||
@@ -75,22 +65,15 @@ export const streamChat =
|
||||
let message = ''
|
||||
|
||||
const reader = res.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
const id = createUniqueId()
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
const chunk = decoder.decode(value)
|
||||
message += chunk
|
||||
if (onMessageStream) onMessageStream({ id, message })
|
||||
if (abortController === null) {
|
||||
reader.cancel()
|
||||
break
|
||||
for await (const { type, value } of readDataStream(reader, {
|
||||
isAborted: () => abortController === null,
|
||||
})) {
|
||||
if (type === 'text') {
|
||||
message += value
|
||||
if (onMessageStream) onMessageStream({ id, message })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
80
packages/embeds/js/src/utils/ai/readDataStream.ts
Normal file
80
packages/embeds/js/src/utils/ai/readDataStream.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { StreamPartType, parseStreamPart } from './streamParts'
|
||||
|
||||
const NEWLINE = '\n'.charCodeAt(0)
|
||||
|
||||
// concatenates all the chunks into a single Uint8Array
|
||||
function concatChunks(chunks: Uint8Array[], totalLength: number) {
|
||||
const concatenatedChunks = new Uint8Array(totalLength)
|
||||
|
||||
let offset = 0
|
||||
for (const chunk of chunks) {
|
||||
concatenatedChunks.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
chunks.length = 0
|
||||
|
||||
return concatenatedChunks
|
||||
}
|
||||
|
||||
/**
|
||||
Converts a ReadableStreamDefaultReader into an async generator that yields
|
||||
StreamPart objects.
|
||||
|
||||
@param reader
|
||||
Reader for the stream to read from.
|
||||
@param isAborted
|
||||
Optional function that returns true if the request has been aborted.
|
||||
If the function returns true, the generator will stop reading the stream.
|
||||
If the function is not provided, the generator will not stop reading the stream.
|
||||
*/
|
||||
export async function* readDataStream(
|
||||
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||
{
|
||||
isAborted,
|
||||
}: {
|
||||
isAborted?: () => boolean
|
||||
} = {}
|
||||
): AsyncGenerator<StreamPartType> {
|
||||
// implementation note: this slightly more complex algorithm is required
|
||||
// to pass the tests in the edge environment.
|
||||
|
||||
const decoder = new TextDecoder()
|
||||
const chunks: Uint8Array[] = []
|
||||
let totalLength = 0
|
||||
|
||||
while (true) {
|
||||
const { value } = await reader.read()
|
||||
|
||||
if (value) {
|
||||
chunks.push(value)
|
||||
totalLength += value.length
|
||||
if (value[value.length - 1] !== NEWLINE) {
|
||||
// if the last character is not a newline, we have not read the whole JSON value
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (chunks.length === 0) {
|
||||
break // we have reached the end of the stream
|
||||
}
|
||||
|
||||
const concatenatedChunks = concatChunks(chunks, totalLength)
|
||||
totalLength = 0
|
||||
|
||||
const streamParts = decoder
|
||||
.decode(concatenatedChunks, { stream: true })
|
||||
.split('\n')
|
||||
.filter((line) => line !== '') // splitting leaves an empty string at the end
|
||||
.map(parseStreamPart)
|
||||
|
||||
for (const streamPart of streamParts) {
|
||||
yield streamPart
|
||||
}
|
||||
|
||||
// The request has been aborted, stop reading the stream.
|
||||
if (isAborted?.()) {
|
||||
reader.cancel()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
377
packages/embeds/js/src/utils/ai/streamParts.ts
Normal file
377
packages/embeds/js/src/utils/ai/streamParts.ts
Normal file
@@ -0,0 +1,377 @@
|
||||
import {
|
||||
AssistantMessage,
|
||||
DataMessage,
|
||||
FunctionCall,
|
||||
JSONValue,
|
||||
ToolCall,
|
||||
} from './types'
|
||||
|
||||
type StreamString =
|
||||
`${(typeof StreamStringPrefixes)[keyof typeof StreamStringPrefixes]}:${string}\n`
|
||||
|
||||
export interface StreamPart<CODE extends string, NAME extends string, TYPE> {
|
||||
code: CODE
|
||||
name: NAME
|
||||
parse: (value: JSONValue) => { type: NAME; value: TYPE }
|
||||
}
|
||||
|
||||
const textStreamPart: StreamPart<'0', 'text', string> = {
|
||||
code: '0',
|
||||
name: 'text',
|
||||
parse: (value: JSONValue) => {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error('"text" parts expect a string value.')
|
||||
}
|
||||
return { type: 'text', value }
|
||||
},
|
||||
}
|
||||
|
||||
const functionCallStreamPart: StreamPart<
|
||||
'1',
|
||||
'function_call',
|
||||
{ function_call: FunctionCall }
|
||||
> = {
|
||||
code: '1',
|
||||
name: 'function_call',
|
||||
parse: (value: JSONValue) => {
|
||||
if (
|
||||
value == null ||
|
||||
typeof value !== 'object' ||
|
||||
!('function_call' in value) ||
|
||||
typeof value.function_call !== 'object' ||
|
||||
value.function_call == null ||
|
||||
!('name' in value.function_call) ||
|
||||
!('arguments' in value.function_call) ||
|
||||
typeof value.function_call.name !== 'string' ||
|
||||
typeof value.function_call.arguments !== 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
'"function_call" parts expect an object with a "function_call" property.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'function_call',
|
||||
value: value as unknown as { function_call: FunctionCall },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const dataStreamPart: StreamPart<'2', 'data', Array<JSONValue>> = {
|
||||
code: '2',
|
||||
name: 'data',
|
||||
parse: (value: JSONValue) => {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error('"data" parts expect an array value.')
|
||||
}
|
||||
|
||||
return { type: 'data', value }
|
||||
},
|
||||
}
|
||||
|
||||
const errorStreamPart: StreamPart<'3', 'error', string> = {
|
||||
code: '3',
|
||||
name: 'error',
|
||||
parse: (value: JSONValue) => {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error('"error" parts expect a string value.')
|
||||
}
|
||||
return { type: 'error', value }
|
||||
},
|
||||
}
|
||||
|
||||
const assistantMessageStreamPart: StreamPart<
|
||||
'4',
|
||||
'assistant_message',
|
||||
AssistantMessage
|
||||
> = {
|
||||
code: '4',
|
||||
name: 'assistant_message',
|
||||
parse: (value: JSONValue) => {
|
||||
if (
|
||||
value == null ||
|
||||
typeof value !== 'object' ||
|
||||
!('id' in value) ||
|
||||
!('role' in value) ||
|
||||
!('content' in value) ||
|
||||
typeof value.id !== 'string' ||
|
||||
typeof value.role !== 'string' ||
|
||||
value.role !== 'assistant' ||
|
||||
!Array.isArray(value.content) ||
|
||||
!value.content.every(
|
||||
(item) =>
|
||||
item != null &&
|
||||
typeof item === 'object' &&
|
||||
'type' in item &&
|
||||
item.type === 'text' &&
|
||||
'text' in item &&
|
||||
item.text != null &&
|
||||
typeof item.text === 'object' &&
|
||||
'value' in item.text &&
|
||||
typeof item.text.value === 'string'
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
'"assistant_message" parts expect an object with an "id", "role", and "content" property.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'assistant_message',
|
||||
value: value as AssistantMessage,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const assistantControlDataStreamPart: StreamPart<
|
||||
'5',
|
||||
'assistant_control_data',
|
||||
{
|
||||
threadId: string
|
||||
messageId: string
|
||||
}
|
||||
> = {
|
||||
code: '5',
|
||||
name: 'assistant_control_data',
|
||||
parse: (value: JSONValue) => {
|
||||
if (
|
||||
value == null ||
|
||||
typeof value !== 'object' ||
|
||||
!('threadId' in value) ||
|
||||
!('messageId' in value) ||
|
||||
typeof value.threadId !== 'string' ||
|
||||
typeof value.messageId !== 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
'"assistant_control_data" parts expect an object with a "threadId" and "messageId" property.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'assistant_control_data',
|
||||
value: {
|
||||
threadId: value.threadId,
|
||||
messageId: value.messageId,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const dataMessageStreamPart: StreamPart<'6', 'data_message', DataMessage> = {
|
||||
code: '6',
|
||||
name: 'data_message',
|
||||
parse: (value: JSONValue) => {
|
||||
if (
|
||||
value == null ||
|
||||
typeof value !== 'object' ||
|
||||
!('role' in value) ||
|
||||
!('data' in value) ||
|
||||
typeof value.role !== 'string' ||
|
||||
value.role !== 'data'
|
||||
) {
|
||||
throw new Error(
|
||||
'"data_message" parts expect an object with a "role" and "data" property.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'data_message',
|
||||
value: value as DataMessage,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const toolCallStreamPart: StreamPart<
|
||||
'7',
|
||||
'tool_calls',
|
||||
{ tool_calls: ToolCall[] }
|
||||
> = {
|
||||
code: '7',
|
||||
name: 'tool_calls',
|
||||
parse: (value: JSONValue) => {
|
||||
if (
|
||||
value == null ||
|
||||
typeof value !== 'object' ||
|
||||
!('tool_calls' in value) ||
|
||||
typeof value.tool_calls !== 'object' ||
|
||||
value.tool_calls == null ||
|
||||
!Array.isArray(value.tool_calls) ||
|
||||
value.tool_calls.some(
|
||||
(tc) =>
|
||||
tc == null ||
|
||||
typeof tc !== 'object' ||
|
||||
!('id' in tc) ||
|
||||
typeof tc.id !== 'string' ||
|
||||
!('type' in tc) ||
|
||||
typeof tc.type !== 'string' ||
|
||||
!('function' in tc) ||
|
||||
tc.function == null ||
|
||||
typeof tc.function !== 'object' ||
|
||||
!('arguments' in tc.function) ||
|
||||
typeof tc.function.name !== 'string' ||
|
||||
typeof tc.function.arguments !== 'string'
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
'"tool_calls" parts expect an object with a ToolCallPayload.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'tool_calls',
|
||||
value: value as unknown as { tool_calls: ToolCall[] },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const messageAnnotationsStreamPart: StreamPart<
|
||||
'8',
|
||||
'message_annotations',
|
||||
Array<JSONValue>
|
||||
> = {
|
||||
code: '8',
|
||||
name: 'message_annotations',
|
||||
parse: (value: JSONValue) => {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error('"message_annotations" parts expect an array value.')
|
||||
}
|
||||
|
||||
return { type: 'message_annotations', value }
|
||||
},
|
||||
}
|
||||
|
||||
const streamParts = [
|
||||
textStreamPart,
|
||||
functionCallStreamPart,
|
||||
dataStreamPart,
|
||||
errorStreamPart,
|
||||
assistantMessageStreamPart,
|
||||
assistantControlDataStreamPart,
|
||||
dataMessageStreamPart,
|
||||
toolCallStreamPart,
|
||||
messageAnnotationsStreamPart,
|
||||
] as const
|
||||
|
||||
// union type of all stream parts
|
||||
type StreamParts =
|
||||
| typeof textStreamPart
|
||||
| typeof functionCallStreamPart
|
||||
| typeof dataStreamPart
|
||||
| typeof errorStreamPart
|
||||
| typeof assistantMessageStreamPart
|
||||
| typeof assistantControlDataStreamPart
|
||||
| typeof dataMessageStreamPart
|
||||
| typeof toolCallStreamPart
|
||||
| typeof messageAnnotationsStreamPart
|
||||
/**
|
||||
* Maps the type of a stream part to its value type.
|
||||
*/
|
||||
type StreamPartValueType = {
|
||||
[P in StreamParts as P['name']]: ReturnType<P['parse']>['value']
|
||||
}
|
||||
|
||||
export type StreamPartType =
|
||||
| ReturnType<typeof textStreamPart.parse>
|
||||
| ReturnType<typeof functionCallStreamPart.parse>
|
||||
| ReturnType<typeof dataStreamPart.parse>
|
||||
| ReturnType<typeof errorStreamPart.parse>
|
||||
| ReturnType<typeof assistantMessageStreamPart.parse>
|
||||
| ReturnType<typeof assistantControlDataStreamPart.parse>
|
||||
| ReturnType<typeof dataMessageStreamPart.parse>
|
||||
| ReturnType<typeof toolCallStreamPart.parse>
|
||||
| ReturnType<typeof messageAnnotationsStreamPart.parse>
|
||||
|
||||
export const streamPartsByCode = {
|
||||
[textStreamPart.code]: textStreamPart,
|
||||
[functionCallStreamPart.code]: functionCallStreamPart,
|
||||
[dataStreamPart.code]: dataStreamPart,
|
||||
[errorStreamPart.code]: errorStreamPart,
|
||||
[assistantMessageStreamPart.code]: assistantMessageStreamPart,
|
||||
[assistantControlDataStreamPart.code]: assistantControlDataStreamPart,
|
||||
[dataMessageStreamPart.code]: dataMessageStreamPart,
|
||||
[toolCallStreamPart.code]: toolCallStreamPart,
|
||||
[messageAnnotationsStreamPart.code]: messageAnnotationsStreamPart,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* The map of prefixes for data in the stream
|
||||
*
|
||||
* - 0: Text from the LLM response
|
||||
* - 1: (OpenAI) function_call responses
|
||||
* - 2: custom JSON added by the user using `Data`
|
||||
* - 6: (OpenAI) tool_call responses
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* 0:Vercel
|
||||
* 0:'s
|
||||
* 0: AI
|
||||
* 0: AI
|
||||
* 0: SDK
|
||||
* 0: is great
|
||||
* 0:!
|
||||
* 2: { "someJson": "value" }
|
||||
* 1: {"function_call": {"name": "get_current_weather", "arguments": "{\\n\\"location\\": \\"Charlottesville, Virginia\\",\\n\\"format\\": \\"celsius\\"\\n}"}}
|
||||
* 6: {"tool_call": {"id": "tool_0", "type": "function", "function": {"name": "get_current_weather", "arguments": "{\\n\\"location\\": \\"Charlottesville, Virginia\\",\\n\\"format\\": \\"celsius\\"\\n}"}}}
|
||||
*```
|
||||
*/
|
||||
export const StreamStringPrefixes = {
|
||||
[textStreamPart.name]: textStreamPart.code,
|
||||
[functionCallStreamPart.name]: functionCallStreamPart.code,
|
||||
[dataStreamPart.name]: dataStreamPart.code,
|
||||
[errorStreamPart.name]: errorStreamPart.code,
|
||||
[assistantMessageStreamPart.name]: assistantMessageStreamPart.code,
|
||||
[assistantControlDataStreamPart.name]: assistantControlDataStreamPart.code,
|
||||
[dataMessageStreamPart.name]: dataMessageStreamPart.code,
|
||||
[toolCallStreamPart.name]: toolCallStreamPart.code,
|
||||
[messageAnnotationsStreamPart.name]: messageAnnotationsStreamPart.code,
|
||||
} as const
|
||||
|
||||
export const validCodes = streamParts.map((part) => part.code)
|
||||
|
||||
/**
|
||||
Parses a stream part from a string.
|
||||
|
||||
@param line The string to parse.
|
||||
@returns The parsed stream part.
|
||||
@throws An error if the string cannot be parsed.
|
||||
*/
|
||||
export const parseStreamPart = (line: string): StreamPartType => {
|
||||
const firstSeparatorIndex = line.indexOf(':')
|
||||
|
||||
if (firstSeparatorIndex === -1) {
|
||||
throw new Error('Failed to parse stream string. No separator found.')
|
||||
}
|
||||
|
||||
const prefix = line.slice(0, firstSeparatorIndex)
|
||||
|
||||
if (!validCodes.includes(prefix as keyof typeof streamPartsByCode)) {
|
||||
throw new Error(`Failed to parse stream string. Invalid code ${prefix}.`)
|
||||
}
|
||||
|
||||
const code = prefix as keyof typeof streamPartsByCode
|
||||
|
||||
const textValue = line.slice(firstSeparatorIndex + 1)
|
||||
const jsonValue: JSONValue = JSON.parse(textValue)
|
||||
|
||||
return streamPartsByCode[code].parse(jsonValue)
|
||||
}
|
||||
|
||||
/**
|
||||
Prepends a string with a prefix from the `StreamChunkPrefixes`, JSON-ifies it,
|
||||
and appends a new line.
|
||||
|
||||
It ensures type-safety for the part type and value.
|
||||
*/
|
||||
export function formatStreamPart<T extends keyof StreamPartValueType>(
|
||||
type: T,
|
||||
value: StreamPartValueType[T]
|
||||
): StreamString {
|
||||
const streamPart = streamParts.find((part) => part.name === type)
|
||||
|
||||
if (!streamPart) {
|
||||
throw new Error(`Invalid stream part type: ${type}`)
|
||||
}
|
||||
|
||||
return `${streamPart.code}:${JSON.stringify(value)}\n`
|
||||
}
|
||||
355
packages/embeds/js/src/utils/ai/types.ts
Normal file
355
packages/embeds/js/src/utils/ai/types.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
// https://github.com/openai/openai-node/blob/07b3504e1c40fd929f4aae1651b83afc19e3baf8/src/resources/chat/completions.ts#L146-L159
|
||||
export interface FunctionCall {
|
||||
/**
|
||||
* The arguments to call the function with, as generated by the model in JSON
|
||||
* format. Note that the model does not always generate valid JSON, and may
|
||||
* hallucinate parameters not defined by your function schema. Validate the
|
||||
* arguments in your code before calling your function.
|
||||
*/
|
||||
arguments?: string
|
||||
|
||||
/**
|
||||
* The name of the function to call.
|
||||
*/
|
||||
name?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The tool calls generated by the model, such as function calls.
|
||||
*/
|
||||
export interface ToolCall {
|
||||
// The ID of the tool call.
|
||||
id: string
|
||||
|
||||
// The type of the tool. Currently, only `function` is supported.
|
||||
type: string
|
||||
|
||||
// The function that the model called.
|
||||
function: {
|
||||
// The name of the function.
|
||||
name: string
|
||||
|
||||
// The arguments to call the function with, as generated by the model in JSON
|
||||
arguments: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls which (if any) function is called by the model.
|
||||
* - none means the model will not call a function and instead generates a message.
|
||||
* - auto means the model can pick between generating a message or calling a function.
|
||||
* - Specifying a particular function via {"type: "function", "function": {"name": "my_function"}} forces the model to call that function.
|
||||
* none is the default when no functions are present. auto is the default if functions are present.
|
||||
*/
|
||||
export type ToolChoice =
|
||||
| 'none'
|
||||
| 'auto'
|
||||
| { type: 'function'; function: { name: string } }
|
||||
|
||||
/**
|
||||
* A list of tools the model may call. Currently, only functions are supported as a tool.
|
||||
* Use this to provide a list of functions the model may generate JSON inputs for.
|
||||
*/
|
||||
export interface Tool {
|
||||
type: 'function'
|
||||
function: Function
|
||||
}
|
||||
|
||||
export interface Function {
|
||||
/**
|
||||
* The name of the function to be called. Must be a-z, A-Z, 0-9, or contain
|
||||
* underscores and dashes, with a maximum length of 64.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* The parameters the functions accepts, described as a JSON Schema object. See the
|
||||
* [guide](/docs/guides/gpt/function-calling) for examples, and the
|
||||
* [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for
|
||||
* documentation about the format.
|
||||
*
|
||||
* To describe a function that accepts no parameters, provide the value
|
||||
* `{"type": "object", "properties": {}}`.
|
||||
*/
|
||||
parameters: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* A description of what the function does, used by the model to choose when and
|
||||
* how to call the function.
|
||||
*/
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type IdGenerator = () => string
|
||||
|
||||
/**
|
||||
* Shared types between the API and UI packages.
|
||||
*/
|
||||
export interface Message {
|
||||
id: string
|
||||
tool_call_id?: string
|
||||
createdAt?: Date
|
||||
content: string
|
||||
ui?: string | JSX.Element | JSX.Element[] | null | undefined
|
||||
role: 'system' | 'user' | 'assistant' | 'function' | 'data' | 'tool'
|
||||
/**
|
||||
* If the message has a role of `function`, the `name` field is the name of the function.
|
||||
* Otherwise, the name field should not be set.
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* If the assistant role makes a function call, the `function_call` field
|
||||
* contains the function call name and arguments. Otherwise, the field should
|
||||
* not be set. (Deprecated and replaced by tool_calls.)
|
||||
*/
|
||||
function_call?: string | FunctionCall
|
||||
|
||||
data?: JSONValue
|
||||
/**
|
||||
* If the assistant role makes a tool call, the `tool_calls` field contains
|
||||
* the tool call name and arguments. Otherwise, the field should not be set.
|
||||
*/
|
||||
tool_calls?: string | ToolCall[]
|
||||
|
||||
/**
|
||||
* Additional message-specific information added on the server via StreamData
|
||||
*/
|
||||
annotations?: JSONValue[] | undefined
|
||||
}
|
||||
|
||||
export type CreateMessage = Omit<Message, 'id'> & {
|
||||
id?: Message['id']
|
||||
}
|
||||
|
||||
export type ChatRequest = {
|
||||
messages: Message[]
|
||||
options?: RequestOptions
|
||||
// @deprecated
|
||||
functions?: Array<Function>
|
||||
// @deprecated
|
||||
function_call?: FunctionCall
|
||||
data?: Record<string, string>
|
||||
tools?: Array<Tool>
|
||||
tool_choice?: ToolChoice
|
||||
}
|
||||
|
||||
export type FunctionCallHandler = (
|
||||
chatMessages: Message[],
|
||||
functionCall: FunctionCall
|
||||
) => Promise<ChatRequest | void>
|
||||
|
||||
export type ToolCallHandler = (
|
||||
chatMessages: Message[],
|
||||
toolCalls: ToolCall[]
|
||||
) => Promise<ChatRequest | void>
|
||||
|
||||
export type RequestOptions = {
|
||||
headers?: Record<string, string> | Headers
|
||||
body?: object
|
||||
}
|
||||
|
||||
export type ChatRequestOptions = {
|
||||
options?: RequestOptions
|
||||
functions?: Array<Function>
|
||||
function_call?: FunctionCall
|
||||
tools?: Array<Tool>
|
||||
tool_choice?: ToolChoice
|
||||
data?: Record<string, string>
|
||||
}
|
||||
|
||||
export type UseChatOptions = {
|
||||
/**
|
||||
* The API endpoint that accepts a `{ messages: Message[] }` object and returns
|
||||
* a stream of tokens of the AI chat response. Defaults to `/api/chat`.
|
||||
*/
|
||||
api?: string
|
||||
|
||||
/**
|
||||
* A unique identifier for the chat. If not provided, a random one will be
|
||||
* generated. When provided, the `useChat` hook with the same `id` will
|
||||
* have shared states across components.
|
||||
*/
|
||||
id?: string
|
||||
|
||||
/**
|
||||
* Initial messages of the chat. Useful to load an existing chat history.
|
||||
*/
|
||||
initialMessages?: Message[]
|
||||
|
||||
/**
|
||||
* Initial input of the chat.
|
||||
*/
|
||||
initialInput?: string
|
||||
|
||||
/**
|
||||
* Callback function to be called when a function call is received.
|
||||
* If the function returns a `ChatRequest` object, the request will be sent
|
||||
* automatically to the API and will be used to update the chat.
|
||||
*/
|
||||
experimental_onFunctionCall?: FunctionCallHandler
|
||||
|
||||
/**
|
||||
* Callback function to be called when a tool call is received.
|
||||
* If the function returns a `ChatRequest` object, the request will be sent
|
||||
* automatically to the API and will be used to update the chat.
|
||||
*/
|
||||
experimental_onToolCall?: ToolCallHandler
|
||||
|
||||
/**
|
||||
* Callback function to be called when the API response is received.
|
||||
*/
|
||||
onResponse?: (response: Response) => void | Promise<void>
|
||||
|
||||
/**
|
||||
* Callback function to be called when the chat is finished streaming.
|
||||
*/
|
||||
onFinish?: (message: Message) => void
|
||||
|
||||
/**
|
||||
* Callback function to be called when an error is encountered.
|
||||
*/
|
||||
onError?: (error: Error) => void
|
||||
|
||||
/**
|
||||
* A way to provide a function that is going to be used for ids for messages.
|
||||
* If not provided nanoid is used by default.
|
||||
*/
|
||||
generateId?: IdGenerator
|
||||
|
||||
/**
|
||||
* The credentials mode to be used for the fetch request.
|
||||
* Possible values are: 'omit', 'same-origin', 'include'.
|
||||
* Defaults to 'same-origin'.
|
||||
*/
|
||||
credentials?: RequestCredentials
|
||||
|
||||
/**
|
||||
* HTTP headers to be sent with the API request.
|
||||
*/
|
||||
headers?: Record<string, string> | Headers
|
||||
|
||||
/**
|
||||
* Extra body object to be sent with the API request.
|
||||
* @example
|
||||
* Send a `sessionId` to the API along with the messages.
|
||||
* ```js
|
||||
* useChat({
|
||||
* body: {
|
||||
* sessionId: '123',
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
body?: object
|
||||
|
||||
/**
|
||||
* Whether to send extra message fields such as `message.id` and `message.createdAt` to the API.
|
||||
* Defaults to `false`. When set to `true`, the API endpoint might need to
|
||||
* handle the extra fields before forwarding the request to the AI service.
|
||||
*/
|
||||
sendExtraMessageFields?: boolean
|
||||
|
||||
/** Stream mode (default to "stream-data") */
|
||||
streamMode?: 'stream-data' | 'text'
|
||||
}
|
||||
|
||||
export type UseCompletionOptions = {
|
||||
/**
|
||||
* The API endpoint that accepts a `{ prompt: string }` object and returns
|
||||
* a stream of tokens of the AI completion response. Defaults to `/api/completion`.
|
||||
*/
|
||||
api?: string
|
||||
/**
|
||||
* An unique identifier for the chat. If not provided, a random one will be
|
||||
* generated. When provided, the `useChat` hook with the same `id` will
|
||||
* have shared states across components.
|
||||
*/
|
||||
id?: string
|
||||
|
||||
/**
|
||||
* Initial prompt input of the completion.
|
||||
*/
|
||||
initialInput?: string
|
||||
|
||||
/**
|
||||
* Initial completion result. Useful to load an existing history.
|
||||
*/
|
||||
initialCompletion?: string
|
||||
|
||||
/**
|
||||
* Callback function to be called when the API response is received.
|
||||
*/
|
||||
onResponse?: (response: Response) => void | Promise<void>
|
||||
|
||||
/**
|
||||
* Callback function to be called when the completion is finished streaming.
|
||||
*/
|
||||
onFinish?: (prompt: string, completion: string) => void
|
||||
|
||||
/**
|
||||
* Callback function to be called when an error is encountered.
|
||||
*/
|
||||
onError?: (error: Error) => void
|
||||
|
||||
/**
|
||||
* The credentials mode to be used for the fetch request.
|
||||
* Possible values are: 'omit', 'same-origin', 'include'.
|
||||
* Defaults to 'same-origin'.
|
||||
*/
|
||||
credentials?: RequestCredentials
|
||||
|
||||
/**
|
||||
* HTTP headers to be sent with the API request.
|
||||
*/
|
||||
headers?: Record<string, string> | Headers
|
||||
|
||||
/**
|
||||
* Extra body object to be sent with the API request.
|
||||
* @example
|
||||
* Send a `sessionId` to the API along with the prompt.
|
||||
* ```js
|
||||
* useChat({
|
||||
* body: {
|
||||
* sessionId: '123',
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
body?: object
|
||||
|
||||
/** Stream mode (default to "stream-data") */
|
||||
streamMode?: 'stream-data' | 'text'
|
||||
}
|
||||
|
||||
export type JSONValue =
|
||||
| null
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| { [x: string]: JSONValue }
|
||||
| Array<JSONValue>
|
||||
|
||||
export type AssistantMessage = {
|
||||
id: string
|
||||
role: 'assistant'
|
||||
content: Array<{
|
||||
type: 'text'
|
||||
text: {
|
||||
value: string
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
/*
|
||||
* A data message is an application-specific message from the assistant
|
||||
* that should be shown in order with the other messages.
|
||||
*
|
||||
* It can trigger other operations on the frontend, such as annotating
|
||||
* a map.
|
||||
*/
|
||||
export type DataMessage = {
|
||||
id?: string // optional id, implement if needed (e.g. for persistance)
|
||||
role: 'data'
|
||||
data: JSONValue // application-specific data
|
||||
}
|
||||
@@ -54,17 +54,12 @@ export const executeClientSideAction = async ({
|
||||
'streamOpenAiChatCompletion' in clientSideAction ||
|
||||
'stream' in clientSideAction
|
||||
) {
|
||||
const runtime =
|
||||
'streamOpenAiChatCompletion' in clientSideAction
|
||||
? clientSideAction.streamOpenAiChatCompletion.runtime
|
||||
: clientSideAction.runtime
|
||||
const { error, message } = await streamChat(context)({
|
||||
messages:
|
||||
'streamOpenAiChatCompletion' in clientSideAction
|
||||
? clientSideAction.streamOpenAiChatCompletion?.messages
|
||||
: undefined,
|
||||
onMessageStream,
|
||||
runtime,
|
||||
})
|
||||
if (error)
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.2.77",
|
||||
"version": "0.2.78",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -35,7 +35,7 @@
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tslib": "2.6.0",
|
||||
"tsx": "3.12.7",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@rollup/plugin-typescript": "11.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.2.77",
|
||||
"version": "0.2.78",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -39,7 +39,7 @@
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tslib": "2.6.0",
|
||||
"tsx": "3.12.7",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@rollup/plugin-typescript": "11.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from '@anthropic-ai/sdk'
|
||||
import { options as createMessageOptions } from '../actions/createChatMessage'
|
||||
import { ReadOnlyVariableStore } from '@typebot.io/forge'
|
||||
import { VariableStore } from '@typebot.io/forge'
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
import { z } from '@typebot.io/forge/zod'
|
||||
|
||||
@@ -9,7 +9,7 @@ export const parseChatMessages = ({
|
||||
variables,
|
||||
}: {
|
||||
options: Pick<z.infer<typeof createMessageOptions>, 'messages'>
|
||||
variables: ReadOnlyVariableStore
|
||||
variables: VariableStore
|
||||
}): Anthropic.Messages.MessageParam[] => {
|
||||
const parsedMessages = messages
|
||||
?.flatMap((message) => {
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.18.0",
|
||||
"ai": "3.0.12"
|
||||
"@anthropic-ai/sdk": "0.20.6",
|
||||
"ai": "3.0.31"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@typebot.io/lib": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"ky": "1.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"ky": "1.2.3",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"ky": "1.2.3",
|
||||
|
||||
@@ -80,6 +80,7 @@ export const createChatCompletion = createAction({
|
||||
blockId: 'anthropic',
|
||||
transform: (options) => ({
|
||||
...options,
|
||||
model: undefined,
|
||||
action: 'Create Chat Message',
|
||||
responseMapping: options.responseMapping?.map((res: any) =>
|
||||
res.item === 'Message content'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { options as createChatCompletionOption } from '../actions/createChatCompletion'
|
||||
import { ReadOnlyVariableStore } from '@typebot.io/forge'
|
||||
import { VariableStore } from '@typebot.io/forge'
|
||||
import { isDefined, isNotEmpty } from '@typebot.io/lib'
|
||||
import { z } from '@typebot.io/forge/zod'
|
||||
|
||||
@@ -8,7 +8,7 @@ export const parseMessages = ({
|
||||
variables,
|
||||
}: {
|
||||
options: Pick<z.infer<typeof createChatCompletionOption>, 'messages'>
|
||||
variables: ReadOnlyVariableStore
|
||||
variables: VariableStore
|
||||
}) =>
|
||||
messages
|
||||
?.flatMap((message) => {
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/node": "^20.12.4",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "3.0.12"
|
||||
"ai": "3.0.31"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export const createChatCompletion = createAction({
|
||||
blockId: 'anthropic',
|
||||
transform: (options) => ({
|
||||
...options,
|
||||
model: undefined,
|
||||
action: 'Create Chat Message',
|
||||
responseMapping: options.responseMapping?.map((res: any) =>
|
||||
res.item === 'Message content'
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/openai-block": "workspace:*",
|
||||
"ky": "1.2.3"
|
||||
|
||||
@@ -1,54 +1,68 @@
|
||||
import { createAction, option } from '@typebot.io/forge'
|
||||
import { isDefined, isEmpty } from '@typebot.io/lib'
|
||||
import {
|
||||
LogsStore,
|
||||
VariableStore,
|
||||
createAction,
|
||||
option,
|
||||
} from '@typebot.io/forge'
|
||||
import { isDefined, isEmpty, isNotEmpty } from '@typebot.io/lib'
|
||||
import { auth } from '../auth'
|
||||
import { ClientOptions, OpenAI } from 'openai'
|
||||
import { baseOptions } from '../baseOptions'
|
||||
import { executeFunction } from '@typebot.io/variables/executeFunction'
|
||||
import { readDataStream } from 'ai'
|
||||
import { deprecatedAskAssistantOptions } from '../deprecated'
|
||||
import { OpenAIAssistantStream } from '../helpers/OpenAIAssistantStream'
|
||||
|
||||
export const askAssistant = createAction({
|
||||
auth,
|
||||
baseOptions,
|
||||
name: 'Ask Assistant',
|
||||
options: option.object({
|
||||
assistantId: option.string.layout({
|
||||
label: 'Assistant ID',
|
||||
placeholder: 'Select an assistant',
|
||||
moreInfoTooltip: 'The OpenAI assistant you want to ask question to.',
|
||||
fetcher: 'fetchAssistants',
|
||||
}),
|
||||
threadId: option.string.layout({
|
||||
label: 'Thread ID',
|
||||
moreInfoTooltip:
|
||||
'Used to remember the conversation with the user. If empty, a new thread is created.',
|
||||
}),
|
||||
message: option.string.layout({
|
||||
label: 'Message',
|
||||
inputType: 'textarea',
|
||||
}),
|
||||
functions: option
|
||||
.array(
|
||||
option.object({
|
||||
name: option.string.layout({
|
||||
fetcher: 'fetchAssistantFunctions',
|
||||
label: 'Name',
|
||||
}),
|
||||
code: option.string.layout({
|
||||
inputType: 'code',
|
||||
label: 'Code',
|
||||
lang: 'javascript',
|
||||
moreInfoTooltip:
|
||||
'A javascript code snippet that can use the defined parameters. It should return a value.',
|
||||
withVariableButton: false,
|
||||
}),
|
||||
})
|
||||
)
|
||||
.layout({ accordion: 'Functions', itemLabel: 'function' }),
|
||||
responseMapping: option
|
||||
.saveResponseArray(['Message', 'Thread ID'] as const)
|
||||
.layout({
|
||||
accordion: 'Save response',
|
||||
options: option
|
||||
.object({
|
||||
assistantId: option.string.layout({
|
||||
label: 'Assistant ID',
|
||||
placeholder: 'Select an assistant',
|
||||
moreInfoTooltip: 'The OpenAI assistant you want to ask question to.',
|
||||
fetcher: 'fetchAssistants',
|
||||
}),
|
||||
}),
|
||||
threadVariableId: option.string.layout({
|
||||
label: 'Thread ID',
|
||||
moreInfoTooltip:
|
||||
'Used to remember the conversation with the user. If empty, a new thread is created.',
|
||||
inputType: 'variableDropdown',
|
||||
}),
|
||||
|
||||
message: option.string.layout({
|
||||
label: 'Message',
|
||||
inputType: 'textarea',
|
||||
}),
|
||||
functions: option
|
||||
.array(
|
||||
option.object({
|
||||
name: option.string.layout({
|
||||
fetcher: 'fetchAssistantFunctions',
|
||||
label: 'Name',
|
||||
}),
|
||||
code: option.string.layout({
|
||||
inputType: 'code',
|
||||
label: 'Code',
|
||||
lang: 'javascript',
|
||||
moreInfoTooltip:
|
||||
'A javascript code snippet that can use the defined parameters. It should return a value.',
|
||||
withVariableButton: false,
|
||||
}),
|
||||
})
|
||||
)
|
||||
.layout({ accordion: 'Functions', itemLabel: 'function' }),
|
||||
responseMapping: option
|
||||
.saveResponseArray(['Message', 'Thread ID'] as const, {
|
||||
item: { hiddenItems: ['Thread ID'] },
|
||||
})
|
||||
.layout({
|
||||
accordion: 'Save response',
|
||||
}),
|
||||
})
|
||||
.merge(deprecatedAskAssistantOptions),
|
||||
fetchers: [
|
||||
{
|
||||
id: 'fetchAssistants',
|
||||
@@ -121,6 +135,23 @@ export const askAssistant = createAction({
|
||||
getSetVariableIds: ({ responseMapping }) =>
|
||||
responseMapping?.map((r) => r.variableId).filter(isDefined) ?? [],
|
||||
run: {
|
||||
stream: {
|
||||
getStreamVariableId: ({ responseMapping }) =>
|
||||
responseMapping?.find((m) => !m.item || m.item === 'Message')
|
||||
?.variableId,
|
||||
run: async ({ credentials, options, variables }) =>
|
||||
createAssistantStream({
|
||||
apiKey: credentials.apiKey,
|
||||
assistantId: options.assistantId,
|
||||
message: options.message,
|
||||
baseUrl: options.baseUrl,
|
||||
apiVersion: options.apiVersion,
|
||||
threadVariableId: options.threadVariableId,
|
||||
variables,
|
||||
functions: options.functions,
|
||||
responseMapping: options.responseMapping,
|
||||
}),
|
||||
},
|
||||
server: async ({
|
||||
credentials: { apiKey },
|
||||
options: {
|
||||
@@ -130,143 +161,188 @@ export const askAssistant = createAction({
|
||||
message,
|
||||
responseMapping,
|
||||
threadId,
|
||||
threadVariableId,
|
||||
functions,
|
||||
},
|
||||
variables,
|
||||
logs,
|
||||
}) => {
|
||||
if (isEmpty(assistantId)) {
|
||||
logs.add('Assistant ID is empty')
|
||||
return
|
||||
}
|
||||
if (isEmpty(message)) {
|
||||
logs.add('Message is empty')
|
||||
return
|
||||
}
|
||||
const config = {
|
||||
const stream = await createAssistantStream({
|
||||
apiKey,
|
||||
baseURL: baseUrl,
|
||||
defaultHeaders: {
|
||||
'api-key': apiKey,
|
||||
},
|
||||
defaultQuery: apiVersion
|
||||
? {
|
||||
'api-version': apiVersion,
|
||||
}
|
||||
: undefined,
|
||||
} satisfies ClientOptions
|
||||
|
||||
const openai = new OpenAI(config)
|
||||
|
||||
// Create a thread if needed
|
||||
const currentThreadId = isEmpty(threadId)
|
||||
? (await openai.beta.threads.create({})).id
|
||||
: threadId
|
||||
|
||||
// Add a message to the thread
|
||||
const createdMessage = await openai.beta.threads.messages.create(
|
||||
currentThreadId,
|
||||
{
|
||||
role: 'user',
|
||||
content: message,
|
||||
}
|
||||
)
|
||||
|
||||
const run = await openai.beta.threads.runs.create(currentThreadId, {
|
||||
assistant_id: assistantId,
|
||||
assistantId,
|
||||
logs,
|
||||
message,
|
||||
baseUrl,
|
||||
apiVersion,
|
||||
threadVariableId,
|
||||
variables,
|
||||
threadId,
|
||||
functions,
|
||||
})
|
||||
|
||||
async function waitForRun(run: OpenAI.Beta.Threads.Runs.Run) {
|
||||
// Poll for status change
|
||||
while (run.status === 'queued' || run.status === 'in_progress') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
if (!stream) return
|
||||
|
||||
run = await openai.beta.threads.runs.retrieve(currentThreadId, run.id)
|
||||
}
|
||||
let writingMessage = ''
|
||||
|
||||
// Check the run status
|
||||
if (
|
||||
run.status === 'cancelled' ||
|
||||
run.status === 'cancelling' ||
|
||||
run.status === 'failed' ||
|
||||
run.status === 'expired'
|
||||
) {
|
||||
throw new Error(run.status)
|
||||
}
|
||||
if (run.status === 'requires_action') {
|
||||
if (run.required_action?.type === 'submit_tool_outputs') {
|
||||
const tool_outputs = (
|
||||
await Promise.all(
|
||||
run.required_action.submit_tool_outputs.tool_calls.map(
|
||||
async (toolCall) => {
|
||||
const parameters = JSON.parse(toolCall.function.arguments)
|
||||
|
||||
const functionToExecute = functions?.find(
|
||||
(f) => f.name === toolCall.function.name
|
||||
)
|
||||
if (!functionToExecute) return
|
||||
|
||||
const name = toolCall.function.name
|
||||
if (!name || !functionToExecute.code) return
|
||||
|
||||
const { output, newVariables } = await executeFunction({
|
||||
variables: variables.list(),
|
||||
body: functionToExecute.code,
|
||||
args: parameters,
|
||||
})
|
||||
|
||||
newVariables?.forEach((variable) => {
|
||||
variables.set(variable.id, variable.value)
|
||||
})
|
||||
|
||||
return {
|
||||
tool_call_id: toolCall.id,
|
||||
output,
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
).filter(isDefined)
|
||||
|
||||
run = await openai.beta.threads.runs.submitToolOutputs(
|
||||
currentThreadId,
|
||||
run.id,
|
||||
{ tool_outputs }
|
||||
)
|
||||
|
||||
await waitForRun(run)
|
||||
}
|
||||
for await (const { type, value } of readDataStream(stream.getReader())) {
|
||||
if (type === 'text') {
|
||||
writingMessage += value
|
||||
}
|
||||
}
|
||||
|
||||
await waitForRun(run)
|
||||
|
||||
const responseMessages = (
|
||||
await openai.beta.threads.messages.list(currentThreadId, {
|
||||
after: createdMessage.id,
|
||||
order: 'asc',
|
||||
})
|
||||
).data
|
||||
|
||||
responseMapping?.forEach((mapping) => {
|
||||
if (!mapping.variableId) return
|
||||
if (!mapping.item || mapping.item === 'Message') {
|
||||
let message = ''
|
||||
const messageContents = responseMessages[0].content
|
||||
for (const content of messageContents) {
|
||||
switch (content.type) {
|
||||
case 'text':
|
||||
message +=
|
||||
(message !== '' ? '\n\n' : '') +
|
||||
content.text.value.replace(/【.+】/g, '')
|
||||
break
|
||||
}
|
||||
}
|
||||
variables.set(mapping.variableId, message)
|
||||
variables.set(
|
||||
mapping.variableId,
|
||||
writingMessage.replace(/【.+】/g, '')
|
||||
)
|
||||
}
|
||||
if (mapping.item === 'Thread ID')
|
||||
variables.set(mapping.variableId, currentThreadId)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const createAssistantStream = async ({
|
||||
apiKey,
|
||||
assistantId,
|
||||
logs,
|
||||
message,
|
||||
baseUrl,
|
||||
apiVersion,
|
||||
threadVariableId,
|
||||
variables,
|
||||
threadId,
|
||||
functions,
|
||||
responseMapping,
|
||||
}: {
|
||||
apiKey?: string
|
||||
assistantId?: string
|
||||
message?: string
|
||||
baseUrl?: string
|
||||
apiVersion?: string
|
||||
threadVariableId?: string
|
||||
threadId?: string
|
||||
functions?: { name?: string; code?: string }[]
|
||||
responseMapping?: {
|
||||
item?: 'Thread ID' | 'Message' | undefined
|
||||
variableId?: string | undefined
|
||||
}[]
|
||||
logs?: LogsStore
|
||||
variables: VariableStore
|
||||
}): Promise<ReadableStream | undefined> => {
|
||||
if (isEmpty(assistantId)) {
|
||||
logs?.add('Assistant ID is empty')
|
||||
return
|
||||
}
|
||||
if (isEmpty(message)) {
|
||||
logs?.add('Message is empty')
|
||||
return
|
||||
}
|
||||
const config = {
|
||||
apiKey,
|
||||
baseURL: baseUrl,
|
||||
defaultHeaders: {
|
||||
'api-key': apiKey,
|
||||
},
|
||||
defaultQuery: apiVersion
|
||||
? {
|
||||
'api-version': apiVersion,
|
||||
}
|
||||
: undefined,
|
||||
} satisfies ClientOptions
|
||||
|
||||
const openai = new OpenAI(config)
|
||||
|
||||
let currentThreadId: string | undefined
|
||||
|
||||
if (
|
||||
threadVariableId &&
|
||||
isNotEmpty(variables.get(threadVariableId)?.toString())
|
||||
) {
|
||||
currentThreadId = variables.get(threadVariableId)?.toString()
|
||||
} else if (isNotEmpty(threadId)) {
|
||||
currentThreadId = threadId
|
||||
} else {
|
||||
currentThreadId = (await openai.beta.threads.create({})).id
|
||||
const threadIdResponseMapping = responseMapping?.find(
|
||||
(mapping) => mapping.item === 'Thread ID'
|
||||
)
|
||||
if (threadIdResponseMapping?.variableId)
|
||||
variables.set(threadIdResponseMapping.variableId, currentThreadId)
|
||||
|
||||
if (threadVariableId) variables.set(threadVariableId, currentThreadId)
|
||||
}
|
||||
|
||||
if (!currentThreadId) {
|
||||
logs?.add('Could not get thread ID')
|
||||
return
|
||||
}
|
||||
|
||||
// Add a message to the thread
|
||||
const createdMessage = await openai.beta.threads.messages.create(
|
||||
currentThreadId,
|
||||
{
|
||||
role: 'user',
|
||||
content: message,
|
||||
}
|
||||
)
|
||||
return OpenAIAssistantStream(
|
||||
{ threadId: currentThreadId, messageId: createdMessage.id },
|
||||
async ({ forwardStream }) => {
|
||||
const runStream = openai.beta.threads.runs.createAndStream(
|
||||
currentThreadId,
|
||||
{
|
||||
assistant_id: assistantId,
|
||||
}
|
||||
)
|
||||
|
||||
let runResult = await forwardStream(runStream)
|
||||
|
||||
while (
|
||||
runResult?.status === 'requires_action' &&
|
||||
runResult.required_action?.type === 'submit_tool_outputs'
|
||||
) {
|
||||
const tool_outputs = (
|
||||
await Promise.all(
|
||||
runResult.required_action.submit_tool_outputs.tool_calls.map(
|
||||
async (toolCall) => {
|
||||
const parameters = JSON.parse(toolCall.function.arguments)
|
||||
|
||||
const functionToExecute = functions?.find(
|
||||
(f) => f.name === toolCall.function.name
|
||||
)
|
||||
if (!functionToExecute) return
|
||||
|
||||
const name = toolCall.function.name
|
||||
if (!name || !functionToExecute.code) return
|
||||
|
||||
const { output, newVariables } = await executeFunction({
|
||||
variables: variables.list(),
|
||||
body: functionToExecute.code,
|
||||
args: parameters,
|
||||
})
|
||||
|
||||
newVariables?.forEach((variable) => {
|
||||
variables.set(variable.id, variable.value)
|
||||
})
|
||||
|
||||
return {
|
||||
tool_call_id: toolCall.id,
|
||||
output,
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
).filter(isDefined)
|
||||
runResult = await forwardStream(
|
||||
openai.beta.threads.runs.submitToolOutputsStream(
|
||||
currentThreadId,
|
||||
runResult.id,
|
||||
{ tool_outputs }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export const createChatCompletion = createAction({
|
||||
blockId: 'anthropic',
|
||||
transform: (options) => ({
|
||||
...options,
|
||||
model: undefined,
|
||||
action: 'Create Chat Message',
|
||||
responseMapping: options.responseMapping?.map((res: any) =>
|
||||
res.item === 'Message content'
|
||||
|
||||
10
packages/forge/blocks/openai/deprecated.ts
Normal file
10
packages/forge/blocks/openai/deprecated.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { option } from '@typebot.io/forge'
|
||||
|
||||
export const deprecatedAskAssistantOptions = option.object({
|
||||
threadId: option.string.layout({
|
||||
label: 'Thread ID',
|
||||
moreInfoTooltip:
|
||||
'Used to remember the conversation with the user. If empty, a new thread is created.',
|
||||
isHidden: true,
|
||||
}),
|
||||
})
|
||||
145
packages/forge/blocks/openai/helpers/OpenAIAssistantStream.ts
Normal file
145
packages/forge/blocks/openai/helpers/OpenAIAssistantStream.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { AssistantMessage, DataMessage, formatStreamPart } from 'ai'
|
||||
import { AssistantStream } from 'openai/lib/AssistantStream'
|
||||
import { Run } from 'openai/resources/beta/threads/runs/runs'
|
||||
|
||||
/**
|
||||
You can pass the thread and the latest message into the `AssistantResponse`. This establishes the context for the response.
|
||||
*/
|
||||
type AssistantResponseSettings = {
|
||||
/**
|
||||
The thread ID that the response is associated with.
|
||||
*/
|
||||
threadId: string
|
||||
|
||||
/**
|
||||
The ID of the latest message that the response is associated with.
|
||||
*/
|
||||
messageId: string
|
||||
}
|
||||
|
||||
/**
|
||||
The process parameter is a callback in which you can run the assistant on threads, and send messages and data messages to the client.
|
||||
*/
|
||||
type AssistantResponseCallback = (options: {
|
||||
/**
|
||||
@deprecated use variable from outer scope instead.
|
||||
*/
|
||||
threadId: string
|
||||
|
||||
/**
|
||||
@deprecated use variable from outer scope instead.
|
||||
*/
|
||||
messageId: string
|
||||
|
||||
/**
|
||||
Forwards an assistant message (non-streaming) to the client.
|
||||
*/
|
||||
sendMessage: (message: AssistantMessage) => void
|
||||
|
||||
/**
|
||||
Send a data message to the client. You can use this to provide information for rendering custom UIs while the assistant is processing the thread.
|
||||
*/
|
||||
sendDataMessage: (message: DataMessage) => void
|
||||
|
||||
/**
|
||||
Forwards the assistant response stream to the client. Returns the `Run` object after it completes, or when it requires an action.
|
||||
*/
|
||||
forwardStream: (stream: AssistantStream) => Promise<Run | undefined>
|
||||
}) => Promise<void>
|
||||
|
||||
export const OpenAIAssistantStream = (
|
||||
{ threadId, messageId }: AssistantResponseSettings,
|
||||
process: AssistantResponseCallback
|
||||
) =>
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
const textEncoder = new TextEncoder()
|
||||
|
||||
const sendMessage = (message: AssistantMessage) => {
|
||||
controller.enqueue(
|
||||
textEncoder.encode(formatStreamPart('assistant_message', message))
|
||||
)
|
||||
}
|
||||
|
||||
const sendDataMessage = (message: DataMessage) => {
|
||||
controller.enqueue(
|
||||
textEncoder.encode(formatStreamPart('data_message', message))
|
||||
)
|
||||
}
|
||||
|
||||
const sendError = (errorMessage: string) => {
|
||||
controller.enqueue(
|
||||
textEncoder.encode(formatStreamPart('error', errorMessage))
|
||||
)
|
||||
}
|
||||
|
||||
const forwardStream = async (stream: AssistantStream) => {
|
||||
let result: Run | undefined = undefined
|
||||
|
||||
for await (const value of stream) {
|
||||
switch (value.event) {
|
||||
case 'thread.message.created': {
|
||||
controller.enqueue(
|
||||
textEncoder.encode(
|
||||
formatStreamPart('assistant_message', {
|
||||
id: value.data.id,
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: { value: '' } }],
|
||||
})
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case 'thread.message.delta': {
|
||||
const content = value.data.delta.content?.[0]
|
||||
|
||||
if (content?.type === 'text' && content.text?.value != null) {
|
||||
controller.enqueue(
|
||||
textEncoder.encode(
|
||||
formatStreamPart('text', content.text.value)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'thread.run.completed':
|
||||
case 'thread.run.requires_action': {
|
||||
result = value.data
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// send the threadId and messageId as the first message:
|
||||
controller.enqueue(
|
||||
textEncoder.encode(
|
||||
formatStreamPart('assistant_control_data', {
|
||||
threadId,
|
||||
messageId,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
try {
|
||||
await process({
|
||||
threadId,
|
||||
messageId,
|
||||
sendMessage,
|
||||
sendDataMessage,
|
||||
forwardStream,
|
||||
})
|
||||
} catch (error) {
|
||||
sendError((error as any).message ?? `${error}`)
|
||||
} finally {
|
||||
controller.close()
|
||||
}
|
||||
},
|
||||
pull(controller) {},
|
||||
cancel() {},
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { OpenAI } from 'openai'
|
||||
import { ReadOnlyVariableStore } from '@typebot.io/forge'
|
||||
import { VariableStore } from '@typebot.io/forge'
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
import { ChatCompletionOptions } from '../shared/parseChatCompletionOptions'
|
||||
|
||||
@@ -8,7 +8,7 @@ export const parseChatCompletionMessages = ({
|
||||
variables,
|
||||
}: {
|
||||
options: ChatCompletionOptions
|
||||
variables: ReadOnlyVariableStore
|
||||
variables: VariableStore
|
||||
}): OpenAI.Chat.ChatCompletionMessageParam[] => {
|
||||
const parsedMessages = messages
|
||||
?.flatMap((message) => {
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
"author": "Baptiste Arnaud",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"ai": "3.0.12",
|
||||
"openai": "4.28.4"
|
||||
"ai": "3.0.31",
|
||||
"openai": "4.38.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/variables": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogsStore, ReadOnlyVariableStore } from '@typebot.io/forge/types'
|
||||
import { VariableStore } from '@typebot.io/forge/types'
|
||||
import { ChatCompletionOptions } from './parseChatCompletionOptions'
|
||||
import { executeFunction } from '@typebot.io/variables/executeFunction'
|
||||
import { OpenAIStream, ToolCallPayload } from 'ai'
|
||||
@@ -10,7 +10,7 @@ import { parseToolParameters } from '../helpers/parseToolParameters'
|
||||
type Props = {
|
||||
credentials: { apiKey?: string }
|
||||
options: ChatCompletionOptions
|
||||
variables: ReadOnlyVariableStore
|
||||
variables: VariableStore
|
||||
config: { baseUrl: string; defaultModel?: string }
|
||||
}
|
||||
export const runChatCompletionStream = async ({
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/qrcode": "^1.5.3",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"qrcode": "^1.5.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export const createChatCompletion = createAction({
|
||||
blockId: 'anthropic',
|
||||
transform: (options) => ({
|
||||
...options,
|
||||
model: undefined,
|
||||
action: 'Create Chat Message',
|
||||
responseMapping: options.responseMapping?.map((res: any) =>
|
||||
res.item === 'Message content'
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"@typebot.io/variables": "workspace:*",
|
||||
"@typebot.io/openai-block": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/react": "18.2.15",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"ky": "1.2.3"
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ export type FunctionToExecute = {
|
||||
content: string
|
||||
}
|
||||
|
||||
export type ReadOnlyVariableStore = Omit<VariableStore, 'set'>
|
||||
|
||||
export type TurnableIntoParam<T = {}> = {
|
||||
blockId: string
|
||||
/**
|
||||
@@ -65,7 +63,7 @@ export type ActionDefinition<
|
||||
run: (params: {
|
||||
credentials: CredentialsFromAuthDef<A>
|
||||
options: z.infer<BaseOptions> & z.infer<Options>
|
||||
variables: ReadOnlyVariableStore
|
||||
variables: VariableStore
|
||||
}) => Promise<ReadableStream<any> | undefined>
|
||||
}
|
||||
web?: {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"next": "14.1.0",
|
||||
"nodemailer": "6.9.8",
|
||||
"tslib": "2.6.0",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "14.0.0",
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
"prisma": "5.12.1",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"tsx": "3.12.7",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"@typebot.io/prisma": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@typebot.io/env": "workspace:*",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ export const clientSideActionSchema = z.discriminatedUnion('type', [
|
||||
messages: z.array(
|
||||
nativeMessageSchema.pick({ content: true, role: true })
|
||||
),
|
||||
runtime: z.enum(['edge', 'nodejs']),
|
||||
}),
|
||||
})
|
||||
.merge(clientSideActionBaseSchema)
|
||||
@@ -152,7 +151,6 @@ export const clientSideActionSchema = z.discriminatedUnion('type', [
|
||||
.object({
|
||||
type: z.literal('stream'),
|
||||
stream: z.literal(true),
|
||||
runtime: z.enum(['edge', 'nodejs']),
|
||||
})
|
||||
.merge(clientSideActionBaseSchema)
|
||||
.openapi({
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"@typebot.io/forge-repository": "workspace:*",
|
||||
"@typebot.io/prisma": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"typescript": "5.3.2"
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ const inspectTypebot = async () => {
|
||||
customDomain: true,
|
||||
createdAt: true,
|
||||
isArchived: true,
|
||||
isClosed: true,
|
||||
publishedTypebot: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"prompts": "2.4.2",
|
||||
"stripe": "12.13.0",
|
||||
"tsx": "3.12.7",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.4.5",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user