mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 04:42:30 +03:00
feat(dashboard): fromA2A orchestration mapper
This commit is contained in:
53
src/app/(dashboard)/dashboard/orchestration/model/fromA2A.ts
Normal file
53
src/app/(dashboard)/dashboard/orchestration/model/fromA2A.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/** A2A tasks → unified orchestration nodes. Pure. */
|
||||
import type { A2ATask } from "@/lib/a2a/taskManager";
|
||||
import type { OrchEdge, OrchNode, OrchState } from "./orchestrationTypes";
|
||||
|
||||
const STATE_MAP: Record<string, OrchState> = {
|
||||
submitted: "queued",
|
||||
working: "running",
|
||||
completed: "succeeded",
|
||||
failed: "failed",
|
||||
cancelled: "cancelled",
|
||||
};
|
||||
const TERMINAL: ReadonlySet<OrchState> = new Set(["succeeded", "failed", "cancelled"]);
|
||||
|
||||
function truncate(s: string, n = 60): string {
|
||||
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
||||
}
|
||||
|
||||
export function fromA2A(tasks: A2ATask[]): { nodes: OrchNode[]; edges: OrchEdge[] } {
|
||||
if (tasks.length === 0) return { nodes: [], edges: [] };
|
||||
const nodes: OrchNode[] = [];
|
||||
const edges: OrchEdge[] = [];
|
||||
const counts: Partial<Record<OrchState, number>> = {};
|
||||
|
||||
for (const t of tasks) {
|
||||
const mapped = STATE_MAP[t.state];
|
||||
const state: OrchState = mapped ?? "failed";
|
||||
counts[state] = (counts[state] ?? 0) + 1;
|
||||
const id = `a2a:${t.id}`;
|
||||
const firstUser = t.input.messages.find((m) => m.role === "user")?.content ?? "";
|
||||
nodes.push({
|
||||
id,
|
||||
kind: "work",
|
||||
source: "a2a",
|
||||
state,
|
||||
label: t.skill,
|
||||
sublabel: mapped ? truncate(firstUser) : `unknown state: ${String(t.state)}`,
|
||||
startedAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
endedAt: TERMINAL.has(state) ? t.updatedAt : undefined,
|
||||
raw: t,
|
||||
});
|
||||
edges.push({
|
||||
id: `e:source:a2a→${id}`,
|
||||
from: "source:a2a",
|
||||
to: id,
|
||||
kind: "owns",
|
||||
active: state === "running",
|
||||
});
|
||||
}
|
||||
|
||||
nodes.unshift({ id: "source:a2a", kind: "source", source: "a2a", label: "A2A", counts });
|
||||
return { nodes, edges };
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { STATUS_HEX } from "../../../src/shared/constants/statusColors.ts";
|
||||
import { fromCloudAgent } from "../../../src/app/(dashboard)/dashboard/orchestration/model/fromCloudAgent.ts";
|
||||
import type { CloudAgentTask } from "../../../src/lib/cloudAgent/types.ts";
|
||||
import { fromA2A } from "../../../src/app/(dashboard)/dashboard/orchestration/model/fromA2A.ts";
|
||||
import type { A2ATask } from "../../../src/lib/a2a/taskManager.ts";
|
||||
|
||||
describe("orchestrationTypes", () => {
|
||||
it("covers all six states with a color each", () => {
|
||||
@@ -101,3 +103,44 @@ describe("fromCloudAgent", () => {
|
||||
assert.equal(out.edges.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
function a2aTask(over: Partial<A2ATask>): A2ATask {
|
||||
return {
|
||||
id: "a1",
|
||||
skill: "smart-routing",
|
||||
state: "working",
|
||||
input: { skill: "smart-routing", messages: [{ role: "user", content: "route this well" }] },
|
||||
artifacts: [],
|
||||
events: [{ timestamp: "2026-08-30T10:00:00Z", state: "submitted" }],
|
||||
metadata: {},
|
||||
createdAt: "2026-08-30T10:00:00Z",
|
||||
updatedAt: "2026-08-30T10:01:00Z",
|
||||
expiresAt: "2026-08-30T10:05:00Z",
|
||||
...over,
|
||||
} as A2ATask;
|
||||
}
|
||||
|
||||
describe("fromA2A", () => {
|
||||
it("maps the five A2A states", () => {
|
||||
const cases: Array<[A2ATask["state"], string]> = [
|
||||
["submitted", "queued"],
|
||||
["working", "running"],
|
||||
["completed", "succeeded"],
|
||||
["failed", "failed"],
|
||||
["cancelled", "cancelled"],
|
||||
];
|
||||
for (const [input, expected] of cases) {
|
||||
const { nodes } = fromA2A([a2aTask({ state: input })]);
|
||||
assert.equal(nodes.find((n) => n.kind === "work")?.state, expected, input);
|
||||
}
|
||||
});
|
||||
it("work node id is a2a:<id>, label is the skill", () => {
|
||||
const { nodes } = fromA2A([a2aTask({})]);
|
||||
const w = nodes.find((n) => n.kind === "work");
|
||||
assert.equal(w?.id, "a2a:a1");
|
||||
assert.equal(w?.label, "smart-routing");
|
||||
});
|
||||
it("empty input emits nothing", () => {
|
||||
assert.equal(fromA2A([]).nodes.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user