create-theokit 1.25.0 → 1.25.1
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 +1 -1
- package/templates/_bot-preset/agents/publisher.ts +1 -1
- package/templates/_bot-preset/agents/researcher.ts +1 -1
- package/templates/default/.env.example +1 -1
- package/templates/default/README.md.tmpl +1 -1
- package/templates/default/agents/chat.ts +12 -4
- package/templates/default/docs/ARCHITECTURE.md +1 -1
- package/templates/default/docs/ENVIRONMENT.md +11 -3
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +8 -5
- package/templates/default/dot-claude/skills/theokit-gateways/SKILL.md +29 -1
- package/templates/default/package.json.tmpl +1 -1
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ export const policy = 'public'
|
|
|
20
20
|
|
|
21
21
|
export default AgentBuilder.create()
|
|
22
22
|
.input(z.object({ instruction: z.string() }))
|
|
23
|
-
.model(process.env.LLM_MODEL ?? 'openai/gpt-4o-mini')
|
|
23
|
+
.model(process.env.LLM_MODEL ?? 'openrouter/openai/gpt-4o-mini')
|
|
24
24
|
.system(
|
|
25
25
|
'You turn research notes into published output. Read notes with read_notes, then publish. ' +
|
|
26
26
|
'Publishing is gated: a human approves it before it happens.',
|
|
@@ -19,7 +19,7 @@ export const policy = 'public'
|
|
|
19
19
|
|
|
20
20
|
export default AgentBuilder.create()
|
|
21
21
|
.input(z.object({ topic: z.string() }))
|
|
22
|
-
.model(process.env.LLM_MODEL ?? 'openai/gpt-4o-mini')
|
|
22
|
+
.model(process.env.LLM_MODEL ?? 'openrouter/openai/gpt-4o-mini')
|
|
23
23
|
.system(
|
|
24
24
|
'You research a topic and leave notes for the publisher bot. ' +
|
|
25
25
|
'Write what you find with write_note; read what you already know with read_notes. ' +
|
|
@@ -56,7 +56,7 @@ your environment.
|
|
|
56
56
|
```ts
|
|
57
57
|
export default AgentBuilder.create()
|
|
58
58
|
.input(z.object({ message: z.string() }))
|
|
59
|
-
.model('openai/gpt-4o-mini')
|
|
59
|
+
.model('openrouter/openai/gpt-4o-mini')
|
|
60
60
|
.system(BASE_INSTRUCTIONS)
|
|
61
61
|
.tool(weatherTool)
|
|
62
62
|
.approval('send_notification', { question: 'Send this notification?' })
|
|
@@ -14,9 +14,17 @@ import { weatherTool } from './tools/weather.js'
|
|
|
14
14
|
* treats `prompts/ tools/ skills/ lib/ …` as semantic folders, so `agents/tools/weather.ts` never becomes
|
|
15
15
|
* a `/api/agents/tools/weather` endpoint. Add a second agent as another `agents/<name>.ts`.
|
|
16
16
|
*
|
|
17
|
-
* `@theokit/sdk` runs the agent; conversation turns auto-persist per session.
|
|
18
|
-
*
|
|
19
|
-
* provider
|
|
17
|
+
* `@theokit/sdk` runs the agent; conversation turns auto-persist per session.
|
|
18
|
+
*
|
|
19
|
+
* The FIRST segment of the model id picks the provider, and the key it needs follows from that:
|
|
20
|
+
* `openrouter/…` needs `OPENROUTER_API_KEY`, `anthropic/…` needs `ANTHROPIC_API_KEY`, `openai/…`
|
|
21
|
+
* needs `OPENAI_API_KEY`. There is no magic routing — a bare `openai/gpt-4o-mini` goes to OpenAI,
|
|
22
|
+
* not through a gateway, even with an OpenRouter key present. Reaching OpenAI's catalog THROUGH
|
|
23
|
+
* OpenRouter means naming the gateway: `openrouter/openai/gpt-4o-mini`, which is what this file
|
|
24
|
+
* declares, because `.env.example` asks for `OPENROUTER_API_KEY` (one key, many models —
|
|
25
|
+
* https://openrouter.ai/models).
|
|
26
|
+
*
|
|
27
|
+
* Change the id and the key changes with it. Both live in `.env` and here; nothing else to wire.
|
|
20
28
|
*/
|
|
21
29
|
/**
|
|
22
30
|
* Who may run this agent, and against which conversation (ADR 0001).
|
|
@@ -48,7 +56,7 @@ export default AgentBuilder.create()
|
|
|
48
56
|
// so setting it changed the model to exactly what it already was (#398, #408). One expression is
|
|
49
57
|
// cheaper than an override path through the framework, and it keeps the value visible in the file
|
|
50
58
|
// that decides it. The literal stays as the fallback: a scaffold has to run with no environment.
|
|
51
|
-
.model(process.env.LLM_MODEL ?? 'openai/gpt-4o-mini')
|
|
59
|
+
.model(process.env.LLM_MODEL ?? 'openrouter/openai/gpt-4o-mini')
|
|
52
60
|
.system(BASE_INSTRUCTIONS)
|
|
53
61
|
.tool(weatherTool)
|
|
54
62
|
.tool(currentTimeTool)
|
|
@@ -59,7 +59,7 @@ An agent that outgrows one file becomes a folder that co-locates its own composi
|
|
|
59
59
|
```ts
|
|
60
60
|
export default AgentBuilder.create()
|
|
61
61
|
.input(z.object({ message: z.string() }))
|
|
62
|
-
.model('openai/gpt-4o-mini')
|
|
62
|
+
.model('openrouter/openai/gpt-4o-mini')
|
|
63
63
|
.system(BASE_INSTRUCTIONS) // agents/prompts/instructions.ts
|
|
64
64
|
.tool(weatherTool) // agents/tools/weather.ts
|
|
65
65
|
.tool(currentTimeTool) // agents/tools/current-time.ts
|
|
@@ -8,9 +8,17 @@ Configuration lives in `.env` (copy `.env.example`). Nothing here is committed
|
|
|
8
8
|
| `ANTHROPIC_API_KEY` | one of these | Use Anthropic directly instead of OpenRouter. |
|
|
9
9
|
| `OPENAI_API_KEY` | one of these | Use OpenAI directly. |
|
|
10
10
|
|
|
11
|
-
The
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
The FIRST segment of the model id in `agents/chat.ts` picks the provider, and that decides which key
|
|
12
|
+
is needed. It is not a hint: `openai/gpt-4o-mini` goes to OpenAI and needs `OPENAI_API_KEY`, even
|
|
13
|
+
with an OpenRouter key present. Reaching another vendor's catalog THROUGH OpenRouter means naming
|
|
14
|
+
the gateway first — `openrouter/openai/gpt-4o-mini`, which is what the scaffold declares, matching
|
|
15
|
+
the key above. See <https://openrouter.ai/models> for the ids OpenRouter serves.
|
|
16
|
+
|
|
17
|
+
| Model id in `agents/chat.ts` | Key it needs |
|
|
18
|
+
| ------------------------------- | -------------------- |
|
|
19
|
+
| `openrouter/openai/gpt-4o-mini` | `OPENROUTER_API_KEY` |
|
|
20
|
+
| `anthropic/claude-sonnet-4-6` | `ANTHROPIC_API_KEY` |
|
|
21
|
+
| `openai/gpt-4o-mini` | `OPENAI_API_KEY` |
|
|
14
22
|
|
|
15
23
|
```bash
|
|
16
24
|
cp .env.example .env
|
|
@@ -25,7 +25,7 @@ import { z } from 'zod'
|
|
|
25
25
|
|
|
26
26
|
export default defineAgent({
|
|
27
27
|
input: z.object({ message: z.string() }),
|
|
28
|
-
model: 'openai/gpt-4o-mini',
|
|
28
|
+
model: 'openrouter/openai/gpt-4o-mini',
|
|
29
29
|
system: 'You are a helpful assistant.',
|
|
30
30
|
})
|
|
31
31
|
```
|
|
@@ -33,8 +33,11 @@ export default defineAgent({
|
|
|
33
33
|
The endpoint streams the ai-sdk `UIMessageStream` that `useAgent` (client hook) consumes.
|
|
34
34
|
`@theokit/sdk` runs the agent; conversation turns auto-persist per session — the SDK owns storage.
|
|
35
35
|
|
|
36
|
-
**Provider resolution:**
|
|
37
|
-
|
|
36
|
+
**Provider resolution:** the FIRST segment of the model id picks the provider, and that decides
|
|
37
|
+
which key is needed — `openrouter/…` needs `OPENROUTER_API_KEY`, `anthropic/…` needs
|
|
38
|
+
`ANTHROPIC_API_KEY`, `openai/…` needs `OPENAI_API_KEY`. A bare vendor prefix is a selection of that
|
|
39
|
+
vendor, not a hint: reaching OpenAI's catalog through OpenRouter means naming the gateway first
|
|
40
|
+
(`openrouter/openai/gpt-4o-mini`). Set the matching key in `.env`.
|
|
38
41
|
|
|
39
42
|
## Advanced Surface — @Agent Decorator (DI / class-based)
|
|
40
43
|
|
|
@@ -46,7 +49,7 @@ When you need dependency injection or composition, build the agent from **capabi
|
|
|
46
49
|
import { applyCapabilities, AgentConfigCapability, ModelCapability } from '@theokit/agents'
|
|
47
50
|
|
|
48
51
|
export const assistantAgent = applyCapabilities([
|
|
49
|
-
new ModelCapability('openai/gpt-4o-mini'),
|
|
52
|
+
new ModelCapability('openrouter/openai/gpt-4o-mini'),
|
|
50
53
|
new AgentConfigCapability({
|
|
51
54
|
systemPrompt: 'You are a helpful assistant.',
|
|
52
55
|
maxIterations: 5,
|
|
@@ -75,7 +78,7 @@ const currentTimeTool = defineAgentTool({
|
|
|
75
78
|
|
|
76
79
|
export default defineAgent({
|
|
77
80
|
input: z.object({ message: z.string() }),
|
|
78
|
-
model: 'openai/gpt-4o-mini',
|
|
81
|
+
model: 'openrouter/openai/gpt-4o-mini',
|
|
79
82
|
system: 'You are a helpful assistant.',
|
|
80
83
|
tools: [currentTimeTool],
|
|
81
84
|
})
|
|
@@ -26,7 +26,7 @@ returns the `Response` your route must return.
|
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
28
|
import { handleChannelWebhook } from 'theokit/server/agent'
|
|
29
|
-
import { telegram } from 'theokit/server/webhook' // also: discord, slack, github, stripe
|
|
29
|
+
import { telegram } from 'theokit/server/webhook' // also: discord, slack, github, stripe, whatsapp
|
|
30
30
|
import { parseInbound } from '@theokit/gateway-telegram'
|
|
31
31
|
|
|
32
32
|
const response = await handleChannelWebhook(request, new URL(request.url).pathname, {
|
|
@@ -42,6 +42,34 @@ const response = await handleChannelWebhook(request, new URL(request.url).pathna
|
|
|
42
42
|
The path it expects is `POST /api/agents/<name>/channels/<platform>/webhook`; `<name>` and
|
|
43
43
|
`<platform>` arrive in `onMessage` as `agent` and `platform`.
|
|
44
44
|
|
|
45
|
+
## Platforms that verify the endpoint first (WhatsApp, Instagram, Messenger)
|
|
46
|
+
|
|
47
|
+
Meta will not deliver anything until it has verified the URL with a `GET` carrying
|
|
48
|
+
`hub.mode=subscribe`, `hub.verify_token` and `hub.challenge`, and it requires the challenge echoed
|
|
49
|
+
back as `text/plain`. Declare a responder per platform and mount the route for `GET` as well as
|
|
50
|
+
`POST`:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { whatsapp, whatsappSubscribe } from 'theokit/server/webhook'
|
|
54
|
+
|
|
55
|
+
const response = await handleChannelWebhook(request, new URL(request.url).pathname, {
|
|
56
|
+
validators: { whatsapp: whatsapp({ appSecret: process.env.META_APP_SECRET! }) },
|
|
57
|
+
subscribe: { whatsapp: whatsappSubscribe({ verifyToken: process.env.META_VERIFY_TOKEN! }) },
|
|
58
|
+
onMessage: async ({ payload }) => {
|
|
59
|
+
/* … */
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`appSecret` is the Meta **app secret**, not the access token — the signature is HMAC-SHA256 of it
|
|
65
|
+
over the raw body. `verifyToken` is the string you typed into the Meta app when registering the URL;
|
|
66
|
+
comparing it is the only thing standing between an arbitrary caller and a subscription. A `GET` for
|
|
67
|
+
a platform with no `subscribe` entry answers `405`, not `404`: the platform is configured, it just
|
|
68
|
+
does not do handshakes.
|
|
69
|
+
|
|
70
|
+
Developing against any of this needs a public URL. `theo.config.ts` has `allowedHosts` for exactly
|
|
71
|
+
that — see the framework README.
|
|
72
|
+
|
|
45
73
|
**Give it a `Request` whose body has not been read.** It calls `request.json()` itself, so a wrapper
|
|
46
74
|
that has already parsed the body — `defineRoute` offers a parsed `body` in its handler context —
|
|
47
75
|
leaves nothing for it to read. Mount it where you still hold the original request, or pass a clone.
|