plugin-sagg-x402 0.1.0 → 0.1.1

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
@@ -7,8 +7,7 @@ ElizaOS agent can use SAGG as its LLM the same way it would use `@elizaos/plugin
7
7
  or `@elizaos/plugin-anthropic` - except every call is paid for by the agent's own
8
8
  funded wallet, with no human provisioning an API key first.
9
9
 
10
- **Status: testnet only (Base Sepolia). Not published to npm or the ElizaOS plugin
11
- registry - prepared for review, not distributed.**
10
+ **Status: testnet only (Base Sepolia). Published on npm as `plugin-sagg-x402`.**
12
11
 
13
12
  ## Why this exists
14
13
 
@@ -45,7 +44,7 @@ npm install
45
44
  npm run build
46
45
  ```
47
46
 
48
- Not published - install from this local checkout, and reference it by local path from
47
+ Install from npm (`npm install plugin-sagg-x402`) and reference it from
49
48
  an ElizaOS project's `character.ts`:
50
49
 
51
50
  ```ts
@@ -107,7 +106,7 @@ Also corrected here: the sibling LangChain/CrewAI adapters default to the model
107
106
  `deepseek-v4-flash-0731`. The real, documented full-form id is
108
107
  `deepseek-ai/DeepSeek-V4-Flash-0731` (PROJECT_STATE.md sec 3.164) - the short form is
109
108
  exactly the class of bug that section found and fixed elsewhere (it breaks model-name
110
- translation for the `eterial-deepseek`/`gonka24-deepseek` fallback tiers specifically).
109
+ translation for some fallback tiers).
111
110
  This plugin defaults to the correct full form; worth fixing upstream in the sibling
112
111
  adapters too.
113
112
 
@@ -184,8 +183,6 @@ SAGG's own maintainers; not fixed here, out of this plugin's scope.
184
183
 
185
184
  ## What this plugin does NOT do
186
185
 
187
- - Publish itself anywhere (no npm, no ElizaOS plugin registry) - prepared for review
188
- only, per its own scope.
189
186
  - Distinguish `TEXT_LARGE` from `TEXT_SMALL` - both call the same configured SAGG model;
190
187
  SAGG's own gateway already handles provider tiering server-side.
191
188
  - Support streaming responses, `TEXT_EMBEDDING`, or any ModelType beyond text
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/signer.ts","../src/client.ts","../src/model.ts"],"sourcesContent":["/**\n * plugin-sagg-x402 - pays for SAGG (https://api.privatedeskai.com) LLM\n * chat-completion requests per-request via the x402 protocol (a signed\n * USDC micropayment) instead of an API key, registered as an ElizaOS\n * model handler for ModelType.TEXT_LARGE/TEXT_SMALL. See README.md for\n * why this exists and how it fits alongside the sibling LangChain/\n * CrewAI/MCP adapters built for the same SAGG x402 endpoint.\n */\n\nimport type { IAgentRuntime, Plugin } from \"@elizaos/core\";\nimport { logger, ModelType } from \"@elizaos/core\";\nimport { handleGenerateText } from \"./model.js\";\n\nexport const saggX402Plugin: Plugin = {\n name: \"sagg-x402\",\n description:\n \"Pays for SAGG LLM completions per-request via the x402 protocol (signed USDC micropayment, no API key). Testnet (Base Sepolia) only as shipped.\",\n\n async init(_config: Record<string, string>, runtime: IAgentRuntime) {\n // Deliberately does NOT validate/throw here - see model.ts's own\n // comment on why a missing/bad private key surfaces at first real\n // call instead of at plugin load time. This only logs a clear,\n // early warning so a misconfiguration is visible in the agent's\n // own startup logs rather than a silent surprise on first use.\n const hasUrl = Boolean(runtime.getSetting(\"SAGG_X402_URL\"));\n const hasKey =\n Boolean(runtime.getSetting(\"SAGG_X402_PRIVATE_KEY\")) ||\n Boolean(runtime.getSetting(\"EVM_PRIVATE_KEY\"));\n if (!hasUrl || !hasKey) {\n logger.warn(\n \"plugin-sagg-x402: SAGG_X402_URL and/or a private key \" +\n \"(SAGG_X402_PRIVATE_KEY or EVM_PRIVATE_KEY) are not configured - \" +\n \"TEXT_LARGE/TEXT_SMALL calls routed to this plugin will fail.\",\n );\n }\n },\n\n models: {\n [ModelType.TEXT_LARGE]: handleGenerateText,\n [ModelType.TEXT_SMALL]: handleGenerateText,\n },\n};\n\nexport default saggX402Plugin;\nexport { handleGenerateText } from \"./model.js\";\nexport { SaggX402Client, SaggX402PaymentError } from \"./client.js\";\nexport { signTransferAuthorization, buildTypedData } from \"./signer.js\";\n","/**\n * EIP-3009 (transferWithAuthorization) signing for the x402 \"exact\"\n * payment scheme, via viem's own EIP-712 typed-data support.\n *\n * Mirrors the EXACT typed-data shape SAGG's Go gateway builds\n * (x402module/poc/pkg/x402protocol/signer_adapter.go, WalletSigner.Sign)\n * and this project's own Python adapters (langchain-x402-sagg/signer.py,\n * crewai-x402-sagg) - same field names, same types, same domain\n * structure - so a signature produced here is verified identically by\n * the same facilitator/on-chain contract, regardless of which language\n * signed it. EIP-712/EIP-3009 are open cryptographic standards, not\n * anything Go- or Python-specific; viem is the standard, widely-used\n * TypeScript implementation (the same library @elizaos/plugin-evm\n * itself is built on) - this mirrors the earlier adapters' own choice\n * of the host ecosystem's standard web3 library over a cross-language\n * bridge to the Go module.\n *\n * One real difference from the Go/Python versions, not a bug: viem's\n * signTypedData derives the \"EIP712Domain\" type entry itself from the\n * `domain` object - it must NOT be listed again in `types` (unlike\n * go-ethereum's apitypes.TypedData and eth_account's\n * encode_typed_data/sign_typed_data, which both require it spelled out\n * explicitly). Verified against viem's own docs before writing this.\n */\n\nimport { privateKeyToAccount, type PrivateKeyAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\n\nexport interface SignedAuthorization {\n signature: Hex;\n fromAddress: Hex;\n toAddress: Hex;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: Hex;\n}\n\n/** A fresh, cryptographically random 32-byte EIP-3009 nonce - a new one\n * MUST be generated per authorization; reusing one with the same signer\n * produces a signature the on-chain contract rejects as already-used on\n * the second attempt (PROJECT_STATE.md's x402 Stage 3 audit, sec 3.147,\n * item 1 - EIP-3009's own nonce tracking is the real replay protection\n * here, not anything this client does). Uses the Web Crypto API\n * (globalThis.crypto), available in both Node 19+ and browsers - no\n * separate crypto import needed. */\nexport function randomNonceHex(): Hex {\n const bytes = new Uint8Array(32);\n globalThis.crypto.getRandomValues(bytes);\n return (\"0x\" +\n Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\n \"\",\n )) as Hex;\n}\n\ninterface TransferAuthorizationTypedData {\n domain: {\n name: string;\n version: string;\n chainId: number;\n verifyingContract: Hex;\n };\n types: {\n TransferWithAuthorization: [\n { name: \"from\"; type: \"address\" },\n { name: \"to\"; type: \"address\" },\n { name: \"value\"; type: \"uint256\" },\n { name: \"validAfter\"; type: \"uint256\" },\n { name: \"validBefore\"; type: \"uint256\" },\n { name: \"nonce\"; type: \"bytes32\" },\n ];\n };\n primaryType: \"TransferWithAuthorization\";\n message: {\n from: Hex;\n to: Hex;\n value: bigint;\n validAfter: bigint;\n validBefore: bigint;\n nonce: Hex;\n };\n}\n\n/** Builds the EIP-712 typed-data object for a TransferWithAuthorization -\n * field-for-field identical to what SAGG's own Go signer and the sibling\n * Python adapters build, minus the EIP712Domain entry viem derives on\n * its own (see this file's own top comment). Exported separately from\n * signTransferAuthorization so tests can independently re-derive the\n * same digest viem signs, the same \"recover the signer independently\"\n * discipline this whole project's own test suites already use. */\nexport function buildTypedData(\n domainName: string,\n domainVersion: string,\n chainId: number,\n tokenAddress: Hex,\n fromAddress: Hex,\n toAddress: Hex,\n value: bigint,\n validAfter: bigint,\n validBefore: bigint,\n nonce: Hex,\n): TransferAuthorizationTypedData {\n return {\n domain: {\n name: domainName,\n version: domainVersion,\n chainId,\n verifyingContract: tokenAddress,\n },\n types: {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n },\n primaryType: \"TransferWithAuthorization\",\n message: {\n from: fromAddress,\n to: toAddress,\n value,\n validAfter,\n validBefore,\n nonce,\n },\n };\n}\n\n/** Signs a fresh EIP-3009 authorization for exactly `value` atomic units\n * of tokenAddress, payable to toAddress, valid from ~now until\n * validTimeoutSeconds from now (mirrors the Go/Python signers' own\n * 10-second clock-drift tolerance on validAfter). */\nexport async function signTransferAuthorization(\n privateKey: Hex,\n domainName: string,\n domainVersion: string,\n chainId: number,\n tokenAddress: Hex,\n toAddress: Hex,\n value: bigint,\n validTimeoutSeconds: number,\n): Promise<SignedAuthorization> {\n const account: PrivateKeyAccount = privateKeyToAccount(privateKey);\n const now = BigInt(Math.floor(Date.now() / 1000));\n const validAfter = now - 10n;\n const validBefore = now + BigInt(validTimeoutSeconds);\n const nonce = randomNonceHex();\n\n const typedData = buildTypedData(\n domainName,\n domainVersion,\n chainId,\n tokenAddress,\n account.address,\n toAddress,\n value,\n validAfter,\n validBefore,\n nonce,\n );\n\n const signature = await account.signTypedData(typedData);\n\n return {\n signature,\n fromAddress: account.address,\n toAddress,\n value: value.toString(),\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n };\n}\n","/**\n * HTTP client implementing the client side of the x402 \"exact\" scheme:\n * probe -> 402 challenge -> sign -> pay -> response. Mirrors\n * x402module/poc/pkg/x402protocol/dynamicclient.go's own two-request\n * design (a fresh, unpaid probe carrying the real body, then a SEPARATE\n * paid retry with its own fresh body) and the sibling Python adapters'\n * client.py (langchain-x402-sagg, crewai-x402-sagg) field-for-field.\n * Node's global `fetch` builds a brand-new request body from the object\n * passed to it on each call, so - like the Python adapters using\n * `requests` - there is no equivalent of the mark3labs/x402-go\n * single-consumed-body bug this project found and worked around in Go\n * (PROJECT_STATE.md sec 3.145); noted for comparison, not because a\n * workaround was needed here.\n */\n\nimport { signTransferAuthorization } from \"./signer.js\";\nimport type { Hex } from \"viem\";\n\nconst CHAIN_IDS: Record<string, number> = {\n base: 8453,\n \"base-sepolia\": 84532,\n};\n\nexport class SaggX402PaymentError extends Error {}\n\n/** Shaped around what SAGG's own x402 handler actually reports, not the\n * generic x402 spec's success/errorReason/transaction Settlement shape\n * (see getSettlement's own doc comment for why those two differ). */\nexport interface Settlement {\n ceilingTx: string;\n refundTx: string | null;\n}\n\ninterface PaymentRequirement {\n maxAmountRequired: string;\n asset: Hex;\n payTo: Hex;\n network: string;\n maxTimeoutSeconds?: number;\n extra?: { name?: string; version?: string };\n}\n\ninterface X402Challenge {\n x402Version: number;\n error?: string;\n accepts?: PaymentRequirement[];\n}\n\nexport interface SaggX402ClientOptions {\n privateKey: Hex;\n maxAmountAtomic: bigint;\n network?: string;\n timeoutMs?: number;\n}\n\n/** Pays for x402-gated SAGG requests using an EVM private key, enforcing\n * a hard per-call maximum (maxAmountAtomic) independent of whatever a\n * server's own requirement asks for - the same \"never auto-pay more\n * than this\" discipline as the Go module's WalletSigner and both Python\n * adapters. */\nexport class SaggX402Client {\n private readonly privateKey: Hex;\n private readonly maxAmountAtomic: bigint;\n private readonly network: string;\n private readonly chainId: number;\n private readonly timeoutMs: number;\n\n constructor(options: SaggX402ClientOptions) {\n const network = options.network ?? \"base-sepolia\";\n const chainId = CHAIN_IDS[network];\n if (chainId === undefined) {\n throw new Error(\n `unsupported network ${JSON.stringify(network)}, want one of ${Object.keys(CHAIN_IDS).join(\", \")}`,\n );\n }\n this.privateKey = options.privateKey;\n this.maxAmountAtomic = options.maxAmountAtomic;\n this.network = network;\n this.chainId = chainId;\n this.timeoutMs = options.timeoutMs ?? 60_000;\n }\n\n /** POSTs jsonBody to url. If the first response is a real x402 402\n * challenge, signs the first accepted requirement (refusing if it\n * exceeds maxAmountAtomic) and reposts a FRESH request with the\n * resulting X-PAYMENT header. Any other first response (2xx, or a\n * non-402 error) is returned as-is, unpaid. */\n async postWithPayment(\n url: string,\n jsonBody: Record<string, unknown>,\n ): Promise<Response> {\n const probe = await this.postJson(url, jsonBody);\n if (probe.status !== 402) {\n return probe;\n }\n\n const challenge = (await probe.json()) as X402Challenge;\n const accepts = challenge.accepts ?? [];\n if (accepts.length === 0) {\n throw new SaggX402PaymentError(\n \"402 challenge carried no accepted payment methods\",\n );\n }\n const requirement = accepts[0];\n\n const amount = BigInt(requirement.maxAmountRequired);\n if (amount > this.maxAmountAtomic) {\n throw new SaggX402PaymentError(\n `requirement asks for ${amount} atomic units, exceeds this client's configured max ${this.maxAmountAtomic}`,\n );\n }\n\n const extra = requirement.extra ?? {};\n if (!extra.name || !extra.version) {\n throw new SaggX402PaymentError(\n \"requirement missing EIP-3009 domain name/version in its 'extra' field\",\n );\n }\n\n // this.chainId was fixed once at construction from this.network -\n // correct ONLY as long as the requirement the server actually\n // issued is for that same network. Real bug found live in the\n // sibling langchain-x402-sagg package on the first real mainnet\n // attempt (PROJECT_STATE.md sec 3.172, then confirmed to be the\n // same shape here too): a client built with the default\n // network=\"base-sepolia\" but pointed at a real mainnet SAGG_X402_URL\n // signs with chainId 84532 while the server's requirement is for\n // chainId 8453 - the facilitator correctly rejects the resulting\n // signature as invalid (\"invalid_exact_evm_signature\"), a confusing\n // failure with no clue as to the real cause. Refusing outright on a\n // mismatch (rather than silently trusting whatever the server\n // claims and re-deriving chainId from it) is the safer failure mode\n // too: a testnet-configured client must never be tricked into\n // producing a valid signature for a real mainnet transfer just\n // because it was pointed at the wrong URL.\n if (requirement.network !== this.network) {\n throw new SaggX402PaymentError(\n `requirement's network ${JSON.stringify(requirement.network)} does not match this client's configured network ${JSON.stringify(this.network)} - refusing to sign. This client was likely constructed with the wrong \\`network\\` option for the URL it's actually talking to.`,\n );\n }\n\n const signed = await signTransferAuthorization(\n this.privateKey,\n extra.name,\n extra.version,\n this.chainId,\n requirement.asset,\n requirement.payTo,\n amount,\n requirement.maxTimeoutSeconds ?? 60,\n );\n\n const paymentPayload = {\n x402Version: 1,\n scheme: \"exact\",\n network: requirement.network,\n payload: {\n signature: signed.signature,\n authorization: {\n from: signed.fromAddress,\n to: signed.toAddress,\n value: signed.value,\n validAfter: signed.validAfter,\n validBefore: signed.validBefore,\n nonce: signed.nonce,\n },\n },\n };\n const headerValue = Buffer.from(JSON.stringify(paymentPayload)).toString(\n \"base64\",\n );\n\n return this.postJson(url, jsonBody, { \"X-PAYMENT\": headerValue });\n }\n\n /** Extracts settlement info from SAGG's OWN response headers.\n *\n * A real, live-testing-only finding, not something assumed from the\n * spec or copied from the sibling adapters: the generic x402 spec's\n * `X-PAYMENT-RESPONSE` header (base64 JSON, what x402-go's OWN stock\n * middleware sets, and what x402protocol.GetSettlement (client.go)\n * and the sibling LangChain/CrewAI adapters' get_settlement look for)\n * is NEVER set by SAGG's actual `/v1/chat/completions` x402 path -\n * confirmed by inspecting real response headers from a live local\n * gateway instance, not by reading the handler source alone. SAGG's\n * x402ChatCompletionsHandler (x402billing.go) is built directly on\n * DynamicServer (needed for per-request dynamic pricing - x402-go's\n * own stock middleware only supports one fixed price set at startup),\n * and sets its OWN plain headers instead:\n * `X-SAGG-X402-Ceiling-Tx` (always present on a served response) and\n * `X-SAGG-X402-Refund-Tx` (present only when a refund was actually\n * sent - absent, not empty, when the refund amount was zero OR when\n * sending it failed, e.g. the server wallet lacking gas - see this\n * plugin's README for what that means for a real refund's\n * observability from the client side). No `success`/`errorReason`\n * fields exist in this scheme at all - a served 200 response with a\n * ceiling-tx header IS the success signal. */\n static getSettlement(response: Response): Settlement | null {\n const ceilingTx = response.headers.get(\"X-SAGG-X402-Ceiling-Tx\");\n if (!ceilingTx) {\n return null;\n }\n return {\n ceilingTx,\n refundTx: response.headers.get(\"X-SAGG-X402-Refund-Tx\"),\n };\n }\n\n private async postJson(\n url: string,\n jsonBody: Record<string, unknown>,\n extraHeaders?: Record<string, string>,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...extraHeaders },\n body: JSON.stringify(jsonBody),\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","/**\n * The actual SAGG-backed TEXT_LARGE/TEXT_SMALL model handler. SAGG's own\n * gateway already does provider tiering/failover server-side (Standard\n * vs Super Deal lines - see the main sagg-project repo) - this plugin\n * deliberately does not reimplement a large/small model distinction of\n * its own; both ModelType.TEXT_LARGE and ModelType.TEXT_SMALL are wired\n * to the SAME handler and the same configured model id (see README's\n * \"What this plugin does NOT do\" for this and other scope calls).\n */\n\nimport type { IAgentRuntime } from \"@elizaos/core\";\nimport type { Hex } from \"viem\";\nimport { SaggX402Client, SaggX402PaymentError } from \"./client.js\";\n\n// The real, documented full-form model id (PROJECT_STATE.md sec 3.164) -\n// NOT the short \"deepseek-v4-flash-0731\" form the sibling LangChain/\n// CrewAI adapters default to. That short form is exactly the class of\n// bug sec 3.164 found and fixed in bench/superdeal_vs_direct.go and\n// cmd/warmup/main.go: it breaks model-name translation for the\n// eterial-deepseek/gonka24-deepseek fallback tiers specifically. Worth\n// fixing in the sibling adapters too, but out of this plugin's own\n// scope - flagged in this plugin's README instead.\nexport const DEFAULT_MODEL = \"deepseek-ai/DeepSeek-V4-Flash-0731\";\nexport const DEFAULT_MAX_AMOUNT_ATOMIC = 1_000_000n; // 1.00 USDC (6 decimals)\nexport const DEFAULT_NETWORK = \"base-sepolia\";\n\ninterface GenerateTextParamsLike {\n prompt?: string;\n system?: string;\n messages?: Array<{ role: string; content: string }>;\n maxTokens?: number;\n temperature?: number;\n stopSequences?: string[];\n}\n\ninterface ChatMessage {\n role: string;\n content: string;\n}\n\n/** One client per plugin instance, built lazily from runtime settings on\n * first use rather than in `init()` - a private key that's missing or\n * wrong should surface as a clear error on the first real request, not\n * silently disable the plugin at startup (ElizaOS loads plugins whose\n * `init` throws inconsistently across versions; failing at call time is\n * the more predictable contract). */\nlet cachedClient: SaggX402Client | null = null;\nlet cachedClientKey: string | null = null;\n\nfunction requireSetting(runtime: IAgentRuntime, key: string): string {\n const value = runtime.getSetting(key);\n if (value === null || value === undefined || value === \"\") {\n throw new Error(\n `plugin-sagg-x402: required setting ${key} is not configured`,\n );\n }\n return String(value);\n}\n\nfunction optionalSetting(runtime: IAgentRuntime, key: string): string | null {\n const value = runtime.getSetting(key);\n if (value === null || value === undefined || value === \"\") {\n return null;\n }\n return String(value);\n}\n\nfunction resolvePrivateKey(runtime: IAgentRuntime): Hex {\n const dedicated = optionalSetting(runtime, \"SAGG_X402_PRIVATE_KEY\");\n const fallback = dedicated ?? optionalSetting(runtime, \"EVM_PRIVATE_KEY\");\n if (!fallback) {\n throw new Error(\n \"plugin-sagg-x402: neither SAGG_X402_PRIVATE_KEY nor EVM_PRIVATE_KEY is configured\",\n );\n }\n return (fallback.startsWith(\"0x\") ? fallback : `0x${fallback}`) as Hex;\n}\n\nfunction getClient(runtime: IAgentRuntime): SaggX402Client {\n const privateKey = resolvePrivateKey(runtime);\n const network = optionalSetting(runtime, \"SAGG_X402_NETWORK\") ?? DEFAULT_NETWORK;\n const maxAmountSetting = optionalSetting(\n runtime,\n \"SAGG_X402_MAX_AMOUNT_ATOMIC\",\n );\n const maxAmountAtomic = maxAmountSetting\n ? BigInt(maxAmountSetting)\n : DEFAULT_MAX_AMOUNT_ATOMIC;\n\n // Cache key covers every input that changes what the client does -\n // a settings change (e.g. rotating the key) must produce a fresh\n // client, not silently keep signing with a stale one.\n const cacheKey = `${privateKey}:${network}:${maxAmountAtomic}`;\n if (cachedClient && cachedClientKey === cacheKey) {\n return cachedClient;\n }\n cachedClient = new SaggX402Client({ privateKey, network, maxAmountAtomic });\n cachedClientKey = cacheKey;\n return cachedClient;\n}\n\nfunction buildMessages(params: GenerateTextParamsLike): ChatMessage[] {\n const messages: ChatMessage[] = [];\n if (params.system) {\n messages.push({ role: \"system\", content: params.system });\n }\n if (params.messages && params.messages.length > 0) {\n for (const m of params.messages) {\n messages.push({ role: m.role, content: m.content });\n }\n return messages;\n }\n if (params.prompt) {\n messages.push({ role: \"user\", content: params.prompt });\n return messages;\n }\n throw new Error(\n \"plugin-sagg-x402: GenerateTextParams carried neither `messages` nor `prompt`\",\n );\n}\n\n/** The handler wired to ModelType.TEXT_LARGE/TEXT_SMALL in index.ts.\n * Pays via x402 on every call - SAGG's x402 path is a genuinely separate\n * accounting mechanism from its API-key/Stripe path (no quota, no\n * subscription), so there is no \"free\" request to fall back to if a\n * payment fails; a payment/HTTP error here is a real, user-visible\n * failure, not something to swallow. */\nexport async function handleGenerateText(\n runtime: IAgentRuntime,\n params: GenerateTextParamsLike,\n): Promise<string> {\n const saggUrl = requireSetting(runtime, \"SAGG_X402_URL\").replace(/\\/+$/, \"\");\n const model = optionalSetting(runtime, \"SAGG_X402_MODEL\") ?? DEFAULT_MODEL;\n const client = getClient(runtime);\n\n const body: Record<string, unknown> = {\n model,\n messages: buildMessages(params),\n };\n if (params.maxTokens !== undefined) body.max_tokens = params.maxTokens;\n if (params.temperature !== undefined) body.temperature = params.temperature;\n if (params.stopSequences && params.stopSequences.length > 0) {\n body.stop = params.stopSequences;\n }\n\n let response: Response;\n try {\n response = await client.postWithPayment(\n `${saggUrl}/v1/chat/completions`,\n body,\n );\n } catch (err) {\n if (err instanceof SaggX402PaymentError) {\n throw new Error(`plugin-sagg-x402: payment refused: ${err.message}`);\n }\n throw err;\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new Error(\n `plugin-sagg-x402: SAGG request failed (${response.status}): ${text}`,\n );\n }\n\n const data = (await response.json()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n const content = data.choices?.[0]?.message?.content;\n if (typeof content !== \"string\") {\n throw new Error(\n \"plugin-sagg-x402: SAGG response had no choices[0].message.content\",\n );\n }\n return content;\n}\n\n/** Exported for tests only - lets a test reset the module-level cache\n * between cases instead of caching a client across unrelated tests. */\nexport function _resetClientCacheForTests(): void {\n cachedClient = null;\n cachedClientKey = null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/signer.ts","../src/client.ts","../src/model.ts"],"sourcesContent":["/**\n * plugin-sagg-x402 - pays for SAGG (https://api.privatedeskai.com) LLM\n * chat-completion requests per-request via the x402 protocol (a signed\n * USDC micropayment) instead of an API key, registered as an ElizaOS\n * model handler for ModelType.TEXT_LARGE/TEXT_SMALL. See README.md for\n * why this exists and how it fits alongside the sibling LangChain/\n * CrewAI/MCP adapters built for the same SAGG x402 endpoint.\n */\n\nimport type { IAgentRuntime, Plugin } from \"@elizaos/core\";\nimport { logger, ModelType } from \"@elizaos/core\";\nimport { handleGenerateText } from \"./model.js\";\n\nexport const saggX402Plugin: Plugin = {\n name: \"sagg-x402\",\n description:\n \"Pays for SAGG LLM completions per-request via the x402 protocol (signed USDC micropayment, no API key). Testnet (Base Sepolia) only as shipped.\",\n\n async init(_config: Record<string, string>, runtime: IAgentRuntime) {\n // Deliberately does NOT validate/throw here - see model.ts's own\n // comment on why a missing/bad private key surfaces at first real\n // call instead of at plugin load time. This only logs a clear,\n // early warning so a misconfiguration is visible in the agent's\n // own startup logs rather than a silent surprise on first use.\n const hasUrl = Boolean(runtime.getSetting(\"SAGG_X402_URL\"));\n const hasKey =\n Boolean(runtime.getSetting(\"SAGG_X402_PRIVATE_KEY\")) ||\n Boolean(runtime.getSetting(\"EVM_PRIVATE_KEY\"));\n if (!hasUrl || !hasKey) {\n logger.warn(\n \"plugin-sagg-x402: SAGG_X402_URL and/or a private key \" +\n \"(SAGG_X402_PRIVATE_KEY or EVM_PRIVATE_KEY) are not configured - \" +\n \"TEXT_LARGE/TEXT_SMALL calls routed to this plugin will fail.\",\n );\n }\n },\n\n models: {\n [ModelType.TEXT_LARGE]: handleGenerateText,\n [ModelType.TEXT_SMALL]: handleGenerateText,\n },\n};\n\nexport default saggX402Plugin;\nexport { handleGenerateText } from \"./model.js\";\nexport { SaggX402Client, SaggX402PaymentError } from \"./client.js\";\nexport { signTransferAuthorization, buildTypedData } from \"./signer.js\";\n","/**\n * EIP-3009 (transferWithAuthorization) signing for the x402 \"exact\"\n * payment scheme, via viem's own EIP-712 typed-data support.\n *\n * Mirrors the EXACT typed-data shape SAGG's Go gateway builds\n * (x402module/poc/pkg/x402protocol/signer_adapter.go, WalletSigner.Sign)\n * and this project's own Python adapters (langchain-x402-sagg/signer.py,\n * crewai-x402-sagg) - same field names, same types, same domain\n * structure - so a signature produced here is verified identically by\n * the same facilitator/on-chain contract, regardless of which language\n * signed it. EIP-712/EIP-3009 are open cryptographic standards, not\n * anything Go- or Python-specific; viem is the standard, widely-used\n * TypeScript implementation (the same library @elizaos/plugin-evm\n * itself is built on) - this mirrors the earlier adapters' own choice\n * of the host ecosystem's standard web3 library over a cross-language\n * bridge to the Go module.\n *\n * One real difference from the Go/Python versions, not a bug: viem's\n * signTypedData derives the \"EIP712Domain\" type entry itself from the\n * `domain` object - it must NOT be listed again in `types` (unlike\n * go-ethereum's apitypes.TypedData and eth_account's\n * encode_typed_data/sign_typed_data, which both require it spelled out\n * explicitly). Verified against viem's own docs before writing this.\n */\n\nimport { privateKeyToAccount, type PrivateKeyAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\n\nexport interface SignedAuthorization {\n signature: Hex;\n fromAddress: Hex;\n toAddress: Hex;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: Hex;\n}\n\n/** A fresh, cryptographically random 32-byte EIP-3009 nonce - a new one\n * MUST be generated per authorization; reusing one with the same signer\n * produces a signature the on-chain contract rejects as already-used on\n * the second attempt (PROJECT_STATE.md's x402 Stage 3 audit, sec 3.147,\n * item 1 - EIP-3009's own nonce tracking is the real replay protection\n * here, not anything this client does). Uses the Web Crypto API\n * (globalThis.crypto), available in both Node 19+ and browsers - no\n * separate crypto import needed. */\nexport function randomNonceHex(): Hex {\n const bytes = new Uint8Array(32);\n globalThis.crypto.getRandomValues(bytes);\n return (\"0x\" +\n Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\n \"\",\n )) as Hex;\n}\n\ninterface TransferAuthorizationTypedData {\n domain: {\n name: string;\n version: string;\n chainId: number;\n verifyingContract: Hex;\n };\n types: {\n TransferWithAuthorization: [\n { name: \"from\"; type: \"address\" },\n { name: \"to\"; type: \"address\" },\n { name: \"value\"; type: \"uint256\" },\n { name: \"validAfter\"; type: \"uint256\" },\n { name: \"validBefore\"; type: \"uint256\" },\n { name: \"nonce\"; type: \"bytes32\" },\n ];\n };\n primaryType: \"TransferWithAuthorization\";\n message: {\n from: Hex;\n to: Hex;\n value: bigint;\n validAfter: bigint;\n validBefore: bigint;\n nonce: Hex;\n };\n}\n\n/** Builds the EIP-712 typed-data object for a TransferWithAuthorization -\n * field-for-field identical to what SAGG's own Go signer and the sibling\n * Python adapters build, minus the EIP712Domain entry viem derives on\n * its own (see this file's own top comment). Exported separately from\n * signTransferAuthorization so tests can independently re-derive the\n * same digest viem signs, the same \"recover the signer independently\"\n * discipline this whole project's own test suites already use. */\nexport function buildTypedData(\n domainName: string,\n domainVersion: string,\n chainId: number,\n tokenAddress: Hex,\n fromAddress: Hex,\n toAddress: Hex,\n value: bigint,\n validAfter: bigint,\n validBefore: bigint,\n nonce: Hex,\n): TransferAuthorizationTypedData {\n return {\n domain: {\n name: domainName,\n version: domainVersion,\n chainId,\n verifyingContract: tokenAddress,\n },\n types: {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n },\n primaryType: \"TransferWithAuthorization\",\n message: {\n from: fromAddress,\n to: toAddress,\n value,\n validAfter,\n validBefore,\n nonce,\n },\n };\n}\n\n/** Signs a fresh EIP-3009 authorization for exactly `value` atomic units\n * of tokenAddress, payable to toAddress, valid from ~now until\n * validTimeoutSeconds from now (mirrors the Go/Python signers' own\n * 10-second clock-drift tolerance on validAfter). */\nexport async function signTransferAuthorization(\n privateKey: Hex,\n domainName: string,\n domainVersion: string,\n chainId: number,\n tokenAddress: Hex,\n toAddress: Hex,\n value: bigint,\n validTimeoutSeconds: number,\n): Promise<SignedAuthorization> {\n const account: PrivateKeyAccount = privateKeyToAccount(privateKey);\n const now = BigInt(Math.floor(Date.now() / 1000));\n const validAfter = now - 10n;\n const validBefore = now + BigInt(validTimeoutSeconds);\n const nonce = randomNonceHex();\n\n const typedData = buildTypedData(\n domainName,\n domainVersion,\n chainId,\n tokenAddress,\n account.address,\n toAddress,\n value,\n validAfter,\n validBefore,\n nonce,\n );\n\n const signature = await account.signTypedData(typedData);\n\n return {\n signature,\n fromAddress: account.address,\n toAddress,\n value: value.toString(),\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n };\n}\n","/**\n * HTTP client implementing the client side of the x402 \"exact\" scheme:\n * probe -> 402 challenge -> sign -> pay -> response. Mirrors\n * x402module/poc/pkg/x402protocol/dynamicclient.go's own two-request\n * design (a fresh, unpaid probe carrying the real body, then a SEPARATE\n * paid retry with its own fresh body) and the sibling Python adapters'\n * client.py (langchain-x402-sagg, crewai-x402-sagg) field-for-field.\n * Node's global `fetch` builds a brand-new request body from the object\n * passed to it on each call, so - like the Python adapters using\n * `requests` - there is no equivalent of the mark3labs/x402-go\n * single-consumed-body bug this project found and worked around in Go\n * (PROJECT_STATE.md sec 3.145); noted for comparison, not because a\n * workaround was needed here.\n */\n\nimport { signTransferAuthorization } from \"./signer.js\";\nimport type { Hex } from \"viem\";\n\nconst CHAIN_IDS: Record<string, number> = {\n base: 8453,\n \"base-sepolia\": 84532,\n};\n\nexport class SaggX402PaymentError extends Error {}\n\n/** Shaped around what SAGG's own x402 handler actually reports, not the\n * generic x402 spec's success/errorReason/transaction Settlement shape\n * (see getSettlement's own doc comment for why those two differ). */\nexport interface Settlement {\n ceilingTx: string;\n refundTx: string | null;\n}\n\ninterface PaymentRequirement {\n maxAmountRequired: string;\n asset: Hex;\n payTo: Hex;\n network: string;\n maxTimeoutSeconds?: number;\n extra?: { name?: string; version?: string };\n}\n\ninterface X402Challenge {\n x402Version: number;\n error?: string;\n accepts?: PaymentRequirement[];\n}\n\nexport interface SaggX402ClientOptions {\n privateKey: Hex;\n maxAmountAtomic: bigint;\n network?: string;\n timeoutMs?: number;\n}\n\n/** Pays for x402-gated SAGG requests using an EVM private key, enforcing\n * a hard per-call maximum (maxAmountAtomic) independent of whatever a\n * server's own requirement asks for - the same \"never auto-pay more\n * than this\" discipline as the Go module's WalletSigner and both Python\n * adapters. */\nexport class SaggX402Client {\n private readonly privateKey: Hex;\n private readonly maxAmountAtomic: bigint;\n private readonly network: string;\n private readonly chainId: number;\n private readonly timeoutMs: number;\n\n constructor(options: SaggX402ClientOptions) {\n const network = options.network ?? \"base-sepolia\";\n const chainId = CHAIN_IDS[network];\n if (chainId === undefined) {\n throw new Error(\n `unsupported network ${JSON.stringify(network)}, want one of ${Object.keys(CHAIN_IDS).join(\", \")}`,\n );\n }\n this.privateKey = options.privateKey;\n this.maxAmountAtomic = options.maxAmountAtomic;\n this.network = network;\n this.chainId = chainId;\n this.timeoutMs = options.timeoutMs ?? 60_000;\n }\n\n /** POSTs jsonBody to url. If the first response is a real x402 402\n * challenge, signs the first accepted requirement (refusing if it\n * exceeds maxAmountAtomic) and reposts a FRESH request with the\n * resulting X-PAYMENT header. Any other first response (2xx, or a\n * non-402 error) is returned as-is, unpaid. */\n async postWithPayment(\n url: string,\n jsonBody: Record<string, unknown>,\n ): Promise<Response> {\n const probe = await this.postJson(url, jsonBody);\n if (probe.status !== 402) {\n return probe;\n }\n\n const challenge = (await probe.json()) as X402Challenge;\n const accepts = challenge.accepts ?? [];\n if (accepts.length === 0) {\n throw new SaggX402PaymentError(\n \"402 challenge carried no accepted payment methods\",\n );\n }\n const requirement = accepts[0];\n\n const amount = BigInt(requirement.maxAmountRequired);\n if (amount > this.maxAmountAtomic) {\n throw new SaggX402PaymentError(\n `requirement asks for ${amount} atomic units, exceeds this client's configured max ${this.maxAmountAtomic}`,\n );\n }\n\n const extra = requirement.extra ?? {};\n if (!extra.name || !extra.version) {\n throw new SaggX402PaymentError(\n \"requirement missing EIP-3009 domain name/version in its 'extra' field\",\n );\n }\n\n // this.chainId was fixed once at construction from this.network -\n // correct ONLY as long as the requirement the server actually\n // issued is for that same network. Real bug found live in the\n // sibling langchain-x402-sagg package on the first real mainnet\n // attempt (PROJECT_STATE.md sec 3.172, then confirmed to be the\n // same shape here too): a client built with the default\n // network=\"base-sepolia\" but pointed at a real mainnet SAGG_X402_URL\n // signs with chainId 84532 while the server's requirement is for\n // chainId 8453 - the facilitator correctly rejects the resulting\n // signature as invalid (\"invalid_exact_evm_signature\"), a confusing\n // failure with no clue as to the real cause. Refusing outright on a\n // mismatch (rather than silently trusting whatever the server\n // claims and re-deriving chainId from it) is the safer failure mode\n // too: a testnet-configured client must never be tricked into\n // producing a valid signature for a real mainnet transfer just\n // because it was pointed at the wrong URL.\n if (requirement.network !== this.network) {\n throw new SaggX402PaymentError(\n `requirement's network ${JSON.stringify(requirement.network)} does not match this client's configured network ${JSON.stringify(this.network)} - refusing to sign. This client was likely constructed with the wrong \\`network\\` option for the URL it's actually talking to.`,\n );\n }\n\n const signed = await signTransferAuthorization(\n this.privateKey,\n extra.name,\n extra.version,\n this.chainId,\n requirement.asset,\n requirement.payTo,\n amount,\n requirement.maxTimeoutSeconds ?? 60,\n );\n\n const paymentPayload = {\n x402Version: 1,\n scheme: \"exact\",\n network: requirement.network,\n payload: {\n signature: signed.signature,\n authorization: {\n from: signed.fromAddress,\n to: signed.toAddress,\n value: signed.value,\n validAfter: signed.validAfter,\n validBefore: signed.validBefore,\n nonce: signed.nonce,\n },\n },\n };\n const headerValue = Buffer.from(JSON.stringify(paymentPayload)).toString(\n \"base64\",\n );\n\n return this.postJson(url, jsonBody, { \"X-PAYMENT\": headerValue });\n }\n\n /** Extracts settlement info from SAGG's OWN response headers.\n *\n * A real, live-testing-only finding, not something assumed from the\n * spec or copied from the sibling adapters: the generic x402 spec's\n * `X-PAYMENT-RESPONSE` header (base64 JSON, what x402-go's OWN stock\n * middleware sets, and what x402protocol.GetSettlement (client.go)\n * and the sibling LangChain/CrewAI adapters' get_settlement look for)\n * is NEVER set by SAGG's actual `/v1/chat/completions` x402 path -\n * confirmed by inspecting real response headers from a live local\n * gateway instance, not by reading the handler source alone. SAGG's\n * x402ChatCompletionsHandler (x402billing.go) is built directly on\n * DynamicServer (needed for per-request dynamic pricing - x402-go's\n * own stock middleware only supports one fixed price set at startup),\n * and sets its OWN plain headers instead:\n * `X-SAGG-X402-Ceiling-Tx` (always present on a served response) and\n * `X-SAGG-X402-Refund-Tx` (present only when a refund was actually\n * sent - absent, not empty, when the refund amount was zero OR when\n * sending it failed, e.g. the server wallet lacking gas - see this\n * plugin's README for what that means for a real refund's\n * observability from the client side). No `success`/`errorReason`\n * fields exist in this scheme at all - a served 200 response with a\n * ceiling-tx header IS the success signal. */\n static getSettlement(response: Response): Settlement | null {\n const ceilingTx = response.headers.get(\"X-SAGG-X402-Ceiling-Tx\");\n if (!ceilingTx) {\n return null;\n }\n return {\n ceilingTx,\n refundTx: response.headers.get(\"X-SAGG-X402-Refund-Tx\"),\n };\n }\n\n private async postJson(\n url: string,\n jsonBody: Record<string, unknown>,\n extraHeaders?: Record<string, string>,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...extraHeaders },\n body: JSON.stringify(jsonBody),\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","/**\n * The actual SAGG-backed TEXT_LARGE/TEXT_SMALL model handler. SAGG's own\n * gateway already does provider tiering/failover server-side (Standard\n * vs Super Deal lines - see the main sagg-project repo) - this plugin\n * deliberately does not reimplement a large/small model distinction of\n * its own; both ModelType.TEXT_LARGE and ModelType.TEXT_SMALL are wired\n * to the SAME handler and the same configured model id (see README's\n * \"What this plugin does NOT do\" for this and other scope calls).\n */\n\nimport type { IAgentRuntime } from \"@elizaos/core\";\nimport type { Hex } from \"viem\";\nimport { SaggX402Client, SaggX402PaymentError } from \"./client.js\";\n\n// The real, documented full-form model id (PROJECT_STATE.md sec 3.164) -\n// NOT the short \"deepseek-v4-flash-0731\" form the sibling LangChain/\n// CrewAI adapters default to. That short form is exactly the class of\n// bug that breaks model-name translation for some fallback tiers. Worth\n// fixing in the sibling adapters too, but out of this plugin's own\n// scope - flagged in this plugin's README instead.\nexport const DEFAULT_MODEL = \"deepseek-ai/DeepSeek-V4-Flash-0731\";\nexport const DEFAULT_MAX_AMOUNT_ATOMIC = 1_000_000n; // 1.00 USDC (6 decimals)\nexport const DEFAULT_NETWORK = \"base-sepolia\";\n\ninterface GenerateTextParamsLike {\n prompt?: string;\n system?: string;\n messages?: Array<{ role: string; content: string }>;\n maxTokens?: number;\n temperature?: number;\n stopSequences?: string[];\n}\n\ninterface ChatMessage {\n role: string;\n content: string;\n}\n\n/** One client per plugin instance, built lazily from runtime settings on\n * first use rather than in `init()` - a private key that's missing or\n * wrong should surface as a clear error on the first real request, not\n * silently disable the plugin at startup (ElizaOS loads plugins whose\n * `init` throws inconsistently across versions; failing at call time is\n * the more predictable contract). */\nlet cachedClient: SaggX402Client | null = null;\nlet cachedClientKey: string | null = null;\n\nfunction requireSetting(runtime: IAgentRuntime, key: string): string {\n const value = runtime.getSetting(key);\n if (value === null || value === undefined || value === \"\") {\n throw new Error(\n `plugin-sagg-x402: required setting ${key} is not configured`,\n );\n }\n return String(value);\n}\n\nfunction optionalSetting(runtime: IAgentRuntime, key: string): string | null {\n const value = runtime.getSetting(key);\n if (value === null || value === undefined || value === \"\") {\n return null;\n }\n return String(value);\n}\n\nfunction resolvePrivateKey(runtime: IAgentRuntime): Hex {\n const dedicated = optionalSetting(runtime, \"SAGG_X402_PRIVATE_KEY\");\n const fallback = dedicated ?? optionalSetting(runtime, \"EVM_PRIVATE_KEY\");\n if (!fallback) {\n throw new Error(\n \"plugin-sagg-x402: neither SAGG_X402_PRIVATE_KEY nor EVM_PRIVATE_KEY is configured\",\n );\n }\n return (fallback.startsWith(\"0x\") ? fallback : `0x${fallback}`) as Hex;\n}\n\nfunction getClient(runtime: IAgentRuntime): SaggX402Client {\n const privateKey = resolvePrivateKey(runtime);\n const network = optionalSetting(runtime, \"SAGG_X402_NETWORK\") ?? DEFAULT_NETWORK;\n const maxAmountSetting = optionalSetting(\n runtime,\n \"SAGG_X402_MAX_AMOUNT_ATOMIC\",\n );\n const maxAmountAtomic = maxAmountSetting\n ? BigInt(maxAmountSetting)\n : DEFAULT_MAX_AMOUNT_ATOMIC;\n\n // Cache key covers every input that changes what the client does -\n // a settings change (e.g. rotating the key) must produce a fresh\n // client, not silently keep signing with a stale one.\n const cacheKey = `${privateKey}:${network}:${maxAmountAtomic}`;\n if (cachedClient && cachedClientKey === cacheKey) {\n return cachedClient;\n }\n cachedClient = new SaggX402Client({ privateKey, network, maxAmountAtomic });\n cachedClientKey = cacheKey;\n return cachedClient;\n}\n\nfunction buildMessages(params: GenerateTextParamsLike): ChatMessage[] {\n const messages: ChatMessage[] = [];\n if (params.system) {\n messages.push({ role: \"system\", content: params.system });\n }\n if (params.messages && params.messages.length > 0) {\n for (const m of params.messages) {\n messages.push({ role: m.role, content: m.content });\n }\n return messages;\n }\n if (params.prompt) {\n messages.push({ role: \"user\", content: params.prompt });\n return messages;\n }\n throw new Error(\n \"plugin-sagg-x402: GenerateTextParams carried neither `messages` nor `prompt`\",\n );\n}\n\n/** The handler wired to ModelType.TEXT_LARGE/TEXT_SMALL in index.ts.\n * Pays via x402 on every call - SAGG's x402 path is a genuinely separate\n * accounting mechanism from its API-key/Stripe path (no quota, no\n * subscription), so there is no \"free\" request to fall back to if a\n * payment fails; a payment/HTTP error here is a real, user-visible\n * failure, not something to swallow. */\nexport async function handleGenerateText(\n runtime: IAgentRuntime,\n params: GenerateTextParamsLike,\n): Promise<string> {\n const saggUrl = requireSetting(runtime, \"SAGG_X402_URL\").replace(/\\/+$/, \"\");\n const model = optionalSetting(runtime, \"SAGG_X402_MODEL\") ?? DEFAULT_MODEL;\n const client = getClient(runtime);\n\n const body: Record<string, unknown> = {\n model,\n messages: buildMessages(params),\n };\n if (params.maxTokens !== undefined) body.max_tokens = params.maxTokens;\n if (params.temperature !== undefined) body.temperature = params.temperature;\n if (params.stopSequences && params.stopSequences.length > 0) {\n body.stop = params.stopSequences;\n }\n\n let response: Response;\n try {\n response = await client.postWithPayment(\n `${saggUrl}/v1/chat/completions`,\n body,\n );\n } catch (err) {\n if (err instanceof SaggX402PaymentError) {\n throw new Error(`plugin-sagg-x402: payment refused: ${err.message}`);\n }\n throw err;\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new Error(\n `plugin-sagg-x402: SAGG request failed (${response.status}): ${text}`,\n );\n }\n\n const data = (await response.json()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n const content = data.choices?.[0]?.message?.content;\n if (typeof content !== \"string\") {\n throw new Error(\n \"plugin-sagg-x402: SAGG response had no choices[0].message.content\",\n );\n }\n return content;\n}\n\n/** Exported for tests only - lets a test reset the module-level cache\n * between cases instead of caching a client across unrelated tests. */\nexport function _resetClientCacheForTests(): void {\n cachedClient = null;\n cachedClientKey = null;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-sagg-x402",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "ElizaOS plugin: pay for SAGG LLM completions per-request via the x402 protocol (signed USDC micropayment) instead of an API key.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -64,5 +64,11 @@
64
64
  "typescript": "^5.6.0",
65
65
  "vitest": "^2.1.0"
66
66
  },
67
- "license": "MIT"
67
+ "license": "MIT",
68
+ "author": "SAGG / PrivateDeskAI <support@privatedeskai.com>",
69
+ "homepage": "https://api.privatedeskai.com",
70
+ "bugs": {
71
+ "email": "support@privatedeskai.com",
72
+ "url": "https://api.privatedeskai.com/support"
73
+ }
68
74
  }