Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fc003c5
WIP agentic tool call streaming
timothycarambat Aug 11, 2025
6ac95b3
WIP rest of providers EXCLUDES Bedrock and GenericOpenAI
timothycarambat Aug 12, 2025
40dcf8b
patch untooled complete/streaming to use chatCallback provider from p…
timothycarambat Aug 12, 2025
79f2e09
modify ollama to function with its own overrides
timothycarambat Aug 13, 2025
c710eee
dev build
timothycarambat Aug 13, 2025
c7b427f
Merge branch 'master' into agentic-streaming
timothycarambat Aug 15, 2025
9a99933
fix message sanization for anthropic agent streaming
shatfield4 Aug 30, 2025
155bb7a
Merge branch 'master' into agentic-streaming
shatfield4 Aug 30, 2025
2d0768b
wip fix anthropic agentic streaming sanitization
shatfield4 Sep 5, 2025
6256998
patch gemini, webgenui, generic aibitat providers + disable providers…
shatfield4 Sep 6, 2025
3a16aac
refactor anthropic aibitat provider for empty message and tool call f…
shatfield4 Sep 9, 2025
72e0983
Merge branch 'master' into agentic-streaming
timothycarambat Sep 29, 2025
e020b79
Add frontend missing prop check
timothycarambat Sep 30, 2025
b289da1
DPAIS, remove temp from call, support streaming'
timothycarambat Sep 30, 2025
3c8b9ef
remove 0 temp to remove possibility of bad temp error/500s/400s
timothycarambat Sep 30, 2025
fe959a7
Patch condition where model is non-streamable and no tools are presen…
timothycarambat Sep 30, 2025
7c7743c
Allow generic Openai to be streamable since using untooled it should …
timothycarambat Sep 30, 2025
8cac95c
rename function and more gemini-specific function to gemini provider
timothycarambat Sep 30, 2025
9923bca
add comments for readability
timothycarambat Sep 30, 2025
b5621d9
Merge branch 'master' into agentic-streaming
timothycarambat Sep 30, 2025
7121f98
migrate CometAPI, but disable as we cannot test
timothycarambat Sep 30, 2025
6632729
Merge branch 'master' into agentic-streaming
timothycarambat Sep 30, 2025
e17178d
Merge branch 'master' into agentic-streaming
timothycarambat Oct 1, 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
2 changes: 1 addition & 1 deletion .github/workflows/dev-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ concurrency:

on:
push:
branches: ['3999-chromium-flags'] # put your current branch to create a build. Core team only.
branches: ['agentic-streaming'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ const RenderChatContent = memo(
);
let thoughtChain = null;
let msgToRender = message;
if (!message) return null;

// If the message is a perfect thought chain, we can render it directly
// Complete == open and close tags match perfectly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { CaretDown } from "@phosphor-icons/react";
import AgentAnimation from "@/media/animations/agent-animation.webm";
import AgentStatic from "@/media/animations/agent-static.png";

export default function StatusResponse({
messages = [],
isThinking = false,
showCheckmark = false,
}) {
export default function StatusResponse({ messages = [], isThinking = false }) {
const [isExpanded, setIsExpanded] = useState(false);
const currentThought = messages[messages.length - 1];
const previousThoughts = messages.slice(0, -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,6 @@ export default function ChatHistory({
key={`status-group-${index}`}
messages={item}
isThinking={!hasSubsequentMessages && lastMessageInfo.isAnimating}
showCheckmark={
hasSubsequentMessages ||
(!lastMessageInfo.isAnimating && !lastMessageInfo.isStatusResponse)
}
/>
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
const socket = new WebSocket(
`${websocketURI()}/api/agent-invocation/${socketId}`
);
socket.supportsAgentStreaming = false;

window.addEventListener(ABORT_STREAM_EVENT, () => {
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END));
Expand All @@ -243,7 +244,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
socket.addEventListener("message", (event) => {
setLoadingResponse(true);
try {
handleSocketResponse(event, setChatHistory);
handleSocketResponse(socket, event, setChatHistory);
} catch (e) {
console.error("Failed to parse data");
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END));
Expand Down
90 changes: 88 additions & 2 deletions frontend/src/utils/chat/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const handledEvents = [
"awaitingFeedback",
"wssFailure",
"rechartVisualize",
// Streaming events
"reportStreamEvent",
];

export function websocketURI() {
Expand All @@ -20,13 +22,13 @@ export function websocketURI() {
return `${wsProtocol}//${new URL(import.meta.env.VITE_API_BASE).host}`;
}

export default function handleSocketResponse(event, setChatHistory) {
export default function handleSocketResponse(socket, event, setChatHistory) {
const data = safeJsonParse(event.data, null);
if (data === null) return;

// No message type is defined then this is a generic message
// that we need to print to the user as a system response
if (!data.hasOwnProperty("type")) {
if (!data.hasOwnProperty("type") && !socket.supportsAgentStreaming) {
return setChatHistory((prev) => {
return [
...prev.filter((msg) => !!msg.content),
Expand All @@ -46,6 +48,90 @@ export default function handleSocketResponse(event, setChatHistory) {

if (!handledEvents.includes(data.type) || !data.content) return;

if (data.type === "reportStreamEvent") {
// Enable agent streaming for the next message so we can handle streaming or non-streaming responses
// If we get this message we know the provider supports agentic streaming
socket.supportsAgentStreaming = true;

return setChatHistory((prev) => {
if (data.content.type === "removeStatusResponse")
return [...prev.filter((msg) => msg.uuid !== data.content.uuid)];

const knownMessage = data.content.uuid
? prev.find((msg) => msg.uuid === data.content.uuid)
: null;
if (!knownMessage) {
if (data.content.type === "fullTextResponse") {
return [
...prev.filter((msg) => !!msg.content),
{
uuid: data.content.uuid,
type: "textResponse",
content: data.content.content,
role: "assistant",
sources: [],
closed: true,
error: null,
animate: false,
pending: false,
},
];
}

return [
...prev.filter((msg) => !!msg.content),
{
uuid: data.content.uuid,
type: "statusResponse",
content: data.content.content,
role: "assistant",
sources: [],
closed: true,
error: null,
animate: false,
pending: false,
},
];
} else {
const { type, content, uuid } = data.content;
// For tool call invocations, we need to update the existing message entirely since it is accumulated
// and we dont know if the function will have arguments or not while streaming - so replace the existing message entirely
if (type === "toolCallInvocation") {
const knownMessage = prev.find((msg) => msg.uuid === uuid);
if (!knownMessage)
return [...prev, { uuid, type: "toolCallInvocation", content }]; // If the message is not known, add it to the end of the list
return [
...prev.filter((msg) => msg.uuid !== uuid),
{ ...knownMessage, content },
]; // If the message is known, replace it with the new content
}

if (type === "textResponseChunk") {
return prev
.map((msg) =>
msg.uuid === uuid
? {
...msg,
type: "textResponse",
content: msg.content + content,
}
: msg?.content
? msg
: null
)
.filter((msg) => !!msg);
}

// Generic text response - will be put in the agent thought bubble
return prev.map((msg) =>
msg.uuid === data.content.uuid
? { ...msg, content: msg.content + data.content.content }
: msg
);
}
});
}

if (data.type === "fileDownload") {
saveAs(data.content.b64Content, data.content.filename ?? "unknown.txt");
return;
Expand Down
Loading