@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/client.js CHANGED
@@ -1,71 +1,34 @@
1
1
  /**
2
2
  * The client a merchant's engineer holds: a catalog, an order desk, and one
3
- * place to say what this process answers.
3
+ * place to say what this process answers. Every method here is one a page of
4
+ * the documentation tells a merchant to call, and the documentation's examples
5
+ * are compiled against these types by a test.
4
6
  *
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.
7
+ * One stream carries orders, price questions and order events, so there is one
8
+ * way to register a handler, `on(kind, handler)`, and one lifecycle, `start`
9
+ * and `stop`.
10
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.
11
+ * Two families of calls on an order look alike and are not. `delivered`,
12
+ * `refused` and `accepted` build the answer a handler returns and send
13
+ * nothing; returning it is what makes forgetting to answer, or answering
14
+ * twice, impossible. `deliver`, `refuse` and `accept` send: they are the
15
+ * asynchronous merchant's calls, made later from anywhere in their code, and
16
+ * they are on every order this client hands back, including `orders.forId`
17
+ * for a process that kept only the identifier.
16
18
  *
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.
19
+ * What the contract gives a failure branch is returned, never thrown, in one
20
+ * envelope whose `ok` says which it was. What has no branch is thrown: a client
21
+ * built wrong is a `TypeError` before anything leaves the process, and a call
22
+ * that went out and produced no answer its route can carry is an
23
+ * `AgentifyError` under the same codes.
57
24
  */
58
25
  import { callRoute, REACH, whatIsKnown, } from "./transport.js";
59
- import { droppedFieldWarning, REGISTERED_AS, reportSafely, startWorker, } from "./worker.js";
60
- /** The word for the reporter, which nothing on the wire carries. */
26
+ import { acceptanceArgumentWarning, droppedFieldWarning, REGISTERED_AS, reportSafely, startWorker, } from "./worker.js";
61
27
  const PROBLEM = "problem";
62
28
  /**
63
- * The words `on` accepts, as a map over the kinds themselves.
64
- *
65
- * A merchant working outside TypeScript, or one who wrote `orders`, would
66
- * otherwise register a handler that is never called and hear nothing about it.
67
- * Written as a mapped type so that a kind the stream grows and this file
68
- * forgets stops it compiling.
29
+ * The words `on` accepts. A merchant outside TypeScript, or one who wrote
30
+ * `orders`, would otherwise register a handler that is never called. As a
31
+ * mapped type, it stops compiling when a kind is added and this is not.
69
32
  */
70
33
  const HANDLER_KINDS = Object.freeze({
71
34
  order: true,
@@ -73,29 +36,20 @@ const HANDLER_KINDS = Object.freeze({
73
36
  event: true,
74
37
  problem: true,
75
38
  });
76
- /**
77
- * Every word `on` accepts, in the order this file lists them, for the sentence
78
- * a merchant reads when they wrote one that is not on the list.
79
- */
39
+ /** Every word `on` accepts, for the sentence that refuses one that is not. */
80
40
  const everyKind = () => Object.keys(HANDLER_KINDS).map((kind) => `'${kind}'`);
81
- /** The words that answer something on the stream, which `problem` does not. */
41
+ /** The kinds that answer something on the stream, which `problem` does not. */
82
42
  const streamKinds = () => Object.values(REGISTERED_AS);
83
43
  /**
84
- * The three codes an order call comes back under when the gateway produced no
85
- * answer this package can read.
44
+ * The codes an order call or an `AgentifyError` carries when the gateway
45
+ * produced no answer this package can read. The SDK produces them; the
46
+ * gateway never sends them.
86
47
  *
87
- * The contract's list of error codes is open exactly so that a case nobody
88
- * anticipated reaches the merchant in its own words instead of being flattened
89
- * into the nearest of three. These are three of those, and they are produced
90
- * here rather than on the wire.
91
- *
92
- * Three and not one because the difference is the whole of what a merchant
93
- * needs at that moment, and they are about their own books. A call that never
94
- * arrived certainly changed nothing. A call answered in words we could not
95
- * read reached us and may well have done its work; what is missing is what it
96
- * said. And a call that was sent into silence — the connection broke, the
97
- * worker was shut down mid-flight — is neither of those, and saying it did not
98
- * arrive would be inventing the one fact the merchant came here for.
48
+ * `call_did_not_reach_us`: the call certainly changed nothing.
49
+ * `answer_not_understood`: the call reached the gateway and may have done its
50
+ * work; what it said could not be read. `outcome_unknown`: the call was sent
51
+ * into silence, such as a dropped connection or a worker shut down
52
+ * mid-flight, and nobody on this side knows whether it arrived.
99
53
  */
100
54
  export const CALL_DID_NOT_REACH_US = "call_did_not_reach_us";
101
55
  export const ANSWER_NOT_UNDERSTOOD = "answer_not_understood";
@@ -106,29 +60,17 @@ const CODE_FOR = {
106
60
  [REACH.UNKNOWN]: OUTCOME_UNKNOWN,
107
61
  };
108
62
  /**
109
- * What is thrown where a route has no failure branch to hand a merchant back.
110
- *
111
- * Three of this client's calls answer with a document or with nothing at all —
112
- * publishing a card, reading one order, listing them — so a call that produced
113
- * no document has nowhere in the contract to be returned. It is thrown, and
114
- * this is what it is thrown as.
63
+ * What is thrown where a call has no failure branch to return: publishing a
64
+ * card, reading one order, listing orders.
115
65
  *
116
- * It carries what the returned failures carry, because the two are the same
117
- * facts about the same calls and a merchant should not have to learn a second
118
- * vocabulary for the half of them that throws. `code` is the gateway's own word
119
- * where the gateway refused in words we recognise, and otherwise one of the
120
- * three this package produces — `call_did_not_reach_us`, `answer_not_understood`,
121
- * `outcome_unknown`. `failedCall` below makes that same choice for the order
122
- * calls, so a refusal has one name on both roads. `route` is the call it
123
- * happened on, under the name the
124
- * contract's own table gives it, so a `catch` can say which of three it was
125
- * without reading the sentence.
66
+ * `code` is the gateway's own code where it refused in words this package
67
+ * recognises, and otherwise `call_did_not_reach_us`, `answer_not_understood`
68
+ * or `outcome_unknown`. `route` names the call as the contract's route table
69
+ * does, so a `catch` can tell the calls apart without reading the message.
126
70
  *
127
- * What is not this: a client built wrong. A missing key, a `baseUrl` that is
128
- * not an address, a handler registered twice and a kind nothing delivers are
129
- * mistakes in the merchant's own code, made before anything left the process,
130
- * and they stay `TypeError`. A merchant catching this class is catching calls
131
- * that went out.
71
+ * A client built wrong (a missing key, a `baseUrl` that is not an address, a
72
+ * handler registered twice) throws `TypeError` instead, before anything leaves
73
+ * the process.
132
74
  */
133
75
  export class AgentifyError extends Error {
134
76
  /** Why the call did not go through, for the code that branches on it. */
@@ -136,15 +78,9 @@ export class AgentifyError extends Error {
136
78
  /** The call it happened on, named as the contract's route table names it. */
137
79
  route;
138
80
  /**
139
- * Whether making the same call again could get past this.
140
- *
141
- * Here for the same reason the code is: the calls that throw and the calls
142
- * that return are the same calls failing the same ways, and a merchant who
143
- * had to work out from a `catch` what a returned error would have told them
144
- * outright is being charged twice for one lesson. The gateway's own answer
145
- * where the gateway refused in words, and true where nothing readable came
146
- * back — which is a statement about what this package knows, not a promise
147
- * that the call will work.
81
+ * Whether making the same call again could get past this: the gateway's own
82
+ * answer where it refused in words, and true where nothing readable came
83
+ * back, which says what this package knows, not that the call will work.
148
84
  */
149
85
  retryable;
150
86
  constructor(code, route, message, retryable) {
@@ -158,35 +94,15 @@ export class AgentifyError extends Error {
158
94
  /**
159
95
  * What an order call hands back when it produced no answer of its own.
160
96
  *
161
- * `code` is the gateway's own word where the gateway refused in words we
162
- * recognise, and otherwise one of the three this package produces for a
163
- * silence. The two halves of the answer have to say the same thing: a refusal
164
- * quoted in the message and filed under `answer_not_understood` is the message
165
- * arguing with the code, and the code is the half a program reads. It is the
166
- * same choice `document` makes for the calls that throw, so one refusal has one
167
- * name whether the merchant catches it or branches on it.
97
+ * `code` is the gateway's own where it refused in words this package
98
+ * recognises, and otherwise one of the three codes for a silence, the same
99
+ * choice `AgentifyError` makes. `retryable` is the gateway's own answer where
100
+ * it gave one, and true where nothing readable came back, since that is a
101
+ * statement about what this package knows, not about the order.
168
102
  *
169
- * `retryable` says whether calling again could change the outcome, and it has
170
- * two sources because there are two situations. Where the gateway refused in
171
- * words, the answer is the gateway's own: it is the side that knows whether
172
- * the door is shut or was merely shut a moment ago, and this package does not
173
- * improve on what it was told. Where no answer arrived, or none that could be
174
- * read, there is nothing to defer to and the answer is true — none of those
175
- * three is a state of the order, and none will still be true in a minute if a
176
- * network settled or a proxy went away.
177
- *
178
- * The fallback is `true` and not `false` on purpose. It is reached only when
179
- * this package could not read an answer, which is a statement about our
180
- * knowledge and not about the order; saying "do not call again" there would be
181
- * this package inventing the one fact the merchant came for.
182
- *
183
- * The sentence about repeating safely is added where two things hold at once:
184
- * the contract says the call may be repeated — delivering is idempotent by the
185
- * order's identifier, and taking an order on happens again on every
186
- * redelivery, while refusing is documented as neither — and calling again
187
- * could get anywhere. Offering "you may safely repeat this" under a refusal
188
- * that will answer the same way for ever is an invitation to do something
189
- * pointless, and it reads as encouragement.
103
+ * The sentence saying the call may be repeated safely is added only where the
104
+ * call is idempotent (delivering, accepting; not refusing) and repeating it
105
+ * could get anywhere.
190
106
  */
191
107
  const failedCall = (repeatIsSafe, failure) => {
192
108
  const retryable = failure.refusal?.retryable ?? true;
@@ -199,17 +115,12 @@ const failedCall = (repeatIsSafe, failure) => {
199
115
  };
200
116
  };
201
117
  /**
202
- * The reporter a client gets when the merchant registered none.
203
- *
204
- * A library writing to the console is a small rudeness. A worker that stopped
205
- * on a contract mismatch and said nothing to anybody is a merchant learning
206
- * about it from a buyer, and between the two this is the cheaper one.
118
+ * The reporter a client gets when the merchant registered none. A worker that
119
+ * stopped and told nobody is worse than a library writing to the console.
207
120
  */
208
121
  const reportToConsole = (problem) => {
209
- // The exception goes out beside the sentence rather than inside it. The
210
- // message already carries what the exception says; what it cannot carry is
211
- // the stack, and the stack is the whole of what a merchant debugging their
212
- // own handler is looking for.
122
+ // The exception goes out beside the sentence, because its stack is what a
123
+ // merchant debugging their own handler needs.
213
124
  if (problem.cause === undefined) {
214
125
  console.error(`[agentify] ${problem.kind}: ${problem.message}`);
215
126
  return;
@@ -217,34 +128,17 @@ const reportToConsole = (problem) => {
217
128
  console.error(`[agentify] ${problem.kind}: ${problem.message}`, problem.cause);
218
129
  };
219
130
  /**
220
- * What a key says about the world it was issued in, read from its own prefix.
221
- *
222
- * ADR-0020 is what makes reading it here legitimate rather than clever: every
223
- * key carries the environment it was issued in as its prefix, and whoever holds
224
- * a key can read that prefix. So a worker learns which deployment it joined
225
- * without a field on the wire, a line in a response document or a step in a
226
- * handshake — none of which this adds.
131
+ * What a key says about the deployment it was issued for, read from its
132
+ * prefix: every key carries its environment as its prefix.
227
133
  *
228
- * The rule's home is `packages/core/src/deployment/environment.ts`, where
229
- * `keyPrefixFor` writes these prefixes and `environmentOfKeyPrefix` reads them.
230
- * The two strings below are a second copy of that rule and not a use of it,
231
- * because this package cannot depend on that module at runtime: it is private
232
- * to the workspace and unpublished, and the dependency tree a merchant installs
233
- * is a recorded decision (ADR-0003 §8) that does not grow to reach it.
134
+ * This copies a rule whose home is `keyPrefixFor` in
135
+ * `packages/core/src/deployment/environment.ts`, which this published package
136
+ * cannot depend on. `which-world.test.ts` builds keys with core's own function
137
+ * and checks these sentences, so a prefix changed on one side fails the tests;
138
+ * change both together.
234
139
  *
235
- * A copy is a thing that drifts, and these two must not. What holds them
236
- * together is `which-world.test.ts`: it builds its keys out of core's own
237
- * `keyPrefixFor` and asserts the sentences written here, so a prefix changed on
238
- * one side and not the other fails `pnpm test` instead of going quiet in
239
- * somebody's log. That import is dev-time and reaches nothing published — the
240
- * tarball ships `dist`, and the build leaves the tests out. Changing either
241
- * side means changing both in the same commit.
242
- *
243
- * A key matching neither prefix says nothing at all, which is the third case
244
- * ADR-0020 names: a key naming no environment, or one that is not ours. There is nothing to fall back to. Guessing is wrong in both directions
245
- * — telling a merchant their money is play when it is real, or the other way
246
- * round — and no line at all is better than a wrong claim about whose money is
247
- * at stake.
140
+ * A key matching neither prefix gets no line at all: guessing would risk
141
+ * telling a merchant their money is play when it is real, or the reverse.
248
142
  */
249
143
  const WHAT_THE_KEY_SAYS = Object.freeze({
250
144
  csk_test_: "with a test key: the money there is not real",
@@ -258,12 +152,9 @@ const keyOf = (apiKey) => {
258
152
  return apiKey;
259
153
  };
260
154
  /**
261
- * The address as given, checked as far as it can be checked without using it.
262
- *
263
- * A value that is not an address is refused at once: it will not become one
264
- * later, and the line that produced it is the line to fix. An address that was
265
- * not given at all is a different matter, and it is kept as the sentence that
266
- * will be raised by the first call that needs it — see `baseUrl` above.
155
+ * The address as given, checked as far as it can be without using it. A value
156
+ * that is not an http or https address is refused at once; an address not
157
+ * given at all is refused by the first call that needs it.
267
158
  */
268
159
  const addressOf = (baseUrl) => {
269
160
  if (baseUrl === undefined)
@@ -295,14 +186,11 @@ export const createClient = (options) => {
295
186
  }
296
187
  };
297
188
  /**
298
- * The handlers, and the loop that dispatches to them.
299
- *
300
- * The registry belongs to the client and not to any one loop, which is what
301
- * lets a merchant stop and start again on the handlers they already
302
- * registered. `worker` is the loop currently running, if one is; it also
303
- * ends on its own when the gateway turns out to speak another dialect, which
304
- * is why every reader asks it whether it is still running rather than
305
- * assuming that a worker that exists is a worker that polls.
189
+ * The handlers, and the loop that dispatches to them. The registry belongs
190
+ * to the client, so a stopped client starts again on the same handlers.
191
+ * `worker` is the loop currently running, if any; it can also end on its
192
+ * own when the gateway speaks another contract version, so readers ask it
193
+ * whether it is still running.
306
194
  */
307
195
  const registry = { problem: reportToConsole };
308
196
  const registered = new Set();
@@ -313,24 +201,10 @@ export const createClient = (options) => {
313
201
  let worldIsSaid = false;
314
202
  /**
315
203
  * The one line this package writes uninvited: where this worker is pointed
316
- * and whether the money there is real.
317
- *
318
- * Once per client and not once per start. The key cannot change under a
319
- * client and neither can the address, so a second line would carry no second
320
- * fact — and a supervisor that stops and starts a worker on a schedule would
321
- * turn it into the kind of line people learn to scroll past. The question is
322
- * closed even where the answer is silence: a key naming no environment is
323
- * asked about once and never again.
324
- *
325
- * It goes to the console because that is where this package already writes
326
- * when nobody gave it anywhere else to write, and at the informational level
327
- * because that is what it is. `console.error` would put it in front of every
328
- * log collector as a fault on a worker that has just started correctly, which
329
- * is a false alarm and the one thing this line must never look like.
330
- *
331
- * It is not a `WorkerProblem`: the merchant's reporter is for what went
332
- * wrong, and a process counting problems should not have to filter out the
333
- * one that means everything is fine.
204
+ * and whether the money there is real. Once per client, since neither the
205
+ * key nor the address can change. It goes to `console.info` because it is
206
+ * not a fault, and it is not a `WorkerProblem`, since the reporter is for
207
+ * what went wrong.
334
208
  */
335
209
  const sayWhichWorld = () => {
336
210
  if (worldIsSaid)
@@ -344,12 +218,9 @@ export const createClient = (options) => {
344
218
  const orderCall = async (route, orderId, body) => {
345
219
  reachable();
346
220
  if (route === "deliver_order") {
347
- // The road the worker's own warning does not cover, and the one a
348
- // merchant delivering asynchronously takes. See `droppedFieldWarning`:
349
- // the field goes no further than the gateway's parse, and without this
350
- // the merchant's handler had no way to learn that what it sent is not
351
- // what went out. A warning and not a refusal — the rest of the delivery
352
- // is good, and the call is the merchant's to make.
221
+ // `droppedFieldWarning` covers the asynchronous delivery too: the field
222
+ // goes no further than the gateway's parse, and this is the merchant's
223
+ // only way to learn so. A warning, not a refusal; the rest is good.
353
224
  const dropped = droppedFieldWarning(orderId, body);
354
225
  if (dropped !== null)
355
226
  reportSafely(registry.problem, dropped);
@@ -365,28 +236,31 @@ export const createClient = (options) => {
365
236
  path: { order_id: orderId },
366
237
  body: {},
367
238
  });
368
- // Accepting the same order again is ordinary: an order is taken on afresh
369
- // on every redelivery, so a repeat does no work twice.
239
+ // Accepting the same order again is harmless: an order is taken on afresh
240
+ // on every redelivery.
370
241
  return answer.ok ? answer.document : { ok: false, error: failedCall(true, answer.failure) };
371
242
  };
372
243
  /**
373
- * The calls one order answers to, closed over that order's identifier and
374
- * over this client.
375
- *
376
- * This closure is the whole mechanism. The identifier is captured here, once,
377
- * at the moment the order was read — off the stream or out of our record —
378
- * and never travels back through a merchant's hands, where it could be the
379
- * wrong string. What holds the client is the same closure: `orderCall` and
380
- * `acceptCall` above are this client's, so an order kept in a merchant's map
381
- * for four hours still knows which gateway to call and with whose key.
244
+ * The calls one order answers to, closed over its identifier and this
245
+ * client. The identifier is captured once, when the order was read, and
246
+ * never passes back through the merchant's hands, and an order kept for
247
+ * hours still knows which gateway to call and with whose key.
382
248
  */
383
249
  const callsFor = (orderId) => ({
384
250
  delivered: (delivery) => ({ delivered: delivery }),
385
251
  refused: (refusal) => ({ refused: refusal }),
386
- accepted: () => ({ accepted: {} }),
252
+ accepted: (...given) => {
253
+ if (given.length > 0)
254
+ reportSafely(registry.problem, acceptanceArgumentWarning(orderId));
255
+ return { accepted: {} };
256
+ },
387
257
  deliver: (delivery) => orderCall("deliver_order", orderId, delivery),
388
258
  refuse: (refusal) => orderCall("refuse_order", orderId, refusal),
389
- accept: () => acceptCall(orderId),
259
+ accept: (...given) => {
260
+ if (given.length > 0)
261
+ reportSafely(registry.problem, acceptanceArgumentWarning(orderId));
262
+ return acceptCall(orderId);
263
+ },
390
264
  });
391
265
  const withCalls = (order) => Object.assign({}, order, callsFor(order.id));
392
266
  const askedWithAnswers = (question) => Object.assign({}, question, {
@@ -405,12 +279,8 @@ export const createClient = (options) => {
405
279
  const answer = await callRoute(gateway, route, options_);
406
280
  if (!answer.ok) {
407
281
  const { failure } = answer;
408
- // The gateway's own word wherever it wrote one, and one of ours only
409
- // where it did not. A refusal we recognise carries the reason a merchant
410
- // can act on, and replacing it with "we could not read the answer" would
411
- // throw away the one thing they came here for. The same holds for its
412
- // answer about calling again — this is the rule `failedCall` applies for
413
- // the calls that return, applied here so the two roads agree.
282
+ // The gateway's own code and retry answer wherever it wrote them, ours
283
+ // only where it did not, as `failedCall` does for the calls that return.
414
284
  throw new AgentifyError(failure.refusal?.code ?? CODE_FOR[failure.reach], failure.route, failure.reason, failure.refusal?.retryable ?? true);
415
285
  }
416
286
  return answer.document;
@@ -424,9 +294,8 @@ export const createClient = (options) => {
424
294
  get: async (orderId) => withCalls(await document("get_order", { path: { order_id: orderId } })),
425
295
  list: async (query) => {
426
296
  const listed = await document("list_orders", {
427
- // The wire carries the two words rather than a boolean, because a
428
- // query string carries text. The merchant writes the boolean their
429
- // language has and this is where the two meet.
297
+ // The wire carries the words "true" and "false", because a query
298
+ // string carries text.
430
299
  query: query?.open === undefined ? {} : { open: query.open ? "true" : "false" },
431
300
  });
432
301
  return listed.orders.map((order) => withCalls(order));
@@ -439,14 +308,12 @@ export const createClient = (options) => {
439
308
  if (registered.has(kind)) {
440
309
  throw new TypeError(`on('${kind}') was called twice on one client: the second handler would silently replace the first, and one message goes to one handler`);
441
310
  }
442
- // Narrowed off the generic so that the switch below is exhaustive over
443
- // the kinds rather than over whatever this call was instantiated with.
311
+ // Narrowed off the generic so the switch is exhaustive over the kinds.
444
312
  const which = kind;
445
313
  switch (which) {
446
314
  case "order": {
447
- // The merchant's handler is wrapped rather than registered as it
448
- // stands: the loop reads a plain order off the envelope, and the
449
- // calls that close it are this client's to attach.
315
+ // Wrapped so the order the loop reads off the envelope gets this
316
+ // client's calls attached.
450
317
  const answering = handler;
451
318
  registry.order = (arrived) => answering(withCalls(arrived));
452
319
  break;
@@ -467,9 +334,8 @@ export const createClient = (options) => {
467
334
  default:
468
335
  return assertEveryKindIsRegistered(which);
469
336
  }
470
- // Written down only once everything that could refuse this call has
471
- // passed, so a call that threw leaves no trace and the same call made
472
- // again succeeds.
337
+ // Recorded only after everything that could refuse has passed, so a call
338
+ // that threw leaves no trace.
473
339
  registered.add(which);
474
340
  },
475
341
  start: async () => {
@@ -480,34 +346,26 @@ export const createClient = (options) => {
480
346
  if (worker?.running() === true) {
481
347
  throw new TypeError("this client is already started: a second loop would open a second long poll for one merchant, and an envelope handed to one of them is not handed to the other");
482
348
  }
483
- // Read off the vocabulary rather than written out, so a kind the stream
484
- // grows is counted as an answer here without anybody remembering to add
485
- // it — a process that answered only the new kind would otherwise be
486
- // refused a start on the grounds that it answers nothing.
349
+ // Read off the vocabulary, so a kind the stream grows counts as an
350
+ // answer without anybody remembering to add it here.
487
351
  if (!streamKinds().some((kind) => registry[kind] !== undefined)) {
488
352
  throw new TypeError(`this client has nothing registered to answer with: register at least one of ${streamKinds()
489
353
  .map((kind) => `on('${kind}')`)
490
354
  .join(", ")} before start(), or the loop would take work off the queue that nobody answers`);
491
355
  }
492
- // After every refusal above and before the loop: a start that threw
493
- // opened nothing, and a line saying otherwise would be the first thing a
494
- // merchant read on the way to an exception.
356
+ // After every refusal above: a start that threw opened nothing and must
357
+ // not say otherwise.
495
358
  sayWhichWorld();
496
359
  worker = startWorker(gateway, registry);
497
360
  },
498
361
  stop: async () => {
499
362
  const owned = worker;
500
- // A client that was never started has nothing to stop, and saying so by
501
- // returning is what lets a shutdown routine call this unconditionally.
363
+ // A client never started has nothing to stop, so a shutdown routine can
364
+ // call this unconditionally.
502
365
  if (owned === undefined)
503
366
  return;
504
- // One promise for every caller — a shutdown routine and a signal handler
505
- // both calling stop() is ordinary — and what it buys is the bookkeeping
506
- // rather than the waiting. Each of them would wait out the same loop
507
- // either way, because the worker's own stop() awaits the one promise the
508
- // loop ends on. What must happen once is the clearing below: two of them
509
- // racing it is two of them deciding whether this client still has a
510
- // worker, and the last one to answer wins.
367
+ // One promise for every concurrent caller, so the clearing below happens
368
+ // once rather than racing over whether this client still has a worker.
511
369
  stopping ??= owned.stop().finally(() => {
512
370
  if (worker === owned)
513
371
  worker = undefined;
@@ -1,17 +1,14 @@
1
1
  /**
2
- * Which contract this package speaks, and whether it understands another.
3
- *
4
- * It sits in a file of its own because both ends of the package need it and
5
- * neither should reach through the other: the worker checks the version on the
6
- * answer to every poll, and `index.ts` publishes the same check to a merchant
7
- * who wants to make it themselves.
2
+ * Which contract this package speaks, and whether it understands another. The
3
+ * worker checks the version on the answer to every poll, and the package
4
+ * exports the same check to a merchant who wants to make it themselves.
8
5
  */
9
6
  /** The contract version this SDK talks in. */
10
7
  export declare const contractVersion = "2";
11
8
  /**
12
- * Whether the SDK understands a contract of the given version. The gateway
13
- * names its own version in the response, the merchant's worker checks it with
14
- * this function and fails at startup rather than on the first order, where a
15
- * divergence of dialects costs the buyer money.
9
+ * Whether this SDK understands a contract of the given version. The gateway
10
+ * names its version in its answers; the worker checks it with this function
11
+ * and stops at startup rather than on the first order, where a mismatch would
12
+ * cost the buyer money.
16
13
  */
17
14
  export declare function speaksContract(version: string): boolean;
package/dist/contract.js CHANGED
@@ -1,19 +1,16 @@
1
1
  /**
2
- * Which contract this package speaks, and whether it understands another.
3
- *
4
- * It sits in a file of its own because both ends of the package need it and
5
- * neither should reach through the other: the worker checks the version on the
6
- * answer to every poll, and `index.ts` publishes the same check to a merchant
7
- * who wants to make it themselves.
2
+ * Which contract this package speaks, and whether it understands another. The
3
+ * worker checks the version on the answer to every poll, and the package
4
+ * exports the same check to a merchant who wants to make it themselves.
8
5
  */
9
6
  import { CONTRACT_VERSION } from "@nuanu-ai/agentify-contracts";
10
7
  /** The contract version this SDK talks in. */
11
8
  export const contractVersion = CONTRACT_VERSION;
12
9
  /**
13
- * Whether the SDK understands a contract of the given version. The gateway
14
- * names its own version in the response, the merchant's worker checks it with
15
- * this function and fails at startup rather than on the first order, where a
16
- * divergence of dialects costs the buyer money.
10
+ * Whether this SDK understands a contract of the given version. The gateway
11
+ * names its version in its answers; the worker checks it with this function
12
+ * and stops at startup rather than on the first order, where a mismatch would
13
+ * cost the buyer money.
17
14
  */
18
15
  export function speaksContract(version) {
19
16
  return version === CONTRACT_VERSION;