diff --git a/autogpt_platform/backend/backend/api/features/chat/response_model.py b/autogpt_platform/backend/backend/api/features/chat/response_model.py index 53a8cf3a1f..5b78e6f14b 100644 --- a/autogpt_platform/backend/backend/api/features/chat/response_model.py +++ b/autogpt_platform/backend/backend/api/features/chat/response_model.py @@ -113,7 +113,7 @@ class StreamToolOutputAvailable(StreamBaseResponse): type: ResponseType = ResponseType.TOOL_OUTPUT_AVAILABLE toolCallId: str = Field(..., description="Tool call ID this responds to") output: str | dict[str, Any] = Field(..., description="Tool execution output") - # Additional fields for internal use (not part of AI SDK spec but useful) + # Keep these for internal backend use toolName: str | None = Field( default=None, description="Name of the tool that was executed" ) @@ -121,6 +121,15 @@ class StreamToolOutputAvailable(StreamBaseResponse): default=True, description="Whether the tool execution succeeded" ) + def to_sse(self) -> str: + """Convert to SSE format, excluding non-spec fields.""" + import json + data = { + "type": self.type.value, + "toolCallId": self.toolCallId, + "output": self.output, + } + return f"data: {json.dumps(data)}\n\n" # ========== Other ========== diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json index f22a182d20..0ab48e2b4b 100644 --- a/autogpt_platform/frontend/package.json +++ b/autogpt_platform/frontend/package.json @@ -30,6 +30,7 @@ "defaults" ], "dependencies": { + "@ai-sdk/react": "3.0.61", "@faker-js/faker": "10.0.0", "@hookform/resolvers": "5.2.2", "@next/third-parties": "15.4.6", @@ -68,6 +69,7 @@ "@vercel/analytics": "1.5.0", "@vercel/speed-insights": "1.2.0", "@xyflow/react": "12.9.2", + "ai": "6.0.59", "boring-avatars": "1.11.2", "class-variance-authority": "0.7.1", "clsx": "2.1.1", diff --git a/autogpt_platform/frontend/pnpm-lock.yaml b/autogpt_platform/frontend/pnpm-lock.yaml index db891ccf3f..a7ee54d188 100644 --- a/autogpt_platform/frontend/pnpm-lock.yaml +++ b/autogpt_platform/frontend/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: dependencies: + '@ai-sdk/react': + specifier: 3.0.61 + version: 3.0.61(react@18.3.1)(zod@3.25.76) '@faker-js/faker': specifier: 10.0.0 version: 10.0.0 @@ -125,6 +128,9 @@ importers: '@xyflow/react': specifier: 12.9.2 version: 12.9.2(@types/react@18.3.17)(immer@11.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ai: + specifier: 6.0.59 + version: 6.0.59(zod@3.25.76) boring-avatars: specifier: 1.11.2 version: 1.11.2 @@ -417,6 +423,28 @@ packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@ai-sdk/gateway@3.0.27': + resolution: {integrity: sha512-Pr+ApS9k6/jcR3kNltJNxo60OdYvnVU4DeRhzVtxUAYTXCHx4qO+qTMG9nNRn+El1acJnNRA//Su47srjXkT/w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.10': + resolution: {integrity: sha512-VeDAiCH+ZK8Xs4hb9Cw7pHlujWNL52RKe8TExOkrw6Ir1AmfajBZTb9XUdKOZO08RwQElIKA8+Ltm+Gqfo8djQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.5': + resolution: {integrity: sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w==} + engines: {node: '>=18'} + + '@ai-sdk/react@3.0.61': + resolution: {integrity: sha512-vCjZBnY2+TawFBXamSKt6elAt9n1MXMfcjSd9DSgT9peCJN27qNGVSXgaGNh/B3cUgeOktFfhB2GVmIqOjvmLQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -3691,6 +3719,10 @@ packages: vue-router: optional: true + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} + '@vercel/speed-insights@1.2.0': resolution: {integrity: sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw==} peerDependencies: @@ -3872,6 +3904,12 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ai@6.0.59: + resolution: {integrity: sha512-9SfCvcr4kVk4t8ZzIuyHpuL1hFYKsYMQfBSbBq3dipXPa+MphARvI8wHEjNaRqYl3JOsJbWxEBIMqHL0L92mUA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -4973,6 +5011,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} @@ -5697,6 +5739,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -7438,6 +7483,11 @@ packages: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true + swr@2.3.8: + resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -7498,6 +7548,10 @@ packages: third-party-capital@1.0.20: resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==} + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + timers-browserify@2.0.12: resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} engines: {node: '>=0.6.0'} @@ -8150,6 +8204,34 @@ snapshots: '@adobe/css-tools@4.4.4': {} + '@ai-sdk/gateway@3.0.27(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.10(zod@3.25.76) + '@vercel/oidc': 3.1.0 + zod: 3.25.76 + + '@ai-sdk/provider-utils@4.0.10(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 3.25.76 + + '@ai-sdk/provider@3.0.5': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@3.0.61(react@18.3.1)(zod@3.25.76)': + dependencies: + '@ai-sdk/provider-utils': 4.0.10(zod@3.25.76) + ai: 6.0.59(zod@3.25.76) + react: 18.3.1 + swr: 2.3.8(react@18.3.1) + throttleit: 2.1.0 + transitivePeerDependencies: + - zod + '@alloc/quick-lru@5.2.0': {} '@apidevtools/json-schema-ref-parser@14.0.1': @@ -11796,6 +11878,8 @@ snapshots: next: 15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 + '@vercel/oidc@3.1.0': {} + '@vercel/speed-insights@1.2.0(next@15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': optionalDependencies: next: 15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -12023,6 +12107,14 @@ snapshots: agent-base@7.1.4: optional: true + ai@6.0.59(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 3.0.27(zod@3.25.76) + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.10(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + zod: 3.25.76 + ajv-draft-04@1.0.0(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -13347,6 +13439,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.6: {} + evp_bytestokey@1.0.3: dependencies: md5.js: 1.3.5 @@ -14164,6 +14258,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: @@ -16341,6 +16437,12 @@ snapshots: transitivePeerDependencies: - encoding + swr@2.3.8(react@18.3.1): + dependencies: + dequal: 2.0.3 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + symbol-tree@3.2.4: optional: true @@ -16413,6 +16515,8 @@ snapshots: third-party-capital@1.0.20: {} + throttleit@2.1.0: {} + timers-browserify@2.0.12: dependencies: setimmediate: 1.0.5 diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx new file mode 100644 index 0000000000..e12f95d6fe --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx @@ -0,0 +1,165 @@ +'use client'; + +import { useChat } from '@ai-sdk/react'; +import { DefaultChatTransport } from 'ai'; +import { useState, useMemo } from 'react'; +import { parseAsString, useQueryState } from 'nuqs'; +import { postV2CreateSession } from '@/app/api/__generated__/endpoints/chat/chat'; + +export default function Page() { + const [sessionId, setSessionId] = useQueryState('sessionId', parseAsString); + const [isCreating, setIsCreating] = useState(false); + const [input, setInput] = useState(''); + + const transport = useMemo(() => { + if (!sessionId) return null; + return new DefaultChatTransport({ + api: `/api/chat/sessions/${sessionId}/stream`, + prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => { + const last = messages[messages.length - 1]; + return { + body: { + message: last.parts?.map((p) => (p.type === 'text' ? p.text : '')).join(''), + is_user_message: last.role === 'user', + context: null, + }, + }; + }, + }); + }, [sessionId]); + + const { messages, sendMessage, status, error } = useChat({ + transport: transport ?? undefined, + }); + + async function createSession(): Promise { + setIsCreating(true); + try { + const response = await postV2CreateSession({ + body: JSON.stringify({}), + }); + if (response.status === 200 && response.data?.id) { + return response.data.id; + } + console.error('[Copilot2] Failed to create session:', response); + return null; + } catch (error) { + console.error('[Copilot2] Error creating session:', error); + return null; + } finally { + setIsCreating(false); + } + } + + async function handleNewSession() { + const newSessionId = await createSession(); + if (newSessionId) { + setSessionId(newSessionId); + } + } + + async function handleStartChat(prompt: string) { + if (!prompt.trim() || isCreating) return; + + const newSessionId = await createSession(); + if (newSessionId) { + await setSessionId(newSessionId); + sendMessage({ text: prompt }); + setInput(''); + } + } + + // Show landing page when no session exists + if (!sessionId) { + return ( +
+
+ +
+ +
+

Start a new conversation

+
{ + e.preventDefault(); + handleStartChat(input); + }} + className="w-full max-w-md" + > + setInput(e.target.value)} + disabled={isCreating} + placeholder="Type your message to start..." + className="w-full rounded-md border border-zinc-300 px-4 py-2" + /> + +
+
+
+ ); + } + + return ( +
+ {/* Sidebar */} +
+ +
+ + {/* Chat area */} +
+
Session ID: {sessionId}
+
+ {messages.map((message) => ( +
+ {message.role === 'user' ? 'User: ' : 'AI: '} + {message.parts.map((part, index) => + part.type === 'text' ?

{part.text}

: null, + )} +
+ ))} + {error &&
Error: {error.message}
} +
+ +
{ + e.preventDefault(); + if (input.trim()) { + sendMessage({ text: input }); + setInput(''); + } + }} + > + setInput(e.target.value)} + disabled={status !== 'ready'} + placeholder="Say something..." + /> + +
+
+
+ ); +} \ No newline at end of file diff --git a/autogpt_platform/frontend/src/services/feature-flags/use-get-flag.ts b/autogpt_platform/frontend/src/services/feature-flags/use-get-flag.ts index 64b69895f3..5de78323c9 100644 --- a/autogpt_platform/frontend/src/services/feature-flags/use-get-flag.ts +++ b/autogpt_platform/frontend/src/services/feature-flags/use-get-flag.ts @@ -47,7 +47,7 @@ const mockFlags = { [Flag.AGENT_FAVORITING]: false, [Flag.MARKETPLACE_SEARCH_TERMS]: DEFAULT_SEARCH_TERMS, [Flag.ENABLE_PLATFORM_PAYMENT]: false, - [Flag.CHAT]: false, + [Flag.CHAT]: true, }; export function useGetFlag(flag: T): FlagValues[T] | null {