mirror of
https://github.com/kennethnym/freya
synced 2026-07-07 00:01:20 +01:00
Add agent response scheduler (#158)
* feat: add agent response scheduler * feat: sendMessage rpc returns created entry * feat: make agent event include conversation entry * feat: add agent response logging
This commit is contained in:
@@ -10,6 +10,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@freya/agent-protocol": "workspace:*",
|
||||
"@nym.sh/jrpc": "^0.1.0"
|
||||
"@nym.sh/jrpc": "1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import type {
|
||||
AgentClientApi,
|
||||
AgentEvent,
|
||||
AgentServerApi,
|
||||
SendMessageResult,
|
||||
} from "@freya/agent-protocol"
|
||||
import type { JrpcChannel, JrpcMessage, JsonRpcMessage } from "@nym.sh/jrpc"
|
||||
|
||||
import {
|
||||
AgentEventKind,
|
||||
type AgentClientApi,
|
||||
type AgentConversationEntryCreatedEvent,
|
||||
type AgentEvent,
|
||||
type AgentServerApi,
|
||||
} from "@freya/agent-protocol"
|
||||
import { JsonRpcClient, JsonRpcServer } from "@nym.sh/jrpc"
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
type MessagePart = { type: "text"; text: string } | { type: "json"; value: unknown }
|
||||
type SendMessageResult = Awaited<ReturnType<AgentServerApi["sendMessage"]>>
|
||||
|
||||
interface AuthUser {
|
||||
id: string
|
||||
@@ -71,7 +74,6 @@ class AgentWebSocketSession implements AgentClientApi {
|
||||
private readonly client: JsonRpcClient<AgentServerApi>
|
||||
private readonly server: JsonRpcServer<AgentClientApi>
|
||||
private conversationId: string | undefined
|
||||
private responseHadText = false
|
||||
|
||||
private constructor(channel: WebSocketJrpcChannel) {
|
||||
this.channel = channel
|
||||
@@ -105,36 +107,23 @@ class AgentWebSocketSession implements AgentClientApi {
|
||||
}
|
||||
|
||||
async ask(message: string): Promise<void> {
|
||||
this.responseHadText = false
|
||||
|
||||
const result = await this.sendMessage(message)
|
||||
if (result.conversationId) {
|
||||
this.conversationId = result.conversationId
|
||||
}
|
||||
|
||||
if (!this.responseHadText) {
|
||||
console.log(`\nagent> ${result.message || "(no message)"}`)
|
||||
}
|
||||
const entry = await this.sendMessage(message)
|
||||
this.conversationId = entry.conversationId
|
||||
console.log(`\nqueued> ${entry.kind} ${entry.id}`)
|
||||
console.log("")
|
||||
}
|
||||
|
||||
notify(event: AgentEvent): void {
|
||||
switch (event.type) {
|
||||
case "conversation_started":
|
||||
switch (event.kind) {
|
||||
case AgentEventKind.ConversationStarted:
|
||||
this.conversationId = event.conversationId
|
||||
break
|
||||
case "message_created":
|
||||
this.printMessage(event.text)
|
||||
case AgentEventKind.ConversationEntryCreated:
|
||||
this.printConversationEntry(event.entry)
|
||||
break
|
||||
case "tool_started":
|
||||
console.log(`\ntool> ${event.toolName} started`)
|
||||
case AgentEventKind.ResponseFinished:
|
||||
break
|
||||
case "tool_finished":
|
||||
console.log(`tool> ${event.toolName} ${event.ok ? "finished" : "failed"}`)
|
||||
break
|
||||
case "message_finished":
|
||||
break
|
||||
case "message_failed":
|
||||
case AgentEventKind.ResponseFailed:
|
||||
console.log(`\nagent! ${event.error}`)
|
||||
break
|
||||
}
|
||||
@@ -156,7 +145,31 @@ class AgentWebSocketSession implements AgentClientApi {
|
||||
if (text === "") return
|
||||
|
||||
console.log(`\nagent> ${text}`)
|
||||
this.responseHadText = true
|
||||
}
|
||||
|
||||
private printConversationEntry(entry: AgentConversationEntryCreatedEvent["entry"]): void {
|
||||
this.conversationId = entry.conversationId
|
||||
|
||||
switch (entry.kind) {
|
||||
case "assistant_message":
|
||||
this.printMessage(messagePartsText(entry.payload.parts))
|
||||
break
|
||||
case "tool_call":
|
||||
console.log(`\ntool> ${payloadString(entry.payload, "toolName", "unknown")} started`)
|
||||
break
|
||||
case "tool_result":
|
||||
console.log(
|
||||
`tool> ${payloadString(entry.payload, "toolName", "unknown")} ${
|
||||
payloadBoolean(entry.payload, "ok") ? "finished" : "failed"
|
||||
}`,
|
||||
)
|
||||
break
|
||||
case "user_message":
|
||||
case "attachment":
|
||||
case "context_summary":
|
||||
case "system_note":
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,6 +704,28 @@ function parseJsonArgument(value: string, fallback: unknown): unknown {
|
||||
}
|
||||
}
|
||||
|
||||
function messagePartsText(parts: MessagePart[]): string {
|
||||
return parts.map(messagePartText).join("\n")
|
||||
}
|
||||
|
||||
function messagePartText(part: MessagePart): string {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return part.text
|
||||
case "json":
|
||||
return formatJson(part.value)
|
||||
}
|
||||
}
|
||||
|
||||
function payloadString(payload: Record<string, unknown>, key: string, fallback: string): string {
|
||||
const value = payload[key]
|
||||
return typeof value === "string" ? value : fallback
|
||||
}
|
||||
|
||||
function payloadBoolean(payload: Record<string, unknown>, key: string): boolean {
|
||||
return payload[key] === true
|
||||
}
|
||||
|
||||
function formatJson(value: unknown): string {
|
||||
const serialized = JSON.stringify(value, null, 2)
|
||||
return serialized ?? "undefined"
|
||||
|
||||
Reference in New Issue
Block a user