@flow-engineer/flow 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +594 -2
- package/coding-agents/skill.md +321 -0
- package/coding-agents/test-skill.md +223 -0
- package/coding-agents/tools-skill.md +89 -0
- package/dist/flow-messaging/src/client.d.ts +34 -0
- package/dist/flow-messaging/src/client.js +130 -0
- package/dist/flow-messaging/src/index.d.ts +37 -0
- package/dist/flow-messaging/src/index.js +409 -0
- package/dist/simulators/src/index.d.ts +2 -0
- package/dist/simulators/src/index.js +2 -0
- package/dist/simulators/src/page.html +1245 -0
- package/dist/simulators/src/person.html +100 -0
- package/dist/simulators/src/platforms.d.ts +29 -0
- package/dist/simulators/src/platforms.js +72 -0
- package/dist/simulators/src/server.d.ts +201 -0
- package/dist/simulators/src/server.js +759 -0
- package/dist/src/agent.d.ts +504 -0
- package/dist/src/agent.js +818 -0
- package/dist/src/channels.d.ts +178 -0
- package/dist/src/channels.js +243 -0
- package/dist/src/conversation.d.ts +361 -0
- package/dist/src/conversation.js +1487 -0
- package/dist/src/db.d.ts +18 -0
- package/dist/src/db.js +57 -0
- package/dist/src/events.d.ts +17 -0
- package/dist/src/events.js +20 -0
- package/dist/src/http.d.ts +45 -0
- package/dist/src/http.js +269 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +11 -0
- package/dist/src/model.d.ts +21 -0
- package/dist/src/model.js +96 -0
- package/dist/src/names.d.ts +17 -0
- package/dist/src/names.js +38 -0
- package/dist/src/nudges.d.ts +23 -0
- package/dist/src/nudges.js +117 -0
- package/dist/src/postgres.d.ts +20 -0
- package/dist/src/postgres.js +62 -0
- package/dist/src/schedules.d.ts +60 -0
- package/dist/src/schedules.js +249 -0
- package/dist/src/skills.d.ts +12 -0
- package/dist/src/skills.js +69 -0
- package/dist/src/store.d.ts +214 -0
- package/dist/src/store.js +448 -0
- package/dist/src/talk.d.ts +57 -0
- package/dist/src/talk.js +166 -0
- package/dist/src/time.d.ts +71 -0
- package/dist/src/time.js +170 -0
- package/dist/src/tools.d.ts +43 -0
- package/dist/src/tools.js +26 -0
- package/dist/src/traces.d.ts +31 -0
- package/dist/src/traces.js +303 -0
- package/dist/src/types.d.ts +260 -0
- package/dist/src/types.js +2 -0
- package/dist/testing/src/check-endpoint.d.ts +31 -0
- package/dist/testing/src/check-endpoint.js +158 -0
- package/dist/testing/src/chinwag.d.ts +2 -0
- package/dist/testing/src/chinwag.js +6 -0
- package/dist/testing/src/cli.d.ts +2 -0
- package/dist/testing/src/cli.js +563 -0
- package/dist/testing/src/client.d.ts +115 -0
- package/dist/testing/src/client.js +955 -0
- package/dist/testing/src/http-agent.d.ts +47 -0
- package/dist/testing/src/http-agent.js +155 -0
- package/dist/testing/src/index.d.ts +3 -0
- package/dist/testing/src/index.js +2 -0
- package/dist/testing/src/new.d.ts +9 -0
- package/dist/testing/src/new.js +122 -0
- package/dist/testing/src/print-run.d.ts +3 -0
- package/dist/testing/src/print-run.js +22 -0
- package/dist/testing/src/protocol.d.ts +159 -0
- package/dist/testing/src/protocol.js +28 -0
- package/dist/testing/src/report.d.ts +74 -0
- package/dist/testing/src/report.js +90 -0
- package/dist/testing/src/show.d.ts +36 -0
- package/dist/testing/src/show.js +280 -0
- package/dist/testing/src/suite.d.ts +87 -0
- package/dist/testing/src/suite.js +198 -0
- package/examples/cafe/agent/AGENTS.md +10 -0
- package/examples/cafe/agent/skills/allergens/SKILL.md +7 -0
- package/examples/cafe/src/agent.ts +39 -0
- package/examples/cafe/src/main.ts +16 -0
- package/examples/cafe/src/tools.ts +62 -0
- package/examples/cafe/tests/scenarios/latte-correction.yaml +30 -0
- package/examples/cafe/tests/scenarios/order-fails.yaml +26 -0
- package/examples/cafe/tests/tools/get_menu.yaml +14 -0
- package/examples/cafe/tests/tools/place_order.yaml +29 -0
- package/examples/http-agent/server.mjs +32 -0
- package/examples/http-agent/tests/scenarios/opening-hours.yaml +16 -0
- package/flow-messaging/README.md +152 -0
- package/package.json +65 -4
- package/spec/agent-endpoint.openapi.yaml +148 -0
- package/spec/nudges.openapi.yaml +167 -0
- package/spec/request.schema.json +121 -0
- package/spec/response.schema.json +284 -0
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flow-engineer/flow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "An open-source runtime for chat agents that feel like a person texting back.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/flow-engineer/flow.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/flow-engineer/flow",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22.18"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/src/index.d.ts",
|
|
18
|
+
"default": "./dist/src/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./simulators": {
|
|
21
|
+
"types": "./dist/simulators/src/index.d.ts",
|
|
22
|
+
"default": "./dist/simulators/src/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./flow-messaging": {
|
|
25
|
+
"types": "./dist/flow-messaging/src/index.d.ts",
|
|
26
|
+
"default": "./dist/flow-messaging/src/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./testing": {
|
|
29
|
+
"types": "./dist/testing/src/index.d.ts",
|
|
30
|
+
"default": "./dist/testing/src/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"bin": {
|
|
35
|
+
"flow": "dist/testing/src/cli.js",
|
|
36
|
+
"chinwag": "dist/testing/src/chinwag.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"examples/cafe/agent",
|
|
41
|
+
"examples/cafe/src",
|
|
42
|
+
"examples/cafe/tests",
|
|
43
|
+
"examples/http-agent",
|
|
44
|
+
"flow-messaging/README.md",
|
|
45
|
+
"coding-agents",
|
|
46
|
+
"spec",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && node -e \"for (const f of ['page.html', 'person.html']) fs.copyFileSync('simulators/src/' + f, 'dist/simulators/src/' + f)\"",
|
|
52
|
+
"prepack": "npm run build",
|
|
53
|
+
"test": "node --test --test-timeout=30000 \"test/**/*.test.ts\"",
|
|
54
|
+
"test:changed": "node scripts/test-changed.ts",
|
|
55
|
+
"new": "node scripts/new-agent.ts",
|
|
56
|
+
"typecheck": "tsc --noEmit"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"openai": "^7.32.0",
|
|
60
|
+
"yaml": "^2.9.1",
|
|
61
|
+
"zod": "^4.6.5"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^26.6.5",
|
|
65
|
+
"typescript": "^7.0.2"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
info:
|
|
3
|
+
title: Flow Framework agent endpoint
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license:
|
|
6
|
+
name: MIT
|
|
7
|
+
identifier: MIT
|
|
8
|
+
description: |
|
|
9
|
+
The HTTP endpoint an agent on any framework, in any language, exposes so Flow Framework's test suite
|
|
10
|
+
can talk to it: `flow test --agent-url <url>`. Implement this one operation and the test
|
|
11
|
+
cloud's simulated people can chat with your agent; `flow check-endpoint <url>` checks an
|
|
12
|
+
implementation against this file without a cloud, a key or a model call.
|
|
13
|
+
|
|
14
|
+
How a run uses it:
|
|
15
|
+
- Each of the person's messages is one POST to the URL you give, from the machine running
|
|
16
|
+
`flow test` (so `http://localhost` works and the agent never has to be reachable from the
|
|
17
|
+
internet). The path is yours; `/flow` is the examples'.
|
|
18
|
+
- A conversation's messages are sent one at a time, in order: the next is POSTed once the last
|
|
19
|
+
one's answer is in. Different conversations may be in flight at once.
|
|
20
|
+
- Keep each conversation's history by `conversationId`, as you would per phone number. The
|
|
21
|
+
request carries only the new message.
|
|
22
|
+
- Ignore request fields you do not know: later versions may add optional ones. The request
|
|
23
|
+
schema is exact for this version; do not reject requests that have more.
|
|
24
|
+
- Answer with status 200 and `{ "replies": [...] }`, the replies to send in order, in the same
|
|
25
|
+
HTTP response. `{ "replies": [] }` sends nothing (not 204, not an empty body). Answering
|
|
26
|
+
later, through a callback, is not supported.
|
|
27
|
+
- Your agent's own tools run on its side, for real: the suite's `tests/tools/*.yaml` simulators
|
|
28
|
+
are not used, and outcome checks see no tool calls, so write what must be true as `judge`
|
|
29
|
+
items.
|
|
30
|
+
- The person's reactions to the agent's messages are not sent in this version.
|
|
31
|
+
|
|
32
|
+
What fails a message's turn (the run records why and goes on to the next message):
|
|
33
|
+
- any status other than 200 (redirects are followed; the final status counts), with the
|
|
34
|
+
first 200 characters of the body in the reason;
|
|
35
|
+
- a 200 whose body is not JSON, or has no `replies` array;
|
|
36
|
+
- no answer within the timeout: 60 seconds by default, `--agent-timeout <s>` to change it;
|
|
37
|
+
- the URL cannot be reached.
|
|
38
|
+
|
|
39
|
+
A reply that is valid JSON but does not fit the run's platform (buttons on SMS, text over its
|
|
40
|
+
maxLength, an unknown key) is not sent; the run says why and the other replies still go.
|
|
41
|
+
`x-chinwag-platforms` below lists what each platform shows.
|
|
42
|
+
servers:
|
|
43
|
+
- url: http://localhost:3000
|
|
44
|
+
description: Wherever your agent listens; `flow test --agent-url` takes the full URL.
|
|
45
|
+
security:
|
|
46
|
+
- {}
|
|
47
|
+
- bearer: []
|
|
48
|
+
paths:
|
|
49
|
+
/flow:
|
|
50
|
+
post:
|
|
51
|
+
operationId: receiveMessage
|
|
52
|
+
summary: One message from the person; answer with the replies to send.
|
|
53
|
+
description: |
|
|
54
|
+
Sent with `content-type: application/json`. When FLOW_FRAMEWORK_AGENT_TOKEN is set where
|
|
55
|
+
`flow test` runs, every request carries `authorization: Bearer <token>`: check it, and
|
|
56
|
+
answer 401 to anything else.
|
|
57
|
+
requestBody:
|
|
58
|
+
required: true
|
|
59
|
+
content:
|
|
60
|
+
application/json:
|
|
61
|
+
schema:
|
|
62
|
+
$ref: ./request.schema.json
|
|
63
|
+
examples:
|
|
64
|
+
text:
|
|
65
|
+
summary: A typed message
|
|
66
|
+
value:
|
|
67
|
+
conversationId: whatsapp:ses_8f2a.opening-hours.whatsapp.1
|
|
68
|
+
messageId: m1
|
|
69
|
+
channel: whatsapp
|
|
70
|
+
message: { text: "hi, what time are you open today?" }
|
|
71
|
+
photo:
|
|
72
|
+
summary: A photo with no caption
|
|
73
|
+
value:
|
|
74
|
+
conversationId: whatsapp:ses_8f2a.receipt.whatsapp.1
|
|
75
|
+
messageId: m2
|
|
76
|
+
channel: whatsapp
|
|
77
|
+
message:
|
|
78
|
+
text: ""
|
|
79
|
+
attachments: [{ kind: image, mime: image/png, name: receipt.png, data: iVBORw0KGgo= }]
|
|
80
|
+
tap:
|
|
81
|
+
summary: A tapped button, replying to the agent's message
|
|
82
|
+
value:
|
|
83
|
+
conversationId: whatsapp:ses_8f2a.order.whatsapp.1
|
|
84
|
+
messageId: m3
|
|
85
|
+
channel: whatsapp
|
|
86
|
+
message:
|
|
87
|
+
text: Oat
|
|
88
|
+
choice: { title: Oat }
|
|
89
|
+
replyTo: { text: "Which milk?", fromAgent: true }
|
|
90
|
+
responses:
|
|
91
|
+
"200":
|
|
92
|
+
description: The replies to send, in order; `[]` is no reply.
|
|
93
|
+
content:
|
|
94
|
+
application/json:
|
|
95
|
+
schema:
|
|
96
|
+
$ref: ./response.schema.json
|
|
97
|
+
examples:
|
|
98
|
+
answer:
|
|
99
|
+
value:
|
|
100
|
+
replies:
|
|
101
|
+
- { type: reaction, emoji: "👍", messageId: m1 }
|
|
102
|
+
- { type: message, text: "We're open 8am to 6pm today.", buttons: [Order ahead] }
|
|
103
|
+
nothing:
|
|
104
|
+
value: { replies: [] }
|
|
105
|
+
default:
|
|
106
|
+
description: Any other status fails this message's turn; the body's first 200 characters are the reason shown in the run.
|
|
107
|
+
components:
|
|
108
|
+
securitySchemes:
|
|
109
|
+
bearer:
|
|
110
|
+
type: http
|
|
111
|
+
scheme: bearer
|
|
112
|
+
description: The value of FLOW_FRAMEWORK_AGENT_TOKEN, when it is set where `flow test` runs.
|
|
113
|
+
# What each platform a run plays shows, as ChannelInfo in src/types.ts: a reply is checked against
|
|
114
|
+
# its run's platform. A key that is absent means the platform does not have that feature. maxLength
|
|
115
|
+
# absent is 4,000. Kept in step with simulators/src/platforms.ts by test/check-endpoint.test.ts.
|
|
116
|
+
x-chinwag-platforms:
|
|
117
|
+
sms:
|
|
118
|
+
reactions: false
|
|
119
|
+
status: false
|
|
120
|
+
formatting: plain
|
|
121
|
+
maxLength: 1530
|
|
122
|
+
whatsapp:
|
|
123
|
+
reactions: true
|
|
124
|
+
status: false
|
|
125
|
+
formatting: whatsapp
|
|
126
|
+
maxLength: 4096
|
|
127
|
+
replies: true
|
|
128
|
+
buttons: { max: 3, labelMax: 20 }
|
|
129
|
+
lists: { maxRows: 10, titleMax: 24, buttonMax: 20 }
|
|
130
|
+
linkButton: { labelMax: 20 }
|
|
131
|
+
images: { captionMax: 1024 }
|
|
132
|
+
readReceipts: true
|
|
133
|
+
imessage:
|
|
134
|
+
reactions: true
|
|
135
|
+
status: false
|
|
136
|
+
formatting: plain
|
|
137
|
+
images: { captionMax: 4000 }
|
|
138
|
+
telegram:
|
|
139
|
+
reactions: true
|
|
140
|
+
status: false
|
|
141
|
+
formatting: markdown
|
|
142
|
+
images: { captionMax: 1024 }
|
|
143
|
+
web:
|
|
144
|
+
reactions: true
|
|
145
|
+
status: true
|
|
146
|
+
formatting: markdown
|
|
147
|
+
linkButton: { labelMax: 20 }
|
|
148
|
+
images: { captionMax: 4000 }
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
info:
|
|
3
|
+
title: Flow Framework nudges over HTTP
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license:
|
|
6
|
+
name: MIT
|
|
7
|
+
identifier: MIT
|
|
8
|
+
description: |
|
|
9
|
+
The endpoint `nudgeHandler(agent, { token, base })` serves in the agent's own server, so a
|
|
10
|
+
backend that is a separate service can send nudges (messages the business starts) without a
|
|
11
|
+
route of its own. Paths are under `base`, `/nudges` by default; anything outside it gets 404.
|
|
12
|
+
|
|
13
|
+
- A POST answers as soon as the nudge is recorded, before it is sent or the agent's turn for it
|
|
14
|
+
runs: usually `queued`. Read how it ended with the GET.
|
|
15
|
+
- What is decided as it is recorded is in the POST's answer, still a 202: it is the nudge's
|
|
16
|
+
state, not an HTTP error. `refused` with `unknown_person` (they never wrote on that channel;
|
|
17
|
+
nothing is recorded, so `id` is "" and there is nothing to GET) or `outside_window` (past
|
|
18
|
+
WhatsApp's 24-hour window, for text and prompt nudges); `dropped` (they texted STOP);
|
|
19
|
+
`scheduled` (it has `at`).
|
|
20
|
+
- Every request needs `Authorization: Bearer <token>`. Wrong tokens are limited per address:
|
|
21
|
+
after 10 in 15 minutes the address gets 429 for the rest of that window, the right token too.
|
|
22
|
+
- Answers are JSON with `cache-control: no-store`.
|
|
23
|
+
servers:
|
|
24
|
+
- url: http://localhost:3000
|
|
25
|
+
description: Wherever the agent's server listens.
|
|
26
|
+
security:
|
|
27
|
+
- bearer: []
|
|
28
|
+
paths:
|
|
29
|
+
/nudges:
|
|
30
|
+
post:
|
|
31
|
+
operationId: recordNudge
|
|
32
|
+
summary: Record a nudge; answers at once with its id and state.
|
|
33
|
+
requestBody:
|
|
34
|
+
required: true
|
|
35
|
+
description: At most 16,384 bytes of JSON. Unknown fields are refused.
|
|
36
|
+
content:
|
|
37
|
+
application/json:
|
|
38
|
+
schema:
|
|
39
|
+
$ref: "#/components/schemas/NudgeRequest"
|
|
40
|
+
examples:
|
|
41
|
+
prompt:
|
|
42
|
+
summary: The agent writes it, in the context of the chat
|
|
43
|
+
value:
|
|
44
|
+
to: { channel: whatsapp, address: "+15550100100" }
|
|
45
|
+
prompt: Their order is ready for pickup.
|
|
46
|
+
idempotency_key: order-1042-ready
|
|
47
|
+
text:
|
|
48
|
+
summary: Sent as written, later
|
|
49
|
+
value:
|
|
50
|
+
to: { channel: sms, address: "+15550100100" }
|
|
51
|
+
text: Your table is booked for 7pm.
|
|
52
|
+
at: 1792335600000
|
|
53
|
+
responses:
|
|
54
|
+
"202":
|
|
55
|
+
description: Recorded (or, for an `idempotency_key` already used, the first nudge with that key).
|
|
56
|
+
content:
|
|
57
|
+
application/json:
|
|
58
|
+
schema:
|
|
59
|
+
$ref: "#/components/schemas/NudgeState"
|
|
60
|
+
examples:
|
|
61
|
+
queued:
|
|
62
|
+
value: { id: n_3f9c2a1b, status: queued }
|
|
63
|
+
unknownPerson:
|
|
64
|
+
value: { id: "", status: refused, reason: unknown_person }
|
|
65
|
+
"400":
|
|
66
|
+
description: The body is not JSON, too large, or does not fit; the channel is not one of the agent's; a declared template's values do not fit it.
|
|
67
|
+
content:
|
|
68
|
+
application/json:
|
|
69
|
+
schema:
|
|
70
|
+
$ref: "#/components/schemas/Error"
|
|
71
|
+
"401":
|
|
72
|
+
description: No token, or a wrong one.
|
|
73
|
+
content:
|
|
74
|
+
application/json:
|
|
75
|
+
schema:
|
|
76
|
+
$ref: "#/components/schemas/Error"
|
|
77
|
+
"429":
|
|
78
|
+
description: Too many wrong tokens from this address.
|
|
79
|
+
content:
|
|
80
|
+
application/json:
|
|
81
|
+
schema:
|
|
82
|
+
$ref: "#/components/schemas/Error"
|
|
83
|
+
/nudges/{id}:
|
|
84
|
+
get:
|
|
85
|
+
operationId: nudgeState
|
|
86
|
+
summary: A nudge's state now.
|
|
87
|
+
parameters:
|
|
88
|
+
- name: id
|
|
89
|
+
in: path
|
|
90
|
+
required: true
|
|
91
|
+
schema: { type: string }
|
|
92
|
+
responses:
|
|
93
|
+
"200":
|
|
94
|
+
description: Its state now.
|
|
95
|
+
content:
|
|
96
|
+
application/json:
|
|
97
|
+
schema:
|
|
98
|
+
$ref: "#/components/schemas/NudgeState"
|
|
99
|
+
example: { id: n_3f9c2a1b, status: sent }
|
|
100
|
+
"401":
|
|
101
|
+
description: No token, or a wrong one.
|
|
102
|
+
"404":
|
|
103
|
+
description: The agent never recorded a nudge with this id.
|
|
104
|
+
"429":
|
|
105
|
+
description: Too many wrong tokens from this address.
|
|
106
|
+
components:
|
|
107
|
+
securitySchemes:
|
|
108
|
+
bearer:
|
|
109
|
+
type: http
|
|
110
|
+
scheme: bearer
|
|
111
|
+
description: The token given to `nudgeHandler`, at least 32 characters.
|
|
112
|
+
schemas:
|
|
113
|
+
NudgeRequest:
|
|
114
|
+
type: object
|
|
115
|
+
additionalProperties: false
|
|
116
|
+
required: [to]
|
|
117
|
+
description: Exactly one of `text`, `prompt` or `template`.
|
|
118
|
+
properties:
|
|
119
|
+
to:
|
|
120
|
+
type: object
|
|
121
|
+
additionalProperties: false
|
|
122
|
+
required: [channel, address]
|
|
123
|
+
description: The person, by a channel identity they have written from.
|
|
124
|
+
properties:
|
|
125
|
+
channel: { type: string, description: One of the agent's channels, by name. }
|
|
126
|
+
address: { type: string, description: "Their address on it: a phone number, or the custom UI's user id." }
|
|
127
|
+
text: { type: string, minLength: 1, description: Sent as written, with no model call. }
|
|
128
|
+
prompt: { type: string, minLength: 1, description: The agent gets a turn with this note and writes the message itself, or sends nothing. }
|
|
129
|
+
template:
|
|
130
|
+
type: object
|
|
131
|
+
additionalProperties: false
|
|
132
|
+
required: [name]
|
|
133
|
+
description: A WhatsApp template the owner set up with Meta, as `Template` in src/types.ts. Cannot be combined with `at` yet.
|
|
134
|
+
properties:
|
|
135
|
+
name: { type: string }
|
|
136
|
+
language: { type: string }
|
|
137
|
+
header:
|
|
138
|
+
oneOf:
|
|
139
|
+
- { type: object, additionalProperties: false, required: [kind, text], properties: { kind: { const: text }, text: { type: string } } }
|
|
140
|
+
- { type: object, additionalProperties: false, required: [kind], properties: { kind: { enum: [image, video, document] }, url: { type: string }, fileId: { type: string } } }
|
|
141
|
+
body: { type: array, items: { type: string } }
|
|
142
|
+
buttons:
|
|
143
|
+
type: array
|
|
144
|
+
items: { type: object, additionalProperties: false, required: [index, value], properties: { index: { type: integer, minimum: 0 }, value: { type: string } } }
|
|
145
|
+
variables: { type: object, additionalProperties: { type: string } }
|
|
146
|
+
at: { type: integer, minimum: 0, description: Send it later, at this time, in milliseconds since the epoch. }
|
|
147
|
+
idempotency_key: { type: string, minLength: 1, maxLength: 200, description: The same key twice sends once; the second call gets the first nudge's id and state. }
|
|
148
|
+
respect_quiet_hours: { type: boolean, description: "Wait for the end of the person's quiet hours (default true); false sends it now." }
|
|
149
|
+
expires_at: { type: integer, minimum: 0, description: "Milliseconds since the epoch: held for quiet hours past this, it is dropped as expired (default 24 hours on)." }
|
|
150
|
+
NudgeState:
|
|
151
|
+
type: object
|
|
152
|
+
required: [id, status]
|
|
153
|
+
properties:
|
|
154
|
+
id: { type: string }
|
|
155
|
+
status:
|
|
156
|
+
enum: [queued, sent, silent, dropped, refused, failed, scheduled, held, expired]
|
|
157
|
+
description: |
|
|
158
|
+
queued (waiting for its send or turn); sent; silent (the agent chose to send nothing);
|
|
159
|
+
dropped (a no-contact rule covers it, or their STOP); refused (outside_window,
|
|
160
|
+
unknown_person); failed (it could not be sent, or the model failed); scheduled; held (their
|
|
161
|
+
quiet hours, until they end or the person writes); expired (held past expires_at).
|
|
162
|
+
reason: { type: string, description: Why, for dropped, refused and failed. }
|
|
163
|
+
Error:
|
|
164
|
+
type: object
|
|
165
|
+
required: [error]
|
|
166
|
+
properties:
|
|
167
|
+
error: { type: string }
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/flow-engineer/flow/blob/main/spec/request.schema.json",
|
|
4
|
+
"title": "AgentRequest",
|
|
5
|
+
"description": "One of the person's messages, as `flow test --agent-url` POSTs it to the agent's endpoint. This schema lists exactly what this version sends (additionalProperties is false so it stays exact); later versions may add optional fields, so an endpoint should ignore fields it does not know rather than validate requests strictly against this file.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"conversationId",
|
|
9
|
+
"messageId",
|
|
10
|
+
"channel",
|
|
11
|
+
"message"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"conversationId": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"description": "The conversation, as a phone number or chat would be: keep the history by it. In a test run it is \"<platform>:<session>.<scenario>.<platform>.<run>\", such as \"whatsapp:ses_8f2a.opening-hours.whatsapp.1\"."
|
|
19
|
+
},
|
|
20
|
+
"messageId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "The id this message has in the conversation (m1, m2, ...): what a reply's `replyTo`, a reaction's `messageId` or a read's `messageIds` names."
|
|
23
|
+
},
|
|
24
|
+
"channel": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1,
|
|
27
|
+
"description": "The platform the run plays (whatsapp, sms, imessage, telegram, web), or the name given with --channel."
|
|
28
|
+
},
|
|
29
|
+
"message": {
|
|
30
|
+
"$ref": "#/$defs/message"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"$defs": {
|
|
34
|
+
"message": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": [
|
|
37
|
+
"text"
|
|
38
|
+
],
|
|
39
|
+
"additionalProperties": false,
|
|
40
|
+
"properties": {
|
|
41
|
+
"text": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "What the person wrote; \"\" when they only sent a file (then it is the file's caption, if any)."
|
|
44
|
+
},
|
|
45
|
+
"replyTo": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"description": "The message they replied to (theirs or the agent's), quoted.",
|
|
48
|
+
"required": [
|
|
49
|
+
"text",
|
|
50
|
+
"fromAgent"
|
|
51
|
+
],
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"properties": {
|
|
54
|
+
"text": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"fromAgent": {
|
|
58
|
+
"type": "boolean",
|
|
59
|
+
"description": "true when the quoted message is the agent's."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"choice": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"description": "They tapped a button or picked a list row rather than typing.",
|
|
66
|
+
"required": [
|
|
67
|
+
"title"
|
|
68
|
+
],
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"properties": {
|
|
71
|
+
"id": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "The list row's id, when it has one."
|
|
74
|
+
},
|
|
75
|
+
"title": {
|
|
76
|
+
"type": "string"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"attachments": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"description": "Photos, voice notes and documents sent with the message.",
|
|
83
|
+
"items": {
|
|
84
|
+
"$ref": "#/$defs/attachment"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"attachment": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"required": [
|
|
92
|
+
"kind",
|
|
93
|
+
"mime",
|
|
94
|
+
"data"
|
|
95
|
+
],
|
|
96
|
+
"additionalProperties": false,
|
|
97
|
+
"properties": {
|
|
98
|
+
"kind": {
|
|
99
|
+
"enum": [
|
|
100
|
+
"image",
|
|
101
|
+
"audio",
|
|
102
|
+
"document"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"mime": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "Such as image/jpeg, audio/ogg, application/pdf."
|
|
108
|
+
},
|
|
109
|
+
"name": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "The file name, when the platform gives one (documents)."
|
|
112
|
+
},
|
|
113
|
+
"data": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"contentEncoding": "base64",
|
|
116
|
+
"description": "The file's bytes, base64 (standard alphabet, with padding)."
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|