create-theokit 1.0.10 → 1.0.11

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-theokit",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: theokit-agents
3
- description: TheoKit agent/LLM integration — @Agent, @Tool, @Toolbox decorators, streaming, memory
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
- ## @Agent Decorator
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
- import { Agent, MainLoop, Hook, Memory, Budget } from '@theokit/agents'
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', // Required: LLM model
23
- systemPrompt: 'You are a helpful task assistant.',
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` class name maps to `GET/POST /api/agents/assistant`.
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
- const all = db.select().from(tasks).all()
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
- ## Frontend — useAgentStream
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
- {events.map(e => <p key={e.id}>{e.content}</p>)}
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, not enforced at runtime)
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 + @Tool
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 (useAgentStream)
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 ChatComponent() {
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
- <input onSubmit={e => send({ message: e.target.value })} />
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/...')` directly — use `theoFetch` for type safety
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