@electric-ax/agents 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoint.js +5 -3
- package/dist/index.cjs +5 -3
- package/dist/index.js +5 -3
- package/docs/entities/agents/horton.md +89 -0
- package/docs/entities/agents/worker.md +102 -0
- package/docs/entities/patterns/blackboard.md +111 -0
- package/docs/entities/patterns/dispatcher.md +77 -0
- package/docs/entities/patterns/manager-worker.md +127 -0
- package/docs/entities/patterns/map-reduce.md +81 -0
- package/docs/entities/patterns/pipeline.md +101 -0
- package/docs/entities/patterns/reactive-observers.md +125 -0
- package/docs/examples/mega-draw.md +106 -0
- package/docs/examples/playground.md +46 -0
- package/docs/index.md +208 -0
- package/docs/quickstart.md +201 -0
- package/docs/reference/agent-config.md +82 -0
- package/docs/reference/agent-tool.md +58 -0
- package/docs/reference/built-in-collections.md +334 -0
- package/docs/reference/cli.md +238 -0
- package/docs/reference/entity-definition.md +57 -0
- package/docs/reference/entity-handle.md +63 -0
- package/docs/reference/entity-registry.md +73 -0
- package/docs/reference/handler-context.md +108 -0
- package/docs/reference/runtime-handler.md +136 -0
- package/docs/reference/shared-state-handle.md +74 -0
- package/docs/reference/state-collection-proxy.md +41 -0
- package/docs/reference/wake-event.md +132 -0
- package/docs/usage/app-setup.md +165 -0
- package/docs/usage/clients-and-react.md +191 -0
- package/docs/usage/configuring-the-agent.md +136 -0
- package/docs/usage/context-composition.md +204 -0
- package/docs/usage/defining-entities.md +181 -0
- package/docs/usage/defining-tools.md +229 -0
- package/docs/usage/embedded-builtins.md +180 -0
- package/docs/usage/managing-state.md +93 -0
- package/docs/usage/overview.md +284 -0
- package/docs/usage/programmatic-runtime-client.md +216 -0
- package/docs/usage/shared-state.md +169 -0
- package/docs/usage/spawning-and-coordinating.md +165 -0
- package/docs/usage/testing.md +76 -0
- package/docs/usage/waking-entities.md +148 -0
- package/docs/usage/writing-handlers.md +267 -0
- package/package.json +2 -1
- package/skills/quickstart/scaffold/package.json +16 -3
- package/skills/quickstart/scaffold/tsconfig.json +8 -3
- package/skills/quickstart/scaffold/vite.config.ts +21 -0
- package/skills/quickstart/scaffold-ui/index.html +12 -0
- package/skills/quickstart/scaffold-ui/main.tsx +235 -0
- package/skills/quickstart.md +244 -334
|
@@ -4,14 +4,27 @@
|
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "tsx server.ts",
|
|
7
|
-
"dev": "tsx --watch server.ts"
|
|
7
|
+
"dev": "tsx --watch server.ts",
|
|
8
|
+
"dev:server": "tsx --watch server.ts",
|
|
9
|
+
"dev:ui": "vite",
|
|
10
|
+
"dev:all": "npm run dev:server & npm run dev:ui"
|
|
8
11
|
},
|
|
9
12
|
"dependencies": {
|
|
10
13
|
"@electric-ax/agents-runtime": "latest",
|
|
11
|
-
"@
|
|
14
|
+
"@radix-ui/themes": "^3.3.0",
|
|
15
|
+
"@sinclair/typebox": "^0.34.49",
|
|
16
|
+
"@tanstack/db": "^0.6.0",
|
|
17
|
+
"@tanstack/react-db": "^0.1.78",
|
|
18
|
+
"react": "^19.2.4",
|
|
19
|
+
"react-dom": "^19.2.4",
|
|
20
|
+
"streamdown": "^2.5.0"
|
|
12
21
|
},
|
|
13
22
|
"devDependencies": {
|
|
23
|
+
"@types/react": "^19.2.14",
|
|
24
|
+
"@types/react-dom": "^19.2.3",
|
|
25
|
+
"@vitejs/plugin-react": "^5.2.0",
|
|
14
26
|
"tsx": "^4.19.0",
|
|
15
|
-
"typescript": "^5.7.0"
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vite": "^7.2.4"
|
|
16
29
|
}
|
|
17
30
|
}
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
"target": "ES2022",
|
|
4
4
|
"module": "ESNext",
|
|
5
5
|
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
6
7
|
"strict": true,
|
|
7
8
|
"esModuleInterop": true,
|
|
8
9
|
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
11
|
"resolveJsonModule": true,
|
|
10
|
-
"
|
|
11
|
-
"noEmit": true
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"paths": {
|
|
15
|
+
"@tanstack/db": ["./node_modules/@tanstack/db"]
|
|
16
|
+
}
|
|
12
17
|
},
|
|
13
|
-
"include": ["**/*.ts"],
|
|
18
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
14
19
|
"exclude": ["node_modules"]
|
|
15
20
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
import react from '@vitejs/plugin-react'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
root: `ui`,
|
|
7
|
+
plugins: [react()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
'@tanstack/db': path.resolve(
|
|
11
|
+
import.meta.dirname,
|
|
12
|
+
`node_modules/@tanstack/db`
|
|
13
|
+
),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
server: {
|
|
17
|
+
port: 5175,
|
|
18
|
+
proxy: { '/api': `http://localhost:3000` },
|
|
19
|
+
},
|
|
20
|
+
build: { outDir: `../dist`, emptyOutDir: true },
|
|
21
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Perspectives Analyzer</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { createAgentsClient, entity } from '@electric-ax/agents-runtime'
|
|
4
|
+
import { useChat } from '@electric-ax/agents-runtime/react'
|
|
5
|
+
import {
|
|
6
|
+
Theme,
|
|
7
|
+
Box,
|
|
8
|
+
Flex,
|
|
9
|
+
Heading,
|
|
10
|
+
Text,
|
|
11
|
+
TextField,
|
|
12
|
+
Button,
|
|
13
|
+
Card,
|
|
14
|
+
} from '@radix-ui/themes'
|
|
15
|
+
import { Streamdown } from 'streamdown'
|
|
16
|
+
import '@radix-ui/themes/styles.css'
|
|
17
|
+
import 'streamdown/styles.css'
|
|
18
|
+
import type { EntityStreamDB } from '@electric-ax/agents-runtime'
|
|
19
|
+
|
|
20
|
+
const AGENTS_URL = `http://localhost:4437`
|
|
21
|
+
|
|
22
|
+
const AGENT_COLORS: Record<string, { bg: string; border: string }> = {
|
|
23
|
+
analyser: { bg: `var(--blue-3)`, border: `var(--blue-6)` },
|
|
24
|
+
optimist: { bg: `var(--green-3)`, border: `var(--green-6)` },
|
|
25
|
+
critic: { bg: `var(--red-3)`, border: `var(--red-6)` },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function useEntityDb(url: string | null, retryMs = 0) {
|
|
29
|
+
const [db, setDb] = useState<EntityStreamDB | null>(null)
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!url) {
|
|
33
|
+
setDb(null)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
let cancelled = false
|
|
37
|
+
let observedDb: EntityStreamDB | null = null
|
|
38
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
39
|
+
|
|
40
|
+
const connect = () => {
|
|
41
|
+
const client = createAgentsClient({ baseUrl: AGENTS_URL })
|
|
42
|
+
client.observe(entity(url)).then(
|
|
43
|
+
(observed) => {
|
|
44
|
+
observedDb = observed as EntityStreamDB
|
|
45
|
+
if (cancelled) {
|
|
46
|
+
observedDb.close()
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
setDb(observedDb)
|
|
50
|
+
},
|
|
51
|
+
() => {
|
|
52
|
+
if (!cancelled && retryMs > 0) {
|
|
53
|
+
timer = setTimeout(connect, retryMs)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
connect()
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true
|
|
62
|
+
if (timer) clearTimeout(timer)
|
|
63
|
+
observedDb?.close()
|
|
64
|
+
}
|
|
65
|
+
}, [url, retryMs])
|
|
66
|
+
|
|
67
|
+
return db
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface AgentMessage {
|
|
71
|
+
agent: string
|
|
72
|
+
text: string
|
|
73
|
+
isStreaming: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function useAgentMessages(
|
|
77
|
+
url: string | null,
|
|
78
|
+
agent: string,
|
|
79
|
+
retryMs = 0
|
|
80
|
+
): AgentMessage[] {
|
|
81
|
+
const db = useEntityDb(url, retryMs)
|
|
82
|
+
const chat = useChat(db)
|
|
83
|
+
|
|
84
|
+
return chat.runs.flatMap((r, ri) =>
|
|
85
|
+
r.texts
|
|
86
|
+
.filter((t) => t.text.trim().length > 0)
|
|
87
|
+
.map((t, ti) => ({
|
|
88
|
+
agent,
|
|
89
|
+
text: t.text,
|
|
90
|
+
isStreaming:
|
|
91
|
+
chat.state === `working` &&
|
|
92
|
+
ri === chat.runs.length - 1 &&
|
|
93
|
+
ti === r.texts.length - 1,
|
|
94
|
+
}))
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function MessageBubble({ msg }: { msg: AgentMessage }) {
|
|
99
|
+
const colors = AGENT_COLORS[msg.agent] ?? {
|
|
100
|
+
bg: `var(--gray-3)`,
|
|
101
|
+
border: `var(--gray-6)`,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Card
|
|
106
|
+
size="1"
|
|
107
|
+
style={{
|
|
108
|
+
background: colors.bg,
|
|
109
|
+
borderLeft: `3px solid ${colors.border}`,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Text size="1" weight="bold" style={{ textTransform: `capitalize` }}>
|
|
113
|
+
{msg.agent}
|
|
114
|
+
</Text>
|
|
115
|
+
<Box mt="1" style={{ fontSize: `var(--font-size-2)` }}>
|
|
116
|
+
<Streamdown isAnimating={msg.isStreaming} controls={false}>
|
|
117
|
+
{msg.text}
|
|
118
|
+
</Streamdown>
|
|
119
|
+
</Box>
|
|
120
|
+
</Card>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function App() {
|
|
125
|
+
const [question, setQuestion] = useState(``)
|
|
126
|
+
const [urls, setUrls] = useState<{
|
|
127
|
+
entityUrl: string
|
|
128
|
+
optimistUrl: string
|
|
129
|
+
criticUrl: string
|
|
130
|
+
} | null>(null)
|
|
131
|
+
const [loading, setLoading] = useState(false)
|
|
132
|
+
const bottomRef = useRef<HTMLDivElement>(null)
|
|
133
|
+
|
|
134
|
+
const analyserMessages = useAgentMessages(urls?.entityUrl ?? null, `analyser`)
|
|
135
|
+
const optimistMessages = useAgentMessages(
|
|
136
|
+
urls?.optimistUrl ?? null,
|
|
137
|
+
`optimist`,
|
|
138
|
+
2000
|
|
139
|
+
)
|
|
140
|
+
const criticMessages = useAgentMessages(
|
|
141
|
+
urls?.criticUrl ?? null,
|
|
142
|
+
`critic`,
|
|
143
|
+
2000
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const allMessages = [
|
|
147
|
+
...analyserMessages,
|
|
148
|
+
...optimistMessages,
|
|
149
|
+
...criticMessages,
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const el = bottomRef.current?.parentElement
|
|
154
|
+
if (!el) return
|
|
155
|
+
const observer = new MutationObserver(() => {
|
|
156
|
+
window.scrollTo({ top: document.body.scrollHeight, behavior: `smooth` })
|
|
157
|
+
})
|
|
158
|
+
observer.observe(el, {
|
|
159
|
+
childList: true,
|
|
160
|
+
subtree: true,
|
|
161
|
+
characterData: true,
|
|
162
|
+
})
|
|
163
|
+
return () => observer.disconnect()
|
|
164
|
+
}, [urls])
|
|
165
|
+
|
|
166
|
+
const handleAnalyze = async () => {
|
|
167
|
+
if (!question.trim()) return
|
|
168
|
+
setLoading(true)
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(`/api/analyze`, {
|
|
171
|
+
method: `POST`,
|
|
172
|
+
headers: { 'Content-Type': `application/json` },
|
|
173
|
+
body: JSON.stringify({ question }),
|
|
174
|
+
})
|
|
175
|
+
const data = (await res.json()) as {
|
|
176
|
+
entityUrl: string
|
|
177
|
+
optimistUrl: string
|
|
178
|
+
criticUrl: string
|
|
179
|
+
}
|
|
180
|
+
setUrls(data)
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error(`Analyze failed:`, err)
|
|
183
|
+
} finally {
|
|
184
|
+
setLoading(false)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Theme appearance="light" accentColor="blue" radius="medium">
|
|
190
|
+
<style>{`@keyframes blink { 50% { opacity: 0; } }`}</style>
|
|
191
|
+
<Box maxWidth="700px" mx="auto" p="5">
|
|
192
|
+
<Heading size="6" mb="1">
|
|
193
|
+
Perspectives Analyzer
|
|
194
|
+
</Heading>
|
|
195
|
+
<Text size="2" color="gray" mb="5" as="p">
|
|
196
|
+
Ask a question and get two perspectives — an optimist and a critic —
|
|
197
|
+
then a balanced analysis.
|
|
198
|
+
</Text>
|
|
199
|
+
|
|
200
|
+
<Flex gap="2" mb="5">
|
|
201
|
+
<Box flexGrow="1">
|
|
202
|
+
<TextField.Root
|
|
203
|
+
size="3"
|
|
204
|
+
placeholder="Enter a question to analyze..."
|
|
205
|
+
value={question}
|
|
206
|
+
onChange={(e) => setQuestion(e.target.value)}
|
|
207
|
+
onKeyDown={(e) => e.key === `Enter` && handleAnalyze()}
|
|
208
|
+
/>
|
|
209
|
+
</Box>
|
|
210
|
+
<Button
|
|
211
|
+
size="3"
|
|
212
|
+
onClick={handleAnalyze}
|
|
213
|
+
disabled={loading || !question.trim()}
|
|
214
|
+
>
|
|
215
|
+
{loading ? `Analyzing...` : `Analyze`}
|
|
216
|
+
</Button>
|
|
217
|
+
</Flex>
|
|
218
|
+
|
|
219
|
+
<Flex direction="column" gap="3">
|
|
220
|
+
{urls && allMessages.length === 0 && (
|
|
221
|
+
<Text color="gray" size="2">
|
|
222
|
+
Waiting for agents to respond...
|
|
223
|
+
</Text>
|
|
224
|
+
)}
|
|
225
|
+
{allMessages.map((msg, i) => (
|
|
226
|
+
<MessageBubble key={`${msg.agent}-${i}`} msg={msg} />
|
|
227
|
+
))}
|
|
228
|
+
<div ref={bottomRef} />
|
|
229
|
+
</Flex>
|
|
230
|
+
</Box>
|
|
231
|
+
</Theme>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
createRoot(document.getElementById(`root`)!).render(<App />)
|