Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -586,7 +586,7 @@ Input delivered to a hosted agent-service detached execution callback.
| `buildRootOwnedChildRunResultHint` | Builds root owned child run result hint. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/child-run/result-summary.ts#L348) |
| `buildRootOwnedChildRunResultText` | Builds root owned child run result text. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/child-run/result-summary.ts#L333) |
| `buildRootOwnedDelegatedFindingsInstruction` | Builds root owned delegated findings instruction. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L34) |
| `buildRuntimeAgentControlPlaneStreamRequestFromInvocation` | Builds runtime agent control plane stream request from invocation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L452) |
| `buildRuntimeAgentControlPlaneStreamRequestFromInvocation` | Builds runtime agent control plane stream request from invocation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L453) |
| `buildRuntimeAvailableSkillsPromptBlock` | Builds a bounded, injection-safe runtime available-skills prompt. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-prompt.ts#L457) |
| `buildRuntimeLoadedSkillResponse` | Build a bounded loaded-skill response and fail closed on invalid metadata. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L1097) |
| `buildRuntimeSkillDefinition` | Build a bounded, immutable runtime skill definition. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L913) |
Expand Down Expand Up @@ -884,10 +884,10 @@ Input delivered to a hosted agent-service detached execution callback.
| `parseHostedAgentServiceConfig` | Configuration used by parse hosted agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/config.ts#L167) |
| `parseHostedChatRequestFromRequest` | Request payload for parse hosted chat request from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L593) |
| `parseRuntimeAgentMarkdownDefinition` | Definition for parse runtime agent markdown. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-definition.ts#L192) |
| `parseRuntimeAgentRunInvocation` | Parses runtime agent run invocation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L479) |
| `parseRuntimeAgentRunInvocation` | Parses runtime agent run invocation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L481) |
| `parseRuntimeAgentRunInvocationAgentServiceChatRequestFromRequest` | Request payload for parse runtime agent run invocation hosted chat request from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L632) |
| `parseRuntimeAgentRunInvocationHostedChatRequestFromRequest` | Request payload for parse runtime agent run invocation hosted chat request from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L632) |
| `parseRuntimeAgentRunInvocationOrError` | Error shape for parse runtime agent run invocation or. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L486) |
| `parseRuntimeAgentRunInvocationOrError` | Error shape for parse runtime agent run invocation or. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L488) |
| `parseRuntimeSkillDocument` | Parses a bounded runtime skill document and fails closed on invalid input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L561) |
| `parseRuntimeSkillMetadata` | Parses bounded runtime skill metadata and fails closed on invalid input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L569) |
| `parseToolInputObject` | Parses tool input object. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/tool-input.ts#L135) |
Expand Down Expand Up @@ -1020,7 +1020,7 @@ Input delivered to a hosted agent-service detached execution callback.

| Name | Description | Source |
| ------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `AgentRuntime` | Implement agent runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/index.ts#L1164) |
| `AgentRuntime` | Implement agent runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/index.ts#L1187) |
| `AgentRuntimeMessageConversionError` | Error shape for agent runtime message conversion. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/message-adapter.ts#L138) |
| `AgentServiceAuthError` | Error shape for hosted service auth. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L14) |
| `AppendConversationRunEventsError` | Error shape for append conversation run events. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/durable-append-errors.ts#L4) |
Expand Down
1 change: 1 addition & 0 deletions src/agent/runtime/agent-invocation-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ describe("agent/runtime-agent-invocation-contract", () => {
agentId: "builder",
threadId: conversationId,
runId: "run_child_1",
messageId,
parentRunId: "run_root_1",
messages: parsed.messages,
tools: parsed.tools,
Expand Down
2 changes: 2 additions & 0 deletions src/agent/runtime/agent-invocation-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ export type RuntimeAgentControlPlaneStreamRequest = {
agentId: RuntimeAgentRunContext["agentId"];
threadId: RuntimeAgentRunContext["conversationId"];
runId: RuntimeAgentRunContext["runId"];
messageId?: RuntimeAgentRunContext["messageId"];
taskId?: string;
parentRunId?: Exclude<RuntimeAgentRunContext["parentRunId"], null | undefined>;
messages: RuntimeAgentRunInvocation["messages"];
Expand All @@ -456,6 +457,7 @@ export function buildRuntimeAgentControlPlaneStreamRequestFromInvocation(
agentId: input.run.agentId,
threadId: input.run.conversationId,
runId: input.run.runId,
messageId: input.run.messageId,
...(input.taskId ? { taskId: input.taskId } : {}),
...(input.run.parentRunId ? { parentRunId: input.run.parentRunId } : {}),
messages: input.messages,
Expand Down
25 changes: 24 additions & 1 deletion src/agent/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ import {
getRuntimeProviderReplayCheckpointMessageId,
getRuntimeProviderReplayCheckpointPersister,
getRuntimeProviderReplayCheckpoints,
getRuntimeProviderReplayCheckpointTurnComplete,
getRuntimeProviderReplayCheckpointTurnFailed,
getRuntimeProviderTools,
getRuntimeSourceIntegrationPolicy,
getRuntimeToolExposureCheckpoint,
Expand Down Expand Up @@ -690,6 +692,8 @@ async function persistToolExposureCheckpointBeforeContinuation(input: {
type RuntimeProviderReplayCheckpointEmission = {
state: ProviderReplayCheckpointEmissionState | undefined;
persist: ((checkpoint: ProviderReplayCheckpoint) => void | Promise<void>) | undefined;
complete: (() => void | Promise<void>) | undefined;
fail: (() => void | Promise<void>) | undefined;
required: boolean;
};

Expand All @@ -707,27 +711,45 @@ function resolveRuntimeProviderReplayCheckpointEmission(
? createProviderReplayCheckpointEmissionState({ messageId, existingCheckpoint })
: undefined,
persist: getRuntimeProviderReplayCheckpointPersister(config),
complete: getRuntimeProviderReplayCheckpointTurnComplete(config),
fail: getRuntimeProviderReplayCheckpointTurnFailed(config),
required: isRuntimeProviderReplayCheckpointPersistenceRequired(config),
};
}

async function persistProviderReplayCheckpointAfterTurn(input: {
emission: RuntimeProviderReplayCheckpointEmission;
providerMetadata: Record<string, unknown> | undefined;
}): Promise<void> {
try {
await persistProviderReplayCheckpointAfterTurnUnsafe(input);
} catch (error) {
await input.emission.fail?.();
throw error;
}
}

async function persistProviderReplayCheckpointAfterTurnUnsafe(input: {
emission: RuntimeProviderReplayCheckpointEmission;
providerMetadata: Record<string, unknown> | undefined;
}): Promise<void> {
if (!input.emission.state) {
if (input.emission.required) {
throw DURABLE_RUN_EVENT_PERSISTENCE_FAILED.create({
detail: "provider replay checkpoint message identity is required",
});
}
await input.emission.complete?.();
return;
}
const checkpoint = captureProviderReplayCheckpoint(
input.emission.state,
input.providerMetadata,
);
if (!checkpoint) return;
if (!checkpoint) {
await input.emission.complete?.();
return;
}
if (!input.emission.persist) {
if (input.emission.required) {
throw DURABLE_RUN_EVENT_PERSISTENCE_FAILED.create({
Expand All @@ -737,6 +759,7 @@ async function persistProviderReplayCheckpointAfterTurn(input: {
return;
}
await input.emission.persist(checkpoint);
await input.emission.complete?.();
}

function isToolVisibleForStep(toolName: string, plan: ToolExposurePlan): boolean {
Expand Down
73 changes: 73 additions & 0 deletions src/agent/runtime/provider-replay-emission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ describe("provider replay checkpoint emission", () => {
checkpoints.push(checkpoint);
operations.push("persist:done");
},
__vfProviderReplayCheckpointTurnComplete: () => {
operations.push("turn:complete");
},
} as AgentConfig & RuntimeToolFilterConfig;

const assistant = agent(config);
Expand All @@ -185,6 +188,7 @@ describe("provider replay checkpoint emission", () => {
}

assertEquals(operations.indexOf("persist:done") < operations.indexOf("model:2"), true);
assertEquals(operations.indexOf("turn:complete") < operations.indexOf("model:2"), true);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
assertEquals(checkpoints.length, 2);
assertEquals(checkpoints[0]?.providerMessageBlockCounts, [2]);
assertEquals(checkpoints[1]?.providerMessageBlockCounts, [2, 1]);
Expand Down Expand Up @@ -230,6 +234,75 @@ describe("provider replay checkpoint emission", () => {
assertEquals(model.callCount, 1);
});

it("closes the provider turn when no replay checkpoint is required", async () => {
let completedTurns = 0;
const model = scriptedModel([{
text: "done",
providerMetadata: metadata([{ type: "text", text: "done" }]),
}], {
modelId: "anthropic/provider-replay-turn-boundary",
provider: "anthropic",
only: "generate",
});
const config = {
id: "provider-replay-turn-boundary",
model: "anthropic/provider-replay-turn-boundary",
system: "Answer.",
skills: false,
maxSteps: 1,
resolveModelTransport: () => ({ model }),
__vfProviderReplayCheckpointTurnComplete: () => {
completedTurns++;
},
__vfPersistProviderReplayCheckpoint: () => {
throw new Error("checkpoint persister must stay unused");
},
} as AgentConfig & RuntimeToolFilterConfig;

await agent(config).generate({ input: "Answer" });

assertEquals(completedTurns, 1);
assertEquals(model.callCount, 1);
});

it("stops execution when the checkpoint persister rejects", async () => {
let failedTurns = 0;
const model = scriptedModel([{
text: "done",
providerMetadata: metadata([{
type: "thinking",
thinking: "",
signature: SIGNATURE,
}, { type: "text", text: "done" }]),
}], {
modelId: "anthropic/rejected-provider-replay-persister",
provider: "anthropic",
only: "generate",
});
const config = {
id: "rejected-provider-replay-persister",
model: "anthropic/rejected-provider-replay-persister",
system: "Answer.",
skills: false,
maxSteps: 1,
resolveModelTransport: () => ({ model }),
__vfProviderReplayCheckpointMessageId: MESSAGE_ID,
__vfPersistProviderReplayCheckpoint: () =>
Promise.reject(new Error("checkpoint sink rejected")),
__vfProviderReplayCheckpointTurnFailed: () => {
failedTurns++;
},
} as AgentConfig & RuntimeToolFilterConfig;

await assertRejects(
() => agent(config).generate({ input: "Answer" }),
Error,
"checkpoint sink rejected",
);
assertEquals(model.callCount, 1);
assertEquals(failedTurns, 1);
});

it("required replay checkpoint persistence rejects a missing durable message identity", async () => {
const model = scriptedModel([{ text: "done" }], {
modelId: "anthropic/missing-provider-replay-message-id",
Expand Down
18 changes: 18 additions & 0 deletions src/agent/runtime/runtime-tool-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export type RuntimeToolFilterConfig = AgentConfig & {
__vfPersistProviderReplayCheckpoint?: (
checkpoint: ProviderReplayCheckpoint,
) => void | Promise<void>;
__vfProviderReplayCheckpointTurnComplete?: () => void | Promise<void>;
__vfProviderReplayCheckpointTurnFailed?: () => void | Promise<void>;
__vfProviderReplayCheckpointPersistenceRequired?: boolean;
__vfPersistToolExposureCheckpoint?: (
checkpoint: ToolExposureCheckpoint,
Expand Down Expand Up @@ -147,6 +149,22 @@ export function getRuntimeProviderReplayCheckpointPersister(
return typeof value === "function" ? value : undefined;
}

/** Return the trusted hook that closes one provider response boundary. */
export function getRuntimeProviderReplayCheckpointTurnComplete(
config: AgentConfig,
): (() => void | Promise<void>) | undefined {
const value = (config as RuntimeToolFilterConfig).__vfProviderReplayCheckpointTurnComplete;
return typeof value === "function" ? value : undefined;
}

/** Return the trusted hook that aborts one provider response boundary. */
export function getRuntimeProviderReplayCheckpointTurnFailed(
config: AgentConfig,
): (() => void | Promise<void>) | undefined {
const value = (config as RuntimeToolFilterConfig).__vfProviderReplayCheckpointTurnFailed;
return typeof value === "function" ? value : undefined;
}

/** Return whether provider replay state must be durable before continuation. */
export function isRuntimeProviderReplayCheckpointPersistenceRequired(
config: AgentConfig,
Expand Down
Loading
Loading