mirror of
https://github.com/baptisteArno/typebot.io.git
synced 2026-06-05 21:04:43 +08:00
🔒️ Upgrade vulnerable deps (ai v5, nodemailer v8, otel sdk-node 0.217) (#2491)
## Summary
Fixes 18 open Dependabot alerts and migrates affected code to the new
major versions:
- `@opentelemetry/sdk-node` → `^0.217.0` (Prometheus exporter DoS,
GHSA-q7rr-3cgh-j5r3)
- `nodemailer` → `^8.0.5` across all manifests + root override
(GHSA-vvjj-xcjg-gr5g, GHSA-c7w3-x93f-qmm8)
- `ai` → `^5.0.52` (GHSA-rwvc-j5jr-mgvh); legacy 3.x dep removed from
`packages/deprecated/legacy` and replaced with a small in-tree
`OpenAIStream` + `StreamingTextResponse` shim
- Provider SDKs aligned to v5 peer: `@ai-sdk/openai`, `anthropic`,
`groq`, `mistral`, `perplexity`, `deepseek`, `togetherai`, `openRouter`,
`dify-ai-provider`
### AI SDK v4 → v5 migration
- `parseTools`: `parameters` renamed to `inputSchema`
- `runChatCompletion` / `runChatCompletionStream`: `maxSteps` replaced
by `stopWhen(stepCountIs(maxSteps))`;
`usage.{prompt,completion,total}Tokens` replaced by
`totalUsage.{input,output,total}Tokens`
- New `toLegacyDataStream` helper that re-emits the v4 data-stream
protocol (`0:text`, `3:error`, `9:tool_call`, …) so existing consumers
in `embeds/js` and the OpenAI `askAssistant` / `askModel` handlers keep
working
- `compatibility: "strict"` removed from `createOpenAI` (option dropped
in v5)
- `formatDataStreamPart` / `processDataStream` imports moved to
`@ai-sdk/ui-utils` (legacy package pinned at 1.2.11)
### E2E test follow-up
Second commit fixes Playwright tests that broke once the env-resolved
URLs / new SDK surface kicked in:
- `fileUpload`: assert exported URL contains `parseS3PublicBaseUrl()`
(not `S3_ENDPOINT`) so it works with `S3_PUBLIC_CUSTOM_DOMAIN`; verify
post-deletion via cache-busted `request.get` instead of a CDN-cached new
tab.
- `ssrf`: assert on the actual "Security validation failed" log emitted
by the pre-flight check; fixture now maps `response.statusCode` into a
`Status` variable so `Status: …` assertions resolve.
- Root `dev` script includes `@typebot.io/partykit` so the webhook
listener e2e test can hit PartyKit on `:1999`.
Also fixes a pre-existing broken anchor link in `whatsapp-ai-agent.mdx`
that blocked the landing-page link checker.
## Test plan
- [ ] `bunx nx test` passes
- [ ] `bunx nx typecheck` passes
- [ ] `bunx nx affected -t
format-and-lint,lint-repo,check-broken-links,test --parallel=4` passes
(pre-commit)
- [ ] `bun run dev` boots builder, viewer, workflows **and** PartyKit
- [ ] Viewer Playwright suite: `fileUpload.spec.ts`, `ssrf.spec.ts`,
`webhookListener.spec.ts` all green
- [ ] Manual smoke: OpenAI `askAssistant` block streams correctly in the
embed (v4 data-stream protocol preserved)
- [ ] Manual smoke: Anthropic / Mistral / Groq blocks still execute
end-to-end
- [ ] Manual smoke: send a test email through a workspace SMTP block
(nodemailer v8)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
77fd228c96
commit
6f289f647f
@ -51,8 +51,8 @@
|
||||
"@effect/opentelemetry": "4.0.0-beta.38",
|
||||
"@giphy/js-fetch-api": "^5.7.0",
|
||||
"@giphy/react-components": "^10.1.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
|
||||
"@opentelemetry/sdk-node": "^0.212.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
|
||||
"@opentelemetry/sdk-node": "^0.217.0",
|
||||
"@opentelemetry/sdk-trace-base": "^2.5.0",
|
||||
"@orpc/client": "^1.13.9",
|
||||
"@orpc/openapi": "^1.13.9",
|
||||
@ -104,7 +104,7 @@
|
||||
"@upstash/ratelimit": "^0.4.3",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"@vercel/otel": "^2.1.1",
|
||||
"ai": "^4.3.19",
|
||||
"ai": "^5.0.52",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"date-fns": "^2.30.0",
|
||||
@ -125,7 +125,7 @@
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
"next-themes": "^0.4.6",
|
||||
"nextjs-cors": "^2.1.2",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^8.0.5",
|
||||
"nuqs": "^2.3.2",
|
||||
"openai": "^6.9.1",
|
||||
"papaparse": "^5.4.1",
|
||||
|
||||
@ -89,7 +89,7 @@ Flowise acts as the backend for your chatbot. **It enables it to process user qu
|
||||
Readwise is currently in private beta, which is why we’re demonstrating how to deploy it on Render.
|
||||
</Warning>
|
||||
|
||||
To deploy Flowise, [follow our Flowise installation guide](./build-ai-chatbot-with-custom-knowledge-base#deploying-flowise-on-render-com.mdx) to set up Flowise on your local machine or a cloud platform like Render.com. Ensure you have the necessary API keys (e.g., OpenAI, Pinecone) and configure them in Flowise.
|
||||
To deploy Flowise, [follow our Flowise installation guide](./build-ai-chatbot-with-custom-knowledge-base.mdx#deploying-flowise-on-rendercom) to set up Flowise on your local machine or a cloud platform like Render.com. Ensure you have the necessary API keys (e.g., OpenAI, Pinecone) and configure them in Flowise.
|
||||
|
||||
#### Build the Knowledge Base
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
"google-spreadsheet": "^4.1.4",
|
||||
"next": "^16.1.6",
|
||||
"nextjs-cors": "^2.1.2",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^8.0.5",
|
||||
"openai": "^6.9.1",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
|
||||
@ -64,6 +64,11 @@
|
||||
}
|
||||
],
|
||||
"responseVariableMapping": [
|
||||
{
|
||||
"id": "mapping-status",
|
||||
"variableId": "var-status",
|
||||
"bodyPath": "statusCode"
|
||||
},
|
||||
{
|
||||
"id": "mapping-data",
|
||||
"variableId": "var-response",
|
||||
@ -86,7 +91,9 @@
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [{ "text": "Response: {{Response}}" }]
|
||||
"children": [
|
||||
{ "text": "Status: {{Status}} — Response: {{Response}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -120,6 +127,10 @@
|
||||
{
|
||||
"id": "var-response",
|
||||
"name": "Response"
|
||||
},
|
||||
{
|
||||
"id": "var-status",
|
||||
"name": "Status"
|
||||
}
|
||||
],
|
||||
"theme": {
|
||||
|
||||
@ -3,6 +3,7 @@ import { createId } from "@paralleldrive/cuid2";
|
||||
import test, { expect } from "@playwright/test";
|
||||
import { env } from "@typebot.io/env";
|
||||
import { isDefined } from "@typebot.io/lib/utils";
|
||||
import { parseS3PublicBaseUrl } from "@typebot.io/lib/s3/parseS3PublicBaseUrl";
|
||||
import { importTypebotInDatabase } from "@typebot.io/playwright/databaseActions";
|
||||
import { parse } from "papaparse";
|
||||
import { getTestAsset } from "@/test/utils/playwright";
|
||||
@ -48,7 +49,7 @@ test("should work as expected", async ({ page, browser }) => {
|
||||
const file = readFileSync(downloadPath as string).toString();
|
||||
const { data } = parse(file);
|
||||
expect(data).toHaveLength(2);
|
||||
expect((data[1] as unknown[])[1]).toContain(env.S3_ENDPOINT);
|
||||
expect((data[1] as unknown[])[1]).toContain(parseS3PublicBaseUrl());
|
||||
|
||||
const urls = (
|
||||
await Promise.all(
|
||||
@ -64,10 +65,19 @@ test("should work as expected", async ({ page, browser }) => {
|
||||
await page2.goto(urls[0]);
|
||||
await expect(page2.locator("pre")).toBeVisible();
|
||||
|
||||
page.getByRole("button", { name: "Delete" }).click();
|
||||
await page.getByRole("button", { name: "Delete" }).click();
|
||||
await page.locator('button >> text="Delete"').click();
|
||||
await expect(page.locator('text="api.json"')).toBeHidden();
|
||||
const page3 = await browser.newPage();
|
||||
await page3.goto(urls[0]);
|
||||
await expect(page3.locator("pre")).toBeHidden();
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const res = await page.request.get(`${urls[0]}?_cb=${Date.now()}`, {
|
||||
headers: { "Cache-Control": "no-cache", Pragma: "no-cache" },
|
||||
});
|
||||
return res.status();
|
||||
},
|
||||
{ timeout: 15_000, intervals: [500, 1000, 2000] },
|
||||
)
|
||||
.not.toBe(200);
|
||||
});
|
||||
|
||||
@ -45,7 +45,7 @@ test.describe("SSRF protection", () => {
|
||||
await page.goto(`http://localhost:3000/typebots/${typebotId}/results`);
|
||||
await page.click('text="See logs"');
|
||||
await expect(
|
||||
page.locator('text="Webhook returned an error."').first(),
|
||||
page.getByText(/Security validation failed/).first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
"postinstall": "cd packages/env && bun run compile && nx db:generate prisma",
|
||||
"prepare": "husky && effect-language-service patch",
|
||||
"pre-commit": "nx affected -t format-and-lint,lint-repo,check-broken-links,test --parallel=4",
|
||||
"dev": "nx run-many --configuration=development -t dev,watch-deps -p builder,viewer,workflows",
|
||||
"dev": "nx run-many --configuration=development -t dev,watch-deps -p builder,viewer,workflows,@typebot.io/partykit",
|
||||
"format-and-lint:fix": "biome check . --write --unsafe",
|
||||
"lint-repo": "sherif -r unordered-dependencies -r packages-without-package-json --ignore-package @typebot.io/legacy --ignore-package bot-engine",
|
||||
"lint-repo:fix": "bun run lint-repo --fix",
|
||||
@ -65,6 +65,9 @@
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"packageManager": "bun@1.3.9",
|
||||
"overrides": {
|
||||
"nodemailer": "^8.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "24.x"
|
||||
},
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/variables": "workspace:*",
|
||||
"ai": "^4.3.19",
|
||||
"ai": "^5.0.52",
|
||||
"ky": "^1.2.4",
|
||||
"@typebot.io/runtime-session-store": "workspace:*",
|
||||
"zod": "^4.3.5"
|
||||
|
||||
@ -24,7 +24,7 @@ export const parseTools = ({
|
||||
if (!tool.code || !tool.name) return acc;
|
||||
acc[tool.name] = {
|
||||
description: tool.description,
|
||||
parameters: parseParameters(tool.parameters),
|
||||
inputSchema: parseParameters(tool.parameters),
|
||||
execute: async (args) => {
|
||||
const { output, newVariables } = await executeFunction({
|
||||
sessionStore,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { LogsStore, VariableStore } from "@typebot.io/forge/types";
|
||||
import { parseUnknownError } from "@typebot.io/lib/parseUnknownError";
|
||||
import type { SessionStore } from "@typebot.io/runtime-session-store";
|
||||
import { generateText, type LanguageModel } from "ai";
|
||||
import { generateText, type LanguageModel, stepCountIs } from "ai";
|
||||
import { maxSteps } from "./constants";
|
||||
import { parseChatCompletionMessages } from "./parseChatCompletionMessages";
|
||||
import { parseTools } from "./parseTools";
|
||||
@ -50,7 +50,7 @@ export const runChatCompletion = async ({
|
||||
temperature,
|
||||
messages: parsedMessages,
|
||||
tools: parseTools({ tools, variables, sessionStore }),
|
||||
maxSteps,
|
||||
stopWhen: stepCountIs(maxSteps),
|
||||
headers,
|
||||
});
|
||||
|
||||
@ -60,15 +60,15 @@ export const runChatCompletion = async ({
|
||||
variables.set([{ id: mapping.variableId, value: response.text }]);
|
||||
if (mapping.item === "Total tokens")
|
||||
variables.set([
|
||||
{ id: mapping.variableId, value: response.usage.totalTokens },
|
||||
{ id: mapping.variableId, value: response.totalUsage.totalTokens },
|
||||
]);
|
||||
if (mapping.item === "Prompt tokens")
|
||||
variables.set([
|
||||
{ id: mapping.variableId, value: response.usage.promptTokens },
|
||||
{ id: mapping.variableId, value: response.totalUsage.inputTokens },
|
||||
]);
|
||||
if (mapping.item === "Completion tokens")
|
||||
variables.set([
|
||||
{ id: mapping.variableId, value: response.usage.completionTokens },
|
||||
{ id: mapping.variableId, value: response.totalUsage.outputTokens },
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -4,11 +4,18 @@ import {
|
||||
parseUnknownErrorSync,
|
||||
} from "@typebot.io/lib/parseUnknownError";
|
||||
import type { SessionStore } from "@typebot.io/runtime-session-store";
|
||||
import { type LanguageModel, type StepResult, streamText, type Tool } from "ai";
|
||||
import {
|
||||
type LanguageModel,
|
||||
type StreamTextOnFinishCallback,
|
||||
stepCountIs,
|
||||
streamText,
|
||||
type Tool,
|
||||
} from "ai";
|
||||
import { maxSteps } from "./constants";
|
||||
import { parseChatCompletionMessages } from "./parseChatCompletionMessages";
|
||||
import { parseTools } from "./parseTools";
|
||||
import type { Tools } from "./schemas";
|
||||
import { toLegacyDataStream } from "./toLegacyDataStream";
|
||||
import type { MessageInput } from "./types";
|
||||
|
||||
type Props = {
|
||||
@ -24,14 +31,7 @@ type Props = {
|
||||
variableId?: string;
|
||||
}[]
|
||||
| undefined;
|
||||
onFinish?: (
|
||||
response: Omit<
|
||||
StepResult<Record<string, Tool>>,
|
||||
"stepType" | "isContinued"
|
||||
> & {
|
||||
readonly steps: StepResult<Record<string, Tool>>[];
|
||||
},
|
||||
) => void;
|
||||
onFinish?: StreamTextOnFinishCallback<Record<string, Tool>>;
|
||||
sessionStore: SessionStore;
|
||||
headers?: Record<string, string | undefined>;
|
||||
};
|
||||
@ -60,24 +60,30 @@ export const runChatCompletionStream = async ({
|
||||
messages: parsedMessages,
|
||||
temperature,
|
||||
tools: parseTools({ tools, variables, sessionStore }),
|
||||
maxSteps,
|
||||
stopWhen: stepCountIs(maxSteps),
|
||||
headers,
|
||||
onFinish: (response) => {
|
||||
responseMapping?.forEach((mapping) => {
|
||||
if (!mapping.variableId) return;
|
||||
if (mapping.item === "Total tokens")
|
||||
variables.set([
|
||||
{ id: mapping.variableId, value: response.usage.totalTokens },
|
||||
{
|
||||
id: mapping.variableId,
|
||||
value: response.totalUsage.totalTokens,
|
||||
},
|
||||
]);
|
||||
if (mapping.item === "Prompt tokens")
|
||||
variables.set([
|
||||
{ id: mapping.variableId, value: response.usage.promptTokens },
|
||||
{
|
||||
id: mapping.variableId,
|
||||
value: response.totalUsage.inputTokens,
|
||||
},
|
||||
]);
|
||||
if (mapping.item === "Completion tokens")
|
||||
variables.set([
|
||||
{
|
||||
id: mapping.variableId,
|
||||
value: response.usage.completionTokens,
|
||||
value: response.totalUsage.outputTokens,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -86,13 +92,13 @@ export const runChatCompletionStream = async ({
|
||||
});
|
||||
|
||||
return {
|
||||
stream: response.toDataStream({
|
||||
stream: toLegacyDataStream({
|
||||
stream: response.fullStream,
|
||||
getErrorMessage: (err) => {
|
||||
return JSON.stringify(
|
||||
parseUnknownErrorSync({ err, context: "While streaming AI" }),
|
||||
);
|
||||
},
|
||||
sendUsage: false,
|
||||
}),
|
||||
};
|
||||
} catch (err) {
|
||||
|
||||
129
packages/ai/src/toLegacyDataStream.ts
Normal file
129
packages/ai/src/toLegacyDataStream.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import type { TextStreamPart, ToolSet } from "ai";
|
||||
|
||||
type Props<Tools extends ToolSet> = {
|
||||
stream: AsyncIterable<TextStreamPart<Tools>>;
|
||||
getErrorMessage: (error: unknown) => string;
|
||||
};
|
||||
|
||||
export const toLegacyDataStream = <Tools extends ToolSet>({
|
||||
stream,
|
||||
getErrorMessage,
|
||||
}: Props<Tools>) =>
|
||||
new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
const textEncoder = new TextEncoder();
|
||||
let stepIndex = 0;
|
||||
|
||||
const enqueuePart = (type: LegacyDataStreamPartType, value: unknown) =>
|
||||
controller.enqueue(
|
||||
textEncoder.encode(formatLegacyDataStreamPart(type, value)),
|
||||
);
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk.type) {
|
||||
case "text-delta": {
|
||||
enqueuePart("text", chunk.text);
|
||||
break;
|
||||
}
|
||||
case "tool-input-start": {
|
||||
enqueuePart("tool_call_streaming_start", {
|
||||
toolCallId: chunk.id,
|
||||
toolName: chunk.toolName,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "tool-input-delta": {
|
||||
enqueuePart("tool_call_delta", {
|
||||
toolCallId: chunk.id,
|
||||
argsTextDelta: chunk.delta,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "tool-call": {
|
||||
enqueuePart("tool_call", {
|
||||
toolCallId: chunk.toolCallId,
|
||||
toolName: chunk.toolName,
|
||||
args: chunk.input,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "tool-result": {
|
||||
enqueuePart("tool_result", {
|
||||
toolCallId: chunk.toolCallId,
|
||||
result: chunk.output,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "tool-error": {
|
||||
enqueuePart("error", getErrorMessage(chunk.error));
|
||||
break;
|
||||
}
|
||||
case "file": {
|
||||
enqueuePart("file", {
|
||||
data: chunk.file.base64,
|
||||
mimeType: chunk.file.mediaType,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "start-step": {
|
||||
stepIndex += 1;
|
||||
enqueuePart("start_step", { messageId: `step-${stepIndex}` });
|
||||
break;
|
||||
}
|
||||
case "finish-step": {
|
||||
enqueuePart("finish_step", {
|
||||
finishReason: chunk.finishReason,
|
||||
isContinued: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "finish": {
|
||||
enqueuePart("finish_message", {
|
||||
finishReason: chunk.finishReason,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
enqueuePart("error", getErrorMessage(chunk.error));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
enqueuePart("error", getErrorMessage(error));
|
||||
} finally {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
type LegacyDataStreamPartType =
|
||||
| "text"
|
||||
| "error"
|
||||
| "tool_call"
|
||||
| "tool_result"
|
||||
| "tool_call_streaming_start"
|
||||
| "tool_call_delta"
|
||||
| "finish_message"
|
||||
| "finish_step"
|
||||
| "start_step"
|
||||
| "file";
|
||||
|
||||
const legacyDataStreamPrefixes = {
|
||||
text: "0",
|
||||
error: "3",
|
||||
tool_call: "9",
|
||||
tool_result: "a",
|
||||
tool_call_streaming_start: "b",
|
||||
tool_call_delta: "c",
|
||||
finish_message: "d",
|
||||
finish_step: "e",
|
||||
start_step: "f",
|
||||
file: "k",
|
||||
} satisfies Record<LegacyDataStreamPartType, string>;
|
||||
|
||||
const formatLegacyDataStreamPart = (
|
||||
type: LegacyDataStreamPartType,
|
||||
value: unknown,
|
||||
) => `${legacyDataStreamPrefixes[type]}:${JSON.stringify(value) ?? "null"}\n`;
|
||||
@ -52,7 +52,7 @@
|
||||
"ky": "^1.2.4",
|
||||
"libphonenumber-js": "1.10.37",
|
||||
"node-html-parser": "6.1.5",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^8.0.5",
|
||||
"openai": "^6.9.1",
|
||||
"qs": "^6.11.2",
|
||||
"stripe": "17.1.0",
|
||||
@ -63,7 +63,7 @@
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/forge-repository": "workspace:*",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/nodemailer": "^7.0.1",
|
||||
"@types/nodemailer": "^8.0.0",
|
||||
"@types/bun": "^1.3.9",
|
||||
"dotenv-cli": "^8.0.0"
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "3.3.15",
|
||||
"openai": "4.81.0"
|
||||
},
|
||||
"scripts": {}
|
||||
|
||||
@ -1 +1,59 @@
|
||||
export { OpenAIStream, StreamingTextResponse } from "ai";
|
||||
type OpenAIChatCompletionChunk = {
|
||||
choices?: {
|
||||
delta?: {
|
||||
content?: string | null;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
export const OpenAIStream = (
|
||||
response: AsyncIterable<OpenAIChatCompletionChunk>,
|
||||
): ReadableStream<Uint8Array> =>
|
||||
new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
try {
|
||||
for await (const chunk of response) {
|
||||
const content = chunk.choices?.[0]?.delta?.content;
|
||||
if (!content) continue;
|
||||
controller.enqueue(
|
||||
textEncoder.encode(formatDataStreamPart("text", content)),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
controller.enqueue(
|
||||
textEncoder.encode(
|
||||
formatDataStreamPart("error", getErrorMessage(error)),
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export class StreamingTextResponse extends Response {
|
||||
constructor(stream: BodyInit, init?: ResponseInit) {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has("Content-Type"))
|
||||
headers.set("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
super(stream, {
|
||||
...init,
|
||||
status: init?.status ?? 200,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const formatDataStreamPart = (type: "text" | "error", value: string) =>
|
||||
`${getDataStreamPrefix(type)}:${JSON.stringify(value) ?? '""'}\n`;
|
||||
|
||||
const getDataStreamPrefix = (type: "text" | "error") =>
|
||||
type === "text" ? "0" : "3";
|
||||
|
||||
const getErrorMessage = (error: unknown) => {
|
||||
if (error instanceof Error) return error.message;
|
||||
return String(error);
|
||||
};
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"@typebot.io/prisma": "workspace:*",
|
||||
"@typebot.io/env": "workspace:*",
|
||||
"effect": "4.0.0-beta.38",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^8.0.5",
|
||||
"react-email": "^4.2.8",
|
||||
"@react-email/render": "^1.2.1",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
@ -35,6 +35,6 @@
|
||||
"@react-email/preview-server": "^4.2.8",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/nodemailer": "^7.0.1"
|
||||
"@types/nodemailer": "^8.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@ai-sdk/anthropic": "^1.2.12",
|
||||
"@ai-sdk/anthropic": "^2.0.79",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/deepseek": "^0.2.16",
|
||||
"@ai-sdk/deepseek": "^1.0.40",
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@typebot.io/forge": "workspace:*"
|
||||
},
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
"@ai-sdk/ui-utils": "^1.2.11",
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"dify-ai-provider": "^0.1.6",
|
||||
"dify-ai-provider": "^1.1.0",
|
||||
"ky": "^1.2.4",
|
||||
"@typebot.io/ai": "workspace:*"
|
||||
},
|
||||
|
||||
@ -25,11 +25,11 @@
|
||||
"@googleapis/gmail": "^14.0.1",
|
||||
"google-auth-library": "^10.1.0",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^8.0.5",
|
||||
"ky": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/nodemailer": "^7.0.1",
|
||||
"@types/nodemailer": "^8.0.0",
|
||||
"@types/react": "^19.2.14"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@ai-sdk/groq": "^1.2.9",
|
||||
"@ai-sdk/groq": "^2.0.40",
|
||||
"@types/react": "^19.2.14"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"@types/react": "^19.2.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/mistral": "^1.2.8",
|
||||
"@ai-sdk/mistral": "^2.0.33",
|
||||
"@typebot.io/ai": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"ky": "^1.2.4",
|
||||
"@openrouter/ai-sdk-provider": "^0.1.0",
|
||||
"@openrouter/ai-sdk-provider": "^1.5.4",
|
||||
"@types/react": "^19.2.14"
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.24",
|
||||
"@ai-sdk/openai": "^2.0.106",
|
||||
"@sentry/nextjs": "^10.43.0",
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@ai-sdk/ui-utils": "^1.2.11",
|
||||
|
||||
@ -18,7 +18,6 @@ export const generateVariables = createAction({
|
||||
getModel: ({ credentials, model }) =>
|
||||
createOpenAI({
|
||||
apiKey: credentials.apiKey,
|
||||
compatibility: "strict",
|
||||
})(model),
|
||||
},
|
||||
turnableInto: [
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { formatDataStreamPart, processDataStream } from "@ai-sdk/ui-utils";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { createActionHandler, createFetcherHandler } from "@typebot.io/forge";
|
||||
import type {
|
||||
@ -10,7 +11,6 @@ import { safeStringify } from "@typebot.io/lib/safeStringify";
|
||||
import { isDefined, isEmpty, isNotEmpty } from "@typebot.io/lib/utils";
|
||||
import type { SessionStore } from "@typebot.io/runtime-session-store";
|
||||
import { executeFunction } from "@typebot.io/variables/executeFunction";
|
||||
import { formatDataStreamPart, processDataStream } from "ai";
|
||||
import type { ClientOptions } from "openai";
|
||||
import OpenAI from "openai";
|
||||
import {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { formatDataStreamPart, processDataStream } from "@ai-sdk/ui-utils";
|
||||
import { createActionHandler } from "@typebot.io/forge";
|
||||
import type {
|
||||
AsyncVariableStore,
|
||||
@ -9,7 +10,6 @@ import { safeStringify } from "@typebot.io/lib/safeStringify";
|
||||
import { isDefined, isEmpty, isNotEmpty } from "@typebot.io/lib/utils";
|
||||
import type { SessionStore } from "@typebot.io/runtime-session-store";
|
||||
import { executeFunction } from "@typebot.io/variables/executeFunction";
|
||||
import { formatDataStreamPart, processDataStream } from "ai";
|
||||
import type { ClientOptions } from "openai";
|
||||
import OpenAI from "openai";
|
||||
import type { ResponseStreamEvent } from "openai/resources/responses/responses";
|
||||
|
||||
@ -24,7 +24,6 @@ export const createChatCompletionHandler = createActionHandler(
|
||||
model: createOpenAI({
|
||||
baseURL: baseUrl ?? options.baseUrl,
|
||||
apiKey,
|
||||
compatibility: "strict",
|
||||
})(modelName),
|
||||
variables,
|
||||
messages: options.messages,
|
||||
@ -71,7 +70,6 @@ export const createChatCompletionHandler = createActionHandler(
|
||||
model: createOpenAI({
|
||||
baseURL: baseUrl ?? options.baseUrl,
|
||||
apiKey,
|
||||
compatibility: "strict",
|
||||
})(modelName),
|
||||
variables,
|
||||
messages: options.messages,
|
||||
|
||||
@ -14,7 +14,6 @@ export const generateVariablesHandler = createActionHandler(generateVariables, {
|
||||
model: createOpenAI({
|
||||
apiKey: credentials.apiKey,
|
||||
baseURL: credentials.baseUrl ?? options.baseUrl,
|
||||
compatibility: "strict",
|
||||
})(options.model),
|
||||
prompt: options.prompt,
|
||||
variablesToExtract: options.variablesToExtract,
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/perplexity": "^1.1.9",
|
||||
"@ai-sdk/perplexity": "^2.0.30",
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*"
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"devDependencies": {
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
"@typebot.io/ai": "workspace:*",
|
||||
"@ai-sdk/togetherai": "^0.2.16",
|
||||
"@ai-sdk/togetherai": "^1.0.42",
|
||||
"@types/react": "^19.2.14"
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"effect": "4.0.0-beta.38",
|
||||
"ky": "^1.2.4",
|
||||
"ioredis": "^5.4.1",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^8.0.5",
|
||||
"minio": "7.1.3",
|
||||
"validator": "^13.12.0",
|
||||
"@paralleldrive/cuid2": "^2.2.1",
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.24",
|
||||
"@ai-sdk/openai": "^2.0.106",
|
||||
"ky": "^1.2.4",
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@paralleldrive/cuid2": "^2.2.1",
|
||||
@ -71,7 +71,7 @@
|
||||
"@typebot.io/blocks-core": "workspace:*",
|
||||
"@typebot.io/groups": "workspace:*",
|
||||
"@typebot.io/rich-text": "workspace:*",
|
||||
"ai": "^4.3.19",
|
||||
"ai": "^5.0.52",
|
||||
"p-limit": "^7.2.0",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.211.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.211.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
|
||||
"@opentelemetry/resources": "^2.5.0",
|
||||
"@opentelemetry/sdk-logs": "^0.211.0",
|
||||
"@opentelemetry/sdk-trace-base": "^2.5.0",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user