From 5c081405e7c0bf26b20b1ec9d6b0c3035771fef6 Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Wed, 5 Aug 2026 23:38:12 -0400 Subject: [PATCH 1/9] feat: add Optume Translations x402 legal translation and document parser action provider --- .../optume_action_provider.py | 261 ++++++++++++++++++ .../optumeActionProvider.ts | 191 +++++++++++++ 2 files changed, 452 insertions(+) create mode 100644 python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py create mode 100644 typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py new file mode 100644 index 000000000..12ccfc798 --- /dev/null +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -0,0 +1,261 @@ +""" +Optume Translations x402 Action Provider for Coinbase AgentKit (Python). + +Enables autonomous AI agents using Coinbase AgentKit to natively parse documents, +extract spatial AST structures, generate legal glossaries, perform legal QA audits, +and execute multi-language legal translations over x402 micropayments ($0.01 - $0.50 USDC) +settled on Base Layer-2. +""" + +import json +import urllib.error +import urllib.request +from typing import Any + +from pydantic import BaseModel, Field + +# --------------------------------------------------------------------------- +# Pydantic Input Schemas for AgentKit Actions +# --------------------------------------------------------------------------- + + +class ParseDocumentInput(BaseModel): + document_url: str = Field( + ..., + description="Public URL or accessible link of the document (PDF, DOCX, XLSX, PPTX, image) to parse.", + ) + + +class ExtractVeritasChunksInput(BaseModel): + document_url: str = Field( + ..., + description="Public URL of document to extract spatial AST layout, table grids, and clause hierarchies.", + ) + + +class AnalyzeLegalDocumentInput(BaseModel): + document_url: str = Field( + ..., + description="Public URL of the legal contract to extract defined terms and generate structured legal glossary.", + ) + + +class CompileContextInput(BaseModel): + document_id: str = Field( + ..., + description="Unique Document ID from pre-translation analysis.", + ) + target_language: str = Field( + "fr", + description="ISO language code for target translation (e.g., 'fr', 'es', 'de', 'ar', 'zh').", + ) + + +class TranslateClausesInput(BaseModel): + document_id: str = Field( + ..., + description="Unique Document ID with compiled context directives.", + ) + target_language: str = Field( + "fr", + description="ISO target language code.", + ) + + +class EvaluateQAInput(BaseModel): + document_id: str = Field( + ..., + description="Unique Document ID of translated legal contract to run QA risk and terminology audit.", + ) + + +class AssembleDocumentInput(BaseModel): + document_id: str = Field( + ..., + description="Unique Document ID to assemble into final spatial-layout-preserved document.", + ) + + +class RunFullPipelineInput(BaseModel): + document_url: str = Field( + ..., + description="Public URL of document to execute complete end-to-end 7-node Veritas legal translation pipeline.", + ) + source_language: str = Field( + "en", + description="ISO source language code (e.g. 'en').", + ) + target_language: str = Field( + "fr", + description="ISO target language code (e.g. 'fr', 'es', 'de', 'ar', 'zh').", + ) + + +# --------------------------------------------------------------------------- +# Optume Action Provider Implementation +# --------------------------------------------------------------------------- + + +class OptumeActionProvider: + """ + Coinbase AgentKit Action Provider for Optume Translations x402 Services. + """ + + def __init__(self, base_url: str = "https://api.optranslations.com"): + self.name = "optume" + self.base_url = base_url.rstrip("/") + + def get_actions(self) -> list[dict[str, Any]]: + """ + Return the list of actions exposed to Coinbase AgentKit. + """ + return [ + { + "name": "parse_document", + "description": "High-speed spatial parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", + "schema": ParseDocumentInput, + "func": self.parse_document, + }, + { + "name": "extract_veritas_chunks", + "description": "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", + "schema": ExtractVeritasChunksInput, + "func": self.extract_veritas_chunks, + }, + { + "name": "analyze_legal_document", + "description": "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", + "schema": AnalyzeLegalDocumentInput, + "func": self.analyze_legal_document, + }, + { + "name": "compile_translation_context", + "description": "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", + "schema": CompileContextInput, + "func": self.compile_translation_context, + }, + { + "name": "translate_legal_clauses", + "description": "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", + "schema": TranslateClausesInput, + "func": self.translate_legal_clauses, + }, + { + "name": "audit_legal_qa", + "description": "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", + "schema": EvaluateQAInput, + "func": self.audit_legal_qa, + }, + { + "name": "assemble_translated_document", + "description": "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", + "schema": AssembleDocumentInput, + "func": self.assemble_translated_document, + }, + { + "name": "run_full_veritas_pipeline", + "description": "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.500 USDC on Base L2).", + "schema": RunFullPipelineInput, + "func": self.run_full_veritas_pipeline, + }, + ] + + def _execute_x402_request( + self, + endpoint_path: str, + payload: dict[str, Any], + x402_payment_proof: str | None = None, + ) -> dict[str, Any]: + """ + Helper method to execute HTTP POST requests to Optume x402 endpoints. + Returns the JSON response or the x402 payment required challenge. + """ + url = f"{self.base_url}{endpoint_path}" + data = json.dumps(payload).encode("utf-8") + headers = {"Content-Type": "application/json"} + + if x402_payment_proof: + headers["X-402-Payment-Proof"] = x402_payment_proof + + req = urllib.request.Request(url, data=data, headers=headers, method="POST") + + try: + with urllib.request.urlopen(req) as response: + body = response.read().decode("utf-8") + return json.loads(body) + except urllib.error.HTTPError as e: + body = e.read().decode("utf-8") + try: + error_json = json.loads(body) + except Exception: + error_json = {"error": str(e), "raw_body": body} + + return { + "status_code": e.code, + "x402_challenge": { + "price_usdc": e.headers.get("X-402-Price-USDC"), + "pay_to": e.headers.get("X-402-Pay-To"), + "network": e.headers.get("X-402-Network", "base"), + }, + "response": error_json, + } + except Exception as e: + return {"error": str(e)} + + # Action implementations + def parse_document( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request("/api/v1/parser/analyze", args, x402_proof) + + def extract_veritas_chunks( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request( + "/api/v1/parser/veritas-chunks", args, x402_proof + ) + + def analyze_legal_document( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request("/api/v1/veritas/analyze", args, x402_proof) + + def compile_translation_context( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request( + "/api/v1/veritas/compile-context", args, x402_proof + ) + + def translate_legal_clauses( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request("/api/v1/veritas/translate", args, x402_proof) + + def audit_legal_qa( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request( + "/api/v1/veritas/evaluate-qa", args, x402_proof + ) + + def assemble_translated_document( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request( + "/api/v1/veritas/assemble-document", args, x402_proof + ) + + def run_full_veritas_pipeline( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> dict[str, Any]: + return self._execute_x402_request("/api/v1/veritas/run-full", args, x402_proof) + + +def optume_action_provider( + base_url: str = "https://api.optranslations.com", +) -> OptumeActionProvider: + """ + Factory function for OptumeActionProvider. + """ + return OptumeActionProvider(base_url=base_url) diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts new file mode 100644 index 000000000..421126296 --- /dev/null +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -0,0 +1,191 @@ +/** + * Optume Translations x402 Action Provider for Coinbase AgentKit (TypeScript). + * + * Enables autonomous AI agents using Coinbase AgentKit to natively parse documents, + * extract spatial AST structures, generate legal glossaries, perform legal QA audits, + * and execute multi-language legal translations over x402 micropayments ($0.01 - $0.50 USDC) + * settled on Base Layer-2. + */ + +import { z } from "zod"; + +export const ParseDocumentSchema = z.object({ + documentUrl: z + .string() + .url() + .describe("Public URL or accessible link of the document (PDF, DOCX, XLSX, PPTX, image) to parse."), +}); + +export const ExtractVeritasChunksSchema = z.object({ + documentUrl: z + .string() + .url() + .describe("Public URL of document to extract spatial AST layout, table grids, and clause hierarchies."), +}); + +export const AnalyzeLegalDocumentSchema = z.object({ + documentUrl: z + .string() + .url() + .describe("Public URL of the legal contract to extract defined terms and generate structured legal glossary."), +}); + +export const CompileContextSchema = z.object({ + documentId: z.string().describe("Unique Document ID from pre-translation analysis."), + targetLanguage: z + .string() + .default("fr") + .describe("ISO language code for target translation (e.g., 'fr', 'es', 'de', 'ar', 'zh')."), +}); + +export const TranslateClausesSchema = z.object({ + documentId: z.string().describe("Unique Document ID with compiled context directives."), + targetLanguage: z.string().default("fr").describe("ISO target language code."), +}); + +export const EvaluateQASchema = z.object({ + documentId: z + .string() + .describe("Unique Document ID of translated legal contract to run QA risk and terminology audit."), +}); + +export const AssembleDocumentSchema = z.object({ + documentId: z + .string() + .describe("Unique Document ID to assemble into final spatial-layout-preserved document."), +}); + +export const RunFullPipelineSchema = z.object({ + documentUrl: z + .string() + .url() + .describe("Public URL of document to execute complete end-to-end 7-node Veritas legal translation pipeline."), + sourceLanguage: z.string().default("en").describe("ISO source language code (e.g. 'en')."), + targetLanguage: z.string().default("fr").describe("ISO target language code (e.g. 'fr', 'es', 'de', 'ar', 'zh')."), +}); + +export class OptumeActionProvider { + public name = "optume"; + public baseUrl: string; + + constructor(baseUrl: string = "https://api.optranslations.com") { + this.baseUrl = baseUrl.replace(/\/$/, ""); + } + + /** + * Return catalog of x402 actions exposed to Coinbase AgentKit. + */ + public getActions() { + return [ + { + name: "parse_document", + description: + "High-speed spatial document parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", + schema: ParseDocumentSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/parser/analyze", args, proof), + }, + { + name: "extract_veritas_chunks", + description: + "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", + schema: ExtractVeritasChunksSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/parser/veritas-chunks", args, proof), + }, + { + name: "analyze_legal_document", + description: + "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", + schema: AnalyzeLegalDocumentSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/veritas/analyze", args, proof), + }, + { + name: "compile_translation_context", + description: + "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", + schema: CompileContextSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/veritas/compile-context", args, proof), + }, + { + name: "translate_legal_clauses", + description: + "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", + schema: TranslateClausesSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/veritas/translate", args, proof), + }, + { + name: "audit_legal_qa", + description: + "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", + schema: EvaluateQASchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/veritas/evaluate-qa", args, proof), + }, + { + name: "assemble_translated_document", + description: + "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", + schema: AssembleDocumentSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/veritas/assemble-document", args, proof), + }, + { + name: "run_full_veritas_pipeline", + description: + "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.500 USDC on Base L2).", + schema: RunFullPipelineSchema, + invoke: (args: z.infer, proof?: string) => + this.executeX402Request("/api/v1/veritas/run-full", args, proof), + }, + ]; + } + + private async executeX402Request( + endpointPath: string, + payload: Record, + x402PaymentProof?: string + ): Promise { + const url = `${this.baseUrl}${endpointPath}`; + const headers: Record = { + "Content-Type": "application/json", + }; + + if (x402PaymentProof) { + headers["X-402-Payment-Proof"] = x402PaymentProof; + } + + try { + const response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(payload), + }); + + const body = await response.json(); + + if (!response.ok) { + return { + statusCode: response.status, + x402Challenge: { + priceUsdc: response.headers.get("x-402-price-usdc"), + payTo: response.headers.get("x-402-pay-to"), + network: response.headers.get("x-402-network") || "base", + }, + response: body, + }; + } + + return body; + } catch (error) { + return { error: String(error) }; + } + } +} + +export function optumeActionProvider(baseUrl: string = "https://api.optranslations.com"): OptumeActionProvider { + return new OptumeActionProvider(baseUrl); +} From 19ecd0297566d274445dbb5b2289f43744f7cc49 Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Wed, 5 Aug 2026 23:43:25 -0400 Subject: [PATCH 2/9] feat: export Optume Translations x402 action provider in package indexes and changelogs --- python/coinbase-agentkit/CHANGELOG.md | 4 ++++ .../coinbase_agentkit/action_providers/__init__.py | 6 ++++++ typescript/agentkit/CHANGELOG.md | 2 ++ typescript/agentkit/src/action-providers/index.ts | 1 + 4 files changed, 13 insertions(+) diff --git a/python/coinbase-agentkit/CHANGELOG.md b/python/coinbase-agentkit/CHANGELOG.md index 533c0e889..1d095465d 100644 --- a/python/coinbase-agentkit/CHANGELOG.md +++ b/python/coinbase-agentkit/CHANGELOG.md @@ -2,6 +2,10 @@ +### Added + +- Added Optume Translations x402 Legal Translation and Document Parser Action Provider (`OptumeActionProvider`). + ## [0.7.4] - 2025-10-03 ### Fixed diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py index 68573da62..5da4aab73 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py @@ -26,6 +26,10 @@ from .morpho.morpho_action_provider import MorphoActionProvider, morpho_action_provider from .nillion.nillion_action_provider import NillionActionProvider, nillion_action_provider from .onramp.onramp_action_provider import OnrampActionProvider, onramp_action_provider +from .optume_translations_x402.optume_action_provider import ( + OptumeActionProvider, + optume_action_provider, +) from .pyth.pyth_action_provider import PythActionProvider, pyth_action_provider from .ssh.ssh_action_provider import SshActionProvider, ssh_action_provider from .superfluid.superfluid_action_provider import ( @@ -54,6 +58,7 @@ "MorphoActionProvider", "NillionActionProvider", "OnrampActionProvider", + "OptumeActionProvider", "PythActionProvider", "SshActionProvider", "SuperfluidActionProvider", @@ -75,6 +80,7 @@ "morpho_action_provider", "nillion_action_provider", "onramp_action_provider", + "optume_action_provider", "pyth_action_provider", "ssh_action_provider", "superfluid_action_provider", diff --git a/typescript/agentkit/CHANGELOG.md b/typescript/agentkit/CHANGELOG.md index fa4b2af96..b1170e058 100644 --- a/typescript/agentkit/CHANGELOG.md +++ b/typescript/agentkit/CHANGELOG.md @@ -10,6 +10,8 @@ ### Patch Changes +- [#1036](https://github.com/coinbase/agentkit/pull/1036) Added Optume Translations x402 Legal Translation and Document Parser Action Provider (`OptumeActionProvider`). + - [#966](https://github.com/coinbase/agentkit/pull/966) [`b211701`](https://github.com/coinbase/agentkit/commit/b21170143825cb1892daaa8e52c68e9c8c446ae1) Thanks [@phdargen](https://github.com/phdargen)! - Bumped x402 packages and fix missing readContract interface - [#982](https://github.com/coinbase/agentkit/pull/982) [`c3dbef6`](https://github.com/coinbase/agentkit/commit/c3dbef60d1613effc9d9805816bec15f5510fdca) Thanks [@fffilimonov](https://github.com/fffilimonov)! - Added dTelecom action provider for decentralized voice services (WebRTC, STT, TTS) with x402 micropayments, and a voice agent example. diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 9f7164086..e1a1abbf5 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -34,6 +34,7 @@ export * from "./wow"; export * from "./allora"; export * from "./flaunch"; export * from "./onramp"; +export * from "./optume_translations_x402/optumeActionProvider"; export * from "./vaultsfyi"; export * from "./x402"; export * from "./yelay"; From ea038a6afd503f834c79a2836b246e33d5b0459f Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 00:22:56 -0400 Subject: [PATCH 3/9] chore: update fallback network to CAIP-2 eip155:8453 for x402 v2 spec compliance --- .../optume_translations_x402/optume_action_provider.py | 2 +- .../optume_translations_x402/optumeActionProvider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 12ccfc798..650febf77 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -195,7 +195,7 @@ def _execute_x402_request( "x402_challenge": { "price_usdc": e.headers.get("X-402-Price-USDC"), "pay_to": e.headers.get("X-402-Pay-To"), - "network": e.headers.get("X-402-Network", "base"), + "network": e.headers.get("X-402-Network", "eip155:8453"), }, "response": error_json, } diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts index 421126296..707e38269 100644 --- a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -173,7 +173,7 @@ export class OptumeActionProvider { x402Challenge: { priceUsdc: response.headers.get("x-402-price-usdc"), payTo: response.headers.get("x-402-pay-to"), - network: response.headers.get("x-402-network") || "base", + network: response.headers.get("x-402-network") || "eip155:8453", }, response: body, }; From 4a8a78b35225179ba439579219604e7937cefa30 Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 05:36:57 -0400 Subject: [PATCH 4/9] feat: add veritas_legal_translation action with zsh.0005/word dynamic pricing --- .../optume_action_provider.py | 409 ++++++++++++++---- .../optumeActionProvider.ts | 352 ++++++++++++--- 2 files changed, 622 insertions(+), 139 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 650febf77..36b0fd0a4 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -9,11 +9,41 @@ import json import urllib.error +import urllib.parse import urllib.request from typing import Any from pydantic import BaseModel, Field +# Graceful import of Coinbase AgentKit ActionProvider base class & decorator +try: + from coinbase_agentkit.action_providers.action_decorator import create_action + from coinbase_agentkit.action_providers.action_provider import ActionProvider + + HAS_AGENTKIT = True +except ImportError: + HAS_AGENTKIT = False + + class ActionProvider: + """Fallback ActionProvider base class when coinbase-agentkit is not installed.""" + + def __init__( + self, name: str = "", dependencies: list[Any] | None = None + ) -> None: + self.name = name + + def get_actions(self, wallet_provider: Any = None) -> list[Any]: + return [] + + def create_action(*args: Any, **kwargs: Any) -> Any: + """Fallback decorator for create_action.""" + + def decorator(func: Any) -> Any: + return func + + return decorator + + # --------------------------------------------------------------------------- # Pydantic Input Schemas for AgentKit Actions # --------------------------------------------------------------------------- @@ -91,74 +121,112 @@ class RunFullPipelineInput(BaseModel): ) +# --------------------------------------------------------------------------- +# Custom Safe HTTP Redirect Handler +# --------------------------------------------------------------------------- + + +class SafeRedirectHandler(urllib.request.HTTPRedirectHandler): + """ + Custom redirect handler enforcing HTTPS origin policy and rejecting + cross-origin or HTTP downgrade redirects. + """ + + def redirect_request( + self, + req: urllib.request.Request, + fp: Any, + code: int, + msg: str, + headers: Any, + newurl: str, + ) -> urllib.request.Request | None: + orig_parsed = urllib.parse.urlparse(req.full_url) + new_parsed = urllib.parse.urlparse(newurl) + + # Reject origin change + if orig_parsed.netloc != new_parsed.netloc: + raise urllib.error.HTTPError( + newurl, + code, + f"Redirect rejected: cross-origin redirect to {new_parsed.netloc} is forbidden.", + headers, + fp, + ) + + # Reject protocol downgrade from HTTPS to HTTP + if orig_parsed.scheme == "https" and new_parsed.scheme != "https": + raise urllib.error.HTTPError( + newurl, + code, + "Redirect rejected: protocol downgrade from HTTPS to HTTP is forbidden.", + headers, + fp, + ) + + new_req = super().redirect_request(req, fp, code, msg, headers, newurl) + if new_req: + # Preserve X-402-Payment-Proof header across allowed redirects + proof_header = req.headers.get("X-402-payment-proof") or req.headers.get( + "X-402-Payment-Proof" + ) + if proof_header: + new_req.add_header("X-402-Payment-Proof", proof_header) + return new_req + + # --------------------------------------------------------------------------- # Optume Action Provider Implementation # --------------------------------------------------------------------------- -class OptumeActionProvider: +class OptumeActionProvider(ActionProvider): """ Coinbase AgentKit Action Provider for Optume Translations x402 Services. """ def __init__(self, base_url: str = "https://api.optranslations.com"): - self.name = "optume" + if HAS_AGENTKIT: + super().__init__("optume", []) + else: + self.name = "optume" + + parsed = urllib.parse.urlparse(base_url) + + # Validate HTTPS-origin policy (allowing http only for localhost/127.0.0.1 in local dev) + if parsed.scheme not in ("https", "http"): + raise ValueError( + f"Invalid base_url scheme '{parsed.scheme}'. Must be HTTPS." + ) + if parsed.scheme == "http" and parsed.hostname not in ( + "localhost", + "127.0.0.1", + "testserver", + ): + raise ValueError("Insecure HTTP base_url allowed only for local testing.") + self.base_url = base_url.rstrip("/") - def get_actions(self) -> list[dict[str, Any]]: + def supports_network(self, network: Any) -> bool: """ - Return the list of actions exposed to Coinbase AgentKit. + Check whether target EVM network is supported (Base Mainnet / Sepolia). """ - return [ - { - "name": "parse_document", - "description": "High-speed spatial parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", - "schema": ParseDocumentInput, - "func": self.parse_document, - }, - { - "name": "extract_veritas_chunks", - "description": "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", - "schema": ExtractVeritasChunksInput, - "func": self.extract_veritas_chunks, - }, - { - "name": "analyze_legal_document", - "description": "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", - "schema": AnalyzeLegalDocumentInput, - "func": self.analyze_legal_document, - }, - { - "name": "compile_translation_context", - "description": "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", - "schema": CompileContextInput, - "func": self.compile_translation_context, - }, - { - "name": "translate_legal_clauses", - "description": "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", - "schema": TranslateClausesInput, - "func": self.translate_legal_clauses, - }, - { - "name": "audit_legal_qa", - "description": "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", - "schema": EvaluateQAInput, - "func": self.audit_legal_qa, - }, - { - "name": "assemble_translated_document", - "description": "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", - "schema": AssembleDocumentInput, - "func": self.assemble_translated_document, - }, - { - "name": "run_full_veritas_pipeline", - "description": "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.500 USDC on Base L2).", - "schema": RunFullPipelineInput, - "func": self.run_full_veritas_pipeline, - }, - ] + net_str = str(network).lower().strip() + if hasattr(network, "network_id"): + net_str = str(network.network_id).lower().strip() + elif hasattr(network, "chain_id"): + net_str = str(network.chain_id).lower().strip() + + supported_bases = { + "base", + "8453", + "84532", + "eip155:8453", + "eip155:84532", + "base-mainnet", + "base-sepolia", + } + return net_str in supported_bases def _execute_x402_request( self, @@ -167,8 +235,7 @@ def _execute_x402_request( x402_payment_proof: str | None = None, ) -> dict[str, Any]: """ - Helper method to execute HTTP POST requests to Optume x402 endpoints. - Returns the JSON response or the x402 payment required challenge. + Helper method to execute HTTP POST requests to Optume x402 endpoints with redirect security. """ url = f"{self.base_url}{endpoint_path}" data = json.dumps(payload).encode("utf-8") @@ -178,13 +245,14 @@ def _execute_x402_request( headers["X-402-Payment-Proof"] = x402_payment_proof req = urllib.request.Request(url, data=data, headers=headers, method="POST") + opener = urllib.request.build_opener(SafeRedirectHandler()) try: - with urllib.request.urlopen(req) as response: + with opener.open(req, timeout=30.0) as response: body = response.read().decode("utf-8") return json.loads(body) except urllib.error.HTTPError as e: - body = e.read().decode("utf-8") + body = e.read().decode("utf-8") if hasattr(e, "read") else str(e) try: error_json = json.loads(body) except Exception: @@ -193,63 +261,236 @@ def _execute_x402_request( return { "status_code": e.code, "x402_challenge": { - "price_usdc": e.headers.get("X-402-Price-USDC"), - "pay_to": e.headers.get("X-402-Pay-To"), - "network": e.headers.get("X-402-Network", "eip155:8453"), + "price_usdc": e.headers.get("X-402-Price-USDC") + if hasattr(e, "headers") + else None, + "pay_to": e.headers.get("X-402-Pay-To") + if hasattr(e, "headers") + else None, + "network": e.headers.get("X-402-Network", "eip155:8453") + if hasattr(e, "headers") + else "eip155:8453", }, "response": error_json, } except Exception as e: return {"error": str(e)} - # Action implementations + # --------------------------------------------------------------------------- + # AgentKit Decorated Action Methods + # --------------------------------------------------------------------------- + + @create_action( + name="parse_document", + description="High-speed spatial parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", + schema=ParseDocumentInput, + ) def parse_document( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request("/api/v1/parser/analyze", args, x402_proof) - + ) -> str | dict[str, Any]: + validated = ( + ParseDocumentInput.model_validate(args) if isinstance(args, dict) else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request("/api/v1/parser/analyze", payload, x402_proof) + return json.dumps(res) if HAS_AGENTKIT else res + + @create_action( + name="extract_veritas_chunks", + description="Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", + schema=ExtractVeritasChunksInput, + ) def extract_veritas_chunks( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request( - "/api/v1/parser/veritas-chunks", args, x402_proof + ) -> str | dict[str, Any]: + validated = ( + ExtractVeritasChunksInput.model_validate(args) + if isinstance(args, dict) + else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request( + "/api/v1/parser/veritas-chunks", payload, x402_proof ) + return json.dumps(res) if HAS_AGENTKIT else res + @create_action( + name="analyze_legal_document", + description="Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", + schema=AnalyzeLegalDocumentInput, + ) def analyze_legal_document( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request("/api/v1/veritas/analyze", args, x402_proof) - + ) -> str | dict[str, Any]: + validated = ( + AnalyzeLegalDocumentInput.model_validate(args) + if isinstance(args, dict) + else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request("/api/v1/veritas/analyze", payload, x402_proof) + return json.dumps(res) if HAS_AGENTKIT else res + + @create_action( + name="compile_translation_context", + description="Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", + schema=CompileContextInput, + ) def compile_translation_context( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request( - "/api/v1/veritas/compile-context", args, x402_proof + ) -> str | dict[str, Any]: + validated = ( + CompileContextInput.model_validate(args) if isinstance(args, dict) else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request( + "/api/v1/veritas/compile-context", payload, x402_proof ) + return json.dumps(res) if HAS_AGENTKIT else res + @create_action( + name="translate_legal_clauses", + description="Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", + schema=TranslateClausesInput, + ) def translate_legal_clauses( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request("/api/v1/veritas/translate", args, x402_proof) + ) -> str | dict[str, Any]: + validated = ( + TranslateClausesInput.model_validate(args) + if isinstance(args, dict) + else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request( + "/api/v1/veritas/translate", payload, x402_proof + ) + return json.dumps(res) if HAS_AGENTKIT else res + @create_action( + name="audit_legal_qa", + description="Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", + schema=EvaluateQAInput, + ) def audit_legal_qa( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request( - "/api/v1/veritas/evaluate-qa", args, x402_proof + ) -> str | dict[str, Any]: + validated = ( + EvaluateQAInput.model_validate(args) if isinstance(args, dict) else args ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request( + "/api/v1/veritas/evaluate-qa", payload, x402_proof + ) + return json.dumps(res) if HAS_AGENTKIT else res + @create_action( + name="assemble_translated_document", + description="Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", + schema=AssembleDocumentInput, + ) def assemble_translated_document( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request( - "/api/v1/veritas/assemble-document", args, x402_proof + ) -> str | dict[str, Any]: + validated = ( + AssembleDocumentInput.model_validate(args) + if isinstance(args, dict) + else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request( + "/api/v1/veritas/assemble-document", payload, x402_proof + ) + return json.dumps(res) if HAS_AGENTKIT else res + + @create_action( + name="veritas_legal_translation", + description="Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", + schema=RunFullPipelineInput, + ) + def veritas_legal_translation( + self, args: dict[str, Any], x402_proof: str | None = None + ) -> str | dict[str, Any]: + validated = ( + RunFullPipelineInput.model_validate(args) + if isinstance(args, dict) + else args + ) + payload = validated.model_dump() if hasattr(validated, "model_dump") else args + res = self._execute_x402_request( + "/api/v1/veritas/run-full", payload, x402_proof ) + return json.dumps(res) if HAS_AGENTKIT else res + @create_action( + name="run_full_veritas_pipeline", + description="Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.0005/word, min $0.05 USDC on Base L2).", + schema=RunFullPipelineInput, + ) def run_full_veritas_pipeline( self, args: dict[str, Any], x402_proof: str | None = None - ) -> dict[str, Any]: - return self._execute_x402_request("/api/v1/veritas/run-full", args, x402_proof) + ) -> str | dict[str, Any]: + return self.veritas_legal_translation(args, x402_proof) + + def get_actions(self, wallet_provider: Any = None) -> list[Any]: + """ + Return the list of actions exposed to Coinbase AgentKit. + """ + if HAS_AGENTKIT and hasattr(super(), "get_actions"): + actions = super().get_actions(wallet_provider) + if actions: + return actions + + return [ + { + "name": "parse_document", + "description": "High-speed spatial parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", + "schema": ParseDocumentInput, + "func": self.parse_document, + }, + { + "name": "extract_veritas_chunks", + "description": "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", + "schema": ExtractVeritasChunksInput, + "func": self.extract_veritas_chunks, + }, + { + "name": "analyze_legal_document", + "description": "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", + "schema": AnalyzeLegalDocumentInput, + "func": self.analyze_legal_document, + }, + { + "name": "compile_translation_context", + "description": "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", + "schema": CompileContextInput, + "func": self.compile_translation_context, + }, + { + "name": "translate_legal_clauses", + "description": "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", + "schema": TranslateClausesInput, + "func": self.translate_legal_clauses, + }, + { + "name": "audit_legal_qa", + "description": "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", + "schema": EvaluateQAInput, + "func": self.audit_legal_qa, + }, + { + "name": "assemble_translated_document", + "description": "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", + "schema": AssembleDocumentInput, + "func": self.assemble_translated_document, + }, + { + "name": "run_full_veritas_pipeline", + "description": "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.500 USDC on Base L2).", + "schema": RunFullPipelineInput, + "func": self.run_full_veritas_pipeline, + }, + ] def optume_action_provider( diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts index 707e38269..1654bb0a8 100644 --- a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -23,12 +23,19 @@ export const ExtractVeritasChunksSchema = z.object({ .describe("Public URL of document to extract spatial AST layout, table grids, and clause hierarchies."), }); -export const AnalyzeLegalDocumentSchema = z.object({ - documentUrl: z - .string() - .url() - .describe("Public URL of the legal contract to extract defined terms and generate structured legal glossary."), -}); +export const AnalyzeLegalDocumentSchema = z + .object({ + rawText: z.string().optional().describe("Raw legal contract text to ingest and analyze."), + documentUrl: z + .string() + .url() + .optional() + .describe("Public URL of legal contract to analyze."), + targetLanguage: z.string().default("fr").describe("ISO target language code."), + }) + .refine((data) => Boolean(data.rawText || data.documentUrl), { + message: "At least one of rawText or documentUrl must be provided.", + }); export const CompileContextSchema = z.object({ documentId: z.string().describe("Unique Document ID from pre-translation analysis."), @@ -41,6 +48,13 @@ export const CompileContextSchema = z.object({ export const TranslateClausesSchema = z.object({ documentId: z.string().describe("Unique Document ID with compiled context directives."), targetLanguage: z.string().default("fr").describe("ISO target language code."), + concurrencyLimit: z + .number() + .int() + .positive() + .max(50) + .default(10) + .describe("Parallel translation concurrency limit."), }); export const EvaluateQASchema = z.object({ @@ -55,21 +69,113 @@ export const AssembleDocumentSchema = z.object({ .describe("Unique Document ID to assemble into final spatial-layout-preserved document."), }); -export const RunFullPipelineSchema = z.object({ - documentUrl: z - .string() - .url() - .describe("Public URL of document to execute complete end-to-end 7-node Veritas legal translation pipeline."), - sourceLanguage: z.string().default("en").describe("ISO source language code (e.g. 'en')."), - targetLanguage: z.string().default("fr").describe("ISO target language code (e.g. 'fr', 'es', 'de', 'ar', 'zh')."), -}); +export const RunFullPipelineSchema = z + .object({ + rawText: z.string().optional().describe("Raw text of contract to translate."), + documentUrl: z + .string() + .url() + .optional() + .describe("Public URL of document to execute complete end-to-end 7-node Veritas legal translation pipeline."), + sourceLanguage: z.string().default("en").describe("ISO source language code (e.g. 'en')."), + targetLanguage: z.string().default("fr").describe("ISO target language code (e.g. 'fr', 'es', 'de', 'ar', 'zh')."), + }) + .refine((data) => Boolean(data.rawText || data.documentUrl), { + message: "At least one of rawText or documentUrl must be provided.", + }); -export class OptumeActionProvider { +export interface ActionProvider { + name: string; + supportsNetwork(network: Record | string): boolean; + getActions(): Array<{ + name: string; + description: string; + schema: z.ZodTypeAny; + invoke: (args: Record, proof?: string) => Promise; + }>; +} + +export class OptumeActionProvider implements ActionProvider { public name = "optume"; public baseUrl: string; constructor(baseUrl: string = "https://api.optranslations.com") { - this.baseUrl = baseUrl.replace(/\/$/, ""); + const trimmed = baseUrl.replace(/\/$/, ""); + try { + const parsed = new URL(trimmed); + if ( + parsed.protocol !== "https:" && + !( + parsed.protocol === "http:" && + (parsed.hostname === "localhost" || + parsed.hostname === "127.0.0.1" || + parsed.hostname === "testserver") + ) + ) { + throw new Error("Invalid baseUrl protocol. Must use HTTPS for remote endpoints."); + } + this.baseUrl = trimmed; + } catch (err) { + throw err; + } + } + + /** + * Check whether target EVM network is supported (Base Mainnet / Sepolia). + */ + public supportsNetwork(network: Record | string): boolean { + let netStr = ""; + if (typeof network === "string") { + netStr = network.toLowerCase().trim(); + } else if (network && typeof network === "object") { + const rec = network as Record; + netStr = String(rec.networkId || rec.chainId || JSON.stringify(network)).toLowerCase().trim(); + } + const supportedBases = new Set([ + "base", + "8453", + "84532", + "eip155:8453", + "eip155:84532", + "base-mainnet", + "base-sepolia", + ]); + return supportedBases.has(netStr); + } + + private buildChallengeResponse(response: Response, body: unknown): Record { + return { + statusCode: response.status, + x402Challenge: { + priceUsdc: response.headers.get("x-402-price-usdc"), + payTo: response.headers.get("x-402-pay-to"), + network: response.headers.get("x-402-network") || "eip155:8453", + }, + response: body, + }; + } + + private validateRedirect( + response: Response, + currentUrl: string + ): { redirectUrl?: string; error?: string } { + if (response.status < 300 || response.status >= 400) { + return {}; + } + const location = response.headers.get("location"); + if (!location) { + return {}; + } + const currentParsed = new URL(currentUrl); + const targetParsed = new URL(location, currentUrl); + + if (targetParsed.hostname !== currentParsed.hostname) { + return { error: `Redirect rejected: Cross-origin redirect to ${targetParsed.hostname} is forbidden.` }; + } + if (currentParsed.protocol === "https:" && targetParsed.protocol !== "https:") { + return { error: "Redirect rejected: Protocol downgrade from HTTPS to HTTP is forbidden." }; + } + return { redirectUrl: targetParsed.toString() }; } /** @@ -82,74 +188,203 @@ export class OptumeActionProvider { description: "High-speed spatial document parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", schema: ParseDocumentSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/parser/analyze", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = ParseDocumentSchema.parse(args); + return JSON.stringify(await this.executeFileUploadRequest("/api/v1/parser/analyze", parsed.documentUrl, proof)); + }, }, { name: "extract_veritas_chunks", description: "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", schema: ExtractVeritasChunksSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/parser/veritas-chunks", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = ExtractVeritasChunksSchema.parse(args); + return JSON.stringify(await this.executeFileUploadRequest("/api/v1/parser/veritas-chunks", parsed.documentUrl, proof)); + }, }, { name: "analyze_legal_document", description: "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", schema: AnalyzeLegalDocumentSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/veritas/analyze", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = AnalyzeLegalDocumentSchema.parse(args); + let rawText = parsed.rawText || ""; + if (!rawText && parsed.documentUrl) { + const resp = await fetch(parsed.documentUrl); + if (!resp.ok) { + return JSON.stringify({ error: `Failed to download document from URL: ${resp.statusText}` }); + } + rawText = await resp.text(); + } + const body = { + raw_text: rawText, + target_language: parsed.targetLanguage || "fr", + }; + return JSON.stringify(await this.executeX402Request("/api/v1/veritas/analyze", body, proof)); + }, }, { name: "compile_translation_context", description: "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", schema: CompileContextSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/veritas/compile-context", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = CompileContextSchema.parse(args); + const path = `/api/v1/veritas/compile-context?document_id=${encodeURIComponent(parsed.documentId)}`; + return JSON.stringify(await this.executeX402Request(path, {}, proof)); + }, }, { name: "translate_legal_clauses", description: "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", schema: TranslateClausesSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/veritas/translate", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = TranslateClausesSchema.parse(args); + const path = `/api/v1/veritas/translate?document_id=${encodeURIComponent(parsed.documentId)}&concurrency_limit=${parsed.concurrencyLimit || 10}`; + return JSON.stringify(await this.executeX402Request(path, {}, proof)); + }, }, { name: "audit_legal_qa", description: "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", schema: EvaluateQASchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/veritas/evaluate-qa", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = EvaluateQASchema.parse(args); + const path = `/api/v1/veritas/evaluate-qa?document_id=${encodeURIComponent(parsed.documentId)}`; + return JSON.stringify(await this.executeX402Request(path, {}, proof)); + }, }, { name: "assemble_translated_document", description: "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", schema: AssembleDocumentSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/veritas/assemble-document", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = AssembleDocumentSchema.parse(args); + const path = `/api/v1/veritas/assemble-document?document_id=${encodeURIComponent(parsed.documentId)}`; + return JSON.stringify(await this.executeX402Request(path, {}, proof)); + }, + }, + { + name: "veritas_legal_translation", + description: + "Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", + schema: RunFullPipelineSchema, + invoke: async (args: Record, proof?: string) => { + const parsed = RunFullPipelineSchema.parse(args); + let rawText = parsed.rawText || ""; + if (!rawText && parsed.documentUrl) { + const resp = await fetch(parsed.documentUrl); + if (!resp.ok) { + return JSON.stringify({ error: `Failed to download document from URL: ${resp.statusText}` }); + } + rawText = await resp.text(); + } + const body = { + raw_text: rawText, + source_language: parsed.sourceLanguage || "en", + target_language: parsed.targetLanguage || "fr", + }; + return JSON.stringify(await this.executeX402Request("/api/v1/veritas/run-full", body, proof)); + }, }, { name: "run_full_veritas_pipeline", description: - "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.500 USDC on Base L2).", + "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.0005/word, min $0.05 USDC on Base L2).", schema: RunFullPipelineSchema, - invoke: (args: z.infer, proof?: string) => - this.executeX402Request("/api/v1/veritas/run-full", args, proof), + invoke: async (args: Record, proof?: string) => { + const parsed = RunFullPipelineSchema.parse(args); + let rawText = parsed.rawText || ""; + if (!rawText && parsed.documentUrl) { + const resp = await fetch(parsed.documentUrl); + if (!resp.ok) { + return JSON.stringify({ error: `Failed to download document from URL: ${resp.statusText}` }); + } + rawText = await resp.text(); + } + const body = { + raw_text: rawText, + source_language: parsed.sourceLanguage || "en", + target_language: parsed.targetLanguage || "fr", + }; + return JSON.stringify(await this.executeX402Request("/api/v1/veritas/run-full", body, proof)); + }, }, ]; } - private async executeX402Request( + public async executeFileUploadRequest( + endpointPath: string, + documentUrl: string, + x402PaymentProof?: string + ): Promise { + const docResp = await fetch(documentUrl); + if (!docResp.ok) { + return { error: `Failed to download document from URL: ${docResp.statusText}` }; + } + const blob = await docResp.blob(); + + let fileName = "document.pdf"; + try { + fileName = new URL(documentUrl).pathname.split("/").pop() || "document.pdf"; + } catch { + fileName = documentUrl.split("/").pop() || "document.pdf"; + } + + const formData = new FormData(); + formData.append("file", blob, fileName); + + let targetUrl = `${this.baseUrl}${endpointPath}`; + const headers: Record = {}; + if (x402PaymentProof) { + headers["X-402-Payment-Proof"] = x402PaymentProof; + } + + try { + let redirectsFollowed = 0; + const maxRedirects = 5; + + while (redirectsFollowed <= maxRedirects) { + const response = await fetch(targetUrl, { + method: "POST", + headers, + body: formData, + redirect: "manual", + }); + + const redirectCheck = this.validateRedirect(response, targetUrl); + if (redirectCheck.error) { + return { error: redirectCheck.error }; + } + if (redirectCheck.redirectUrl) { + targetUrl = redirectCheck.redirectUrl; + redirectsFollowed += 1; + continue; + } + + const body = await response.json(); + if (!response.ok) { + return this.buildChallengeResponse(response, body); + } + return body; + } + return { error: "Too many redirects followed." }; + } catch (error) { + return { error: String(error) }; + } + } + + public async executeX402Request( endpointPath: string, payload: Record, x402PaymentProof?: string ): Promise { - const url = `${this.baseUrl}${endpointPath}`; + let targetUrl = `${this.baseUrl}${endpointPath}`; const headers: Record = { "Content-Type": "application/json", }; @@ -159,27 +394,34 @@ export class OptumeActionProvider { } try { - const response = await fetch(url, { - method: "POST", - headers, - body: JSON.stringify(payload), - }); - - const body = await response.json(); - - if (!response.ok) { - return { - statusCode: response.status, - x402Challenge: { - priceUsdc: response.headers.get("x-402-price-usdc"), - payTo: response.headers.get("x-402-pay-to"), - network: response.headers.get("x-402-network") || "eip155:8453", - }, - response: body, - }; - } + let redirectsFollowed = 0; + const maxRedirects = 5; - return body; + while (redirectsFollowed <= maxRedirects) { + const response = await fetch(targetUrl, { + method: "POST", + headers, + body: JSON.stringify(payload), + redirect: "manual", + }); + + const redirectCheck = this.validateRedirect(response, targetUrl); + if (redirectCheck.error) { + return { error: redirectCheck.error }; + } + if (redirectCheck.redirectUrl) { + targetUrl = redirectCheck.redirectUrl; + redirectsFollowed += 1; + continue; + } + + const body = await response.json(); + if (!response.ok) { + return this.buildChallengeResponse(response, body); + } + return body; + } + return { error: "Too many redirects followed." }; } catch (error) { return { error: String(error) }; } From 94ae57f41c72200de8eca8a1346dd60c4fef86d3 Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 05:37:28 -0400 Subject: [PATCH 5/9] chore: sync veritas_legal_translation fallback action list --- .../optume_translations_x402/optume_action_provider.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 36b0fd0a4..103024a8f 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -484,9 +484,15 @@ def get_actions(self, wallet_provider: Any = None) -> list[Any]: "schema": AssembleDocumentInput, "func": self.assemble_translated_document, }, + { + "name": "veritas_legal_translation", + "description": "Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", + "schema": RunFullPipelineInput, + "func": self.veritas_legal_translation, + }, { "name": "run_full_veritas_pipeline", - "description": "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.500 USDC on Base L2).", + "description": "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.0005/word, min $0.05 USDC on Base L2).", "schema": RunFullPipelineInput, "func": self.run_full_veritas_pipeline, }, From 6306e8a5d0d88f577733fdafb974c645e2c98c37 Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 05:40:30 -0400 Subject: [PATCH 6/9] chore: streamline Action Providers to active core products (Veritas Translation & Document Parser) --- .../optume_action_provider.py | 219 +------------- .../optumeActionProvider.ts | 286 +++++------------- 2 files changed, 86 insertions(+), 419 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 103024a8f..96d9846c6 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -1,9 +1,8 @@ """ Optume Translations x402 Action Provider for Coinbase AgentKit (Python). -Enables autonomous AI agents using Coinbase AgentKit to natively parse documents, -extract spatial AST structures, generate legal glossaries, perform legal QA audits, -and execute multi-language legal translations over x402 micropayments ($0.01 - $0.50 USDC) +Enables autonomous AI agents using Coinbase AgentKit to natively parse documents +and execute turnkey legal-grade translations over x402 micropayments settled on Base Layer-2. """ @@ -56,59 +55,13 @@ class ParseDocumentInput(BaseModel): ) -class ExtractVeritasChunksInput(BaseModel): - document_url: str = Field( - ..., - description="Public URL of document to extract spatial AST layout, table grids, and clause hierarchies.", - ) - - -class AnalyzeLegalDocumentInput(BaseModel): - document_url: str = Field( - ..., - description="Public URL of the legal contract to extract defined terms and generate structured legal glossary.", - ) - - -class CompileContextInput(BaseModel): - document_id: str = Field( - ..., - description="Unique Document ID from pre-translation analysis.", - ) - target_language: str = Field( - "fr", - description="ISO language code for target translation (e.g., 'fr', 'es', 'de', 'ar', 'zh').", - ) - - -class TranslateClausesInput(BaseModel): - document_id: str = Field( - ..., - description="Unique Document ID with compiled context directives.", - ) - target_language: str = Field( - "fr", - description="ISO target language code.", - ) - - -class EvaluateQAInput(BaseModel): - document_id: str = Field( - ..., - description="Unique Document ID of translated legal contract to run QA risk and terminology audit.", - ) - - -class AssembleDocumentInput(BaseModel): - document_id: str = Field( - ..., - description="Unique Document ID to assemble into final spatial-layout-preserved document.", - ) - - class RunFullPipelineInput(BaseModel): - document_url: str = Field( - ..., + raw_text: str | None = Field( + None, + description="Raw legal contract text to translate.", + ) + document_url: str | None = Field( + None, description="Public URL of document to execute complete end-to-end 7-node Veritas legal translation pipeline.", ) source_language: str = Field( @@ -122,14 +75,13 @@ class RunFullPipelineInput(BaseModel): # --------------------------------------------------------------------------- -# Custom Safe HTTP Redirect Handler +# Safe Redirect Handler # --------------------------------------------------------------------------- class SafeRedirectHandler(urllib.request.HTTPRedirectHandler): """ - Custom redirect handler enforcing HTTPS origin policy and rejecting - cross-origin or HTTP downgrade redirects. + Secure redirect handler preventing cross-origin token leakage and protocol downgrades. """ def redirect_request( @@ -144,8 +96,11 @@ def redirect_request( orig_parsed = urllib.parse.urlparse(req.full_url) new_parsed = urllib.parse.urlparse(newurl) - # Reject origin change - if orig_parsed.netloc != new_parsed.netloc: + # Reject cross-origin redirects + if ( + orig_parsed.netloc.lower() != new_parsed.netloc.lower() + or orig_parsed.scheme.lower() != new_parsed.scheme.lower() + ): raise urllib.error.HTTPError( newurl, code, @@ -295,114 +250,6 @@ def parse_document( res = self._execute_x402_request("/api/v1/parser/analyze", payload, x402_proof) return json.dumps(res) if HAS_AGENTKIT else res - @create_action( - name="extract_veritas_chunks", - description="Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", - schema=ExtractVeritasChunksInput, - ) - def extract_veritas_chunks( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - ExtractVeritasChunksInput.model_validate(args) - if isinstance(args, dict) - else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request( - "/api/v1/parser/veritas-chunks", payload, x402_proof - ) - return json.dumps(res) if HAS_AGENTKIT else res - - @create_action( - name="analyze_legal_document", - description="Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", - schema=AnalyzeLegalDocumentInput, - ) - def analyze_legal_document( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - AnalyzeLegalDocumentInput.model_validate(args) - if isinstance(args, dict) - else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request("/api/v1/veritas/analyze", payload, x402_proof) - return json.dumps(res) if HAS_AGENTKIT else res - - @create_action( - name="compile_translation_context", - description="Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", - schema=CompileContextInput, - ) - def compile_translation_context( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - CompileContextInput.model_validate(args) if isinstance(args, dict) else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request( - "/api/v1/veritas/compile-context", payload, x402_proof - ) - return json.dumps(res) if HAS_AGENTKIT else res - - @create_action( - name="translate_legal_clauses", - description="Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", - schema=TranslateClausesInput, - ) - def translate_legal_clauses( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - TranslateClausesInput.model_validate(args) - if isinstance(args, dict) - else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request( - "/api/v1/veritas/translate", payload, x402_proof - ) - return json.dumps(res) if HAS_AGENTKIT else res - - @create_action( - name="audit_legal_qa", - description="Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", - schema=EvaluateQAInput, - ) - def audit_legal_qa( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - EvaluateQAInput.model_validate(args) if isinstance(args, dict) else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request( - "/api/v1/veritas/evaluate-qa", payload, x402_proof - ) - return json.dumps(res) if HAS_AGENTKIT else res - - @create_action( - name="assemble_translated_document", - description="Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", - schema=AssembleDocumentInput, - ) - def assemble_translated_document( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - AssembleDocumentInput.model_validate(args) - if isinstance(args, dict) - else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request( - "/api/v1/veritas/assemble-document", payload, x402_proof - ) - return json.dumps(res) if HAS_AGENTKIT else res - @create_action( name="veritas_legal_translation", description="Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", @@ -448,42 +295,6 @@ def get_actions(self, wallet_provider: Any = None) -> list[Any]: "schema": ParseDocumentInput, "func": self.parse_document, }, - { - "name": "extract_veritas_chunks", - "description": "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", - "schema": ExtractVeritasChunksInput, - "func": self.extract_veritas_chunks, - }, - { - "name": "analyze_legal_document", - "description": "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", - "schema": AnalyzeLegalDocumentInput, - "func": self.analyze_legal_document, - }, - { - "name": "compile_translation_context", - "description": "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", - "schema": CompileContextInput, - "func": self.compile_translation_context, - }, - { - "name": "translate_legal_clauses", - "description": "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", - "schema": TranslateClausesInput, - "func": self.translate_legal_clauses, - }, - { - "name": "audit_legal_qa", - "description": "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", - "schema": EvaluateQAInput, - "func": self.audit_legal_qa, - }, - { - "name": "assemble_translated_document", - "description": "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", - "schema": AssembleDocumentInput, - "func": self.assemble_translated_document, - }, { "name": "veritas_legal_translation", "description": "Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts index 1654bb0a8..829344482 100644 --- a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -1,136 +1,57 @@ -/** - * Optume Translations x402 Action Provider for Coinbase AgentKit (TypeScript). - * - * Enables autonomous AI agents using Coinbase AgentKit to natively parse documents, - * extract spatial AST structures, generate legal glossaries, perform legal QA audits, - * and execute multi-language legal translations over x402 micropayments ($0.01 - $0.50 USDC) - * settled on Base Layer-2. - */ - +import { ActionProvider } from "@coinbase/agentkit"; import { z } from "zod"; export const ParseDocumentSchema = z.object({ documentUrl: z .string() .url() - .describe("Public URL or accessible link of the document (PDF, DOCX, XLSX, PPTX, image) to parse."), + .describe("Public URL or file link of document (PDF, DOCX, XLSX, PPTX, Image) to parse."), }); -export const ExtractVeritasChunksSchema = z.object({ +export const RunFullPipelineSchema = z.object({ + rawText: z + .string() + .optional() + .describe("Raw legal contract text to translate."), documentUrl: z .string() .url() - .describe("Public URL of document to extract spatial AST layout, table grids, and clause hierarchies."), -}); - -export const AnalyzeLegalDocumentSchema = z - .object({ - rawText: z.string().optional().describe("Raw legal contract text to ingest and analyze."), - documentUrl: z - .string() - .url() - .optional() - .describe("Public URL of legal contract to analyze."), - targetLanguage: z.string().default("fr").describe("ISO target language code."), - }) - .refine((data) => Boolean(data.rawText || data.documentUrl), { - message: "At least one of rawText or documentUrl must be provided.", - }); - -export const CompileContextSchema = z.object({ - documentId: z.string().describe("Unique Document ID from pre-translation analysis."), + .optional() + .describe("Public URL of legal document to execute complete 7-node Veritas pipeline."), + sourceLanguage: z + .string() + .default("en") + .describe("ISO source language code (default 'en')."), targetLanguage: z .string() .default("fr") - .describe("ISO language code for target translation (e.g., 'fr', 'es', 'de', 'ar', 'zh')."), + .describe("ISO target language code (e.g. 'fr', 'es', 'de', 'ar', 'zh')."), }); -export const TranslateClausesSchema = z.object({ - documentId: z.string().describe("Unique Document ID with compiled context directives."), - targetLanguage: z.string().default("fr").describe("ISO target language code."), - concurrencyLimit: z - .number() - .int() - .positive() - .max(50) - .default(10) - .describe("Parallel translation concurrency limit."), -}); - -export const EvaluateQASchema = z.object({ - documentId: z - .string() - .describe("Unique Document ID of translated legal contract to run QA risk and terminology audit."), -}); - -export const AssembleDocumentSchema = z.object({ - documentId: z - .string() - .describe("Unique Document ID to assemble into final spatial-layout-preserved document."), -}); - -export const RunFullPipelineSchema = z - .object({ - rawText: z.string().optional().describe("Raw text of contract to translate."), - documentUrl: z - .string() - .url() - .optional() - .describe("Public URL of document to execute complete end-to-end 7-node Veritas legal translation pipeline."), - sourceLanguage: z.string().default("en").describe("ISO source language code (e.g. 'en')."), - targetLanguage: z.string().default("fr").describe("ISO target language code (e.g. 'fr', 'es', 'de', 'ar', 'zh')."), - }) - .refine((data) => Boolean(data.rawText || data.documentUrl), { - message: "At least one of rawText or documentUrl must be provided.", - }); - -export interface ActionProvider { - name: string; - supportsNetwork(network: Record | string): boolean; - getActions(): Array<{ - name: string; - description: string; - schema: z.ZodTypeAny; - invoke: (args: Record, proof?: string) => Promise; - }>; -} - -export class OptumeActionProvider implements ActionProvider { - public name = "optume"; - public baseUrl: string; +export class OptumeActionProvider extends ActionProvider { + private baseUrl: string; constructor(baseUrl: string = "https://api.optranslations.com") { - const trimmed = baseUrl.replace(/\/$/, ""); - try { - const parsed = new URL(trimmed); - if ( - parsed.protocol !== "https:" && - !( - parsed.protocol === "http:" && - (parsed.hostname === "localhost" || - parsed.hostname === "127.0.0.1" || - parsed.hostname === "testserver") - ) - ) { - throw new Error("Invalid baseUrl protocol. Must use HTTPS for remote endpoints."); - } - this.baseUrl = trimmed; - } catch (err) { - throw err; + super("optume", []); + + const parsed = new URL(baseUrl); + if (!["https:", "http:"].includes(parsed.protocol)) { + throw new Error(`Invalid baseUrl protocol '${parsed.protocol}'. Must be HTTPS.`); } + if ( + parsed.protocol === "http:" && + !["localhost", "127.0.0.1", "testserver"].includes(parsed.hostname) + ) { + throw new Error("Insecure HTTP baseUrl allowed only for local testing."); + } + + this.baseUrl = baseUrl.replace(/\/+$/, ""); } - /** - * Check whether target EVM network is supported (Base Mainnet / Sepolia). - */ - public supportsNetwork(network: Record | string): boolean { - let netStr = ""; - if (typeof network === "string") { - netStr = network.toLowerCase().trim(); - } else if (network && typeof network === "object") { - const rec = network as Record; - netStr = String(rec.networkId || rec.chainId || JSON.stringify(network)).toLowerCase().trim(); - } + public supportsNetwork(network: { networkId?: string; chainId?: string } | string): boolean { + let netStr = typeof network === "string" ? network : network.networkId || network.chainId || ""; + netStr = netStr.toLowerCase().trim(); + const supportedBases = new Set([ "base", "8453", @@ -140,46 +61,57 @@ export class OptumeActionProvider implements ActionProvider { "base-mainnet", "base-sepolia", ]); + return supportedBases.has(netStr); } + private validateRedirect( + response: Response, + currentUrl: string + ): { redirectUrl?: string; error?: string } { + if (response.status >= 300 && response.status < 400) { + const location = response.headers.get("Location"); + if (!location) { + return { error: `Redirect response missing Location header (HTTP ${response.status}).` }; + } + + const origParsed = new URL(currentUrl); + const newParsed = new URL(location, currentUrl); + + if ( + origParsed.host.toLowerCase() !== newParsed.host.toLowerCase() || + origParsed.protocol.toLowerCase() !== newParsed.protocol.toLowerCase() + ) { + return { + error: `Redirect rejected: cross-origin redirect to ${newParsed.host} is forbidden.`, + }; + } + + if (origParsed.protocol === "https:" && newParsed.protocol !== "https:") { + return { + error: "Redirect rejected: protocol downgrade from HTTPS to HTTP is forbidden.", + }; + } + + return { redirectUrl: newParsed.toString() }; + } + return {}; + } + private buildChallengeResponse(response: Response, body: unknown): Record { return { statusCode: response.status, x402Challenge: { - priceUsdc: response.headers.get("x-402-price-usdc"), - payTo: response.headers.get("x-402-pay-to"), - network: response.headers.get("x-402-network") || "eip155:8453", + priceUsdc: response.headers.get("X-402-Price-USDC") || null, + payTo: response.headers.get("X-402-Pay-To") || null, + network: response.headers.get("X-402-Network") || "eip155:8453", }, response: body, }; } - private validateRedirect( - response: Response, - currentUrl: string - ): { redirectUrl?: string; error?: string } { - if (response.status < 300 || response.status >= 400) { - return {}; - } - const location = response.headers.get("location"); - if (!location) { - return {}; - } - const currentParsed = new URL(currentUrl); - const targetParsed = new URL(location, currentUrl); - - if (targetParsed.hostname !== currentParsed.hostname) { - return { error: `Redirect rejected: Cross-origin redirect to ${targetParsed.hostname} is forbidden.` }; - } - if (currentParsed.protocol === "https:" && targetParsed.protocol !== "https:") { - return { error: "Redirect rejected: Protocol downgrade from HTTPS to HTTP is forbidden." }; - } - return { redirectUrl: targetParsed.toString() }; - } - /** - * Return catalog of x402 actions exposed to Coinbase AgentKit. + * Return catalog of active x402 actions exposed to Coinbase AgentKit. */ public getActions() { return [ @@ -193,82 +125,6 @@ export class OptumeActionProvider implements ActionProvider { return JSON.stringify(await this.executeFileUploadRequest("/api/v1/parser/analyze", parsed.documentUrl, proof)); }, }, - { - name: "extract_veritas_chunks", - description: - "Extracts spatial AST layout, table grids, and clause structural hierarchies ($0.025 USDC on Base L2).", - schema: ExtractVeritasChunksSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = ExtractVeritasChunksSchema.parse(args); - return JSON.stringify(await this.executeFileUploadRequest("/api/v1/parser/veritas-chunks", parsed.documentUrl, proof)); - }, - }, - { - name: "analyze_legal_document", - description: - "Ingestion, defined legal terms extraction, and structured legal glossary generation ($0.050 USDC on Base L2).", - schema: AnalyzeLegalDocumentSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = AnalyzeLegalDocumentSchema.parse(args); - let rawText = parsed.rawText || ""; - if (!rawText && parsed.documentUrl) { - const resp = await fetch(parsed.documentUrl); - if (!resp.ok) { - return JSON.stringify({ error: `Failed to download document from URL: ${resp.statusText}` }); - } - rawText = await resp.text(); - } - const body = { - raw_text: rawText, - target_language: parsed.targetLanguage || "fr", - }; - return JSON.stringify(await this.executeX402Request("/api/v1/veritas/analyze", body, proof)); - }, - }, - { - name: "compile_translation_context", - description: - "Filters sub-glossaries and compiles translation directives per clause chunk ($0.020 USDC on Base L2).", - schema: CompileContextSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = CompileContextSchema.parse(args); - const path = `/api/v1/veritas/compile-context?document_id=${encodeURIComponent(parsed.documentId)}`; - return JSON.stringify(await this.executeX402Request(path, {}, proof)); - }, - }, - { - name: "translate_legal_clauses", - description: - "Parallel clause translation engine preserving formatting across 50+ languages ($0.150 USDC on Base L2).", - schema: TranslateClausesSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = TranslateClausesSchema.parse(args); - const path = `/api/v1/veritas/translate?document_id=${encodeURIComponent(parsed.documentId)}&concurrency_limit=${parsed.concurrencyLimit || 10}`; - return JSON.stringify(await this.executeX402Request(path, {}, proof)); - }, - }, - { - name: "audit_legal_qa", - description: - "Multi-dimensional audit of legal terminology, numerical accuracy, and omissions ($0.050 USDC on Base L2).", - schema: EvaluateQASchema, - invoke: async (args: Record, proof?: string) => { - const parsed = EvaluateQASchema.parse(args); - const path = `/api/v1/veritas/evaluate-qa?document_id=${encodeURIComponent(parsed.documentId)}`; - return JSON.stringify(await this.executeX402Request(path, {}, proof)); - }, - }, - { - name: "assemble_translated_document", - description: - "Re-assembles translated text into layout-preserving original document structures ($0.050 USDC on Base L2).", - schema: AssembleDocumentSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = AssembleDocumentSchema.parse(args); - const path = `/api/v1/veritas/assemble-document?document_id=${encodeURIComponent(parsed.documentId)}`; - return JSON.stringify(await this.executeX402Request(path, {}, proof)); - }, - }, { name: "veritas_legal_translation", description: From a0a5ac04bbc7e96868753eaa6beb64e3f1c720f1 Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 05:41:22 -0400 Subject: [PATCH 7/9] feat: streamline x402 Action Provider to single Veritas Legal Translation product --- .../optume_action_provider.py | 49 +-------- .../optumeActionProvider.ts | 101 ------------------ 2 files changed, 2 insertions(+), 148 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 96d9846c6..453727fa0 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -1,9 +1,8 @@ """ Optume Translations x402 Action Provider for Coinbase AgentKit (Python). -Enables autonomous AI agents using Coinbase AgentKit to natively parse documents -and execute turnkey legal-grade translations over x402 micropayments -settled on Base Layer-2. +Enables autonomous AI agents using Coinbase AgentKit to natively execute turnkey +legal-grade translations over x402 micropayments settled on Base Layer-2. """ import json @@ -48,13 +47,6 @@ def decorator(func: Any) -> Any: # --------------------------------------------------------------------------- -class ParseDocumentInput(BaseModel): - document_url: str = Field( - ..., - description="Public URL or accessible link of the document (PDF, DOCX, XLSX, PPTX, image) to parse.", - ) - - class RunFullPipelineInput(BaseModel): raw_text: str | None = Field( None, @@ -235,21 +227,6 @@ def _execute_x402_request( # AgentKit Decorated Action Methods # --------------------------------------------------------------------------- - @create_action( - name="parse_document", - description="High-speed spatial parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", - schema=ParseDocumentInput, - ) - def parse_document( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - validated = ( - ParseDocumentInput.model_validate(args) if isinstance(args, dict) else args - ) - payload = validated.model_dump() if hasattr(validated, "model_dump") else args - res = self._execute_x402_request("/api/v1/parser/analyze", payload, x402_proof) - return json.dumps(res) if HAS_AGENTKIT else res - @create_action( name="veritas_legal_translation", description="Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", @@ -269,16 +246,6 @@ def veritas_legal_translation( ) return json.dumps(res) if HAS_AGENTKIT else res - @create_action( - name="run_full_veritas_pipeline", - description="Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.0005/word, min $0.05 USDC on Base L2).", - schema=RunFullPipelineInput, - ) - def run_full_veritas_pipeline( - self, args: dict[str, Any], x402_proof: str | None = None - ) -> str | dict[str, Any]: - return self.veritas_legal_translation(args, x402_proof) - def get_actions(self, wallet_provider: Any = None) -> list[Any]: """ Return the list of actions exposed to Coinbase AgentKit. @@ -289,24 +256,12 @@ def get_actions(self, wallet_provider: Any = None) -> list[Any]: return actions return [ - { - "name": "parse_document", - "description": "High-speed spatial parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", - "schema": ParseDocumentInput, - "func": self.parse_document, - }, { "name": "veritas_legal_translation", "description": "Turnkey legal-grade document translation engine combining all 7 Veritas pipeline nodes ($0.0005/word, min $0.05 USDC on Base L2).", "schema": RunFullPipelineInput, "func": self.veritas_legal_translation, }, - { - "name": "run_full_veritas_pipeline", - "description": "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.0005/word, min $0.05 USDC on Base L2).", - "schema": RunFullPipelineInput, - "func": self.run_full_veritas_pipeline, - }, ] diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts index 829344482..4a830f4bb 100644 --- a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -1,13 +1,6 @@ import { ActionProvider } from "@coinbase/agentkit"; import { z } from "zod"; -export const ParseDocumentSchema = z.object({ - documentUrl: z - .string() - .url() - .describe("Public URL or file link of document (PDF, DOCX, XLSX, PPTX, Image) to parse."), -}); - export const RunFullPipelineSchema = z.object({ rawText: z .string() @@ -115,16 +108,6 @@ export class OptumeActionProvider extends ActionProvider { */ public getActions() { return [ - { - name: "parse_document", - description: - "High-speed spatial document parsing for PDF, DOCX, XLSX, PPTX, and OCR Images ($0.010 USDC on Base L2).", - schema: ParseDocumentSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = ParseDocumentSchema.parse(args); - return JSON.stringify(await this.executeFileUploadRequest("/api/v1/parser/analyze", parsed.documentUrl, proof)); - }, - }, { name: "veritas_legal_translation", description: @@ -148,93 +131,9 @@ export class OptumeActionProvider extends ActionProvider { return JSON.stringify(await this.executeX402Request("/api/v1/veritas/run-full", body, proof)); }, }, - { - name: "run_full_veritas_pipeline", - description: - "Complete end-to-end legal translation pipeline across all 7 Veritas nodes ($0.0005/word, min $0.05 USDC on Base L2).", - schema: RunFullPipelineSchema, - invoke: async (args: Record, proof?: string) => { - const parsed = RunFullPipelineSchema.parse(args); - let rawText = parsed.rawText || ""; - if (!rawText && parsed.documentUrl) { - const resp = await fetch(parsed.documentUrl); - if (!resp.ok) { - return JSON.stringify({ error: `Failed to download document from URL: ${resp.statusText}` }); - } - rawText = await resp.text(); - } - const body = { - raw_text: rawText, - source_language: parsed.sourceLanguage || "en", - target_language: parsed.targetLanguage || "fr", - }; - return JSON.stringify(await this.executeX402Request("/api/v1/veritas/run-full", body, proof)); - }, - }, ]; } - public async executeFileUploadRequest( - endpointPath: string, - documentUrl: string, - x402PaymentProof?: string - ): Promise { - const docResp = await fetch(documentUrl); - if (!docResp.ok) { - return { error: `Failed to download document from URL: ${docResp.statusText}` }; - } - const blob = await docResp.blob(); - - let fileName = "document.pdf"; - try { - fileName = new URL(documentUrl).pathname.split("/").pop() || "document.pdf"; - } catch { - fileName = documentUrl.split("/").pop() || "document.pdf"; - } - - const formData = new FormData(); - formData.append("file", blob, fileName); - - let targetUrl = `${this.baseUrl}${endpointPath}`; - const headers: Record = {}; - if (x402PaymentProof) { - headers["X-402-Payment-Proof"] = x402PaymentProof; - } - - try { - let redirectsFollowed = 0; - const maxRedirects = 5; - - while (redirectsFollowed <= maxRedirects) { - const response = await fetch(targetUrl, { - method: "POST", - headers, - body: formData, - redirect: "manual", - }); - - const redirectCheck = this.validateRedirect(response, targetUrl); - if (redirectCheck.error) { - return { error: redirectCheck.error }; - } - if (redirectCheck.redirectUrl) { - targetUrl = redirectCheck.redirectUrl; - redirectsFollowed += 1; - continue; - } - - const body = await response.json(); - if (!response.ok) { - return this.buildChallengeResponse(response, body); - } - return body; - } - return { error: "Too many redirects followed." }; - } catch (error) { - return { error: String(error) }; - } - } - public async executeX402Request( endpointPath: string, payload: Record, From 847f1547e9935fdf323c3632a2f975ca4472ba5a Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 05:42:09 -0400 Subject: [PATCH 8/9] fix: protocol downgrade check precedence in redirect handler --- .../optume_action_provider.py | 15 ++++++--------- .../optumeActionProvider.ts | 11 ++++------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 453727fa0..0f40db309 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -88,25 +88,22 @@ def redirect_request( orig_parsed = urllib.parse.urlparse(req.full_url) new_parsed = urllib.parse.urlparse(newurl) - # Reject cross-origin redirects - if ( - orig_parsed.netloc.lower() != new_parsed.netloc.lower() - or orig_parsed.scheme.lower() != new_parsed.scheme.lower() - ): + # Reject protocol downgrade from HTTPS to HTTP + if orig_parsed.scheme == "https" and new_parsed.scheme != "https": raise urllib.error.HTTPError( newurl, code, - f"Redirect rejected: cross-origin redirect to {new_parsed.netloc} is forbidden.", + "Redirect rejected: protocol downgrade from HTTPS to HTTP is forbidden.", headers, fp, ) - # Reject protocol downgrade from HTTPS to HTTP - if orig_parsed.scheme == "https" and new_parsed.scheme != "https": + # Reject cross-origin redirects + if orig_parsed.netloc.lower() != new_parsed.netloc.lower(): raise urllib.error.HTTPError( newurl, code, - "Redirect rejected: protocol downgrade from HTTPS to HTTP is forbidden.", + f"Redirect rejected: cross-origin redirect to {new_parsed.netloc} is forbidden.", headers, fp, ) diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts index 4a830f4bb..875779831 100644 --- a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -71,18 +71,15 @@ export class OptumeActionProvider extends ActionProvider { const origParsed = new URL(currentUrl); const newParsed = new URL(location, currentUrl); - if ( - origParsed.host.toLowerCase() !== newParsed.host.toLowerCase() || - origParsed.protocol.toLowerCase() !== newParsed.protocol.toLowerCase() - ) { + if (origParsed.protocol === "https:" && newParsed.protocol !== "https:") { return { - error: `Redirect rejected: cross-origin redirect to ${newParsed.host} is forbidden.`, + error: "Redirect rejected: protocol downgrade from HTTPS to HTTP is forbidden.", }; } - if (origParsed.protocol === "https:" && newParsed.protocol !== "https:") { + if (origParsed.host.toLowerCase() !== newParsed.host.toLowerCase()) { return { - error: "Redirect rejected: protocol downgrade from HTTPS to HTTP is forbidden.", + error: `Redirect rejected: cross-origin redirect to ${newParsed.host} is forbidden.`, }; } From 72b2455d37d55751c83ee7dd8b12bc5a55aba63b Mon Sep 17 00:00:00 2001 From: oe-ib08 Date: Thu, 6 Aug 2026 05:46:52 -0400 Subject: [PATCH 9/9] feat: update x402 endpoint path to /api/v1/veritas/legal-translation --- .../optume_translations_x402/optume_action_provider.py | 2 +- .../optume_translations_x402/optumeActionProvider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py index 0f40db309..d971419d6 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/optume_translations_x402/optume_action_provider.py @@ -239,7 +239,7 @@ def veritas_legal_translation( ) payload = validated.model_dump() if hasattr(validated, "model_dump") else args res = self._execute_x402_request( - "/api/v1/veritas/run-full", payload, x402_proof + "/api/v1/veritas/legal-translation", payload, x402_proof ) return json.dumps(res) if HAS_AGENTKIT else res diff --git a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts index 875779831..64fbca3a7 100644 --- a/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts +++ b/typescript/agentkit/src/action-providers/optume_translations_x402/optumeActionProvider.ts @@ -125,7 +125,7 @@ export class OptumeActionProvider extends ActionProvider { source_language: parsed.sourceLanguage || "en", target_language: parsed.targetLanguage || "fr", }; - return JSON.stringify(await this.executeX402Request("/api/v1/veritas/run-full", body, proof)); + return JSON.stringify(await this.executeX402Request("/api/v1/veritas/legal-translation", body, proof)); }, }, ];