refactor: derive session payloads from ManagedAgent
This commit is contained in:
@@ -9,7 +9,7 @@ import type {
|
|||||||
} from "./agent-sdk-types.js";
|
} from "./agent-sdk-types.js";
|
||||||
import type {
|
import type {
|
||||||
AgentManager,
|
AgentManager,
|
||||||
AgentSnapshot,
|
ManagedAgent,
|
||||||
WaitForAgentResult,
|
WaitForAgentResult,
|
||||||
} from "./agent-manager.js";
|
} from "./agent-manager.js";
|
||||||
import {
|
import {
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
AgentSnapshotPayloadSchema,
|
AgentSnapshotPayloadSchema,
|
||||||
serializeAgentSnapshot,
|
serializeAgentSnapshot,
|
||||||
} from "../messages.js";
|
} from "../messages.js";
|
||||||
|
import { toAgentPayload } from "./agent-projections.js";
|
||||||
import { curateAgentActivity } from "./activity-curator.js";
|
import { curateAgentActivity } from "./activity-curator.js";
|
||||||
import { AGENT_PROVIDER_DEFINITIONS } from "./provider-manifest.js";
|
import { AGENT_PROVIDER_DEFINITIONS } from "./provider-manifest.js";
|
||||||
import { AgentRegistry } from "./agent-registry.js";
|
import { AgentRegistry } from "./agent-registry.js";
|
||||||
@@ -90,7 +91,7 @@ async function resolveAgentTitle(
|
|||||||
|
|
||||||
async function serializeSnapshotWithMetadata(
|
async function serializeSnapshotWithMetadata(
|
||||||
agentRegistry: AgentRegistry,
|
agentRegistry: AgentRegistry,
|
||||||
snapshot: AgentSnapshot
|
snapshot: ManagedAgent
|
||||||
) {
|
) {
|
||||||
const title = await resolveAgentTitle(agentRegistry, snapshot.id);
|
const title = await resolveAgentTitle(agentRegistry, snapshot.id);
|
||||||
return serializeAgentSnapshot(snapshot, { title });
|
return serializeAgentSnapshot(snapshot, { title });
|
||||||
@@ -232,7 +233,7 @@ export async function createAgentMcpServer(
|
|||||||
structuredContent: ensureValidJson({
|
structuredContent: ensureValidJson({
|
||||||
agentId: snapshot.id,
|
agentId: snapshot.id,
|
||||||
type: provider,
|
type: provider,
|
||||||
status: snapshot.status,
|
status: snapshot.lifecycle,
|
||||||
cwd: snapshot.cwd,
|
cwd: snapshot.cwd,
|
||||||
currentModeId: snapshot.currentModeId,
|
currentModeId: snapshot.currentModeId,
|
||||||
availableModes: snapshot.availableModes,
|
availableModes: snapshot.availableModes,
|
||||||
@@ -394,7 +395,7 @@ export async function createAgentMcpServer(
|
|||||||
|
|
||||||
const responseData = {
|
const responseData = {
|
||||||
success: true,
|
success: true,
|
||||||
status: snapshot?.status ?? "idle",
|
status: snapshot?.lifecycle ?? "idle",
|
||||||
lastMessage: null,
|
lastMessage: null,
|
||||||
permission: null,
|
permission: null,
|
||||||
};
|
};
|
||||||
@@ -435,7 +436,7 @@ export async function createAgentMcpServer(
|
|||||||
return {
|
return {
|
||||||
content: [],
|
content: [],
|
||||||
structuredContent: ensureValidJson({
|
structuredContent: ensureValidJson({
|
||||||
status: snapshot.status,
|
status: snapshot.lifecycle,
|
||||||
snapshot: structuredSnapshot,
|
snapshot: structuredSnapshot,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -616,13 +617,14 @@ export async function createAgentMcpServer(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const permissions = agentManager.listAgents().flatMap((agent) =>
|
const permissions = agentManager.listAgents().flatMap((agent) => {
|
||||||
agent.pendingPermissions.map((request) => ({
|
const payload = toAgentPayload(agent);
|
||||||
|
return payload.pendingPermissions.map((request) => ({
|
||||||
agentId: agent.id,
|
agentId: agent.id,
|
||||||
status: agent.status,
|
status: payload.status,
|
||||||
request,
|
request,
|
||||||
}))
|
}));
|
||||||
);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [],
|
content: [],
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
AGENT_LIFECYCLE_STATUSES,
|
AGENT_LIFECYCLE_STATUSES,
|
||||||
type AgentSnapshot,
|
type ManagedAgent,
|
||||||
} from "./agent/agent-manager.js";
|
} from "./agent/agent-manager.js";
|
||||||
|
import { toAgentPayload } from "./agent/agent-projections.js";
|
||||||
import type {
|
import type {
|
||||||
AgentCapabilityFlags,
|
AgentCapabilityFlags,
|
||||||
AgentModelDefinition,
|
AgentModelDefinition,
|
||||||
@@ -16,16 +17,6 @@ import type {
|
|||||||
AgentUsage,
|
AgentUsage,
|
||||||
} from "./agent/agent-sdk-types.js";
|
} from "./agent/agent-sdk-types.js";
|
||||||
|
|
||||||
export type AgentSnapshotPayload = Omit<
|
|
||||||
AgentSnapshot,
|
|
||||||
"createdAt" | "updatedAt" | "lastUserMessageAt"
|
|
||||||
> & {
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
lastUserMessageAt: string | null;
|
|
||||||
title: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AGENT_PROVIDERS: [AgentProvider, AgentProvider] = ["claude", "codex"];
|
const AGENT_PROVIDERS: [AgentProvider, AgentProvider] = ["claude", "codex"];
|
||||||
const AgentProviderSchema = z.enum(AGENT_PROVIDERS);
|
const AgentProviderSchema = z.enum(AGENT_PROVIDERS);
|
||||||
|
|
||||||
@@ -230,96 +221,17 @@ export const AgentSnapshotPayloadSchema = z.object({
|
|||||||
title: z.string().nullable(),
|
title: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
function sanitizeOptionalJson(value: unknown): unknown {
|
export type AgentSnapshotPayload = z.infer<typeof AgentSnapshotPayloadSchema>;
|
||||||
if (value === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value
|
|
||||||
.map((item) => sanitizeOptionalJson(item))
|
|
||||||
.filter((item) => item !== undefined);
|
|
||||||
}
|
|
||||||
if (value instanceof Date) {
|
|
||||||
return value.toISOString();
|
|
||||||
}
|
|
||||||
if (typeof value === "object") {
|
|
||||||
const result: Record<string, unknown> = {};
|
|
||||||
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
||||||
const sanitized = sanitizeOptionalJson(val);
|
|
||||||
if (sanitized !== undefined) {
|
|
||||||
result[key] = sanitized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Object.keys(result).length ? result : undefined;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeOptionalJsonValue<T>(value: T | undefined): T | undefined {
|
|
||||||
const sanitized = sanitizeOptionalJson(value);
|
|
||||||
return sanitized === undefined ? undefined : (sanitized as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizePersistenceHandle(
|
|
||||||
handle: AgentPersistenceHandle | null
|
|
||||||
): AgentPersistenceHandle | null {
|
|
||||||
if (!handle) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const sanitized: AgentPersistenceHandle = {
|
|
||||||
provider: handle.provider,
|
|
||||||
sessionId: handle.sessionId,
|
|
||||||
};
|
|
||||||
if (handle.nativeHandle !== undefined) {
|
|
||||||
sanitized.nativeHandle = handle.nativeHandle;
|
|
||||||
}
|
|
||||||
const metadata = sanitizeOptionalJsonValue(handle.metadata);
|
|
||||||
if (metadata !== undefined) {
|
|
||||||
sanitized.metadata = metadata;
|
|
||||||
}
|
|
||||||
return sanitized;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentStreamEventPayload = z.infer<
|
export type AgentStreamEventPayload = z.infer<
|
||||||
typeof AgentStreamEventPayloadSchema
|
typeof AgentStreamEventPayloadSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function serializeAgentSnapshot(
|
export function serializeAgentSnapshot(
|
||||||
snapshot: AgentSnapshot,
|
agent: ManagedAgent,
|
||||||
options?: { title?: string | null }
|
options?: { title?: string | null }
|
||||||
): AgentSnapshotPayload {
|
): AgentSnapshotPayload {
|
||||||
const payload: AgentSnapshotPayload = {
|
return toAgentPayload(agent, options);
|
||||||
id: snapshot.id,
|
|
||||||
provider: snapshot.provider,
|
|
||||||
cwd: snapshot.cwd,
|
|
||||||
model: snapshot.model,
|
|
||||||
createdAt: snapshot.createdAt.toISOString(),
|
|
||||||
updatedAt: snapshot.updatedAt.toISOString(),
|
|
||||||
lastUserMessageAt: snapshot.lastUserMessageAt
|
|
||||||
? snapshot.lastUserMessageAt.toISOString()
|
|
||||||
: null,
|
|
||||||
status: snapshot.status,
|
|
||||||
sessionId: snapshot.sessionId,
|
|
||||||
capabilities: snapshot.capabilities,
|
|
||||||
currentModeId: snapshot.currentModeId,
|
|
||||||
availableModes: snapshot.availableModes,
|
|
||||||
pendingPermissions: snapshot.pendingPermissions,
|
|
||||||
persistence: sanitizePersistenceHandle(snapshot.persistence),
|
|
||||||
title: options?.title ?? null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const lastUsage = sanitizeOptionalJsonValue<AgentUsage>(snapshot.lastUsage);
|
|
||||||
if (lastUsage !== undefined) {
|
|
||||||
payload.lastUsage = lastUsage;
|
|
||||||
}
|
|
||||||
if (snapshot.lastError !== undefined) {
|
|
||||||
payload.lastError = snapshot.lastError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeAgentStreamEvent(
|
export function serializeAgentStreamEvent(
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
OpenRouterProviderOptions,
|
OpenRouterProviderOptions,
|
||||||
} from "@openrouter/ai-sdk-provider";
|
} from "@openrouter/ai-sdk-provider";
|
||||||
import {
|
import {
|
||||||
serializeAgentSnapshot,
|
|
||||||
serializeAgentStreamEvent,
|
serializeAgentStreamEvent,
|
||||||
type AgentSnapshotPayload,
|
type AgentSnapshotPayload,
|
||||||
type SessionInboundMessage,
|
type SessionInboundMessage,
|
||||||
@@ -39,7 +38,8 @@ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|||||||
import { createTerminalMcpServer } from "./terminal-mcp/index.js";
|
import { createTerminalMcpServer } from "./terminal-mcp/index.js";
|
||||||
import { fetchProviderModelCatalog } from "./agent/model-catalog.js";
|
import { fetchProviderModelCatalog } from "./agent/model-catalog.js";
|
||||||
import { AgentManager } from "./agent/agent-manager.js";
|
import { AgentManager } from "./agent/agent-manager.js";
|
||||||
import type { AgentSnapshot } from "./agent/agent-manager.js";
|
import type { ManagedAgent } from "./agent/agent-manager.js";
|
||||||
|
import { toAgentPayload } from "./agent/agent-projections.js";
|
||||||
import type {
|
import type {
|
||||||
AgentPermissionResponse,
|
AgentPermissionResponse,
|
||||||
AgentPromptInput,
|
AgentPromptInput,
|
||||||
@@ -234,7 +234,7 @@ export class Session {
|
|||||||
private unsubscribeAgentEvents: (() => void) | null = null;
|
private unsubscribeAgentEvents: (() => void) | null = null;
|
||||||
private pendingAgentInitializations: Map<
|
private pendingAgentInitializations: Map<
|
||||||
string,
|
string,
|
||||||
Promise<AgentSnapshot>
|
Promise<ManagedAgent>
|
||||||
> = new Map();
|
> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -325,7 +325,7 @@ export class Session {
|
|||||||
throw new Error(`Agent ${agentId} not found`);
|
throw new Error(`Agent ${agentId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.status !== "running") {
|
if (snapshot.lifecycle !== "running") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,10 +532,10 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async buildAgentPayload(
|
private async buildAgentPayload(
|
||||||
agent: AgentSnapshot
|
agent: ManagedAgent
|
||||||
): Promise<AgentSnapshotPayload> {
|
): Promise<AgentSnapshotPayload> {
|
||||||
const title = await this.getStoredAgentTitle(agent.id);
|
const title = await this.getStoredAgentTitle(agent.id);
|
||||||
return serializeAgentSnapshot(agent, { title });
|
return toAgentPayload(agent, { title });
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildStoredAgentPayload(record: StoredAgentRecord): AgentSnapshotPayload {
|
private buildStoredAgentPayload(record: StoredAgentRecord): AgentSnapshotPayload {
|
||||||
@@ -576,7 +576,7 @@ export class Session {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ensureAgentLoaded(agentId: string): Promise<AgentSnapshot> {
|
private async ensureAgentLoaded(agentId: string): Promise<ManagedAgent> {
|
||||||
const existing = this.agentManager.getAgent(agentId);
|
const existing = this.agentManager.getAgent(agentId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
@@ -594,7 +594,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handle = toAgentPersistenceHandle(record.persistence);
|
const handle = toAgentPersistenceHandle(record.persistence);
|
||||||
let snapshot: AgentSnapshot;
|
let snapshot: ManagedAgent;
|
||||||
if (handle) {
|
if (handle) {
|
||||||
snapshot = await this.agentManager.resumeAgent(
|
snapshot = await this.agentManager.resumeAgent(
|
||||||
handle,
|
handle,
|
||||||
@@ -619,7 +619,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async forwardAgentState(agent: AgentSnapshot): Promise<void> {
|
private async forwardAgentState(agent: ManagedAgent): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const payload = await this.buildAgentPayload(agent);
|
const payload = await this.buildAgentPayload(agent);
|
||||||
this.emit({
|
this.emit({
|
||||||
@@ -1189,7 +1189,7 @@ export class Session {
|
|||||||
payload: {
|
payload: {
|
||||||
status: "agent_initialized",
|
status: "agent_initialized",
|
||||||
agentId,
|
agentId,
|
||||||
agentStatus: snapshot.status,
|
agentStatus: snapshot.lifecycle,
|
||||||
requestId,
|
requestId,
|
||||||
timelineSize,
|
timelineSize,
|
||||||
},
|
},
|
||||||
@@ -1197,7 +1197,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[Session ${this.clientId}] Agent ${agentId} initialized with ${timelineSize} timeline item(s); status=${snapshot.status}`
|
`[Session ${this.clientId}] Agent ${agentId} initialized with ${timelineSize} timeline item(s); status=${snapshot.lifecycle}`
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -1370,7 +1370,7 @@ export class Session {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let snapshot: AgentSnapshot;
|
let snapshot: ManagedAgent;
|
||||||
const existing = this.agentManager.getAgent(agentId);
|
const existing = this.agentManager.getAgent(agentId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await this.interruptAgentIfRunning(agentId);
|
await this.interruptAgentIfRunning(agentId);
|
||||||
@@ -2048,7 +2048,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitAgentTimelineSnapshot(agent: AgentSnapshot): number {
|
private emitAgentTimelineSnapshot(agent: ManagedAgent): number {
|
||||||
const timeline = this.agentManager.getTimeline(agent.id);
|
const timeline = this.agentManager.getTimeline(agent.id);
|
||||||
const events = timeline.map((item) => ({
|
const events = timeline.map((item) => ({
|
||||||
event: serializeAgentStreamEvent({
|
event: serializeAgentStreamEvent({
|
||||||
|
|||||||
Reference in New Issue
Block a user