@forgeintel/sdk 0.4.0-beta.0 → 0.5.0-alpha.feedback3.b13
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 +36 -17
- package/dist/ask.d.ts +2 -0
- package/dist/ask.js +8 -6
- package/dist/bazaar.d.ts +7 -0
- package/dist/bazaar.js +58 -0
- package/dist/client-signals.d.ts +4 -0
- package/dist/client-signals.js +45 -0
- package/dist/context.d.ts +34 -22
- package/dist/context.js +67 -27
- package/dist/core.d.ts +20 -9
- package/dist/core.js +186 -71
- package/dist/express.js +37 -6
- package/dist/fetch.d.ts +3 -1
- package/dist/fetch.js +77 -8
- package/dist/hono.js +1 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/next.js +4 -0
- package/dist/openapi.d.ts +7 -4
- package/dist/openapi.js +53 -22
- package/dist/reporter.d.ts +12 -1
- package/dist/x402.d.ts +34 -6
- package/dist/x402.js +73 -17
- package/package.json +4 -8
package/dist/x402.js
CHANGED
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
import { ASK } from "./ask.js";
|
|
2
|
-
import {
|
|
2
|
+
import { addContextToBazaar } from "./bazaar.js";
|
|
3
|
+
import { ISSUES, PROTOCOL } from "./values.js";
|
|
3
4
|
/** Key of the Forge extension in x402 v2 `extensions`: in the 402 challenge (next to e.g. `bazaar`) and in the payment receipt. */
|
|
4
5
|
export const FEEDBACK_EXTENSION = "forge-feedback";
|
|
6
|
+
/** Key of the object the SDK adds to paid JSON response bodies: feedback_id, feedback_url, and optionally rate_this_call. */
|
|
7
|
+
export const FEEDBACK_FIELD = "forge_feedback";
|
|
8
|
+
const originOf = (url) => {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(String(url));
|
|
11
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed.origin : undefined;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The service origin a challenge was issued for: v2 `resource.url` (header or body), or v1 `accepts[].resource`.
|
|
19
|
+
* Used for absolute rating links when no origin is configured or registered. Never throws.
|
|
20
|
+
*/
|
|
21
|
+
export function challengeOrigin(challenge) {
|
|
22
|
+
try {
|
|
23
|
+
const value = typeof challenge === "string" ? JSON.parse(Buffer.from(challenge, "base64").toString("utf8")) : challenge;
|
|
24
|
+
const resource = value?.resource?.url ?? (Array.isArray(value?.accepts) ? value.accepts[0]?.resource : undefined);
|
|
25
|
+
return originOf(resource);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** The service origin a payment was made for (x402 v2 `resource.url` in the payment header). Never throws. */
|
|
32
|
+
export function paymentOrigin(headerValue) {
|
|
33
|
+
try {
|
|
34
|
+
return originOf(JSON.parse(Buffer.from(headerValue, "base64").toString("utf8"))?.resource?.url);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
5
40
|
/**
|
|
6
41
|
* The `forge-feedback` challenge extension: how to rate the call, as structured data. Clients that inspect
|
|
7
42
|
* the 402 (e.g. `awal x402 details`) print every extension, so this reaches agents before they pay.
|
|
8
|
-
*
|
|
43
|
+
* Agent context is a separate switch with its own extension (`forge-agent-context`).
|
|
9
44
|
*/
|
|
10
|
-
export function feedbackExtension(rateUrl, tone = "soft"
|
|
45
|
+
export function feedbackExtension(rateUrl, tone = "soft") {
|
|
11
46
|
return {
|
|
12
47
|
info: {
|
|
13
48
|
protocol: PROTOCOL,
|
|
@@ -15,9 +50,9 @@ export function feedbackExtension(rateUrl, tone = "soft", agentContext) {
|
|
|
15
50
|
...ASK[tone].extensionExtras,
|
|
16
51
|
rate: `GET ${rateUrl}?feedback_id=FEEDBACK_ID&outcome=fully`,
|
|
17
52
|
outcome: ["fully", "partially", "no"],
|
|
18
|
-
|
|
53
|
+
issue: { when: "The outcome is not fully. Tells other agents what went wrong.", values: Object.keys(ISSUES) },
|
|
54
|
+
feedback_id: `In the paid response body (${FEEDBACK_FIELD}.feedback_id) and the Forge-Feedback-Id header.`,
|
|
19
55
|
payment: "None. Plain GET, not an x402 endpoint.",
|
|
20
|
-
...(agentContext ? { agent_context: agentContext } : {}),
|
|
21
56
|
},
|
|
22
57
|
};
|
|
23
58
|
}
|
|
@@ -30,6 +65,7 @@ export function receiptExtension(rateUrl, feedbackId, tone = "soft") {
|
|
|
30
65
|
feedback_id: feedbackId,
|
|
31
66
|
rate: `GET ${rateUrl}?feedback_id=${feedbackId}&outcome=fully`,
|
|
32
67
|
outcome: ["fully", "partially", "no"],
|
|
68
|
+
issue: { when: "The outcome is not fully. Tells other agents what went wrong.", values: Object.keys(ISSUES) },
|
|
33
69
|
payment: "None. Plain GET, not an x402 endpoint.",
|
|
34
70
|
},
|
|
35
71
|
};
|
|
@@ -55,39 +91,59 @@ export function describeReceipt(headerValue, extension) {
|
|
|
55
91
|
receipt.extensions = { ...extensions, [FEEDBACK_EXTENSION]: extension };
|
|
56
92
|
return Buffer.from(JSON.stringify(receipt), "utf8").toString("base64");
|
|
57
93
|
}
|
|
58
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The longest challenge description the SDK produces. Clients copy the description into the payment payload,
|
|
96
|
+
* and the CDP facilitator rejects a payload whose resource.description exceeds 500 characters, failing the payment.
|
|
97
|
+
*/
|
|
98
|
+
export const MAX_DESCRIPTION = 500;
|
|
99
|
+
function appendSentence(description, sentence, marker, shortSentence) {
|
|
59
100
|
const current = typeof description === "string" ? description.trim() : "";
|
|
60
|
-
|
|
101
|
+
if (current.includes(marker))
|
|
102
|
+
return current;
|
|
103
|
+
for (const candidate of [sentence, shortSentence]) {
|
|
104
|
+
if (!candidate)
|
|
105
|
+
continue;
|
|
106
|
+
const next = current ? `${current} ${candidate}` : candidate;
|
|
107
|
+
if (next.length <= MAX_DESCRIPTION)
|
|
108
|
+
return next;
|
|
109
|
+
}
|
|
110
|
+
// Nothing fits: the forge-feedback extension still carries the ask.
|
|
111
|
+
return current;
|
|
61
112
|
}
|
|
62
113
|
/** Apply additions to a v2 PaymentRequired in place. Returns whether anything changed. */
|
|
63
114
|
function addToV2(challenge, add) {
|
|
64
115
|
let changed = false;
|
|
65
116
|
const resource = challenge.resource;
|
|
66
117
|
if (add.sentence && resource && typeof resource === "object") {
|
|
67
|
-
const next = appendSentence(resource.description, add.sentence, add.marker ?? add.sentence);
|
|
118
|
+
const next = appendSentence(resource.description, add.sentence, add.marker ?? add.sentence, add.shortSentence);
|
|
68
119
|
if (next !== resource.description) {
|
|
69
120
|
resource.description = next;
|
|
70
121
|
changed = true;
|
|
71
122
|
}
|
|
72
123
|
}
|
|
73
|
-
|
|
124
|
+
for (const [key, extension] of [[FEEDBACK_EXTENSION, add.extension], ["forge-agent-context", add.contextExtension]]) {
|
|
125
|
+
if (!extension)
|
|
126
|
+
continue;
|
|
74
127
|
const extensions = challenge.extensions;
|
|
75
128
|
if (extensions === undefined || extensions === null) {
|
|
76
|
-
challenge.extensions = { [
|
|
129
|
+
challenge.extensions = { [key]: extension };
|
|
77
130
|
changed = true;
|
|
78
131
|
}
|
|
79
|
-
else if (typeof extensions === "object" && !Array.isArray(extensions) && !(
|
|
80
|
-
// Added last, so the merchant's own extensions (e.g. bazaar) keep their order
|
|
81
|
-
challenge.extensions = { ...extensions, [
|
|
132
|
+
else if (typeof extensions === "object" && !Array.isArray(extensions) && !(key in extensions)) {
|
|
133
|
+
// Added last, so the merchant's own extensions (e.g. bazaar) keep their order.
|
|
134
|
+
challenge.extensions = { ...extensions, [key]: extension };
|
|
82
135
|
changed = true;
|
|
83
136
|
}
|
|
84
137
|
}
|
|
138
|
+
const extensions = challenge.extensions;
|
|
139
|
+
if (add.bazaarContext && extensions && typeof extensions === "object" && addContextToBazaar(extensions.bazaar, add.bazaarContext))
|
|
140
|
+
changed = true;
|
|
85
141
|
return changed;
|
|
86
142
|
}
|
|
87
143
|
/**
|
|
88
|
-
* Add the rating sentence and
|
|
89
|
-
* `accepts` is untouched: v2 matches payments on `accepts
|
|
90
|
-
* the server
|
|
144
|
+
* Add the rating sentence, Forge's extensions and Bazaar agent context to a base64 PAYMENT-REQUIRED header (x402 v2).
|
|
145
|
+
* `accepts` is untouched: v2 matches payments on `accepts`. @x402/core checks that each echoed extension's `info`
|
|
146
|
+
* contains what the server advertised, so added extensions and added Bazaar fields don't affect payment.
|
|
91
147
|
* Returns undefined when the header can't be parsed or already has everything.
|
|
92
148
|
*/
|
|
93
149
|
export function describeChallenge(headerValue, additions) {
|
|
@@ -120,7 +176,7 @@ export function describeChallengeBody(body, additions) {
|
|
|
120
176
|
const marker = additions.marker ?? sentence;
|
|
121
177
|
return {
|
|
122
178
|
...b,
|
|
123
|
-
accepts: b.accepts.map((a) => a && typeof a === "object" ? { ...a, description: appendSentence(a.description, sentence, marker) } : a),
|
|
179
|
+
accepts: b.accepts.map((a) => a && typeof a === "object" ? { ...a, description: appendSentence(a.description, sentence, marker, additions.shortSentence) } : a),
|
|
124
180
|
};
|
|
125
181
|
}
|
|
126
182
|
if (b.x402Version === 2 && b.resource && typeof b.resource === "object") {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeintel/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The Forge SDK for x402 paid APIs: agent feedback
|
|
3
|
+
"version": "0.5.0-alpha.feedback3.b13",
|
|
4
|
+
"description": "The Forge SDK for x402 paid APIs: agent feedback, agent context, OpenAPI and challenge enrichment, and passive call signals. Express, Hono, Next.js and fetch handlers. No Forge network request on the merchant response path.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -61,11 +61,7 @@
|
|
|
61
61
|
"cloudflare-workers",
|
|
62
62
|
"bun"
|
|
63
63
|
],
|
|
64
|
-
"
|
|
65
|
-
"type": "git",
|
|
66
|
-
"url": "git+https://github.com/ClawCash/forge-feedback.git",
|
|
67
|
-
"directory": "packages/sdk"
|
|
68
|
-
},
|
|
64
|
+
"homepage": "https://docs.forgeintel.co",
|
|
69
65
|
"scripts": {
|
|
70
66
|
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
71
67
|
"test": "node -e \"require('fs').rmSync('dist-test',{recursive:true,force:true})\" && tsc -p tsconfig.test.json && node --test --test-reporter=spec \"dist-test/test/**/*.test.js\"",
|
|
@@ -73,7 +69,7 @@
|
|
|
73
69
|
},
|
|
74
70
|
"publishConfig": {
|
|
75
71
|
"access": "public",
|
|
76
|
-
"tag": "
|
|
72
|
+
"tag": "latest"
|
|
77
73
|
},
|
|
78
74
|
"peerDependencies": {
|
|
79
75
|
"express": ">=4.21 <6",
|