@pylonsync/functions 0.20.0 → 0.22.0

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/dist/agent.d.ts CHANGED
@@ -36,6 +36,20 @@
36
36
  * including tool calls, with zero extra plumbing.
37
37
  */
38
38
  import type { ActionCtx, AnyValidator, FnDefinition, ValidatorSchema } from "./types";
39
+ /** The run a tool call belongs to: the third argument of every tool
40
+ * handler. */
41
+ export interface AgentToolRun {
42
+ runId: string;
43
+ /** The agent's name (its file name in `functions/`). */
44
+ agent: string;
45
+ /** The user the run belongs to. */
46
+ userId: string | null;
47
+ /** The `context` server code passed to `ctx.agents.run`, or the one
48
+ * stored on the run by an earlier `ctx.agents.run`. `null` when the
49
+ * run has none (a run a client started with `streamFn`). Set only by
50
+ * server code, so a tool can trust it to identify what it acts on. */
51
+ context: Record<string, unknown> | null;
52
+ }
39
53
  /** One tool an agent can call. */
40
54
  export interface AgentTool {
41
55
  /** Shown to the model — say when to use the tool, not how it works. */
@@ -45,10 +59,11 @@ export interface AgentTool {
45
59
  * a tool_result error the model can react to. Omit for no-arg tools. */
46
60
  args?: ValidatorSchema;
47
61
  /** Runs with the agent action's ctx (runQuery/runMutation, llm,
48
- * email, …). The return value is JSON-serialized into the
49
- * tool_result the model sees. Throwing marks the result is_error —
50
- * the model sees the message and can recover. */
51
- handler: (ctx: ActionCtx, input: Record<string, unknown>) => unknown;
62
+ * email, …). `run` identifies the run, including the server-set
63
+ * `context` from `ctx.agents.run`. The return value is
64
+ * JSON-serialized into the tool_result the model sees. Throwing marks
65
+ * the result is_error — the model sees the message and can recover. */
66
+ handler: (ctx: ActionCtx, input: Record<string, unknown>, run: AgentToolRun) => unknown;
52
67
  }
53
68
  export interface AgentDefinition {
54
69
  /** System prompt. A function receives the ctx + call args for
@@ -83,8 +98,10 @@ export interface AgentCallArgs {
83
98
  /** Optional display title, stored on new runs. */
84
99
  title?: string;
85
100
  /** Ask the run to stop. Requires `runId`, ignores `input`, and
86
- * returns as soon as the request is recorded — a live generation
87
- * stops at its next turn boundary. */
101
+ * returns as soon as the request is recorded. The running loop
102
+ * notices within a few seconds: a model response in progress is
103
+ * aborted (the provider stops generating), and a running tool sees
104
+ * `ctx.signal` abort. */
88
105
  cancel?: boolean;
89
106
  }
90
107
  /** What the agent action resolves with (also the `event: result`
@@ -115,5 +132,9 @@ export declare function validatorSchemaToJsonSchema(schema: ValidatorSchema): Re
115
132
  /** Marker so the SDK's discoverFunctions can detect agents and inject
116
133
  * the AgentRun/AgentMessage entities into the manifest. */
117
134
  export declare const AGENT_MARKER = "__pylonAgent";
135
+ /** ctx key the runtime sets when server code started this call with
136
+ * `ctx.agents.run`: `{ context }`. A symbol, so no JSON a client sends
137
+ * can set it. */
138
+ export declare const AGENT_INVOCATION: unique symbol;
118
139
  export declare function agent(def: AgentDefinition): FnDefinition<AgentCallArgs, AgentResult>;
119
140
  export declare function isAgentDefinition(value: unknown): boolean;
package/dist/define.d.ts CHANGED
@@ -44,7 +44,8 @@ interface CommonDef<TSchema extends ValidatorSchema | undefined = ValidatorSchem
44
44
  * IDLE-timeout SECONDS for this function: how long it may go without
45
45
  * producing any activity (a stream chunk, a `ctx.db` op, an LLM
46
46
  * event) before the host cancels the call. Defaults to
47
- * `PYLON_FN_CALL_TIMEOUT` (30s). Activity restarts the budget, so a
47
+ * `PYLON_FN_CALL_TIMEOUT` (30s; Stack0 Cloud sets 300s). Activity
48
+ * restarts the budget, so a
48
49
  * streaming agent run stays alive as long as it keeps producing; a
49
50
  * silent hang is cancelled at the budget. Total lifetime is capped at
50
51
  * 10× this value however chatty the call is.
package/dist/index.d.ts CHANGED
@@ -20,10 +20,11 @@
20
20
  export { query, mutation, action } from "./define";
21
21
  export { v } from "./validators";
22
22
  export { workflow } from "./workflows";
23
+ export type { RawResponse, RawResponseInit } from "./response";
23
24
  export { agent, isAgentDefinition, validatorToJsonSchema, validatorSchemaToJsonSchema, } from "./agent";
24
- export type { AgentDefinition, AgentTool, AgentCallArgs, AgentResult, } from "./agent";
25
+ export type { AgentDefinition, AgentTool, AgentToolRun, AgentCallArgs, AgentResult, } from "./agent";
25
26
  export type { WorkflowDefinition, WorkflowRun, WorkflowRunRequest, WorkflowRunnerResponse, WorkflowStepResult, } from "./workflows";
26
27
  export { resetDb, installTestIsolation } from "./testing";
27
28
  export { slugifyName, availableSlug } from "./slugify";
28
29
  export type { SsrResponse, SsrCookieOptions, SsrMetadata, Sitemap, SitemapEntry, Robots, RobotsRule, } from "./ssr-runtime";
29
- export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, Shards, ShardsReader, ShardsWriter, ShardInfo, ShardTransfer, MemberRow, Workflows, VectorSearchQuery, VectorSearchResult, SearchResult, PaginationResult, Llm, LlmMessage, LlmContentBlock, LlmTool, LlmCompleteRequest, LlmCompleteResponse, LlmStreamEvent, Rooms, Domains, TenantDomainResult, TenantDomainDns, DomainAvailability, DomainContact, RegisterDomainOptions, RegisteredDomainResult, } from "./types";
30
+ export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, Shards, ShardsReader, ShardsWriter, ShardInfo, ShardTransfer, MemberRow, Workflows, WorkflowRunSummary, WorkflowRunStatus, Audit, AuditLog, AuditEntry, Agents, AgentRunOptions, VectorSearchQuery, VectorSearchResult, SearchResult, PaginationResult, Llm, LlmMessage, LlmContentBlock, LlmTool, LlmCompleteRequest, LlmCompleteResponse, LlmStreamEvent, Rooms, Domains, TenantDomainResult, TenantDomainRecord, TenantDomainDns, DomainAvailability, DomainContact, RegisterDomainOptions, RegisteredDomainResult, } from "./types";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Raw HTTP responses from webhook actions.
3
+ *
4
+ * An action called through `/api/webhooks/<name>` normally answers with its
5
+ * return value as JSON and status 200. Returning `ctx.response(...)` sends
6
+ * the given status, content type, headers, and body instead. Every other
7
+ * route (`/api/fn/<name>`, `ctx.runAction`) returns the value as ordinary
8
+ * JSON data.
9
+ *
10
+ * The host enforces the same rules (crates/router/src/raw_response.rs), so
11
+ * a hand-built object cannot bypass them. The two header lists must stay in
12
+ * sync.
13
+ */
14
+ /** What `ctx.response` accepts. */
15
+ export interface RawResponseInit {
16
+ /** HTTP status, 200-599. Default 200. */
17
+ status?: number;
18
+ /** Shorthand for the `Content-Type` header. Default `text/plain; charset=utf-8`. */
19
+ contentType?: string;
20
+ /** Extra response headers. Values must be visible ASCII. */
21
+ headers?: Record<string, string>;
22
+ /** Response body. Default empty. Must be empty for 204, 205, and 304. */
23
+ body?: string;
24
+ }
25
+ /** The marked value an action returns. Build it with `ctx.response`. */
26
+ export interface RawResponse {
27
+ readonly __pylonResponse: 1;
28
+ readonly status: number;
29
+ readonly headers: Readonly<Record<string, string>>;
30
+ readonly body: string;
31
+ }
32
+ /**
33
+ * Build a raw HTTP response for a webhook action to return.
34
+ *
35
+ * ```ts
36
+ * return ctx.response({ contentType: "text/xml", body: "<Response/>" });
37
+ * ```
38
+ */
39
+ export declare function response(init?: RawResponseInit): RawResponse;
package/dist/runtime.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * each ctx.db / ctx.scheduler / ctx.runMutation call), so the map never
15
15
  * needs to queue multiple RPCs per call_id.
16
16
  */
17
- import type { DbReader, DbWriter, Llm, Rooms, Workflows } from "./types";
17
+ import type { DbReader, DbWriter, Llm, Rooms, Workflows, Audit } from "./types";
18
18
  export declare function buildDbReader(callId: string, ssrRead?: boolean): DbReader;
19
19
  export declare function buildSsrFnCaller(callId: string): (name: string, args?: Record<string, unknown>) => Promise<unknown>;
20
20
  export declare function buildDbWriter(callId: string): DbWriter;
@@ -46,4 +46,6 @@ export declare function buildRooms(callId: string): Rooms;
46
46
  * takes it from there, so `start` returns the instance id immediately.
47
47
  * Uses the queued call_id-keyed `rpc` (the reply carries no op_id).
48
48
  */
49
+ /** Build `ctx.audit` — round-trips `audit_op` frames to the host. */
50
+ export declare function buildAudit(callId: string): Audit;
49
51
  export declare function buildWorkflows(callId: string): Workflows;
@@ -337,6 +337,13 @@ export declare function moduleExistsIn(fs: any, path: any, cwd: string, dir: str
337
337
  * in the process.
338
338
  */
339
339
  export declare function findBoundaryIn(fs: any, path: any, cwd: string, componentPath: string, fileName: string): string | null;
340
+ /** Loopback host? `host` is a lowercase authority (`host[:port]`,
341
+ * `[ipv6]:port`). True for exactly `localhost`, dotted 127.0.0.0/8
342
+ * without leading zeros, `::1`, or `0.0.0.0`, with an optional numeric
343
+ * port. The match is exact, so
344
+ * `localhost.example.com` is not loopback. Mirrors `is_loopback_host` in
345
+ * crates/router/src/public_url.rs. Exported for tests. */
346
+ export declare function isLoopbackHost(host: string): boolean;
340
347
  /** Pure origin resolution (exported for tests).
341
348
  *
342
349
  * SECURITY: the request `Host` (and `X-Forwarded-Proto`) is attacker-
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Type definitions for the function system.
3
3
  */
4
+ import type { RawResponse, RawResponseInit } from "./response";
4
5
  /**
5
6
  * Declarative auth requirement for a function. The framework
6
7
  * enforces this BEFORE the handler runs — if the caller doesn't
@@ -296,8 +297,23 @@ export interface Scheduler {
296
297
  runAfter(delayMs: number, fnName: string, args: Record<string, unknown>): Promise<string>;
297
298
  /** Schedule a function to run at a specific time (Unix ms). */
298
299
  runAt(timestamp: number, fnName: string, args: Record<string, unknown>): Promise<string>;
299
- /** Cancel a previously scheduled function. */
300
- cancel(scheduleId: string): Promise<void>;
300
+ /**
301
+ * Cancel a scheduled function that has not started. Pass the id
302
+ * `runAfter` or `runAt` returned.
303
+ *
304
+ * Resolves `{ cancelled: true }` when the job will not run, and
305
+ * `{ cancelled: false }` when the id is unknown or the job already
306
+ * started or finished (a running job is not interrupted). Rejects with
307
+ * `SCHEDULE_CANCEL_FAILED` when the cancel could not be saved; the job
308
+ * then still runs.
309
+ *
310
+ * Inside a mutation, the cancel commits or rolls back with the
311
+ * mutation: if the mutation throws, the job stays scheduled. From an
312
+ * action, it takes effect at once.
313
+ */
314
+ cancel(scheduleId: string): Promise<{
315
+ cancelled: boolean;
316
+ }>;
301
317
  }
302
318
  /**
303
319
  * Transactional email transport.
@@ -411,16 +427,24 @@ export interface Llm {
411
427
  * if (res.stop_reason === "tool_use") { ...run tools, loop... }
412
428
  * ```
413
429
  *
414
- * Streaming does NOT extend the function's call deadline — it is an
415
- * absolute wall clock from invocation (PYLON_FN_CALL_TIMEOUT, 30s
416
- * default). A long agent run must declare its own `timeout` on the
417
- * function def.
430
+ * The function's call timeout (PYLON_FN_CALL_TIMEOUT, 30s default, or
431
+ * the function's own `timeout`) is an idle timeout. A running stream
432
+ * counts as activity, but no call outlives 10× its timeout, so a long
433
+ * agent run should still declare its own `timeout`.
434
+ *
435
+ * Pass `{ signal }` to stop a stream early. When the signal aborts,
436
+ * the promise rejects with `err.code === "LLM_CANCELLED"` and the
437
+ * server closes the provider connection, so the model stops
438
+ * generating (and billing) tokens. A stream still running when the
439
+ * function returns or times out is cancelled the same way.
418
440
  *
419
441
  * Same errors and same gating as {@link Llm.complete} — including
420
442
  * the model allowlist, so streaming can't be used to reach a model
421
443
  * `complete` would refuse.
422
444
  */
423
- stream(request: LlmCompleteRequest, onEvent: (event: LlmStreamEvent) => void): Promise<LlmCompleteResponse>;
445
+ stream(request: LlmCompleteRequest, onEvent: (event: LlmStreamEvent) => void, options?: {
446
+ signal?: AbortSignal;
447
+ }): Promise<LlmCompleteResponse>;
424
448
  /**
425
449
  * Batch-embed texts via the configured embeddings provider. One
426
450
  * embedding per input, in input order. Pair with a
@@ -523,18 +547,153 @@ export interface Workflows {
523
547
  /**
524
548
  * Start a workflow instance by name. Returns immediately with the
525
549
  * instance id — the engine's background driver executes the steps.
550
+ *
551
+ * With `key` (for example a lead id), at most one non-terminal run
552
+ * exists per workflow name and key. If one exists, no new run starts
553
+ * and its id is returned with `created: false`. A finished run frees
554
+ * the key.
526
555
  */
527
- start(name: string, input?: unknown): Promise<{
556
+ start(name: string, input?: unknown, opts?: {
557
+ key?: string;
558
+ }): Promise<{
528
559
  id: string;
560
+ created: boolean;
529
561
  }>;
530
562
  /**
531
- * Deliver an event to an instance paused on
532
- * `wf.waitForEvent(event)`. Rejects when the instance isn't waiting
533
- * for that event.
563
+ * Send an event to a run. If the run is waiting for it
564
+ * (`wf.waitForEvent(event)`), the run resumes and `delivered` is true.
565
+ * Otherwise the event is buffered (`buffered: true`) and the run's next
566
+ * `waitForEvent(event)` consumes it. Rejects when the run has finished
567
+ * or already holds 100 buffered events. Events sent without data
568
+ * arrive as `{}`.
534
569
  */
535
570
  sendEvent(workflowId: string, event: string, data?: unknown): Promise<{
536
571
  delivered: boolean;
572
+ buffered: boolean;
537
573
  }>;
574
+ /**
575
+ * Cancel a run. No further step runs. A step already in progress may
576
+ * finish, but its result is discarded. Resolves `cancelled: false` when
577
+ * the run had already finished. Rejects for an unknown id.
578
+ */
579
+ cancel(workflowId: string, opts?: {
580
+ reason?: string;
581
+ }): Promise<{
582
+ cancelled: boolean;
583
+ }>;
584
+ /** Read one run, or null if the id is unknown. */
585
+ get(workflowId: string): Promise<WorkflowRunSummary | null>;
586
+ /**
587
+ * List runs, newest first. `status: "active"` matches every
588
+ * non-terminal status. `limit` defaults to 100 (max 1000).
589
+ *
590
+ * ```ts
591
+ * // Seller texted STOP: end every cadence for the lead.
592
+ * const runs = await ctx.workflows.list({ key: leadId, status: "active" });
593
+ * for (const run of runs) {
594
+ * await ctx.workflows.cancel(run.id, { reason: "STOP" });
595
+ * }
596
+ * ```
597
+ */
598
+ list(filter?: {
599
+ name?: string;
600
+ key?: string;
601
+ status?: WorkflowRunStatus | "active";
602
+ limit?: number;
603
+ }): Promise<WorkflowRunSummary[]>;
604
+ }
605
+ /**
606
+ * The application audit log (`ctx.audit` on mutations and actions).
607
+ * Append-only, stored with the auth audit events.
608
+ */
609
+ export interface AuditLog {
610
+ /**
611
+ * Record an event. The actor and tenant come from the caller's session.
612
+ * `action` is a short dotted name (`lead.export`, `script.update`) and
613
+ * is stored as `app.<action>`. `meta` values are stored as strings
614
+ * (non-strings as JSON text); keep secrets out of it.
615
+ *
616
+ * Inside a mutation the event is written after the mutation commits and
617
+ * dropped if it rolls back. Rejects with `AUDIT_WRITE_FAILED` when an
618
+ * action's event could not be stored.
619
+ */
620
+ log(event: {
621
+ action: string;
622
+ entity?: string;
623
+ entityId?: string;
624
+ /** The user the event is about, when not the actor. */
625
+ subject?: string;
626
+ meta?: Record<string, unknown>;
627
+ }): Promise<{
628
+ id: string;
629
+ }>;
630
+ }
631
+ /**
632
+ * `ctx.audit` on actions: {@link AuditLog.log} plus reads. Mutations get
633
+ * only `log`: in a Postgres mutation the transaction holds a connection,
634
+ * and a read would need a second.
635
+ */
636
+ export interface Audit extends AuditLog {
637
+ /**
638
+ * Read events, newest first. A caller with a tenant reads that tenant's
639
+ * events; a caller without one reads only events it performed. Admin
640
+ * callers may pass `tenant` or read all tenants. `action` matches
641
+ * `lead.export` as `app.lead.export`; framework actions
642
+ * (`entity.update`, `retention.delete`) and auth action names match as
643
+ * given. `limit` defaults to 100 (max 1000). To page,
644
+ * pass `beforeId` set to the last event's `id`. `before` (unix seconds)
645
+ * limits by time.
646
+ */
647
+ list(filter?: {
648
+ entity?: string;
649
+ entityId?: string;
650
+ actor?: string;
651
+ action?: string;
652
+ tenant?: string;
653
+ before?: number;
654
+ beforeId?: string;
655
+ limit?: number;
656
+ }): Promise<AuditEntry[]>;
657
+ }
658
+ export interface AuditEntry {
659
+ id: string;
660
+ /** Unix seconds. */
661
+ createdAt: number;
662
+ /** `app.<action>`, `entity.insert|update|delete`, `retention.delete`, or an auth action. */
663
+ action: string;
664
+ actor: string | null;
665
+ subject: string | null;
666
+ tenant: string | null;
667
+ entity: string | null;
668
+ entityId: string | null;
669
+ ip: string | null;
670
+ success: boolean;
671
+ reason: string | null;
672
+ /** For `entity.*` events, `fields` lists the changed field names. */
673
+ meta: Record<string, string>;
674
+ }
675
+ export type WorkflowRunStatus = "pending" | "running" | "sleeping" | "waiting" | "completed" | "failed" | "cancelled";
676
+ /** A workflow run without its step history. Times are unix seconds. */
677
+ export interface WorkflowRunSummary {
678
+ id: string;
679
+ name: string;
680
+ key: string | null;
681
+ status: WorkflowRunStatus;
682
+ input: unknown;
683
+ output: unknown;
684
+ error: string | null;
685
+ cancelReason: string | null;
686
+ /** Event name the run is waiting for, when `status` is "waiting". */
687
+ waitingFor: string | null;
688
+ /** When the current wait times out, if it has a timeout. */
689
+ waitDeadline: number | null;
690
+ /** When the current sleep ends. */
691
+ wakeAt: number | null;
692
+ /** Events sent but not yet consumed by a `waitForEvent`. */
693
+ bufferedEvents: number;
694
+ createdAt: number | null;
695
+ startedAt: number | null;
696
+ completedAt: number | null;
538
697
  }
539
698
  export interface LlmMessage {
540
699
  role: "user" | "assistant" | "system" | "tool";
@@ -915,6 +1074,8 @@ export interface MutationCtx<R extends AuthRequirement = "optional"> {
915
1074
  connections: Connections;
916
1075
  /** Durable workflows: start / deliver events — see {@link Workflows}. */
917
1076
  workflows: Workflows;
1077
+ /** Application audit log (write only in mutations) — see {@link AuditLog}. */
1078
+ audit: AuditLog;
918
1079
  /** Signed file-download URLs — see {@link Files}. */
919
1080
  files: Files;
920
1081
  /** Shard tickets, reads, and inputs after the commit — see {@link Shards}. */
@@ -947,8 +1108,18 @@ export interface TenantDomainResult {
947
1108
  status: string;
948
1109
  /** Whether the hostname + TLS certificate are both active. */
949
1110
  active?: boolean;
950
- /** The single CNAME the customer points their hostname at. */
951
- cnameTarget: string;
1111
+ /**
1112
+ * True for an apex domain (`acme.com`). An apex cannot be a CNAME, so the
1113
+ * customer adds A/AAAA records pointing at the app instead.
1114
+ */
1115
+ apex?: boolean;
1116
+ /**
1117
+ * Every DNS record the customer adds, for both kinds of domain: a CNAME
1118
+ * (subdomain) or A/AAAA (apex), plus ownership and certificate records.
1119
+ */
1120
+ records?: TenantDomainRecord[];
1121
+ /** The CNAME the customer points a subdomain at; null for an apex. */
1122
+ cnameTarget: string | null;
952
1123
  /** TXT record proving domain ownership before DNS cutover (or null). */
953
1124
  ownership: TenantDomainDns | null;
954
1125
  /** TXT records that issue the DV certificate. */
@@ -956,6 +1127,13 @@ export interface TenantDomainResult {
956
1127
  /** Human-readable provisioning errors, empty when healthy. */
957
1128
  errors: string[];
958
1129
  }
1130
+ export interface TenantDomainRecord {
1131
+ type: "A" | "AAAA" | "CNAME" | "TXT";
1132
+ name: string;
1133
+ value: string;
1134
+ /** What the record is for, to show the customer. */
1135
+ purpose: string;
1136
+ }
959
1137
  /**
960
1138
  * Platform domains: attach / register / detach custom domains for your app's
961
1139
  * OWN end-customers, so each tenant can reach your app on their own hostname.
@@ -1067,6 +1245,49 @@ export interface Domains {
1067
1245
  * itself serve the app — attach a host with `add()` afterward. */
1068
1246
  register(domainName: string, opts?: RegisterDomainOptions): Promise<RegisteredDomainResult>;
1069
1247
  }
1248
+ /** Options for {@link Agents.run}. */
1249
+ export interface AgentRunOptions {
1250
+ /** The message for this turn. */
1251
+ input: string;
1252
+ /** Continue this run. It must belong to `as.userId` and this agent. */
1253
+ runId?: string;
1254
+ /** Display title, stored on a new run. */
1255
+ title?: string;
1256
+ /**
1257
+ * The user the run belongs to and runs as. The run shows up in that
1258
+ * user's synced `AgentRun`/`AgentMessage` rows. `admin: true` also
1259
+ * gives the run admin rights (needed for an agent declared
1260
+ * `auth: "admin"`), and is allowed only when the caller is an admin.
1261
+ */
1262
+ as: {
1263
+ userId: string;
1264
+ admin?: boolean;
1265
+ };
1266
+ /**
1267
+ * JSON the agent's tool handlers receive as `run.context` (their third
1268
+ * argument), for example the conversation a webhook turn belongs to.
1269
+ * Stored on the run as `AgentRun.context`, so later turns of the run
1270
+ * see it too. The run's owner can read it; do not put secrets in it.
1271
+ */
1272
+ context?: Record<string, unknown>;
1273
+ }
1274
+ /**
1275
+ * Run an `agent()` from server code. See {@link ActionCtx.agents}.
1276
+ */
1277
+ export interface Agents {
1278
+ /**
1279
+ * Run agent `name` (its file name in `functions/`) as `options.as`.
1280
+ * Resolves with the agent's result once the turn finishes, like a
1281
+ * `streamFn` call's final `result` event. The agent's tokens are not
1282
+ * streamed to this caller; they are in the run's `AgentMessage` rows.
1283
+ *
1284
+ * Only server code can do this: the calling action must be
1285
+ * `internal: true` (jobs, workflows, scheduled work) or run with admin
1286
+ * rights (an admin session, or a webhook after `ctx.auth.elevate`).
1287
+ * Otherwise it throws `AGENT_RUN_FORBIDDEN`.
1288
+ */
1289
+ run(name: string, options: AgentRunOptions): Promise<import("./agent").AgentResult>;
1290
+ }
1070
1291
  export interface ActionCtx<R extends AuthRequirement = "optional"> {
1071
1292
  auth: AuthInfo<R>;
1072
1293
  stream: Stream;
@@ -1081,6 +1302,10 @@ export interface ActionCtx<R extends AuthRequirement = "optional"> {
1081
1302
  connections: Connections;
1082
1303
  /** Durable workflows: start / deliver events — see {@link Workflows}. */
1083
1304
  workflows: Workflows;
1305
+ /** Application audit log — see {@link Audit}. */
1306
+ audit: Audit;
1307
+ /** Run an `agent()` as a named user from server code — see {@link Agents}. */
1308
+ agents: Agents;
1084
1309
  /** Attach / register custom domains for your app's OWN end-customers
1085
1310
  * (platform domains) — see {@link Domains}. Cloud-only. */
1086
1311
  domains: Domains;
@@ -1125,12 +1350,34 @@ export interface ActionCtx<R extends AuthRequirement = "optional"> {
1125
1350
  * ```
1126
1351
  */
1127
1352
  request?: RequestInfo;
1353
+ /**
1354
+ * Build a raw HTTP response. When an action called through
1355
+ * `/api/webhooks/<name>` returns it, the route sends this status,
1356
+ * content type, headers, and body instead of JSON. Other routes return
1357
+ * it as ordinary JSON data. Throws on an invalid status or header.
1358
+ *
1359
+ * ```ts
1360
+ * // Twilio expects TwiML, not JSON.
1361
+ * return ctx.response({ contentType: "text/xml", body: "<Response/>" });
1362
+ * ```
1363
+ */
1364
+ response(init?: RawResponseInit): RawResponse;
1128
1365
  }
1129
1366
  /** HTTP request metadata available on an action's ctx when invoked via an
1130
1367
  * HTTP route binding. Header names are lowercased. */
1131
1368
  export interface RequestInfo {
1132
1369
  method: string;
1370
+ /** Request path with the query string, as received. */
1133
1371
  path: string;
1372
+ /**
1373
+ * The full URL the client requested: scheme, host, path, and query.
1374
+ * Behind a proxy, the host comes from `X-Forwarded-Host` or `Host`
1375
+ * only when it is trusted (loopback, the `PYLON_PUBLIC_URL` host,
1376
+ * `PYLON_CANONICAL_HOST`, a `PYLON_TRUSTED_HOSTS` entry, or one of the
1377
+ * app's domains); otherwise the `PYLON_PUBLIC_URL` origin is used. Use
1378
+ * it to verify providers that sign the whole URL, such as Twilio.
1379
+ */
1380
+ url: string;
1134
1381
  headers: Record<string, string>;
1135
1382
  rawBody: string;
1136
1383
  }
@@ -57,6 +57,7 @@ export type WorkflowRunnerResponse = {
57
57
  } | {
58
58
  action: "wait_event";
59
59
  event: string;
60
+ timeout?: string;
60
61
  } | {
61
62
  action: "complete";
62
63
  output: unknown;
@@ -80,10 +81,26 @@ export interface WorkflowRun<TInput = unknown> {
80
81
  /** Pause the workflow for a duration ("30s", "5m", "24h", "7d"). */
81
82
  sleep(duration: string): Promise<void>;
82
83
  /**
83
- * Pause until `POST /api/workflows/<id>/event` delivers this event.
84
- * Resolves with the event's data payload.
84
+ * Pause until the event arrives through `ctx.workflows.sendEvent` or
85
+ * `POST /api/workflows/<id>/event`. Resolves with the event's data
86
+ * (`{}` when it was sent without data).
87
+ *
88
+ * Events sent while the run is not waiting for them are buffered. The
89
+ * next `waitForEvent` with that name consumes the oldest buffered
90
+ * event, so an event that arrives during a step is not lost.
91
+ *
92
+ * The same event name can be awaited more than once in a run; each
93
+ * call consumes one event.
85
94
  */
86
95
  waitForEvent<T = unknown>(eventName: string): Promise<T>;
96
+ /**
97
+ * Same as above, but resolves with `null` if no event arrives within
98
+ * `timeout` ("60s", "5m", "24h", "7d"). The timeout is durable: it
99
+ * survives restarts, like `sleep`.
100
+ */
101
+ waitForEvent<T = unknown>(eventName: string, opts: {
102
+ timeout: string;
103
+ }): Promise<T | null>;
87
104
  }
88
105
  export interface WorkflowDefinition<TInput = unknown> {
89
106
  readonly __pylonWorkflow: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/functions",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "TypeScript function runtime for pylon — defines server-side queries, mutations, and actions.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -128,6 +128,10 @@ export function registerAgentInternals(
128
128
  pendingInput: [],
129
129
  cancelRequested: false,
130
130
  steps: 0,
131
+ // Only written when present, so an app that declares its own
132
+ // AgentRun without a `context` column keeps working until it
133
+ // uses ctx.agents.run with a context.
134
+ ...(args.context != null ? { context: args.context } : {}),
131
135
  createdAt: nowIso(),
132
136
  updatedAt: nowIso(),
133
137
  });
@@ -188,6 +192,7 @@ export function registerAgentInternals(
188
192
  if (args.error !== undefined) patch.error = args.error;
189
193
  if (args.streamId !== undefined) patch.streamId = args.streamId;
190
194
  if (args.steps !== undefined) patch.steps = Number(args.steps) || 0;
195
+ if (args.context != null) patch.context = args.context;
191
196
  // Claiming the run starts a new generation, which supersedes
192
197
  // a cancel left set by one that died before acting on it.
193
198
  // Terminal statuses clear it because it has been honoured.