From 29fc89a848e8999b366917bdd44a5a79bdda8ee9 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 8 Sep 2026 19:20:06 -0400 Subject: [PATCH 1/2] Let createSessionOnMissing mint a sign_in session A kind option on createSessionOnMissing rides through to POST /v1/sessions. A gate running an empty compliance policy that only needs an account to key state on could previously mint only the KYC kind, which asks the buyer for documents nothing checks. For sign_in the denial's default message says what the session actually asks for instead of the KYC copy. Needs @agent-score/sdk 2.7.10. Co-Authored-By: Claude Fable 5.1 --- README.md | 2 ++ package.json | 4 ++-- src/core.ts | 16 +++++++++++++++- tests/express.test.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4d9cb7c..37722a0 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ const _gate = agentscoreGate({ minAge: 21, allowedJurisdictions: ["US"], createSessionOnMissing: { apiKey: process.env.AGENTSCORE_API_KEY!, context: "wine-purchase" }, + // With an EMPTY policy (no KYC), add kind: "sign_in" so the minted session is a plain + // account sign-in (no identity documents) that still yields an operator token. }); // Run the gate CONDITIONALLY: only when a payment credential is already attached. diff --git a/package.json b/package.json index 1bc84db..12fa942 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agent-score/commerce", - "version": "2.11.1", + "version": "2.12.0", "description": "Agentic commerce SDK: identity middleware (Hono, Express, Fastify, Next.js, Web Fetch) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agentic commerce.", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -148,7 +148,7 @@ "bun": ">=1.3.0 <2" }, "dependencies": { - "@agent-score/sdk": "^2.7.9" + "@agent-score/sdk": "^2.7.10" }, "overrides": { "axios": "^1.18.0", diff --git a/src/core.ts b/src/core.ts index 1eb75d3..b2ae4c8 100644 --- a/src/core.ts +++ b/src/core.ts @@ -74,6 +74,13 @@ export interface CreateSessionOnMissing { baseUrl?: string; context?: string; productName?: string; + /** Session kind sent to `POST /v1/sessions`. `'kyc'` (the API default) runs identity + * verification; `'sign_in'` is registration-only (the buyer signs in with an AgentScore + * account, no identity documents) and mints a `sign_in`-scoped credential. Use it when the + * gate runs with an EMPTY compliance policy and only needs an account to key state on + * (a prepaid balance, say): a KYC session there asks the buyer for documents nothing + * will ever check. The denial's default `error.message` follows the kind. */ + kind?: 'kyc' | 'sign_in'; /** Per-request override of `context` / `productName`. Invoked with the framework context. */ getSessionOptions?: (ctx: TCtx) => Promise<{ context?: string; productName?: string }> | { context?: string; productName?: string }; @@ -569,9 +576,10 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore async function tryMintSessionDenial(ctx: unknown): Promise { if (!createSessionOnMissing) return undefined; try { - const sessionBody: { context?: string; product_name?: string } = {}; + const sessionBody: { context?: string; product_name?: string; kind?: 'kyc' | 'sign_in' } = {}; if (createSessionOnMissing.context != null) sessionBody.context = createSessionOnMissing.context; if (createSessionOnMissing.productName != null) sessionBody.product_name = createSessionOnMissing.productName; + if (createSessionOnMissing.kind != null) sessionBody.kind = createSessionOnMissing.kind; if (createSessionOnMissing.getSessionOptions && ctx !== undefined) { try { @@ -589,6 +597,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore const data = (await sessionSdk.createSession({ ...(sessionBody.context !== undefined ? { context: sessionBody.context } : {}), ...(sessionBody.product_name !== undefined ? { product_name: sessionBody.product_name } : {}), + ...(sessionBody.kind !== undefined ? { kind: sessionBody.kind } : {}), })) as unknown as Record; // Validate required fields before trusting the response. A misbehaving (or mocked-wrong) @@ -629,6 +638,11 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore const apiNextSteps = data.next_steps as Record | undefined; return { code: 'identity_verification_required', + // The per-code default message talks about KYC, which a sign_in session never runs; + // say what this session actually asks for so a merchant's default 403 is not a lie. + ...(sessionBody.kind === 'sign_in' && { + message: 'Sign-in is required to access this resource. Visit verify_url to sign in with an AgentScore account (no identity documents), then poll poll_url for the operator token and retry.', + }), verify_url: data.verify_url as string, session_id: data.session_id as string, poll_secret: data.poll_secret as string, diff --git a/tests/express.test.ts b/tests/express.test.ts index 6250af1..373bd40 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -1416,6 +1416,32 @@ describe('agentscoreGate middleware — createSessionOnMissing', () => { }); }); + it('sends kind in the POST body and swaps the KYC default message for the sign_in one', async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce(SESSION_RESPONSE), + } as unknown as globalThis.Response); + + const mw = agentscoreGate({ + apiKey: API_KEY, + createSessionOnMissing: { apiKey: 'ask_session_key', kind: 'sign_in' }, + }); + const req = makeReq(); + const { res, status, json } = makeRes(); + const next = makeNext(); + + await mw(req, res, next); + + const fetchCall = (global.fetch as ReturnType).mock.calls[0]; + expect(JSON.parse(fetchCall[1].body as string)).toEqual({ kind: 'sign_in' }); + expect(status).toHaveBeenCalledWith(403); + const bodyArg = (json as ReturnType).mock.calls[0]![0] as { error: { code: string; message: string } }; + expect(bodyArg.error.code).toBe('identity_verification_required'); + expect(bodyArg.error.message).toContain('sign in with an AgentScore account'); + expect(bodyArg.error.message).not.toContain('KYC'); + }); + it('uses custom baseUrl for session creation', async () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, From 9fa419052c04f0b2297889f01be3e301c4becc3c Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 8 Sep 2026 20:08:37 -0400 Subject: [PATCH 2/2] Lock @agent-score/sdk 2.7.10 Co-Authored-By: Claude Fable 5.1 --- bun.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index 7a3b0cf..fa1e314 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "@agent-score/commerce", "dependencies": { - "@agent-score/sdk": "^2.7.9", + "@agent-score/sdk": "^2.7.10", }, "devDependencies": { "@a2a-js/sdk": "^1.1.0", @@ -69,7 +69,7 @@ "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], - "@agent-score/sdk": ["@agent-score/sdk@2.7.9", "", {}, "sha512-KA8zET+D0GXhkorFT6YaBAGyrE0AQF/7X8sNtllrMBC6w2g6bkpts0CZSd38QmBJYadWdbia7ZJu7mHB8hYmqg=="], + "@agent-score/sdk": ["@agent-score/sdk@2.7.10", "", {}, "sha512-QnDBdH3lqGP1JLc8AaTlXFgBNs+F5WiD+GBwOgKRaxBnojUWv0oLr3ekBDrAR2FF8O8yVQmC9bvM07uX0dht+Q=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],