feat: add agent protocol entry events (#157)

* feat: add agent protocol events

* feat: add tool payload schemas
This commit is contained in:
2026-07-04 15:03:03 +01:00
committed by GitHub
parent 9aaefda216
commit 6401616890
6 changed files with 84 additions and 13 deletions

View File

@@ -158,6 +158,9 @@
"packages/freya-agent-protocol": { "packages/freya-agent-protocol": {
"name": "@freya/agent-protocol", "name": "@freya/agent-protocol",
"version": "0.0.0", "version": "0.0.0",
"dependencies": {
"@freya/core": "workspace:*",
},
}, },
"packages/freya-components": { "packages/freya-components": {
"name": "@freya/components", "name": "@freya/components",

View File

@@ -6,5 +6,8 @@
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"test": "bun test ./src" "test": "bun test ./src"
},
"dependencies": {
"@freya/core": "workspace:*"
} }
} }

View File

@@ -1,20 +1,40 @@
import { ConversationEntryKind, ConversationEntryVisibility } from "@freya/core"
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import type { AgentEvent, AgentServerApi } from "./index" import { AgentEventKind, type AgentEvent, type AgentServerApi } from "./index"
describe("agent protocol", () => { describe("agent protocol", () => {
test("defines server methods and agent events", () => { test("defines server methods and agent events", () => {
const server: AgentServerApi = { const server: AgentServerApi = {
async sendMessage(message) { async sendMessage(message) {
return { message, conversationId: "conversation-1" } return {
id: "entry-1",
conversationId: "conversation-1",
sequence: 1,
kind: ConversationEntryKind.UserMessage,
visibility: ConversationEntryVisibility.UserVisible,
fileId: null,
payload: {
role: "user",
parts: [{ type: "text", text: message }],
},
metadata: {},
createdAt: "2026-07-03T00:00:00.000Z",
}
},
notify() {
// no-op for protocol shape test
}, },
ping() { ping() {
return "pong" return "pong"
}, },
} }
const event: AgentEvent = { type: "message_finished" } const event: AgentEvent = {
kind: AgentEventKind.ResponseFinished,
conversationId: "conversation-1",
}
expect(server.ping()).toBe("pong") expect(server.ping()).toBe("pong")
expect(event.type).toBe("message_finished") expect(event.kind).toBe(AgentEventKind.ResponseFinished)
}) })
}) })

View File

@@ -1,18 +1,46 @@
export interface SendMessageResult { import type { ConversationEntry } from "@freya/core"
message: string
export const AgentEventKind = {
ConversationStarted: "conversation_started",
ConversationEntryCreated: "conversation_entry_created",
ResponseFinished: "response_finished",
ResponseFailed: "response_failed",
} as const
export type AgentEventKind = (typeof AgentEventKind)[keyof typeof AgentEventKind]
export interface AgentConversationStartedEvent {
kind: typeof AgentEventKind.ConversationStarted
conversationId: string conversationId: string
} }
export interface AgentConversationEntryCreatedEvent {
kind: typeof AgentEventKind.ConversationEntryCreated
entry: ConversationEntry
}
export interface AgentResponseFinishedEvent {
kind: typeof AgentEventKind.ResponseFinished
conversationId: string
}
export interface AgentResponseFailedEvent {
kind: typeof AgentEventKind.ResponseFailed
conversationId: string
error: string
}
export type AgentEvent = export type AgentEvent =
| { type: "conversation_started"; conversationId: string } | AgentConversationStartedEvent
| { type: "message_created"; text: string } | AgentConversationEntryCreatedEvent
| { type: "tool_started"; toolName: string } | AgentResponseFinishedEvent
| { type: "tool_finished"; toolName: string; ok: boolean } | AgentResponseFailedEvent
| { type: "message_finished" }
| { type: "message_failed"; error: string } export type UserEvent = { type: "typing" }
export interface AgentServerApi { export interface AgentServerApi {
sendMessage(message: string): Promise<SendMessageResult> sendMessage(message: string): Promise<ConversationEntry>
notify(event: UserEvent): void
ping(): "pong" ping(): "pong"
} }

View File

@@ -146,6 +146,19 @@ export const ConversationEntryMetadata = type({
/** Metadata bag attached to a conversation entry. */ /** Metadata bag attached to a conversation entry. */
export type ConversationEntryMetadata = typeof ConversationEntryMetadata.infer export type ConversationEntryMetadata = typeof ConversationEntryMetadata.infer
export const ToolCallPayload = type({
toolName: "string",
})
export type ToolCallPayload = typeof ToolCallPayload.infer
export const ToolResultPayload = type({
toolName: "string",
ok: "boolean",
})
export type ToolResultPayload = typeof ToolResultPayload.infer
/** Generic object payload used by operational entries. */ /** Generic object payload used by operational entries. */
export const GenericObjectPayload = type("Record<string, unknown>") export const GenericObjectPayload = type("Record<string, unknown>")
@@ -158,6 +171,8 @@ export type ConversationEntryPayload =
| AssistantMessagePayload | AssistantMessagePayload
| AttachmentPayload | AttachmentPayload
| ContextSummaryPayload | ContextSummaryPayload
| ToolCallPayload
| ToolResultPayload
| GenericObjectPayload | GenericObjectPayload
export const Conversation = type({ export const Conversation = type({

View File

@@ -29,7 +29,9 @@ export {
SystemNoteConversationEntry, SystemNoteConversationEntry,
TextMessagePart, TextMessagePart,
ToolCallConversationEntry, ToolCallConversationEntry,
ToolCallPayload,
ToolResultConversationEntry, ToolResultConversationEntry,
ToolResultPayload,
UserMessagePayload, UserMessagePayload,
UserMessageConversationEntry, UserMessageConversationEntry,
} from "./conversation" } from "./conversation"