Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
befe901
feat: base of ai-sdk v5 migration with alpha
titouv May 22, 2025
64a2a9d
fix: suggestin message apparition
titouv May 22, 2025
9f6fa74
feat: work
titouv May 22, 2025
0043653
feat: update supabase sql schema
titouv May 22, 2025
5f42ad9
fix: openproviders
titouv May 22, 2025
e499520
feat: dev
titouv May 22, 2025
ea6c5a4
fix: temporary typescript fix for build
titouv May 22, 2025
5d657da
fix: import type for UIMessageWithMetadata
titouv May 22, 2025
2859b58
feat: update MessageAssistant and MessageUser
titouv May 22, 2025
5fac748
refactor: remove commented-out code and handle empty text parts in Me…
titouv May 22, 2025
951548e
feat: sanitize user input in logUserMessage and update message handli…
titouv May 22, 2025
bab653d
refactor: chat session management
titouv May 22, 2025
e792616
fix: suggestion submit message apparition
titouv May 22, 2025
91f06d3
update for new beta version
titouv Jun 6, 2025
78b3e71
Merge remote-tracking branch 'upstream/main' into ai-sdk-v5
titouv Jun 7, 2025
a256bcb
improvement and updates for alpha v15
titouv Jun 18, 2025
8129335
Merge remote-tracking branch 'upstream/main' into ai-sdk-v5
titouv Jun 18, 2025
751ad64
dev
titouv Jun 19, 2025
ca1a260
Merge remote-tracking branch 'upstream/main' into ai-sdk-v5
titouv Jun 19, 2025
4a7a83e
update package and fixes
titouv Jun 26, 2025
70d245f
Merge remote-tracking branch 'upstream/main' into ai-sdk-v5
titouv Jun 26, 2025
71a4576
adaptation from merge
titouv Jun 26, 2025
4a6d217
fix submit
titouv Jun 26, 2025
5cacff2
update to latest beta version of packages
titouv Jun 28, 2025
4fcad3b
fix todo preview
titouv Jun 28, 2025
67f1d41
Merge remote-tracking branch 'upstream/main' into ai-sdk-v5
titouv Jun 28, 2025
973fb3e
Merge remote-tracking branch 'upstream/main' into HEAD
titouv Jul 31, 2025
bdc3aaf
fix missing removed files
titouv Jul 31, 2025
684335c
fix type error
titouv Jul 31, 2025
0880947
remove unwanted changes from the PR
titouv Jul 31, 2025
8482f28
fix first streaming issue
titouv Aug 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,7 @@ CREATE TABLE messages (
id SERIAL PRIMARY KEY, -- Using SERIAL for auto-incrementing integer ID
chat_id UUID NOT NULL,
user_id UUID,
content TEXT,
role TEXT NOT NULL CHECK (role IN ('system', 'user', 'assistant', 'data')), -- Added CHECK constraint
experimental_attachments JSONB, -- Storing Attachment[] as JSONB
role TEXT NOT NULL CHECK (role IN ('system', 'user', 'assistant')), -- Added CHECK constraint
parts JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE,
Expand Down
26 changes: 18 additions & 8 deletions app/api/chat/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { saveFinalAssistantMessage } from "@/app/api/chat/db"
import { UIMessageFull } from "@/app/components/chat/use-chat-core"
import type {
ChatApiParams,
LogUserMessageParams,
Expand All @@ -9,6 +10,7 @@ import { FREE_MODELS_IDS, NON_AUTH_ALLOWED_MODELS } from "@/lib/config"
import { getProviderForModel } from "@/lib/openproviders/provider-map"
import { sanitizeUserInput } from "@/lib/sanitize"
import { validateUserIdentity } from "@/lib/server/api"
// import type { SupabaseClient } from "@/lib/supabase/server"
import { checkUsageByModel, incrementUsage } from "@/lib/usage"
import { getUserKey, type ProviderWithoutOllama } from "@/lib/user-keys"

Expand Down Expand Up @@ -53,6 +55,16 @@ export async function validateAndTrackUsage({
return supabase
}

function sanitizeUserMessagePart(part: UIMessageFull["parts"][number]) {
if (part.type === "text") {
return {
...part,
text: sanitizeUserInput(part.text),
}
}
return part
}

export async function incrementMessageCount({
supabase,
userId,
Expand All @@ -74,20 +86,18 @@ export async function logUserMessage({
supabase,
userId,
chatId,
content,
attachments,
model,
isAuthenticated,
parts,
message_group_id,
}: LogUserMessageParams): Promise<void> {
if (!supabase) return

const { error } = await supabase.from("messages").insert({
chat_id: chatId,
role: "user",
content: sanitizeUserInput(content),
experimental_attachments: attachments,
user_id: userId,
parts: parts.map(sanitizeUserMessagePart),
message_group_id,
})

Expand All @@ -99,7 +109,7 @@ export async function logUserMessage({
export async function storeAssistantMessage({
supabase,
chatId,
messages,
parts,
message_group_id,
model,
}: StoreAssistantMessageParams): Promise<void> {
Expand All @@ -108,9 +118,9 @@ export async function storeAssistantMessage({
await saveFinalAssistantMessage(
supabase,
chatId,
messages,
message_group_id,
model
parts,
model,
message_group_id
)
} catch (err) {
console.error("Failed to save assistant messages:", err)
Expand Down
69 changes: 4 additions & 65 deletions app/api/chat/db.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UIMessageFull } from "@/app/components/chat/use-chat-core"
import type { ContentPart, Message } from "@/app/types/api.types"
import type { Database, Json } from "@/app/types/database.types"
import type { SupabaseClient } from "@supabase/supabase-js"
Expand All @@ -7,78 +8,16 @@ const DEFAULT_STEP = 0
export async function saveFinalAssistantMessage(
supabase: SupabaseClient<Database>,
chatId: string,
messages: Message[],
rawParts: UIMessageFull["parts"],
message_group_id?: string,
model?: string
) {
const parts: ContentPart[] = []
const toolMap = new Map<string, ContentPart>()
const textParts: string[] = []

for (const msg of messages) {
if (msg.role === "assistant" && Array.isArray(msg.content)) {
for (const part of msg.content) {
if (part.type === "text") {
textParts.push(part.text || "")
parts.push(part)
} else if (part.type === "tool-invocation" && part.toolInvocation) {
const { toolCallId, state } = part.toolInvocation
if (!toolCallId) continue

const existing = toolMap.get(toolCallId)
if (state === "result" || !existing) {
toolMap.set(toolCallId, {
...part,
toolInvocation: {
...part.toolInvocation,
args: part.toolInvocation?.args || {},
},
})
}
} else if (part.type === "reasoning") {
parts.push({
type: "reasoning",
reasoning: part.text || "",
details: [
{
type: "text",
text: part.text || "",
},
],
})
} else if (part.type === "step-start") {
parts.push(part)
}
}
} else if (msg.role === "tool" && Array.isArray(msg.content)) {
for (const part of msg.content) {
if (part.type === "tool-result") {
const toolCallId = part.toolCallId || ""
toolMap.set(toolCallId, {
type: "tool-invocation",
toolInvocation: {
state: "result",
step: DEFAULT_STEP,
toolCallId,
toolName: part.toolName || "",
result: part.result,
},
})
}
}
}
}

// Merge tool parts at the end
parts.push(...toolMap.values())

const finalPlainText = textParts.join("\n\n")
const parts = rawParts

const { error } = await supabase.from("messages").insert({
chat_id: chatId,
role: "assistant",
content: finalPlainText || "",
parts: parts as unknown as Json,
parts: parts,
message_group_id,
model,
})
Expand Down
51 changes: 33 additions & 18 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import {
MessageMetadata,
UIMessageFull,
} from "@/app/components/chat/use-chat-core"
import { SYSTEM_PROMPT_DEFAULT } from "@/lib/config"
import { getAllModels } from "@/lib/models"
import { getProviderForModel } from "@/lib/openproviders/provider-map"
import type { ProviderWithoutOllama } from "@/lib/user-keys"
import { Attachment } from "@ai-sdk/ui-utils"
import { Message as MessageAISDK, streamText, ToolSet } from "ai"
import { convertToModelMessages, stepCountIs, streamText, ToolSet } from "ai"
import {
incrementMessageCount,
logUserMessage,
storeAssistantMessage,
validateAndTrackUsage,
} from "./api"
import { createErrorResponse, extractErrorMessage } from "./utils"

// import { createErrorResponse } from "./utils"

// import { createErrorResponse, extractErrorMessage } from "./utils"

export const maxDuration = 60

type ChatRequest = {
messages: MessageAISDK[]
messages: UIMessageFull[]
chatId: string
userId: string
model: string
Expand All @@ -38,6 +44,8 @@ export async function POST(req: Request) {
message_group_id,
} = (await req.json()) as ChatRequest

console.log("messages start", messages)

if (!messages || !chatId || !userId) {
return new Response(
JSON.stringify({ error: "Error, missing information" }),
Expand All @@ -63,10 +71,9 @@ export async function POST(req: Request) {
supabase,
userId,
chatId,
content: userMessage.content,
attachments: userMessage.experimental_attachments as Attachment[],
model,
isAuthenticated,
parts: userMessage.parts,
message_group_id,
})
}
Expand All @@ -92,35 +99,41 @@ export async function POST(req: Request) {
const result = streamText({
model: modelConfig.apiSdk(apiKey, { enableSearch }),
system: effectiveSystemPrompt,
messages: messages,
messages: convertToModelMessages(messages),
tools: {} as ToolSet,
maxSteps: 10,
onError: (err: unknown) => {
stopWhen: (ops) => stepCountIs(10)(ops),
onError: (err: any) => {
console.error("Streaming error occurred:", err)
// Don't set streamError anymore - let the AI SDK handle it through the stream
},

onFinish: async ({ response }) => {
onFinish: async ({ content }) => {
const parts: UIMessageFull["parts"] =
content as unknown as UIMessageFull["parts"] // TODO FIX
if (supabase) {
await storeAssistantMessage({
supabase,
chatId,
messages:
response.messages as unknown as import("@/app/types/api.types").Message[],
parts,
message_group_id,
model,
})
}
},
})

return result.toDataStreamResponse({
return result.toUIMessageStreamResponse({
sendReasoning: true,
sendSources: true,
getErrorMessage: (error: unknown) => {
console.error("Error forwarded to client:", error)
return extractErrorMessage(error)
messageMetadata: (): MessageMetadata | undefined => {
return {
createdAt: new Date().toISOString(),
}
},
sendSources: true,
// getErrorMessage: (error: unknown) => {
// console.error("Error forwarded to client:", error)
// return extractErrorMessage(error)
// },
})
} catch (err: unknown) {
console.error("Error in /api/chat:", err)
Expand All @@ -130,6 +143,8 @@ export async function POST(req: Request) {
statusCode?: number
}

return createErrorResponse(error)
throw err

// return createErrorResponse(error)
}
}
Loading