@flow-engineer/flow 0.0.1 → 0.2.1

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 (104) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +611 -0
  3. package/coding-agents/skill.md +353 -0
  4. package/coding-agents/test-skill.md +223 -0
  5. package/coding-agents/tools-skill.md +89 -0
  6. package/dist/flow-messaging/src/client.d.ts +40 -0
  7. package/dist/flow-messaging/src/client.js +142 -0
  8. package/dist/flow-messaging/src/index.d.ts +72 -0
  9. package/dist/flow-messaging/src/index.js +605 -0
  10. package/dist/simulators/src/index.d.ts +2 -0
  11. package/dist/simulators/src/index.js +2 -0
  12. package/dist/simulators/src/page.html +1245 -0
  13. package/dist/simulators/src/person.html +100 -0
  14. package/dist/simulators/src/platforms.d.ts +29 -0
  15. package/dist/simulators/src/platforms.js +72 -0
  16. package/dist/simulators/src/server.d.ts +201 -0
  17. package/dist/simulators/src/server.js +759 -0
  18. package/dist/src/agent.d.ts +504 -0
  19. package/dist/src/agent.js +818 -0
  20. package/dist/src/channels.d.ts +178 -0
  21. package/dist/src/channels.js +243 -0
  22. package/dist/src/conversation.d.ts +361 -0
  23. package/dist/src/conversation.js +1487 -0
  24. package/dist/src/db.d.ts +18 -0
  25. package/dist/src/db.js +57 -0
  26. package/dist/src/events.d.ts +17 -0
  27. package/dist/src/events.js +20 -0
  28. package/dist/src/http.d.ts +45 -0
  29. package/dist/src/http.js +269 -0
  30. package/dist/src/index.d.ts +14 -0
  31. package/dist/src/index.js +11 -0
  32. package/dist/src/model.d.ts +21 -0
  33. package/dist/src/model.js +96 -0
  34. package/dist/src/names.d.ts +22 -0
  35. package/dist/src/names.js +43 -0
  36. package/dist/src/nudges.d.ts +23 -0
  37. package/dist/src/nudges.js +117 -0
  38. package/dist/src/postgres.d.ts +20 -0
  39. package/dist/src/postgres.js +62 -0
  40. package/dist/src/schedules.d.ts +60 -0
  41. package/dist/src/schedules.js +249 -0
  42. package/dist/src/skills.d.ts +12 -0
  43. package/dist/src/skills.js +69 -0
  44. package/dist/src/store.d.ts +214 -0
  45. package/dist/src/store.js +448 -0
  46. package/dist/src/talk.d.ts +57 -0
  47. package/dist/src/talk.js +166 -0
  48. package/dist/src/time.d.ts +71 -0
  49. package/dist/src/time.js +170 -0
  50. package/dist/src/tools.d.ts +43 -0
  51. package/dist/src/tools.js +26 -0
  52. package/dist/src/traces.d.ts +31 -0
  53. package/dist/src/traces.js +303 -0
  54. package/dist/src/types.d.ts +260 -0
  55. package/dist/src/types.js +2 -0
  56. package/dist/testing/src/account.d.ts +4 -0
  57. package/dist/testing/src/account.js +580 -0
  58. package/dist/testing/src/chat.d.ts +3 -0
  59. package/dist/testing/src/chat.js +117 -0
  60. package/dist/testing/src/check-endpoint.d.ts +31 -0
  61. package/dist/testing/src/check-endpoint.js +158 -0
  62. package/dist/testing/src/chinwag.d.ts +2 -0
  63. package/dist/testing/src/chinwag.js +6 -0
  64. package/dist/testing/src/cli.d.ts +2 -0
  65. package/dist/testing/src/cli.js +592 -0
  66. package/dist/testing/src/client.d.ts +117 -0
  67. package/dist/testing/src/client.js +956 -0
  68. package/dist/testing/src/flow-login.d.ts +143 -0
  69. package/dist/testing/src/flow-login.js +249 -0
  70. package/dist/testing/src/http-agent.d.ts +47 -0
  71. package/dist/testing/src/http-agent.js +155 -0
  72. package/dist/testing/src/index.d.ts +3 -0
  73. package/dist/testing/src/index.js +2 -0
  74. package/dist/testing/src/new.d.ts +13 -0
  75. package/dist/testing/src/new.js +147 -0
  76. package/dist/testing/src/print-run.d.ts +3 -0
  77. package/dist/testing/src/print-run.js +22 -0
  78. package/dist/testing/src/protocol.d.ts +159 -0
  79. package/dist/testing/src/protocol.js +28 -0
  80. package/dist/testing/src/report.d.ts +74 -0
  81. package/dist/testing/src/report.js +90 -0
  82. package/dist/testing/src/show.d.ts +36 -0
  83. package/dist/testing/src/show.js +280 -0
  84. package/dist/testing/src/suite.d.ts +87 -0
  85. package/dist/testing/src/suite.js +198 -0
  86. package/docs/agents.md +85 -0
  87. package/examples/cafe/agent/AGENTS.md +10 -0
  88. package/examples/cafe/agent/skills/allergens/SKILL.md +7 -0
  89. package/examples/cafe/src/agent.ts +43 -0
  90. package/examples/cafe/src/channels.ts +20 -0
  91. package/examples/cafe/src/main.ts +30 -0
  92. package/examples/cafe/src/tools.ts +62 -0
  93. package/examples/cafe/tests/scenarios/latte-correction.yaml +30 -0
  94. package/examples/cafe/tests/scenarios/order-fails.yaml +26 -0
  95. package/examples/cafe/tests/tools/get_menu.yaml +14 -0
  96. package/examples/cafe/tests/tools/place_order.yaml +29 -0
  97. package/examples/http-agent/server.mjs +32 -0
  98. package/examples/http-agent/tests/scenarios/opening-hours.yaml +16 -0
  99. package/flow-messaging/README.md +191 -0
  100. package/package.json +63 -8
  101. package/spec/agent-endpoint.openapi.yaml +148 -0
  102. package/spec/nudges.openapi.yaml +167 -0
  103. package/spec/request.schema.json +121 -0
  104. package/spec/response.schema.json +284 -0
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: flow-framework-tools
3
+ description: Write or change a Flow Framework agent's tools and their test files (tests/tools/*.yaml). Load when adding a tool to a Flow Framework agent, changing what a tool returns, or describing how a tool behaves in tests.
4
+ ---
5
+
6
+ # Writing tools for a Flow Framework agent
7
+
8
+ Read the `flow-framework` skill (`skill.md`) first if you haven't. Tests that use these tool files are
9
+ in the `flow-framework-tests` skill (`test-skill.md`).
10
+
11
+ ## The tool
12
+
13
+ ```ts
14
+ import { tool, z } from "@flow-engineer/flow";
15
+
16
+ export const findSlots = tool({
17
+ name: "find_session_slots",
18
+ description: "Every open time to move the session to, for a date range.",
19
+ input: z.object({
20
+ from_date: z.string().optional().describe("First day to search, YYYY-MM-DD; default today"),
21
+ to_date: z.string().optional().describe("Last day, YYYY-MM-DD; default a week later"),
22
+ }),
23
+ run: async ({ from_date, to_date }, { conversationId }) => backend.findSlots(conversationId, from_date, to_date),
24
+ });
25
+ ```
26
+
27
+ - The name and description are what the model reads to choose the tool: say what it does and
28
+ what it returns, in one line.
29
+ - `input` is a zod schema; Flow Framework checks the model's input against it before `run` is called and
30
+ sends the model the error if it doesn't fit.
31
+ - `run` returns any JSON-able value; throw to report a failure (the agent sees `Failed: <message>`).
32
+ - Don't make tools for talking to the person (send_<channel>, react, share_updates), for reminders
33
+ or scheduled messages, or for opting out of messages: Flow Framework has them.
34
+ - Key a person's data by `context.person` (who Flow Framework verified they are) or `context.conversationId`,
35
+ never by an id the model passed. `context.person` is `key:<resolve's value>`, else
36
+ `phone:<E.164>` (`phone:+15550100100`), else `<channel>:<address>`. To look the person up in the
37
+ owner's backend use `context.key`: exactly what `resolve` returned (`customer-7`), or null when
38
+ they are known only by phone or channel.
39
+ - A tool that changes something (books, pays, sends) passes `context.idempotencyKey` to its backend:
40
+ it is the same for the same input in the same turn, also when the turn runs again after a
41
+ restart, so the booking happens once. `context.callId` is the model's id for the call;
42
+ `context.signal` aborts when the person is forgotten.
43
+ - Don't decide what runs in the background. The reply waits for every call; one moves to the
44
+ background only if the person writes again meanwhile or it passes its limit (the agent's
45
+ `deadlineMs`, 60 s by default), and its result comes back into the chat by itself. Set
46
+ `deadlineMs` on a tool for its own limit: shorter for one that should never hold a reply long,
47
+ longer for one known to be slow.
48
+ - Say what you covered. An agent reads a list as complete: return the range or filter you
49
+ searched ("Mon Oct 12 through Sun Oct 18", "all open times") and never cut results short
50
+ silently. If there are more, say so.
51
+
52
+ ## The tool's test file
53
+
54
+ In tests no real tool runs: a model plays each tool from the scenario's world. `tests/tools/<tool>.yaml`
55
+ tells it how the tool behaves: every kind of response it gives, each with an example and how long
56
+ it takes. Write one per tool.
57
+
58
+ ```yaml
59
+ tool: place_order
60
+ behaviour: |
61
+ Order numbers are BL- followed by four digits and go up by one per order. ready_by is about
62
+ ten minutes after the order is placed. Fails if an item, size or milk is not on the menu.
63
+ responses:
64
+ - when: The order goes through (usual)
65
+ latency_ms: 4800
66
+ input: { name: Sam, items: [{ item: Latte, size: large, milk: oat, quantity: 1 }] }
67
+ output: { order_id: BL-1040, ready_by: "{{now + 10m}}", pay_at_pickup: true }
68
+ - when: The point of sale is busy; the order still goes through
69
+ latency_ms: 11000
70
+ output: { order_id: BL-1041, ready_by: "{{now + 15m}}", pay_at_pickup: true }
71
+ - when: The point of sale is down
72
+ latency_ms: 3000
73
+ error: Point of sale unavailable
74
+ ```
75
+
76
+ - `behaviour`: what the tool does beyond its description, in plain words (ids, ordering, limits,
77
+ when it fails).
78
+ - `responses`: one entry per kind of outcome, the usual one first, then slow ones and each error
79
+ the real system can give. `when` says when it happens; `latency_ms` is how long it takes;
80
+ then `output` (what `run` returns) or `error` (what it throws). `input` is optional, as an
81
+ example of a call.
82
+ - Use real responses when you have them (copy them from the agent's recorded tool calls, with
83
+ personal data removed); otherwise write realistic ones in exactly the shape `run` returns.
84
+ - Examples show the shape, not the answer: the simulator takes ids, names and dates from the
85
+ scenario's world, the call's input and what the agent already said. Write dates as placeholders
86
+ for the test's clock (`{{now + 10m}}`, `{{now + 2d | date}}`; see the `flow-framework-tests` skill),
87
+ never as written dates, which go stale.
88
+ - Latency lives only here. Scenarios describe situations ("the point of sale is slow this
89
+ morning"); the simulator picks the matching response and latency on each call.
@@ -0,0 +1,40 @@
1
+ export declare const API_VERSION = "2026-11-01";
2
+ /** The spec's typed error ({"error": {type, message, channel_code, retry_after, ...}}), or "network". */
3
+ export declare class FlowMessagingError extends Error {
4
+ readonly status: number;
5
+ readonly type: string;
6
+ readonly channelCode?: string;
7
+ readonly retryAfter?: number;
8
+ constructor(status: number, type: string, message: string, extra?: {
9
+ channelCode?: string;
10
+ retryAfter?: number;
11
+ });
12
+ /** Worth trying again later: rate limits, Flow's or the channel's 5xx, no answer. */
13
+ get retryable(): boolean;
14
+ }
15
+ /**
16
+ * One of Flow's object IDs as a URL path segment: `<prefix>_` and letters, digits, `_` or `-` (Flow
17
+ * makes `<prefix>_<ULID>`). IDs come from inbound events and callers, and every request carries the
18
+ * key, so anything else (a `/`, `..`, `?`) is refused rather than sent to another path.
19
+ */
20
+ export declare function idSegment(prefix: string, id: unknown): string;
21
+ export declare class Client {
22
+ private readonly apiKey;
23
+ readonly baseUrl: string;
24
+ constructor(options: {
25
+ apiKey: string;
26
+ baseUrl: string;
27
+ });
28
+ get<T>(path: string): Promise<T>;
29
+ /** A file's bytes: GET /v1/files/{id} answers them or redirects to a signed URL, which fetch follows without the key. */
30
+ download(fileId: string): Promise<Uint8Array>;
31
+ /** A POST with its own Idempotency-Key, kept across the retries of this one call; `tries: 1` for courtesies. */
32
+ post<T>(path: string, body: unknown, tries?: number): Promise<T>;
33
+ private request;
34
+ }
35
+ /**
36
+ * Flow-Signature: `t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Each v1 is HMAC-SHA256, under one of the
37
+ * endpoint's signing secrets, of `t.<t>.<raw body>`; any match within five minutes of now passes
38
+ * (two secrets overlap while one is rotated).
39
+ */
40
+ export declare function verifySignature(body: Buffer, header: string | undefined, secrets: string[], nowSeconds?: number): boolean;
@@ -0,0 +1,142 @@
1
+ // A small client for Flow Messaging (api.flow.engineer), against its OpenAPI spec, version
2
+ // 2026-11-01: plain fetch, only the calls the channels and the webhook make.
3
+ //
4
+ // Every POST carries an Idempotency-Key, and a retry repeats the same key, so a send is never made
5
+ // twice. A 429 or a 5xx is tried again in place after its Retry-After when that is short; a longer
6
+ // wait, or any other error, is raised as FlowMessagingError (the spec's typed error). Keys and
7
+ // message text are never logged.
8
+ import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
9
+ export const API_VERSION = "2026-11-01";
10
+ const TRIES = 3;
11
+ /** The longest Retry-After waited out in place (seconds); a longer one is the caller's. */
12
+ const MAX_WAIT = 10;
13
+ const TIMEOUT_MS = 20_000;
14
+ /** How far a webhook's signed timestamp may be from now (seconds), as the spec says. */
15
+ const SIGNATURE_TOLERANCE = 300;
16
+ /** The spec's typed error ({"error": {type, message, channel_code, retry_after, ...}}), or "network". */
17
+ export class FlowMessagingError extends Error {
18
+ status;
19
+ type;
20
+ channelCode;
21
+ retryAfter;
22
+ constructor(status, type, message, extra = {}) {
23
+ super(message);
24
+ this.name = "FlowMessagingError";
25
+ this.status = status;
26
+ this.type = type;
27
+ this.channelCode = extra.channelCode;
28
+ this.retryAfter = extra.retryAfter;
29
+ }
30
+ /** Worth trying again later: rate limits, Flow's or the channel's 5xx, no answer. */
31
+ get retryable() {
32
+ return this.status === 0 || this.status === 429 || this.status >= 500 || (this.type === "idempotency_conflict" && this.channelCode === "in_progress");
33
+ }
34
+ }
35
+ /**
36
+ * One of Flow's object IDs as a URL path segment: `<prefix>_` and letters, digits, `_` or `-` (Flow
37
+ * makes `<prefix>_<ULID>`). IDs come from inbound events and callers, and every request carries the
38
+ * key, so anything else (a `/`, `..`, `?`) is refused rather than sent to another path.
39
+ */
40
+ export function idSegment(prefix, id) {
41
+ if (typeof id !== "string" || !new RegExp(`^${prefix}_[A-Za-z0-9_-]+$`).test(id)) {
42
+ throw new Error(`not a Flow Messaging ${prefix}_ id: ${JSON.stringify(String(id)).slice(0, 80)}`);
43
+ }
44
+ return encodeURIComponent(id);
45
+ }
46
+ export class Client {
47
+ apiKey;
48
+ baseUrl;
49
+ constructor(options) {
50
+ this.apiKey = options.apiKey;
51
+ let end = options.baseUrl.length;
52
+ while (end > 0 && options.baseUrl[end - 1] === "/")
53
+ end--;
54
+ this.baseUrl = options.baseUrl.slice(0, end);
55
+ }
56
+ get(path) {
57
+ return this.request("GET", path);
58
+ }
59
+ /** A file's bytes: GET /v1/files/{id} answers them or redirects to a signed URL, which fetch follows without the key. */
60
+ async download(fileId) {
61
+ const path = `/v1/files/${idSegment("file", fileId)}`;
62
+ if (!this.apiKey)
63
+ throw new Error("set FLOW_MESSAGING_KEY (or pass apiKey) to reach Flow Messaging");
64
+ const response = await fetch(this.baseUrl + path, { headers: { authorization: `Bearer ${this.apiKey}`, "flow-version": API_VERSION }, signal: AbortSignal.timeout(60_000) });
65
+ if (!response.ok)
66
+ throw await errorOf(response);
67
+ return new Uint8Array(await response.arrayBuffer());
68
+ }
69
+ /** A POST with its own Idempotency-Key, kept across the retries of this one call; `tries: 1` for courtesies. */
70
+ post(path, body, tries = TRIES) {
71
+ return this.request("POST", path, body, randomUUID(), tries);
72
+ }
73
+ async request(method, path, body, idempotencyKey, tries = TRIES) {
74
+ if (!this.apiKey)
75
+ throw new Error("set FLOW_MESSAGING_KEY (or pass apiKey) to reach Flow Messaging");
76
+ const headers = { authorization: `Bearer ${this.apiKey}`, "flow-version": API_VERSION };
77
+ if (body !== undefined)
78
+ headers["content-type"] = "application/json";
79
+ if (idempotencyKey)
80
+ headers["idempotency-key"] = idempotencyKey;
81
+ let last;
82
+ for (let attempt = 1; attempt <= tries; attempt++) {
83
+ let response;
84
+ try {
85
+ response = await fetch(this.baseUrl + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(TIMEOUT_MS) });
86
+ }
87
+ catch (error) {
88
+ last = new FlowMessagingError(0, "network", `no answer from Flow Messaging (${error instanceof Error ? error.name : "error"})`);
89
+ if (attempt < tries)
90
+ await wait(2 ** (attempt - 1));
91
+ continue;
92
+ }
93
+ if (response.ok) {
94
+ const text = await response.text();
95
+ return (text ? JSON.parse(text) : {});
96
+ }
97
+ last = await errorOf(response);
98
+ const pause = last.retryAfter ?? 2 ** (attempt - 1);
99
+ if (!last.retryable || attempt === tries || pause > MAX_WAIT)
100
+ throw last;
101
+ await wait(pause);
102
+ }
103
+ throw last;
104
+ }
105
+ }
106
+ async function errorOf(response) {
107
+ let body = {};
108
+ try {
109
+ const parsed = (await response.json());
110
+ if (parsed && typeof parsed.error === "object")
111
+ body = parsed.error;
112
+ }
113
+ catch { }
114
+ const header = Number(response.headers.get("retry-after"));
115
+ const retryAfter = typeof body.retry_after === "number" ? body.retry_after : Number.isFinite(header) && response.headers.has("retry-after") ? header : undefined;
116
+ const type = body.type ?? (response.status >= 500 ? "api_error" : "invalid_request");
117
+ return new FlowMessagingError(response.status, type, body.message ?? `Flow Messaging answered ${response.status}`, { channelCode: body.channel_code, retryAfter });
118
+ }
119
+ const wait = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
120
+ /**
121
+ * Flow-Signature: `t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Each v1 is HMAC-SHA256, under one of the
122
+ * endpoint's signing secrets, of `t.<t>.<raw body>`; any match within five minutes of now passes
123
+ * (two secrets overlap while one is rotated).
124
+ */
125
+ export function verifySignature(body, header, secrets, nowSeconds = Date.now() / 1000) {
126
+ let stamp = "";
127
+ const given = [];
128
+ for (const part of (header ?? "").split(",")) {
129
+ const [key, value = ""] = part.trim().split("=", 2);
130
+ if (key === "t")
131
+ stamp = value;
132
+ else if (key === "v1" && value)
133
+ given.push(value);
134
+ }
135
+ if (!/^\d+$/.test(stamp) || !given.length || Math.abs(nowSeconds - Number(stamp)) > SIGNATURE_TOLERANCE)
136
+ return false;
137
+ const signed = Buffer.concat([Buffer.from(`t.${stamp}.`), body]);
138
+ return secrets.some((secret) => {
139
+ const expected = Buffer.from(createHmac("sha256", secret).update(signed).digest("hex"));
140
+ return given.some((value) => value.length === expected.length && timingSafeEqual(Buffer.from(value), expected));
141
+ });
142
+ }
@@ -0,0 +1,72 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import { type Agent, type Channel, type ChannelInfo, type WhatsAppFeature } from "../../src/index.ts";
3
+ export { API_VERSION, FlowMessagingError, verifySignature } from "./client.ts";
4
+ export interface FlowMessagingOptions {
5
+ /** The app's API key, `fk_live_...` or `fk_test_...`. Default: FLOW_MESSAGING_KEY (or the older FLOW_MESSAGING_API_KEY). */
6
+ apiKey?: string;
7
+ /** The webhook endpoint's signing secret(s), comma-separated while one is rotated. Default: FLOW_MESSAGING_WEBHOOK_SECRET. */
8
+ webhookSecret?: string;
9
+ /** Default: FLOW_MESSAGING_BASE_URL (or the older FLOW_MESSAGING_API_URL), else https://api.flow.engineer. */
10
+ baseUrl?: string;
11
+ }
12
+ export interface FlowChannelOptions {
13
+ /** The Flow sender (`snd_...`) this channel starts conversations from. Default: FLOW_MESSAGING_<CHANNEL>_SENDER. */
14
+ sender?: string;
15
+ /** The agent's name for the channel. Default: whatsapp, telegram, imessage or sms. */
16
+ name?: string;
17
+ instructions?: string;
18
+ }
19
+ /** What the webhook and the stream need of an agent. */
20
+ export type WebhookAgent = Pick<Agent, "message" | "reacted" | "undelivered">;
21
+ export interface StreamOptions {
22
+ /**
23
+ * A file that keeps the ID of the last event the agent handled, so a restart resumes after it and
24
+ * Flow replays what arrived while the agent was away (for as long as the plan keeps events, 30 days
25
+ * by default). Without it the cursor is in memory: a dropped stream still resumes, a restart starts from now.
26
+ */
27
+ cursorFile?: string;
28
+ }
29
+ /** Why the stream stopped for good: Flow refused the key or the request, so reconnecting cannot help. */
30
+ export interface StreamStop {
31
+ /** The WebSocket close code, such as 4401 (authentication) or 4403 (permission). */
32
+ code: number;
33
+ type: string;
34
+ message: string;
35
+ }
36
+ export interface StreamHandle {
37
+ /** Closes the stream and waits for the event being handled. */
38
+ close(): Promise<void>;
39
+ /** Resolves when Flow refused the stream for good (a bad or revoked key, say); never after close(). */
40
+ stopped: Promise<StreamStop>;
41
+ }
42
+ /** Telegram as Flow sends it: an inline keyboard of up to 10 buttons (or one link), photos, Markdown, no read receipts for bots. */
43
+ export declare const TELEGRAM: ChannelInfo;
44
+ /**
45
+ * iMessage as Flow sends it: plain text up to 9999 characters, photos, inline replies, tapbacks
46
+ * (other emoji go as emoji reactions), typing and read receipts (the whole conversation). No buttons,
47
+ * lists or templates. No reply window is set: the line only answers people who wrote to it, and a
48
+ * send it will not deliver comes back as Not delivered (outside_window).
49
+ */
50
+ export declare const IMESSAGE: ChannelInfo;
51
+ export declare function flowMessaging(options?: FlowMessagingOptions): {
52
+ whatsapp: (o?: FlowChannelOptions & {
53
+ disable?: WhatsAppFeature[];
54
+ }) => Channel;
55
+ telegram: (o?: FlowChannelOptions) => Channel;
56
+ imessage: (o?: FlowChannelOptions) => Channel;
57
+ sms: (o?: FlowChannelOptions) => Channel;
58
+ webhook: (agent: WebhookAgent) => (req: IncomingMessage, res: ServerResponse) => Promise<void>;
59
+ receive: (agent: WebhookAgent, request: {
60
+ body: Buffer | string;
61
+ headers: Record<string, string | string[] | undefined>;
62
+ }) => Promise<{
63
+ status: number;
64
+ body: string;
65
+ }>;
66
+ stream: (agent: WebhookAgent, o?: StreamOptions) => StreamHandle;
67
+ start: (agent: WebhookAgent, o?: StreamOptions & {
68
+ port?: number;
69
+ }) => StreamHandle & {
70
+ mode: "webhook" | "stream";
71
+ };
72
+ };