File size: 4,138 Bytes
bc575bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import type { AgentLane, OperationResultRecord, SuspendedRun } from "@earendil-works/pi-agent-core";
import type { ImageContent } from "@earendil-works/pi-ai";
import type {
	AgentController as AgentControllerService,
	AgentOperationError,
	AgentOperationResponse,
	AgentPromptRequest,
	AgentQueueResponse,
} from "./agent-controller.ts";

export function createAgentController(lane: AgentLane): AgentControllerService {
	const queue = async (
		operation: "steer" | "followUp" | "nextRun",
		request: AgentPromptRequest,
		context: Parameters<AgentControllerService["steer"]>[1],
	): Promise<AgentQueueResponse> => {
		const [message, images] = toTextPrompt(request);
		const result = await lane[operation](message, images, context);
		return result.ok
			? { accepted: true, entryId: result.value.entryId, error: null }
			: { accepted: false, entryId: null, error: toAgentError(result.error) };
	};

	return {
		async prompt(request, context) {
			const [message, images] = toTextPrompt(request);
			const result = await lane.prompt(message, images, context);
			return result.ok
				? toOperationResponse(result.value)
				: { accepted: false, operationId: operationId(result.error), error: toAgentError(result.error) };
		},
		async requestAbort(operationId, context) {
			const result = await lane.requestAbort(operationId, context);
			if (!result.ok) throw new Error(result.error.message);
		},
		steer: (request, context) => queue("steer", request, context),
		followUp: (request, context) => queue("followUp", request, context),
		nextRun: (request, context) => queue("nextRun", request, context),
		async cancelQueued(entryId, context) {
			const result = await lane.cancelQueued(entryId, context);
			if (!result.ok) throw new Error(result.error.message);
			return { outcome: result.value.kind };
		},
		async resume(context) {
			const result = await lane.resume(context);
			return result.ok
				? toOperationResponse(result.value)
				: { accepted: false, operationId: null, error: toAgentError(result.error) };
		},
		async compact(request, context) {
			const result = await lane.compact(
				request.customInstructions === null ? undefined : { customInstructions: request.customInstructions },
				context,
			);
			return result.ok
				? toOperationResponse(result.value.compaction)
				: { accepted: false, operationId: operationId(result.error), error: toAgentError(result.error) };
		},
		async navigate(request, context) {
			const result = await lane.navigateTree(
				request.targetId,
				{
					summarize: request.summarize,
					...(request.label === null ? {} : { label: request.label }),
					...(request.customInstructions === null ? {} : { customInstructions: request.customInstructions }),
				},
				context,
			);
			return result.ok
				? toOperationResponse(result.value.navigation)
				: { accepted: false, operationId: operationId(result.error), error: toAgentError(result.error) };
		},
	};
}

function toOperationResponse(value: OperationResultRecord | SuspendedRun): AgentOperationResponse {
	return {
		accepted: true,
		operationId: value.operationId,
		error:
			"status" in value && value.status === "failed" && value.error !== undefined
				? { code: value.error.code, message: value.error.message }
				: null,
	};
}

function operationId(error: { readonly _tag: string }): string | null {
	return "operationId" in error && typeof error.operationId === "string" ? error.operationId : null;
}

function toAgentError(error: { readonly _tag: string; readonly message: string }): AgentOperationError {
	const code =
		{
			LaneBusy: "lane_busy",
			InvalidMessage: "invalid_message",
			UnknownSkill: "unknown_skill",
			UnknownTemplate: "unknown_template",
			NothingToCompact: "nothing_to_compact",
			NothingToResume: "nothing_to_resume",
			InvalidNavigation: "invalid_navigation",
			UnknownTarget: "unknown_target",
			Closed: "closed",
		}[error._tag] ?? "operation_failed";
	return { code, message: error.message };
}

function toTextPrompt(request: AgentPromptRequest): [message: string, images: ImageContent[] | undefined] {
	return [request.message, request.images ?? undefined];
}