@nuanu-ai/agentify-contracts 0.5.0 → 0.6.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/dist/api.js +1 -1
- package/dist/card.d.ts +6 -5
- package/dist/card.js +114 -11
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/plain-text.d.ts +60 -0
- package/dist/plain-text.js +191 -0
- package/dist/price-rule.d.ts +73 -0
- package/dist/price-rule.js +104 -0
- package/dist/primitives.d.ts +6 -6
- package/dist/primitives.js +6 -6
- package/dist/quote.js +3 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -897,7 +897,7 @@ export const API_ROUTES = Object.freeze({
|
|
|
897
897
|
method: "POST",
|
|
898
898
|
path: "/v0/quotes/:price_id/answer",
|
|
899
899
|
auth: "merchant_key",
|
|
900
|
-
description: "The price and availability for a question that came off the worker stream, against the price_id that question carried. The acknowledgement says whether the answer arrived in time to price the purchase; when it did not, stock held against the question can be released.",
|
|
900
|
+
description: "The price and availability for a question that came off the worker stream, against the price_id that question carried. The acknowledgement says whether the answer arrived in time to price the purchase; when it did not, stock held against the question can be released. A price that is zero, not in USD or USDC, or written with fewer than two or more than six digits after the dot is refused as malformed_body with the reason as its message, and the question stays open for a corrected answer.",
|
|
901
901
|
request: QuoteResponseSchema,
|
|
902
902
|
response: { document: QuoteAnswerAckSchema },
|
|
903
903
|
},
|
package/dist/card.d.ts
CHANGED
|
@@ -76,11 +76,12 @@ export declare const ServiceNameSchema: z.ZodString;
|
|
|
76
76
|
*/
|
|
77
77
|
export declare const TagsSchema: z.ZodArray<z.ZodString>;
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
79
|
+
* A card as a merchant publishes it.
|
|
80
|
+
*
|
|
81
|
+
* The plain-text rule runs whatever else is wrong with the card, so a merchant
|
|
82
|
+
* hears about their markup in the same answer as about their price. The rules
|
|
83
|
+
* that compare one field with another run only once the shape is right, as
|
|
84
|
+
* they always have: they need the fields to be what they claim to be.
|
|
84
85
|
*/
|
|
85
86
|
export declare const CardSchema: z.ZodObject<{
|
|
86
87
|
merchant_item_id: z.ZodString;
|
package/dist/card.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
import { ParamSpecSchema, paramSpecToValidator } from "./param-spec.js";
|
|
29
|
+
import { notPlainTextIn } from "./plain-text.js";
|
|
29
30
|
import { IdentifierSchema, MoneySchema, TimestampSchema } from "./primitives.js";
|
|
30
31
|
import { SellingStateSchema } from "./selling.js";
|
|
31
32
|
/**
|
|
@@ -348,8 +349,14 @@ const CardFieldsSchema = z.strictObject({
|
|
|
348
349
|
*
|
|
349
350
|
* Written as the two fields, or as the one string that becomes them. What is
|
|
350
351
|
* stored and what an agent reads are the two fields either way.
|
|
352
|
+
*
|
|
353
|
+
* Publishing holds it to the price rule in `price-rule.ts`, outside this
|
|
354
|
+
* schema so that a card stored before the rule stays readable; the export
|
|
355
|
+
* says so, or a generated client would first meet the rule as a refusal.
|
|
351
356
|
*/
|
|
352
|
-
price: CardPriceSchema
|
|
357
|
+
price: CardPriceSchema.meta({
|
|
358
|
+
description: 'Publishing refuses a price that is zero, not in USD or USDC, or written with fewer than two or more than six digits after the dot ("5.00", "0.001"), so that a price the catalog shows is one a payment can be taken at.',
|
|
359
|
+
}),
|
|
353
360
|
/** What the agent has to supply to buy. Absent when the purchase needs no input. */
|
|
354
361
|
params: writtenShort(ParamSpecSchema).optional(),
|
|
355
362
|
result: writtenShort(DeclaredResultSchema),
|
|
@@ -390,7 +397,7 @@ const CardFieldsSchema = z.strictObject({
|
|
|
390
397
|
* wait for a synchronous answer is our system-wide budget, the same for every
|
|
391
398
|
* product, so it has no field here at all.
|
|
392
399
|
*/
|
|
393
|
-
|
|
400
|
+
const cardRules = (card, ctx) => {
|
|
394
401
|
if (card.fulfillment === "confirm") {
|
|
395
402
|
// The gate, and the reason it is a refusal rather than a note somewhere.
|
|
396
403
|
// A confirmation request reaches the merchant before any money moves and
|
|
@@ -428,13 +435,106 @@ export const CardSchema = CardFieldsSchema.superRefine((card, ctx) => {
|
|
|
428
435
|
message: 'a synchronous card delivers inside the system-wide response budget and sets no delivery deadline; "async" and "confirm" do',
|
|
429
436
|
});
|
|
430
437
|
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
+
};
|
|
439
|
+
// JSON Schema has no way to say "this field only when that one has this
|
|
440
|
+
// value", and zod drops the rules above when it renders a document. Left at
|
|
441
|
+
// that, an engineer generating a client from the export would build one that
|
|
442
|
+
// sends deadlines a card cannot carry and only find out on the first publish.
|
|
443
|
+
// Saying it in words is weaker than checking it, and better than silence.
|
|
444
|
+
const CARD_RULES_IN_WORDS = 'A card cannot be published as fulfillment "confirm" during the pilot: the confirmation request has no shape on the wire yet, so a handler could not tell one from a paid order. confirm_deadline_seconds is only allowed when fulfillment is "confirm", and fulfill_deadline_seconds only when fulfillment is "async" or "confirm" — a synchronous card delivers inside the system-wide response budget and names no deadline of its own.';
|
|
445
|
+
/**
|
|
446
|
+
* The words on a card that an agent reads: the title, the description, and
|
|
447
|
+
* the title of each field the card declares.
|
|
448
|
+
*
|
|
449
|
+
* It reads whatever arrived rather than a card known to be whole, because the
|
|
450
|
+
* rule runs beside the shape checks and not after them — a merchant told about
|
|
451
|
+
* a price first and about their markup on the next attempt fixes one thing per
|
|
452
|
+
* publish. So anything that is not where a card keeps its words is passed over
|
|
453
|
+
* here and named by the shape check instead.
|
|
454
|
+
*/
|
|
455
|
+
const wordsOf = (card) => {
|
|
456
|
+
if (!isRecord(card))
|
|
457
|
+
return [];
|
|
458
|
+
const words = [];
|
|
459
|
+
if (typeof card.title === "string") {
|
|
460
|
+
words.push({ path: ["title"], text: card.title, called: "title", lines: "one line" });
|
|
461
|
+
}
|
|
462
|
+
if (typeof card.description === "string") {
|
|
463
|
+
words.push({
|
|
464
|
+
path: ["description"],
|
|
465
|
+
text: card.description,
|
|
466
|
+
called: "description",
|
|
467
|
+
lines: "several lines",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
for (const declaration of ["params", "result"]) {
|
|
471
|
+
const fields = card[declaration];
|
|
472
|
+
if (!isRecord(fields))
|
|
473
|
+
continue;
|
|
474
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
475
|
+
if (isRecord(field) && typeof field.title === "string") {
|
|
476
|
+
words.push({
|
|
477
|
+
path: [declaration, name, "title"],
|
|
478
|
+
text: field.title,
|
|
479
|
+
called: "title",
|
|
480
|
+
lines: "one line",
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return words;
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* The card's words are plain text, and a finding for each kind of thing in them
|
|
489
|
+
* that is not (see `plain-text.ts` for what counts and why the door refuses
|
|
490
|
+
* rather than cleans).
|
|
491
|
+
*
|
|
492
|
+
* Each finding names the field, what was found in it and where, and what the
|
|
493
|
+
* field is held to. The sentence is written for the merchant who has to find
|
|
494
|
+
* the characters in their own editor, which is why it quotes the fragment and
|
|
495
|
+
* counts the position rather than printing the rule.
|
|
496
|
+
*/
|
|
497
|
+
const plainWords = (card, ctx) => {
|
|
498
|
+
for (const words of wordsOf(card)) {
|
|
499
|
+
const heldTo = words.lines === "one line"
|
|
500
|
+
? `a ${words.called} is plain text on one line`
|
|
501
|
+
: `a ${words.called} is plain text`;
|
|
502
|
+
for (const phrase of notPlainTextIn(words.text, words.lines)) {
|
|
503
|
+
ctx.addIssue({
|
|
504
|
+
code: "custom",
|
|
505
|
+
path: [...words.path],
|
|
506
|
+
message: `this ${words.called} carries ${phrase}, and ${heldTo}, which an agent reads exactly as it is written`,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
const PLAIN_TEXT_IN_WORDS = "The title, the description and the title of every declared field are plain text, which an agent reads exactly as it is written: a card is refused where one of them carries HTML markup (a tag, or the opening of a comment), an HTML character reference such as & or ’, or a control character, and a description alone may carry line feeds. An ampersand, a comparison or an arrow written as text passes.";
|
|
512
|
+
/**
|
|
513
|
+
* The card as it is stored and as its merchant reads it back: every rule a
|
|
514
|
+
* publish holds it to except that its words are plain text.
|
|
515
|
+
*
|
|
516
|
+
* That one rule belongs to the door and not to the reader. A card stored
|
|
517
|
+
* before it may carry markup, and every answer that carries a stored card is
|
|
518
|
+
* held to its contract on the way out — so holding a stored card to the rule
|
|
519
|
+
* would fail the merchant's whole list, and the page of the catalog it sits
|
|
520
|
+
* on, over one old row, and the merchant could not even see the card they were
|
|
521
|
+
* meant to republish.
|
|
522
|
+
*/
|
|
523
|
+
const StoredCardSchema = CardFieldsSchema.superRefine(cardRules).meta({
|
|
524
|
+
description: `A product in the catalog, as it is stored and as its merchant reads it back. Three rules hold beyond the shape below. ${CARD_RULES_IN_WORDS} A publish also holds the title, the description and each declared field's title to plain text; reading a card back does not, so a card stored before that rule is read back exactly as it was stored.`,
|
|
525
|
+
});
|
|
526
|
+
/**
|
|
527
|
+
* A card as a merchant publishes it.
|
|
528
|
+
*
|
|
529
|
+
* The plain-text rule runs whatever else is wrong with the card, so a merchant
|
|
530
|
+
* hears about their markup in the same answer as about their price. The rules
|
|
531
|
+
* that compare one field with another run only once the shape is right, as
|
|
532
|
+
* they always have: they need the fields to be what they claim to be.
|
|
533
|
+
*/
|
|
534
|
+
export const CardSchema = CardFieldsSchema.superRefine(cardRules)
|
|
535
|
+
.superRefine(plainWords, { when: () => true })
|
|
536
|
+
.meta({
|
|
537
|
+
description: `A product in the catalog, as the merchant publishes it. Four rules are enforced beyond the shape below. ${CARD_RULES_IN_WORDS} ${PLAIN_TEXT_IN_WORDS} The shape below is the canonical card, the form every card is stored and read back in; three fields also take a shorter spelling at publication, which JSON Schema has no way to show alongside the canonical one. price may be written as one string, the amount and the currency code with a single space between them ("5.00 USD"). A field of params or result may be written as its type word alone (access_url: "string") where it carries no title and no required flag. fulfillment may be left out, and a card that leaves it out is "sync". Each of those is opened out into the form below as the card is accepted, so a card generated from this document is accepted unchanged and a card read back is always in this form.`,
|
|
438
538
|
});
|
|
439
539
|
/**
|
|
440
540
|
* The check an agent's purchase parameters are held to, for this card.
|
|
@@ -688,8 +788,11 @@ export const MerchantCardSchema = z
|
|
|
688
788
|
id: IdentifierSchema,
|
|
689
789
|
/** When this version of the card was published. */
|
|
690
790
|
as_of: TimestampSchema,
|
|
691
|
-
/**
|
|
692
|
-
|
|
791
|
+
/**
|
|
792
|
+
* The card exactly as its merchant published it, held to the rules of a
|
|
793
|
+
* stored card rather than to the publish door's.
|
|
794
|
+
*/
|
|
795
|
+
card: StoredCardSchema,
|
|
693
796
|
/** What a purchase of this card meets right now. */
|
|
694
797
|
selling: SellingStateSchema,
|
|
695
798
|
/** Whether the pause is this card's own, rather than the whole catalog's. */
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ export type { OrderStatus } from "./order-status.js";
|
|
|
41
41
|
export { ORDER_STATUSES, OrderStatusSchema } from "./order-status.js";
|
|
42
42
|
export type { FieldSpec, FieldSpecInput, ParamSpec, ParamSpecDirection, ParamSpecInput, ParamType, } from "./param-spec.js";
|
|
43
43
|
export { FieldSpecSchema, ParamNameSchema, ParamSpecSchema, ParamTypeSchema, PROTOTYPE_KEY_IS_DROPPED, paramSpecToValidator, } from "./param-spec.js";
|
|
44
|
+
export type { TextLines } from "./plain-text.js";
|
|
45
|
+
export { notPlainTextIn } from "./plain-text.js";
|
|
46
|
+
export { PAYABLE_CURRENCIES, PAYABLE_DECIMALS, priceProblemsOf } from "./price-rule.js";
|
|
44
47
|
export type { Amount, CurrencyCode, Identifier, Money, SalePrice, Timestamp, } from "./primitives.js";
|
|
45
48
|
export { AmountSchema, CurrencyCodeSchema, IdentifierSchema, MoneySchema, SalePriceSchema, TimestampSchema, } from "./primitives.js";
|
|
46
49
|
export type { QuotePurpose, QuoteRequest, QuoteResponse } from "./quote.js";
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,8 @@ export { CabinetKeySchema, DisabledKeySchema, ForgottenCabinetKeySchema, IssuedK
|
|
|
46
46
|
export { OrderSchema } from "./order.js";
|
|
47
47
|
export { ORDER_STATUSES, OrderStatusSchema } from "./order-status.js";
|
|
48
48
|
export { FieldSpecSchema, ParamNameSchema, ParamSpecSchema, ParamTypeSchema, PROTOTYPE_KEY_IS_DROPPED, paramSpecToValidator, } from "./param-spec.js";
|
|
49
|
+
export { notPlainTextIn } from "./plain-text.js";
|
|
50
|
+
export { PAYABLE_CURRENCIES, PAYABLE_DECIMALS, priceProblemsOf } from "./price-rule.js";
|
|
49
51
|
export { AmountSchema, CurrencyCodeSchema, IdentifierSchema, MoneySchema, SalePriceSchema, TimestampSchema, } from "./primitives.js";
|
|
50
52
|
export { QuotePurposeSchema, QuoteRequestSchema, QuoteResponseSchema } from "./quote.js";
|
|
51
53
|
export { ReceiptOutcomeSchema, ReceiptSchema } from "./receipt.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain text: what the words on a card are written in.
|
|
3
|
+
*
|
|
4
|
+
* A card's title, its description and the title of each field it declares are
|
|
5
|
+
* read by a buying program exactly as they were written. Nothing between the
|
|
6
|
+
* merchant and the agent renders HTML, so markup reaches the agent as markup —
|
|
7
|
+
* `<p>Valid for twelve months.</p>` is read with its angle brackets, and
|
|
8
|
+
* `Coffee & Brunch` with its number — and a discovery catalog that does
|
|
9
|
+
* render HTML would swallow some of it and show the rest. Either way the words
|
|
10
|
+
* an agent acts on are not the words the merchant meant.
|
|
11
|
+
*
|
|
12
|
+
* The door refuses such text rather than cleaning it, and that choice is the
|
|
13
|
+
* whole of this file. Cleaning would make an agent read text the merchant never
|
|
14
|
+
* wrote: stripping a tag can join two sentences, decoding a reference is a
|
|
15
|
+
* guess about which table it came from, and dropping an invisible character
|
|
16
|
+
* changes a string somebody may be matching on. And the merchant would never
|
|
17
|
+
* learn that their text was being rewritten, so the source stays broken for
|
|
18
|
+
* every other place it goes. A refusal that names what it found and where is
|
|
19
|
+
* the version of this a merchant can act on. A shop connector whose source is
|
|
20
|
+
* HTML turns it into text on its own side, before the door, and asks this same
|
|
21
|
+
* rule whether it succeeded.
|
|
22
|
+
*
|
|
23
|
+
* What is refused is what reads as markup or as a reference, and nothing that
|
|
24
|
+
* merely shares a character with them. An ampersand between words, a
|
|
25
|
+
* comparison and an arrow are text: `Tea & coffee`, `5 < 10`, `a -> b`, `AT&T`
|
|
26
|
+
* and `R&D;` all pass, and so does a space after a bracket, `List< String >`,
|
|
27
|
+
* which an HTML parser reads as text too.
|
|
28
|
+
*
|
|
29
|
+
* Three more shapes pass, and for them the rule is a choice rather than a fact
|
|
30
|
+
* about HTML: an address in angle brackets, `<jane@example.com>` or
|
|
31
|
+
* `<https://example.com>`, a bracket that is never closed, `x<y`, and a
|
|
32
|
+
* bracket before a name HTML would not give an element, `Map<String, Integer>`.
|
|
33
|
+
* A browser would swallow each of them as a tag, or drop it. They pass because
|
|
34
|
+
* each is how plain text has always been written, the rule is about text
|
|
35
|
+
* written as HTML rather than about everything a renderer might misread, and
|
|
36
|
+
* no reader between the merchant and an agent renders HTML.
|
|
37
|
+
*
|
|
38
|
+
* The rule is the publish door's and not the reader's. A card stored before it
|
|
39
|
+
* may carry anything it refuses, and every answer that carries a stored card is
|
|
40
|
+
* held to its contract on the way out — so a rule applied on reading would turn
|
|
41
|
+
* one old row into a failed catalog for every agent and a merchant who cannot
|
|
42
|
+
* see the card they are meant to fix.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Whether a piece of text is one line, as a title is, or may run to several, as
|
|
46
|
+
* a description may.
|
|
47
|
+
*/
|
|
48
|
+
export type TextLines = "one line" | "several lines";
|
|
49
|
+
/**
|
|
50
|
+
* What in this text is not plain text, one phrase for each kind of thing found
|
|
51
|
+
* — markup, character references, control characters — and nothing when all of
|
|
52
|
+
* it is.
|
|
53
|
+
*
|
|
54
|
+
* Each phrase says what was found, how often, and where the first of it is, as
|
|
55
|
+
* in `HTML markup in 4 places, the first "<p>" at character 1`, and quotes
|
|
56
|
+
* nothing it would have to print as a control character. It is the one
|
|
57
|
+
* definition of plain text: the publish door refuses a card with it, and a
|
|
58
|
+
* connector that turns a shop's HTML into text asks it whether it succeeded.
|
|
59
|
+
*/
|
|
60
|
+
export declare const notPlainTextIn: (text: string, lines: TextLines) => readonly string[];
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain text: what the words on a card are written in.
|
|
3
|
+
*
|
|
4
|
+
* A card's title, its description and the title of each field it declares are
|
|
5
|
+
* read by a buying program exactly as they were written. Nothing between the
|
|
6
|
+
* merchant and the agent renders HTML, so markup reaches the agent as markup —
|
|
7
|
+
* `<p>Valid for twelve months.</p>` is read with its angle brackets, and
|
|
8
|
+
* `Coffee & Brunch` with its number — and a discovery catalog that does
|
|
9
|
+
* render HTML would swallow some of it and show the rest. Either way the words
|
|
10
|
+
* an agent acts on are not the words the merchant meant.
|
|
11
|
+
*
|
|
12
|
+
* The door refuses such text rather than cleaning it, and that choice is the
|
|
13
|
+
* whole of this file. Cleaning would make an agent read text the merchant never
|
|
14
|
+
* wrote: stripping a tag can join two sentences, decoding a reference is a
|
|
15
|
+
* guess about which table it came from, and dropping an invisible character
|
|
16
|
+
* changes a string somebody may be matching on. And the merchant would never
|
|
17
|
+
* learn that their text was being rewritten, so the source stays broken for
|
|
18
|
+
* every other place it goes. A refusal that names what it found and where is
|
|
19
|
+
* the version of this a merchant can act on. A shop connector whose source is
|
|
20
|
+
* HTML turns it into text on its own side, before the door, and asks this same
|
|
21
|
+
* rule whether it succeeded.
|
|
22
|
+
*
|
|
23
|
+
* What is refused is what reads as markup or as a reference, and nothing that
|
|
24
|
+
* merely shares a character with them. An ampersand between words, a
|
|
25
|
+
* comparison and an arrow are text: `Tea & coffee`, `5 < 10`, `a -> b`, `AT&T`
|
|
26
|
+
* and `R&D;` all pass, and so does a space after a bracket, `List< String >`,
|
|
27
|
+
* which an HTML parser reads as text too.
|
|
28
|
+
*
|
|
29
|
+
* Three more shapes pass, and for them the rule is a choice rather than a fact
|
|
30
|
+
* about HTML: an address in angle brackets, `<jane@example.com>` or
|
|
31
|
+
* `<https://example.com>`, a bracket that is never closed, `x<y`, and a
|
|
32
|
+
* bracket before a name HTML would not give an element, `Map<String, Integer>`.
|
|
33
|
+
* A browser would swallow each of them as a tag, or drop it. They pass because
|
|
34
|
+
* each is how plain text has always been written, the rule is about text
|
|
35
|
+
* written as HTML rather than about everything a renderer might misread, and
|
|
36
|
+
* no reader between the merchant and an agent renders HTML.
|
|
37
|
+
*
|
|
38
|
+
* The rule is the publish door's and not the reader's. A card stored before it
|
|
39
|
+
* may carry anything it refuses, and every answer that carries a stored card is
|
|
40
|
+
* held to its contract on the way out — so a rule applied on reading would turn
|
|
41
|
+
* one old row into a failed catalog for every agent and a merchant who cannot
|
|
42
|
+
* see the card they are meant to fix.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Markup: an HTML comment's opening, or a tag.
|
|
46
|
+
*
|
|
47
|
+
* A comment is refused on its opening alone, because nobody writing prose
|
|
48
|
+
* writes `<!--`, and matching on to the close would cost a scan to the end of
|
|
49
|
+
* the text for every opening in it.
|
|
50
|
+
*
|
|
51
|
+
* A tag is an angle bracket, an optional slash, a name, and then the bracket's
|
|
52
|
+
* close — at once, after a slash, or after whitespace and whatever attributes
|
|
53
|
+
* follow. The name begins with a letter and carries letters and digits, joined
|
|
54
|
+
* by a colon or a hyphen where there is one, which is how `<o:p>`, the tag a
|
|
55
|
+
* word processor leaves in pasted text, and a custom element are written. An
|
|
56
|
+
* attribute value in quotation marks may carry either bracket, as the alt text
|
|
57
|
+
* a block editor writes into an image does: `<img alt="Mug <3 coffee">` is one
|
|
58
|
+
* tag. A quotation mark that opens no value — the apostrophe in
|
|
59
|
+
* `<img alt=Mom's>`, or `<your friend's name>` — does not hide its tag either:
|
|
60
|
+
* where the tag cannot be read with its quotation marks paired, it is read to
|
|
61
|
+
* the first closing bracket, as it was before quoted values were known. What
|
|
62
|
+
* the name does not take is the rest of what can follow a bracket —
|
|
63
|
+
* a digit, a space, an `@`, a `:/` — which is why `<3`, `List< String >` and
|
|
64
|
+
* `<https://example.com>` are not tags here. The close has to be there, so
|
|
65
|
+
* `x<y` is not one either.
|
|
66
|
+
*
|
|
67
|
+
* The scan is linear in the length of the text however it is written: outside
|
|
68
|
+
* quotation marks everything stops at the next angle bracket, a quoted value
|
|
69
|
+
* stops at its own closing mark, and a quotation mark can be read only one
|
|
70
|
+
* way at any point, so no stretch of text is read over and over from one
|
|
71
|
+
* bracket. The tests hold it at a quarter of a megabyte, which is more than a
|
|
72
|
+
* publish body may carry, and on the short inputs that turn exponential the
|
|
73
|
+
* moment a quotation mark may be read two ways.
|
|
74
|
+
*/
|
|
75
|
+
const MARKUP = /<!--|<\/?[A-Za-z][A-Za-z0-9]*(?:[:-][A-Za-z0-9]+)*(?:(?:[\t\n\f\r /](?:[^<>"']|"[^"]*"|'[^']*')*)?>|[\t\n\f\r /][^<>]*>)/g;
|
|
76
|
+
/**
|
|
77
|
+
* A character reference: an ampersand, a number or a name, and a semicolon.
|
|
78
|
+
*
|
|
79
|
+
* The semicolon, and a name of at least two characters, are what separate a
|
|
80
|
+
* reference from an ampersand in prose. HTML has no name of one letter, so
|
|
81
|
+
* `AT&T`, `A&B` and `R&D;` are text. A name of two or more is refused whether
|
|
82
|
+
* or not HTML's own list carries it: `é` and `&foo;` are both text
|
|
83
|
+
* written for an HTML reader, and a merchant who meant a character writes the
|
|
84
|
+
* character.
|
|
85
|
+
*/
|
|
86
|
+
const REFERENCE = /&(?:#[0-9]+|#[xX][0-9A-Fa-f]+|[A-Za-z][A-Za-z0-9]+);/g;
|
|
87
|
+
/**
|
|
88
|
+
* Control characters: C0, DEL and C1. A description may carry a line feed,
|
|
89
|
+
* U+000A, and nothing else from this range.
|
|
90
|
+
*
|
|
91
|
+
* A line feed is the one line break every reader agrees on, and a description
|
|
92
|
+
* is prose of up to five hundred characters that may run to paragraphs. A
|
|
93
|
+
* carriage return is refused there with the rest, because `\r\n` is a second
|
|
94
|
+
* spelling of the same break and a carriage return on its own sends a terminal
|
|
95
|
+
* back to the start of the line it is printing. A title and a field's title are
|
|
96
|
+
* one line, so they carry no line break at all. The rest of the range shows
|
|
97
|
+
* nothing, and a character that shows nothing makes two strings that look
|
|
98
|
+
* identical and are not.
|
|
99
|
+
*/
|
|
100
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are what it refuses
|
|
101
|
+
const CONTROL_IN_ONE_LINE = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
102
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are what it refuses
|
|
103
|
+
const CONTROL_IN_LINES = /[\u0000-\u0009\u000B-\u001F\u007F-\u009F]/g;
|
|
104
|
+
/**
|
|
105
|
+
* The names a merchant knows the commonest control characters by, in text of
|
|
106
|
+
* each shape.
|
|
107
|
+
*
|
|
108
|
+
* A carriage return in a description is nearly always half of the `\r\n` a
|
|
109
|
+
* form on Windows submits between two lines, so there it is named together
|
|
110
|
+
* with the one line break a description does take.
|
|
111
|
+
*/
|
|
112
|
+
const CONTROL_NAMES = {
|
|
113
|
+
"one line": { "\t": "a tab", "\n": "a line break", "\r": "a carriage return" },
|
|
114
|
+
"several lines": {
|
|
115
|
+
"\t": "a tab",
|
|
116
|
+
"\r": "a carriage return; a description breaks its lines with a line feed, U+000A, alone",
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The longest piece of the text quoted back in a finding.
|
|
121
|
+
*
|
|
122
|
+
* A tag can carry an address of any length, and a finding is a line a person
|
|
123
|
+
* reads; forty characters is enough to recognise the tag by. Where one is cut,
|
|
124
|
+
* the finding says so.
|
|
125
|
+
*/
|
|
126
|
+
const QUOTED_AT_MOST = 40;
|
|
127
|
+
/** A character by its code point, as `U+0007`. */
|
|
128
|
+
const codeOf = (character) => `U+${(character.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
129
|
+
/**
|
|
130
|
+
* DEL and C1, which a quotation leaves as they are.
|
|
131
|
+
*
|
|
132
|
+
* `JSON.stringify` escapes C0 and a lone surrogate and nothing else, so a
|
|
133
|
+
* control character inside a quoted tag would otherwise reach the merchant's
|
|
134
|
+
* terminal as itself — and U+009B is the start of an escape sequence there.
|
|
135
|
+
*/
|
|
136
|
+
const UNESCAPED_BY_JSON = /[\u007F-\u009F]/g;
|
|
137
|
+
/**
|
|
138
|
+
* A piece of the text as a finding quotes it: in quotation marks, with every
|
|
139
|
+
* control character escaped, and cut where it is long.
|
|
140
|
+
*
|
|
141
|
+
* The cut is made between two characters and never inside one: a character
|
|
142
|
+
* outside the basic plane is two units long, and a cut through the middle of
|
|
143
|
+
* it would quote half a character as an escape nobody wrote.
|
|
144
|
+
*/
|
|
145
|
+
const quoted = (fragment) => {
|
|
146
|
+
const whole = fragment.length <= QUOTED_AT_MOST;
|
|
147
|
+
const head = whole ? fragment : fragment.slice(0, QUOTED_AT_MOST).replace(/[\uD800-\uDBFF]$/, "");
|
|
148
|
+
const shown = JSON.stringify(head).replace(UNESCAPED_BY_JSON, (character) => `\\u${(character.codePointAt(0) ?? 0).toString(16).padStart(4, "0")}`);
|
|
149
|
+
return whole ? shown : `${shown}… (cut short)`;
|
|
150
|
+
};
|
|
151
|
+
/** A control character as a finding names it: by its code, never printed. */
|
|
152
|
+
const namedIn = (lines) => (character) => {
|
|
153
|
+
const name = CONTROL_NAMES[lines][character];
|
|
154
|
+
return name === undefined ? codeOf(character) : `${codeOf(character)} (${name})`;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* One kind of thing that is not plain text, said once for all the places it
|
|
158
|
+
* occurs: how many there are, and the first of them, where it is.
|
|
159
|
+
*
|
|
160
|
+
* The position counts from one, in the same units a description's length is
|
|
161
|
+
* counted in: UTF-16 code units, so a character outside the basic plane — an
|
|
162
|
+
* emoji — counts as two, and the number can run ahead of what an editor shows
|
|
163
|
+
* by one for each such character before it. It is the same count in both
|
|
164
|
+
* places, which is what lets a merchant set one number against the other.
|
|
165
|
+
*/
|
|
166
|
+
const found = (text, pattern, one, many, shown) => {
|
|
167
|
+
const matches = [...text.matchAll(pattern)];
|
|
168
|
+
const first = matches[0];
|
|
169
|
+
if (first === undefined)
|
|
170
|
+
return null;
|
|
171
|
+
const where = `${shown(first[0])} at character ${first.index + 1}`;
|
|
172
|
+
return matches.length === 1
|
|
173
|
+
? `${one}, ${where}`
|
|
174
|
+
: `${many} in ${matches.length} places, the first ${where}`;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* What in this text is not plain text, one phrase for each kind of thing found
|
|
178
|
+
* — markup, character references, control characters — and nothing when all of
|
|
179
|
+
* it is.
|
|
180
|
+
*
|
|
181
|
+
* Each phrase says what was found, how often, and where the first of it is, as
|
|
182
|
+
* in `HTML markup in 4 places, the first "<p>" at character 1`, and quotes
|
|
183
|
+
* nothing it would have to print as a control character. It is the one
|
|
184
|
+
* definition of plain text: the publish door refuses a card with it, and a
|
|
185
|
+
* connector that turns a shop's HTML into text asks it whether it succeeded.
|
|
186
|
+
*/
|
|
187
|
+
export const notPlainTextIn = (text, lines) => [
|
|
188
|
+
found(text, MARKUP, "HTML markup", "HTML markup", quoted),
|
|
189
|
+
found(text, REFERENCE, "an HTML character reference", "HTML character references", quoted),
|
|
190
|
+
found(text, lines === "one line" ? CONTROL_IN_ONE_LINE : CONTROL_IN_LINES, "a control character", "control characters", namedIn(lines)),
|
|
191
|
+
].filter((phrase) => phrase !== null);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a price a merchant sets has to be before Agentify will sell at it.
|
|
3
|
+
*
|
|
4
|
+
* A price comes in by two doors: on a card, when it is published, and in the
|
|
5
|
+
* answer to a price question, when a purchase is priced live. The gateway holds
|
|
6
|
+
* both to the rule below, and the merchant SDK's own check of a card holds it
|
|
7
|
+
* to the same rule, so a card the check passes is not refused at publication
|
|
8
|
+
* for its price. Three parts, each standing for a payment that cannot be
|
|
9
|
+
* taken.
|
|
10
|
+
*
|
|
11
|
+
* A price of zero is refused, by design. A payment request for nothing asks
|
|
12
|
+
* for something that cannot be done, and a gateway that took free items would
|
|
13
|
+
* be hosting content rather than selling it. A merchant gives a free item away
|
|
14
|
+
* from their own site, with no payment request in front of it (ADR-0002 §2).
|
|
15
|
+
*
|
|
16
|
+
* A currency other than the dollar is refused, because there is no exchange
|
|
17
|
+
* rate anywhere in this system to charge it at.
|
|
18
|
+
*
|
|
19
|
+
* An amount is written in dollars with at least two digits after the dot, so
|
|
20
|
+
* that a merchant who counts in cents and writes "500" for five dollars is
|
|
21
|
+
* refused rather than listed at five hundred. Fractions of a cent stay allowed,
|
|
22
|
+
* because a price per call is often below one, down to the finest amount the
|
|
23
|
+
* token a buyer pays in can carry and no further: past that there is no exact
|
|
24
|
+
* amount to charge, and a rounded charge is a different charge.
|
|
25
|
+
*
|
|
26
|
+
* So a price the door took is a price the payment edge can charge. The edge
|
|
27
|
+
* reads the currencies from here, and the places its token carries on every
|
|
28
|
+
* chain it can charge on are held equal to `PAYABLE_DECIMALS` by a test beside
|
|
29
|
+
* it, so the door and the edge cannot become two answers.
|
|
30
|
+
*
|
|
31
|
+
* It is a rule applied at the doors rather than part of any schema, and that is
|
|
32
|
+
* the point of it being a function. The schemas also read back every document
|
|
33
|
+
* already written — every answer that carries a card is held to the contract
|
|
34
|
+
* on its way out — so a rule written into the card's schema would turn the
|
|
35
|
+
* whole list of a merchant who published such a price before the rule existed
|
|
36
|
+
* into a failure, for cards they could otherwise see, pause and replace.
|
|
37
|
+
*/
|
|
38
|
+
import type { Money } from "./primitives.js";
|
|
39
|
+
import type { Problem } from "./results.js";
|
|
40
|
+
/**
|
|
41
|
+
* The currencies a price may be written in, and the one conversion Agentify
|
|
42
|
+
* makes.
|
|
43
|
+
*
|
|
44
|
+
* A card priced in dollars is charged in the network's own dollar-denominated
|
|
45
|
+
* asset, one for one. That is a decision and not the absence of one, so it is
|
|
46
|
+
* written here rather than left to be inferred from the fact that it works: a
|
|
47
|
+
* merchant who writes "USD" is charging their buyer USDC on the configured
|
|
48
|
+
* chain, and the two are held to be the same number of dollars.
|
|
49
|
+
*
|
|
50
|
+
* Everything else is refused. Nobody has decided where an exchange rate would
|
|
51
|
+
* come from, and a charge based on an invented one would be the clearest
|
|
52
|
+
* possible claim beyond the evidence.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PAYABLE_CURRENCIES: readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* How many places after the dot a price may carry: the places of USDC, which is
|
|
57
|
+
* what a buyer pays in. Its smallest unit is a millionth of a dollar, so
|
|
58
|
+
* "0.000001" is the finest price there is an exact charge for.
|
|
59
|
+
*/
|
|
60
|
+
export declare const PAYABLE_DECIMALS = 6;
|
|
61
|
+
/**
|
|
62
|
+
* What stands between this price and a sale, as findings on the fields of the
|
|
63
|
+
* document that carried it — empty where nothing does.
|
|
64
|
+
*
|
|
65
|
+
* The paths name `price` because a card and a price answer both carry the
|
|
66
|
+
* price under that name, so one finding reads right in either. An amount gets
|
|
67
|
+
* at most one finding: zero is said before the places, because "0" written as
|
|
68
|
+
* "0.00" would only be refused again.
|
|
69
|
+
*
|
|
70
|
+
* The amount has already passed `AmountSchema`, so it is digits with at most
|
|
71
|
+
* one dot, and the places are what follows the dot.
|
|
72
|
+
*/
|
|
73
|
+
export declare function priceProblemsOf(price: Money): Problem[];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a price a merchant sets has to be before Agentify will sell at it.
|
|
3
|
+
*
|
|
4
|
+
* A price comes in by two doors: on a card, when it is published, and in the
|
|
5
|
+
* answer to a price question, when a purchase is priced live. The gateway holds
|
|
6
|
+
* both to the rule below, and the merchant SDK's own check of a card holds it
|
|
7
|
+
* to the same rule, so a card the check passes is not refused at publication
|
|
8
|
+
* for its price. Three parts, each standing for a payment that cannot be
|
|
9
|
+
* taken.
|
|
10
|
+
*
|
|
11
|
+
* A price of zero is refused, by design. A payment request for nothing asks
|
|
12
|
+
* for something that cannot be done, and a gateway that took free items would
|
|
13
|
+
* be hosting content rather than selling it. A merchant gives a free item away
|
|
14
|
+
* from their own site, with no payment request in front of it (ADR-0002 §2).
|
|
15
|
+
*
|
|
16
|
+
* A currency other than the dollar is refused, because there is no exchange
|
|
17
|
+
* rate anywhere in this system to charge it at.
|
|
18
|
+
*
|
|
19
|
+
* An amount is written in dollars with at least two digits after the dot, so
|
|
20
|
+
* that a merchant who counts in cents and writes "500" for five dollars is
|
|
21
|
+
* refused rather than listed at five hundred. Fractions of a cent stay allowed,
|
|
22
|
+
* because a price per call is often below one, down to the finest amount the
|
|
23
|
+
* token a buyer pays in can carry and no further: past that there is no exact
|
|
24
|
+
* amount to charge, and a rounded charge is a different charge.
|
|
25
|
+
*
|
|
26
|
+
* So a price the door took is a price the payment edge can charge. The edge
|
|
27
|
+
* reads the currencies from here, and the places its token carries on every
|
|
28
|
+
* chain it can charge on are held equal to `PAYABLE_DECIMALS` by a test beside
|
|
29
|
+
* it, so the door and the edge cannot become two answers.
|
|
30
|
+
*
|
|
31
|
+
* It is a rule applied at the doors rather than part of any schema, and that is
|
|
32
|
+
* the point of it being a function. The schemas also read back every document
|
|
33
|
+
* already written — every answer that carries a card is held to the contract
|
|
34
|
+
* on its way out — so a rule written into the card's schema would turn the
|
|
35
|
+
* whole list of a merchant who published such a price before the rule existed
|
|
36
|
+
* into a failure, for cards they could otherwise see, pause and replace.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* The currencies a price may be written in, and the one conversion Agentify
|
|
40
|
+
* makes.
|
|
41
|
+
*
|
|
42
|
+
* A card priced in dollars is charged in the network's own dollar-denominated
|
|
43
|
+
* asset, one for one. That is a decision and not the absence of one, so it is
|
|
44
|
+
* written here rather than left to be inferred from the fact that it works: a
|
|
45
|
+
* merchant who writes "USD" is charging their buyer USDC on the configured
|
|
46
|
+
* chain, and the two are held to be the same number of dollars.
|
|
47
|
+
*
|
|
48
|
+
* Everything else is refused. Nobody has decided where an exchange rate would
|
|
49
|
+
* come from, and a charge based on an invented one would be the clearest
|
|
50
|
+
* possible claim beyond the evidence.
|
|
51
|
+
*/
|
|
52
|
+
export const PAYABLE_CURRENCIES = Object.freeze(["USD", "USDC"]);
|
|
53
|
+
/**
|
|
54
|
+
* How many places after the dot a price may carry: the places of USDC, which is
|
|
55
|
+
* what a buyer pays in. Its smallest unit is a millionth of a dollar, so
|
|
56
|
+
* "0.000001" is the finest price there is an exact charge for.
|
|
57
|
+
*/
|
|
58
|
+
export const PAYABLE_DECIMALS = 6;
|
|
59
|
+
/**
|
|
60
|
+
* What stands between this price and a sale, as findings on the fields of the
|
|
61
|
+
* document that carried it — empty where nothing does.
|
|
62
|
+
*
|
|
63
|
+
* The paths name `price` because a card and a price answer both carry the
|
|
64
|
+
* price under that name, so one finding reads right in either. An amount gets
|
|
65
|
+
* at most one finding: zero is said before the places, because "0" written as
|
|
66
|
+
* "0.00" would only be refused again.
|
|
67
|
+
*
|
|
68
|
+
* The amount has already passed `AmountSchema`, so it is digits with at most
|
|
69
|
+
* one dot, and the places are what follows the dot.
|
|
70
|
+
*/
|
|
71
|
+
export function priceProblemsOf(price) {
|
|
72
|
+
const problems = [];
|
|
73
|
+
const written = JSON.stringify(price.amount);
|
|
74
|
+
const places = price.amount.split(".")[1]?.length ?? 0;
|
|
75
|
+
if (!/[1-9]/.test(price.amount)) {
|
|
76
|
+
problems.push({
|
|
77
|
+
path: ["price", "amount"],
|
|
78
|
+
code: "custom",
|
|
79
|
+
message: `the price is ${written}, and nothing is sold through Agentify at a price of zero: a free item is offered from your own site, without a payment`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else if (places < 2) {
|
|
83
|
+
problems.push({
|
|
84
|
+
path: ["price", "amount"],
|
|
85
|
+
code: "custom",
|
|
86
|
+
message: `the price is ${written}, and a price is written in dollars with at least two digits after the dot — five dollars is "5.00" — so that an amount counted in cents is never charged as dollars`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (places > PAYABLE_DECIMALS) {
|
|
90
|
+
problems.push({
|
|
91
|
+
path: ["price", "amount"],
|
|
92
|
+
code: "custom",
|
|
93
|
+
message: `the price is ${written}, and a price carries at most ${PAYABLE_DECIMALS} digits after the dot, the finest amount USDC can be paid in, so there is no exact amount to charge for it`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (!PAYABLE_CURRENCIES.includes(price.currency)) {
|
|
97
|
+
problems.push({
|
|
98
|
+
path: ["price", "currency"],
|
|
99
|
+
code: "custom",
|
|
100
|
+
message: `the price is in ${JSON.stringify(price.currency)}, which Agentify cannot charge: it takes USD, paid as USDC one for one, or USDC itself, and holds no exchange rate to anything else`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return problems;
|
|
104
|
+
}
|
package/dist/primitives.d.ts
CHANGED
|
@@ -39,13 +39,13 @@ export declare const AmountSchema: z.ZodString;
|
|
|
39
39
|
* does not yet say which of the two it carries, so the shape has to admit
|
|
40
40
|
* both: a national code is three letters (`USD`), and a token ticker is longer
|
|
41
41
|
* and sometimes carries a digit (`USDC`, `USD1`). Eight admits every ticker
|
|
42
|
-
* this contract has had to carry
|
|
43
|
-
* gateway's to decide.
|
|
42
|
+
* this contract has had to carry.
|
|
44
43
|
*
|
|
45
|
-
* What this does not do is check membership in any list.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* What this does not do is check membership in any list. Which currencies a
|
|
45
|
+
* merchant may set a price in is answered once, by the price rule in
|
|
46
|
+
* `price-rule.ts`, at the moment a price comes in. This shape also reads back
|
|
47
|
+
* every document already written, and a list here would make one written
|
|
48
|
+
* before the list changed unreadable.
|
|
49
49
|
*/
|
|
50
50
|
export declare const CurrencyCodeSchema: z.ZodString;
|
|
51
51
|
/** A sum of money: how much, in what. */
|
package/dist/primitives.js
CHANGED
|
@@ -41,13 +41,13 @@ export const AmountSchema = z
|
|
|
41
41
|
* does not yet say which of the two it carries, so the shape has to admit
|
|
42
42
|
* both: a national code is three letters (`USD`), and a token ticker is longer
|
|
43
43
|
* and sometimes carries a digit (`USDC`, `USD1`). Eight admits every ticker
|
|
44
|
-
* this contract has had to carry
|
|
45
|
-
* gateway's to decide.
|
|
44
|
+
* this contract has had to carry.
|
|
46
45
|
*
|
|
47
|
-
* What this does not do is check membership in any list.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* What this does not do is check membership in any list. Which currencies a
|
|
47
|
+
* merchant may set a price in is answered once, by the price rule in
|
|
48
|
+
* `price-rule.ts`, at the moment a price comes in. This shape also reads back
|
|
49
|
+
* every document already written, and a list here would make one written
|
|
50
|
+
* before the list changed unreadable.
|
|
51
51
|
*/
|
|
52
52
|
export const CurrencyCodeSchema = z
|
|
53
53
|
.string()
|
package/dist/quote.js
CHANGED
|
@@ -82,7 +82,9 @@ export const QuoteRequestSchema = z.strictObject({
|
|
|
82
82
|
export const QuoteResponseSchema = z.discriminatedUnion("available", [
|
|
83
83
|
z.strictObject({
|
|
84
84
|
available: z.literal(true),
|
|
85
|
-
price: MoneySchema
|
|
85
|
+
price: MoneySchema.meta({
|
|
86
|
+
description: 'Held to the rule a card\'s price meets at publication: above zero, in USD or USDC, with two to six digits after the dot ("5.00", "0.001"); an answer that breaks it is refused and prices nothing.',
|
|
87
|
+
}),
|
|
86
88
|
as_of: TimestampSchema,
|
|
87
89
|
}),
|
|
88
90
|
z.strictObject({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuanu-ai/agentify-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Agentify wire contract as code: the schemas for cards, orders, price checks and receipts that the gateway and the merchant SDK both read.",
|
|
6
6
|
"keywords": [
|