Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 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.1133",
"version": "0.1.1141",
"license": "Apache-2.0",
"nodeModulesDir": "auto",
"minimumDependencyAge": {
Expand Down
74 changes: 37 additions & 37 deletions docs/api-reference/veryfront/agent.md

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions docs/api-reference/veryfront/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,44 +531,44 @@ import { compactForStep, compactHistoricalUiMessageToolInputs, compactOldToolInp

| Name | Description | Source |
|------|-------------|--------|
| `DEFAULT_MESSAGE_PREP_LIMITS` | Default limits for chat history preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L35) |
| `DEFAULT_MESSAGE_PREP_LIMITS` | Default limits for chat history preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L36) |

#### Functions

| Name | Description | Source |
|------|-------------|--------|
| `compactForStep` | Compact for step. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1769) |
| `compactHistoricalUiMessageToolInputs` | Compact large historical UI-message tool inputs after matching results are available. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1491) |
| `compactOldToolInputs` | Compact large historical tool-call inputs after matching results are available. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1404) |
| `compressTurn` | Compress turn. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L227) |
| `dedupeToolHistory` | Dedupe tool history. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1793) |
| `enforceTokenBudget` | Enforce token budget. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1860) |
| `enforceTokenBudgetWithTurnCompression` | Enforce token budget with turn compression. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L302) |
| `ensureToolCallInputs` | Ensure tool call inputs helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1738) |
| `estimateMessageTokenBreakdown` | Estimate token categories for provider, UI, or runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L180) |
| `estimateOverhead` | Estimate overhead. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1732) |
| `estimateTokens` | Estimate tokens. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L98) |
| `isModelSupportedFileMediaType` | Check whether the model supports the file media type. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L658) |
| `maskOldToolOutputs` | Mask old tool outputs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1306) |
| `normalizeMessageFilePartMediaTypes` | Normalizes message file part media types. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L664) |
| `prepareProviderModelMessagesFromUiMessages` | Prepare provider model messages from UI messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1005) |
| `repairToolPairs` | Repair tool pairs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1589) |
| `rewriteUnsupportedFilePartsAsAnnotations` | Rewrite unsupported file parts as annotations. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L691) |
| `sanitizeProviderModelMessages` | Sanitize provider model messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L896) |
| `stripPendingToolParts` | Strip pending tool parts. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L792) |
| `compactForStep` | Compact for step. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1796) |
| `compactHistoricalUiMessageToolInputs` | Compact large historical UI-message tool inputs after matching results are available. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1518) |
| `compactOldToolInputs` | Compact large historical tool-call inputs after matching results are available. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1431) |
| `compressTurn` | Compress turn. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L228) |
| `dedupeToolHistory` | Dedupe tool history. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1820) |
| `enforceTokenBudget` | Enforce token budget. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1887) |
| `enforceTokenBudgetWithTurnCompression` | Enforce token budget with turn compression. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L303) |
| `ensureToolCallInputs` | Ensure tool call inputs helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1765) |
| `estimateMessageTokenBreakdown` | Estimate token categories for provider, UI, or runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L181) |
| `estimateOverhead` | Estimate overhead. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1759) |
| `estimateTokens` | Estimate tokens. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L99) |
| `isModelSupportedFileMediaType` | Check whether the model supports the file media type. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L659) |
| `maskOldToolOutputs` | Mask old tool outputs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1333) |
| `normalizeMessageFilePartMediaTypes` | Normalizes message file part media types. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L665) |
| `prepareProviderModelMessagesFromUiMessages` | Prepare provider model messages from UI messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1032) |
| `repairToolPairs` | Repair tool pairs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L1616) |
| `rewriteUnsupportedFilePartsAsAnnotations` | Rewrite unsupported file parts as annotations. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L692) |
| `sanitizeProviderModelMessages` | Sanitize provider model messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L923) |
| `stripPendingToolParts` | Strip pending tool parts. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L796) |

#### Types

| Name | Description | Source |
|------|-------------|--------|
| `HistoricalToolInputCompactionDiagnostic` | Diagnostic emitted when a completed historical tool input is compacted. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L72) |
| `HistoricalToolInputRetainedField` | Field selector retained in a historical tool-input summary. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L47) |
| `HistoricalToolInputRetentionOptions` | Options for historical tool-input compaction. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L85) |
| `HistoricalToolInputRetentionPolicy` | Policy for compacting a completed historical tool-call input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L59) |
| `HistoricalToolInputRetentionPolicyResolver` | Resolves the retention policy for a completed historical tool input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L66) |
| `MessagePrepLimits` | Tunable limits used while preparing chat history for model context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L25) |
| `MessageTokenBreakdown` | Approximate token categories for context diagnostics. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L103) |
| `PrepareProviderModelMessagesFromUiMessagesOptions` | Options accepted by prepare provider model messages from UI messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L92) |
| `HistoricalToolInputCompactionDiagnostic` | Diagnostic emitted when a completed historical tool input is compacted. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L73) |
| `HistoricalToolInputRetainedField` | Field selector retained in a historical tool-input summary. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L48) |
| `HistoricalToolInputRetentionOptions` | Options for historical tool-input compaction. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L86) |
| `HistoricalToolInputRetentionPolicy` | Policy for compacting a completed historical tool-call input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L60) |
| `HistoricalToolInputRetentionPolicyResolver` | Resolves the retention policy for a completed historical tool input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L67) |
| `MessagePrepLimits` | Tunable limits used while preparing chat history for model context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L26) |
| `MessageTokenBreakdown` | Approximate token categories for context diagnostics. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L104) |
| `PrepareProviderModelMessagesFromUiMessagesOptions` | Options accepted by prepare provider model messages from UI messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/message-prep.ts#L93) |

### `veryfront/chat/protocol`

Expand Down
8 changes: 4 additions & 4 deletions docs/guides/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const pipeline = new MiddlewarePipeline()

### Run the pipeline

Use `handle()` to run the middleware chain and then your route handler. If a middleware short-circuits (returns a `Response` - e.g. a rate-limit rejection), `handle()` returns that response and your handler never runs; otherwise your handler runs as the terminal step:
Use `handle()` to run the middleware chain and then your route handler. If a middleware short-circuits (returns a `Response`, e.g. a rate-limit rejection), `handle()` returns that response and your handler never runs; otherwise your handler runs as the terminal step:

```ts
// app/api/users/route.ts
Expand Down Expand Up @@ -122,16 +122,16 @@ curl -i http://localhost:3000/api/users

The response includes any headers added by the middleware that matched the request.

> **`handle()` vs `execute()`.** `execute()` is a lower-level variant with **no terminal handler**: it returns the short-circuiting middleware's `Response`, or a synthesized **404 Not Found** when the chain passes through. It always resolves to a `Response` (never `undefined`), so `if (await pipeline.execute(req))` is always truthy - use `execute()` only when a middleware is always expected to produce the response. For the common "middleware, then my route handler" case, prefer `handle()`.
> **`handle()` vs `execute()`.** `execute()` is a lower-level variant with **no terminal handler**: it returns the short-circuiting middleware's `Response`, or a synthesized **404 Not Found** when the chain passes through. It always resolves to a `Response` (never `undefined`), so `if (await pipeline.execute(req))` is always truthy. Use `execute()` only when a middleware is always expected to produce the response. For the common "middleware, then my route handler" case, prefer `handle()`.

### In-memory state across requests

Middleware and route handlers created at module scope - for example a `rateLimit()` store, or a module-level counter - behave differently by environment:
Middleware and route handlers created at module scope, for example a `rateLimit()` store or a module-level counter, behave differently by environment:

- **In development**, the dev server re-evaluates each route module on every request so edits hot-reload. A fresh module scope means module-level variables and default in-memory stores are re-created per request: a counter always reads back its initial value, and the default in-memory rate-limit store never accumulates across requests. To exercise threshold behavior in dev, drive the pipeline multiple times within a single request, or use an external store.
- **In production**, the compiled route module is cached per release, so module-scoped state persists across requests **within one server process and one release**. It is still **not** shared across multiple instances, and it resets on every redeploy (and under memory-pressure eviction).

For anything that must be correct across requests, instances, and deploys - rate limiting, counters, sessions - use an external store (see the `RateLimitStore` interface and the Redis example in the rate-limit reference) rather than module-scoped memory.
For anything that must be correct across requests, instances, and deploys, such as rate limiting, counters, and sessions, use an external store (see the `RateLimitStore` interface and the Redis example in the rate-limit reference) rather than module-scoped memory.

### Cleanup callbacks

Expand Down
2 changes: 0 additions & 2 deletions scripts/lint/test-typecheck-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,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",
"src/discovery/agent-scoped-capabilities.test.ts",
Expand Down
116 changes: 115 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 All @@ -20,6 +24,7 @@ import {
type HostedRuntimeSourceIdentity,
verifyHostedRuntimeSourceBinding,
} from "./runtime-source-binding.ts";
import { getHostedChatUiToolIdentity } from "./chat-request-tool-part.ts";

/** Public API contract for hosted chat request principal. */
export type HostedChatRequestPrincipal = {
Expand Down Expand Up @@ -123,6 +128,115 @@ 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 "completed";
default:
return "completed";
}
}

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

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

for (const part of message.parts) {
const uiToolIdentity = getHostedChatUiToolIdentity(part);
if (uiToolIdentity) {
knownToolNames.set(uiToolIdentity.toolCallId, uiToolIdentity.toolName);
const input = isRecord(part as unknown)
? (part as Record<string, unknown>)["input"]
: undefined;
if (isRecord(input)) {
knownToolInputs.set(uiToolIdentity.toolCallId, input);
}
partIndexByToolCallId.set(uiToolIdentity.toolCallId, parts.length);
}

if (isRecord(part) && part.type === "tool_call" && "id" in part && "name" in part) {
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;

const input = isRecord(part.input) ? part.input : {};
knownToolNames.set(toolCallId, toolName);
knownToolInputs.set(toolCallId, input);
partIndexByToolCallId.set(toolCallId, parts.length);
parts.push({
type: "tool_call",
toolCallId,
toolName,
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: knownToolInputs.get(toolCallId) ?? {},
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 +342,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