Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions typescript/agentkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,23 @@ const agent = createAgent({
</table>
</details>
<details>
<summary><strong>ConnectMeGuru</strong></summary>
<table width="100%">
<tr>
<td width="200"><code>search_esim_plans</code></td>
<td width="768">Searches over 3,000+ international travel eSIM data plans across 190+ countries with retail prices and validity periods.</td>
</tr>
<tr>
<td width="200"><code>purchase_esim</code></td>
<td width="768">Initiates an eSIM purchase and generates a non-custodial USDT payment invoice on Polygon, Arbitrum One, or TRON.</td>
</tr>
<tr>
<td width="200"><code>check_order_status</code></td>
<td width="768">Checks on-chain payment settlement and retrieves the fulfilled eSIM profile (ICCID, SM-DP+ LPA string, and QR code URL).</td>
</tr>
</table>
</details>
<details>
<summary><strong>CDP API</strong></summary>
<table width="100%">
<tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ConnectMeGuru Action Provider

The ConnectMeGuru Action Provider enables AI agents to search and purchase international travel eSIM data plans across 190+ countries with non-custodial USDT payments on Polygon, Arbitrum One, and TRON.

## Actions

- `search_esim_plans`: Search 3,000+ local and regional eSIM packages by destination country.
- `purchase_esim`: Create a non-custodial crypto checkout invoice with collision-free spot discount pricing.
- `check_order_status`: Poll on-chain payment confirmation and retrieve the fulfilled eSIM profile (ICCID, SM-DP+ LPA string, and QR code URL).

## Configuration

```typescript
import { connectmeguruActionProvider } from "@coinbase/agentkit";

const actionProvider = connectmeguruActionProvider({
patToken: process.env.CMG_PAT_TOKEN, // Optional: Personal Access Token for verified machine identity
baseUrl: "https://www.connectmeguru.com/api", // Optional: Custom API URL
});
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {
ConnectMeGuruActionProvider,
connectmeguruActionProvider,
} from "./connectmeguruActionProvider";

describe("ConnectMeGuruActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = connectmeguruActionProvider({
baseUrl: "https://www.connectmeguru.com/api",
patToken: "test_pat_token",
});

beforeEach(() => {
jest.resetAllMocks();
});

describe("searchEsimPlans", () => {
it("should return plans when API call is successful", async () => {
const mockResponse = {
success: true,
totalCount: 1,
plans: [
{
packageCode: "P4XU0X3CX",
name: "Japan 1GB 7Days",
dataAmount: 1,
duration: 7,
retailPrice: 3.85,
},
],
};
fetchMock.mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockResponse),
});

const result = await provider.searchEsimPlans({ country: "Japan" });
const parsed = JSON.parse(result);
expect(parsed.success).toBe(true);
expect(parsed.plans[0].packageCode).toBe("P4XU0X3CX");
});

it("should handle API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
statusText: "Internal Server Error",
});

const result = await provider.searchEsimPlans({ country: "Japan" });
expect(result).toContain("Error searching eSIM plans");
expect(result).toContain("500");
});

it("should handle network failure", async () => {
fetchMock.mockRejectedValue(new Error("Network connection dropped"));

const result = await provider.searchEsimPlans({ country: "Japan" });
expect(result).toContain("Error searching eSIM plans");
expect(result).toContain("Network connection dropped");
});
});

describe("purchaseEsim", () => {
it("should return invoice when checkout is successful", async () => {
const mockInvoice = {
status: "PENDING_PAYMENT",
invoiceId: "CMG-INV-TEST-001",
plan: {
packageCode: "P4XU0X3CX",
name: "Japan 1GB 7Days",
},
payment: {
currency: "USDT",
network: "polygon",
expectedAmount: 3.850012,
receivingAddress: "0x1234567890123456789012345678901234567890",
},
};

fetchMock.mockResolvedValue({
status: 402,
ok: false,
json: jest.fn().mockResolvedValue(mockInvoice),
});

const result = await provider.purchaseEsim({
packageCode: "P4XU0X3CX",
customerEmail: "agent@example.com",
network: "polygon",
currency: "USDT",
});

const parsed = JSON.parse(result);
expect(parsed.invoiceId).toBe("CMG-INV-TEST-001");
expect(parsed.payment.expectedAmount).toBe(3.850012);
});

it("should handle purchase failure", async () => {
fetchMock.mockResolvedValue({
status: 400,
ok: false,
json: jest.fn().mockResolvedValue({ error: "Invalid package code" }),
});

const result = await provider.purchaseEsim({
packageCode: "INVALID",
customerEmail: "agent@example.com",
network: "polygon",
currency: "USDT",
});

expect(result).toContain("Error purchasing eSIM");
expect(result).toContain("Invalid package code");
});
});

describe("checkOrderStatus", () => {
it("should return fulfilled eSIM details when completed", async () => {
const mockOrder = {
status: "COMPLETED",
invoiceId: "CMG-INV-TEST-001",
esim: {
iccid: "8985200000000000001",
lpaString: "LPA:1$smdp.io$MATCHING-ID",
qrCodeUrl: "https://qr.connectmeguru.com/esim.png",
},
};

fetchMock.mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockOrder),
});

const result = await provider.checkOrderStatus({
invoiceId: "CMG-INV-TEST-001",
});

const parsed = JSON.parse(result);
expect(parsed.status).toBe("COMPLETED");
expect(parsed.esim.iccid).toBe("8985200000000000001");
});

it("should handle order not found error", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 404,
statusText: "Not Found",
json: jest.fn().mockResolvedValue({ error: "Invoice not found" }),
});

const result = await provider.checkOrderStatus({
invoiceId: "NON_EXISTENT",
});

expect(result).toContain("Error checking order status");
expect(result).toContain("Invoice not found");
});
});

describe("supportsNetwork", () => {
it("should return true for any network", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { z } from "zod";
import { ActionProvider } from "../actionProvider";
import { CreateAction } from "../actionDecorator";
import { SearchEsimSchema, PurchaseEsimSchema, CheckOrderStatusSchema } from "./schemas";
import { CONNECTMEGURU_BASE_URL } from "./constants";

export interface ConnectMeGuruConfig {
patToken?: string;
baseUrl?: string;
}

/**
* Extract human-readable message from an unknown error.
*/
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message || error.toString();
}
const str = String(error);
if (str === "[object Object]") {
try {
return JSON.stringify(error);
} catch {
return "Unknown error";
}
}
return str;
}

/**
* ConnectMeGuruActionProvider provides actions for autonomous global travel eSIM search and purchase.
*
* Supports searching 3,000+ data plans across 190+ countries, non-custodial crypto checkout
* using USDT on Polygon, Arbitrum One, and TRON, and instant eSIM profile delivery (QR code and LPA string).
*/
export class ConnectMeGuruActionProvider extends ActionProvider {
private readonly patToken: string;
private readonly baseUrl: string;

/**
* Creates a new ConnectMeGuruActionProvider instance.
*
* @param config - Optional configuration containing Personal Access Token or custom base URL.
*/
constructor(config?: ConnectMeGuruConfig) {
super("connectmeguru", []);
this.patToken = config?.patToken || process.env.CMG_PAT_TOKEN || "";
this.baseUrl = (config?.baseUrl || process.env.CMG_BASE_URL || CONNECTMEGURU_BASE_URL).replace(/\/$/, "");
}

/**
* Search for available travel eSIM data packages for a destination country.
*
* @param args - Search arguments containing the destination country.
* @returns JSON string containing list of available eSIM plans with prices and durations.
*/
@CreateAction({
name: "search_esim_plans",
description: `Search for available international travel eSIM data plans and pricing across 190+ countries.
Takes the destination country name or ISO code and returns a list of package codes, data caps (GB), validity duration (days), and retail prices in USD/USDT.`,
schema: SearchEsimSchema,
})
async searchEsimPlans(args: z.infer<typeof SearchEsimSchema>): Promise<string> {
try {
const url = `${this.baseUrl}/products/search?country=${encodeURIComponent(args.country)}`;
const response = await fetch(url);

if (!response.ok) {
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
}

const data = await response.json();
return JSON.stringify(data, null, 2);
} catch (error) {
return `Error searching eSIM plans: ${getErrorMessage(error)}`;
}
}

/**
* Purchase an eSIM plan and generate a non-custodial USDT payment invoice.
*
* @param args - Purchase arguments including packageCode, customerEmail, network, and currency.
* @returns JSON string containing payment invoice, exact USDT amount with spot discount offset, and receiving wallet address.
*/
@CreateAction({
name: "purchase_esim",
description: `Initiates an eSIM purchase and generates a non-custodial USDC or USDT payment invoice.
Requires packageCode (from search_esim_plans), customerEmail, network ('base', 'polygon', 'arbitrum', or 'tron'), and currency ('USDC' or 'USDT').
Defaults to USDC on Base (Coinbase L2) for sub-cent transaction fees.
Returns payment instructions including receiving address, exact token amount to transfer, and expiry timestamp.`,
schema: PurchaseEsimSchema,
})
async purchaseEsim(args: z.infer<typeof PurchaseEsimSchema>): Promise<string> {
try {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (this.patToken) {
headers["Authorization"] = `Bearer ${this.patToken}`;
}

const response = await fetch(`${this.baseUrl}/agentic/checkout`, {
method: "POST",
headers,
body: JSON.stringify({
packageCode: args.packageCode,
customerEmail: args.customerEmail,
preferredCurrency: args.currency ? args.currency.toUpperCase() : "USDC",
preferredNetwork: args.network ? args.network.toUpperCase() : "BASE",
}),
});

if (response.status !== 200 && response.status !== 402) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
}

const data = await response.json();
return JSON.stringify(data, null, 2);
} catch (error) {
return `Error purchasing eSIM: ${getErrorMessage(error)}`;
}
}

/**
* Check order status and download eSIM profile.
*
* @param args - Order status arguments containing invoiceId.
* @returns JSON string containing fulfillment status, ICCID, LPA activation string, and QR code URL.
*/
@CreateAction({
name: "check_order_status",
description: `Check the on-chain payment settlement and provisioning status for a ConnectMeGuru invoice ID.
When status is COMPLETED, returns the eSIM ICCID, LPA activation code string, and QR code image URL for installation.`,
schema: CheckOrderStatusSchema,
})
async checkOrderStatus(args: z.infer<typeof CheckOrderStatusSchema>): Promise<string> {
try {
const url = `${this.baseUrl}/agentic/order/${encodeURIComponent(args.invoiceId)}`;
const response = await fetch(url);

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
}

const data = await response.json();
return JSON.stringify(data, null, 2);
} catch (error) {
return `Error checking order status: ${getErrorMessage(error)}`;
}
}

/**
* Checks if the action provider supports the given network.
* ConnectMeGuru supports non-custodial USDT payments across EVM (Polygon, Arbitrum) and TRON.
*
* @returns True, as ConnectMeGuru actions are supported across networks.
*/
supportsNetwork(): boolean {
return true;
}
}

/**
* Creates a new instance of the ConnectMeGuru action provider.
*
* @param config - Optional configuration for ConnectMeGuru Action Provider.
* @returns A new ConnectMeGuruActionProvider instance.
*/
export const connectmeguruActionProvider = (config?: ConnectMeGuruConfig) =>
new ConnectMeGuruActionProvider(config);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* ConnectMeGuru API Constants
*/
export const CONNECTMEGURU_BASE_URL = "https://www.connectmeguru.com/api";
export const CONNECTMEGURU_PORTAL_URL = "https://www.connectmeguru.com/developers/agentic";
export const SUPPORTED_NETWORKS = ["polygon", "arbitrum", "tron"] as const;
export const SUPPORTED_CURRENCY = "USDT" as const;
Loading
Loading