create-theokit 1.0.10 → 1.0.12
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/package.json
CHANGED
|
@@ -12,6 +12,7 @@ This project includes TheoKit-aware skills that activate automatically when you
|
|
|
12
12
|
| theokit-agents | `**/*agent*`, `**/*tool*`, `**/*Agent*`, `**/*Tool*` | @Agent, @Tool, @Toolbox decorators, LLM integration |
|
|
13
13
|
| theokit-database | `**/*schema*`, `**/*db*`, `**/drizzle*`, `**/*migration*`, `**/*seed*` | Drizzle ORM, SQLite, schema patterns, migrations |
|
|
14
14
|
| theokit-frontend | `app/**` | File-based routing, layouts, theoFetch, useAgentStream |
|
|
15
|
+
| theokit-ui | `app/**`, `**/*Chat*`, `**/*Sidebar*`, `**/*Theme*` | @theokit/ui components: ChatThread, ChatMessage, CodeBlock, Sidebar, theming |
|
|
15
16
|
| theokit-config | `theo.config*`, `**/*config*` | defineConfig options, plugins, security, storage |
|
|
16
17
|
|
|
17
18
|
### Settings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: theokit-agents
|
|
3
|
-
description: TheoKit agent/LLM integration —
|
|
3
|
+
description: TheoKit agent/LLM integration — two streaming surfaces (decorator vs manual), @Tool, @Toolbox, memory
|
|
4
4
|
user-invocable: false
|
|
5
5
|
paths:
|
|
6
6
|
- "**/*agent*"
|
|
@@ -13,31 +13,51 @@ paths:
|
|
|
13
13
|
|
|
14
14
|
# TheoKit Agents & Tools
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Two Streaming Surfaces — pick one per endpoint
|
|
17
|
+
|
|
18
|
+
TheoKit ships two ways to create agent endpoints. Use ONE per endpoint, not both.
|
|
19
|
+
|
|
20
|
+
| Surface | Server | Events | Client | When to use |
|
|
21
|
+
|---------|--------|--------|--------|-------------|
|
|
22
|
+
| **Manual** (recommended for most apps) | `defineAgentEndpoint({ handler: async function* })` | `AgentEvent` (Message, ToolCall, Result, Error) | `useAgentStream()` / `consumeAgentStream()` | Full control over the LLM loop — you write the generator |
|
|
23
|
+
| **Decorator** (`@theokit/agents`) | `@Agent` class → auto-generated route | `AgentStreamEvent` (TextDelta, ToolCall, Done...) | (same client hooks work) | Declarative — framework manages LLM loop via `@MainLoop` |
|
|
24
|
+
|
|
25
|
+
### Surface 1: Manual (defineAgentEndpoint)
|
|
17
26
|
|
|
18
27
|
```typescript
|
|
19
|
-
|
|
28
|
+
// server/routes/agents/assistant.ts
|
|
29
|
+
import { defineAgentEndpoint } from 'theokit/server/define'
|
|
30
|
+
import type { AgentEvent } from 'theokit'
|
|
31
|
+
|
|
32
|
+
export const POST = defineAgentEndpoint({
|
|
33
|
+
handler: async function* ({ body }): AsyncGenerator<AgentEvent> {
|
|
34
|
+
// You control the LLM loop
|
|
35
|
+
yield { type: 'message', content: 'Thinking...' }
|
|
36
|
+
const result = await callLLM(body.message)
|
|
37
|
+
yield { type: 'message', content: result }
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Surface 2: Decorator (@Agent)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// server/agents/assistant.agent.ts
|
|
46
|
+
import { Agent, MainLoop, Tool, Toolbox } from '@theokit/agents'
|
|
20
47
|
|
|
21
48
|
@Agent({
|
|
22
|
-
model: 'openai/gpt-4o-mini',
|
|
23
|
-
systemPrompt: 'You are a helpful
|
|
49
|
+
model: 'openai/gpt-4o-mini',
|
|
50
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
24
51
|
})
|
|
25
|
-
@Memory({ provider: 'built-in', scope: 'per-user' }) // Optional
|
|
26
|
-
@Budget({ maxCostUsd: 1.00, window: 'daily' }) // Optional
|
|
27
52
|
export class AssistantAgent {
|
|
28
53
|
@MainLoop({ strategy: 'react', maxIterations: 5 })
|
|
29
54
|
async run() {
|
|
30
|
-
// Framework handles the LLM loop
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Hook('before:llm-call')
|
|
34
|
-
async onBeforeLLM(ctx) {
|
|
35
|
-
// Intercept before each LLM call
|
|
55
|
+
// Framework handles the LLM loop via @theokit/sdk
|
|
36
56
|
}
|
|
37
57
|
}
|
|
38
58
|
```
|
|
39
59
|
|
|
40
|
-
Convention: `AssistantAgent`
|
|
60
|
+
Convention: `AssistantAgent` → `POST /api/agents/assistant`
|
|
41
61
|
|
|
42
62
|
## @Tool Decorator
|
|
43
63
|
|
|
@@ -50,56 +70,63 @@ export class TaskTools {
|
|
|
50
70
|
@Tool({
|
|
51
71
|
name: 'list_tasks',
|
|
52
72
|
description: 'List all tasks, optionally filtered by status',
|
|
53
|
-
input: z.object({
|
|
54
|
-
done: z.boolean().optional(),
|
|
55
|
-
}),
|
|
73
|
+
input: z.object({ done: z.boolean().optional() }),
|
|
56
74
|
})
|
|
57
75
|
async listTasks({ done }: { done?: boolean }) {
|
|
58
|
-
|
|
59
|
-
return done !== undefined ? all.filter(t => t.done === done) : all
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@Tool({
|
|
63
|
-
name: 'create_task',
|
|
64
|
-
description: 'Create a new task with a title',
|
|
65
|
-
input: z.object({
|
|
66
|
-
title: z.string().min(1),
|
|
67
|
-
}),
|
|
68
|
-
})
|
|
69
|
-
async createTask({ title }: { title: string }) {
|
|
70
|
-
return db.insert(tasks).values({ title }).returning().get()
|
|
76
|
+
return db.select().from(tasks).all()
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
```
|
|
74
80
|
|
|
75
|
-
##
|
|
81
|
+
## Client — useAgentStream (React hook)
|
|
82
|
+
|
|
83
|
+
Works with BOTH surfaces. Transport: `fetch` POST + `ReadableStream` (SSE).
|
|
76
84
|
|
|
77
85
|
```typescript
|
|
78
86
|
import { useAgentStream } from 'theokit/client'
|
|
79
87
|
|
|
80
88
|
function ChatUI() {
|
|
81
|
-
const { status, events, send } = useAgentStream('/api/agents/assistant')
|
|
89
|
+
const { status, events, send, reset } = useAgentStream('/api/agents/assistant')
|
|
82
90
|
|
|
83
91
|
return (
|
|
84
92
|
<div>
|
|
85
|
-
{
|
|
93
|
+
{status === 'streaming' && <p>Thinking...</p>}
|
|
94
|
+
{events.map(e => (
|
|
95
|
+
<div key={e.id}>
|
|
96
|
+
{e.type === 'message' && <p>{e.content}</p>}
|
|
97
|
+
{e.type === 'tool_call' && <p>Using tool: {e.name}</p>}
|
|
98
|
+
</div>
|
|
99
|
+
))}
|
|
86
100
|
<button onClick={() => send({ message: 'Hello' })}>Send</button>
|
|
87
101
|
</div>
|
|
88
102
|
)
|
|
89
103
|
}
|
|
90
104
|
```
|
|
91
105
|
|
|
106
|
+
### Non-React: consumeAgentStream
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { consumeAgentStream } from 'theokit/client'
|
|
110
|
+
|
|
111
|
+
const stream = consumeAgentStream('/api/agents/assistant', { body: { message: 'Hi' } })
|
|
112
|
+
for await (const event of stream) {
|
|
113
|
+
console.log(event.type, event.content)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
92
117
|
## Rules
|
|
93
118
|
|
|
94
119
|
- Tool `name` and `description` are ALWAYS explicit — never inferred from method names (G4)
|
|
95
120
|
- Tool `input` uses Zod schema — same pattern as defineRoute
|
|
96
121
|
- `@UseGuards()` works on agents (shared with HTTP pipeline)
|
|
97
|
-
- `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings
|
|
122
|
+
- `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings)
|
|
98
123
|
- Agent runtime is `@theokit/sdk` — NEVER call LLM APIs directly via fetch
|
|
124
|
+
- Pick ONE surface per endpoint — don't mix defineAgentEndpoint with @Agent for the same route
|
|
99
125
|
|
|
100
126
|
## Anti-patterns
|
|
101
127
|
|
|
102
|
-
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use @Agent
|
|
128
|
+
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use @Agent or defineAgentEndpoint
|
|
103
129
|
- NEVER reimplement tool calling loop — the SDK handles it
|
|
104
|
-
- NEVER store conversations manually — use @Memory
|
|
130
|
+
- NEVER store conversations manually — use @Memory (decorator) or SDK persistence
|
|
105
131
|
- NEVER infer tool capability from method name — always provide explicit `name` + `description`
|
|
132
|
+
- NEVER mix both surfaces for the same endpoint — pick manual OR decorator
|
|
@@ -43,18 +43,21 @@ import { createAppClient } from 'theokit/client'
|
|
|
43
43
|
|
|
44
44
|
const client = createAppClient()
|
|
45
45
|
|
|
46
|
-
// Proxy pattern — method names match route structure
|
|
47
46
|
const tasks = await client.tasks.GET()
|
|
48
47
|
const task = await client.tasks[':id'].GET({ params: { id: 1 } })
|
|
49
48
|
const created = await client.tasks.POST({ body: { title: 'New' } })
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
## Agent Streaming
|
|
51
|
+
## Agent Streaming
|
|
52
|
+
|
|
53
|
+
Three client APIs, all from `theokit/client`:
|
|
54
|
+
|
|
55
|
+
### useAgentStream (React hook — most common)
|
|
53
56
|
|
|
54
57
|
```typescript
|
|
55
58
|
import { useAgentStream } from 'theokit/client'
|
|
56
59
|
|
|
57
|
-
function
|
|
60
|
+
function ChatUI() {
|
|
58
61
|
const { status, events, send, reset } = useAgentStream('/api/agents/assistant')
|
|
59
62
|
|
|
60
63
|
return (
|
|
@@ -66,12 +69,34 @@ function ChatComponent() {
|
|
|
66
69
|
{event.type === 'tool_call' && <p>Using tool: {event.name}</p>}
|
|
67
70
|
</div>
|
|
68
71
|
))}
|
|
69
|
-
<
|
|
72
|
+
<button onClick={() => send({ message: 'Hello' })}>Send</button>
|
|
70
73
|
</div>
|
|
71
74
|
)
|
|
72
75
|
}
|
|
73
76
|
```
|
|
74
77
|
|
|
78
|
+
### consumeAgentStream (non-React, async iterable)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { consumeAgentStream } from 'theokit/client'
|
|
82
|
+
|
|
83
|
+
const stream = consumeAgentStream('/api/agents/assistant', {
|
|
84
|
+
body: { message: 'Hello' },
|
|
85
|
+
})
|
|
86
|
+
for await (const event of stream) {
|
|
87
|
+
console.log(event.type, event.content)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### parseSSEChunk (low-level SSE parser)
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { parseSSEChunk } from 'theokit/client'
|
|
95
|
+
|
|
96
|
+
// Parse a single SSE line into an AgentEvent (or null)
|
|
97
|
+
const event = parseSSEChunk('data: {"type":"message","content":"Hello"}')
|
|
98
|
+
```
|
|
99
|
+
|
|
75
100
|
## Path Aliases
|
|
76
101
|
|
|
77
102
|
```typescript
|
|
@@ -83,7 +108,7 @@ Configured in `tsconfig.json` — works in both server and app code.
|
|
|
83
108
|
|
|
84
109
|
## Anti-patterns
|
|
85
110
|
|
|
86
|
-
- NEVER use `fetch('/api/...')`
|
|
111
|
+
- NEVER use raw `fetch('/api/...')` — use `theoFetch` for type safety
|
|
87
112
|
- NEVER create pages outside `app/` — they won't be discovered by the router
|
|
88
113
|
- NEVER import server code directly in `app/` — use theoFetch or server actions
|
|
89
114
|
- NEVER use `useEffect` + `fetch` for data loading — use theoFetch or useAgentStream
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theokit-ui
|
|
3
|
+
description: "@theokit/ui component library — chat UI (ChatThread, ChatMessage, ChatComposer, CodeBlock), theming, providers, sidebar"
|
|
4
|
+
user-invocable: false
|
|
5
|
+
paths:
|
|
6
|
+
- "app/**"
|
|
7
|
+
- "**/*Chat*"
|
|
8
|
+
- "**/*chat*"
|
|
9
|
+
- "**/*Sidebar*"
|
|
10
|
+
- "**/*sidebar*"
|
|
11
|
+
- "**/*theme*"
|
|
12
|
+
- "**/*Theme*"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# @theokit/ui — AI Chat Component Library
|
|
16
|
+
|
|
17
|
+
`@theokit/ui` is an optional peer dependency. If installed, it provides ready-made components for chat UIs, theming, and layout. **Never build custom equivalents** of components `@theokit/ui` provides.
|
|
18
|
+
|
|
19
|
+
## Package Identity
|
|
20
|
+
|
|
21
|
+
The published package is `@theokit/ui` (NOT `@usetheo/ui` — that was the old name).
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Install from npm
|
|
25
|
+
npm install @theokit/ui
|
|
26
|
+
|
|
27
|
+
# Or link from source (development)
|
|
28
|
+
npm link ../theo-ui # if you have the source repo
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Provider Setup (required before using any component)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// app/layout.tsx
|
|
35
|
+
import '@theokit/ui/styles.css'
|
|
36
|
+
import { TheoUIProvider, ThemeProvider } from '@theokit/ui'
|
|
37
|
+
|
|
38
|
+
export default function Layout({ children }) {
|
|
39
|
+
return (
|
|
40
|
+
<TheoUIProvider>
|
|
41
|
+
<ThemeProvider>
|
|
42
|
+
{children}
|
|
43
|
+
</ThemeProvider>
|
|
44
|
+
</TheoUIProvider>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Chat Components
|
|
50
|
+
|
|
51
|
+
### Full Chat Page (typical assembly)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import {
|
|
55
|
+
PageShell,
|
|
56
|
+
Sidebar,
|
|
57
|
+
SessionListItem,
|
|
58
|
+
ChatThread,
|
|
59
|
+
ChatMessage,
|
|
60
|
+
ChatMessageContent,
|
|
61
|
+
ChatComposer,
|
|
62
|
+
} from '@theokit/ui'
|
|
63
|
+
import { useAgentStream } from 'theokit/client'
|
|
64
|
+
|
|
65
|
+
function ChatPage() {
|
|
66
|
+
const { status, events, send } = useAgentStream('/api/agents/assistant')
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<PageShell sidebar={
|
|
70
|
+
<Sidebar>
|
|
71
|
+
{sessions.map(s => (
|
|
72
|
+
<SessionListItem key={s.id} title={s.title} onClick={() => select(s)} />
|
|
73
|
+
))}
|
|
74
|
+
</Sidebar>
|
|
75
|
+
}>
|
|
76
|
+
<ChatThread>
|
|
77
|
+
{messages.map(m => (
|
|
78
|
+
<ChatMessage key={m.id} role={m.role}>
|
|
79
|
+
<ChatMessageContent markdown={m.content} />
|
|
80
|
+
</ChatMessage>
|
|
81
|
+
))}
|
|
82
|
+
</ChatThread>
|
|
83
|
+
|
|
84
|
+
<ChatComposer
|
|
85
|
+
disabled={status === 'streaming'}
|
|
86
|
+
onSubmit={text => send({ message: text })}
|
|
87
|
+
/>
|
|
88
|
+
</PageShell>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Individual Components
|
|
94
|
+
|
|
95
|
+
| Component | Purpose | Key Props |
|
|
96
|
+
|-----------|---------|-----------|
|
|
97
|
+
| `ChatThread` | Scrollable message container | `children` (ChatMessage elements) |
|
|
98
|
+
| `ChatMessage` | Single message bubble | `role: 'user' \| 'assistant'`, `children` |
|
|
99
|
+
| `ChatMessageContent` | Markdown + code rendering | `markdown: string` (handles streaming partial) |
|
|
100
|
+
| `CodeBlock` | Syntax-highlighted code | `code: string`, `language?: string` (uses shiki, lazy-loaded) |
|
|
101
|
+
| `ChatComposer` | Message input + submit | `onSubmit: (text) => void`, `disabled?: boolean` |
|
|
102
|
+
| `PageShell` | App layout with sidebar slot | `sidebar?: ReactNode`, `children` |
|
|
103
|
+
| `Sidebar` | Collapsible side panel | `children` |
|
|
104
|
+
| `SessionListItem` | Session entry in sidebar | `title: string`, `onClick`, `active?: boolean` |
|
|
105
|
+
|
|
106
|
+
### Other Useful Components
|
|
107
|
+
|
|
108
|
+
| Component | Purpose |
|
|
109
|
+
|-----------|---------|
|
|
110
|
+
| `Button`, `Input`, `Textarea` | Form primitives (themed) |
|
|
111
|
+
| `ToolCallCard` | Display agent tool invocations |
|
|
112
|
+
| `AgentStream` | Lower-level stream renderer |
|
|
113
|
+
| `ThemeSwitcher` | Light/dark mode toggle |
|
|
114
|
+
| `Avatar` | User/agent avatar |
|
|
115
|
+
| `Alert` | Status messages |
|
|
116
|
+
|
|
117
|
+
## Peer Dependencies (install only what you use)
|
|
118
|
+
|
|
119
|
+
**Chat/markdown path** (most apps need these):
|
|
120
|
+
```bash
|
|
121
|
+
npm install mdast-util-from-markdown mdast-util-to-hast mdast-util-gfm \
|
|
122
|
+
hast-util-to-jsx-runtime hast-util-sanitize hast-util-from-html \
|
|
123
|
+
micromark-extension-gfm unist-util-visit unist-util-visit-parents shiki
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**DO NOT install** unless you use the specific components:
|
|
127
|
+
- `mermaid` — only for diagram rendering components
|
|
128
|
+
- `katex` — only for math/LaTeX rendering
|
|
129
|
+
- `roughjs` / `perfect-freehand` — only for whiteboard/drawing components
|
|
130
|
+
|
|
131
|
+
## Theming
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { defineTheme, ThemeProvider } from '@theokit/ui'
|
|
135
|
+
|
|
136
|
+
// Built-in themes
|
|
137
|
+
import { dracula, oneDark, githubDark, anthropicStyle } from '@theokit/ui'
|
|
138
|
+
|
|
139
|
+
// Custom theme
|
|
140
|
+
const myTheme = defineTheme({
|
|
141
|
+
name: 'my-theme',
|
|
142
|
+
colors: { primary: '#3b82f6', background: '#0a0a0a' },
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
<ThemeProvider theme={myTheme}>
|
|
146
|
+
{children}
|
|
147
|
+
</ThemeProvider>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Anti-patterns
|
|
151
|
+
|
|
152
|
+
- NEVER build a custom chat message component — use `ChatMessage` + `ChatMessageContent`
|
|
153
|
+
- NEVER build a custom markdown renderer — `ChatMessageContent` handles it (including streaming partial fences)
|
|
154
|
+
- NEVER build a custom code highlighter — `CodeBlock` uses shiki (lazy-loaded)
|
|
155
|
+
- NEVER import from `@usetheo/ui` — that's the deprecated package name; use `@theokit/ui`
|
|
156
|
+
- NEVER install ALL peer deps — only install the peers for components you actually use
|
|
157
|
+
- NEVER use components without wrapping in `TheoUIProvider` + `ThemeProvider` first
|