Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions docs/api-reference/veryfront/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -802,14 +802,14 @@ Input delivered to a hosted agent-service detached execution callback.
| `getConfirmedProjectContextSwitchId` | Return only the confirmed project id for legacy callers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/context.ts#L258) |
| `getConversationRun` | Return conversation run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/durable.ts#L1065) |
| `getConversationRunEventJsonByteLength` | Return conversation run event JSON byte length. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-event-normalization.ts#L27) |
| `getEmptyHostedFinalizedMessageTerminalError` | Error shape for get empty hosted finalized message terminal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L118) |
| `getEmptyHostedFinalizedMessageTerminalError` | Error shape for get empty hosted finalized message terminal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L113) |
| `getForkRuntimeAllowedToolNames` | Return fork runtime allowed tool names. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/provider-native-tool-inventory.ts#L69) |
| `getForwardedHostedModelId` | Return forwarded hosted model ID. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/runtime-request-config.ts#L86) |
| `getForwardedHostedRuntimeOverrides` | Return forwarded hosted runtime overrides. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/runtime-request-config.ts#L96) |
| `getHostedChildWrittenArtifactPath` | Return hosted child written artifact path. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-artifact-support.ts#L112) |
| `getHostedMirroredAbortErrorText` | Return hosted mirrored abort error text. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/mirrored-tool-chunk-state.ts#L163) |
| `getHostedServiceTokenFromRequest` | Request payload for get hosted service token from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L201) |
| `getHostedStreamErrorText` | Return hosted stream error text. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L113) |
| `getHostedStreamErrorText` | Return hosted stream error text. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L108) |
| `getInputRequest` | Request payload for get input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/request-protocol.ts#L254) |
| `getMaxForkRuntimeStepCount` | Return max fork runtime step count. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/fork-runtime-stream.ts#L445) |
| `getProjectAgentRuntimeAgentIdCandidates` | Return project agent runtime agent ID candidates. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/agent-runtime.ts#L223) |
Expand Down Expand Up @@ -988,7 +988,7 @@ Input delivered to a hosted agent-service detached execution callback.
| `selectProviderCompatibleTools` | Select provider compatible tools helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/provider-tool-compat.ts#L126) |
| `shouldBlockHostedChildSameTurnRetry` | Should block hosted child same turn retry helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-status.ts#L49) |
| `shouldContinueForkRuntimeStep` | Should continue fork runtime step helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/fork-runtime-step-progress.ts#L42) |
| `shouldFailEmptyHostedFinalizedMessage` | Message shape for should fail empty hosted finalized. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L132) |
| `shouldFailEmptyHostedFinalizedMessage` | Message shape for should fail empty hosted finalized. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L127) |
| `shouldInjectDefaultResearchArtifactPath` | Should inject default research artifact path helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-policy.ts#L77) |
| `shouldPruneSandboxToolsFromHostedChildRequest` | Request payload for should prune sandbox tools from hosted child. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-requested-tools.ts#L122) |
| `shouldReinforceLoadSkillContinuation` | Should reinforce load skill continuation helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L239) |
Expand Down Expand Up @@ -1617,7 +1617,7 @@ Input delivered to a hosted agent-service detached execution callback.
| `HostedServiceProjectAccessError` | Error shape for hosted service project access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L63) |
| `HostedServiceProjectAccessResult` | Result returned from hosted service project access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L70) |
| `HostedStreamPartForUiChunkMapping` | Public API contract for hosted stream part for UI chunk mapping. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/hosted-ui-chunk-mapping.ts#L31) |
| `HostedStreamTerminalError` | Error shape for hosted stream terminal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L59) |
| `HostedStreamTerminalError` | Error shape for hosted stream terminal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L54) |
| `HostedTerminalError` | Error shape for hosted terminal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-finalization.ts#L6) |
| `HostedUiChunkMappingOptions` | Options accepted by hosted UI chunk mapping. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/chat/hosted-ui-chunk-mapping.ts#L5) |
| `HumanInputField` | Public API contract for human input field. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/human-input.ts#L138) |
Expand Down
63 changes: 63 additions & 0 deletions src/agent/hosted/hosted-chat-finalization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,69 @@ describe("agent/hosted-chat-finalization", () => {
});
}

it("preserves delivered output when a trailing model step fails after a completed tool handoff", async () => {
const calls: string[] = [];
const terminalStates: HostedLifecycleTerminalState[] = [];

await finalizeHostedChatRun({
kind: "response",
responseMessage: createResponseMessage({ parts: [] }),
isAborted: false,
streamResult: createStreamResult({
text: "The environment panel is open and ready.",
finishReason: "tool-calls",
}),
lifecycleAdapter: createLifecycleAdapter({
calls,
terminalStates,
mirror: createDurableRunMirror({ calls }),
}),
mirroredToolChunkState: createMirroredToolChunkState(),
capturedMessageId: "assistant-message-1",
incompleteToolCallsPartErrorText: "Tool call did not complete",
cleanup: async () => {
calls.push("cleanup");
},
streamError: new Error("Provider request failed with status 502"),
});

assertEquals(terminalStates, [{ status: "completed" }]);
});

it("fails a watchdog timeout after a completed tool handoff", async () => {
const calls: string[] = [];
const terminalStates: HostedLifecycleTerminalState[] = [];

await finalizeHostedChatRun({
kind: "response",
responseMessage: createResponseMessage({ parts: [] }),
isAborted: false,
streamResult: createStreamResult({
text: "The environment panel is open and ready.",
finishReason: "tool-calls",
}),
lifecycleAdapter: createLifecycleAdapter({
calls,
terminalStates,
mirror: createDurableRunMirror({ calls }),
}),
mirroredToolChunkState: createMirroredToolChunkState(),
capturedMessageId: "assistant-message-1",
incompleteToolCallsPartErrorText: "Tool call did not complete",
cleanup: async () => {
calls.push("cleanup");
},
streamError: new Error("Chat stream idle timeout after 300000ms"),
});

assertEquals(terminalStates, [{
status: "failed",
terminalErrorCode: "STREAM_TIMEOUT",
terminalErrorMessage:
"This run timed out after 5 minutes before the agent finished. Try again to continue, or narrow the request.",
}]);
});

it("logs and suppresses cleanup errors after terminal dispatch", async () => {
const calls: string[] = [];
const terminalStates: HostedLifecycleTerminalState[] = [];
Expand Down
7 changes: 2 additions & 5 deletions src/agent/hosted/hosted-chat-finalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import {
toConversationHostedTerminalState,
} from "../conversation/hosted-terminal.ts";
import type { MirroredToolChunkState } from "../streaming/mirrored-tool-chunk-state.ts";
import {
hasCompletedStepSignal,
isLateProviderBodyReadError,
} from "../streaming/stream-outcome.ts";
import { hasCompletedStepSignal, isStreamTimeoutError } from "../streaming/stream-outcome.ts";
import type { HostedChatExecutionLifecycleAdapter } from "./chat-execution-lifecycle-types.ts";
import {
buildDetachedFallbackChunks,
Expand Down Expand Up @@ -246,7 +243,7 @@ function shouldFailStreamError(input: {
if (
input.hasOutput &&
hasFinalStepCompletionSignal(input.finalStep) &&
isLateProviderBodyReadError(input.streamError)
!isStreamTimeoutError(input.streamError)
) {
return false;
}
Expand Down
42 changes: 42 additions & 0 deletions src/agent/hosted/stream-finalization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,48 @@ describe("agent/hosted-stream-finalization", () => {
]);
});

it("completes hosted responses after a trailing provider failure", async () => {
const calls: string[] = [];

await finalizeHostedResponse({
isAborted: false,
streamError: new Error("Provider request failed with status 502"),
getFinalStep: async () => ({ step: 1, finishReason: "tool-calls" }),
buildState: async () => ({
persistedMessage: { id: "msg-1" },
finalizedMessage: { id: "msg-1", parts: ["text"] },
fallbackChunks: ["chunk-1"],
hasIncompleteToolParts: false,
metadata: { modelId: "anthropic/claude-haiku-4-5" },
}),
shouldFailEmptyMessage: () => false,
resolveEmptyTerminalError: ({ streamError }) => ({
code: "STREAM_ERROR",
message: streamError instanceof Error ? streamError.message : String(streamError),
}),
appendFallbackChunk: async (chunk) => {
calls.push(`append:${chunk}`);
},
flushMirror: async () => {
calls.push("flush");
},
dispatchTerminalState: async (state) => {
calls.push(`dispatch:${state.status}:${state.metadata?.modelId}`);
},
resolveTerminalState: () => ({ status: "completed" }),
cleanup: async () => {
calls.push("cleanup");
},
});

assertEquals(calls, [
"append:chunk-1",
"flush",
"dispatch:completed:anthropic/claude-haiku-4-5",
"cleanup",
]);
});

it("fails hosted responses with content when a body read fails without a final step finish reason", async () => {
const calls: string[] = [];

Expand Down
Loading