@loopbacktools/protocol 0.0.0-stage → 0.4.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 +14 -2
- package/dist/openapi/openapi.1.json +1903 -0
- package/dist/src/briefing.d.ts +80 -0
- package/dist/src/briefing.js +219 -0
- package/dist/src/generated/document.gen.d.ts +3256 -0
- package/dist/src/generated/document.gen.js +25 -0
- package/dist/src/generated/requests.gen.d.ts +480 -0
- package/dist/src/generated/requests.gen.js +1 -0
- package/dist/src/generated/schemas.gen.d.ts +5 -0
- package/dist/src/generated/schemas.gen.js +6 -0
- package/dist/src/generated/schemas.r1.gen.d.ts +5433 -0
- package/dist/src/generated/schemas.r1.gen.js +58 -0
- package/dist/src/generated/vocabularies.gen.d.ts +11 -0
- package/dist/src/generated/vocabularies.gen.js +13 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/schema.d.ts +9 -0
- package/dist/src/schema.js +8 -0
- package/dist/src/unknown.d.ts +94 -0
- package/dist/src/unknown.js +59 -0
- package/dist/src/versions.d.ts +11 -0
- package/dist/src/versions.js +11 -0
- package/openapi/openapi.1.json +1903 -0
- package/package.json +61 -3
- package/spec/main.tsp +660 -0
package/spec/main.tsp
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import "@typespec/http";
|
|
2
|
+
import "@typespec/openapi";
|
|
3
|
+
import "@typespec/versioning";
|
|
4
|
+
|
|
5
|
+
using Http;
|
|
6
|
+
using Versioning;
|
|
7
|
+
|
|
8
|
+
// The Loopback contract. Everything the HTTP API accepts or returns, the shapes MCP tools take and
|
|
9
|
+
// give back, and the frames the event socket sends are declared here, and only here.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loopback puts what an agent needs from a person in front of them and hands the reply back.
|
|
13
|
+
*
|
|
14
|
+
* An agent asks over MCP; a person answers through this API, usually from the web inbox. Live updates
|
|
15
|
+
* arrive over the WebSocket at `/api/v1/events`, which sends a `SnapshotEvent` and then `AskEvent`s.
|
|
16
|
+
*
|
|
17
|
+
* Every response names the code that answered: `Loopback-Revision`, the revision of this contract the
|
|
18
|
+
* server writes, and `Loopback-Build`, when the deploy it is from was made. A client that names the
|
|
19
|
+
* revision it writes in `Loopback-Revision` on its requests learns when the server is newer than
|
|
20
|
+
* itself, and is refused with `409` and the code `reload` when it is the newer, as after a rollback.
|
|
21
|
+
*/
|
|
22
|
+
@versioned(Revisions)
|
|
23
|
+
@service(#{ title: "Loopback" })
|
|
24
|
+
@server("/api/v1", "Loopback API")
|
|
25
|
+
namespace Loopback;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The contract's revisions, oldest first. Change the shape of any message and declare it here as a
|
|
29
|
+
* new revision, marking what changed with `@added`, `@removed`, `@renamedFrom` or `@typeChangedFrom`:
|
|
30
|
+
* every part of a deployment names the revision it writes, and newer code upgrades older messages.
|
|
31
|
+
* Never edit a revision that has shipped; `revisions/` holds each one frozen, and a test fails when
|
|
32
|
+
* the contract no longer emits it unchanged. See docs/design/versioned-messages.md.
|
|
33
|
+
*/
|
|
34
|
+
enum Revisions {
|
|
35
|
+
r1: "1",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- vocabulary -----------------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What an agent is asking for: an answer to a question, a verdict on something it produced, or the
|
|
42
|
+
* person's word on each point of something it explained.
|
|
43
|
+
*/
|
|
44
|
+
enum AskKind {
|
|
45
|
+
question,
|
|
46
|
+
review,
|
|
47
|
+
briefing,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Where an ask stands. While it is open, it is either the person's move or the agent's. */
|
|
51
|
+
enum AskStatus {
|
|
52
|
+
awaiting_human,
|
|
53
|
+
awaiting_agent,
|
|
54
|
+
closed,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Image formats an agent may upload for a review. */
|
|
58
|
+
union ImageType {
|
|
59
|
+
png: "image/png",
|
|
60
|
+
jpeg: "image/jpeg",
|
|
61
|
+
webp: "image/webp",
|
|
62
|
+
gif: "image/gif",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- content --------------------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/** Prose, copy or a plan, as GitHub-flavoured markdown. */
|
|
68
|
+
model MarkdownBlock {
|
|
69
|
+
type: "markdown";
|
|
70
|
+
@minLength(1) text: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Source code, shown highlighted. */
|
|
74
|
+
model CodeBlock {
|
|
75
|
+
type: "code";
|
|
76
|
+
@minLength(1) code: string;
|
|
77
|
+
|
|
78
|
+
/** A language name the highlighter knows, such as `ts` or `python`. */
|
|
79
|
+
language?: string;
|
|
80
|
+
|
|
81
|
+
filename?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A unified diff, as `git diff` prints it. */
|
|
85
|
+
model DiffBlock {
|
|
86
|
+
type: "diff";
|
|
87
|
+
@minLength(1) diff: string;
|
|
88
|
+
filename?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** An image uploaded beforehand through an upload ticket. */
|
|
92
|
+
model ImageBlock {
|
|
93
|
+
type: "image";
|
|
94
|
+
@minLength(1) uploadId: string;
|
|
95
|
+
caption?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One piece of what is shown to the person. */
|
|
99
|
+
union Block {
|
|
100
|
+
markdown: MarkdownBlock,
|
|
101
|
+
code: CodeBlock,
|
|
102
|
+
diff: DiffBlock,
|
|
103
|
+
image: ImageBlock,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A choice offered with a question. */
|
|
107
|
+
model Choice {
|
|
108
|
+
@minLength(1) @maxLength(80) label: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** One point of a briefing: a line the person sees folded, and what opens beneath it. */
|
|
113
|
+
model BriefingPoint {
|
|
114
|
+
/** The point in one line, shown while it is folded. */
|
|
115
|
+
@minLength(1) @maxLength(200) title: string;
|
|
116
|
+
|
|
117
|
+
/** The point explained, as markdown, shown when the person opens it. */
|
|
118
|
+
detail?: string;
|
|
119
|
+
|
|
120
|
+
/** Anything to look at with it: code, a diff or an image. */
|
|
121
|
+
content?: Block[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A list of points under an optional heading. */
|
|
125
|
+
model BriefingSection {
|
|
126
|
+
title?: string;
|
|
127
|
+
@minItems(1) points: BriefingPoint[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- what agents send -----------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A question for the person, with optional choices. They may always answer in their own words. All
|
|
134
|
+
* of it together may be at most 256 KiB.
|
|
135
|
+
*/
|
|
136
|
+
model QuestionInput {
|
|
137
|
+
/** The question, as markdown. */
|
|
138
|
+
@minLength(1) question: string;
|
|
139
|
+
|
|
140
|
+
/** Between two and six choices, offered as buttons. */
|
|
141
|
+
@minItems(2) @maxItems(6) options?: Choice[];
|
|
142
|
+
|
|
143
|
+
/** Whether the person may pick more than one choice. */
|
|
144
|
+
multiple?: boolean;
|
|
145
|
+
|
|
146
|
+
/** Background the person needs to answer, as markdown. */
|
|
147
|
+
context?: string;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The project you are working in: the name of the folder at the root of its repository (the
|
|
151
|
+
* last part of `git rev-parse --show-toplevel`), or of the working directory outside one. The
|
|
152
|
+
* person works on many projects at once from one inbox; this is how they know who is asking.
|
|
153
|
+
*/
|
|
154
|
+
@minLength(1) project: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Work put in front of the person for approval or feedback. All of it together may be at most
|
|
159
|
+
* 256 KiB: show the part that matters, or split it into several reviews.
|
|
160
|
+
*/
|
|
161
|
+
model ReviewInput {
|
|
162
|
+
@minLength(1) @maxLength(200) title: string;
|
|
163
|
+
|
|
164
|
+
/** What is being reviewed, in order. */
|
|
165
|
+
@minItems(1) content: Block[];
|
|
166
|
+
|
|
167
|
+
/** What changed, or what to look at. */
|
|
168
|
+
summary?: string;
|
|
169
|
+
|
|
170
|
+
/** The id of the review this one revises, after changes were requested. */
|
|
171
|
+
revises?: string;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The project you are working in: the name of the folder at the root of its repository (the
|
|
175
|
+
* last part of `git rev-parse --show-toplevel`), or of the working directory outside one. The
|
|
176
|
+
* person works on many projects at once from one inbox; this is how they know who is asking.
|
|
177
|
+
*/
|
|
178
|
+
@minLength(1) project: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Something explained as points the person can reply on one at a time: findings, options, steps, a
|
|
183
|
+
* list of changes. They may write on any point, or mark it resolved, and you answer on each point
|
|
184
|
+
* they wrote on until every point is resolved. Points are numbered for you, straight through every
|
|
185
|
+
* section: at most fifty. All of it together may be at most 256 KiB.
|
|
186
|
+
*/
|
|
187
|
+
model BriefingInput {
|
|
188
|
+
@minLength(1) @maxLength(200) title: string;
|
|
189
|
+
|
|
190
|
+
/** What this explains, and why, as markdown. */
|
|
191
|
+
summary?: string;
|
|
192
|
+
|
|
193
|
+
/** The points, in lists under optional headings. */
|
|
194
|
+
@minItems(1) sections: BriefingSection[];
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The project you are working in: the name of the folder at the root of its repository (the
|
|
198
|
+
* last part of `git rev-parse --show-toplevel`), or of the working directory outside one. The
|
|
199
|
+
* person works on many projects at once from one inbox; this is how they know who is asking.
|
|
200
|
+
*/
|
|
201
|
+
@minLength(1) project: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Your answer on one point of a briefing. */
|
|
205
|
+
model PointAnswer {
|
|
206
|
+
/** The point's id, such as `3` or `3.1`. */
|
|
207
|
+
@minLength(1) point: string;
|
|
208
|
+
|
|
209
|
+
/** The answer, as markdown. */
|
|
210
|
+
@minLength(1) answer: string;
|
|
211
|
+
|
|
212
|
+
/** Anything new to look at alongside the answer. */
|
|
213
|
+
content?: Block[];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Sub-points this point breaks into, numbered beneath it (`3.1`, `3.2`, …), each of which the
|
|
217
|
+
* person can reply on in turn. Points go at most three levels deep.
|
|
218
|
+
*/
|
|
219
|
+
points?: BriefingPoint[];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Your answers on a briefing, one per point: every point the person wrote on without resolving it,
|
|
224
|
+
* and any other open point you have more to say on. All of it together may be at most 256 KiB.
|
|
225
|
+
*/
|
|
226
|
+
model BriefingAnswerInput {
|
|
227
|
+
@minLength(1) askId: string;
|
|
228
|
+
@minItems(1) answers: PointAnswer[];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The agent's answer to a question the person asked back, with its content at most 256 KiB. */
|
|
232
|
+
model FollowUpInput {
|
|
233
|
+
@minLength(1) askId: string;
|
|
234
|
+
|
|
235
|
+
/** The answer, as markdown. */
|
|
236
|
+
@minLength(1) answer: string;
|
|
237
|
+
|
|
238
|
+
/** Anything new to look at alongside the answer. */
|
|
239
|
+
content?: Block[];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** A request for somewhere to upload an image. */
|
|
243
|
+
model UploadRequest {
|
|
244
|
+
contentType: ImageType;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Where to upload an image, once, before it expires. */
|
|
248
|
+
model UploadTicket {
|
|
249
|
+
uploadId: string;
|
|
250
|
+
|
|
251
|
+
/** Send the image here with `PUT` and its `Content-Type`. No credentials needed. */
|
|
252
|
+
uploadUrl: string;
|
|
253
|
+
|
|
254
|
+
expiresAt: utcDateTime;
|
|
255
|
+
maxBytes: int32;
|
|
256
|
+
|
|
257
|
+
/** A ready-to-run command that performs the upload; replace `FILE` with the image's path. */
|
|
258
|
+
command: string;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** A request for the address of the person's inbox. */
|
|
262
|
+
model InboxRequest {
|
|
263
|
+
/** An ask to open directly. Leave it out for the inbox itself. */
|
|
264
|
+
askId?: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Where the person reads and answers everything agents ask. */
|
|
268
|
+
model InboxLink {
|
|
269
|
+
url: string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- what people send -----------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
/** An answer to a question: the choices picked, words of their own, or both. */
|
|
275
|
+
model Answered {
|
|
276
|
+
outcome: "answered";
|
|
277
|
+
selected: string[];
|
|
278
|
+
text?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** A review approved, with an optional note. */
|
|
282
|
+
model Approved {
|
|
283
|
+
outcome: "approved";
|
|
284
|
+
note?: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** A review sent back with feedback. */
|
|
288
|
+
model ChangesRequested {
|
|
289
|
+
outcome: "changes_requested";
|
|
290
|
+
@minLength(1) feedback: string;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** A question asked back before deciding. The ask stays open until the agent answers. */
|
|
294
|
+
model FollowUp {
|
|
295
|
+
outcome: "follow_up";
|
|
296
|
+
@minLength(1) question: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Declined without an answer. */
|
|
300
|
+
model Dismissed {
|
|
301
|
+
outcome: "dismissed";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The person on one point of a briefing: words, resolved, or both. */
|
|
305
|
+
model PointReply {
|
|
306
|
+
@minLength(1) point: string;
|
|
307
|
+
|
|
308
|
+
/** What they said: a question, a push-back or a comment. */
|
|
309
|
+
text?: string;
|
|
310
|
+
|
|
311
|
+
/** Whether they are done with the point, and everything beneath it. */
|
|
312
|
+
resolved?: boolean;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Replies on the points of a briefing. The agent answers every point written on and left unresolved;
|
|
317
|
+
* the briefing closes once every point is resolved.
|
|
318
|
+
*/
|
|
319
|
+
model Responded {
|
|
320
|
+
outcome: "responded";
|
|
321
|
+
@minItems(1) points: PointReply[];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* What a person replies. A `follow_up` hands the move to the agent; so does a `responded` that
|
|
326
|
+
* leaves any point unresolved. Every other reply closes the ask.
|
|
327
|
+
*/
|
|
328
|
+
union Reply {
|
|
329
|
+
answered: Answered,
|
|
330
|
+
approved: Approved,
|
|
331
|
+
changes_requested: ChangesRequested,
|
|
332
|
+
follow_up: FollowUp,
|
|
333
|
+
dismissed: Dismissed,
|
|
334
|
+
responded: Responded,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// --- asks -----------------------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
/** A question as it is shown to the person. */
|
|
340
|
+
model QuestionPrompt {
|
|
341
|
+
question: string;
|
|
342
|
+
options: Choice[];
|
|
343
|
+
multiple: boolean;
|
|
344
|
+
context?: string;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** A review as it is shown to the person. */
|
|
348
|
+
model ReviewPrompt {
|
|
349
|
+
title: string;
|
|
350
|
+
content: Block[];
|
|
351
|
+
summary?: string;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** A point of a briefing as it is shown, with the id replies name it by. */
|
|
355
|
+
model Point {
|
|
356
|
+
/** `3` for the third point of the briefing, `3.1` for the first beneath it. */
|
|
357
|
+
id: string;
|
|
358
|
+
|
|
359
|
+
...BriefingPoint;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** A list of a briefing's points as it is shown. */
|
|
363
|
+
model BriefingPromptSection {
|
|
364
|
+
title?: string;
|
|
365
|
+
points: Point[];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** A briefing as it is shown to the person, before any sub-points the agent adds while answering. */
|
|
369
|
+
model BriefingPrompt {
|
|
370
|
+
title: string;
|
|
371
|
+
summary?: string;
|
|
372
|
+
sections: BriefingPromptSection[];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Someone a deployment's gate recognised: a person, or the agent they authorised. */
|
|
376
|
+
model Actor {
|
|
377
|
+
/** Stable within the deployment, as its gate names them. */
|
|
378
|
+
id: string;
|
|
379
|
+
|
|
380
|
+
/** How to show them, when the gate knows. */
|
|
381
|
+
name?: string;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** A reply the person gave. */
|
|
385
|
+
model HumanEntry {
|
|
386
|
+
author: "human";
|
|
387
|
+
reply: Reply;
|
|
388
|
+
|
|
389
|
+
/** Who replied, when the deployment knows. */
|
|
390
|
+
by?: Actor;
|
|
391
|
+
|
|
392
|
+
at: utcDateTime;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** An answer the agent gave: to a question asked back, or on one point of a briefing. */
|
|
396
|
+
model AgentEntry {
|
|
397
|
+
author: "agent";
|
|
398
|
+
answer: string;
|
|
399
|
+
content?: Block[];
|
|
400
|
+
|
|
401
|
+
/** The briefing point this answers. */
|
|
402
|
+
point?: string;
|
|
403
|
+
|
|
404
|
+
/** Sub-points the answer broke the point into, with their ids. */
|
|
405
|
+
points?: Point[];
|
|
406
|
+
|
|
407
|
+
at: utcDateTime;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** One turn in an ask's thread after it was opened. */
|
|
411
|
+
union Entry {
|
|
412
|
+
human: HumanEntry,
|
|
413
|
+
agent: AgentEntry,
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** What every ask carries, whatever its kind. */
|
|
417
|
+
model AskCommon {
|
|
418
|
+
id: string;
|
|
419
|
+
status: AskStatus;
|
|
420
|
+
|
|
421
|
+
/** Whether an agent is following this ask right now: waiting on its call, or polling it as a background task. */
|
|
422
|
+
waiting: boolean;
|
|
423
|
+
|
|
424
|
+
project?: string;
|
|
425
|
+
|
|
426
|
+
/** The MCP client that asked, such as `claude-code 2.1.0`. */
|
|
427
|
+
client?: string;
|
|
428
|
+
|
|
429
|
+
/** Whose agent asked: the person its credential belongs to, when the deployment knows. */
|
|
430
|
+
raisedBy?: Actor;
|
|
431
|
+
|
|
432
|
+
/** Every turn after the ask was opened, oldest first. */
|
|
433
|
+
thread: Entry[];
|
|
434
|
+
|
|
435
|
+
createdAt: utcDateTime;
|
|
436
|
+
updatedAt: utcDateTime;
|
|
437
|
+
closedAt?: utcDateTime;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** A question an agent asked. */
|
|
441
|
+
model QuestionAsk {
|
|
442
|
+
kind: "question";
|
|
443
|
+
prompt: QuestionPrompt;
|
|
444
|
+
...AskCommon;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Work an agent put up for review. */
|
|
448
|
+
model ReviewAsk {
|
|
449
|
+
kind: "review";
|
|
450
|
+
prompt: ReviewPrompt;
|
|
451
|
+
|
|
452
|
+
/** The id of the review this one revises. */
|
|
453
|
+
revises?: string;
|
|
454
|
+
|
|
455
|
+
...AskCommon;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Points an agent explained, for the person to reply on one at a time. */
|
|
459
|
+
model BriefingAsk {
|
|
460
|
+
kind: "briefing";
|
|
461
|
+
prompt: BriefingPrompt;
|
|
462
|
+
...AskCommon;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Something an agent needs from a person: a short thread that ends with a reply. */
|
|
466
|
+
union Ask {
|
|
467
|
+
question: QuestionAsk,
|
|
468
|
+
review: ReviewAsk,
|
|
469
|
+
briefing: BriefingAsk,
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** What a blocking MCP tool returns: the reply that ended its wait. */
|
|
473
|
+
model ToolReply {
|
|
474
|
+
askId: string;
|
|
475
|
+
reply: Reply;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Open asks, newest first, and the most recently closed. */
|
|
479
|
+
model Snapshot {
|
|
480
|
+
open: Ask[];
|
|
481
|
+
recent: Ask[];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** A page of closed asks, newest first. */
|
|
485
|
+
model AskPage {
|
|
486
|
+
items: Ask[];
|
|
487
|
+
|
|
488
|
+
/** Pass as `before` for the next page. Absent on the last page. */
|
|
489
|
+
nextBefore?: utcDateTime;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* A one-time pass onto the event socket, for a client that cannot send headers there, as a browser
|
|
494
|
+
* cannot. Offer it as the subprotocol `loopback.ticket.<ticket>` alongside `loopback.v1`. It works
|
|
495
|
+
* once, within a minute, so a copy that turns up in a log is of no use to anyone.
|
|
496
|
+
*/
|
|
497
|
+
model EventTicket {
|
|
498
|
+
ticket: string;
|
|
499
|
+
expiresAt: utcDateTime;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Who the caller is. */
|
|
503
|
+
model Whoami {
|
|
504
|
+
space: string;
|
|
505
|
+
actor: Actor;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** A stored upload. */
|
|
509
|
+
model UploadReceipt {
|
|
510
|
+
uploadId: string;
|
|
511
|
+
contentType: ImageType;
|
|
512
|
+
size: int32;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// --- event socket frames --------------------------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
/** The first frame on the event socket, and the state to start from. */
|
|
518
|
+
model SnapshotEvent {
|
|
519
|
+
type: "snapshot";
|
|
520
|
+
snapshot: Snapshot;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** An ask was opened, or changed: a new turn, a close, or an agent starting or stopping following it. */
|
|
524
|
+
model AskEvent {
|
|
525
|
+
type: "ask.opened" | "ask.updated";
|
|
526
|
+
ask: Ask;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** A frame on the event socket. */
|
|
530
|
+
union ServerEvent {
|
|
531
|
+
snapshot: SnapshotEvent,
|
|
532
|
+
ask: AskEvent,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// --- errors ---------------------------------------------------------------------------------------
|
|
536
|
+
|
|
537
|
+
/** Why a request was refused. */
|
|
538
|
+
model Problem {
|
|
539
|
+
/** A stable, machine-readable reason, such as `ask.closed`. */
|
|
540
|
+
code: string;
|
|
541
|
+
|
|
542
|
+
message: string;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
@error
|
|
546
|
+
model Unauthorized {
|
|
547
|
+
@statusCode statusCode: 401;
|
|
548
|
+
@body body: Problem;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
@error
|
|
552
|
+
model NotFound {
|
|
553
|
+
@statusCode statusCode: 404;
|
|
554
|
+
@body body: Problem;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
@error
|
|
558
|
+
model Conflict {
|
|
559
|
+
@statusCode statusCode: 409;
|
|
560
|
+
@body body: Problem;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@error
|
|
564
|
+
model Gone {
|
|
565
|
+
@statusCode statusCode: 410;
|
|
566
|
+
@body body: Problem;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
@error
|
|
570
|
+
model TooLarge {
|
|
571
|
+
@statusCode statusCode: 413;
|
|
572
|
+
@body body: Problem;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
@error
|
|
576
|
+
model Unprocessable {
|
|
577
|
+
@statusCode statusCode: 422;
|
|
578
|
+
@body body: Problem;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// --- operations -----------------------------------------------------------------------------------
|
|
582
|
+
|
|
583
|
+
@route("/whoami")
|
|
584
|
+
@useAuth(BearerAuth)
|
|
585
|
+
interface Session {
|
|
586
|
+
/** The space the credentials open. */
|
|
587
|
+
@get whoami(): Whoami | Unauthorized;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
@route("/events/tickets")
|
|
591
|
+
@useAuth(BearerAuth)
|
|
592
|
+
interface EventTickets {
|
|
593
|
+
/** A one-time pass onto the event socket, `/api/v1/events`. */
|
|
594
|
+
@post issue(): {
|
|
595
|
+
@statusCode statusCode: 201;
|
|
596
|
+
@body body: EventTicket;
|
|
597
|
+
} | Unauthorized;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@route("/snapshot")
|
|
601
|
+
@useAuth(BearerAuth)
|
|
602
|
+
interface Snapshots {
|
|
603
|
+
/** Every open ask and the fifty most recently closed: what an inbox starts from. */
|
|
604
|
+
@get read(): Snapshot | Unauthorized;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
@route("/asks")
|
|
608
|
+
@useAuth(BearerAuth)
|
|
609
|
+
interface Asks {
|
|
610
|
+
/** Closed asks, newest first, for paging back through history. */
|
|
611
|
+
@get list(
|
|
612
|
+
/** Only asks closed before this moment. */
|
|
613
|
+
@query before?: utcDateTime,
|
|
614
|
+
|
|
615
|
+
@query @minValue(1) @maxValue(100) limit?: int32,
|
|
616
|
+
): AskPage | Unauthorized;
|
|
617
|
+
|
|
618
|
+
@get read(@path id: string): Ask | NotFound | Unauthorized;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Reply to an ask. Refused with 409 when the ask is closed or it is the agent's turn, except that
|
|
622
|
+
* a briefing may be dismissed during the agent's turn. Refused with 422 when the reply does not
|
|
623
|
+
* fit: `answered` is for questions and needs a choice or text, with choices among those offered
|
|
624
|
+
* and only one unless several are allowed; `approved` and `changes_requested` are for reviews;
|
|
625
|
+
* `responded` is for briefings, names each of their points at most once with words or resolved,
|
|
626
|
+
* and either resolves every point or leaves the agent a point to answer. A briefing takes no
|
|
627
|
+
* `follow_up`.
|
|
628
|
+
*/
|
|
629
|
+
@route("/{id}/reply")
|
|
630
|
+
@post
|
|
631
|
+
reply(@path id: string, @body reply: Reply): Ask | NotFound | Conflict | Unprocessable | Unauthorized;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
@route("/files")
|
|
635
|
+
@useAuth(BearerAuth)
|
|
636
|
+
interface Files {
|
|
637
|
+
/** An uploaded image. */
|
|
638
|
+
@get read(@path uploadId: string): {
|
|
639
|
+
@header contentType: ImageType;
|
|
640
|
+
@body body: bytes;
|
|
641
|
+
} | NotFound | Unauthorized;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
@route("/uploads")
|
|
645
|
+
@useAuth(NoAuth)
|
|
646
|
+
interface Uploads {
|
|
647
|
+
/**
|
|
648
|
+
* Store an image against an upload ticket. The URL is the credential: it works once, for
|
|
649
|
+
* fifteen minutes, for the declared content type, up to the ticket's `maxBytes`.
|
|
650
|
+
*/
|
|
651
|
+
@put store(
|
|
652
|
+
@path spaceId: string,
|
|
653
|
+
@path token: string,
|
|
654
|
+
@header contentType: string,
|
|
655
|
+
@body body: bytes,
|
|
656
|
+
): {
|
|
657
|
+
@statusCode statusCode: 201;
|
|
658
|
+
@body body: UploadReceipt;
|
|
659
|
+
} | NotFound | Gone | TooLarge | Unprocessable;
|
|
660
|
+
}
|