@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/README.md +4 -2
- package/dist/backoff.d.ts +17 -39
- package/dist/backoff.js +17 -39
- package/dist/check-card.d.ts +15 -24
- package/dist/check-card.js +15 -24
- package/dist/cli.d.ts +6 -16
- package/dist/cli.js +6 -16
- package/dist/client.d.ts +123 -251
- package/dist/client.js +113 -255
- package/dist/contract.d.ts +7 -10
- package/dist/contract.js +7 -10
- package/dist/index.d.ts +16 -28
- package/dist/index.js +16 -28
- package/dist/schema.d.ts +15 -39
- package/dist/schema.js +13 -31
- package/dist/transport.d.ts +45 -125
- package/dist/transport.js +54 -145
- package/dist/verify.d.ts +8 -28
- package/dist/verify.js +17 -60
- package/dist/worker.d.ts +97 -239
- package/dist/worker.js +111 -255
- package/package.json +2 -2
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
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
|
|
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
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
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
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
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
|
-
*
|
|
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
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
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
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
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
|
|
210
|
-
//
|
|
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
|
|
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
|
-
*
|
|
229
|
-
* `
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
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
|
|
236
|
-
*
|
|
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
|
|
262
|
-
*
|
|
263
|
-
*
|
|
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
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
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
|
-
*
|
|
319
|
-
*
|
|
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
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
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
|
|
369
|
-
// on every redelivery
|
|
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
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
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: () =>
|
|
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: () =>
|
|
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
|
|
409
|
-
// where it did not
|
|
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
|
|
428
|
-
//
|
|
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
|
|
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
|
-
//
|
|
448
|
-
//
|
|
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
|
-
//
|
|
471
|
-
//
|
|
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
|
|
484
|
-
//
|
|
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
|
|
493
|
-
//
|
|
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
|
|
501
|
-
//
|
|
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
|
|
505
|
-
//
|
|
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;
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which contract this package speaks, and whether it understands another.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
13
|
-
* names its
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
*
|
|
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
|
|
14
|
-
* names its
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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;
|