@pikku/skills 0.12.22 → 0.12.26
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/CHANGELOG.md +134 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-a11y/SKILL.md +59 -0
- package/skills/pikku-addon/SKILL.md +2 -2
- package/skills/pikku-agent/SKILL.md +67 -316
- package/skills/pikku-agent/references/agents.md +299 -0
- package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
- package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
- package/skills/pikku-architect/SKILL.md +265 -0
- package/skills/pikku-auth/SKILL.md +89 -0
- package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +126 -34
- package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
- package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
- package/skills/pikku-auth/references/permissions.md +261 -0
- package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
- package/skills/pikku-build/SKILL.md +88 -0
- package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +76 -24
- package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
- package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +56 -1
- package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
- package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
- package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
- package/skills/{pikku-build-app → pikku-build}/references/ship.md +9 -3
- package/skills/pikku-concepts/SKILL.md +72 -7
- package/skills/pikku-concepts/references/concept-mapping.md +8 -8
- package/skills/pikku-deploy/SKILL.md +158 -0
- package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
- package/skills/pikku-deploy/references/cloudflare.md +104 -0
- package/skills/pikku-deploy/references/express.md +92 -0
- package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
- package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
- package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
- package/skills/pikku-deploy/references/uws.md +72 -0
- package/skills/pikku-deploy/references/ws.md +75 -0
- package/skills/pikku-emails/SKILL.md +3 -2
- package/skills/pikku-fabric/SKILL.md +47 -20
- package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
- package/skills/pikku-i18n/SKILL.md +62 -207
- package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
- package/skills/pikku-i18n/references/messages.md +218 -0
- package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
- package/skills/pikku-knowledge/SKILL.md +15 -0
- package/skills/pikku-kysely/SKILL.md +13 -13
- package/skills/pikku-list-query/SKILL.md +163 -0
- package/skills/pikku-meta/SKILL.md +58 -130
- package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
- package/skills/pikku-meta/references/meta.md +114 -0
- package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
- package/skills/pikku-middleware/SKILL.md +5 -5
- package/skills/pikku-n8n-import/SKILL.md +0 -1
- package/skills/pikku-permissions/SKILL.md +75 -229
- package/skills/pikku-react/SKILL.md +50 -298
- package/skills/pikku-react/references/client.md +313 -0
- package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
- package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
- package/skills/pikku-realtime/SKILL.md +110 -251
- package/skills/pikku-scenario/SKILL.md +60 -45
- package/skills/pikku-scenario/references/persona-run.md +148 -0
- package/skills/pikku-seo/SKILL.md +133 -0
- package/skills/pikku-service-backends/SKILL.md +154 -0
- package/skills/pikku-service-backends/references/aws.md +106 -0
- package/skills/pikku-service-backends/references/backblaze.md +57 -0
- package/skills/pikku-service-backends/references/mongodb.md +90 -0
- package/skills/pikku-service-backends/references/redis.md +75 -0
- package/skills/pikku-service-backends/references/schema.md +63 -0
- package/skills/pikku-services/SKILL.md +68 -291
- package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
- package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
- package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
- package/skills/pikku-services/references/services.md +272 -0
- package/skills/pikku-software-archaeology/README.md +5 -1
- package/skills/pikku-software-archaeology/SKILL.md +16 -2
- package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
- package/skills/pikku-webhook/SKILL.md +224 -0
- package/skills/pikku-wiring/SKILL.md +180 -0
- package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
- package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
- package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
- package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
- package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
- package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
- package/skills/pikku-wiring/references/realtime.md +265 -0
- package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
- package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
- package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
- package/skills/pikku-workflow/SKILL.md +39 -2
- package/skills/pikku-aws/SKILL.md +0 -161
- package/skills/pikku-backblaze/SKILL.md +0 -104
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
- package/skills/pikku-deploy-express/SKILL.md +0 -122
- package/skills/pikku-deploy-uws/SKILL.md +0 -144
- package/skills/pikku-mongodb/SKILL.md +0 -113
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-redis/SKILL.md +0 -99
- package/skills/pikku-schema-ajv/SKILL.md +0 -83
- package/skills/pikku-schema-cfworker/SKILL.md +0 -82
- package/skills/pikku-ws/SKILL.md +0 -87
- /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
- /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
- /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
- /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
- /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-a11y
|
|
3
|
+
description: >-
|
|
4
|
+
Accessibility rules (WCAG 2.2) for the app UI: labeled inputs, real buttons/links, keyboard and focus, contrast and not-color-alone, modals, reduced motion.
|
|
5
|
+
TRIGGER when: building forms or any interactive UI, icon-only buttons, modals/drawers, tables/lists with actions, keyboard/focus work, or the user mentions accessibility / screen readers / WCAG.
|
|
6
|
+
DO NOT TRIGGER when: working on backend functions, database, or deployment with no UI.
|
|
7
|
+
installGroups: [client]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Accessibility Rules
|
|
11
|
+
|
|
12
|
+
Mantine components are accessible ONLY when used properly — the rules below are the
|
|
13
|
+
"properly". They apply to every page; heading order, landmarks, and image alt text are
|
|
14
|
+
covered in the `pikku-seo` skill and apply app-wide, not just on public pages.
|
|
15
|
+
|
|
16
|
+
## Every input has a label
|
|
17
|
+
|
|
18
|
+
- Use the `label` prop on every Mantine input — a placeholder is NOT a label (it
|
|
19
|
+
disappears on input and is never announced as one). Placeholder = example value only.
|
|
20
|
+
- Use the `error` and `description` props for validation/help text — Mantine associates
|
|
21
|
+
them with the input for screen readers; a loose `<Text c="red">` next to the field
|
|
22
|
+
does not.
|
|
23
|
+
- Icon-only controls (`ActionIcon`, icon `Button`) MUST have `aria-label={m.key()}`
|
|
24
|
+
naming the action ("Delete item", not "Trash icon").
|
|
25
|
+
|
|
26
|
+
## Interactive = a real button or link
|
|
27
|
+
|
|
28
|
+
- Never `onClick` on a `div`/`Box`/`Card` — it is invisible to keyboard and screen
|
|
29
|
+
readers. Use `Button`, `ActionIcon`, `UnstyledButton`, or `<Link>`; navigation is a
|
|
30
|
+
link (href), actions are buttons.
|
|
31
|
+
- Everything reachable by Tab, activatable by Enter/Space. Never remove focus outlines
|
|
32
|
+
(the theme owns the focus ring), never set `tabIndex` greater than 0, never trap focus
|
|
33
|
+
yourself.
|
|
34
|
+
- Whole-row/whole-card click: put the button/link INSIDE with the row as its label —
|
|
35
|
+
don't make the container clickable and unfocusable.
|
|
36
|
+
|
|
37
|
+
## Don't say it with color alone
|
|
38
|
+
|
|
39
|
+
- Status must carry text or an icon, not only a color: a Badge says "Overdue", a form
|
|
40
|
+
error has a message — a red tint by itself is invisible to colorblind users.
|
|
41
|
+
- Contrast comes from the theme; don't undermine it by stacking `c="dimmed"` on small
|
|
42
|
+
text over tinted backgrounds. Body copy stays at least AA-readable.
|
|
43
|
+
- Touch targets: WCAG 2.2 minimum 24px — don't shrink `ActionIcon`/`Checkbox` below
|
|
44
|
+
size `sm`, and keep adjacent row actions spaced.
|
|
45
|
+
|
|
46
|
+
## Overlays and motion
|
|
47
|
+
|
|
48
|
+
- Modals/drawers: use Mantine `Modal`/`Drawer` and ALWAYS pass `title` — that is what
|
|
49
|
+
gets announced; focus trap and Escape come built in. (This project uses drawers, not
|
|
50
|
+
dialogs.)
|
|
51
|
+
- Landing-page animation (the only custom-CSS surface) respects
|
|
52
|
+
`prefers-reduced-motion: reduce` — gate transforms/parallax behind the media query.
|
|
53
|
+
|
|
54
|
+
## Self-check before declaring UI done
|
|
55
|
+
|
|
56
|
+
Tab through the page once: every control reachable and visibly focused, every input
|
|
57
|
+
labeled, every icon button named, every status readable without color. A browser
|
|
58
|
+
scenario proves the flow works, not that it is reachable without a mouse — this
|
|
59
|
+
manual pass is the only check that does.
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
ref(), pikkuAddonServices, pikkuAddonWireServices, addon package structure, and cross-project
|
|
6
6
|
function sharing. TRIGGER when: code uses wireAddon/ref()/pikkuAddonServices, user asks about
|
|
7
7
|
addons, reusable function packages, cross-project sharing, or addon package structure. DO NOT
|
|
8
|
-
TRIGGER when: user asks about internal function composition (use pikku-
|
|
8
|
+
TRIGGER when: user asks about internal function composition (use pikku-wiring) or general function
|
|
9
9
|
definitions (use pikku-concepts).
|
|
10
10
|
installGroups: [core]
|
|
11
11
|
---
|
|
@@ -159,7 +159,7 @@ export const createSingletonServices = pikkuAddonServices(
|
|
|
159
159
|
|
|
160
160
|
`secrets` and `variables` arrive **typed against the addon's own declarations**,
|
|
161
161
|
and a secret is a `SecretValue` — `.reveal()` is the only way to the plaintext
|
|
162
|
-
(see `pikku-
|
|
162
|
+
(see `pikku-services`). `pikkuAddonConfig` is the matching factory for the addon's
|
|
163
163
|
config object.
|
|
164
164
|
|
|
165
165
|
### `pikkuAddonWireServices(factory)`
|
|
@@ -1,322 +1,73 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-agent
|
|
3
3
|
description: >-
|
|
4
|
-
Use when building AI agents, chatbots
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
streamAgent, user asks
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Use when building AI agents, chatbots or LLM-powered assistants with Pikku — pikkuAgent, ref()
|
|
5
|
+
tool registration, memory, streaming, tool approval, thread ownership, invocation via rpc.agent,
|
|
6
|
+
the VercelAgentRunner and its provider map, and the voiceInput/voiceOutput middlewares. TRIGGER
|
|
7
|
+
when: code uses pikkuAgent/rpc.agent/runAgent/streamAgent/VercelAgentRunner/voiceInput, user asks
|
|
8
|
+
about AI agents, chatbots, tool-calling, agent memory or streaming, model providers, speech in or
|
|
9
|
+
out, or `pikku enable agent`. DO NOT TRIGGER when: user asks about MCP tool exposure (use
|
|
10
|
+
pikku-wiring), workflows (use pikku-workflow), or general function definitions (use
|
|
11
|
+
pikku-concepts).
|
|
10
12
|
installGroups: [core]
|
|
11
13
|
---
|
|
12
14
|
|
|
13
|
-
# Pikku AI
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
embedder?: string, // Embedding service name
|
|
73
|
-
lastMessages?: number, // How many messages to retain in context
|
|
74
|
-
workingMemory?: ZodSchema, // Schema for structured working memory
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
maxSteps?: number, // Max tool-call rounds per invocation
|
|
78
|
-
toolChoice?: 'auto' | 'required' | 'none',
|
|
79
|
-
prepareStep?: (ctx) => void, // See "Narrowing tools per step"
|
|
80
|
-
input?: ZodSchema,
|
|
81
|
-
output?: ZodSchema, // Structured output — only honoured with NO tools
|
|
82
|
-
tags?: string[],
|
|
83
|
-
|
|
84
|
-
sessionScope?: 'user' | 'org', // Who owns this agent's threads. Default 'user'
|
|
85
|
-
auth?: boolean, // Default false — see below
|
|
86
|
-
scopes?: ScopeId[], // AND gate, checked before permissions
|
|
87
|
-
permissions?: PermissionGroup,
|
|
88
|
-
|
|
89
|
-
middleware?: PikkuMiddleware[],
|
|
90
|
-
channelMiddleware?: PikkuChannelMiddleware[],
|
|
91
|
-
agentMiddleware?: PikkuAgentMiddlewareHooks[],
|
|
92
|
-
})
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**`goal` is the required prompt field, not `instructions`** — there is no
|
|
96
|
-
`instructions` key. `role`/`personality`/`goal` are concatenated in that order,
|
|
97
|
-
and nothing validates which text lands in which, so the split is purely for
|
|
98
|
-
legibility: prose in the "wrong" one still reaches the model.
|
|
99
|
-
|
|
100
|
-
**Tools are `ref('domain:funcName')` handles, not imported function values.** The
|
|
101
|
-
inspector resolves the ref against the generated function map, which is what lets
|
|
102
|
-
an agent reach a function in another package (or a `graph:*` builtin) without an
|
|
103
|
-
import cycle.
|
|
104
|
-
|
|
105
|
-
`auth` defaults to `false` because agents are usually invoked from an
|
|
106
|
-
already-authenticated `pikkuFunc`. `scopes` and `permissions` are enforced either
|
|
107
|
-
way — see `pikku-permissions`.
|
|
108
|
-
|
|
109
|
-
### Invoking an agent
|
|
110
|
-
|
|
111
|
-
From inside a Pikku function, go through `wire.rpc.agent` — it carries the
|
|
112
|
-
session, credentials, and RPC depth for you:
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
const result = await rpc.agent.run('todo-agent', {
|
|
116
|
-
message, threadId, resourceId, // required
|
|
117
|
-
attachments?, model?, temperature?, context?,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
await rpc.agent.stream('todo-agent', input) // writes to the wire's channel
|
|
121
|
-
await rpc.agent.approve(runId, [{ toolCallId, approved }], expectedAgentName?)
|
|
122
|
-
await rpc.agent.resume(runId, { toolCallId, approved })
|
|
123
|
-
await rpc.agent.interrupt(runId, 'user' | 'speech' | 'timeout')
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
`context` is a string injected into the system prompt for this request only —
|
|
127
|
-
use it for upfront state (current org, project, deployment) so the agent stops
|
|
128
|
-
asking the user for identifiers it could have been handed.
|
|
129
|
-
|
|
130
|
-
`run` resolves to:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
{
|
|
134
|
-
runId, threadId, text,
|
|
135
|
-
object?, // set when the agent has an `output` schema
|
|
136
|
-
steps, // tool calls made
|
|
137
|
-
usage: { inputTokens, outputTokens },
|
|
138
|
-
status?: 'completed' | 'suspended',
|
|
139
|
-
pendingApprovals?: [{ toolCallId, toolName, args, reason?, runId }],
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
`runAgent` / `streamAgent` from `@pikku/core/agent` are the layer beneath
|
|
144
|
-
this. Their third argument is `RunAgentParams` (`{ sessionService?,
|
|
145
|
-
getCredential?, anonymousOwnerResourceId? }`) — **not** `{ singletonServices }`.
|
|
146
|
-
Reach for them only outside a wired function; inside one, `rpc.agent` is the
|
|
147
|
-
supported path.
|
|
148
|
-
|
|
149
|
-
### Stream events
|
|
150
|
-
|
|
151
|
-
`rpc.agent.stream` pushes `AgentStreamEvent`s onto the channel:
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
// { type: 'step-start', stepNumber }
|
|
155
|
-
// { type: 'text-delta' | 'reasoning-delta', text }
|
|
156
|
-
// { type: 'tool-call', toolCallId, toolName, args }
|
|
157
|
-
// { type: 'tool-result', toolCallId, toolName, result }
|
|
158
|
-
// { type: 'agent-call' | 'agent-result', agentName, session, input | result }
|
|
159
|
-
// { type: 'approval-request', toolCallId, toolName, args, reason?, runId? }
|
|
160
|
-
// { type: 'credential-request', toolCallId, toolName, credentialName,
|
|
161
|
-
// credentialType: 'oauth2' | 'apikey', connectUrl?, runId }
|
|
162
|
-
// { type: 'usage', tokens: { input, output }, model }
|
|
163
|
-
// { type: 'transcript', text } // what the user was heard to say
|
|
164
|
-
// { type: 'audio-delta', data, format, text? } | { type: 'audio-done' }
|
|
165
|
-
// { type: 'data', name, data } | { type: 'generative-ui', spec }
|
|
166
|
-
// { type: 'suspended', reason: 'rpc-missing', missingRpcs }
|
|
167
|
-
// { type: 'interrupted', runId, text, reason }
|
|
168
|
-
// { type: 'error', message }
|
|
169
|
-
// { type: 'done' }
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Every event except `agent-call`/`agent-result`/`suspended` also carries optional
|
|
173
|
-
`agent` and `session` fields, so a UI can attribute output to a sub-agent rather
|
|
174
|
-
than folding it into the parent's transcript.
|
|
175
|
-
|
|
176
|
-
## Usage Patterns
|
|
177
|
-
|
|
178
|
-
### Define an Agent
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
182
|
-
import { ref } from '#pikku/function'
|
|
183
|
-
|
|
184
|
-
export const todoAgent = pikkuAgent({
|
|
185
|
-
name: 'todo-agent',
|
|
186
|
-
description: 'Manages a todo list',
|
|
187
|
-
goal: 'You help users manage their todos. You can list, add, complete and delete them.',
|
|
188
|
-
model: 'openai/gpt-5-mini',
|
|
189
|
-
tools: [
|
|
190
|
-
ref('todos:listTodos'),
|
|
191
|
-
ref('todos:addTodo'),
|
|
192
|
-
ref('todos:completeTodo'),
|
|
193
|
-
ref('graph:sleep'),
|
|
194
|
-
],
|
|
195
|
-
memory: { storage: 'agentStorage', lastMessages: 20 },
|
|
196
|
-
maxSteps: 10,
|
|
197
|
-
toolChoice: 'auto',
|
|
198
|
-
})
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Scaffold the HTTP surface
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
pikku enable agent
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
The next `pikku all` generates `agent.gen.ts` — run/stream/approve/resume
|
|
208
|
-
callers plus thread listing endpoints, with thread ownership already enforced
|
|
209
|
-
against the session. Don't hand-write these routes.
|
|
210
|
-
|
|
211
|
-
### Structured output
|
|
212
|
-
|
|
213
|
-
An `output` schema fills `result.object`, but **only when the agent exposes no
|
|
214
|
-
tools** — with a tool present the runner falls back to free text, silently. If
|
|
215
|
-
you need both, split the classification into its own tool-free agent.
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
export const structuredAgent = pikkuAgent({
|
|
219
|
-
name: 'structured-agent',
|
|
220
|
-
description: 'Classifies a message and returns a structured verdict',
|
|
221
|
-
goal: 'You classify the sentiment of the user message.',
|
|
222
|
-
model: 'openai/gpt-5-mini',
|
|
223
|
-
output: z.object({ sentiment: z.string(), score: z.number() }),
|
|
224
|
-
})
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Narrowing tools per step
|
|
228
|
-
|
|
229
|
-
`prepareStep` runs before each step with the live tool array for that step, so
|
|
230
|
-
mutating it in place changes what the model is offered from there on. `stop()`
|
|
231
|
-
ends the loop — called before step 0 the run completes with an empty result
|
|
232
|
-
rather than signalling that it was short-circuited.
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
prepareStep: ({ stepNumber, tools }) => {
|
|
236
|
-
if (stepNumber >= 1) tools.length = 0 // withdraw tools after the first step
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Tool approval
|
|
241
|
-
|
|
242
|
-
A tool that should pause for a human sets `approvalRequired: true` (with an
|
|
243
|
-
optional `approvalDescription`) on the _function_, not on the agent. The run then
|
|
244
|
-
resolves with `status: 'suspended'` and `pendingApprovals`, and streaming emits
|
|
245
|
-
`approval-request`. Answer with `rpc.agent.approve(runId, approvals)`.
|
|
246
|
-
|
|
247
|
-
Authorization around tools is two-layer: an agent only sees tools its session can
|
|
248
|
-
reach, and the function's own `permissions` still guard the call when the model
|
|
249
|
-
picks one.
|
|
250
|
-
|
|
251
|
-
### Thread ownership
|
|
252
|
-
|
|
253
|
-
`resourceId` is caller-supplied but never trusted as an owner. The session's
|
|
254
|
-
principal (`userId`, or `orgId` when `sessionScope: 'org'`) is prefixed onto it,
|
|
255
|
-
so a client can sub-partition inside its own boundary and cannot read across one.
|
|
256
|
-
A sessionless run gets an ephemeral anonymous owner instead.
|
|
257
|
-
|
|
258
|
-
## Complete Example
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
// functions/todos.functions.ts
|
|
262
|
-
export const listTodos = pikkuSessionlessFunc({
|
|
263
|
-
description: 'List all todo items',
|
|
264
|
-
func: async ({ db }, { status }) => {
|
|
265
|
-
return { todos: await db.listTodos(status) }
|
|
266
|
-
},
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
export const createTodo = pikkuFunc({
|
|
270
|
-
description: 'Create a new todo item',
|
|
271
|
-
func: async ({ db }, { text, priority, dueDate }) => {
|
|
272
|
-
return await db.createTodo({ text, priority, dueDate })
|
|
273
|
-
},
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
export const completeTodo = pikkuFunc({
|
|
277
|
-
description: 'Mark a todo as complete',
|
|
278
|
-
func: async ({ db }, { todoId }) => {
|
|
279
|
-
return await db.completeTodo(todoId)
|
|
280
|
-
},
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
// agents/todo-assistant.agent.ts
|
|
284
|
-
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
285
|
-
import { ref } from '#pikku/function'
|
|
286
|
-
|
|
287
|
-
export const todoAssistant = pikkuAgent({
|
|
288
|
-
name: 'todo-assistant',
|
|
289
|
-
description: 'A helpful assistant that manages todos',
|
|
290
|
-
role: 'You are an assistant that manages a user’s todo list.',
|
|
291
|
-
personality: 'Concise. One short paragraph unless asked for detail.',
|
|
292
|
-
goal: `Keep the user's todos accurate.
|
|
293
|
-
- When creating todos, infer priority if not specified
|
|
294
|
-
- When listing todos, summarize the results`,
|
|
295
|
-
model: 'openai/gpt-5-mini',
|
|
296
|
-
tools: [
|
|
297
|
-
ref('todos:listTodos'),
|
|
298
|
-
ref('todos:createTodo'),
|
|
299
|
-
ref('todos:completeTodo'),
|
|
300
|
-
],
|
|
301
|
-
memory: { storage: 'agentStorage', lastMessages: 20 },
|
|
302
|
-
maxSteps: 5,
|
|
303
|
-
temperature: 0.7,
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
// Wire to HTTP for a chat endpoint — or skip this entirely and run
|
|
307
|
-
// `pikku enable agent`, which scaffolds run/stream/approve/resume for you.
|
|
308
|
-
wireHTTP({
|
|
309
|
-
method: 'post',
|
|
310
|
-
route: '/chat',
|
|
311
|
-
func: pikkuFunc({
|
|
312
|
-
title: 'Chat',
|
|
313
|
-
func: async (_services, { message, threadId }, { session, rpc }) => {
|
|
314
|
-
return await rpc.agent.run('todo-assistant', {
|
|
315
|
-
message,
|
|
316
|
-
threadId,
|
|
317
|
-
resourceId: session.userId,
|
|
318
|
-
})
|
|
319
|
-
},
|
|
320
|
-
}),
|
|
321
|
-
})
|
|
322
|
-
```
|
|
15
|
+
# Pikku AI Agents
|
|
16
|
+
|
|
17
|
+
Signatures and option keys come from `pikku doc` — run `pikku doc --ai` for the
|
|
18
|
+
installed surface. This skill is the part the compiler cannot tell you: how an
|
|
19
|
+
agent reaches the rest of the app, and which of its knobs mean something other
|
|
20
|
+
than what they look like.
|
|
21
|
+
|
|
22
|
+
## Pick the reference
|
|
23
|
+
|
|
24
|
+
| You are… | Read |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| Defining or invoking an agent — tools, memory, streaming, approval, threads | `references/agents.md` |
|
|
27
|
+
| Wiring the runner, or pointing model strings at a provider or gateway | `references/runner-vercel.md` |
|
|
28
|
+
| Adding speech in or out of an agent | `references/voice.md` |
|
|
29
|
+
|
|
30
|
+
## An agent is a function that reaches other functions
|
|
31
|
+
|
|
32
|
+
Tools, sub-agents and workflows are all supplied as `ref('domain:funcName')`
|
|
33
|
+
handles rather than imported values. The inspector resolves each ref against the
|
|
34
|
+
generated function map, which is what lets an agent call into another package or
|
|
35
|
+
a `graph:*` builtin without an import cycle — and what lets the tool menu be
|
|
36
|
+
filtered per session before the model ever sees it.
|
|
37
|
+
|
|
38
|
+
Invoke through `wire.rpc.agent` from inside a Pikku function; it carries the
|
|
39
|
+
session, the credentials and the RPC depth for you.
|
|
40
|
+
|
|
41
|
+
## The knobs that do not mean what they look like
|
|
42
|
+
|
|
43
|
+
- **`goal` is the prompt field, and it is required.** There is no `instructions`
|
|
44
|
+
key. `role`, `personality` and `goal` are concatenated in that order and
|
|
45
|
+
nothing validates which text lands where, so the split buys legibility only.
|
|
46
|
+
- **`output` is honoured only when the agent has no tools.** A structured-output
|
|
47
|
+
schema on a tool-calling agent is silently inert.
|
|
48
|
+
- **`auth` defaults to `false`**, because agents are normally invoked from an
|
|
49
|
+
already-authenticated `pikkuFunc`. `scopes` and `permissions` are enforced
|
|
50
|
+
either way — see `pikku-auth`.
|
|
51
|
+
- **`approvalRequired` sits on the tool function, not on the agent.** The run
|
|
52
|
+
then resolves `status: 'suspended'` with `pendingApprovals`; answer with
|
|
53
|
+
`rpc.agent.approve(runId, approvals)`.
|
|
54
|
+
- **Model strings split on the first slash only** — `provider/model`, so
|
|
55
|
+
`'ollama/qwen2.5:7b'` is fine and a string with no slash throws rather than
|
|
56
|
+
defaulting to a provider.
|
|
57
|
+
|
|
58
|
+
## What NOT to do
|
|
59
|
+
|
|
60
|
+
- **Do not trust a caller-supplied `resourceId` as an owner.** It never is: the
|
|
61
|
+
session's principal (`userId`, or `orgId` under `sessionScope: 'org'`) is
|
|
62
|
+
prefixed onto it, so a client sub-partitions inside its own boundary and cannot
|
|
63
|
+
read across one. A sessionless run gets an ephemeral anonymous owner.
|
|
64
|
+
- **Do not rely on the tool menu alone for authorization.** It is two-layer — the
|
|
65
|
+
session decides which tools an agent can see, and the function's own
|
|
66
|
+
`permissions` still guard the call when the model picks one.
|
|
67
|
+
- **Do not point the `'*'` provider at a single vendor.** It resolves every
|
|
68
|
+
provider name with no exact entry, so aimed at one vendor an `anthropic/…`
|
|
69
|
+
string silently reaches OpenAI. Point it at a gateway or a scripted test
|
|
70
|
+
provider — something that genuinely accepts arbitrary model names.
|
|
71
|
+
- **Do not add `@pikku/ai-voice` as a dependency.** It still publishes but its
|
|
72
|
+
entire source is `export {}`. Voice is two middlewares in `@pikku/core/agent`,
|
|
73
|
+
with the speech models reached through the `agentRunner`.
|