87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import type { AiAgentMessage } from './aiAgentConversation'
|
|
|
|
interface BrowserNodePreview {
|
|
nodeId: string
|
|
text: string
|
|
}
|
|
|
|
export interface BrowserObservation {
|
|
documentId?: string
|
|
url: string
|
|
totalCharacters?: number
|
|
totalNodes?: number
|
|
nodes: BrowserNodePreview[]
|
|
status: 'pending' | 'done' | 'error'
|
|
}
|
|
|
|
function parseObject(value?: string): Record<string, unknown> | null {
|
|
if (!value) return null
|
|
try {
|
|
const parsed: unknown = JSON.parse(value)
|
|
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
? parsed as Record<string, unknown>
|
|
: null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function stringField(object: Record<string, unknown> | null, ...keys: string[]): string | undefined {
|
|
for (const key of keys) {
|
|
const value = object?.[key]
|
|
if (typeof value === 'string' && value.trim()) return value.trim()
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function numberField(object: Record<string, unknown> | null, ...keys: string[]): number | undefined {
|
|
for (const key of keys) {
|
|
const value = object?.[key]
|
|
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function browserAction(action: AiAgentMessage['actions'][number]): boolean {
|
|
if (action.tool === 'read_web_page' || action.tool === 'read_web_page_nodes') return true
|
|
const input = parseObject(action.input)
|
|
if (action.tool !== 'magic_brush') return false
|
|
const steps = input?.steps
|
|
return Array.isArray(steps) && steps.some((step) => (
|
|
typeof step === 'object'
|
|
&& step !== null
|
|
&& ['read_web_page', 'read_web_page_nodes'].includes(String((step as Record<string, unknown>).tool))
|
|
))
|
|
}
|
|
|
|
function previewNodes(output: Record<string, unknown> | null): BrowserNodePreview[] {
|
|
const raw = output?.node_previews ?? output?.nodes
|
|
if (!Array.isArray(raw)) return []
|
|
return raw.flatMap((item) => {
|
|
if (typeof item !== 'object' || item === null) return []
|
|
const node = item as Record<string, unknown>
|
|
const nodeId = stringField(node, 'node_id', 'nodeId')
|
|
const text = stringField(node, 'text')
|
|
return nodeId && text ? [{ nodeId, text }] : []
|
|
})
|
|
}
|
|
|
|
export function latestBrowserObservation(messages: AiAgentMessage[]): BrowserObservation | null {
|
|
const actions = messages.flatMap((message) => message.actions).filter(browserAction)
|
|
const action = actions.at(-1)
|
|
if (!action) return null
|
|
|
|
const input = parseObject(action.input)
|
|
const output = parseObject(action.output)
|
|
const url = stringField(output, 'url') ?? stringField(input, 'url')
|
|
if (!url) return null
|
|
|
|
return {
|
|
documentId: stringField(output, 'document_id', 'documentId'),
|
|
url,
|
|
totalCharacters: numberField(output, 'total_characters', 'totalCharacters'),
|
|
totalNodes: numberField(output, 'total_nodes', 'totalNodes'),
|
|
nodes: previewNodes(output),
|
|
status: action.status,
|
|
}
|
|
}
|