Skip to content
Closed
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
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ API_PORT=8787
WEB_HOST=127.0.0.1
WEB_PORT=3000

# Independent Deep Agents runtime. `npm run dev` starts services/deepagents-runtime
# on :8790 and sets RUNTIME_SERVICE_URL automatically. Leave empty only when you
# want the in-process TypeScript stub, or pass --no-runtime.
# RUNTIME_SERVICE_URL=http://127.0.0.1:8790
# RUNTIME_SERVICE_TOKEN=
# RUNTIME_HOST=127.0.0.1
# RUNTIME_PORT=8790
# RUNTIME_STUB_HOST=127.0.0.1
# RUNTIME_STUB_PORT=8790
# Use a scripted model when LLM_API_KEY is empty. Set live to require a real key.
# DEEPAGENTS_RUNTIME_MODEL=fake

# LLM_* values are optional server-side defaults. Models can also be created in the Web UI after deploy.
LLM_PROVIDER=openai-compatible
LLM_MODEL=qwen-plus
Expand Down
1 change: 0 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
},
"dependencies": {
"@ag-ui/client": "^0.0.57",
"@ag-ui/mastra": "^1.0.3",
"@copilotkit/runtime": "0.0.0-mme-ag-ui-0-0-46-20260227141603",
"@datafoundry/agent-runtime": "0.2.0",
"@datafoundry/contracts": "0.2.0",
Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/interaction-runtime-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";

import {
buildHitlSuspendBridgeEvents,
parseInterruptValue,
type HitlToolCallBoundaryState,
type InteractionInterrupt
} from "./interaction-runtime-adapter.js";
Expand Down Expand Up @@ -108,3 +109,20 @@ describe("buildHitlSuspendBridgeEvents", () => {
expect(events[0]).toBe(interactionEvent);
});
});

describe("parseInterruptValue", () => {
it("accepts agent_interrupt and legacy mastra_suspend payloads", () => {
expect(parseInterruptValue({
type: "agent_interrupt",
toolCallId: "c1",
toolName: "ask_user",
runId: "run-1"
}).type).toBe("agent_interrupt");
expect(parseInterruptValue({
type: "mastra_suspend",
toolCallId: "c1",
toolName: "submit_plan",
runId: "run-1"
}).toolName).toBe("submit_plan");
});
});
14 changes: 8 additions & 6 deletions apps/api/src/interaction-runtime-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { MetadataStore } from "@datafoundry/metadata";
import { createHash, randomUUID } from "node:crypto";

export type InteractionInterrupt = {
type?: "agent_interrupt" | "mastra_suspend";
args: unknown;
resumeSchema: unknown;
runId: string;
Expand All @@ -28,7 +29,7 @@ export class InteractionRuntimeAdapter {
) {}

/**
* Convert Mastra's interrupt event into the stable application interaction event.
* Convert a runtime interrupt event into the stable application interaction event.
* Returns both the parsed interrupt (for TOOL_CALL_START persistence) and the
* `interaction.requested` CUSTOM event.
*/
Expand Down Expand Up @@ -123,7 +124,7 @@ export const buildHitlToolCallStartEvent = (interrupt: InteractionInterrupt): Ba

/**
* Pair with {@link buildHitlToolCallStartEvent} before the transport-only RUN_FINISHED.
* Mastra's on_interrupt path never emits TOOL_CALL_END; without it, AbstractAgent
* Some runtimes emit only on_interrupt; without TOOL_CALL_END, AbstractAgent
* verifyEvents rejects RUN_FINISHED while the tool call is still active.
*/
export const buildHitlToolCallEndEvent = (interrupt: InteractionInterrupt): BaseEvent =>
Expand Down Expand Up @@ -172,7 +173,7 @@ export function buildHitlSuspendBridgeEvents(input: {
return events;
}

/** Extract a Mastra resume command from an AG-UI run request. */
/** Extract a HITL resume command from an AG-UI run request. */
export const extractInteractionResume = (input: RunAgentInput): InteractionResume | undefined => {
if (!isRecord(input.forwardedProps) || !isRecord(input.forwardedProps.command)) {
return undefined;
Expand Down Expand Up @@ -205,7 +206,7 @@ const readInterruptEventValue = (
return customEvent.value;
}
return {
type: "mastra_suspend",
type: interrupt.type ?? "agent_interrupt",
args: interrupt.args,
resumeSchema: interrupt.resumeSchema,
runId: interrupt.runId,
Expand All @@ -215,11 +216,11 @@ const readInterruptEventValue = (
};
};

const parseInterruptValue = (value: unknown): InteractionInterrupt => {
export const parseInterruptValue = (value: unknown): InteractionInterrupt => {
const parsed = typeof value === "string" ? parseJson(value) : value;
if (
!isRecord(parsed)
|| parsed.type !== "mastra_suspend"
|| (parsed.type !== "agent_interrupt" && parsed.type !== "mastra_suspend")
|| typeof parsed.toolCallId !== "string"
|| typeof parsed.toolName !== "string"
|| typeof parsed.runId !== "string"
Expand All @@ -230,6 +231,7 @@ const parseInterruptValue = (value: unknown): InteractionInterrupt => {
throw new Error(`UNSUPPORTED_INTERACTION_TOOL:${parsed.toolName}`);
}
return {
type: parsed.type,
args: parsed.args,
resumeSchema: parsed.resumeSchema,
runId: parsed.runId,
Expand Down
8 changes: 5 additions & 3 deletions apps/api/src/routes/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const CAPABILITIES = {
"conversation.memory": true,
"conversation.title": true,
"interaction.resume": true,
"runtime.dataTools": false,
"runtime.traceDag": false,
"datasource.fieldMasking": true,
"datasource.extendedTypes": true,
"datasource.introspectionPolicy": true,
Expand All @@ -22,12 +24,12 @@ const CAPABILITIES = {
"kb.scope": true,
"llm.advancedSampling": true,
"llm.samplingParams": true,
knowledge: true,
mcp: true,
knowledge: false,
mcp: false,
"mcp.stdio": true,
"mcp.toolPolicy": true,
"skill.resourceBinding": true,
skills: true
skills: false
};

export const handleCapabilitiesRequest = (method: string | undefined): ConfigApiResponse => {
Expand Down
Loading