@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/errors/error-handler.d.ts +11 -0
  3. package/dist/errors/error-handler.js +8 -0
  4. package/dist/errors/serialized-error.d.ts +3 -0
  5. package/dist/function/function-runner.d.ts +1 -1
  6. package/dist/function/function-runner.js +3 -3
  7. package/dist/function/functions.types.d.ts +33 -21
  8. package/dist/middleware/auth-apikey.d.ts +7 -0
  9. package/dist/middleware/auth-apikey.js +6 -0
  10. package/dist/middleware/auth-bearer.d.ts +3 -0
  11. package/dist/middleware/auth-bearer.js +2 -0
  12. package/dist/middleware/auth-cookie.d.ts +9 -0
  13. package/dist/middleware/auth-cookie.js +6 -4
  14. package/dist/middleware/cors.d.ts +8 -0
  15. package/dist/middleware/cors.js +2 -0
  16. package/dist/middleware/middleware-factories.d.ts +14 -0
  17. package/dist/middleware/middleware-factories.js +14 -0
  18. package/dist/services/local-secrets.js +1 -1
  19. package/dist/services/persona-sign-in.d.ts +0 -2
  20. package/dist/services/persona-sign-in.js +15 -66
  21. package/dist/services/personas-service.d.ts +4 -0
  22. package/dist/services/personas-service.js +4 -0
  23. package/dist/services/secret-service.d.ts +0 -1
  24. package/dist/testing/service-tests/secret-service-tests.js +1 -1
  25. package/dist/types/core.types.d.ts +5 -0
  26. package/dist/wirings/addon/wire-addon.d.ts +17 -15
  27. package/dist/wirings/addon/wire-addon.js +6 -0
  28. package/dist/wirings/addon/wire-remote-addon.d.ts +4 -0
  29. package/dist/wirings/addon/wire-remote-addon.js +4 -0
  30. package/dist/wirings/agent/agent-prepare.js +2 -2
  31. package/dist/wirings/agent/agent.types.d.ts +31 -27
  32. package/dist/wirings/agent-scorer/agent-scorer.d.ts +4 -0
  33. package/dist/wirings/agent-scorer/agent-scorer.js +4 -0
  34. package/dist/wirings/channel/channel.types.d.ts +11 -0
  35. package/dist/wirings/cli/cli.types.d.ts +43 -1
  36. package/dist/wirings/cli/define-cli-commands.d.ts +6 -1
  37. package/dist/wirings/cli/define-cli-commands.js +6 -1
  38. package/dist/wirings/credential/credential.types.d.ts +7 -0
  39. package/dist/wirings/gateway/gateway.types.d.ts +23 -0
  40. package/dist/wirings/http/http.types.d.ts +51 -37
  41. package/dist/wirings/mcp/mcp.types.d.ts +27 -0
  42. package/dist/wirings/queue/queue.types.d.ts +6 -0
  43. package/dist/wirings/scheduler/scheduler.types.d.ts +5 -0
  44. package/dist/wirings/secret/secret.types.d.ts +9 -14
  45. package/dist/wirings/secret/secret.types.js +7 -0
  46. package/dist/wirings/trigger/trigger.types.d.ts +4 -1
  47. package/dist/wirings/variable/variable.types.d.ts +14 -13
  48. package/dist/wirings/variable/variable.types.js +7 -0
  49. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +7 -0
  50. package/dist/wirings/workflow/scenario-cookie-jar.d.ts +6 -0
  51. package/dist/wirings/workflow/scenario-cookie-jar.js +6 -0
  52. package/dist/wirings/workflow/scenario-poll.d.ts +6 -0
  53. package/dist/wirings/workflow/scenario-poll.js +6 -0
  54. package/dist/wirings/workflow/scenario-step-guards.d.ts +6 -0
  55. package/dist/wirings/workflow/scenario-step-guards.js +6 -0
  56. package/dist/wirings/workflow/workflow-errors.d.ts +4 -0
  57. package/dist/wirings/workflow/workflow-errors.js +4 -0
  58. package/package.json +1 -1
  59. package/src/app-leaf-surface.test.ts +2 -2
  60. package/src/errors/error-handler.ts +11 -0
  61. package/src/errors/serialized-error.ts +3 -0
  62. package/src/function/function-runner.ts +4 -4
  63. package/src/function/functions.types.ts +33 -21
  64. package/src/middleware/auth-apikey.ts +7 -0
  65. package/src/middleware/auth-bearer.ts +3 -0
  66. package/src/middleware/auth-cookie.ts +9 -4
  67. package/src/middleware/cors.ts +8 -0
  68. package/src/middleware/middleware-factories.ts +14 -0
  69. package/src/services/local-secrets.test.ts +2 -2
  70. package/src/services/local-secrets.ts +1 -1
  71. package/src/services/persona-sign-in.test.ts +28 -34
  72. package/src/services/persona-sign-in.ts +16 -88
  73. package/src/services/personas-service.ts +4 -0
  74. package/src/services/secret-service.ts +0 -1
  75. package/src/testing/service-tests/secret-service-tests.ts +1 -1
  76. package/src/types/core.types.ts +5 -0
  77. package/src/wirings/addon/addon-auth-tags.test.ts +4 -4
  78. package/src/wirings/addon/wire-addon.ts +17 -15
  79. package/src/wirings/addon/wire-remote-addon.ts +4 -0
  80. package/src/wirings/agent/agent-authorization.test.ts +7 -3
  81. package/src/wirings/agent/agent-prepare.ts +2 -2
  82. package/src/wirings/agent/agent.types.ts +31 -27
  83. package/src/wirings/agent-scorer/agent-scorer.ts +4 -0
  84. package/src/wirings/channel/channel.types.ts +11 -0
  85. package/src/wirings/cli/cli.types.ts +44 -1
  86. package/src/wirings/cli/define-cli-commands.ts +6 -1
  87. package/src/wirings/credential/credential.types.ts +7 -0
  88. package/src/wirings/gateway/gateway-authorization.test.ts +3 -3
  89. package/src/wirings/gateway/gateway.types.ts +23 -0
  90. package/src/wirings/http/http-runner-addon-ref.test.ts +1 -1
  91. package/src/wirings/http/http.types.ts +94 -69
  92. package/src/wirings/mcp/mcp.types.ts +27 -0
  93. package/src/wirings/queue/queue.types.ts +6 -0
  94. package/src/wirings/scheduler/scheduler.types.ts +5 -0
  95. package/src/wirings/secret/secret.types.ts +9 -14
  96. package/src/wirings/trigger/trigger.types.ts +4 -1
  97. package/src/wirings/variable/variable.types.ts +14 -13
  98. package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
  99. package/src/wirings/workflow/scenario-cookie-jar.ts +6 -0
  100. package/src/wirings/workflow/scenario-poll.ts +6 -0
  101. package/src/wirings/workflow/scenario-step-guards.ts +6 -0
  102. package/src/wirings/workflow/workflow-errors.ts +4 -0
  103. package/tsconfig.tsbuildinfo +1 -1
@@ -31,7 +31,7 @@ export const defineSecretServiceTests = (name, secretService) => {
31
31
  test('getSecret throws for missing key', async () => {
32
32
  const service = await factory({ key: kek });
33
33
  await assert.rejects(() => service.getSecret('nonexistent'), {
34
- message: 'Requested secret not found',
34
+ message: 'Requested secret not found: nonexistent',
35
35
  });
36
36
  });
37
37
  test('setSecret upserts existing key', async () => {
@@ -271,10 +271,15 @@ export type ServerLifecycle<SingletonServices extends CoreSingletonServices = Co
271
271
  export type CommonWireMeta = {
272
272
  pikkuFuncId: string;
273
273
  packageName?: string;
274
+ /** A human name for this wiring, shown wherever it is listed rather than called. */
274
275
  title?: string;
276
+ /** Filters this wiring in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
275
277
  tags?: string[];
278
+ /** A one-line description for listings, where the full `description` is too long. */
276
279
  summary?: string;
280
+ /** What this does, for whoever is reading the wiring rather than writing it. */
277
281
  description?: string;
282
+ /** Names of error classes this may throw, so each one's registered status is used instead of a 500. */
278
283
  errors?: string[];
279
284
  middleware?: MiddlewareMetadata[];
280
285
  permissions?: PermissionMetadata[];
@@ -1,34 +1,30 @@
1
1
  import type { CorePikkuMiddleware } from '../../middleware/middleware.types.js';
2
2
  export type WireAddonConfig = {
3
+ /** How this instance is addressed. One package may be wired more than once, and the name is what tells the instances apart. */
3
4
  name: string;
5
+ /** The npm package the addon ships in. */
4
6
  package: string;
7
+ /** Where to reach the addon when it runs as its own service rather than in-process. */
5
8
  rpcEndpoint?: string;
9
+ /** Requires a session for every function in the addon, whatever each one declares. Gates an addon whose functions are individually open. */
6
10
  auth?: boolean;
11
+ /** Offers the addon's functions to MCP clients as tools, without wiring each one. */
7
12
  mcp?: boolean;
13
+ /** Filters this addon in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
8
14
  tags?: string[];
9
15
  /** Required of every function in the addon, on top of the function's own. */
10
16
  scopes?: string[];
17
+ /** Points a secret the addon reads at a different key in this deployment, so two instances can hold different credentials. */
11
18
  secretOverrides?: Record<string, string>;
19
+ /** Points a variable the addon reads at a different key in this deployment. */
12
20
  variableOverrides?: Record<string, string>;
21
+ /** Points a credential the addon reads at a different key in this deployment. */
13
22
  credentialOverrides?: Record<string, string>;
14
- /**
15
- * Secrets this instance may read on top of the ones it declared, named as the
16
- * addon reads them — the scope check runs before `secretOverrides` renames
17
- * them, so an overridden secret is named here by its addon-side key. Listing
18
- * one in `secretOverrides` grants it too.
19
- *
20
- * For an addon whose secret names come off its input rather than its own
21
- * source, this is how the host lends names the addon could not declare.
22
- */
23
+ /** Extra secrets this instance may read, named as the addon reads them — the scope check runs before `secretOverrides` renames them. */
23
24
  secretGrants?: string[];
24
25
  /** Credentials this instance may read on top of the ones it declared. */
25
26
  credentialGrants?: string[];
26
- /**
27
- * Hands this instance the whole `SecretService` instead of one scoped to the
28
- * secrets it declared. The value is the reason, recorded in the deploy
29
- * manifest — an addon that names secrets at runtime cannot be scoped, and
30
- * only the consuming app, never the addon, can grant it.
31
- */
27
+ /** Hands over the whole `SecretService` unscoped. The value is the reason, recorded in the deploy manifest. */
32
28
  globalSecrets?: string;
33
29
  /**
34
30
  * Hands this instance the whole `CredentialService` instead of one narrowed
@@ -37,6 +33,12 @@ export type WireAddonConfig = {
37
33
  */
38
34
  globalCredentials?: string;
39
35
  };
36
+ /**
37
+ * Installs an addon into this project: its functions, wirings and scopes become
38
+ * part of the app, under the namespace and options given here.
39
+ *
40
+ * @example snippet: addonWiring
41
+ */
40
42
  export declare const wireAddon: (config: WireAddonConfig) => void;
41
43
  /**
42
44
  * knowledge: decisions/security/addon-scopes-are-resolved-where-the-function-runs.md
@@ -1,5 +1,11 @@
1
1
  import { pikkuState } from '../../pikku-state.js';
2
2
  import { getTagGroups } from '../../utils.js';
3
+ /**
4
+ * Installs an addon into this project: its functions, wirings and scopes become
5
+ * part of the app, under the namespace and options given here.
6
+ *
7
+ * @example snippet: addonWiring
8
+ */
3
9
  export const wireAddon = (config) => {
4
10
  pikkuState(null, 'addons', 'packages').set(config.name, {
5
11
  package: config.package,
@@ -19,4 +19,8 @@ export type WireRemoteAddonConfig = {
19
19
  remoteName?: (fn: string) => string;
20
20
  tags?: string[];
21
21
  };
22
+ /**
23
+ * Installs an addon that runs as its own deployed service: the contract is
24
+ * local, the calls go over the wire to the addon's own host.
25
+ */
22
26
  export declare const wireRemoteAddon: (config: WireRemoteAddonConfig) => void;
@@ -1,4 +1,8 @@
1
1
  import { pikkuState } from '../../pikku-state.js';
2
+ /**
3
+ * Installs an addon that runs as its own deployed service: the contract is
4
+ * local, the calls go over the wire to the addon's own host.
5
+ */
2
6
  export const wireRemoteAddon = (config) => {
3
7
  pikkuState(null, 'addons', 'packages').set(config.name, {
4
8
  package: config.package,
@@ -1,7 +1,7 @@
1
1
  import { PikkuError } from '../../errors/error-handler.js';
2
2
  import { checkAuthPermissions, runPermissions, } from '../../permissions.js';
3
3
  import { AIProviderNotConfiguredError } from '../../errors/errors.js';
4
- import { ForbiddenError } from '../../errors/errors.js';
4
+ import { ForbiddenError, MissingSessionError } from '../../errors/errors.js';
5
5
  import { verifyScopes } from '../../scopes.js';
6
6
  import { pikkuState, getSingletonServices } from '../../pikku-state.js';
7
7
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js';
@@ -166,7 +166,7 @@ export async function assertAgentAuthorized(agent, params, packageName) {
166
166
  ? await params.sessionService.get()
167
167
  : undefined;
168
168
  if (agent.auth === true && !session) {
169
- throw new ForbiddenError('Authentication required');
169
+ throw new MissingSessionError('Authentication required');
170
170
  }
171
171
  verifyScopes(agent.scopes, session);
172
172
  const singletonServices = getSingletonServices();
@@ -171,6 +171,7 @@ export interface AgentToolDef extends Partial<ApprovalPolicy> {
171
171
  forwardsApproval?: boolean;
172
172
  }
173
173
  export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown> = Record<string, unknown>, SingletonServices extends CoreSingletonServices = CoreSingletonServices> {
174
+ /** Rewrites what the model is about to see — messages and instructions — before each turn. */
174
175
  modifyInput?: (services: SingletonServices, ctx: {
175
176
  messages: AgentMessage[];
176
177
  instructions: string;
@@ -198,6 +199,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
198
199
  messages: AgentMessage[];
199
200
  instructions: string;
200
201
  };
202
+ /** Sees the model's output as it streams, for redaction or live inspection. Keeps its own `state` across chunks, unlike the shared run notes. */
201
203
  modifyOutputStream?: (services: SingletonServices, ctx: {
202
204
  event: AgentStreamEvent;
203
205
  allEvents: readonly AgentStreamEvent[];
@@ -242,6 +244,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
242
244
  * and handed to anything that grades the run, and scrubbing the reply alone
243
245
  * leaves them untouched.
244
246
  */
247
+ /** Rewrites the finished output of a turn, after streaming has completed. */
245
248
  modifyOutput?: (services: SingletonServices, ctx: {
246
249
  text: string;
247
250
  messages: AgentMessage[];
@@ -259,6 +262,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
259
262
  messages: AgentMessage[];
260
263
  toolCalls?: NonNullable<AgentStep['toolCalls']>;
261
264
  };
265
+ /** Runs before a tool executes, and may rewrite the arguments the model chose. Returning nothing leaves them as they are. */
262
266
  beforeToolCall?: (services: SingletonServices, ctx: {
263
267
  toolName: string;
264
268
  toolCallId: string;
@@ -268,6 +272,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
268
272
  } | void> | {
269
273
  args: Record<string, unknown>;
270
274
  } | void;
275
+ /** Runs once a tool has returned, and may replace its result before the model sees it. */
271
276
  afterToolCall?: (services: SingletonServices, ctx: {
272
277
  toolName: string;
273
278
  toolCallId: string;
@@ -279,6 +284,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
279
284
  } | void> | {
280
285
  result: unknown;
281
286
  } | void;
287
+ /** Runs at the end of each model turn. For observation — it cannot change what happened. */
282
288
  afterStep?: (services: SingletonServices, ctx: {
283
289
  stepNumber: number;
284
290
  text: string;
@@ -300,6 +306,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
300
306
  };
301
307
  finishReason: string;
302
308
  }) => Promise<void> | void;
309
+ /** Runs when a turn throws, with the step it failed on. For logging and cleanup; it does not swallow the error. */
303
310
  onError?: (services: SingletonServices, ctx: {
304
311
  error: Error;
305
312
  stepNumber: number;
@@ -314,58 +321,51 @@ export type AgentMemoryConfig = {
314
321
  workingMemory?: unknown;
315
322
  };
316
323
  export type CoreAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>, Scope extends string = string, Scorer extends string = string> = {
324
+ /** Unique across the project. It is how the agent is invoked and how its runs are grouped. */
317
325
  name: string;
326
+ /** What the agent is for. Another agent choosing whether to delegate to this one reads it, so write it for that reader. */
318
327
  description: string;
328
+ /** A one-line description for listings, where the full `description` is too long. */
319
329
  summary?: string;
330
+ /** Names of error classes this may throw, so each one's registered status is used instead of a 500. */
320
331
  errors?: string[];
321
- /**
322
- * The three fields below are the system prompt. `buildInstructions` joins
323
- * whichever are set with a blank line, always in this order — role, then
324
- * personality, then goal — and appends the tool-usage rules if the agent has
325
- * tools. Nothing checks them against each other: the split is there to keep a
326
- * prompt legible, and text put in the wrong one still reaches the model.
327
- *
328
- * Who the agent is. 'You are a support engineer triaging inbound bugs.'
329
- */
332
+ /** Who the agent is. Joined with `personality` and `goal`, in that order, to form the system prompt. */
330
333
  role?: string;
331
334
  /** How it should sound: tone, vocabulary, how much it says at a time. */
332
335
  personality?: string;
333
336
  /** What it is for — the only one of the three that is required. */
334
337
  goal: string;
338
+ /** Which model to run on, as the provider names it. */
335
339
  model: string;
340
+ /** How much the model is allowed to vary its answer. Lower is more repeatable, which is what a tool-driving agent usually wants. */
336
341
  temperature?: number;
337
342
  /** Ownership/partitioning of this agent's threads and runs. Defaults to `'user'`. */
338
343
  sessionScope?: SessionScope;
344
+ /** Functions the model may call, given to it as tools. A `readonly` one may be called without asking first. */
339
345
  tools?: unknown[];
346
+ /** Other agents this one may hand work to. `agentMode` decides whether it delegates or supervises. */
340
347
  agents?: unknown[];
348
+ /** Workflows the model may start, for work too long to sit inside one agent run. */
341
349
  workflows?: unknown[];
342
- /**
343
- * Grades this agent's finished runs on live traffic, named by the generated
344
- * `ScorerName` union rather than by `ref()` — a scorer is not a function, so
345
- * there is nothing in the function map for a ref to resolve against.
346
- *
347
- * A reference-based judge listed here is never sampled: live traffic has no
348
- * answer key. Scenarios name scorers directly and may grade with scorers an
349
- * agent does not ship with.
350
- */
350
+ /** Grades finished runs on live traffic. A reference-based judge is never sampled here — live traffic has no answer key. */
351
351
  scorers?: Scorer[];
352
+ /** `delegate` hands a sub-agent the task and takes its answer; `supervise` keeps this agent in the loop over each step. */
352
353
  agentMode?: 'delegate' | 'supervise';
354
+ /** What the agent carries between runs, and how much of it. */
353
355
  memory?: AgentMemoryConfig;
356
+ /** How many model turns a single run may take before it is stopped. The guard against a tool loop that never converges. */
354
357
  maxSteps?: number;
358
+ /** Whether the model may answer without calling a tool (`auto`), must call one (`required`), or may not (`none`). */
355
359
  toolChoice?: 'auto' | 'required' | 'none';
356
- /**
357
- * Per-provider model settings, keyed by provider id and passed through
358
- * untouched — for anything only one vendor offers, which the fields above
359
- * deliberately do not try to unify.
360
- *
361
- * `{ openai: { reasoningEffort: 'minimal' } }` is the one that matters for
362
- * voice: on gpt-5-mini it measured 0.9s to first token against 2.5s at the
363
- * default, and a spoken reply is waited through rather than skimmed.
364
- */
360
+ /** Per-provider settings passed through untouched, for what only one vendor offers. */
365
361
  providerOptions?: AIProviderOptions;
362
+ /** The schema of what starts a run, which is also its input type. */
366
363
  input?: unknown;
364
+ /** The schema the final answer must match, which is also its return type. Without one the agent returns free text. */
367
365
  output?: unknown;
366
+ /** Filters this agent in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
368
367
  tags?: string[];
368
+ /** 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. */
369
369
  prepareStep?: (ctx: {
370
370
  stepNumber: number;
371
371
  messages: AgentMessage[];
@@ -374,8 +374,11 @@ export type CoreAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMidd
374
374
  model: string;
375
375
  stop: () => void;
376
376
  }) => void | Promise<void>;
377
+ /** Wraps the whole run: auth, tracing, spend limits. */
377
378
  middleware?: PikkuMiddleware[];
379
+ /** Wraps each message when the agent is driven over a channel. */
378
380
  channelMiddleware?: CorePikkuChannelMiddleware<any, any>[];
381
+ /** Hooks around each step and each tool call — the place to inspect, rewrite or veto what the model is about to do. */
379
382
  agentMiddleware?: PikkuAgentMiddlewareHooks<any, any>[];
380
383
  /**
381
384
  * Whether a session is required to run this agent. Defaults to `false`, since
@@ -394,6 +397,7 @@ export type CoreAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMidd
394
397
  * `#pikku/scopes`, so an undeclared scope is a compile error.
395
398
  */
396
399
  scopes?: Scope[];
400
+ /** Checks that run before the agent starts. Grouped names OR together, so any one passing admits the caller. */
397
401
  permissions?: CorePermissionGroup<PikkuPermission>;
398
402
  };
399
403
  export type AgentStreamEvent = {
@@ -2,6 +2,8 @@ import type { JudgeToolCallDisclosure, PikkuAgentScorer, ScorerInput, ScorerOutp
2
2
  /**
3
3
  * A heuristic scorer: pure code over the finished run, no model call, so it
4
4
  * grades on the fast lane.
5
+ *
6
+ * @example snippet: agentScorer
5
7
  */
6
8
  export declare const pikkuAgentScorer: <Services = any>(config: {
7
9
  name: string;
@@ -22,6 +24,8 @@ export declare const pikkuAgentScorer: <Services = any>(config: {
22
24
  * The rubric field is `goal`, matching `pikkuAgent`'s prompt vocabulary — a
23
25
  * judge is a degenerate agent, and should use the same word for the same thing.
24
26
  * `prompt` is the escape hatch for non-standard framing.
27
+ *
28
+ * @example snippet: agentJudge
25
29
  */
26
30
  export declare const pikkuAgentJudge: <Services = any>(config: {
27
31
  name: string;
@@ -9,6 +9,8 @@ const assertSampleRate = (name, sampleRate) => {
9
9
  /**
10
10
  * A heuristic scorer: pure code over the finished run, no model call, so it
11
11
  * grades on the fast lane.
12
+ *
13
+ * @example snippet: agentScorer
12
14
  */
13
15
  export const pikkuAgentScorer = (config) => ({
14
16
  name: config.name,
@@ -25,6 +27,8 @@ export const pikkuAgentScorer = (config) => ({
25
27
  * The rubric field is `goal`, matching `pikkuAgent`'s prompt vocabulary — a
26
28
  * judge is a degenerate agent, and should use the same word for the same thing.
27
29
  * `prompt` is the escape hatch for non-standard framing.
30
+ *
31
+ * @example snippet: agentJudge
28
32
  */
29
33
  export const pikkuAgentJudge = (config) => ({
30
34
  name: config.name,
@@ -48,8 +48,11 @@ export interface ChannelMeta {
48
48
  }
49
49
  export type ChannelsMeta = Record<string, ChannelMeta>;
50
50
  export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = CorePikkuFunctionConfig<CorePikkuFunction<void, unknown> | CorePikkuFunctionSessionless<void, unknown>, CorePikkuPermission<void>, CorePikkuMiddleware>, ChannelDisconnect = CorePikkuFunctionConfig<CorePikkuFunction<void, void> | CorePikkuFunctionSessionless<void, void>, CorePikkuPermission<void>, CorePikkuMiddleware>, ChannelFunctionMessage = CorePikkuFunctionConfig<CorePikkuFunction<unknown, unknown> | CorePikkuFunctionSessionless<unknown, unknown>, CorePikkuPermission<unknown>, CorePikkuMiddleware>, PikkuPermission = CorePikkuPermission<ChannelData>, PikkuMiddleware = CorePikkuMiddleware> = {
51
+ /** Unique across the project. It is how the channel is addressed in `pikku meta` and by the generated client. */
51
52
  name: string;
53
+ /** The path a client opens the socket on. */
52
54
  route: Channel;
55
+ /** Runs once when a client connects, before any message. Its return value is sent as the first message. */
53
56
  onConnect?: ChannelConnect | {
54
57
  func?: ChannelConnect;
55
58
  middleware?: PikkuMiddleware[];
@@ -58,17 +61,25 @@ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = Co
58
61
  func?: ChannelDisconnect;
59
62
  middleware?: PikkuMiddleware[];
60
63
  };
64
+ /** Handles any message that no `onMessageWiring` entry claimed. Without one, an unrouted message is dropped. */
61
65
  onMessage?: ChannelFunctionMessage;
66
+ /** 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. */
62
67
  onMessageWiring?: Record<string, Record<string, ChannelFunctionMessage | {
63
68
  func: ChannelFunctionMessage;
64
69
  auth?: boolean;
65
70
  middleware?: PikkuMiddleware[];
66
71
  }>>;
72
+ /** Wraps the connection: it runs on connect, where the session is established, not on every message. */
67
73
  middleware?: PikkuMiddleware[];
74
+ /** Wraps every message, which is where per-message concerns like rate limiting belong. */
68
75
  channelMiddleware?: Array<CorePikkuChannelMiddleware | CorePikkuChannelMiddlewareFactory>;
76
+ /** Whether opening the channel requires a session. Defaults to true — a channel is closed unless it says otherwise. */
69
77
  auth?: boolean;
78
+ /** Whether this channel carries binary frames. Text frames are still parsed as JSON. */
70
79
  binary?: boolean | null;
80
+ /** Handles binary frames, which never reach `onMessage` because they are not JSON. Return a value to reply in kind. */
71
81
  onBinaryMessage?: (services: any, data: BinaryData, channel: PikkuChannel<ChannelData, any>) => Promise<BinaryData | void> | BinaryData | void;
82
+ /** Filters this channel in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
72
83
  tags?: string[];
73
84
  };
74
85
  /**
@@ -127,13 +127,18 @@ type StripBrackets<S extends string> = S extends `<${infer Inner}>` ? Inner : S
127
127
  * Split string by spaces
128
128
  */
129
129
  type SplitBySpace<S extends string> = S extends `${infer First} ${infer Rest}` ? [First, ...SplitBySpace<Rest>] : S extends '' ? [] : [S];
130
+ /**
131
+ * Strip the `...` a variadic positional carries, so `[files...]` validates
132
+ * against the `files` key the runtime parser fills.
133
+ */
134
+ type StripVariadic<S extends string> = S extends `${infer Name}...` ? Name : S;
130
135
  /**
131
136
  * Recursively build a tuple by stripping brackets from each element
132
137
  */
133
138
  type BuildParamsTuple<Parts extends readonly string[]> = Parts extends readonly [
134
139
  infer First extends string,
135
140
  ...infer Rest extends string[]
136
- ] ? [StripBrackets<First>, ...BuildParamsTuple<Rest>] : [];
141
+ ] ? [StripVariadic<StripBrackets<First>>, ...BuildParamsTuple<Rest>] : [];
137
142
  /**
138
143
  * Extract parameter names from CLI parameter string into a tuple
139
144
  * Example: "<env> [region]" => ["env", "region"]
@@ -152,16 +157,39 @@ export type ValidateParameters<Params extends string, Input> = AllParamsValid<Ex
152
157
  * This is a helper type for creating type-safe CLI commands.
153
158
  */
154
159
  export type CoreCLICommandConfig<FuncConfig, PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<any, any>, PikkuCLIRender extends CorePikkuCLIRender<any, any, any> = CorePikkuCLIRender<any, any>, Params extends string = string> = {
160
+ /**
161
+ * Positional arguments, written the way `--help` prints them: `<env>` is
162
+ * required, `[region]` optional. Each name must be a key of the function's
163
+ * input schema, so a typo is a compile error rather than an argument that
164
+ * silently never arrives.
165
+ */
155
166
  parameters?: ValidateParameters<Params, ExtractFunctionInput<FuncConfig>>;
167
+ /** The function to run. Omit it on a command that exists only to hold `subcommands`. */
156
168
  func?: FuncConfig;
169
+ /** The heading shown above this command's own help. */
157
170
  title?: string;
171
+ /** The one-line summary listed beside the command name. */
158
172
  description?: string;
173
+ /**
174
+ * Prints the result. The function returns data and this is the only thing
175
+ * that writes to stdout, which is what lets one command serve both a human
176
+ * reading it and a script parsing it.
177
+ */
159
178
  render?: PikkuCLIRender;
179
+ /**
180
+ * Flags, keyed by the input field each one fills. A field with no entry is
181
+ * still accepted as `--field`; an entry is how it gets a short form, a
182
+ * default, or help text.
183
+ */
160
184
  options?: {
161
185
  [K in keyof ExtractFunctionInput<FuncConfig>]?: {
186
+ /** What this flag is for, as `--help` prints it. */
162
187
  description?: string;
188
+ /** A single-letter alias, so `--verbose` can also be `-v`. */
163
189
  short?: string;
190
+ /** How the argument is parsed. Inferred from the input schema when omitted. */
164
191
  type?: CLIOptionType;
192
+ /** Used when the flag is absent, and shown in `--help` so nobody has to guess it. */
165
193
  default?: ExtractFunctionInput<FuncConfig>[K];
166
194
  /**
167
195
  * The values this option accepts. Rejected by the parser and listed in
@@ -171,10 +199,15 @@ export type CoreCLICommandConfig<FuncConfig, PikkuMiddleware extends CorePikkuMi
171
199
  choices?: ReadonlyArray<ExtractFunctionInput<FuncConfig>[K]>;
172
200
  };
173
201
  };
202
+ /** Wraps every run of this command. */
174
203
  middleware?: PikkuMiddleware[];
204
+ /** Nested commands, keyed by the word that selects them: `deploy plan` is `deploy` with a `plan` subcommand. */
175
205
  subcommands?: Record<string, CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender, any>>;
206
+ /** Whether running this requires a logged-in session. Defaults to true — a command is closed unless it says otherwise. */
176
207
  auth?: boolean;
208
+ /** Permission checks run before the function, for a command that not every logged-in user may run. */
177
209
  permissions?: any[];
210
+ /** Runs when the parent is invoked with no subcommand named. */
178
211
  isDefault?: boolean;
179
212
  };
180
213
  /**
@@ -203,14 +236,23 @@ export type CLICommandShorthand<In, Out, PikkuFunctionConfig extends CorePikkuFu
203
236
  * CLI wiring configuration
204
237
  */
205
238
  export interface CoreCLI<Commands extends Record<string, CoreCLICommandConfig<any, any, any>>, Options, PikkuMiddleware, PikkuCLIRender> {
239
+ /** The command name a user types. It is also what `--help` prints as the program. */
206
240
  program: string;
241
+ /** What the program is for, shown at the top of `--help`. */
207
242
  description?: string;
243
+ /** Top-level commands, keyed by the word that selects them. */
208
244
  commands: Commands;
245
+ /** Flags accepted before any command, for things that apply to the whole program. */
209
246
  options?: CLIOptions<Options>;
247
+ /** Wraps every command. */
210
248
  middleware?: PikkuMiddleware[];
249
+ /** The fallback printer for commands that declare none of their own. */
211
250
  render?: PikkuCLIRender;
251
+ /** A one-line description for listings, where the full `description` is too long. */
212
252
  summary?: string;
253
+ /** Names of error classes any command may throw, so each one's registered exit behaviour is used. */
213
254
  errors?: string[];
255
+ /** Filters this program in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
214
256
  tags?: string[];
215
257
  /**
216
258
  * Requires a session on the websocket serving this program remotely. Has no
@@ -1,3 +1,8 @@
1
1
  import type { CoreCLICommandConfig } from './cli.types.js';
2
- /** Identity at runtime; exists so a commands record can be typed and composed before `wireCLI`. */
2
+ /**
3
+ * Identity at runtime; exists so a commands record can be typed and composed
4
+ * before `wireCLI`.
5
+ *
6
+ * @example snippet: cliCommandContract
7
+ */
3
8
  export declare function defineCLICommands<T extends Record<string, CoreCLICommandConfig<any, any, any, any>>>(commands: T): T;
@@ -1,4 +1,9 @@
1
- /** Identity at runtime; exists so a commands record can be typed and composed before `wireCLI`. */
1
+ /**
2
+ * Identity at runtime; exists so a commands record can be typed and composed
3
+ * before `wireCLI`.
4
+ *
5
+ * @example snippet: cliCommandContract
6
+ */
2
7
  export function defineCLICommands(commands) {
3
8
  return commands;
4
9
  }
@@ -1,11 +1,18 @@
1
1
  import type { OAuth2CredentialConfig } from '../secret/secret.types.js';
2
2
  export type CoreCredential<T = unknown> = {
3
+ /** How the credential is asked for in code. Generated into `CredentialsMap`, so it is what `credentials.get` autocompletes. */
3
4
  name: string;
5
+ /** The name shown to whoever has to supply the value, who is often not the person who wrote this. */
4
6
  displayName: string;
7
+ /** What the credential is for and where to obtain one. */
5
8
  description?: string;
9
+ /** `singleton` is one value for the whole deployment; `wire` is one per user, supplied by them and stored against their account. */
6
10
  type: 'singleton' | 'wire';
11
+ /** The shape of the value, validated when it is supplied rather than when it is first used. */
7
12
  schema: T;
13
+ /** Where to go to create one, shown next to the field asking for it. */
8
14
  docsUrl?: string;
15
+ /** 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. */
9
16
  oauth2?: OAuth2CredentialConfig & {
10
17
  appCredentialSecretId: string;
11
18
  };
@@ -9,6 +9,10 @@ export interface GatewayAttachment {
9
9
  mimeType?: string;
10
10
  filename?: string;
11
11
  }
12
+ /**
13
+ * One message arriving from a gateway, normalised: who sent it, in which
14
+ * conversation, and what they said.
15
+ */
12
16
  export interface GatewayInboundMessage {
13
17
  /** Platform-specific: a phone number, a Slack user id, and so on. */
14
18
  senderId: string;
@@ -17,17 +21,29 @@ export interface GatewayInboundMessage {
17
21
  attachments?: GatewayAttachment[];
18
22
  metadata?: Record<string, unknown>;
19
23
  }
24
+ /**
25
+ * One message to send back through a gateway — plain text, or the provider's
26
+ * own rich content.
27
+ */
20
28
  export interface GatewayOutboundMessage {
21
29
  text?: string;
22
30
  richContent?: Record<string, unknown>;
23
31
  attachments?: GatewayAttachment[];
24
32
  }
33
+ /**
34
+ * What a gateway's `verifyWebhook` returns — verified, with the response the
35
+ * provider expects back, or not.
36
+ */
25
37
  export type WebhookVerificationResult = {
26
38
  verified: true;
27
39
  response: unknown;
28
40
  } | {
29
41
  verified: false;
30
42
  };
43
+ /**
44
+ * What a gateway integration implements: parse an incoming event into a
45
+ * message, send one back, and open and close the connection.
46
+ */
31
47
  export interface GatewayAdapter {
32
48
  name: string;
33
49
  /** Return null to ignore the event, e.g. a delivery receipt. */
@@ -49,14 +65,21 @@ export interface PikkuGateway {
49
65
  /** 'webhook' the platform POSTs to us, 'websocket' the client connects to us, 'listener' no route at all. */
50
66
  export type GatewayTransportType = 'webhook' | 'websocket' | 'listener';
51
67
  export type CoreGateway<PikkuFunctionConfig = CorePikkuFunctionConfig<any, any>, PikkuMiddleware extends CorePikkuMiddleware = CorePikkuMiddleware> = Partial<Pick<CommonWireMeta, 'title' | 'summary' | 'description' | 'errors'>> & {
68
+ /** Unique across the project. It is how the gateway is addressed in `pikku meta` and in logs. */
52
69
  name: string;
70
+ /** How the platform reaches us: a `webhook` it posts to, a `websocket` it holds open, or a `listener` we open outward. */
53
71
  type: GatewayTransportType;
54
72
  /** Required for 'webhook' and 'websocket'; unused for 'listener'. */
55
73
  route?: string;
74
+ /** Which service this speaks to — slack, whatsapp, discord. It selects the adapter's dialect, not the transport. */
56
75
  platform?: string;
76
+ /** 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. */
57
77
  adapter: GatewayAdapter | GatewayAdapterFactory;
78
+ /** The function to run per inbound message. It receives the normalised message, not the platform's raw payload. */
58
79
  func: PikkuFunctionConfig;
80
+ /** Wraps every inbound message: signature verification, tracing, rate limiting. */
59
81
  middleware?: CorePikkuMiddlewareGroup<any, any>;
82
+ /** Filters this gateway in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
60
83
  tags?: string[];
61
84
  /** Unset lets the handler's own `auth` govern; gateway handlers are sessionless by default. */
62
85
  auth?: boolean;