Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
883fe86
Accept durable replay shapes at provider conversion
kojiwakayama Jul 25, 2026
f25dbfd
Preserve durable replay without admitting broken tool history
kojiwakayama Jul 25, 2026
61174a5
Preserve actionable replay diagnostics
kojiwakayama Jul 25, 2026
c13d34e
Keep replay fixture names aligned with their UI shape
kojiwakayama Jul 25, 2026
ba7558e
Reject replay roles that providers cannot represent
kojiwakayama Jul 25, 2026
778b72e
Keep mixed replay tool identities consistent
kojiwakayama Jul 25, 2026
ca10867
Reject ambiguous duplicate replay tool calls
kojiwakayama Jul 25, 2026
8f4d851
Preserve normalized tool-result replay messages
kojiwakayama Jul 25, 2026
c59c639
Validate normalized replay results against their calls
kojiwakayama Jul 25, 2026
822b01c
Reject ambiguous duplicate replay results
kojiwakayama Jul 25, 2026
063ba39
Keep accepted replay history provider-faithful
kojiwakayama Jul 25, 2026
fc0a19b
Keep replay history provider-faithful across transient states
kojiwakayama Jul 25, 2026
a1a2368
Preserve provider adjacency for replay duplicates
kojiwakayama Jul 25, 2026
ea71407
Keep empty replay placeholders from severing tool results
kojiwakayama Jul 25, 2026
db245a1
Preserve replay tool inputs across assistant results
kojiwakayama Jul 25, 2026
b89ccd8
Align replay validation with provider-visible history
kojiwakayama Jul 25, 2026
4c300ea
Drop empty replay text before provider conversion
kojiwakayama Jul 25, 2026
90031d5
Preserve authoritative replay error results
kojiwakayama Jul 25, 2026
80d8c3b
Preserve fallback errors when durable results are absent
kojiwakayama Jul 25, 2026
dd708cb
Regenerate hosted chat API references
kojiwakayama Jul 25, 2026
55ab740
Format regenerated API reference docs
kojiwakayama Jul 25, 2026
62c44db
Keep generated references in canonical output form
kojiwakayama Jul 25, 2026
046fcba
Unblock replay fixes on citation persistence
kojiwakayama Jul 25, 2026
64823c1
Keep replay validation aligned with terminal tool calls
kojiwakayama Jul 25, 2026
3fa5795
Keep replay branch mergeable with current main
kojiwakayama Jul 25, 2026
c03efe2
Keep raw replay calls out of UI result detection
kojiwakayama Jul 25, 2026
53733c6
Keep chat replay branch mergeable with current main
kojiwakayama Jul 25, 2026
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 deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "veryfront",
"version": "0.1.1132",
"version": "0.1.1141",
"license": "Apache-2.0",
"nodeModulesDir": "auto",
"minimumDependencyAge": {
Expand Down
1 change: 0 additions & 1 deletion scripts/lint/test-typecheck-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"src/build/production-build/manifest.test.ts",
"src/build/renderer/services/css-bundler.test.ts",
"src/cache/registry.test.ts",
"src/chat/conversation.test.ts",
"src/chat/message-prep.test.ts",
"src/chat/upload-handler.test.ts",
"src/config/env.test.ts",
Expand Down
108 changes: 107 additions & 1 deletion src/agent/hosted/chat-request-parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import type {
ChatRequestContext,
ChatRuntimeOverrides,
ChatToolPartState,
ChatUiMessage,
ChatUiMessagePart,
ChatUiMessageRole,
DurableRootRunDescriptor,
} from "#veryfront/chat/types.ts";
import { stringifyUnknown } from "#veryfront/chat/conversation.ts";
import {
buildHostedChatRequestInputFromRuntimeAgentInvocation,
type HostedChatRequest,
Expand Down Expand Up @@ -123,6 +127,108 @@ function normalizeProjectSlug(projectSlug: string | undefined): string | undefin
return normalized || undefined;
}

function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}

function mapRawToolCallState(state: string): ChatToolPartState {
switch (state) {
case "streaming":
return "input-streaming";
case "pending":
return "pending";
case "error":
return "error";
Comment thread
Copilot marked this conversation as resolved.
Outdated
default:
return "completed";
}
}

function normalizeHostedChatRequestMessages(
messages: HostedChatRequest["messages"],
): ChatUiMessage[] {
const knownToolNames = new Map<string, string>();

return messages.map((message) => {
const parts: ChatUiMessagePart[] = [];
const partIndexByToolCallId = new Map<string, number>();

for (const part of message.parts) {
if (
part.type === "tool_call" && "toolCallId" in part && "toolName" in part &&
typeof part.toolCallId === "string" && typeof part.toolName === "string"
) {
knownToolNames.set(part.toolCallId, part.toolName);
partIndexByToolCallId.set(part.toolCallId, parts.length);
}
Comment thread
kojiwakayama marked this conversation as resolved.
Outdated

if (isRecord(part) && part.type === "tool_call" && ("id" in part || "name" in part)) {
Comment thread
kojiwakayama marked this conversation as resolved.
Outdated
const rawPart: Record<string, unknown> = part;
const rawId = rawPart["id"];
const rawName = rawPart["name"];
const toolCallId = typeof rawId === "string" ? rawId : "";
const toolName = typeof rawName === "string" ? rawName : "";
const state = typeof part.state === "string" ? part.state : "completed";
if (!toolCallId || !toolName) continue;

knownToolNames.set(toolCallId, toolName);
partIndexByToolCallId.set(toolCallId, parts.length);
parts.push({
type: "tool_call",
toolCallId,
toolName,
input: isRecord(part.input) ? part.input : {},
state: mapRawToolCallState(state),
});
continue;
}

if (isRecord(part) && part.type === "tool_result") {
const toolCallId = typeof part.tool_call_id === "string" ? part.tool_call_id : "";
const explicitToolName = typeof part.tool_name === "string" ? part.tool_name : "";
const toolName = explicitToolName || knownToolNames.get(toolCallId);
if (!toolCallId || !toolName) continue;

const resultPart: ChatUiMessagePart = {
type: "tool_call",
toolCallId,
toolName,
input: {},
state: part.is_error === true ? "output-error" : "output-available",
output: part.output,
...(part.is_error === true
? { errorText: stringifyUnknown(part.output ?? "Tool error") }
: {}),
};
Comment thread
kojiwakayama marked this conversation as resolved.

const existingIndex = partIndexByToolCallId.get(toolCallId);
if (existingIndex !== undefined) {
const existingPart = parts[existingIndex];
if (existingPart && isRecord(existingPart)) {
parts[existingIndex] = {
...existingPart,
state: resultPart.state,
output: resultPart.output,
Comment thread
kojiwakayama marked this conversation as resolved.
...(resultPart.errorText ? { errorText: resultPart.errorText } : {}),
} as ChatUiMessagePart;
}
} else {
parts.push(resultPart);
Comment thread
kojiwakayama marked this conversation as resolved.
}
continue;
}

parts.push(part as ChatUiMessagePart);
}

return {
...message,
role: message.role as ChatUiMessageRole,
parts,
};
});
}

function isBlankProjectSlug(projectSlug: string | undefined): boolean {
return projectSlug !== undefined && !normalizeProjectSlug(projectSlug);
}
Expand Down Expand Up @@ -228,7 +334,7 @@ export async function buildParsedHostedChatRequest(input: {
agentId: input.agentId,
userId: input.userId,
authToken: input.authToken,
messages: messages as ChatUiMessage[],
messages: normalizeHostedChatRequestMessages(messages),
validatedContext,
projectId,
projectSlug: verifiedProjectSlug,
Expand Down
Loading
Loading