Perceptions and InteractionRequests
It is possible to send _metadata
as part of your DeveloperDispatchedPerception
and use that metadata in your MentalProcess
. It is also possible to dispatch InteractionRequest
objects with _metadata
attached from the Soul Engine for use in your client side applications.
Example Soul Client Dispatch
// from your application
soul.dispatch({
name: "kafischer",
action: "chatted",
content: "Hi there!",
_metadata: {
discordMessage: {
id: 123,
userId: 123,
guildId: 123,
guildName: "Open Souls",
},
},
})
And that metadata is useable from your MentalProcess
as part of both the step's memory _metadata and the invokingPerception metadata.
import { usePerceptions, useActions } from "@opensouls/engine"
const exampleProcess: MentalProcess = async ({ workingMemory }) => {
import { log } from useActions()
// get information about the incoming perceptions
const { invokingPerception, pendingPerceptions } = usePerceptions()
log(`The soul received a discord message from the guild: ${invokingPerception._metadata.discordMessage.guildName}`)
// rest of the mental process
// ...
return workingMemory
}
export default exampleProcess
Example Mental Process Dispatch
Your MentalProcess
can dispatch _metadata as well. dispatch
is available from the useActions
hook.
import { usePerceptions, useActions } from "@opensouls/engine"
const heartReactor: MentalProcess = async ({ workingMemory }) => {
import { log, dispatch } from useActions()
// get information about the incoming perceptions
const { invokingPerception, pendingPerceptions } = usePerceptions()
dispatch({
action: "emojiReacts",
content: "❤️",
_metadata: {
messageId: invokingPerception._metadata.discordMessage.id,
},
})
return workingMemory
}
export default heartReactor
This is useable on the Soul client side through an ActionEvent:
import { ActionEvent, Soul, SoulEvent } from "@opensouls/soul";
// ... the rest of your app
soul.on("emojiReacts", (evt: ActionEvent) => {
console.log("reacts!", evt)
const message = findMessage(evt._metadata.messageId)
this.message.react(await evt.content())
})
Reference
A Perception
(what goes in to the Soul Engine) and an InteractionRequest
(what comes out of the Soul Engine) are both instances of a SoulEvent
which has this shape:
export enum SoulEventKinds {
Perception = "perception",
InteractionRequest = "interactionRequest",
System = "system",
}
export interface SoulEvent {
_id: string
_kind: SoulEventKinds
_timestamp: number // milliseconds since epoch
_metadata?: Record<string, Json>
_pending?: boolean
internal?: boolean
_mentalProcess?: {
name: string
params: Json
}
action: string
content: string
name?: string,
}
export type Json =
| { [key: string]: Json | undefined }
| Json[]
| boolean
| null
| number
| string
| undefined
On the Soul client side, we have a special event for an InteractionRequest
which handles streaming, etc for you. This is an ActionEvent
:
/**
* `ActionEvent` is designed to be isomorphic between streaming and non-streaming actions.
* When an event is not streaming,
* the `content` will be immediately available as a `Promise<string>`. The `stream`
* will be an `AsyncIterable<string>` that yields a single value if the event
* is not streaming, allowing for consistent handling of event data.
*
* If the event *is* streaming then content will resolve when the stream is complete.
*/
export type ActionEvent = {
content: () => Promise<string>
isStreaming: boolean
stream: () => AsyncIterable<string>
action: string
name?: string,
_metadata: InteractionRequest['_metadata']
_timestamp: InteractionRequest['_timestamp']
// @deprecated perception is deprecated, use interactionRequest
perception: InteractionRequest
interactionRequest: InteractionRequest
}
It's also possible to listen to all SoulEvent
dispatches through the Events.newSoulEvent
event (but this will not automatically handle streaming for you).
soul.on(Events.newSoulEvent, (evt: SoulEvent) => {
//...handle
})