@pikku/skills 0.12.9 → 0.12.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/CHANGELOG.md +768 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +4 -4
- package/skills/pikku-addon/SKILL.md +20 -14
- package/skills/pikku-addon/references/addon-package-manifest.md +2 -2
- package/skills/{pikku-ai-agent → pikku-agent}/SKILL.md +24 -24
- package/skills/pikku-ai-vercel/SKILL.md +18 -18
- package/skills/pikku-ai-voice/SKILL.md +15 -15
- package/skills/pikku-audit/SKILL.md +28 -13
- package/skills/pikku-aws/SKILL.md +2 -2
- package/skills/pikku-better-auth/SKILL.md +97 -17
- package/skills/pikku-build-app/SKILL.md +621 -0
- package/skills/pikku-build-app/references/multi-app.md +117 -0
- package/skills/pikku-build-app/references/ship.md +98 -0
- package/skills/pikku-build-app/references/theming.md +70 -0
- package/skills/pikku-build-platform/SKILL.md +239 -0
- package/skills/pikku-build-quick/SKILL.md +238 -0
- package/skills/pikku-cli/SKILL.md +7 -7
- package/skills/pikku-cli/references/complete-example.md +1 -1
- package/skills/pikku-concepts/SKILL.md +10 -7
- package/skills/pikku-concepts/references/concept-mapping.md +1 -1
- package/skills/pikku-config/SKILL.md +5 -3
- package/skills/pikku-deploy-azure/SKILL.md +5 -4
- package/skills/pikku-deploy-cloudflare/SKILL.md +9 -9
- package/skills/pikku-deploy-uws/SKILL.md +5 -2
- package/skills/pikku-deps/SKILL.md +42 -3
- package/skills/pikku-emails/SKILL.md +5 -5
- package/skills/pikku-fabric/SKILL.md +27 -3
- package/skills/pikku-fabric-debug/SKILL.md +1 -1
- package/skills/pikku-feature/SKILL.md +5 -4
- package/skills/pikku-http/SKILL.md +4 -4
- package/skills/pikku-http/references/http-options.md +13 -13
- package/skills/pikku-i18n/SKILL.md +2 -1
- package/skills/pikku-info/SKILL.md +1 -1
- package/skills/pikku-knowledge/SKILL.md +13 -13
- package/skills/pikku-kysely/SKILL.md +68 -41
- package/skills/pikku-machine-auth/SKILL.md +10 -10
- package/skills/pikku-mcp/SKILL.md +23 -20
- package/skills/pikku-middleware/SKILL.md +19 -12
- package/skills/pikku-middleware/references/middleware-patterns.md +14 -8
- package/skills/pikku-mongodb/SKILL.md +11 -11
- package/skills/pikku-n8n-import/SKILL.md +12 -12
- package/skills/pikku-n8n-import/SPEC.md +3 -0
- package/skills/pikku-n8n-import/references/addon-mapping.md +14 -8
- package/skills/pikku-n8n-import/references/code-translation.md +26 -22
- package/skills/pikku-n8n-import/references/loops-and-control.md +7 -7
- package/skills/pikku-paraglide/SKILL.md +11 -6
- package/skills/pikku-permissions/SKILL.md +19 -15
- package/skills/pikku-product-second-opinion/README.md +3 -3
- package/skills/pikku-product-second-opinion/SKILL.md +83 -73
- package/skills/pikku-product-second-opinion/example/sample-report.md +15 -12
- package/skills/pikku-product-second-opinion/references/report-template.md +10 -7
- package/skills/pikku-queue/SKILL.md +1 -1
- package/skills/pikku-react/SKILL.md +53 -13
- package/skills/pikku-realtime/SKILL.md +51 -19
- package/skills/pikku-rpc/SKILL.md +1 -1
- package/skills/pikku-rtl/SKILL.md +1 -1
- package/skills/pikku-scenario/SKILL.md +164 -44
- package/skills/pikku-schedule/SKILL.md +6 -1
- package/skills/pikku-schema-ajv/SKILL.md +2 -2
- package/skills/pikku-schema-cfworker/SKILL.md +1 -1
- package/skills/pikku-security/SKILL.md +9 -5
- package/skills/pikku-services/SKILL.md +27 -18
- package/skills/pikku-services/references/audit-wire-service.md +14 -8
- package/skills/pikku-software-archaeology/SKILL.md +27 -23
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +580 -102
- package/skills/pikku-software-archaeology/scripts/validate.mjs +146 -69
- package/skills/pikku-tag-middleware/SKILL.md +1 -0
- package/skills/pikku-template-clone/SKILL.md +2 -1
- package/skills/pikku-trigger/SKILL.md +3 -3
- package/skills/pikku-versioning/SKILL.md +87 -3
- package/skills/pikku-websocket/SKILL.md +4 -3
- package/skills/pikku-workflow/SKILL.md +2 -2
- package/skills/pikku-workflow/references/workflow-reference.md +24 -10
- package/skills/pikku-ws/SKILL.md +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/skills",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.11",
|
|
4
4
|
"description": "The Pikku agent skills — the instruction set coding agents read to build, wire and deploy Pikku projects",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"skills"
|
|
27
27
|
],
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^24.
|
|
29
|
+
"@types/node": "^24.13.3",
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
|
-
"yaml": "^2.
|
|
31
|
+
"yaml": "^2.9.0"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=24"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|
|
@@ -40,7 +40,7 @@ See `pikku-concepts` for the core mental model.
|
|
|
40
40
|
Register an addon in the consuming project:
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
|
-
import { wireAddon } from '#pikku'
|
|
43
|
+
import { wireAddon } from '#pikku/addon'
|
|
44
44
|
|
|
45
45
|
wireAddon({
|
|
46
46
|
name: string, // Namespace for addon functions (e.g. 'todos')
|
|
@@ -123,7 +123,7 @@ Type-safe reference to a function — local or addon — for use in any wiring.
|
|
|
123
123
|
returns a function config that proxies the call via RPC at runtime:
|
|
124
124
|
|
|
125
125
|
```typescript
|
|
126
|
-
import { ref } from '#pikku'
|
|
126
|
+
import { ref } from '#pikku/function'
|
|
127
127
|
|
|
128
128
|
ref('todos:addTodo') // namespace:functionName for an addon function
|
|
129
129
|
ref('myLocalFunc') // a local function by name
|
|
@@ -134,7 +134,7 @@ There is no `addon()` helper; `ref()` covers both. For an addon that publishes
|
|
|
134
134
|
`refChannel` and `refCLI`, which carry the addon's own route/config metadata:
|
|
135
135
|
|
|
136
136
|
```typescript
|
|
137
|
-
import { refHTTP } from '#pikku'
|
|
137
|
+
import { refHTTP } from '#pikku/function'
|
|
138
138
|
|
|
139
139
|
wireHTTP(refHTTP('todos:listTodos', { basePath: '/api' }))
|
|
140
140
|
```
|
|
@@ -146,7 +146,7 @@ second argument is always present — an addon never falls back to its own logge
|
|
|
146
146
|
variables or secrets; the consuming app supplies them:
|
|
147
147
|
|
|
148
148
|
```typescript
|
|
149
|
-
import { pikkuAddonServices } from '#pikku'
|
|
149
|
+
import { pikkuAddonServices } from '#pikku/addon/setup'
|
|
150
150
|
|
|
151
151
|
export const createSingletonServices = pikkuAddonServices(
|
|
152
152
|
async (config, { secrets, logger }) => {
|
|
@@ -167,7 +167,7 @@ config object.
|
|
|
167
167
|
Define per-request services for an addon package (created fresh per HTTP request, queue job, etc.):
|
|
168
168
|
|
|
169
169
|
```typescript
|
|
170
|
-
import { pikkuAddonWireServices } from '#pikku'
|
|
170
|
+
import { pikkuAddonWireServices } from '#pikku/addon/setup'
|
|
171
171
|
|
|
172
172
|
export const createWireServices = pikkuAddonWireServices(
|
|
173
173
|
async (singletonServices, wire) => {
|
|
@@ -195,7 +195,7 @@ This generates `package.json` (exports `.pikku/*` + `dist/`), `pikku.config.json
|
|
|
195
195
|
|
|
196
196
|
```typescript
|
|
197
197
|
// src/services.ts
|
|
198
|
-
import { pikkuAddonServices, pikkuAddonWireServices } from '#pikku'
|
|
198
|
+
import { pikkuAddonServices, pikkuAddonWireServices } from '#pikku/addon/setup'
|
|
199
199
|
import { TodoStore } from './todo-store.service.js'
|
|
200
200
|
|
|
201
201
|
export const createSingletonServices = pikkuAddonServices(async () => {
|
|
@@ -213,10 +213,14 @@ export const createWireServices = pikkuAddonWireServices(
|
|
|
213
213
|
|
|
214
214
|
### Functions
|
|
215
215
|
|
|
216
|
+
An addon generates its whole tree under `#pikku/addon/*`, so it authors against
|
|
217
|
+
`#pikku/addon/function`, `#pikku/addon/http` and so on. An application's leaves
|
|
218
|
+
stay flat, which is what stops a linked addon resolving against its host.
|
|
219
|
+
|
|
216
220
|
```typescript
|
|
217
221
|
// src/functions/addTodo.function.ts
|
|
218
222
|
import { z } from 'zod'
|
|
219
|
-
import { pikkuSessionlessFunc } from '#pikku'
|
|
223
|
+
import { pikkuSessionlessFunc } from '#pikku/addon/function'
|
|
220
224
|
|
|
221
225
|
const AddTodoInput = z.object({ title: z.string() })
|
|
222
226
|
const AddTodoOutput = z.object({ id: z.string(), title: z.string() })
|
|
@@ -269,7 +273,7 @@ yarn add @my-org/addon-todos
|
|
|
269
273
|
|
|
270
274
|
```typescript
|
|
271
275
|
// wirings/todos.wirings.ts
|
|
272
|
-
import { wireAddon } from '#pikku'
|
|
276
|
+
import { wireAddon } from '#pikku/addon'
|
|
273
277
|
|
|
274
278
|
wireAddon({ name: 'todos', package: '@my-org/addon-todos' })
|
|
275
279
|
```
|
|
@@ -290,7 +294,8 @@ export const myFunc = pikkuFunc({
|
|
|
290
294
|
### Wire to HTTP
|
|
291
295
|
|
|
292
296
|
```typescript
|
|
293
|
-
import { wireHTTP
|
|
297
|
+
import { wireHTTP } from '#pikku/http'
|
|
298
|
+
import { ref } from '#pikku/function'
|
|
294
299
|
|
|
295
300
|
wireHTTP({
|
|
296
301
|
method: 'get',
|
|
@@ -303,7 +308,8 @@ wireHTTP({
|
|
|
303
308
|
Or batch multiple addon routes with `defineHTTPRoutes` + `wireHTTPRoutes`:
|
|
304
309
|
|
|
305
310
|
```typescript
|
|
306
|
-
import { wireHTTPRoutes, defineHTTPRoutes
|
|
311
|
+
import { wireHTTPRoutes, defineHTTPRoutes } from '#pikku/http'
|
|
312
|
+
import { ref } from '#pikku/function'
|
|
307
313
|
|
|
308
314
|
const todoRoutes = defineHTTPRoutes({
|
|
309
315
|
tags: ['todos'],
|
|
@@ -320,10 +326,10 @@ wireHTTPRoutes({ basePath: '/api', routes: { todos: todoRoutes } })
|
|
|
320
326
|
### Use in AI Agents
|
|
321
327
|
|
|
322
328
|
```typescript
|
|
323
|
-
import {
|
|
324
|
-
import { ref } from '#pikku'
|
|
329
|
+
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
330
|
+
import { ref } from '#pikku/function'
|
|
325
331
|
|
|
326
|
-
export const todoAgent =
|
|
332
|
+
export const todoAgent = pikkuAgent({
|
|
327
333
|
name: 'todo-agent',
|
|
328
334
|
description: 'Manages a todo list',
|
|
329
335
|
goal: 'You help users manage their todos.',
|
|
@@ -337,4 +343,4 @@ export const todoAgent = pikkuAIAgent({
|
|
|
337
343
|
})
|
|
338
344
|
```
|
|
339
345
|
|
|
340
|
-
See `pikku-
|
|
346
|
+
See `pikku-agent` — an addon function is just another `ref()` in `tools`.
|
|
@@ -40,8 +40,8 @@ my-addon/
|
|
|
40
40
|
{
|
|
41
41
|
"name": "@my-org/addon-todos",
|
|
42
42
|
"imports": {
|
|
43
|
-
"#pikku": "./.pikku
|
|
44
|
-
"#pikku/*": "./.pikku
|
|
43
|
+
"#pikku/*.js": "./.pikku/*.ts",
|
|
44
|
+
"#pikku/*": "./.pikku/*/index.ts"
|
|
45
45
|
},
|
|
46
46
|
"exports": {
|
|
47
47
|
".": { "types": "./dist/src/index.d.ts", "import": "./dist/src/index.js" },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: pikku-
|
|
2
|
+
name: pikku-agent
|
|
3
3
|
description: >-
|
|
4
4
|
Use when building AI agents, chatbots, or LLM-powered assistants with Pikku. Covers
|
|
5
|
-
|
|
6
|
-
invocation via rpc.agent. TRIGGER when: code uses
|
|
7
|
-
|
|
5
|
+
pikkuAgent, ref() tool registration, memory, streaming, tool approval, thread ownership, and
|
|
6
|
+
invocation via rpc.agent. TRIGGER when: code uses pikkuAgent/rpc.agent/runAgent/
|
|
7
|
+
streamAgent, user asks about AI agents, chatbots, LLM assistants, tool-calling agents, agent
|
|
8
8
|
memory/streaming, or `pikku enable agent`. DO NOT TRIGGER when: user asks about MCP tool
|
|
9
9
|
exposure (use pikku-mcp) or general function definitions (use pikku-concepts).
|
|
10
10
|
installGroups: [core]
|
|
@@ -35,15 +35,15 @@ See `pikku-concepts` for the core mental model.
|
|
|
35
35
|
|
|
36
36
|
## API Reference
|
|
37
37
|
|
|
38
|
-
### `
|
|
38
|
+
### `pikkuAgent(config)`
|
|
39
39
|
|
|
40
40
|
Import it from the generated agent types file — `#pikku` does not re-export it:
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
|
-
import {
|
|
44
|
-
import { ref } from '#pikku/
|
|
43
|
+
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
44
|
+
import { ref } from '#pikku/function'
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
pikkuAgent({
|
|
47
47
|
name: string, // Unique agent identifier
|
|
48
48
|
description: string, // What the agent does (shown in agent listings)
|
|
49
49
|
summary?: string,
|
|
@@ -67,7 +67,7 @@ pikkuAIAgent({
|
|
|
67
67
|
agentMode?: 'delegate' | 'supervise',
|
|
68
68
|
|
|
69
69
|
memory?: {
|
|
70
|
-
storage?: string, // Service name for persistence (e.g. '
|
|
70
|
+
storage?: string, // Service name for persistence (e.g. 'agentStorage')
|
|
71
71
|
vector?: string, // Vector store service name
|
|
72
72
|
embedder?: string, // Embedding service name
|
|
73
73
|
lastMessages?: number, // How many messages to retain in context
|
|
@@ -88,7 +88,7 @@ pikkuAIAgent({
|
|
|
88
88
|
|
|
89
89
|
middleware?: PikkuMiddleware[],
|
|
90
90
|
channelMiddleware?: PikkuChannelMiddleware[],
|
|
91
|
-
|
|
91
|
+
agentMiddleware?: PikkuAgentMiddlewareHooks[],
|
|
92
92
|
})
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -140,15 +140,15 @@ asking the user for identifiers it could have been handed.
|
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
`
|
|
144
|
-
this. Their third argument is `
|
|
143
|
+
`runAgent` / `streamAgent` from `@pikku/core/agent` are the layer beneath
|
|
144
|
+
this. Their third argument is `RunAgentParams` (`{ sessionService?,
|
|
145
145
|
getCredential?, anonymousOwnerResourceId? }`) — **not** `{ singletonServices }`.
|
|
146
146
|
Reach for them only outside a wired function; inside one, `rpc.agent` is the
|
|
147
147
|
supported path.
|
|
148
148
|
|
|
149
149
|
### Stream events
|
|
150
150
|
|
|
151
|
-
`rpc.agent.stream` pushes `
|
|
151
|
+
`rpc.agent.stream` pushes `AgentStreamEvent`s onto the channel:
|
|
152
152
|
|
|
153
153
|
```typescript
|
|
154
154
|
// { type: 'step-start', stepNumber }
|
|
@@ -178,10 +178,10 @@ than folding it into the parent's transcript.
|
|
|
178
178
|
### Define an Agent
|
|
179
179
|
|
|
180
180
|
```typescript
|
|
181
|
-
import {
|
|
182
|
-
import { ref } from '#pikku/
|
|
181
|
+
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
182
|
+
import { ref } from '#pikku/function'
|
|
183
183
|
|
|
184
|
-
export const todoAgent =
|
|
184
|
+
export const todoAgent = pikkuAgent({
|
|
185
185
|
name: 'todo-agent',
|
|
186
186
|
description: 'Manages a todo list',
|
|
187
187
|
goal: 'You help users manage their todos. You can list, add, complete and delete them.',
|
|
@@ -192,7 +192,7 @@ export const todoAgent = pikkuAIAgent({
|
|
|
192
192
|
ref('todos:completeTodo'),
|
|
193
193
|
ref('graph:sleep'),
|
|
194
194
|
],
|
|
195
|
-
memory: { storage: '
|
|
195
|
+
memory: { storage: 'agentStorage', lastMessages: 20 },
|
|
196
196
|
maxSteps: 10,
|
|
197
197
|
toolChoice: 'auto',
|
|
198
198
|
})
|
|
@@ -216,7 +216,7 @@ tools** — with a tool present the runner falls back to free text, silently. If
|
|
|
216
216
|
you need both, split the classification into its own tool-free agent.
|
|
217
217
|
|
|
218
218
|
```typescript
|
|
219
|
-
export const structuredAgent =
|
|
219
|
+
export const structuredAgent = pikkuAgent({
|
|
220
220
|
name: 'structured-agent',
|
|
221
221
|
description: 'Classifies a message and returns a structured verdict',
|
|
222
222
|
goal: 'You classify the sentiment of the user message.',
|
|
@@ -234,14 +234,14 @@ rather than signalling that it was short-circuited.
|
|
|
234
234
|
|
|
235
235
|
```typescript
|
|
236
236
|
prepareStep: ({ stepNumber, tools }) => {
|
|
237
|
-
if (stepNumber >= 1) tools.length = 0
|
|
237
|
+
if (stepNumber >= 1) tools.length = 0 // withdraw tools after the first step
|
|
238
238
|
}
|
|
239
239
|
```
|
|
240
240
|
|
|
241
241
|
### Tool approval
|
|
242
242
|
|
|
243
243
|
A tool that should pause for a human sets `approvalRequired: true` (with an
|
|
244
|
-
optional `approvalDescription`) on the
|
|
244
|
+
optional `approvalDescription`) on the _function_, not on the agent. The run then
|
|
245
245
|
resolves with `status: 'suspended'` and `pendingApprovals`, and streaming emits
|
|
246
246
|
`approval-request`. Answer with `rpc.agent.approve(runId, approvals)`.
|
|
247
247
|
|
|
@@ -282,10 +282,10 @@ export const completeTodo = pikkuFunc({
|
|
|
282
282
|
})
|
|
283
283
|
|
|
284
284
|
// agents/todo-assistant.agent.ts
|
|
285
|
-
import {
|
|
286
|
-
import { ref } from '#pikku/
|
|
285
|
+
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
286
|
+
import { ref } from '#pikku/function'
|
|
287
287
|
|
|
288
|
-
export const todoAssistant =
|
|
288
|
+
export const todoAssistant = pikkuAgent({
|
|
289
289
|
name: 'todo-assistant',
|
|
290
290
|
description: 'A helpful assistant that manages todos',
|
|
291
291
|
role: 'You are an assistant that manages a user’s todo list.',
|
|
@@ -299,7 +299,7 @@ export const todoAssistant = pikkuAIAgent({
|
|
|
299
299
|
ref('todos:createTodo'),
|
|
300
300
|
ref('todos:completeTodo'),
|
|
301
301
|
],
|
|
302
|
-
memory: { storage: '
|
|
302
|
+
memory: { storage: 'agentStorage', lastMessages: 20 },
|
|
303
303
|
maxSteps: 5,
|
|
304
304
|
temperature: 0.7,
|
|
305
305
|
})
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
name: pikku-ai-vercel
|
|
3
3
|
description: >-
|
|
4
4
|
Use when setting up AI agent execution with the Vercel AI SDK in a Pikku app. Covers
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@pikku/ai-vercel. DO NOT TRIGGER when: user asks about AI agent wiring (use pikku-
|
|
5
|
+
VercelAgentRunner for streaming and non-streaming AI agent steps. TRIGGER when: code uses
|
|
6
|
+
VercelAgentRunner, user asks about Vercel AI SDK integration, AI agent runners, or
|
|
7
|
+
@pikku/ai-vercel. DO NOT TRIGGER when: user asks about AI agent wiring (use pikku-agent) or
|
|
8
8
|
voice I/O (use pikku-ai-voice).
|
|
9
9
|
installGroups: [core]
|
|
10
10
|
---
|
|
@@ -21,7 +21,7 @@ Use this skill as an execution checklist, not reference material.
|
|
|
21
21
|
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
22
22
|
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
23
23
|
|
|
24
|
-
`@pikku/ai-vercel` provides an AI agent runner backed by the [Vercel AI SDK](https://sdk.vercel.ai/). Implements `
|
|
24
|
+
`@pikku/ai-vercel` provides an AI agent runner backed by the [Vercel AI SDK](https://sdk.vercel.ai/). Implements `AgentRunnerService` from `@pikku/core`.
|
|
25
25
|
|
|
26
26
|
## Installation
|
|
27
27
|
|
|
@@ -31,12 +31,12 @@ yarn add @pikku/ai-vercel ai @ai-sdk/openai # or any AI SDK provider
|
|
|
31
31
|
|
|
32
32
|
## API Reference
|
|
33
33
|
|
|
34
|
-
### `
|
|
34
|
+
### `VercelAgentRunner`
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
|
-
import {
|
|
37
|
+
import { VercelAgentRunner } from '@pikku/ai-vercel'
|
|
38
38
|
|
|
39
|
-
const runner = new
|
|
39
|
+
const runner = new VercelAgentRunner(
|
|
40
40
|
providers: Record<string, any>, // provider name → AI SDK provider
|
|
41
41
|
providerFactory?: (apiKey: string) => Record<string, any>,
|
|
42
42
|
allowedAttachmentHosts?: string[]
|
|
@@ -45,8 +45,8 @@ const runner = new VercelAIAgentRunner(
|
|
|
45
45
|
|
|
46
46
|
**Methods:**
|
|
47
47
|
|
|
48
|
-
- `stream(params:
|
|
49
|
-
- `run(params:
|
|
48
|
+
- `stream(params: AgentRunnerParams, channel: AgentStreamChannel): Promise<AgentStepResult>` — Stream AI responses with tool calls
|
|
49
|
+
- `run(params: AgentRunnerParams): Promise<AgentStepResult>` — Execute a single AI step (non-streaming)
|
|
50
50
|
- `transcribe({ model, audio, … })` / `generateSpeech({ model, text, voice, … })` — what `voiceInput`/`voiceOutput` call; see `pikku-ai-voice`
|
|
51
51
|
- `generateImage`, `embed`, `embedMany`, `rerank` — the remaining AI SDK surfaces
|
|
52
52
|
- `withApiKey(apiKey)` — returns a **new** runner built from `providerFactory`; returns `this` unchanged when no factory was supplied or the key is blank. This is the per-user-credential path
|
|
@@ -75,7 +75,7 @@ gateway-routed providers after construction.
|
|
|
75
75
|
### Basic Setup
|
|
76
76
|
|
|
77
77
|
```typescript
|
|
78
|
-
import {
|
|
78
|
+
import { VercelAgentRunner } from '@pikku/ai-vercel'
|
|
79
79
|
import { createOpenAI } from '@ai-sdk/openai'
|
|
80
80
|
import { createAnthropic } from '@ai-sdk/anthropic'
|
|
81
81
|
|
|
@@ -86,19 +86,19 @@ const createSingletonServices = pikkuServices(async (config, { secrets }) => {
|
|
|
86
86
|
apiKey: (await secrets.getSecret('OPENAI_API_KEY')).reveal(),
|
|
87
87
|
})
|
|
88
88
|
}
|
|
89
|
-
return { config,
|
|
89
|
+
return { config, agentRunner: new VercelAgentRunner(providers) }
|
|
90
90
|
})
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
The service key is **`
|
|
93
|
+
The service key is **`agentRunner`** — that is the name the agent wiring looks
|
|
94
94
|
up. Registering it as `aiRunner` leaves every agent unable to call a model.
|
|
95
95
|
|
|
96
96
|
### With an agent
|
|
97
97
|
|
|
98
98
|
```typescript
|
|
99
|
-
import {
|
|
99
|
+
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
100
100
|
|
|
101
|
-
export const assistant =
|
|
101
|
+
export const assistant = pikkuAgent({
|
|
102
102
|
name: 'assistant',
|
|
103
103
|
description: 'Answers questions',
|
|
104
104
|
goal: 'You are a helpful assistant.',
|
|
@@ -106,16 +106,16 @@ export const assistant = pikkuAIAgent({
|
|
|
106
106
|
})
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
There is no `
|
|
110
|
-
generated agent types. See `pikku-
|
|
109
|
+
There is no `wireAgent` — agents are declared with `pikkuAgent` from the
|
|
110
|
+
generated agent types. See `pikku-agent` for the full config.
|
|
111
111
|
|
|
112
112
|
### Testing without a real provider
|
|
113
113
|
|
|
114
|
-
Replacing the
|
|
114
|
+
Replacing the _provider_ rather than the runner keeps every code path under test
|
|
115
115
|
real — tool loop, streaming, memory, approvals — and only scripts the replies.
|
|
116
116
|
Sealing it with `'*'` means no model string, including ones added later, can
|
|
117
117
|
reach a live endpoint:
|
|
118
118
|
|
|
119
119
|
```typescript
|
|
120
|
-
new
|
|
120
|
+
new VercelAgentRunner({ '*': createMockLlmProvider() })
|
|
121
121
|
```
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
name: pikku-ai-voice
|
|
3
3
|
description: >-
|
|
4
4
|
Use when adding voice input (speech-to-text) or voice output (text-to-speech) to AI agents in a
|
|
5
|
-
Pikku app. Covers the voiceInput/voiceOutput AI middleware from @pikku/core/
|
|
5
|
+
Pikku app. Covers the voiceInput/voiceOutput AI middleware from @pikku/core/agent, per-script
|
|
6
6
|
voices, and barge-in. TRIGGER when: code uses voiceInput, voiceOutput, or user asks about voice
|
|
7
7
|
agents, speech-to-text, text-to-speech, transcription, or @pikku/ai-voice. DO NOT TRIGGER when:
|
|
8
|
-
user asks about AI agent wiring generally (use pikku-
|
|
8
|
+
user asks about AI agent wiring generally (use pikku-agent) or the runner itself (use
|
|
9
9
|
pikku-ai-vercel).
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -27,14 +27,14 @@ The package still publishes, but its entire source is `export {}` — there are
|
|
|
27
27
|
`STTService`/`TTSService` interfaces and nothing to import. Do not add it as a
|
|
28
28
|
dependency.
|
|
29
29
|
|
|
30
|
-
Voice now lives in **`@pikku/core/
|
|
31
|
-
speech models are reached through the `
|
|
30
|
+
Voice now lives in **`@pikku/core/agent`** as two AI middlewares, and the
|
|
31
|
+
speech models are reached through the `agentRunner` (`transcribe` /
|
|
32
32
|
`generateSpeech`) rather than through separate services. See `pikku-ai-vercel`.
|
|
33
33
|
|
|
34
34
|
## API Reference
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
|
-
import { voiceInput, voiceOutput } from '@pikku/core/
|
|
37
|
+
import { voiceInput, voiceOutput } from '@pikku/core/agent'
|
|
38
38
|
|
|
39
39
|
voiceInput(config?: {
|
|
40
40
|
model?: string // transcription model — required in practice
|
|
@@ -54,9 +54,9 @@ voiceOutput(config?: {
|
|
|
54
54
|
})
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Both attach through the agent's **`
|
|
58
|
-
`middlewareHooks` option, and the agent is declared with `
|
|
59
|
-
is no `
|
|
57
|
+
Both attach through the agent's **`agentMiddleware`** array, not a
|
|
58
|
+
`middlewareHooks` option, and the agent is declared with `pikkuAgent` — there
|
|
59
|
+
is no `wireAgent`.
|
|
60
60
|
|
|
61
61
|
### `voiceInput` — audio in, text in its place
|
|
62
62
|
|
|
@@ -74,7 +74,7 @@ spoken, which is why it records two shared-notes keys on the way past:
|
|
|
74
74
|
|
|
75
75
|
Behaviours that decide how a voice loop should be written:
|
|
76
76
|
|
|
77
|
-
- **It is a no-op without `
|
|
77
|
+
- **It is a no-op without `agentRunner.transcribe`** — no error, the audio
|
|
78
78
|
simply passes through untouched.
|
|
79
79
|
- **`config.model` is required once audio actually arrives**, and throws then
|
|
80
80
|
rather than at wiring time.
|
|
@@ -84,7 +84,7 @@ Behaviours that decide how a voice loop should be written:
|
|
|
84
84
|
distinct from a transcription failure, which is worth reporting.
|
|
85
85
|
- **Non-speech means an empty transcript, and nothing cleverer.** There was a
|
|
86
86
|
per-segment confidence gate here and it was removed: Whisper is
|
|
87
|
-
subtitle-trained, so it is
|
|
87
|
+
subtitle-trained, so it is _confident_ when it invents ("Thank you." scored
|
|
88
88
|
better than the real sentence beside it). Pick an ASR that returns an empty
|
|
89
89
|
string on silence rather than trying to filter one that doesn't.
|
|
90
90
|
- Audio arrives either inline (base64 `data`) or as a `url` fetched through
|
|
@@ -112,7 +112,7 @@ awaits the chain, and emits `audio-done` before the `done` event.
|
|
|
112
112
|
### `speakableScripts` — declare what the model can pronounce
|
|
113
113
|
|
|
114
114
|
Handed a script it has no voice for, a speech model typically neither fails nor
|
|
115
|
-
stays quiet: Kokoro reads out the
|
|
115
|
+
stays quiet: Kokoro reads out the _letter names_ — 24 seconds of "Arabic meem,
|
|
116
116
|
Arabic ra" for a one-line sentence. Declaring the range leaves anything outside
|
|
117
117
|
it unspoken and reports it once per reply as a `voice-unsupported` data event.
|
|
118
118
|
|
|
@@ -133,15 +133,15 @@ fallback)` are exported if you need the same decision outside the middleware.
|
|
|
133
133
|
## Usage Pattern
|
|
134
134
|
|
|
135
135
|
```typescript
|
|
136
|
-
import {
|
|
137
|
-
import { voiceInput, voiceOutput } from '@pikku/core/
|
|
136
|
+
import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
|
|
137
|
+
import { voiceInput, voiceOutput } from '@pikku/core/agent'
|
|
138
138
|
|
|
139
|
-
export const voiceAssistant =
|
|
139
|
+
export const voiceAssistant = pikkuAgent({
|
|
140
140
|
name: 'voice-assistant',
|
|
141
141
|
description: 'Holds a spoken conversation',
|
|
142
142
|
goal: 'You are a voice assistant. You are being listened to, not read.',
|
|
143
143
|
model: 'openai/gpt-5-mini',
|
|
144
|
-
|
|
144
|
+
agentMiddleware: [
|
|
145
145
|
voiceInput({ model: 'deepinfra/openai/whisper-large-v3-turbo' }),
|
|
146
146
|
voiceOutput({
|
|
147
147
|
model: 'deepinfra/hexgrad/Kokoro-82M',
|
|
@@ -38,11 +38,13 @@ An event only persists when the function opts in with **`audit: true`** — othe
|
|
|
38
38
|
```typescript
|
|
39
39
|
import { NoopAuditService, createInvocationAudit } from '@pikku/core/services'
|
|
40
40
|
|
|
41
|
-
export const createSingletonServices = pikkuServices(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
41
|
+
export const createSingletonServices = pikkuServices(
|
|
42
|
+
async (config, existing) => {
|
|
43
|
+
// Prod platforms may inject a queue-backed sink as existing.audit.
|
|
44
|
+
const audit = existing?.audit ?? new NoopAuditService()
|
|
45
|
+
return { ...existing, config, /* ... */ audit }
|
|
46
|
+
}
|
|
47
|
+
)
|
|
46
48
|
|
|
47
49
|
// auditLog is created per invocation from the sink. Returned unconditionally so
|
|
48
50
|
// a write from a function that forgot `audit: true` warns instead of vanishing.
|
|
@@ -67,12 +69,17 @@ Mark the function `audit: true` and call `auditLog?.write(...)`. Domain history
|
|
|
67
69
|
|
|
68
70
|
```typescript
|
|
69
71
|
export const cancelInvoice = pikkuFunc({
|
|
70
|
-
audit: true,
|
|
72
|
+
audit: true, // REQUIRED — else write() is a no-op
|
|
71
73
|
input: CancelInvoiceInput,
|
|
72
74
|
output: CancelInvoiceOutput,
|
|
73
75
|
func: async ({ kysely, auditLog }, { invoiceId }, { session }) => {
|
|
74
|
-
const inv = await kysely
|
|
75
|
-
|
|
76
|
+
const inv = await kysely
|
|
77
|
+
.selectFrom('invoice') /* ... */
|
|
78
|
+
.executeTakeFirstOrThrow()
|
|
79
|
+
await kysely
|
|
80
|
+
.updateTable('invoice')
|
|
81
|
+
.set({ status: 'cancelled' }) /* ... */
|
|
82
|
+
.execute()
|
|
76
83
|
|
|
77
84
|
await auditLog?.write({
|
|
78
85
|
type: 'invoice.update',
|
|
@@ -108,7 +115,10 @@ import { createAuditedKysely } from '@pikku/kysely'
|
|
|
108
115
|
export const createWireServices = pikkuWireServices(async (services, wire) => {
|
|
109
116
|
if (!services.audit) return {}
|
|
110
117
|
const auditLog = createInvocationAudit(services.audit, wire)
|
|
111
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
auditLog,
|
|
120
|
+
kysely: createAuditedKysely(services.kysely, { audit: auditLog }),
|
|
121
|
+
}
|
|
112
122
|
})
|
|
113
123
|
```
|
|
114
124
|
|
|
@@ -172,15 +182,20 @@ const rows = await kysely
|
|
|
172
182
|
|
|
173
183
|
```typescript
|
|
174
184
|
type AuditEvent = {
|
|
175
|
-
type: string
|
|
185
|
+
type: string // e.g. 'invoice.update'
|
|
176
186
|
source: 'auto' | 'explicit'
|
|
177
|
-
occurredAt: string
|
|
187
|
+
occurredAt: string // auto-filled by auditLog
|
|
178
188
|
eventId?: string
|
|
179
189
|
outcome?: 'success' | 'failed' | 'denied'
|
|
180
|
-
functionId
|
|
190
|
+
functionId?
|
|
191
|
+
wireType?
|
|
192
|
+
wireId?
|
|
193
|
+
traceId?
|
|
194
|
+
transactionId?
|
|
195
|
+
queryId? // auto
|
|
181
196
|
userIdentity?: { userId?; orgId?; pikkuUserId? } // auto from wire session
|
|
182
197
|
input?: unknown
|
|
183
|
-
metadata?: Record<string, unknown>
|
|
198
|
+
metadata?: Record<string, unknown> // your domain payload
|
|
184
199
|
}
|
|
185
200
|
```
|
|
186
201
|
|
|
@@ -64,12 +64,12 @@ provision an S3 bucket per logical bucket — the config takes only one.
|
|
|
64
64
|
|
|
65
65
|
### Behaviours worth knowing before you rely on them
|
|
66
66
|
|
|
67
|
-
- **`signURL` fails open.** A signing error is logged and the
|
|
67
|
+
- **`signURL` fails open.** A signing error is logged and the _unsigned_ URL is
|
|
68
68
|
returned rather than thrown. If your CloudFront distribution is private the
|
|
69
69
|
client then gets a 403; if it isn't, you have just handed out an unrestricted
|
|
70
70
|
link. Check that `signConfig` is a valid CloudFront key pair at boot.
|
|
71
71
|
- **`signContentKey` builds `https://<bucketName>/<bucket>/<contentKey>`** — it
|
|
72
|
-
uses `bucketName` as the
|
|
72
|
+
uses `bucketName` as the _host_. For signed content the value must therefore be
|
|
73
73
|
your CloudFront domain, not a plain bucket name, which also means the same
|
|
74
74
|
config field is doing two jobs.
|
|
75
75
|
- **Presigned upload URLs expire after a fixed 3600s.** It is not configurable
|