@nuanu-ai/agentify 0.2.4

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.
@@ -0,0 +1,377 @@
1
+ /**
2
+ * The client a merchant's engineer holds: a catalog, an order desk, and one
3
+ * place to say what this process answers.
4
+ *
5
+ * The shape of it is the portal's, not this file's. Every method here exists
6
+ * because a page of the documentation tells a merchant to call it, with the
7
+ * arguments that page passes and answering with what that page reads — and the
8
+ * examples on those pages are compiled against these types by a test, so the
9
+ * code a merchant copies out of the documentation is code that builds.
10
+ *
11
+ * Three kinds travel one stream, so there is one way to register a handler for
12
+ * one of them — `on(kind, handler)` — and one lifecycle, `start` and `stop`,
13
+ * for the loop that carries all three. What a handler receives carries the
14
+ * calls that answer it, and that is the part worth reading before changing
15
+ * anything here, because two things that look alike are deliberately not.
16
+ *
17
+ * `order.delivered(...)`, `order.refused(...)` and `order.accepted(...)`
18
+ * build the answer and send nothing; the handler still returns it. A bot
19
+ * library lets a handler reply and forget, and forgetting there costs a
20
+ * message; forgetting here is an order nobody answered — redelivered, a
21
+ * delivery attempt spent, and in the asynchronous mode a debt to a buyer at
22
+ * the end of it. Returning the answer is what makes forgetting impossible and
23
+ * answering twice impossible.
24
+ *
25
+ * `order.deliver(...)`, `order.refuse(...)` and `order.accept(...)` are the
26
+ * other thing: they send. They are the asynchronous merchant's closure verbs,
27
+ * made hours later and from anywhere in their code, and they exist on the
28
+ * order rather than beside it so that a merchant never holds an identifier of
29
+ * ours. The same calls are on every order this client hands back — off the
30
+ * stream, off `orders.get`, off `orders.list`, and off `orders.forId` for the
31
+ * process that kept nothing but the identifier — because a process that
32
+ * restarted has no object left to hold, and that is exactly the moment
33
+ * juggling identifiers would come back.
34
+ *
35
+ * One rule runs through the whole surface and it is worth stating once. What
36
+ * the contract gives a call a failure branch for is returned, never thrown,
37
+ * and every one of those answers is the same envelope: `ok` says which it was,
38
+ * a card that was not published comes back with its findings under `problems`,
39
+ * and an order call that did not go through comes back as an error with a flag
40
+ * saying whether repeating it could help. A merchant's integration code is
41
+ * expected to read those and branch on them.
42
+ *
43
+ * What is thrown is what has no branch to be read on, and it is two kinds. A
44
+ * client built wrong — no key, an address that is not one, a handler
45
+ * registered twice — is a `TypeError` at the line that is wrong, before
46
+ * anything leaves the process. A call that went out and produced no answer of
47
+ * the kind its route promises, where the route has nowhere to put one, is a
48
+ * `AgentifyError` carrying the same code vocabulary the returned failures use.
49
+ *
50
+ * The one place that rule needed a decision rather than a reading is a
51
+ * `deliver` or a `refuse` whose answer never arrived, or arrived in words this
52
+ * package cannot read. The contract has a branch for it — an error with a flag
53
+ * saying whether calling again could change the outcome is exactly the shape
54
+ * of "the connection dropped, call again" — so it comes back through that
55
+ * branch, under a code this package produces and the gateway never sends. The
56
+ * three codes and what separates them are written down beside them below.
57
+ */
58
+ import type { Acceptance, CardInput, Delivery, HandlerAnswer, Money, Order, OrderAcceptResponse, OrderCallResponse, OrderWithStatus, PublishResult, QuoteRequest, QuoteResponse, Refusal } from "@nuanu-ai/agentify-contracts";
59
+ import { type EventHandler, type ProblemReporter, type StreamHandlerKind } from "./worker.js";
60
+ export interface ClientOptions {
61
+ /**
62
+ * The merchant's API key.
63
+ *
64
+ * It admits `undefined` because the thing a merchant actually writes is
65
+ * `process.env.AGENTIFY_API_KEY`, which is `string | undefined` in every
66
+ * strict TypeScript project there is. Refusing that at the type level would
67
+ * teach every merchant to silence it with a non-null assertion, which turns
68
+ * an unset variable into an authorisation failure much later. It is refused
69
+ * here instead, at the line that is wrong, with a sentence that names what
70
+ * is missing.
71
+ */
72
+ readonly apiKey: string | undefined;
73
+ /**
74
+ * Where the gateway is: `https://test.agentify.ad` while the merchant
75
+ * is building, `https://app.agentify.ad` once they are live.
76
+ *
77
+ * Optional, and there is deliberately no default behind it. Both addresses
78
+ * are settled and this package still refuses to pick one, because which of
79
+ * them a process talks to is not a convenience — it is the difference
80
+ * between a sale and a rehearsal (ADR-0020). A default would be right for
81
+ * whoever wrote it and wrong for somebody's production the first time a
82
+ * deployment inherited it, and that is a mistake nothing downstream can
83
+ * catch: every answer from the other site parses perfectly.
84
+ *
85
+ * Leaving it out builds a client, exactly as the quickstart's first step
86
+ * says it does, and every call that would have to reach the gateway fails
87
+ * with a sentence naming what is missing and both addresses to choose
88
+ * between. That is where the failure belongs: the documentation tells the
89
+ * merchant that the first call is what checks whether they can reach us.
90
+ * Giving a wrong address, on the other hand, is refused here and now — a
91
+ * value that is not an address at all cannot become one later.
92
+ */
93
+ readonly baseUrl?: string | undefined;
94
+ }
95
+ /**
96
+ * The calls every order carries, wherever the merchant got it from.
97
+ *
98
+ * The first three build an answer and send nothing: they are what a handler
99
+ * returns, and the return is what this SDK posts. The last three send, and
100
+ * they are the calls an asynchronous merchant makes later — after the supplier
101
+ * answered, from another part of their code, from another process life.
102
+ *
103
+ * There are three of each because the two halves are not the same set of
104
+ * choices seen twice. A synchronous handler chooses between delivering and
105
+ * refusing on the spot; an asynchronous one accepts and then, eventually,
106
+ * delivers or refuses. Accepting from outside a handler exists for the
107
+ * merchant who decides later, in another part of their code.
108
+ */
109
+ export interface OrderCalls {
110
+ /** The goods, as the handler's answer. Building one sends nothing. */
111
+ delivered(delivery: Delivery): HandlerAnswer;
112
+ /** A final "this cannot be delivered", as the handler's answer. */
113
+ refused(refusal: Refusal): HandlerAnswer;
114
+ /**
115
+ * Taking the order on without delivering yet, as the handler's answer. An
116
+ * empty acceptance is a complete answer; the expected time is said where it
117
+ * is known.
118
+ */
119
+ accepted(acceptance?: Acceptance): HandlerAnswer;
120
+ /**
121
+ * The goods for an order taken on earlier. Idempotent by the order's own
122
+ * identifier, so repeating it after a dropped connection does no work twice.
123
+ */
124
+ deliver(delivery: Delivery): Promise<OrderCallResponse>;
125
+ /** A final "this cannot be delivered" for an order already taken on. */
126
+ refuse(refusal: Refusal): Promise<OrderCallResponse>;
127
+ /** Takes the order on from outside a handler. Repeats are ordinary. */
128
+ accept(acceptance?: Acceptance): Promise<OrderAcceptResponse>;
129
+ }
130
+ /**
131
+ * The least an order can be and still be answerable: its identifier, and the
132
+ * calls that close it.
133
+ *
134
+ * It is what `orders.forId` hands back, and it is the shape a merchant's own
135
+ * function should take when all it does is close an order — every richer order
136
+ * below satisfies it.
137
+ */
138
+ export type OrderHandle = {
139
+ readonly id: string;
140
+ } & OrderCalls;
141
+ /**
142
+ * An order off the stream, with the calls that close it.
143
+ *
144
+ * "Live" is the whole of the difference from the plain document: this object
145
+ * holds the client that produced it, so it can call home on its own and the
146
+ * merchant never writes an identifier of ours as an argument.
147
+ */
148
+ export type LiveOrder = Order & OrderCalls;
149
+ /** The same, read back from our record, so it also says where the order stands. */
150
+ export type LiveOrderWithStatus = OrderWithStatus & OrderCalls;
151
+ /**
152
+ * The two answers a price question has, built from the question itself.
153
+ *
154
+ * `as_of` is what separates "I went and looked" from "here is what was in the
155
+ * cache", and the gateway reads it to decide how far it trusts the answer. Left
156
+ * out, it is the moment the answer is built — which is the truth for a price
157
+ * computed on the spot, and is why a merchant answering from a cache passes the
158
+ * moment that cache was filled instead.
159
+ */
160
+ export interface QuoteCalls {
161
+ available(price: Money, asOf?: string): QuoteResponse;
162
+ unavailable(asOf?: string): QuoteResponse;
163
+ }
164
+ /** A price question, with the two answers it can be given. */
165
+ export type LiveQuoteRequest = QuoteRequest & QuoteCalls;
166
+ /** What the merchant's code does with one paid order. */
167
+ export type OrderHandler = (order: LiveOrder) => HandlerAnswer | Promise<HandlerAnswer>;
168
+ /** What the merchant's code answers to "how much is this and is it there". */
169
+ export type QuoteHandler = (question: LiveQuoteRequest) => QuoteResponse | Promise<QuoteResponse>;
170
+ /**
171
+ * Everything a merchant's process can be asked to answer, by the word they
172
+ * register it under.
173
+ *
174
+ * A map rather than four methods, because the stream is one stream and this is
175
+ * the list of what travels on it. The day the contract carries a fourth kind —
176
+ * the confirmation request, whose shape it does not carry yet — `REGISTERED_AS`
177
+ * in `worker.ts` gains its word, and from that word every list in this file
178
+ * follows: what `on` accepts, what `start` counts as an answer, and the
179
+ * sentence that names a missing registration. What does not follow is what
180
+ * answers it, so this map gains an entry and the worker's dispatch gains an
181
+ * arm, and both refuse to compile until they do.
182
+ *
183
+ * `problem` is on the same list although nothing on the wire carries it. It is
184
+ * one registration per process, exactly like the other three, and a second
185
+ * place to register one of four things would put back the split this surface
186
+ * exists to remove.
187
+ */
188
+ export interface Handlers {
189
+ order: OrderHandler;
190
+ quote: QuoteHandler;
191
+ event: EventHandler;
192
+ problem: ProblemReporter;
193
+ }
194
+ /** The word for the reporter, which nothing on the wire carries. */
195
+ declare const PROBLEM = "problem";
196
+ export type HandlerKind = StreamHandlerKind | typeof PROBLEM;
197
+ /**
198
+ * Whether a type is a union of more than one thing.
199
+ *
200
+ * Here for one purpose, at `on` below: to tell a kind that is one kind from a
201
+ * kind that is any of several, because only the first can say which handler is
202
+ * the right one.
203
+ */
204
+ type IsUnion<Kind, Each = Kind> = Kind extends unknown ? [Each] extends [Kind] ? false : true : never;
205
+ export interface CatalogNamespace {
206
+ /**
207
+ * Publishes one product, or says what is wrong with the card.
208
+ *
209
+ * Publishing again under the same `merchant_item_id` changes that card
210
+ * rather than adding a second one, so a publish script can be run as often
211
+ * as a merchant likes.
212
+ *
213
+ * The card is taken as it may be written rather than as it is stored, which
214
+ * is what `CardInput` is: the price as one string or as two fields, a
215
+ * declared field as its type word or written out, the fulfillment mode left
216
+ * off a card that delivers in the answer to the purchase. Every one of those
217
+ * is opened out when the card is accepted, so a card read back afterwards is
218
+ * in the one canonical form.
219
+ */
220
+ publish(card: CardInput): Promise<PublishResult>;
221
+ }
222
+ export interface OrdersNamespace {
223
+ /**
224
+ * The calls for an order named by its identifier, without asking us
225
+ * anything.
226
+ *
227
+ * This is the one place an identifier of ours is written by a merchant, and
228
+ * it is here for the process that kept nothing else: a job queued against
229
+ * our identifier, a row in their own database. It reaches no gateway, which
230
+ * is the point — `get` below would tell them more about the order, and
231
+ * during an outage it cannot tell them anything at all, while a delivery
232
+ * that comes back as "the network failed, call again" is exactly the answer
233
+ * they need to keep retrying.
234
+ */
235
+ forId(orderId: string): OrderHandle;
236
+ /**
237
+ * One order and the state it is in, with the calls that close it.
238
+ *
239
+ * A round trip, and what it buys is the order itself: its parameters, what
240
+ * it was sold for, and where it stands.
241
+ */
242
+ get(orderId: string): Promise<LiveOrderWithStatus>;
243
+ /**
244
+ * Orders and the states they are in; with `open`, only those still owed
245
+ * something.
246
+ *
247
+ * The list is what a process reads after a restart: every order it still
248
+ * owes a delivery for, each one able to be delivered or refused on the spot.
249
+ */
250
+ list(query?: {
251
+ readonly open?: boolean;
252
+ }): Promise<readonly LiveOrderWithStatus[]>;
253
+ }
254
+ export interface AgentifyClient {
255
+ readonly catalog: CatalogNamespace;
256
+ readonly orders: OrdersNamespace;
257
+ /**
258
+ * Registers what this process answers for one kind.
259
+ *
260
+ * Once per kind: a second registration is refused rather than replacing the
261
+ * first, because one message goes to one handler and a silently replaced
262
+ * handler is a process that has stopped answering without saying so.
263
+ *
264
+ * Handlers may be registered before or after `start`; the loop reads them
265
+ * when an envelope arrives, not when it begins.
266
+ *
267
+ * The condition on the second parameter is what makes the two halves agree,
268
+ * and it is worth the sentence it costs. Written plainly as
269
+ * `(kind: Kind, handler: Handlers[Kind])`, a call whose kind is not a
270
+ * literal — a loop over `['order', 'quote']`, a kind held in a variable —
271
+ * infers `Kind` as the union, and `Handlers[Kind]` is then the union of
272
+ * handlers, so an order handler registered under `'quote'` type-checks. It
273
+ * fails at the first price question, where the handler calls a method the
274
+ * question does not have, and every sale of that card quietly falls back to
275
+ * the card's own price. So a kind that is not one kind admits no handler at
276
+ * all, and the call has to be narrowed to one of them first.
277
+ */
278
+ on<Kind extends HandlerKind>(kind: Kind, handler: IsUnion<Kind> extends true ? never : Handlers[Kind]): void;
279
+ /**
280
+ * Opens the subscription and returns once it is running.
281
+ *
282
+ * The subscription is outgoing — this side opens it, and nothing of the
283
+ * merchant's has to be reachable from outside. It carries the orders, the
284
+ * price questions and the order events together, so a process needs one of
285
+ * these and not three.
286
+ */
287
+ start(): Promise<void>;
288
+ /**
289
+ * Stops the subscription and waits for it to finish. Safe to call twice, and
290
+ * safe to call on a client that was never started.
291
+ *
292
+ * Two things are abandoned rather than finished, and they are not abandoned
293
+ * on the same terms. A poll parked at the gateway is dropped, and whatever
294
+ * it would have carried was never handed to anybody, so it is redelivered.
295
+ * An answer already on its way — a delivery the handler produced a moment
296
+ * ago — is dropped too, and there the honest thing to say is that nobody on
297
+ * this side knows whether it arrived first: the order may already be closed
298
+ * by it, or may come back. The merchant is told which of their orders that
299
+ * happened to, because on their side the work happened.
300
+ *
301
+ * Stopping does not drain. The contract has a word for a poll that asks for
302
+ * whatever is queued right now and comes straight back, and it is not what a
303
+ * worker shutting down wants: draining pulls more work into a process that
304
+ * is going away.
305
+ *
306
+ * The handlers survive it. A supervisor that stops a client and starts it
307
+ * again is running the same process, and having to register everything a
308
+ * second time would be a second place for the two to disagree.
309
+ */
310
+ stop(): Promise<void>;
311
+ }
312
+ /**
313
+ * The three codes an order call comes back under when the gateway produced no
314
+ * answer this package can read.
315
+ *
316
+ * The contract's list of error codes is open exactly so that a case nobody
317
+ * anticipated reaches the merchant in its own words instead of being flattened
318
+ * into the nearest of three. These are three of those, and they are produced
319
+ * here rather than on the wire.
320
+ *
321
+ * Three and not one because the difference is the whole of what a merchant
322
+ * needs at that moment, and they are about their own books. A call that never
323
+ * arrived certainly changed nothing. A call answered in words we could not
324
+ * read reached us and may well have done its work; what is missing is what it
325
+ * said. And a call that was sent into silence — the connection broke, the
326
+ * worker was shut down mid-flight — is neither of those, and saying it did not
327
+ * arrive would be inventing the one fact the merchant came here for.
328
+ */
329
+ export declare const CALL_DID_NOT_REACH_US = "call_did_not_reach_us";
330
+ export declare const ANSWER_NOT_UNDERSTOOD = "answer_not_understood";
331
+ export declare const OUTCOME_UNKNOWN = "outcome_unknown";
332
+ /**
333
+ * What is thrown where a route has no failure branch to hand a merchant back.
334
+ *
335
+ * Three of this client's calls answer with a document or with nothing at all —
336
+ * publishing a card, reading one order, listing them — so a call that produced
337
+ * no document has nowhere in the contract to be returned. It is thrown, and
338
+ * this is what it is thrown as.
339
+ *
340
+ * It carries what the returned failures carry, because the two are the same
341
+ * facts about the same calls and a merchant should not have to learn a second
342
+ * vocabulary for the half of them that throws. `code` is the gateway's own word
343
+ * where the gateway refused in words we recognise, and otherwise one of the
344
+ * three this package produces — `call_did_not_reach_us`, `answer_not_understood`,
345
+ * `outcome_unknown`. `failedCall` below makes that same choice for the order
346
+ * calls, so a refusal has one name on both roads. `route` is the call it
347
+ * happened on, under the name the
348
+ * contract's own table gives it, so a `catch` can say which of three it was
349
+ * without reading the sentence.
350
+ *
351
+ * What is not this: a client built wrong. A missing key, a `baseUrl` that is
352
+ * not an address, a handler registered twice and a kind nothing delivers are
353
+ * mistakes in the merchant's own code, made before anything left the process,
354
+ * and they stay `TypeError`. A merchant catching this class is catching calls
355
+ * that went out.
356
+ */
357
+ export declare class AgentifyError extends Error {
358
+ /** Why the call did not go through, for the code that branches on it. */
359
+ readonly code: string;
360
+ /** The call it happened on, named as the contract's route table names it. */
361
+ readonly route: string;
362
+ /**
363
+ * Whether making the same call again could get past this.
364
+ *
365
+ * Here for the same reason the code is: the calls that throw and the calls
366
+ * that return are the same calls failing the same ways, and a merchant who
367
+ * had to work out from a `catch` what a returned error would have told them
368
+ * outright is being charged twice for one lesson. The gateway's own answer
369
+ * where the gateway refused in words, and true where nothing readable came
370
+ * back — which is a statement about what this package knows, not a promise
371
+ * that the call will work.
372
+ */
373
+ readonly retryable: boolean;
374
+ constructor(code: string, route: string, message: string, retryable: boolean);
375
+ }
376
+ export declare const createClient: (options: ClientOptions) => AgentifyClient;
377
+ export {};