@pikku/core 0.12.91 → 0.12.93
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 +102 -0
- package/dist/errors/error-handler.d.ts +11 -0
- package/dist/errors/error-handler.js +8 -0
- package/dist/errors/serialized-error.d.ts +3 -0
- package/dist/function/function-runner.d.ts +1 -1
- package/dist/function/function-runner.js +3 -3
- package/dist/function/functions.types.d.ts +33 -21
- package/dist/middleware/auth-apikey.d.ts +7 -0
- package/dist/middleware/auth-apikey.js +6 -0
- package/dist/middleware/auth-bearer.d.ts +3 -0
- package/dist/middleware/auth-bearer.js +2 -0
- package/dist/middleware/auth-cookie.d.ts +9 -0
- package/dist/middleware/auth-cookie.js +6 -4
- package/dist/middleware/cors.d.ts +8 -0
- package/dist/middleware/cors.js +2 -0
- package/dist/middleware/middleware-factories.d.ts +14 -0
- package/dist/middleware/middleware-factories.js +14 -0
- package/dist/services/local-secrets.js +1 -1
- package/dist/services/persona-sign-in.d.ts +0 -2
- package/dist/services/persona-sign-in.js +15 -66
- package/dist/services/personas-service.d.ts +4 -0
- package/dist/services/personas-service.js +4 -0
- package/dist/services/secret-service.d.ts +0 -1
- package/dist/testing/service-tests/secret-service-tests.js +1 -1
- package/dist/types/core.types.d.ts +5 -0
- package/dist/wirings/addon/wire-addon.d.ts +17 -15
- package/dist/wirings/addon/wire-addon.js +6 -0
- package/dist/wirings/addon/wire-remote-addon.d.ts +4 -0
- package/dist/wirings/addon/wire-remote-addon.js +4 -0
- package/dist/wirings/agent/agent-prepare.js +2 -2
- package/dist/wirings/agent/agent.types.d.ts +31 -27
- package/dist/wirings/agent-scorer/agent-scorer.d.ts +4 -0
- package/dist/wirings/agent-scorer/agent-scorer.js +4 -0
- package/dist/wirings/channel/channel.types.d.ts +11 -0
- package/dist/wirings/cli/cli.types.d.ts +43 -1
- package/dist/wirings/cli/define-cli-commands.d.ts +6 -1
- package/dist/wirings/cli/define-cli-commands.js +6 -1
- package/dist/wirings/credential/credential.types.d.ts +7 -0
- package/dist/wirings/gateway/gateway.types.d.ts +23 -0
- package/dist/wirings/http/http.types.d.ts +51 -37
- package/dist/wirings/mcp/mcp.types.d.ts +27 -0
- package/dist/wirings/queue/queue.types.d.ts +6 -0
- package/dist/wirings/scheduler/scheduler.types.d.ts +5 -0
- package/dist/wirings/secret/secret.types.d.ts +9 -14
- package/dist/wirings/secret/secret.types.js +7 -0
- package/dist/wirings/trigger/trigger.types.d.ts +4 -1
- package/dist/wirings/variable/variable.types.d.ts +14 -13
- package/dist/wirings/variable/variable.types.js +7 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +7 -0
- package/dist/wirings/workflow/scenario-cookie-jar.d.ts +6 -0
- package/dist/wirings/workflow/scenario-cookie-jar.js +6 -0
- package/dist/wirings/workflow/scenario-poll.d.ts +6 -0
- package/dist/wirings/workflow/scenario-poll.js +6 -0
- package/dist/wirings/workflow/scenario-step-guards.d.ts +6 -0
- package/dist/wirings/workflow/scenario-step-guards.js +6 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +4 -0
- package/dist/wirings/workflow/workflow-errors.js +4 -0
- package/package.json +1 -1
- package/src/app-leaf-surface.test.ts +2 -2
- package/src/errors/error-handler.ts +11 -0
- package/src/errors/serialized-error.ts +3 -0
- package/src/function/function-runner.ts +4 -4
- package/src/function/functions.types.ts +33 -21
- package/src/middleware/auth-apikey.ts +7 -0
- package/src/middleware/auth-bearer.ts +3 -0
- package/src/middleware/auth-cookie.ts +9 -4
- package/src/middleware/cors.ts +8 -0
- package/src/middleware/middleware-factories.ts +14 -0
- package/src/services/local-secrets.test.ts +2 -2
- package/src/services/local-secrets.ts +1 -1
- package/src/services/persona-sign-in.test.ts +28 -34
- package/src/services/persona-sign-in.ts +16 -88
- package/src/services/personas-service.ts +4 -0
- package/src/services/secret-service.ts +0 -1
- package/src/testing/service-tests/secret-service-tests.ts +1 -1
- package/src/types/core.types.ts +5 -0
- package/src/wirings/addon/addon-auth-tags.test.ts +4 -4
- package/src/wirings/addon/wire-addon.ts +17 -15
- package/src/wirings/addon/wire-remote-addon.ts +4 -0
- package/src/wirings/agent/agent-authorization.test.ts +7 -3
- package/src/wirings/agent/agent-prepare.ts +2 -2
- package/src/wirings/agent/agent.types.ts +31 -27
- package/src/wirings/agent-scorer/agent-scorer.ts +4 -0
- package/src/wirings/channel/channel.types.ts +11 -0
- package/src/wirings/cli/cli.types.ts +44 -1
- package/src/wirings/cli/define-cli-commands.ts +6 -1
- package/src/wirings/credential/credential.types.ts +7 -0
- package/src/wirings/gateway/gateway-authorization.test.ts +3 -3
- package/src/wirings/gateway/gateway.types.ts +23 -0
- package/src/wirings/http/http-runner-addon-ref.test.ts +1 -1
- package/src/wirings/http/http.types.ts +94 -69
- package/src/wirings/mcp/mcp.types.ts +27 -0
- package/src/wirings/queue/queue.types.ts +6 -0
- package/src/wirings/scheduler/scheduler.types.ts +5 -0
- package/src/wirings/secret/secret.types.ts +9 -14
- package/src/wirings/trigger/trigger.types.ts +4 -1
- package/src/wirings/variable/variable.types.ts +14 -13
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/scenario-cookie-jar.ts +6 -0
- package/src/wirings/workflow/scenario-poll.ts +6 -0
- package/src/wirings/workflow/scenario-step-guards.ts +6 -0
- package/src/wirings/workflow/workflow-errors.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
} from '../../pikku-state.js'
|
|
9
9
|
import { assertAgentAuthorized } from './agent-prepare.js'
|
|
10
10
|
import { clearPermissionsCache } from '../../permissions.js'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ForbiddenError,
|
|
13
|
+
MissingScopeError,
|
|
14
|
+
MissingSessionError,
|
|
15
|
+
} from '../../errors/errors.js'
|
|
12
16
|
import type { CoreAgent } from './agent.types.js'
|
|
13
17
|
import type { CoreUserSession } from '../../types/core.types.js'
|
|
14
18
|
|
|
@@ -73,7 +77,7 @@ describe('assertAgentAuthorized', () => {
|
|
|
73
77
|
await assertAgentAuthorized(agent, {}, null)
|
|
74
78
|
})
|
|
75
79
|
|
|
76
|
-
test('throws
|
|
80
|
+
test('throws MissingSessionError when auth is explicitly true and no session exists', async () => {
|
|
77
81
|
const agent = addAgent('needs-session', { auth: true })
|
|
78
82
|
await assert.rejects(
|
|
79
83
|
() =>
|
|
@@ -82,7 +86,7 @@ describe('assertAgentAuthorized', () => {
|
|
|
82
86
|
{ sessionService: sessionService(undefined) },
|
|
83
87
|
null
|
|
84
88
|
),
|
|
85
|
-
|
|
89
|
+
MissingSessionError
|
|
86
90
|
)
|
|
87
91
|
})
|
|
88
92
|
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type PermissionWire,
|
|
23
23
|
} from '../../permissions.js'
|
|
24
24
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
25
|
-
import { ForbiddenError } from '../../errors/errors.js'
|
|
25
|
+
import { ForbiddenError, MissingSessionError } from '../../errors/errors.js'
|
|
26
26
|
import { verifyScopes } from '../../scopes.js'
|
|
27
27
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
28
28
|
import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
|
|
@@ -315,7 +315,7 @@ export async function assertAgentAuthorized(
|
|
|
315
315
|
: undefined
|
|
316
316
|
|
|
317
317
|
if (agent.auth === true && !session) {
|
|
318
|
-
throw new
|
|
318
|
+
throw new MissingSessionError('Authentication required')
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
verifyScopes(agent.scopes, session)
|
|
@@ -183,6 +183,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
183
183
|
State extends Record<string, unknown> = Record<string, unknown>,
|
|
184
184
|
SingletonServices extends CoreSingletonServices = CoreSingletonServices,
|
|
185
185
|
> {
|
|
186
|
+
/** Rewrites what the model is about to see — messages and instructions — before each turn. */
|
|
186
187
|
modifyInput?: (
|
|
187
188
|
services: SingletonServices,
|
|
188
189
|
ctx: {
|
|
@@ -210,6 +211,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
210
211
|
| Promise<{ messages: AgentMessage[]; instructions: string }>
|
|
211
212
|
| { messages: AgentMessage[]; instructions: string }
|
|
212
213
|
|
|
214
|
+
/** Sees the model's output as it streams, for redaction or live inspection. Keeps its own `state` across chunks, unlike the shared run notes. */
|
|
213
215
|
modifyOutputStream?: (
|
|
214
216
|
services: SingletonServices,
|
|
215
217
|
ctx: {
|
|
@@ -262,6 +264,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
262
264
|
* and handed to anything that grades the run, and scrubbing the reply alone
|
|
263
265
|
* leaves them untouched.
|
|
264
266
|
*/
|
|
267
|
+
/** Rewrites the finished output of a turn, after streaming has completed. */
|
|
265
268
|
modifyOutput?: (
|
|
266
269
|
services: SingletonServices,
|
|
267
270
|
ctx: {
|
|
@@ -282,6 +285,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
282
285
|
toolCalls?: NonNullable<AgentStep['toolCalls']>
|
|
283
286
|
}
|
|
284
287
|
|
|
288
|
+
/** Runs before a tool executes, and may rewrite the arguments the model chose. Returning nothing leaves them as they are. */
|
|
285
289
|
beforeToolCall?: (
|
|
286
290
|
services: SingletonServices,
|
|
287
291
|
ctx: {
|
|
@@ -294,6 +298,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
294
298
|
| { args: Record<string, unknown> }
|
|
295
299
|
| void
|
|
296
300
|
|
|
301
|
+
/** Runs once a tool has returned, and may replace its result before the model sees it. */
|
|
297
302
|
afterToolCall?: (
|
|
298
303
|
services: SingletonServices,
|
|
299
304
|
ctx: {
|
|
@@ -305,6 +310,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
305
310
|
}
|
|
306
311
|
) => Promise<{ result: unknown } | void> | { result: unknown } | void
|
|
307
312
|
|
|
313
|
+
/** Runs at the end of each model turn. For observation — it cannot change what happened. */
|
|
308
314
|
afterStep?: (
|
|
309
315
|
services: SingletonServices,
|
|
310
316
|
ctx: {
|
|
@@ -323,6 +329,7 @@ export interface PikkuAgentMiddlewareHooks<
|
|
|
323
329
|
}
|
|
324
330
|
) => Promise<void> | void
|
|
325
331
|
|
|
332
|
+
/** Runs when a turn throws, with the step it failed on. For logging and cleanup; it does not swallow the error. */
|
|
326
333
|
onError?: (
|
|
327
334
|
services: SingletonServices,
|
|
328
335
|
ctx: {
|
|
@@ -347,58 +354,51 @@ export type CoreAgent<
|
|
|
347
354
|
Scope extends string = string,
|
|
348
355
|
Scorer extends string = string,
|
|
349
356
|
> = {
|
|
357
|
+
/** Unique across the project. It is how the agent is invoked and how its runs are grouped. */
|
|
350
358
|
name: string
|
|
359
|
+
/** What the agent is for. Another agent choosing whether to delegate to this one reads it, so write it for that reader. */
|
|
351
360
|
description: string
|
|
361
|
+
/** A one-line description for listings, where the full `description` is too long. */
|
|
352
362
|
summary?: string
|
|
363
|
+
/** Names of error classes this may throw, so each one's registered status is used instead of a 500. */
|
|
353
364
|
errors?: string[]
|
|
354
|
-
/**
|
|
355
|
-
* The three fields below are the system prompt. `buildInstructions` joins
|
|
356
|
-
* whichever are set with a blank line, always in this order — role, then
|
|
357
|
-
* personality, then goal — and appends the tool-usage rules if the agent has
|
|
358
|
-
* tools. Nothing checks them against each other: the split is there to keep a
|
|
359
|
-
* prompt legible, and text put in the wrong one still reaches the model.
|
|
360
|
-
*
|
|
361
|
-
* Who the agent is. 'You are a support engineer triaging inbound bugs.'
|
|
362
|
-
*/
|
|
365
|
+
/** Who the agent is. Joined with `personality` and `goal`, in that order, to form the system prompt. */
|
|
363
366
|
role?: string
|
|
364
367
|
/** How it should sound: tone, vocabulary, how much it says at a time. */
|
|
365
368
|
personality?: string
|
|
366
369
|
/** What it is for — the only one of the three that is required. */
|
|
367
370
|
goal: string
|
|
371
|
+
/** Which model to run on, as the provider names it. */
|
|
368
372
|
model: string
|
|
373
|
+
/** How much the model is allowed to vary its answer. Lower is more repeatable, which is what a tool-driving agent usually wants. */
|
|
369
374
|
temperature?: number
|
|
370
375
|
/** Ownership/partitioning of this agent's threads and runs. Defaults to `'user'`. */
|
|
371
376
|
sessionScope?: SessionScope
|
|
377
|
+
/** Functions the model may call, given to it as tools. A `readonly` one may be called without asking first. */
|
|
372
378
|
tools?: unknown[]
|
|
379
|
+
/** Other agents this one may hand work to. `agentMode` decides whether it delegates or supervises. */
|
|
373
380
|
agents?: unknown[]
|
|
381
|
+
/** Workflows the model may start, for work too long to sit inside one agent run. */
|
|
374
382
|
workflows?: unknown[]
|
|
375
|
-
/**
|
|
376
|
-
* Grades this agent's finished runs on live traffic, named by the generated
|
|
377
|
-
* `ScorerName` union rather than by `ref()` — a scorer is not a function, so
|
|
378
|
-
* there is nothing in the function map for a ref to resolve against.
|
|
379
|
-
*
|
|
380
|
-
* A reference-based judge listed here is never sampled: live traffic has no
|
|
381
|
-
* answer key. Scenarios name scorers directly and may grade with scorers an
|
|
382
|
-
* agent does not ship with.
|
|
383
|
-
*/
|
|
383
|
+
/** Grades finished runs on live traffic. A reference-based judge is never sampled here — live traffic has no answer key. */
|
|
384
384
|
scorers?: Scorer[]
|
|
385
|
+
/** `delegate` hands a sub-agent the task and takes its answer; `supervise` keeps this agent in the loop over each step. */
|
|
385
386
|
agentMode?: 'delegate' | 'supervise'
|
|
387
|
+
/** What the agent carries between runs, and how much of it. */
|
|
386
388
|
memory?: AgentMemoryConfig
|
|
389
|
+
/** How many model turns a single run may take before it is stopped. The guard against a tool loop that never converges. */
|
|
387
390
|
maxSteps?: number
|
|
391
|
+
/** Whether the model may answer without calling a tool (`auto`), must call one (`required`), or may not (`none`). */
|
|
388
392
|
toolChoice?: 'auto' | 'required' | 'none'
|
|
389
|
-
/**
|
|
390
|
-
* Per-provider model settings, keyed by provider id and passed through
|
|
391
|
-
* untouched — for anything only one vendor offers, which the fields above
|
|
392
|
-
* deliberately do not try to unify.
|
|
393
|
-
*
|
|
394
|
-
* `{ openai: { reasoningEffort: 'minimal' } }` is the one that matters for
|
|
395
|
-
* voice: on gpt-5-mini it measured 0.9s to first token against 2.5s at the
|
|
396
|
-
* default, and a spoken reply is waited through rather than skimmed.
|
|
397
|
-
*/
|
|
393
|
+
/** Per-provider settings passed through untouched, for what only one vendor offers. */
|
|
398
394
|
providerOptions?: AIProviderOptions
|
|
395
|
+
/** The schema of what starts a run, which is also its input type. */
|
|
399
396
|
input?: unknown
|
|
397
|
+
/** The schema the final answer must match, which is also its return type. Without one the agent returns free text. */
|
|
400
398
|
output?: unknown
|
|
399
|
+
/** Filters this agent in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
401
400
|
tags?: string[]
|
|
401
|
+
/** Runs before each model turn, to change what that turn sees or to stop the run. This is where a step budget or a tool narrowing goes. */
|
|
402
402
|
prepareStep?: (ctx: {
|
|
403
403
|
stepNumber: number
|
|
404
404
|
messages: AgentMessage[]
|
|
@@ -407,8 +407,11 @@ export type CoreAgent<
|
|
|
407
407
|
model: string
|
|
408
408
|
stop: () => void
|
|
409
409
|
}) => void | Promise<void>
|
|
410
|
+
/** Wraps the whole run: auth, tracing, spend limits. */
|
|
410
411
|
middleware?: PikkuMiddleware[]
|
|
412
|
+
/** Wraps each message when the agent is driven over a channel. */
|
|
411
413
|
channelMiddleware?: CorePikkuChannelMiddleware<any, any>[]
|
|
414
|
+
/** Hooks around each step and each tool call — the place to inspect, rewrite or veto what the model is about to do. */
|
|
412
415
|
agentMiddleware?: PikkuAgentMiddlewareHooks<any, any>[]
|
|
413
416
|
/**
|
|
414
417
|
* Whether a session is required to run this agent. Defaults to `false`, since
|
|
@@ -427,6 +430,7 @@ export type CoreAgent<
|
|
|
427
430
|
* `#pikku/scopes`, so an undeclared scope is a compile error.
|
|
428
431
|
*/
|
|
429
432
|
scopes?: Scope[]
|
|
433
|
+
/** Checks that run before the agent starts. Grouped names OR together, so any one passing admits the caller. */
|
|
430
434
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
431
435
|
}
|
|
432
436
|
|
|
@@ -18,6 +18,8 @@ const assertSampleRate = (name: string, sampleRate: number | undefined) => {
|
|
|
18
18
|
/**
|
|
19
19
|
* A heuristic scorer: pure code over the finished run, no model call, so it
|
|
20
20
|
* grades on the fast lane.
|
|
21
|
+
*
|
|
22
|
+
* @example snippet: agentScorer
|
|
21
23
|
*/
|
|
22
24
|
export const pikkuAgentScorer = <Services = any>(config: {
|
|
23
25
|
name: string
|
|
@@ -49,6 +51,8 @@ export const pikkuAgentScorer = <Services = any>(config: {
|
|
|
49
51
|
* The rubric field is `goal`, matching `pikkuAgent`'s prompt vocabulary — a
|
|
50
52
|
* judge is a degenerate agent, and should use the same word for the same thing.
|
|
51
53
|
* `prompt` is the escape hatch for non-standard framing.
|
|
54
|
+
*
|
|
55
|
+
* @example snippet: agentJudge
|
|
52
56
|
*/
|
|
53
57
|
export const pikkuAgentJudge = <Services = any>(config: {
|
|
54
58
|
name: string
|
|
@@ -98,8 +98,11 @@ export type CoreChannel<
|
|
|
98
98
|
PikkuPermission = CorePikkuPermission<ChannelData>,
|
|
99
99
|
PikkuMiddleware = CorePikkuMiddleware,
|
|
100
100
|
> = {
|
|
101
|
+
/** Unique across the project. It is how the channel is addressed in `pikku meta` and by the generated client. */
|
|
101
102
|
name: string
|
|
103
|
+
/** The path a client opens the socket on. */
|
|
102
104
|
route: Channel
|
|
105
|
+
/** Runs once when a client connects, before any message. Its return value is sent as the first message. */
|
|
103
106
|
onConnect?:
|
|
104
107
|
| ChannelConnect
|
|
105
108
|
| {
|
|
@@ -112,7 +115,9 @@ export type CoreChannel<
|
|
|
112
115
|
func?: ChannelDisconnect
|
|
113
116
|
middleware?: PikkuMiddleware[]
|
|
114
117
|
}
|
|
118
|
+
/** Handles any message that no `onMessageWiring` entry claimed. Without one, an unrouted message is dropped. */
|
|
115
119
|
onMessage?: ChannelFunctionMessage
|
|
120
|
+
/** Routes messages by a key in their payload: the outer key is the field to switch on, the inner key its value. This is how one socket carries many operations. */
|
|
116
121
|
onMessageWiring?: Record<
|
|
117
122
|
string,
|
|
118
123
|
Record<
|
|
@@ -125,17 +130,23 @@ export type CoreChannel<
|
|
|
125
130
|
}
|
|
126
131
|
>
|
|
127
132
|
>
|
|
133
|
+
/** Wraps the connection: it runs on connect, where the session is established, not on every message. */
|
|
128
134
|
middleware?: PikkuMiddleware[]
|
|
135
|
+
/** Wraps every message, which is where per-message concerns like rate limiting belong. */
|
|
129
136
|
channelMiddleware?: Array<
|
|
130
137
|
CorePikkuChannelMiddleware | CorePikkuChannelMiddlewareFactory
|
|
131
138
|
>
|
|
139
|
+
/** Whether opening the channel requires a session. Defaults to true — a channel is closed unless it says otherwise. */
|
|
132
140
|
auth?: boolean
|
|
141
|
+
/** Whether this channel carries binary frames. Text frames are still parsed as JSON. */
|
|
133
142
|
binary?: boolean | null
|
|
143
|
+
/** Handles binary frames, which never reach `onMessage` because they are not JSON. Return a value to reply in kind. */
|
|
134
144
|
onBinaryMessage?: (
|
|
135
145
|
services: any,
|
|
136
146
|
data: BinaryData,
|
|
137
147
|
channel: PikkuChannel<ChannelData, any>
|
|
138
148
|
) => Promise<BinaryData | void> | BinaryData | void
|
|
149
|
+
/** Filters this channel in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
139
150
|
tags?: string[]
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -174,6 +174,12 @@ type SplitBySpace<S extends string> = S extends `${infer First} ${infer Rest}`
|
|
|
174
174
|
? []
|
|
175
175
|
: [S]
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Strip the `...` a variadic positional carries, so `[files...]` validates
|
|
179
|
+
* against the `files` key the runtime parser fills.
|
|
180
|
+
*/
|
|
181
|
+
type StripVariadic<S extends string> = S extends `${infer Name}...` ? Name : S
|
|
182
|
+
|
|
177
183
|
/**
|
|
178
184
|
* Recursively build a tuple by stripping brackets from each element
|
|
179
185
|
*/
|
|
@@ -182,7 +188,7 @@ type BuildParamsTuple<Parts extends readonly string[]> =
|
|
|
182
188
|
infer First extends string,
|
|
183
189
|
...infer Rest extends string[],
|
|
184
190
|
]
|
|
185
|
-
? [StripBrackets<First
|
|
191
|
+
? [StripVariadic<StripBrackets<First>>, ...BuildParamsTuple<Rest>]
|
|
186
192
|
: []
|
|
187
193
|
|
|
188
194
|
/**
|
|
@@ -227,16 +233,39 @@ export type CoreCLICommandConfig<
|
|
|
227
233
|
>,
|
|
228
234
|
Params extends string = string,
|
|
229
235
|
> = {
|
|
236
|
+
/**
|
|
237
|
+
* Positional arguments, written the way `--help` prints them: `<env>` is
|
|
238
|
+
* required, `[region]` optional. Each name must be a key of the function's
|
|
239
|
+
* input schema, so a typo is a compile error rather than an argument that
|
|
240
|
+
* silently never arrives.
|
|
241
|
+
*/
|
|
230
242
|
parameters?: ValidateParameters<Params, ExtractFunctionInput<FuncConfig>>
|
|
243
|
+
/** The function to run. Omit it on a command that exists only to hold `subcommands`. */
|
|
231
244
|
func?: FuncConfig
|
|
245
|
+
/** The heading shown above this command's own help. */
|
|
232
246
|
title?: string
|
|
247
|
+
/** The one-line summary listed beside the command name. */
|
|
233
248
|
description?: string
|
|
249
|
+
/**
|
|
250
|
+
* Prints the result. The function returns data and this is the only thing
|
|
251
|
+
* that writes to stdout, which is what lets one command serve both a human
|
|
252
|
+
* reading it and a script parsing it.
|
|
253
|
+
*/
|
|
234
254
|
render?: PikkuCLIRender
|
|
255
|
+
/**
|
|
256
|
+
* Flags, keyed by the input field each one fills. A field with no entry is
|
|
257
|
+
* still accepted as `--field`; an entry is how it gets a short form, a
|
|
258
|
+
* default, or help text.
|
|
259
|
+
*/
|
|
235
260
|
options?: {
|
|
236
261
|
[K in keyof ExtractFunctionInput<FuncConfig>]?: {
|
|
262
|
+
/** What this flag is for, as `--help` prints it. */
|
|
237
263
|
description?: string
|
|
264
|
+
/** A single-letter alias, so `--verbose` can also be `-v`. */
|
|
238
265
|
short?: string
|
|
266
|
+
/** How the argument is parsed. Inferred from the input schema when omitted. */
|
|
239
267
|
type?: CLIOptionType
|
|
268
|
+
/** Used when the flag is absent, and shown in `--help` so nobody has to guess it. */
|
|
240
269
|
default?: ExtractFunctionInput<FuncConfig>[K]
|
|
241
270
|
/**
|
|
242
271
|
* The values this option accepts. Rejected by the parser and listed in
|
|
@@ -246,13 +275,18 @@ export type CoreCLICommandConfig<
|
|
|
246
275
|
choices?: ReadonlyArray<ExtractFunctionInput<FuncConfig>[K]>
|
|
247
276
|
}
|
|
248
277
|
}
|
|
278
|
+
/** Wraps every run of this command. */
|
|
249
279
|
middleware?: PikkuMiddleware[]
|
|
280
|
+
/** Nested commands, keyed by the word that selects them: `deploy plan` is `deploy` with a `plan` subcommand. */
|
|
250
281
|
subcommands?: Record<
|
|
251
282
|
string,
|
|
252
283
|
CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender, any>
|
|
253
284
|
>
|
|
285
|
+
/** Whether running this requires a logged-in session. Defaults to true — a command is closed unless it says otherwise. */
|
|
254
286
|
auth?: boolean
|
|
287
|
+
/** Permission checks run before the function, for a command that not every logged-in user may run. */
|
|
255
288
|
permissions?: any[]
|
|
289
|
+
/** Runs when the parent is invoked with no subcommand named. */
|
|
256
290
|
isDefault?: boolean
|
|
257
291
|
}
|
|
258
292
|
|
|
@@ -308,14 +342,23 @@ export interface CoreCLI<
|
|
|
308
342
|
PikkuMiddleware,
|
|
309
343
|
PikkuCLIRender,
|
|
310
344
|
> {
|
|
345
|
+
/** The command name a user types. It is also what `--help` prints as the program. */
|
|
311
346
|
program: string
|
|
347
|
+
/** What the program is for, shown at the top of `--help`. */
|
|
312
348
|
description?: string
|
|
349
|
+
/** Top-level commands, keyed by the word that selects them. */
|
|
313
350
|
commands: Commands
|
|
351
|
+
/** Flags accepted before any command, for things that apply to the whole program. */
|
|
314
352
|
options?: CLIOptions<Options>
|
|
353
|
+
/** Wraps every command. */
|
|
315
354
|
middleware?: PikkuMiddleware[]
|
|
355
|
+
/** The fallback printer for commands that declare none of their own. */
|
|
316
356
|
render?: PikkuCLIRender
|
|
357
|
+
/** A one-line description for listings, where the full `description` is too long. */
|
|
317
358
|
summary?: string
|
|
359
|
+
/** Names of error classes any command may throw, so each one's registered exit behaviour is used. */
|
|
318
360
|
errors?: string[]
|
|
361
|
+
/** Filters this program in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
319
362
|
tags?: string[]
|
|
320
363
|
/**
|
|
321
364
|
* Requires a session on the websocket serving this program remotely. Has no
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { CoreCLICommandConfig } from './cli.types.js'
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Identity at runtime; exists so a commands record can be typed and composed
|
|
5
|
+
* before `wireCLI`.
|
|
6
|
+
*
|
|
7
|
+
* @example snippet: cliCommandContract
|
|
8
|
+
*/
|
|
4
9
|
export function defineCLICommands<
|
|
5
10
|
T extends Record<string, CoreCLICommandConfig<any, any, any, any>>,
|
|
6
11
|
>(commands: T): T {
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import type { OAuth2CredentialConfig } from '../secret/secret.types.js'
|
|
2
2
|
|
|
3
3
|
export type CoreCredential<T = unknown> = {
|
|
4
|
+
/** How the credential is asked for in code. Generated into `CredentialsMap`, so it is what `credentials.get` autocompletes. */
|
|
4
5
|
name: string
|
|
6
|
+
/** The name shown to whoever has to supply the value, who is often not the person who wrote this. */
|
|
5
7
|
displayName: string
|
|
8
|
+
/** What the credential is for and where to obtain one. */
|
|
6
9
|
description?: string
|
|
10
|
+
/** `singleton` is one value for the whole deployment; `wire` is one per user, supplied by them and stored against their account. */
|
|
7
11
|
type: 'singleton' | 'wire'
|
|
12
|
+
/** The shape of the value, validated when it is supplied rather than when it is first used. */
|
|
8
13
|
schema: T
|
|
14
|
+
/** Where to go to create one, shown next to the field asking for it. */
|
|
9
15
|
docsUrl?: string
|
|
16
|
+
/** Makes this an OAuth connection rather than a value pasted in: the user is sent to the provider and the tokens are stored for them. */
|
|
10
17
|
oauth2?: OAuth2CredentialConfig & {
|
|
11
18
|
appCredentialSecretId: string
|
|
12
19
|
}
|
|
@@ -280,7 +280,7 @@ describe('gateway handler authorization', () => {
|
|
|
280
280
|
|
|
281
281
|
const response = await postMessage('/webhooks/declared-session')
|
|
282
282
|
|
|
283
|
-
assert.equal(response.status,
|
|
283
|
+
assert.equal(response.status, 401)
|
|
284
284
|
assert.deepEqual(
|
|
285
285
|
calls,
|
|
286
286
|
[],
|
|
@@ -339,7 +339,7 @@ describe('gateway handler authorization', () => {
|
|
|
339
339
|
|
|
340
340
|
const response = await postMessage('/webhooks/gw-auth')
|
|
341
341
|
|
|
342
|
-
assert.equal(response.status,
|
|
342
|
+
assert.equal(response.status, 401)
|
|
343
343
|
assert.deepEqual(calls, [], 'auth: true must require a session')
|
|
344
344
|
})
|
|
345
345
|
|
|
@@ -365,7 +365,7 @@ describe('gateway handler authorization', () => {
|
|
|
365
365
|
|
|
366
366
|
const response = await postMessage('/webhooks/handler-auth')
|
|
367
367
|
|
|
368
|
-
assert.equal(response.status,
|
|
368
|
+
assert.equal(response.status, 401)
|
|
369
369
|
assert.deepEqual(calls, [], 'auth: true must require a session')
|
|
370
370
|
})
|
|
371
371
|
|
|
@@ -17,6 +17,10 @@ export interface GatewayAttachment {
|
|
|
17
17
|
filename?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* One message arriving from a gateway, normalised: who sent it, in which
|
|
22
|
+
* conversation, and what they said.
|
|
23
|
+
*/
|
|
20
24
|
export interface GatewayInboundMessage {
|
|
21
25
|
/** Platform-specific: a phone number, a Slack user id, and so on. */
|
|
22
26
|
senderId: string
|
|
@@ -26,15 +30,27 @@ export interface GatewayInboundMessage {
|
|
|
26
30
|
metadata?: Record<string, unknown>
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
/**
|
|
34
|
+
* One message to send back through a gateway — plain text, or the provider's
|
|
35
|
+
* own rich content.
|
|
36
|
+
*/
|
|
29
37
|
export interface GatewayOutboundMessage {
|
|
30
38
|
text?: string
|
|
31
39
|
richContent?: Record<string, unknown>
|
|
32
40
|
attachments?: GatewayAttachment[]
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
/**
|
|
44
|
+
* What a gateway's `verifyWebhook` returns — verified, with the response the
|
|
45
|
+
* provider expects back, or not.
|
|
46
|
+
*/
|
|
35
47
|
export type WebhookVerificationResult =
|
|
36
48
|
{ verified: true; response: unknown } | { verified: false }
|
|
37
49
|
|
|
50
|
+
/**
|
|
51
|
+
* What a gateway integration implements: parse an incoming event into a
|
|
52
|
+
* message, send one back, and open and close the connection.
|
|
53
|
+
*/
|
|
38
54
|
export interface GatewayAdapter {
|
|
39
55
|
name: string
|
|
40
56
|
/** Return null to ignore the event, e.g. a delivery receipt. */
|
|
@@ -70,14 +86,21 @@ export type CoreGateway<
|
|
|
70
86
|
> = Partial<
|
|
71
87
|
Pick<CommonWireMeta, 'title' | 'summary' | 'description' | 'errors'>
|
|
72
88
|
> & {
|
|
89
|
+
/** Unique across the project. It is how the gateway is addressed in `pikku meta` and in logs. */
|
|
73
90
|
name: string
|
|
91
|
+
/** How the platform reaches us: a `webhook` it posts to, a `websocket` it holds open, or a `listener` we open outward. */
|
|
74
92
|
type: GatewayTransportType
|
|
75
93
|
/** Required for 'webhook' and 'websocket'; unused for 'listener'. */
|
|
76
94
|
route?: string
|
|
95
|
+
/** Which service this speaks to — slack, whatsapp, discord. It selects the adapter's dialect, not the transport. */
|
|
77
96
|
platform?: string
|
|
97
|
+
/** Translates between the platform's message format and pikku's. A factory is called with services, for an adapter that needs a token or a client. */
|
|
78
98
|
adapter: GatewayAdapter | GatewayAdapterFactory
|
|
99
|
+
/** The function to run per inbound message. It receives the normalised message, not the platform's raw payload. */
|
|
79
100
|
func: PikkuFunctionConfig
|
|
101
|
+
/** Wraps every inbound message: signature verification, tracing, rate limiting. */
|
|
80
102
|
middleware?: CorePikkuMiddlewareGroup<any, any>
|
|
103
|
+
/** Filters this gateway in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
81
104
|
tags?: string[]
|
|
82
105
|
/** Unset lets the handler's own `auth` govern; gateway handlers are sessionless by default. */
|
|
83
106
|
auth?: boolean
|
|
@@ -233,6 +233,6 @@ describe('http routes wired with ref() to an addon function', () => {
|
|
|
233
233
|
|
|
234
234
|
const response = await fetch(new ParamsRequest('/addon/greet', 'get'))
|
|
235
235
|
|
|
236
|
-
assert.strictEqual(response.status,
|
|
236
|
+
assert.strictEqual(response.status, 401)
|
|
237
237
|
})
|
|
238
238
|
})
|