fix(stage-ui): preserve and propagate discord context in bridge (#934)
--------- Co-authored-by: Neko Ayaka <neko@ayaka.moe>
This commit is contained in:
@@ -74,11 +74,7 @@ function handleActivate(char: Character) {
|
||||
v-model="searchQuery"
|
||||
placeholder="Search..."
|
||||
class="w-64"
|
||||
>
|
||||
<template #prefix>
|
||||
<div class="i-solar:magnifer-linear text-neutral-400" />
|
||||
</template>
|
||||
</FieldInput>
|
||||
/>
|
||||
<Button @click="handleCreate">
|
||||
<div class="i-solar:add-circle-bold mr-2" />
|
||||
Create New
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@proj-airi/server-shared": "workspace:^",
|
||||
"crossws": "^0.4.1",
|
||||
"h3": "^2.0.1-rc.6",
|
||||
"listhen": "^1.9.0"
|
||||
"listhen": "^1.9.0",
|
||||
"nanoid": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import type { AuthenticatedPeer, Peer } from './types'
|
||||
import { availableLogLevelStrings, Format, LogLevelString, logLevelStringToLogLevelMap, useLogg } from '@guiiai/logg'
|
||||
import { MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from '@proj-airi/server-shared/types'
|
||||
import { defineWebSocketHandler, H3 } from 'h3'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
import packageJSON from '../package.json'
|
||||
|
||||
import { optionOrEnv } from './config'
|
||||
import {
|
||||
@@ -20,10 +23,42 @@ import {
|
||||
matchesDestinations,
|
||||
} from './middlewares'
|
||||
|
||||
function createServerEventMetadata(serverInstanceId: string, parentId?: string) {
|
||||
return {
|
||||
event: {
|
||||
id: nanoid(),
|
||||
parentId,
|
||||
},
|
||||
source: {
|
||||
plugin: WebSocketEventSource.Server,
|
||||
instanceId: serverInstanceId,
|
||||
version: packageJSON.version,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// pre-stringified responses
|
||||
const RESPONSES = {
|
||||
authenticated: JSON.stringify({ type: 'module:authenticated', data: { authenticated: true }, source: WebSocketEventSource.Server } satisfies WebSocketEvent),
|
||||
notAuthenticated: JSON.stringify({ type: 'error', data: { message: 'not authenticated' }, source: WebSocketEventSource.Server } satisfies WebSocketEvent),
|
||||
authenticated: (serverInstanceId: string, parentId?: string) => JSON.stringify({
|
||||
type: 'module:authenticated',
|
||||
data: { authenticated: true },
|
||||
metadata: createServerEventMetadata(serverInstanceId, parentId),
|
||||
} satisfies WebSocketEvent),
|
||||
notAuthenticated: (serverInstanceId: string, parentId?: string) => JSON.stringify({
|
||||
type: 'error',
|
||||
data: { message: 'not authenticated' },
|
||||
metadata: createServerEventMetadata(serverInstanceId, parentId),
|
||||
} satisfies WebSocketEvent),
|
||||
error: (message: string, serverInstanceId: string, parentId?: string) => JSON.stringify({
|
||||
type: 'error',
|
||||
data: { message },
|
||||
metadata: createServerEventMetadata(serverInstanceId, parentId),
|
||||
}),
|
||||
heartbeat: (kind: MessageHeartbeatKind, message: MessageHeartbeat | string, serverInstanceId: string, parentId?: string) => JSON.stringify({
|
||||
type: 'transport:connection:heartbeat',
|
||||
data: { kind, message, at: Date.now() },
|
||||
metadata: createServerEventMetadata(serverInstanceId, parentId),
|
||||
} satisfies WebSocketEvent),
|
||||
}
|
||||
|
||||
const DEFAULT_HEARTBEAT_TTL_MS = 60_000
|
||||
@@ -34,6 +69,7 @@ function send(peer: Peer, event: WebSocketEvent<Record<string, unknown>> | strin
|
||||
}
|
||||
|
||||
export function setupApp(options?: {
|
||||
instanceId?: string
|
||||
auth?: {
|
||||
token: string
|
||||
}
|
||||
@@ -51,6 +87,7 @@ export function setupApp(options?: {
|
||||
message?: MessageHeartbeat | string
|
||||
}
|
||||
}): H3 {
|
||||
const instanceId = options?.instanceId || optionOrEnv(undefined, 'SERVER_INSTANCE_ID', nanoid())
|
||||
const authToken = optionOrEnv(options?.auth?.token, 'AUTHENTICATION_TOKEN', '')
|
||||
|
||||
const appLogLevel = optionOrEnv(options?.logger?.app?.level, 'LOG_LEVEL', LogLevelString.Log, { validator: (value): value is LogLevelString => availableLogLevelStrings.includes(value as LogLevelString) })
|
||||
@@ -144,7 +181,7 @@ export function setupApp(options?: {
|
||||
}
|
||||
catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
send(peer, { type: 'error', data: { message: `invalid JSON, error: ${errorMessage}` }, source: WebSocketEventSource.Server })
|
||||
send(peer, RESPONSES.error(`invalid JSON, error: ${errorMessage}`, instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -171,15 +208,7 @@ export function setupApp(options?: {
|
||||
}
|
||||
|
||||
if (event.data.kind === MessageHeartbeatKind.Ping) {
|
||||
send(peer, {
|
||||
type: 'transport:connection:heartbeat',
|
||||
data: {
|
||||
kind: MessageHeartbeatKind.Pong,
|
||||
message: heartbeatMessage,
|
||||
at: Date.now(),
|
||||
},
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.heartbeat(MessageHeartbeatKind.Pong, heartbeatMessage, instanceId, event.metadata?.event.id))
|
||||
}
|
||||
|
||||
return
|
||||
@@ -188,11 +217,7 @@ export function setupApp(options?: {
|
||||
case 'module:authenticate': {
|
||||
if (authToken && event.data.token !== authToken) {
|
||||
logger.withFields({ peer: peer.id, peerRemote: peer.remoteAddress, peerRequest: peer.request.url }).log('authentication failed')
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'invalid token' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('invalid token', instanceId, event.metadata?.event.id))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -217,31 +242,19 @@ export function setupApp(options?: {
|
||||
// verify
|
||||
const { name, index, identity } = event.data as { name: string, index?: number, identity?: MetadataEventSource }
|
||||
if (!name || typeof name !== 'string') {
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'the field \'name\' must be a non-empty string for event \'module:announce\'' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('the field \'name\' must be a non-empty string for event \'module:announce\'', instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
if (typeof index !== 'undefined') {
|
||||
if (!Number.isInteger(index) || index < 0) {
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'the field \'index\' must be a non-negative integer for event \'module:announce\'' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('the field \'index\' must be a non-negative integer for event \'module:announce\'', instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
if (authToken && !p.authenticated) {
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'must authenticate before announcing' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('must authenticate before announcing', instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -261,21 +274,13 @@ export function setupApp(options?: {
|
||||
const { moduleName, moduleIndex, config } = event.data
|
||||
|
||||
if (moduleName === '') {
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'the field \'moduleName\' can\'t be empty for event \'ui:configure\'' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('the field \'moduleName\' can\'t be empty for event \'ui:configure\'', instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
if (typeof moduleIndex !== 'undefined') {
|
||||
if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'the field \'moduleIndex\' must be a non-negative integer for event \'ui:configure\'' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('the field \'moduleIndex\' must be a non-negative integer for event \'ui:configure\'', instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -286,16 +291,12 @@ export function setupApp(options?: {
|
||||
send(target.peer, {
|
||||
type: 'module:configure',
|
||||
data: { config },
|
||||
// NOTICE: here we will forward the source as-is
|
||||
source: event.source,
|
||||
// NOTICE: this will forward the original event metadata as-is
|
||||
metadata: event.metadata,
|
||||
})
|
||||
}
|
||||
else {
|
||||
send(peer, {
|
||||
type: 'error',
|
||||
data: { message: 'module not found, it hasn\'t announced itself or the name is incorrect' },
|
||||
source: WebSocketEventSource.Server,
|
||||
})
|
||||
send(peer, RESPONSES.error('module not found, it hasn\'t announced itself or the name is incorrect', instanceId))
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RouteTargetExpression, WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-shared/types'
|
||||
import type { RouteTargetExpression, WebSocketBaseEvent, WebSocketEventOf, WebSocketEvents } from '@proj-airi/server-shared/types'
|
||||
|
||||
import type { AuthenticatedPeer } from '../types'
|
||||
|
||||
@@ -24,20 +24,26 @@ function createPeer(options: {
|
||||
}
|
||||
}
|
||||
|
||||
function createSparkNotifyEvent(overrides?: Partial<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify'], any>>): WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify'], any> {
|
||||
function createSparkNotifyEvent(overrides: Partial<WebSocketEventOf<'spark:notify'>> = {}): WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify'], any> {
|
||||
const data: WebSocketEvents['spark:notify'] = {
|
||||
id: 'evt-1',
|
||||
eventId: 'spark-1',
|
||||
kind: 'ping',
|
||||
urgency: 'soon',
|
||||
headline: 'hello',
|
||||
destinations: ['module:character'],
|
||||
...overrides.data,
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'spark:notify',
|
||||
data: {
|
||||
id: 'evt-1',
|
||||
eventId: 'spark-1',
|
||||
kind: 'ping',
|
||||
urgency: 'soon',
|
||||
headline: 'hello',
|
||||
destinations: ['module:character'],
|
||||
data,
|
||||
metadata: overrides.metadata ?? {
|
||||
source: { plugin: 'server-runtime', instanceId: 'test' },
|
||||
event: { id: data.id },
|
||||
},
|
||||
source: 'proj-airi:server-runtime',
|
||||
...overrides,
|
||||
}
|
||||
route: overrides.route,
|
||||
} as WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify'], any>
|
||||
}
|
||||
|
||||
describe('match-expression', () => {
|
||||
|
||||
@@ -69,7 +69,7 @@ export function createPolicyMiddleware(policy: RoutingPolicy): RouteMiddleware {
|
||||
}
|
||||
}
|
||||
|
||||
export function collectDestinations(event: WebSocketEvent) {
|
||||
export function collectDestinations(event: WebSocketEvent | (Omit<WebSocketEvent, 'metadata'> & Partial<Pick<WebSocketEvent, 'metadata'>>)) {
|
||||
if (event.route?.destinations?.length) {
|
||||
return event.route.destinations
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type {
|
||||
WebSocketEvent,
|
||||
WebSocketEventOptionalSource,
|
||||
WebSocketEvents,
|
||||
WebSocketEventSource,
|
||||
} from '@proj-airi/server-shared/types'
|
||||
|
||||
import WebSocket from 'crossws/websocket'
|
||||
@@ -38,6 +37,10 @@ function createInstanceId() {
|
||||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
function createEventId() {
|
||||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
|
||||
}
|
||||
|
||||
export class Client<C = undefined> {
|
||||
private connected = false
|
||||
private connecting = false
|
||||
@@ -305,9 +308,15 @@ export class Client<C = undefined> {
|
||||
send(data: WebSocketEventOptionalSource<C>): void {
|
||||
if (this.websocket && this.connected) {
|
||||
const payload = {
|
||||
source: this.opts.name as WebSocketEventSource | string,
|
||||
metadata: { source: this.identity },
|
||||
...data,
|
||||
metadata: {
|
||||
...data?.metadata,
|
||||
source: data?.metadata?.source ?? this.identity,
|
||||
event: {
|
||||
id: data?.metadata?.event?.id ?? createEventId(),
|
||||
...data?.metadata?.event,
|
||||
},
|
||||
},
|
||||
} as WebSocketEvent<C>
|
||||
|
||||
this.opts.onAnySend?.(payload)
|
||||
|
||||
@@ -158,7 +158,15 @@ export type WebSocketEventInputVoice = WebSocketEventInputVoiceBase & Partial<Wi
|
||||
|
||||
export type WebSocketEventDataInputs = WebSocketEventInputText | WebSocketEventInputTextVoice | WebSocketEventInputVoice
|
||||
|
||||
export type WebSocketEventInputs = WebSocketBaseEvent<'input:text' | 'input:text:voice' | 'input:voice', WebSocketEventInputText | WebSocketEventInputTextVoice | WebSocketEventInputVoice>
|
||||
export type WebSocketEventInputs = WebSocketEventOf<'input:text'> | WebSocketEventOf<'input:text:voice'> | WebSocketEventOf<'input:voice'>
|
||||
|
||||
export interface WebSocketEventBaseMetadata {
|
||||
source?: MetadataEventSource
|
||||
event?: {
|
||||
id?: string
|
||||
parentId?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebSocketBaseEvent<T, D, S extends string = string> {
|
||||
type: T
|
||||
@@ -167,8 +175,12 @@ export interface WebSocketBaseEvent<T, D, S extends string = string> {
|
||||
* @deprecated Prefer metadata.source.
|
||||
*/
|
||||
source?: WebSocketEventSource | S
|
||||
metadata?: {
|
||||
metadata: {
|
||||
source: MetadataEventSource
|
||||
event: {
|
||||
id: string
|
||||
parentId?: string
|
||||
}
|
||||
}
|
||||
route?: RouteConfig
|
||||
}
|
||||
@@ -350,5 +362,9 @@ export type WebSocketEvent<C = undefined> = {
|
||||
}[keyof WebSocketEvents<C>]
|
||||
|
||||
export type WebSocketEventOptionalSource<C = undefined> = {
|
||||
[K in keyof WebSocketEvents<C>]: Omit<WebSocketBaseEvent<K, WebSocketEvents<C>[K]>, 'source'> & Partial<Pick<WebSocketBaseEvent<K, WebSocketEvents<C>[K]>, 'source'>>;
|
||||
[K in keyof WebSocketEvents<C>]: Omit<WebSocketBaseEvent<K, WebSocketEvents<C>[K]>, 'metadata'> & { metadata?: WebSocketEventBaseMetadata };
|
||||
}[keyof WebSocketEvents<C>]
|
||||
|
||||
export type WebSocketEventOf<E, C = undefined> = E extends keyof WebSocketEvents<C>
|
||||
? Omit<WebSocketBaseEvent<E, WebSocketEvents<C>[E]>, 'metadata'> & { metadata?: WebSocketEventBaseMetadata }
|
||||
: never
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { WebSocketBaseEvent, WebSocketEventOf, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { ChatStreamEvent, ContextMessage } from '@proj-airi/stage-ui/types/chat'
|
||||
|
||||
import type { FlowDirection, FlowEntry, SparkNotifyEntryState } from './context-flow-types'
|
||||
@@ -262,7 +262,7 @@ async function sendTestSparkNotify() {
|
||||
metadata: parsed.metadata && typeof parsed.metadata === 'object' ? parsed.metadata : undefined,
|
||||
}
|
||||
|
||||
const simulatedEvent: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']> = {
|
||||
const simulatedEvent: WebSocketEventOf<'spark:notify'> = {
|
||||
type: 'spark:notify',
|
||||
source: 'devtools',
|
||||
data: notify,
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
import type { WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { WebSocketEventOf, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { ChatProvider, ChatProviderWithExtraOptions, EmbedProvider, EmbedProviderWithExtraOptions, SpeechProvider, SpeechProviderWithExtraOptions, TranscriptionProvider, TranscriptionProviderWithExtraOptions } from '@xsai-ext/providers/utils'
|
||||
import type { Message } from '@xsai/shared-chat'
|
||||
|
||||
@@ -57,8 +57,8 @@ export interface SparkNotifyAgentDeps {
|
||||
getSystemPrompt: () => string
|
||||
getProcessing: () => boolean
|
||||
setProcessing: (next: boolean) => void
|
||||
getPending: () => Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>
|
||||
setPending: (next: Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>) => void
|
||||
getPending: () => Array<WebSocketEventOf<'spark:notify'>>
|
||||
setPending: (next: Array<WebSocketEventOf<'spark:notify'>>) => void
|
||||
}
|
||||
|
||||
function getSparkNotifyHandlingAgentInstruction(moduleName: string) {
|
||||
@@ -101,7 +101,7 @@ export const sparkCommandSchema = z.object({
|
||||
export type SparkCommandSchema = z.infer<typeof sparkCommandSchema>
|
||||
|
||||
export function setupAgentSparkNotifyHandler(deps: SparkNotifyAgentDeps) {
|
||||
async function runNotifyAgent(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
async function runNotifyAgent(event: WebSocketEventOf<'spark:notify'>) {
|
||||
const activeProvider = deps.getActiveProvider()
|
||||
const activeModel = deps.getActiveModel()
|
||||
if (!activeProvider || !activeModel) {
|
||||
@@ -225,7 +225,7 @@ export function setupAgentSparkNotifyHandler(deps: SparkNotifyAgentDeps) {
|
||||
} satisfies SparkNotifyResponse
|
||||
}
|
||||
|
||||
async function handle(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
async function handle(event: WebSocketEventOf<'spark:notify'>) {
|
||||
if (event.data.urgency !== 'immediate' && deps.getPending().length > 0) {
|
||||
deps.setPending([...deps.getPending(), event])
|
||||
return undefined
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable style/indent-binary-ops */
|
||||
/* eslint-disable style/operator-linebreak */
|
||||
|
||||
import type { WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { WebSocketEventOf } from '@proj-airi/server-sdk'
|
||||
import type { Store, StoreDefinition } from 'pinia'
|
||||
import type { Mock } from 'vitest'
|
||||
import type { UnwrapRef } from 'vue'
|
||||
@@ -163,7 +163,7 @@ describe('store character-orchestrator', () => {
|
||||
mockedStore(useCharacterStore).onSparkNotifyReactionStreamEnd = mockOnSparkNotifyReactionStreamEnd
|
||||
|
||||
const store = useCharacterOrchestratorStore()
|
||||
const event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']> = {
|
||||
const event: WebSocketEventOf<'spark:notify'> = {
|
||||
type: 'spark:notify',
|
||||
source: 'minecraft',
|
||||
data: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { WebSocketBaseEvent, WebSocketEventOf, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
@@ -22,9 +22,9 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
const modsServerChannelStore = useModsServerChannelStore()
|
||||
|
||||
const processing = ref(false)
|
||||
const pendingNotifies = ref<Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>>([])
|
||||
const pendingNotifies = ref<Array<WebSocketEventOf<'spark:notify'>>>([])
|
||||
const scheduledNotifies = ref<Array<{
|
||||
event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>
|
||||
event: WebSocketEventOf<'spark:notify'>
|
||||
enqueuedAt: number
|
||||
nextRunAt: number
|
||||
attempts: number
|
||||
@@ -52,7 +52,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
setPending: next => pendingNotifies.value = next,
|
||||
})
|
||||
|
||||
function computeNextRunAt(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>, attempts: number) {
|
||||
function computeNextRunAt(event: WebSocketEventOf<'spark:notify'>, attempts: number) {
|
||||
const now = Date.now()
|
||||
const baseDelay = (() => {
|
||||
switch (event.data.urgency) {
|
||||
@@ -74,7 +74,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
pendingNotifies.value = pendingNotifies.value.filter(item => item.data.id !== eventId)
|
||||
}
|
||||
|
||||
function enqueueSparkNotify(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>, options?: { reason?: string, nextRunAt?: number, maxAttempts?: number }) {
|
||||
function enqueueSparkNotify(event: WebSocketEventOf<'spark:notify'>, options?: { reason?: string, nextRunAt?: number, maxAttempts?: number }) {
|
||||
if (!pendingNotifies.value.find(item => item.data.id === event.data.id)) {
|
||||
pendingNotifies.value = [...pendingNotifies.value, event]
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
}]
|
||||
}
|
||||
|
||||
async function processSparkNotify(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
async function processSparkNotify(event: WebSocketEventOf<'spark:notify'>) {
|
||||
const result = await sparkNotifyAgent.handle(event)
|
||||
if (!result?.commands?.length)
|
||||
return result
|
||||
@@ -104,7 +104,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
return result
|
||||
}
|
||||
|
||||
async function handleIncomingSparkNotify(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
async function handleIncomingSparkNotify(event: WebSocketEventOf<'spark:notify'>) {
|
||||
if (event.data.urgency === 'immediate' && !processing.value) {
|
||||
return await processSparkNotify(event)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
return
|
||||
|
||||
for (const task of dueTasks) {
|
||||
const event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']> = {
|
||||
const event: WebSocketEventOf<'spark:notify'> = {
|
||||
type: 'spark:notify',
|
||||
source: 'character:task-scheduler',
|
||||
data: {
|
||||
|
||||
@@ -101,6 +101,7 @@ export const useContextBridgeStore = defineStore('mods:api:context-bridge', () =
|
||||
input: {
|
||||
type: 'input:text',
|
||||
data: {
|
||||
...event.data,
|
||||
text,
|
||||
textRaw,
|
||||
overrides,
|
||||
@@ -165,8 +166,8 @@ export const useContextBridgeStore = defineStore('mods:api:context-bridge', () =
|
||||
serverChannelStore.send({
|
||||
type: 'output:gen-ai:chat:message',
|
||||
data: {
|
||||
...context.input?.data,
|
||||
message,
|
||||
...context.input?.metadata?.source,
|
||||
'stage-web': isStageWeb(),
|
||||
'stage-tamagotchi': isStageTamagotchi(),
|
||||
'gen-ai:chat': {
|
||||
@@ -183,8 +184,9 @@ export const useContextBridgeStore = defineStore('mods:api:context-bridge', () =
|
||||
serverChannelStore.send({
|
||||
type: 'output:gen-ai:chat:complete',
|
||||
data: {
|
||||
...context.input?.data,
|
||||
'message': chat.output,
|
||||
...context.input?.metadata?.source,
|
||||
// TODO: tool calls should be captured properly
|
||||
'toolCalls': [],
|
||||
'stage-web': isStageWeb(),
|
||||
'stage-tamagotchi': isStageTamagotchi(),
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
generic="InputType extends 'number' | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))"
|
||||
>
|
||||
import { Input } from '../input'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
@@ -6,14 +10,14 @@ const props = withDefaults(defineProps<{
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
type?: string
|
||||
type?: InputType
|
||||
inputClass?: string
|
||||
singleLine?: boolean
|
||||
}>(), {
|
||||
singleLine: true,
|
||||
})
|
||||
|
||||
const modelValue = defineModel<string | number>({ required: false })
|
||||
const modelValue = defineModel<T>({ required: false })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -47,8 +51,8 @@ const modelValue = defineModel<string | number>({ required: false })
|
||||
:class="props.inputClass"
|
||||
/>
|
||||
<textarea
|
||||
v-else
|
||||
v-model="modelValue"
|
||||
v-else-if="props.type !== 'number'"
|
||||
v-model="modelValue as string | undefined"
|
||||
:type="props.type"
|
||||
:placeholder="props.placeholder"
|
||||
:class="[
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
generic="InputType extends 'number' | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))"
|
||||
>
|
||||
// Define button variants for better type safety and maintainability
|
||||
type InputVariant = 'primary' | 'secondary' | 'primary-dimmed'
|
||||
|
||||
@@ -8,7 +12,7 @@ type InputTheme = 'default'
|
||||
type InputSize = 'sm' | 'md' | 'lg'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
type?: string
|
||||
type?: InputType
|
||||
variant?: InputVariant // Button style variant
|
||||
size?: InputSize // Button size variant
|
||||
theme?: InputTheme // Button theme
|
||||
@@ -18,7 +22,7 @@ const props = withDefaults(defineProps<{
|
||||
theme: 'default',
|
||||
})
|
||||
|
||||
const modelValue = defineModel<string | number>({ required: false })
|
||||
const modelValue = defineModel<T>({ required: false })
|
||||
|
||||
const variantClasses: Record<InputVariant, Record<InputTheme, {
|
||||
default: string[]
|
||||
|
||||
Generated
+4
-1
@@ -124,7 +124,7 @@ catalogs:
|
||||
specifier: ^0.9.0
|
||||
version: 0.9.0
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
specifier: 5.1.6
|
||||
version: 5.1.6
|
||||
ofetch:
|
||||
specifier: ^1.5.1
|
||||
@@ -1896,6 +1896,9 @@ importers:
|
||||
listhen:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
nanoid:
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.6
|
||||
|
||||
packages/server-sdk:
|
||||
dependencies:
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ catalog:
|
||||
injeca: ^0.1.5
|
||||
is-network-error: ^1.3.0
|
||||
nano-staged: ^0.9.0
|
||||
nanoid: ^5.1.6
|
||||
nanoid: 5.1.6
|
||||
ofetch: ^1.5.1
|
||||
posthog-js: 1.306.1
|
||||
splitpanes: ^4.0.4
|
||||
|
||||
@@ -157,13 +157,10 @@ export class DiscordAdapter {
|
||||
// Handle output from AIRI system (IA response)
|
||||
this.airiClient.onEvent('output:gen-ai:chat:message', async (event) => {
|
||||
try {
|
||||
const { message, discord } = event.data as {
|
||||
message: { content: string }
|
||||
discord?: { channelId: string }
|
||||
}
|
||||
|
||||
if (discord?.channelId) {
|
||||
const channel = await this.discordClient.channels.fetch(discord.channelId)
|
||||
const message = (event.data as { message?: { content: string } }).message
|
||||
const discordContext = (event.data)['gen-ai:chat'].input.data.discord
|
||||
if (message?.content && discordContext?.channelId) {
|
||||
const channel = await this.discordClient.channels.fetch(discordContext.channelId)
|
||||
if (channel?.isTextBased() && 'send' in channel && typeof channel.send === 'function') {
|
||||
await channel.send(message.content)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user