@nuanu-ai/agentify 0.5.0 → 0.5.2

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/worker.d.ts CHANGED
@@ -1,76 +1,37 @@
1
1
  /**
2
- * The one loop that carries everything the merchant's process receives.
3
- *
4
- * The transport is HTTP long polling against the gateway (ADR-0004): the
5
- * worker asks for the next batch, the gateway holds the request until
6
- * something arrives or its own window closes, and the answer is a batch of
7
- * envelopes or an empty one. An empty batch is the ordinary answer to a quiet
8
- * window and not a failure. There is one loop and not three, because the
9
- * stream carries three kinds on one connection and the portal promises the
10
- * merchant one subscription.
11
- *
12
- * How each kind is answered is the part worth reading before changing
13
- * anything, because the three are answered in three different places and none
14
- * of them is an acknowledgement of the envelope itself.
15
- *
16
- * An order is given to the merchant's handler, and whatever the handler
17
- * returns is posted to the order's answer route — in every mode, without the
18
- * merchant asking. In the synchronous mode that return is the whole of the
19
- * merchant's answer; in the asynchronous one it is usually an acceptance, and
20
- * the goods follow later through the explicit `deliver` call, which is the
21
- * merchant's to make and not this loop's.
22
- *
23
- * A price question is given to the price handler and the answer goes to the
24
- * quote route against the `price_id` the question carried.
25
- *
26
- * An event is handed over and nothing is sent back. An event notifies, it does
27
- * not ask for work.
28
- *
29
- * What happens when a handler throws is the design decision the rest follows
30
- * from: nothing is sent. An order nobody answered comes back on its own,
31
- * because the gateway's own order machine is watching for that answer and
32
- * decides there should be another attempt — that is the machine's meaning of
33
- * "not delivered". A retry written on this side would be a second machine with
34
- * a second opinion about how many times a merchant's handler may run, and the
35
- * two would disagree the first time one of them restarted. The same holds for a
36
- * handler that answers with something the contract refuses, and for an envelope
37
- * that arrives with no handler registered for its kind: the merchant is told
38
- * through the problem channel, and an order or a price question comes back on
39
- * its own.
40
- *
41
- * An event is the exception and it is worth knowing about. It carries no
42
- * acknowledgement, so nothing here can ask for one again and nothing on the
43
- * other side is waiting for a reply to notice that it went unanswered. The
44
- * contract makes no promise that one is ever sent a second time, and whether a
45
- * particular gateway sends one anyway is that gateway's business. A handler
46
- * that throws on an event has lost what it carried.
47
- *
48
- * The transport failures are the one thing this loop does retry, because there
49
- * is nothing else to do with them: a poll that did not reach the gateway
50
- * delivered no envelope to anybody. That retry is the backoff in
51
- * `backoff.ts` and nothing more.
2
+ * The one loop that carries everything the merchant's process receives, over
3
+ * HTTP long polling: the worker asks for the next batch, and the gateway holds
4
+ * the request until something arrives or its own window closes. An empty batch
5
+ * is the ordinary answer to a quiet window.
6
+ *
7
+ * An order goes to the merchant's handler, and what the handler returns is
8
+ * posted to the order's answer route in every mode. A price question goes to
9
+ * the price handler, and its answer to the quote route against the question's
10
+ * `price_id`. An event is handed over and nothing is sent back.
11
+ *
12
+ * When a handler throws, nothing is sent and nothing is retried here: an
13
+ * unanswered order comes back because the gateway decides on another attempt,
14
+ * and a second retry policy on this side would disagree with it. The same
15
+ * holds for an answer the contract refuses and for a kind with no handler; the
16
+ * merchant is told through the problem reporter. An event is not sent again,
17
+ * so a handler that throws on one has lost what it carried. The only retry
18
+ * here is of a poll that failed, with the backoff in `backoff.ts`.
52
19
  */
53
20
  import { type HandlerAnswer, type Order, type OrderEvent, type QuoteRequest, type QuoteResponse } from "@nuanu-ai/agentify-contracts";
54
21
  import { type Gateway } from "./transport.js";
55
22
  /**
56
- * What this loop calls with one paid order off the stream.
57
- *
58
- * It is not what a merchant writes. The order a merchant's handler receives
59
- * carries the calls that close it, and putting those on it is `client.ts`'s
60
- * job: this loop reads the envelope and knows nothing about a gateway address
61
- * or an API key beyond the one it polls with. So the client registers a
62
- * function of this shape which wraps the merchant's own.
23
+ * What this loop calls with one paid order off the stream. `client.ts`
24
+ * registers a function of this shape that attaches the order's calls and then
25
+ * calls the merchant's own handler.
63
26
  */
64
- export type OrderDispatch = (order: Order) => HandlerAnswer | Promise<HandlerAnswer>;
27
+ type OrderDispatch = (order: Order) => HandlerAnswer | Promise<HandlerAnswer>;
65
28
  /** The same, for "how much is this and is it there". */
66
- export type QuoteDispatch = (question: QuoteRequest) => QuoteResponse | Promise<QuoteResponse>;
29
+ type QuoteDispatch = (question: QuoteRequest) => QuoteResponse | Promise<QuoteResponse>;
67
30
  /**
68
- * How this delivery of a message is named, for the one kind that needs it.
69
- *
70
- * `id` names this message and `sent_at` names when it went out. What the pair is
71
- * not is a dependable way to recognise a repeat: a gateway sending an order
72
- * again builds a fresh envelope around it under a fresh `id`, and what holds
73
- * still across such a repeat is the order's own identifier in the payload.
31
+ * The name of one delivery of a message: its envelope's `id` and when it was
32
+ * sent. It is no way to recognise a repeat; an order sent again comes in a
33
+ * fresh envelope with a fresh `id`, and what holds still is the order's own
34
+ * identifier.
74
35
  */
75
36
  export interface Delivered {
76
37
  readonly id: string;
@@ -79,26 +40,16 @@ export interface Delivered {
79
40
  /**
80
41
  * What the merchant's code does with something that happened to an order.
81
42
  *
82
- * The second argument is this delivery's own name, and it is the reason this
83
- * signature is not just the event. An order is answered against its own
84
- * identifier and a price question against its `price_id`; an event is answered
85
- * against nothing at all, so this pair is the only thing naming the message
86
- * itself rather than the order it is about, which is what a log line or a
87
- * question to us afterwards needs.
88
- *
89
- * It is not there to be deduplicated against. Nothing acknowledges an event, so
90
- * nothing asks for one again, and the contract makes no promise that one is ever
91
- * sent twice — which means the risk an event handler is written against is a
92
- * message that never arrives rather than one that arrives twice.
43
+ * The second argument names this delivery of the message, for a log line or a
44
+ * question to Agentify afterwards, since an event is answered against nothing.
45
+ * An event is sent at most once, so the risk to write against is an event that
46
+ * never arrives, not one that arrives twice; reading open orders with
47
+ * `orders.list({ open: true })` finds a refund owed whose event went missing.
93
48
  */
94
49
  export type EventHandler = (event: OrderEvent, delivered: Delivered) => void | Promise<void>;
95
50
  /**
96
- * The things the worker has to tell the merchant about, under the names their
97
- * code can branch on.
98
- *
99
- * They are one vocabulary rather than several channels because every one of
100
- * them has the same shape of consequence — something did not get through — and
101
- * a merchant who wants to count them wants to count them together.
51
+ * The things the worker tells the merchant about, under names their code can
52
+ * branch on, through the reporter registered with `on('problem')`.
102
53
  */
103
54
  export declare const WORKER_PROBLEM_KINDS: Readonly<{
104
55
  /** The gateway speaks another dialect of the contract. The loop stops. */
@@ -123,8 +74,11 @@ export declare const WORKER_PROBLEM_KINDS: Readonly<{
123
74
  readonly WORKER_FAILED: "worker_failed";
124
75
  /** A delivery carried the one field name this contract removes in silence. */
125
76
  readonly DELIVERY_FIELD_DROPPED: "delivery_field_dropped";
77
+ /** An acceptance was given an argument, which it does not carry. The order is taken on. */
78
+ readonly ACCEPTANCE_ARGUMENT_IGNORED: "acceptance_argument_ignored";
126
79
  }>;
127
80
  export type WorkerProblemKind = (typeof WORKER_PROBLEM_KINDS)[keyof typeof WORKER_PROBLEM_KINDS];
81
+ /** One thing the worker has to tell the merchant about. */
128
82
  export interface WorkerProblem {
129
83
  readonly kind: WorkerProblemKind;
130
84
  /** What happened, in one sentence a person can act on. */
@@ -136,64 +90,42 @@ export interface WorkerProblem {
136
90
  /** The order or the price question it is about, where it is about one. */
137
91
  readonly subject?: string;
138
92
  }
93
+ /**
94
+ * Where the worker reports problems, registered with `on('problem')`. It is
95
+ * called synchronously; whatever it throws or rejects with is swallowed, so a
96
+ * failing reporter cannot stop the worker. Without one, problems go to
97
+ * `console.error`.
98
+ */
139
99
  export type ProblemReporter = (problem: WorkerProblem) => void;
140
100
  /**
141
- * Calls a merchant's reporter and lets nothing it does escape.
142
- *
143
- * Reporting must not be able to end the loop that reports. A merchant's
144
- * reporter is their code — a logger over a stream that closed during shutdown,
145
- * a client that was never configured — and an exception out of it would
146
- * otherwise unwind the worker and, from the worker's own last-resort handler,
147
- * escape as an unhandled rejection and take the host process down with it.
148
- * There is nowhere left to report a reporter that throws, so it is swallowed.
149
- *
150
- * Both ways of throwing, and the second is the one that hid. A reporter
151
- * declared `void` may still be an `async` function — TypeScript allows it, and
152
- * a merchant shipping problems to an HTTP logger will write one — and such a
153
- * reporter does not throw, it returns a promise that rejects a turn later, past
154
- * this `catch`, with nothing holding it. That is the unhandled rejection this
155
- * comment claims not to allow, arriving by the door the `try` does not cover.
156
- * So what comes back is settled too, and its failure dropped in the same
157
- * silence and for the same reason.
158
- *
159
- * It is at the top of the file rather than inside the loop because the loop is
160
- * not the only thing that reports: a merchant calling `deliver` on an order
161
- * directly has a reporter too, and the same reasoning covers it exactly.
101
+ * Calls a merchant's reporter and lets nothing it does escape. A reporter that
102
+ * throws, or that is `async` and rejects a turn later, would otherwise end the
103
+ * loop or surface as an unhandled rejection and take the host process down;
104
+ * there is nowhere left to report it, so it is swallowed. A merchant calling
105
+ * `deliver` directly reports through this too.
162
106
  */
163
107
  export declare const reportSafely: (reporter: ProblemReporter, problem: WorkerProblem) => void;
164
108
  /**
165
- * The one loss in this contract that nobody is told about, said out loud here
166
- * because this is the place it would reach the agent.
167
- *
168
- * A field named `__proto__` is removed while a delivery is parsed, before any
169
- * check runs — it is neither carried nor refused, and the merchant's handler
170
- * has no way to know that what it returned is not what went out. The contracts
171
- * package names the delivery as the place the loss lands in what the agent is
172
- * handed — `DeliverySchema` in its `handler.ts` — and a delivery is written
173
- * here, so this file says it too.
174
- *
175
- * No card can declare such a field, so nothing legitimate reaches here; a
176
- * merchant only meets it by delivering a name nobody asked for.
177
- *
178
- * It is one function because there are two roads to the same gateway and the
179
- * loss is the same on both: the worker answering with what a handler returned,
180
- * and a merchant calling `deliver` on an order himself. Written twice, one of
181
- * them would drift or, as it did, never be written at all.
109
+ * The warning for a delivery carrying a field named `__proto__`, which is
110
+ * removed while the delivery is parsed: the agent never receives it, and
111
+ * without this the merchant would not know. No card can declare such a field.
112
+ * Both roads to the gateway use it, the worker answering for a handler and a
113
+ * merchant calling `deliver` directly.
182
114
  */
183
115
  export declare const droppedFieldWarning: (orderId: string, delivered: Readonly<Record<string, unknown>> | undefined) => WorkerProblem | null;
184
116
  /**
185
- * Which registration each kind on the stream is answered by, in the word a
186
- * merchant passes to `on`.
187
- *
188
- * This table is the vocabulary, and everything that has to know the stream's
189
- * kinds is derived from it rather than written out again: what a merchant may
190
- * register, what the registry holds, what `start` counts as an answer, and the
191
- * sentence that names a registration a merchant is missing. It is written over
192
- * the contract's own kinds, so a fourth kind added to the envelope — the
193
- * confirmation request, whose shape the contract does not carry yet — stops
194
- * this file compiling until it has a word of its own here. The `switch` in
195
- * `dispatch` below is the second half of that guard, and it is the one that
196
- * demands the behaviour.
117
+ * The warning reported when `accepted` or `accept` is given an argument.
118
+ * TypeScript refuses one, but JavaScript code can pass it, and nothing passed
119
+ * reaches the gateway. The order is accepted all the same, because refusing
120
+ * would strand an order its buyer has paid for.
121
+ */
122
+ export declare const acceptanceArgumentWarning: (orderId: string) => WorkerProblem;
123
+ /**
124
+ * Which registration answers each kind on the stream, by the word a merchant
125
+ * passes to `on`. Everything that has to know the stream's kinds derives from
126
+ * this table, and the `satisfies` clause stops the file compiling when the
127
+ * contract gains a kind this table lacks; the `switch` in `dispatch` demands
128
+ * the behaviour for it.
197
129
  */
198
130
  export declare const REGISTERED_AS: Readonly<{
199
131
  readonly order: "order";
@@ -201,35 +133,22 @@ export declare const REGISTERED_AS: Readonly<{
201
133
  readonly order_event: "event";
202
134
  }>;
203
135
  /**
204
- * The words a merchant registers something on the stream under.
205
- *
206
- * Indexed by the table's own keys and not by the contract's kinds, which reads
207
- * the same today and behaves very differently on the day a fourth kind is
208
- * added. Indexing by a key the table does not have yet collapses this type, and
209
- * with it `Handlers[Kind]` and the contextual type of every `agentify.on(...)`
210
- * call — so the first compile after the contract grows reports sixty errors, of
211
- * which fifty-five are implicit-any at call sites that are not the problem, and
212
- * the three places that do need a hand are buried among them. Indexed this way
213
- * that same compile names two places, both real.
214
- *
215
- * Completeness is not what was lost: the `satisfies` clause on the table above
216
- * is what demands a word per contract kind, and it still fires.
136
+ * The words a merchant registers something on the stream under. Indexed by
137
+ * the table's own keys rather than the contract's kinds, so that when the
138
+ * contract grows a kind, the compile errors name the two places that need
139
+ * work instead of collapsing every `on(...)` call site into implicit `any`.
217
140
  */
218
141
  export type StreamHandlerKind = (typeof REGISTERED_AS)[keyof typeof REGISTERED_AS];
219
- /** What answers each of them, in the shape this loop calls. */
220
- export interface StreamDispatch {
142
+ /** What answers each kind, in the shape this loop calls. */
143
+ interface StreamDispatch {
221
144
  order: OrderDispatch;
222
145
  quote: QuoteDispatch;
223
146
  event: EventHandler;
224
147
  }
225
148
  /**
226
- * The handlers the loop dispatches to, read at the moment an envelope is
227
- * handled rather than captured when the loop starts.
228
- *
229
- * The registry belongs to the client and outlives any one loop. A merchant
230
- * registers what their process answers, starts, stops, and starts again on the
231
- * same handlers — and a handler registered while a loop is already running is
232
- * in place for the next envelope rather than for the next loop.
149
+ * The handlers the loop dispatches to, read when an envelope is handled rather
150
+ * than when the loop starts, so a handler registered while the loop runs is in
151
+ * place for the next envelope. It belongs to the client and outlives any loop.
233
152
  */
234
153
  export type HandlerRegistry = {
235
154
  [Kind in StreamHandlerKind]?: StreamDispatch[Kind] | undefined;
@@ -237,12 +156,8 @@ export type HandlerRegistry = {
237
156
  problem: ProblemReporter;
238
157
  };
239
158
  /**
240
- * Time, as the loop sees it.
241
- *
242
- * A seam rather than a call to `setTimeout` in the middle of the loop, so that
243
- * a test can assert which delays were asked for without waiting through them.
244
- * It is not part of the published surface: a merchant has no business
245
- * replacing the clock their worker runs on.
159
+ * Time, as the loop sees it: a seam so a test can check which delays were
160
+ * asked for without waiting. Not part of the published surface.
246
161
  */
247
162
  export interface WorkerClock {
248
163
  now(): number;
@@ -251,100 +166,43 @@ export interface WorkerClock {
251
166
  }
252
167
  export declare const systemClock: WorkerClock;
253
168
  /**
254
- * The shortest a quiet poll may take before the next one.
255
- *
256
- * The gateway holds a poll open for its own window, so in ordinary work this
257
- * costs nothing: by the time an empty batch comes back, the floor has long
258
- * passed and the next poll goes out at once — which is what keeps a waiting
259
- * agent's price question free of any polling lag (ADR-0004 §4). It exists for
260
- * the gateway that is not holding requests at all, misconfigured or half
261
- * broken, where a loop with no floor turns into as many requests a second as
262
- * the network allows. One second is the whole of the claim: slow enough that
263
- * nothing is hammered, fast enough that it is invisible against a window
264
- * measured in tens of seconds.
169
+ * The shortest a quiet poll may take before the next one. Against a gateway
170
+ * that holds polls open it costs nothing, since the window is far longer; it
171
+ * exists so a gateway that answers at once does not get as many requests a
172
+ * second as the network allows.
265
173
  */
266
174
  export declare const QUIET_POLL_FLOOR_MS = 1000;
267
175
  /**
268
- * How long the worker asks the gateway to hold a poll open, per ADR-0004 §1.
269
- *
270
- * It is a request and not an expectation. The gateway holds it for its own
271
- * window at most, and the contract says as much, so a shorter answer is
272
- * ordinary rather than a fault. Asking is what makes the number visible on the
273
- * wire, where a gateway that wanted to answer sooner can see it.
176
+ * How long the worker asks the gateway to hold a poll open. The gateway holds
177
+ * it for its own window at most, so a shorter answer is ordinary.
274
178
  */
275
179
  export declare const POLL_WAIT_SECONDS = 25;
276
180
  /**
277
- * How long the worker will wait for a poll before giving up on it.
278
- *
279
- * A window asked for is not a promise kept: a connection dropped silently by
280
- * something in the middle — a load balancer that recycled it, a firewall that
281
- * forgot it — leaves this side waiting on an answer that will never come, with
282
- * nothing reported and nothing retried. Without a deadline of our own the only
283
- * thing that eventually breaks that is whatever timeout the runtime happens to
284
- * default to, which is a number nobody here chose and which is measured in
285
- * minutes against a window measured in seconds.
286
- *
287
- * Twice the window, so a gateway that holds the full window and answers late is
288
- * never cut off, and a connection that has gone quiet costs one window of
289
- * silence rather than several minutes of it.
290
- *
291
- * This worker never comes near it against the gateway in this repository, and
292
- * that is not luck: every poll names the window it wants, and that gateway
293
- * holds a poll for the smaller of the window asked for and its own, so the wait
294
- * is bounded by the twenty-five seconds above however the gateway is
295
- * configured. Naming the window is this side's guarantee. The deadline is for
296
- * the other case — a connection that died without saying so.
297
- *
298
- * A second belt exists on the gateway's side, and it is not for this worker.
299
- * The window is optional on the wire, and a poll that names none is held for
300
- * the gateway's whole configured window instead; that window is refused above a
301
- * ceiling set under this number (`WORKER_POLL_WAIT_CEILING_MS` in
302
- * `apps/gateway/src/config.ts`), which is where this figure is copied. The
303
- * gateway depends on the contracts and the core machine rather than on the SDK,
304
- * so it cannot read it, and nothing checks that the copy still matches:
305
- * changing what is written here means changing it there in the same breath.
306
- *
307
- * It is also the reason two poll failures can be a minute and a half apart,
308
- * which matters to anybody counting them from outside. A gateway that refuses
309
- * connections fails a poll at once and the backoff is the whole gap; one that
310
- * accepts and never answers fails only when this deadline runs out, so the gap
311
- * is this plus the backoff. `DOUBT_MS` in packages/slice/src/subscription.ts is
312
- * that sum, and it is named here because raising this number silently narrows
313
- * it — and a window too narrow reads the quiet between two failures as a
314
- * recovery.
181
+ * How long the worker waits for a poll before giving up on it: twice the
182
+ * window asked for, so a gateway that answers late is never cut off, while a
183
+ * connection dropped silently by something in the middle costs one window
184
+ * rather than the runtime's default of minutes.
185
+ *
186
+ * Two copies elsewhere must move with it. `WORKER_POLL_WAIT_CEILING_MS` in
187
+ * `apps/gateway/src/config.ts` is set under this number, and nothing checks
188
+ * that it still is. `DOUBT_MS` in packages/slice/src/subscription.ts is this
189
+ * plus the backoff ceiling, since a gateway that accepts and never answers
190
+ * fails a poll only when this runs out; raise this alone and a healthcheck
191
+ * reads the quiet between two failures as a recovery.
315
192
  */
316
193
  export declare const POLL_DEADLINE_MS: number;
317
194
  /** What `startWorker` hands its caller, which is the client and never a merchant. */
318
195
  export interface RunningWorker {
319
196
  /**
320
- * Stops the loop and waits for it to finish. Safe to call twice.
321
- *
322
- * Two things are abandoned rather than finished, and they are not abandoned
323
- * on the same terms. A poll parked at the gateway is dropped, and the
324
- * gateway stops it and keeps whatever arrives for the next poll; only
325
- * something taken in the instant before the drop is lost, and that is an
326
- * order, which comes again, or an event, which is sent once — the list of
327
- * open orders is what finds a refund owed whose event went missing.
328
- * An answer already on its way — a delivery the handler produced a moment
329
- * ago — is dropped too, and there the honest thing to say is that nobody on
330
- * this side knows whether it arrived first: the order may already be closed
331
- * by it, or may come back. The merchant is told which of their orders that
332
- * happened to, because on their side the work happened.
333
- *
334
- * Stopping does not drain. The contract has a word for a poll that asks for
335
- * whatever is queued right now and comes straight back, and it is not what a
336
- * worker shutting down wants: draining pulls more work into a process that
337
- * is going away.
197
+ * Stops the loop and waits for it to finish. Safe to call twice. A parked
198
+ * poll is dropped, and so is an answer already on its way, which the
199
+ * merchant is told about by order; the queue is not drained.
338
200
  */
339
201
  stop(): Promise<void>;
340
202
  /** Whether the loop is still going. False once it has stopped for any reason. */
341
203
  running(): boolean;
342
204
  }
343
205
  export declare const startWorker: (gateway: Gateway, handlers: HandlerRegistry, clock?: WorkerClock,
344
- /**
345
- * The deadline is a parameter for the same reason the clock is: a test
346
- * cannot wait out fifty seconds to find out whether the worker gives up on a
347
- * poll, and a deadline nothing exercises is a deadline nobody has seen work.
348
- * It is not part of anything a merchant passes.
349
- */
206
+ /** A parameter so a test can see the deadline work without waiting it out. */
350
207
  deadlineMs?: number) => RunningWorker;
208
+ export {};