@nuanu-ai/agentify 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -24
- package/dist/client.d.ts +39 -3
- package/dist/client.js +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# `@nuanu-ai/agentify`
|
|
2
2
|
|
|
3
|
-
Agentify
|
|
4
|
-
gets a card
|
|
5
|
-
`/x402/<item>/purchase`, where an agent pays for it with the x402
|
|
6
|
-
protocol. The buyer pays into the merchant's own payout contract, which nobody
|
|
3
|
+
Agentify publishes a merchant's existing products in its own catalogue, which
|
|
4
|
+
AI agents read and buy from. Each product gets a card there and a paid address
|
|
5
|
+
of its own, `/x402/<item>/purchase`, where an agent pays for it with the x402
|
|
6
|
+
payment protocol. The buyer pays into the merchant's own payout contract, which nobody
|
|
7
7
|
owns and nobody can redirect. It keeps the money until it is paid out, which
|
|
8
8
|
the merchant can do at any time, and then sends 99.9% to the merchant's wallet
|
|
9
9
|
and 0.1% to Agentify ([Money](https://agentify.ad/docs/money)).
|
|
@@ -14,6 +14,15 @@ each order with the goods or with a refusal. Nothing of the merchant's has to
|
|
|
14
14
|
be reachable from outside — the process opens that connection itself, so no
|
|
15
15
|
public address and no open port are needed.
|
|
16
16
|
|
|
17
|
+
## Before the first call
|
|
18
|
+
|
|
19
|
+
The key comes from the test channel's dashboard at
|
|
20
|
+
[test.agentify.ad/dashboard](https://test.agentify.ad/dashboard): sign in with
|
|
21
|
+
your email, and on API Keys issue a key, which is shown once. Before your first
|
|
22
|
+
card can be published, the dashboard also needs the seller name buyers will
|
|
23
|
+
see and, in Settings, the wallet your share of each sale is paid out to; a
|
|
24
|
+
publish without them is refused with findings that name what is missing.
|
|
25
|
+
|
|
17
26
|
## Install
|
|
18
27
|
|
|
19
28
|
```sh
|
|
@@ -23,7 +32,7 @@ npm install @nuanu-ai/agentify
|
|
|
23
32
|
Node.js 24 or newer is required. What arrives with it is our own contracts
|
|
24
33
|
package and zod underneath that, and nothing else.
|
|
25
34
|
|
|
26
|
-
##
|
|
35
|
+
## Test or live: the address decides
|
|
27
36
|
|
|
28
37
|
```ts
|
|
29
38
|
import { createClient } from '@nuanu-ai/agentify'
|
|
@@ -34,12 +43,12 @@ const agentify = createClient({
|
|
|
34
43
|
})
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
There are two
|
|
46
|
+
There are two channels, one address each. At `https://test.agentify.ad` you
|
|
38
47
|
build: payments settle on a test chain and the money there is not real. At
|
|
39
|
-
`https://agentify.ad` you sell, and it is. A key issued
|
|
40
|
-
|
|
48
|
+
`https://agentify.ad` you sell, and it is. A key issued on one does not open
|
|
49
|
+
the other, and the key says which one you are holding: `csk_test_` for the
|
|
41
50
|
first, `csk_live_` for the second. This package picks neither address for you,
|
|
42
|
-
and
|
|
51
|
+
and your process's first log line names the one it started against and whether
|
|
43
52
|
the money there is real.
|
|
44
53
|
|
|
45
54
|
## A card, and the handler that fills the orders
|
|
@@ -88,12 +97,39 @@ yours, so its `id` is also its idempotency key: pass it into your own delivery
|
|
|
88
97
|
system and the second arrival hands back the first result instead of delivering
|
|
89
98
|
again.
|
|
90
99
|
|
|
91
|
-
A
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
A card we would not publish comes back as `ok: false`, with a code, a sentence a
|
|
101
|
+
person can read, whether repeating could change the outcome, and its findings
|
|
102
|
+
under `problems`. `deliver`, `refuse`, `accept` and `close` hand every failure back in
|
|
103
|
+
that same envelope, and throw only on a client built with no address. `catalog.publish`, `orders.get` and
|
|
104
|
+
`orders.list` throw an `AgentifyError` whenever no publish result or order came
|
|
105
|
+
back — a refused key included — with the same codes, the flag and the call it
|
|
106
|
+
happened on.
|
|
107
|
+
|
|
108
|
+
To stop Agentify accepting deliveries for an order, call its `close` method.
|
|
109
|
+
The optional message is your statement to the buyer, which their agent reads
|
|
110
|
+
as your words: plain text of at most 2000 characters, with no markup and no
|
|
111
|
+
control characters. A message that breaks that rule is refused before anything
|
|
112
|
+
is recorded.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const order = await agentify.orders.get(orderId)
|
|
116
|
+
const closed = await order.close({ message: 'Please contact our shop to continue.' })
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Closing cannot be undone, moves no money and confirms no refund; you and the
|
|
120
|
+
buyer handle any refund or other agreement outside Agentify. A repeat of the
|
|
121
|
+
call keeps the first closure time and message. Two refusals leave the order as
|
|
122
|
+
it was: `fulfillment_complete` where the goods were delivered or the parcel's
|
|
123
|
+
shipment is recorded, and `nothing_to_close` where the order ended with
|
|
124
|
+
nothing charged.
|
|
125
|
+
|
|
126
|
+
An order read back carries `payment_status` for its original payment and
|
|
127
|
+
`merchant_closure` for your closure. Closing does not cancel a charge Agentify
|
|
128
|
+
had already sent to the facilitator. If that charge completes after you
|
|
129
|
+
closed, you receive `order.unfulfilled` with the reason `paid_after_closure`,
|
|
130
|
+
and the order stays on `orders.list({ open: true })` until you close it again;
|
|
131
|
+
that second closure records its own time and message. The whole rule is on
|
|
132
|
+
[Orders](https://agentify.ad/docs/orders#closing-fulfillment).
|
|
97
133
|
|
|
98
134
|
## The check that needs no key
|
|
99
135
|
|
|
@@ -107,15 +143,16 @@ check is exported as `checkCard`, for cards assembled in code rather than kept
|
|
|
107
143
|
in files.
|
|
108
144
|
|
|
109
145
|
That is one half of checking yourself. The other half — whether your handler
|
|
110
|
-
holds against a real order
|
|
111
|
-
|
|
112
|
-
[quickstart](https://agentify.ad/docs/quickstart)
|
|
146
|
+
holds against a real order — is a test purchase you make yourself on the test
|
|
147
|
+
channel, buying your own card with an x402 client and test USDC; the
|
|
148
|
+
[quickstart](https://agentify.ad/docs/quickstart#_6-make-your-first-test-purchase)
|
|
149
|
+
walks you through one.
|
|
113
150
|
|
|
114
151
|
## Where the rest of it is written
|
|
115
152
|
|
|
116
153
|
The merchant documentation is public. [The first test
|
|
117
154
|
sale](https://agentify.ad/docs/quickstart) walks from an empty project to
|
|
118
|
-
a test purchase
|
|
155
|
+
a test purchase you make yourself. Beside it are [the product
|
|
119
156
|
card](https://agentify.ad/docs/cards) field by field, [orders and fulfillment
|
|
120
157
|
modes](https://agentify.ad/docs/orders) for delivering later rather than in
|
|
121
158
|
the answer to the purchase, and [what can go
|
|
@@ -128,11 +165,12 @@ than the engineer.
|
|
|
128
165
|
## Versions
|
|
129
166
|
|
|
130
167
|
This is an early `0.x` pilot surface and the names in it can still change. The
|
|
131
|
-
SDK and
|
|
132
|
-
|
|
133
|
-
version does not move with every change, so a release older than
|
|
134
|
-
|
|
135
|
-
|
|
168
|
+
SDK and Agentify agree on a contract version, and a process whose version
|
|
169
|
+
Agentify does not share stops before it takes an order. During the pilot that
|
|
170
|
+
version does not move with every change, so a release older than Agentify can
|
|
171
|
+
read a word it does not know as a failure rather than stop: keep the package
|
|
172
|
+
current. What changed in each release is in the
|
|
173
|
+
[changelog](https://github.com/nuanu-ai/agentify/blob/main/packages/sdk/CHANGELOG.md). Register `agentify.on('problem', …)`, which is where you
|
|
136
174
|
hear about that and about everything else that did not get through.
|
|
137
175
|
|
|
138
176
|
## License
|
package/dist/client.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* that went out and produced no answer its route can carry is an
|
|
23
23
|
* `AgentifyError` under the same codes.
|
|
24
24
|
*/
|
|
25
|
-
import type { CardInput, Delivery, HandlerAnswer, Money, Order, OrderAcceptResponse, OrderCallResponse, OrderWithStatus, PublishResult, QuoteRequest, QuoteResponse, Refusal } from "@nuanu-ai/agentify-contracts";
|
|
25
|
+
import type { CardInput, Delivery, HandlerAnswer, Money, Order, OrderAcceptResponse, OrderCallResponse, OrderCloseRequest, OrderCloseResponse, OrderWithStatus, PublishResult, QuoteRequest, QuoteResponse, Refusal } from "@nuanu-ai/agentify-contracts";
|
|
26
26
|
import { type EventHandler, type ProblemReporter, type StreamHandlerKind } from "./worker.js";
|
|
27
27
|
export interface ClientOptions {
|
|
28
28
|
/**
|
|
@@ -78,6 +78,26 @@ export interface OrderCalls {
|
|
|
78
78
|
refuse(refusal: Refusal): Promise<OrderCallResponse>;
|
|
79
79
|
/** Takes the order on from outside a handler. Repeating it is harmless. */
|
|
80
80
|
accept(): Promise<OrderAcceptResponse>;
|
|
81
|
+
/**
|
|
82
|
+
* Stops future fulfillment through Agentify for this order. The message is
|
|
83
|
+
* your statement to the buyer, plain text of at most 2000 characters, which
|
|
84
|
+
* their agent reads as your words; Agentify confirms no refund or agreement.
|
|
85
|
+
* Closing cannot be undone, and it causes no new charge.
|
|
86
|
+
*
|
|
87
|
+
* Calling it again succeeds and keeps the first closure's time and message,
|
|
88
|
+
* with one exception. Closing cancels no payment: a payment Agentify had
|
|
89
|
+
* already asked the facilitator to execute may still go through after you
|
|
90
|
+
* close. If it does, you receive `order.unfulfilled` with reason
|
|
91
|
+
* `paid_after_closure`, the order is on `orders.list({ open: true })`, and
|
|
92
|
+
* closing it again then records that closure's own time and message and
|
|
93
|
+
* takes it off the list.
|
|
94
|
+
*
|
|
95
|
+
* Resolves to `{ ok: true }`, or to `ok: false` with an `error`, leaving the
|
|
96
|
+
* order unchanged: `fulfillment_complete` where its goods were delivered or
|
|
97
|
+
* its parcel's shipment is recorded, and `nothing_to_close` where it ended
|
|
98
|
+
* without a charge and holds nothing for its buyer. Neither is retryable.
|
|
99
|
+
*/
|
|
100
|
+
close(options?: OrderCloseRequest): Promise<OrderCloseResponse>;
|
|
81
101
|
}
|
|
82
102
|
/**
|
|
83
103
|
* The least an order can be and still be answered: its identifier and the
|
|
@@ -162,7 +182,8 @@ export interface OrdersNamespace {
|
|
|
162
182
|
/**
|
|
163
183
|
* Orders and the states they are in. With `open: true`, only the ones a
|
|
164
184
|
* restarted worker should walk: orders it was handed and has not finished,
|
|
165
|
-
* an
|
|
185
|
+
* an unfulfilled paid order, one delivered but never paid for, and a closed
|
|
186
|
+
* one whose payment completed after you closed it. An order
|
|
166
187
|
* that was priced and never paid never reached the handler, so it is not on
|
|
167
188
|
* that list; `get` still reads it. Throws `AgentifyError` when the call
|
|
168
189
|
* fails.
|
|
@@ -197,7 +218,6 @@ export interface AgentifyClient {
|
|
|
197
218
|
* answer.
|
|
198
219
|
*/
|
|
199
220
|
start(): Promise<void>;
|
|
200
|
-
/**
|
|
201
221
|
/**
|
|
202
222
|
* Stops the subscription and waits for it to finish. Safe to call twice,
|
|
203
223
|
* and on a client that was never started. Handlers stay registered.
|
|
@@ -253,5 +273,21 @@ export declare class AgentifyError extends Error {
|
|
|
253
273
|
readonly retryable: boolean;
|
|
254
274
|
constructor(code: string, route: string, message: string, retryable: boolean);
|
|
255
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Builds the client a merchant's code publishes cards and answers orders with,
|
|
278
|
+
* against one gateway: `https://test.agentify.ad` while building,
|
|
279
|
+
* `https://agentify.ad` once live, named in `baseUrl`.
|
|
280
|
+
*
|
|
281
|
+
* Building it calls nothing. It throws a `TypeError` at once when `apiKey` is
|
|
282
|
+
* missing or blank and when `baseUrl` is given but is not an http or https
|
|
283
|
+
* address. Left without `baseUrl`, it is still built, and every call that has to
|
|
284
|
+
* reach the gateway throws a `TypeError` naming both addresses, since there is
|
|
285
|
+
* no default to fall back on. Whether the key opens that gateway is learned at
|
|
286
|
+
* the first call.
|
|
287
|
+
*
|
|
288
|
+
* Returns the client: `catalog.publish` for cards, `orders` to read and close
|
|
289
|
+
* orders, `on` to register handlers, and `start` and `stop` for the
|
|
290
|
+
* subscription that brings orders, price questions and order events in.
|
|
291
|
+
*/
|
|
256
292
|
export declare const createClient: (options: ClientOptions) => AgentifyClient;
|
|
257
293
|
export {};
|
package/dist/client.js
CHANGED
|
@@ -101,7 +101,7 @@ export class AgentifyError extends Error {
|
|
|
101
101
|
* statement about what this package knows, not about the order.
|
|
102
102
|
*
|
|
103
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
|
|
104
|
+
* call is idempotent (delivering, accepting or closing; not refusing) and repeating it
|
|
105
105
|
* could get anywhere.
|
|
106
106
|
*/
|
|
107
107
|
const failedCall = (repeatIsSafe, failure) => {
|
|
@@ -175,6 +175,22 @@ const addressOf = (baseUrl) => {
|
|
|
175
175
|
const assertEveryKindIsRegistered = (kind) => {
|
|
176
176
|
throw new TypeError(`on() has no place to put a handler for ${JSON.stringify(kind)}`);
|
|
177
177
|
};
|
|
178
|
+
/**
|
|
179
|
+
* Builds the client a merchant's code publishes cards and answers orders with,
|
|
180
|
+
* against one gateway: `https://test.agentify.ad` while building,
|
|
181
|
+
* `https://agentify.ad` once live, named in `baseUrl`.
|
|
182
|
+
*
|
|
183
|
+
* Building it calls nothing. It throws a `TypeError` at once when `apiKey` is
|
|
184
|
+
* missing or blank and when `baseUrl` is given but is not an http or https
|
|
185
|
+
* address. Left without `baseUrl`, it is still built, and every call that has to
|
|
186
|
+
* reach the gateway throws a `TypeError` naming both addresses, since there is
|
|
187
|
+
* no default to fall back on. Whether the key opens that gateway is learned at
|
|
188
|
+
* the first call.
|
|
189
|
+
*
|
|
190
|
+
* Returns the client: `catalog.publish` for cards, `orders` to read and close
|
|
191
|
+
* orders, `on` to register handlers, and `start` and `stop` for the
|
|
192
|
+
* subscription that brings orders, price questions and order events in.
|
|
193
|
+
*/
|
|
178
194
|
export const createClient = (options) => {
|
|
179
195
|
const gateway = {
|
|
180
196
|
apiKey: keyOf(options.apiKey),
|
|
@@ -256,6 +272,14 @@ export const createClient = (options) => {
|
|
|
256
272
|
},
|
|
257
273
|
deliver: (delivery) => orderCall("deliver_order", orderId, delivery),
|
|
258
274
|
refuse: (refusal) => orderCall("refuse_order", orderId, refusal),
|
|
275
|
+
close: async (options = {}) => {
|
|
276
|
+
reachable();
|
|
277
|
+
const answer = await callRoute(gateway, "close_order", {
|
|
278
|
+
path: { order_id: orderId },
|
|
279
|
+
body: options,
|
|
280
|
+
});
|
|
281
|
+
return answer.ok ? answer.document : { ok: false, error: failedCall(true, answer.failure) };
|
|
282
|
+
},
|
|
259
283
|
accept: (...given) => {
|
|
260
284
|
if (given.length > 0)
|
|
261
285
|
reportSafely(registry.problem, acceptanceArgumentWarning(orderId));
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* an order, a handler's answer — are re-exported here, so integration code
|
|
21
21
|
* needs one import.
|
|
22
22
|
*/
|
|
23
|
-
export type { CallError, Card, CardInput, Delivery, Fulfillment, HandlerAnswer, MerchantFinding, Money, Order, OrderCallResponse, OrderCallResult, OrderEvent, OrderList, OrderStatus, OrderWithStatus, Problem, PublishResult, QuotePurpose, QuoteRequest, QuoteResponse, Refusal, RefusalCode, SalePrice, Shipment, ShipTo, } from "@nuanu-ai/agentify-contracts";
|
|
23
|
+
export type { CallError, Card, CardInput, Delivery, Fulfillment, HandlerAnswer, MerchantClosure, MerchantFinding, Money, Order, OrderCallResponse, OrderCallResult, OrderCloseRequest, OrderCloseResponse, OrderEvent, OrderList, OrderStatus, OrderWithStatus, PaymentStatus, Problem, PublishResult, QuotePurpose, QuoteRequest, QuoteResponse, Refusal, RefusalCode, SalePrice, Shipment, ShipTo, } from "@nuanu-ai/agentify-contracts";
|
|
24
24
|
export { CARD_REJECTED, MERCHANT_FINDINGS, ORDER_EVENT_TYPES, RECOMMENDED_REFUSAL_CODES, } from "@nuanu-ai/agentify-contracts";
|
|
25
25
|
export type { CardCheck } from "./check-card.js";
|
|
26
26
|
export { checkCard } from "./check-card.js";
|
package/dist/worker.d.ts
CHANGED
|
@@ -44,7 +44,8 @@ export interface Delivered {
|
|
|
44
44
|
* question to Agentify afterwards, since an event is answered against nothing.
|
|
45
45
|
* An event is sent at most once, so the risk to write against is an event that
|
|
46
46
|
* never arrives, not one that arrives twice; reading open orders with
|
|
47
|
-
* `orders.list({ open: true })` finds a
|
|
47
|
+
* `orders.list({ open: true })` finds a paid order whose `order.unfulfilled`
|
|
48
|
+
* went missing, including one paid after you closed it.
|
|
48
49
|
*/
|
|
49
50
|
export type EventHandler = (event: OrderEvent, delivered: Delivered) => void | Promise<void>;
|
|
50
51
|
/**
|
package/dist/worker.js
CHANGED
|
@@ -369,7 +369,7 @@ deadlineMs = POLL_DEADLINE_MS) => {
|
|
|
369
369
|
subject: event.order_id,
|
|
370
370
|
// An event is sent at most once and carries no acknowledgement, so
|
|
371
371
|
// nothing here can ask for it again: what the handler failed on is
|
|
372
|
-
// lost, and the open-orders list is where
|
|
372
|
+
// lost, and the open-orders list is where an unfulfilled order shows up.
|
|
373
373
|
message: `the event handler threw on ${event.type} for order ${event.order_id}; nothing here can ask for that event again, and whether it is sent again is not something this SDK knows: ${String(cause)}`,
|
|
374
374
|
});
|
|
375
375
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuanu-ai/agentify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Sell your existing products to AI agents: publish product cards to an x402 storefront and fill the paid orders from your own service.",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@nuanu-ai/agentify-contracts": "0.
|
|
39
|
+
"@nuanu-ai/agentify-contracts": "0.12.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"vitest": "^4.1.11",
|