@forgeintel/sdk 0.5.0-beta.11 → 0.5.0-beta.13

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Beta.** Install with `npm install @forgeintel/sdk`. The API may change before 1.0.
4
4
 
5
- The Forge SDK for x402 paid APIs. Paid responses carry a `feedback_id`, the 402 challenge asks agents to rate the call, a rating is one free GET, and every call is reported to your Forge dashboard in the background. Never on your critical path: no network calls while your API serves a request, and it fails open. Aware of x402 v1 and v2, and of OpenAPI 2.0 through 3.2. Every change is additive, and anything it doesn't understand passes through untouched.
5
+ The Forge SDK for x402 paid APIs. Paid responses carry a `feedback_id`, the 402 challenge asks agents to rate the call, a rating is a free GET with no payment, and every call is reported to your Forge dashboard in the background. Never on your critical path: no network calls while your API serves a request, and it fails open. Aware of x402 v1 and v2, and of OpenAPI 2.0 through 3.2. Every change is additive, and anything it doesn't understand passes through untouched.
6
6
 
7
7
  ## Express
8
8
 
@@ -17,7 +17,7 @@ app.use(forge.middleware()); // 1. first: before payments
17
17
  app.use(paymentMiddleware(routes, resourceServer)); // 2. your existing x402 setup, unchanged
18
18
  ```
19
19
 
20
- Works with `@x402/express` (v2) and `x402-express` (v1), on Express 4.21+ or 5. The root export is the Express adapter (also at `@forgeintel/sdk/express`).
20
+ Works with `@x402/express` (v2) and `x402-express` (v1), on Express 4.21+ or 5. Complete example: [forge-examples/node-express](https://github.com/tryforgeintel/forge-examples/tree/main/node-express). The root export is the Express adapter (also at `@forgeintel/sdk/express`).
21
21
 
22
22
  ## Hono (Node, Bun, Deno, Cloudflare Workers)
23
23
 
@@ -47,7 +47,7 @@ export const { GET, POST, HEAD } = forge.routes;
47
47
 
48
48
  ## Any other framework
49
49
 
50
- `@forgeintel/sdk/core` is the same logic without a framework. Call `forge.route()` first, then `forge.call()` per paid request, and `call.headers()` / `call.json()` / `call.finish()` where you build the 402 and the paid response. See the [core integration guide](https://github.com/ClawCash/forge-feedback/tree/main/examples/node-http-core) for a complete `node:http` + `@x402/core` example.
50
+ `@forgeintel/sdk/core` is the same logic without a framework. Call `forge.route()` first, then `forge.call()` per paid request, and `call.headers()` / `call.json()` / `call.finish()` where you build the 402 and the paid response. See the [core API reference](https://docs.forgeintel.co/reference/core-api).
51
51
 
52
52
  Zero runtime dependencies. Node ≥ 20.19. Works from both `import` and `require()`.
53
53
 
@@ -103,7 +103,7 @@ OpenAPI: Swagger 2.0 and OpenAPI 3.0, 3.1 and 3.2. Additive only; shared compone
103
103
 
104
104
  `forge.diagnostics()` returns counters and the last OpenAPI report. `forge.enrichOpenApi(doc)` is available for build-time use. Call `await forge.shutdown()` in your shutdown handler to flush pending events.
105
105
 
106
- Source, examples and design notes: [github.com/ClawCash/forge-feedback](https://github.com/ClawCash/forge-feedback).
106
+ Runnable examples (Express and FastAPI): [github.com/tryforgeintel/forge-examples](https://github.com/tryforgeintel/forge-examples). Docs: [docs.forgeintel.co](https://docs.forgeintel.co).
107
107
 
108
108
  Client headers are captured automatically at discovery, challenge, payment, and rating stages, including unknown clients. Forge identifies awal, AgentCash, pay.sh and x402scan-mcp from known headers; other clients remain unknown. There is no client-signals switch. Legacy `agent_context.client` inputs remain accepted but are no longer requested.
109
109
 
package/dist/core.js CHANGED
@@ -104,7 +104,10 @@ export function checkOptions(input) {
104
104
  }
105
105
  return { errors, warnings, options };
106
106
  }
107
- /** Everything passes through untouched. Used when invalid options turned Forge off. */
107
+ /**
108
+ * Used when invalid options turned Forge off: nothing is recorded or added, but agent context is still removed from
109
+ * requests, so agents that learned about it (an earlier deploy, a cached listing) can't trip strict validators.
110
+ */
108
111
  function disabledCore(errors, warnings) {
109
112
  const passThrough = {
110
113
  feedbackId: undefined,
@@ -113,8 +116,8 @@ function disabledCore(errors, warnings) {
113
116
  json: (_status, body) => body,
114
117
  text: (_status, _type, body) => body,
115
118
  headers: () => ({}),
116
- requestBody: (body) => body,
117
- requestUrl: (url) => url,
119
+ requestBody: (body) => takeFromBody(body).body,
120
+ requestUrl: (url) => takeFromUrl(url).url,
118
121
  finish: () => { },
119
122
  };
120
123
  return {
package/dist/express.js CHANGED
@@ -202,8 +202,18 @@ export function createForge(options) {
202
202
  diagnostics: core.diagnostics,
203
203
  shutdown: core.shutdown,
204
204
  middleware() {
205
- if (!core.enabled)
206
- return (_req, _res, next) => next();
205
+ if (!core.enabled) {
206
+ // Disabled by invalid options: only remove agent context, so strict validators never see it.
207
+ return (req, _res, next) => {
208
+ try {
209
+ takeAgentContext(req, core.call({ method: req.method, path: req.path, header: () => undefined }));
210
+ }
211
+ catch {
212
+ // never break the business request
213
+ }
214
+ next();
215
+ };
216
+ }
207
217
  return (req, res, next) => {
208
218
  core
209
219
  .route({
package/dist/fetch.js CHANGED
@@ -113,8 +113,6 @@ export function createForge(options) {
113
113
  }
114
114
  }
115
115
  async function handle(request, next) {
116
- if (!core.enabled)
117
- return next(request);
118
116
  // Before the handler: our own routes, the spec, and agent context. Required-context errors stop paid attempts.
119
117
  let call;
120
118
  let forwarded = request;
@@ -168,6 +166,9 @@ export function createForge(options) {
168
166
  }
169
167
  return next(request);
170
168
  }
169
+ // Disabled by invalid options: agent context is removed (above), nothing else.
170
+ if (!core.enabled)
171
+ return next(forwarded);
171
172
  const response = await next(forwarded);
172
173
  // After the handler: decorate the 402 or the paid response. Any failure before the body is read: the original
173
174
  // response goes out. Once read, the body is always sent from what was read (decorated or not).
package/dist/hono.js CHANGED
@@ -10,8 +10,6 @@ export function createForge(options) {
10
10
  shutdown: forge.shutdown,
11
11
  validate: forge.validate,
12
12
  middleware() {
13
- if (!forge.enabled)
14
- return async (_c, next) => next();
15
13
  return async (c, next) => {
16
14
  let ranNext = false;
17
15
  const response = await forge.handle(c.req.raw, async (request) => {
package/dist/openapi.js CHANGED
@@ -61,6 +61,8 @@ function feedbackProperties(rateUrl, hintField = false) {
61
61
  }
62
62
  /** Returns an extended copy of an object schema, or a reason it can't be extended safely. */
63
63
  function extendSchema(doc, schema, props) {
64
+ if (schema === true)
65
+ schema = {}; // `true` accepts any value, like {}
64
66
  if (!isObj(schema))
65
67
  return { reason: "no_schema" };
66
68
  let target = schema;
@@ -83,7 +85,10 @@ function extendSchema(doc, schema, props) {
83
85
  if (COMPOSITION.some((k) => k in copy))
84
86
  return { reason: "composition" };
85
87
  const types = Array.isArray(copy.type) ? copy.type : [copy.type];
86
- const objectish = types.includes("object") || (copy.type === undefined && (isObj(copy.properties) || Array.isArray(copy.allOf)));
88
+ // {} (annotations only) accepts any value, e.g. FastAPI's default response schema. `properties` only constrains
89
+ // objects, so documenting the field there narrows nothing.
90
+ const anyValue = Object.keys(copy).every((k) => ANNOTATIONS.has(k));
91
+ const objectish = anyValue || types.includes("object") || (copy.type === undefined && (isObj(copy.properties) || Array.isArray(copy.allOf)));
87
92
  if (!objectish)
88
93
  return { reason: "not_object" };
89
94
  if ("propertyNames" in copy || "maxProperties" in copy)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeintel/sdk",
3
- "version": "0.5.0-beta.11",
3
+ "version": "0.5.0-beta.13",
4
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",
@@ -61,11 +61,7 @@
61
61
  "cloudflare-workers",
62
62
  "bun"
63
63
  ],
64
- "repository": {
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\"",