Files
copilotkit__copilotkit/packages/shared/src/finalize-events.ts
Tyler Slaton 96885b5959 refactor: consolidate V1/V2 packages into flat @copilotkit/* structure
Flatten all packages from packages/v1/* and packages/v2/* into packages/* —
every package now lives directly under the @copilotkit/ scope with no v1/v2
subdirectories.

- Move all v1 packages (react-core, react-ui, runtime, shared, etc.) from
  packages/v1/* to packages/*
- Absorb v2 react code into packages/react-core/src/v2/ (exported via /v2 subpath)
- Absorb v2 agent code into packages/runtime/src/agent/ (exported via /v2 subpath)
- Move v2 packages (core, angular, demo-agents, etc.) to packages/*
- Replace all @copilotkitnext/* imports with @copilotkit/* equivalents
- Keep @copilotkitnext/angular as the sole exception (angular remains on next)
- Update CI workflows, renovate config, release scripts for flat structure
- No public API surface changes — all exports fields are preserved

Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>
2026-03-28 16:45:10 -07:00

155 lines
4.2 KiB
TypeScript

import { BaseEvent, EventType, RunErrorEvent } from "@ag-ui/client";
import { randomUUID } from "./utils";
interface FinalizeRunOptions {
stopRequested?: boolean;
interruptionMessage?: string;
}
const defaultStopMessage = "Run stopped by user";
const defaultAbruptEndMessage = "Run ended without emitting a terminal event";
export function finalizeRunEvents(
events: BaseEvent[],
options: FinalizeRunOptions = {},
): BaseEvent[] {
const { stopRequested = false, interruptionMessage } = options;
const resolvedStopMessage = interruptionMessage ?? defaultStopMessage;
const resolvedAbruptMessage =
interruptionMessage && interruptionMessage !== defaultStopMessage
? interruptionMessage
: defaultAbruptEndMessage;
const appended: BaseEvent[] = [];
const openMessageIds = new Set<string>();
const openToolCalls = new Map<
string,
{
hasEnd: boolean;
hasResult: boolean;
}
>();
for (const event of events) {
switch (event.type) {
case EventType.TEXT_MESSAGE_START: {
const messageId = (event as { messageId?: string }).messageId;
if (typeof messageId === "string") {
openMessageIds.add(messageId);
}
break;
}
case EventType.TEXT_MESSAGE_END: {
const messageId = (event as { messageId?: string }).messageId;
if (typeof messageId === "string") {
openMessageIds.delete(messageId);
}
break;
}
case EventType.TOOL_CALL_START: {
const toolCallId = (event as { toolCallId?: string }).toolCallId;
if (typeof toolCallId === "string") {
openToolCalls.set(toolCallId, {
hasEnd: false,
hasResult: false,
});
}
break;
}
case EventType.TOOL_CALL_END: {
const toolCallId = (event as { toolCallId?: string }).toolCallId;
const info = toolCallId ? openToolCalls.get(toolCallId) : undefined;
if (info) {
info.hasEnd = true;
}
break;
}
case EventType.TOOL_CALL_RESULT: {
const toolCallId = (event as { toolCallId?: string }).toolCallId;
const info = toolCallId ? openToolCalls.get(toolCallId) : undefined;
if (info) {
info.hasResult = true;
}
break;
}
default:
break;
}
}
const hasRunFinished = events.some(
(event) => event.type === EventType.RUN_FINISHED,
);
const hasRunError = events.some(
(event) => event.type === EventType.RUN_ERROR,
);
const hasTerminalEvent = hasRunFinished || hasRunError;
const terminalEventMissing = !hasTerminalEvent;
for (const messageId of openMessageIds) {
const endEvent = {
type: EventType.TEXT_MESSAGE_END,
messageId,
} as BaseEvent;
events.push(endEvent);
appended.push(endEvent);
}
for (const [toolCallId, info] of openToolCalls) {
if (!info.hasEnd) {
const endEvent = {
type: EventType.TOOL_CALL_END,
toolCallId,
} as BaseEvent;
events.push(endEvent);
appended.push(endEvent);
}
if (terminalEventMissing && !info.hasResult) {
const resultEvent = {
type: EventType.TOOL_CALL_RESULT,
toolCallId,
messageId: `${toolCallId ?? randomUUID()}-result`,
role: "tool",
content: JSON.stringify(
stopRequested
? {
status: "stopped",
reason: "stop_requested",
message: resolvedStopMessage,
}
: {
status: "error",
reason: "missing_terminal_event",
message: resolvedAbruptMessage,
},
),
} as BaseEvent;
events.push(resultEvent);
appended.push(resultEvent);
}
}
if (terminalEventMissing) {
if (stopRequested) {
const finishedEvent = {
type: EventType.RUN_FINISHED,
} as BaseEvent;
events.push(finishedEvent);
appended.push(finishedEvent);
} else {
const errorEvent: RunErrorEvent = {
type: EventType.RUN_ERROR,
message: resolvedAbruptMessage,
code: "INCOMPLETE_STREAM",
};
events.push(errorEvent);
appended.push(errorEvent);
}
}
return appended;
}