This is page 2 of 5. Use http://codebase.md/stripe/agent-toolkit?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ └── workflows
│ ├── main.yml
│ ├── npm_release_shared.yml
│ ├── pypi_release.yml
│ └── sync-skills.yml
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── gemini-extension.json
├── LICENSE
├── llm
│ ├── ai-sdk
│ │ ├── jest.config.ts
│ │ ├── LICENSE
│ │ ├── meter
│ │ │ ├── examples
│ │ │ │ ├── .env.example
│ │ │ │ ├── .gitignore
│ │ │ │ ├── anthropic.ts
│ │ │ │ ├── google.ts
│ │ │ │ ├── openai.ts
│ │ │ │ ├── README.md
│ │ │ │ └── tsconfig.json
│ │ │ ├── index.ts
│ │ │ ├── meter-event-logging.ts
│ │ │ ├── meter-event-types.ts
│ │ │ ├── README.md
│ │ │ ├── tests
│ │ │ │ ├── ai-sdk-billing-wrapper-anthropic.test.ts
│ │ │ │ ├── ai-sdk-billing-wrapper-general.test.ts
│ │ │ │ ├── ai-sdk-billing-wrapper-google.test.ts
│ │ │ │ ├── ai-sdk-billing-wrapper-openai.test.ts
│ │ │ │ ├── ai-sdk-billing-wrapper-other-providers.test.ts
│ │ │ │ ├── meter-event-logging.test.ts
│ │ │ │ └── model-name-normalization.test.ts
│ │ │ ├── tsconfig.json
│ │ │ ├── types.ts
│ │ │ ├── utils.ts
│ │ │ └── wrapperV2.ts
│ │ ├── package.json
│ │ ├── pnpm-lock.yaml
│ │ ├── provider
│ │ │ ├── examples
│ │ │ │ ├── .env.example
│ │ │ │ ├── .gitignore
│ │ │ │ ├── anthropic.ts
│ │ │ │ ├── google.ts
│ │ │ │ ├── openai.ts
│ │ │ │ ├── README.md
│ │ │ │ └── tsconfig.json
│ │ │ ├── index.ts
│ │ │ ├── README.md
│ │ │ ├── stripe-language-model.ts
│ │ │ ├── stripe-provider.ts
│ │ │ ├── tests
│ │ │ │ ├── stripe-language-model.test.ts
│ │ │ │ ├── stripe-provider.test.ts
│ │ │ │ └── utils.test.ts
│ │ │ ├── tsconfig.build.json
│ │ │ ├── tsconfig.json
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── README.md
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── README.md
│ └── token-meter
│ ├── examples
│ │ ├── anthropic.ts
│ │ ├── gemini.ts
│ │ └── openai.ts
│ ├── index.ts
│ ├── jest.config.ts
│ ├── LICENSE
│ ├── meter-event-logging.ts
│ ├── meter-event-types.ts
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── README.md
│ ├── tests
│ │ ├── meter-event-logging.test.ts
│ │ ├── model-name-normalization.test.ts
│ │ ├── token-meter-anthropic.test.ts
│ │ ├── token-meter-gemini.test.ts
│ │ ├── token-meter-general.test.ts
│ │ ├── token-meter-openai.test.ts
│ │ └── type-detection.test.ts
│ ├── token-meter.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── types.ts
│ └── utils
│ └── type-detection.ts
├── README.md
├── SECURITY.md
├── skills
│ ├── get-started-kiro.md
│ ├── README.md
│ ├── stripe-best-practices.md
│ └── sync.js
└── tools
├── modelcontextprotocol
│ ├── .dxtignore
│ ├── .gitignore
│ ├── .node-version
│ ├── .prettierrc
│ ├── build-dxt.js
│ ├── Dockerfile
│ ├── eslint.config.mjs
│ ├── jest.config.ts
│ ├── LICENSE
│ ├── manifest.json
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── README.md
│ ├── server.json
│ ├── src
│ │ ├── index.ts
│ │ └── test
│ │ └── index.test.ts
│ ├── stripe_icon.png
│ └── tsconfig.json
├── python
│ ├── .editorconfig
│ ├── .flake8
│ ├── examples
│ │ ├── crewai
│ │ │ ├── .env.template
│ │ │ ├── main.py
│ │ │ └── README.md
│ │ ├── langchain
│ │ │ ├── __init__.py
│ │ │ ├── .env.template
│ │ │ ├── main.py
│ │ │ └── README.md
│ │ ├── openai
│ │ │ ├── .env.template
│ │ │ ├── customer_support
│ │ │ │ ├── .env.template
│ │ │ │ ├── emailer.py
│ │ │ │ ├── env.py
│ │ │ │ ├── main.py
│ │ │ │ ├── pyproject.toml
│ │ │ │ ├── README.md
│ │ │ │ ├── repl.py
│ │ │ │ └── support_agent.py
│ │ │ ├── file_search
│ │ │ │ ├── main.py
│ │ │ │ └── README.md
│ │ │ └── web_search
│ │ │ ├── .env.template
│ │ │ ├── main.py
│ │ │ └── README.md
│ │ └── strands
│ │ └── main.py
│ ├── Makefile
│ ├── pyproject.toml
│ ├── README.md
│ ├── requirements.txt
│ ├── stripe_agent_toolkit
│ │ ├── __init__.py
│ │ ├── api.py
│ │ ├── configuration.py
│ │ ├── crewai
│ │ │ ├── tool.py
│ │ │ └── toolkit.py
│ │ ├── functions.py
│ │ ├── langchain
│ │ │ ├── tool.py
│ │ │ └── toolkit.py
│ │ ├── openai
│ │ │ ├── hooks.py
│ │ │ ├── tool.py
│ │ │ └── toolkit.py
│ │ ├── prompts.py
│ │ ├── schema.py
│ │ ├── strands
│ │ │ ├── __init__.py
│ │ │ ├── hooks.py
│ │ │ ├── tool.py
│ │ │ └── toolkit.py
│ │ └── tools.py
│ └── tests
│ ├── __init__.py
│ ├── test_configuration.py
│ └── test_functions.py
├── README.md
└── typescript
├── .gitignore
├── .prettierrc
├── eslint.config.mjs
├── examples
│ ├── ai-sdk
│ │ ├── .env.template
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── README.md
│ │ └── tsconfig.json
│ ├── cloudflare
│ │ ├── .dev.vars.example
│ │ ├── .gitignore
│ │ ├── biome.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── app.ts
│ │ │ ├── imageGenerator.ts
│ │ │ ├── index.ts
│ │ │ ├── oauth.ts
│ │ │ └── utils.ts
│ │ ├── tsconfig.json
│ │ ├── worker-configuration.d.ts
│ │ └── wrangler.jsonc
│ ├── langchain
│ │ ├── .env.template
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── README.md
│ │ └── tsconfig.json
│ └── openai
│ ├── .env.template
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── jest.config.ts
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── src
│ ├── ai-sdk
│ │ ├── index.ts
│ │ ├── tool.ts
│ │ └── toolkit.ts
│ ├── cloudflare
│ │ ├── index.ts
│ │ └── README.md
│ ├── langchain
│ │ ├── index.ts
│ │ ├── tool.ts
│ │ └── toolkit.ts
│ ├── modelcontextprotocol
│ │ ├── index.ts
│ │ ├── README.md
│ │ ├── register-paid-tool.ts
│ │ └── toolkit.ts
│ ├── openai
│ │ ├── index.ts
│ │ └── toolkit.ts
│ ├── shared
│ │ ├── api.ts
│ │ ├── balance
│ │ │ └── retrieveBalance.ts
│ │ ├── configuration.ts
│ │ ├── coupons
│ │ │ ├── createCoupon.ts
│ │ │ └── listCoupons.ts
│ │ ├── customers
│ │ │ ├── createCustomer.ts
│ │ │ └── listCustomers.ts
│ │ ├── disputes
│ │ │ ├── listDisputes.ts
│ │ │ └── updateDispute.ts
│ │ ├── documentation
│ │ │ └── searchDocumentation.ts
│ │ ├── invoiceItems
│ │ │ └── createInvoiceItem.ts
│ │ ├── invoices
│ │ │ ├── createInvoice.ts
│ │ │ ├── finalizeInvoice.ts
│ │ │ └── listInvoices.ts
│ │ ├── paymentIntents
│ │ │ └── listPaymentIntents.ts
│ │ ├── paymentLinks
│ │ │ └── createPaymentLink.ts
│ │ ├── prices
│ │ │ ├── createPrice.ts
│ │ │ └── listPrices.ts
│ │ ├── products
│ │ │ ├── createProduct.ts
│ │ │ └── listProducts.ts
│ │ ├── refunds
│ │ │ └── createRefund.ts
│ │ ├── subscriptions
│ │ │ ├── cancelSubscription.ts
│ │ │ ├── listSubscriptions.ts
│ │ │ └── updateSubscription.ts
│ │ └── tools.ts
│ └── test
│ ├── modelcontextprotocol
│ │ └── register-paid-tool.test.ts
│ └── shared
│ ├── balance
│ │ ├── functions.test.ts
│ │ └── parameters.test.ts
│ ├── configuration.test.ts
│ ├── customers
│ │ ├── functions.test.ts
│ │ └── parameters.test.ts
│ ├── disputes
│ │ └── functions.test.ts
│ ├── documentation
│ │ ├── functions.test.ts
│ │ └── parameters.test.ts
│ ├── invoiceItems
│ │ ├── functions.test.ts
│ │ ├── parameters.test.ts
│ │ └── prompts.test.ts
│ ├── invoices
│ │ ├── functions.test.ts
│ │ ├── parameters.test.ts
│ │ └── prompts.test.ts
│ ├── paymentIntents
│ │ ├── functions.test.ts
│ │ ├── parameters.test.ts
│ │ └── prompts.test.ts
│ ├── paymentLinks
│ │ ├── functions.test.ts
│ │ ├── parameters.test.ts
│ │ └── prompts.test.ts
│ ├── prices
│ │ ├── functions.test.ts
│ │ └── parameters.test.ts
│ ├── products
│ │ ├── functions.test.ts
│ │ └── parameters.test.ts
│ ├── refunds
│ │ ├── functions.test.ts
│ │ └── parameters.test.ts
│ └── subscriptions
│ ├── functions.test.ts
│ ├── parameters.test.ts
│ └── prompts.test.ts
├── tsconfig.json
└── tsup.config.ts
```
# Files
--------------------------------------------------------------------------------
/tools/typescript/src/shared/prices/createPrice.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const createPricePrompt = (_context: Context = {}) => `
This tool will create a price in Stripe. If a product has not already been specified, a product should be created first.
It takes three arguments:
- product (str): The ID of the product to create the price for.
- unit_amount (int): The unit amount of the price in cents.
- currency (str): The currency of the price.
`;
export const createPrice = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof createPriceParameters>>
) => {
try {
const price = await stripe.prices.create(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return price;
} catch (error) {
return 'Failed to create price';
}
};
export const createPriceParameters = (_context: Context = {}) =>
z.object({
product: z
.string()
.describe('The ID of the product to create the price for.'),
unit_amount: z
.number()
.int()
.describe('The unit amount of the price in cents.'),
currency: z.string().describe('The currency of the price.'),
});
export const createPriceAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Create price',
});
const tool = (context: Context): StripeToolDefinition => ({
method: 'create_price',
name: 'Create Price',
description: createPricePrompt(context),
inputSchema: createPriceParameters(context),
annotations: createPriceAnnotations(),
actions: {
prices: {
create: true,
},
},
execute: createPrice,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/configuration.ts:
--------------------------------------------------------------------------------
```typescript
import type {StripeToolDefinition} from './tools';
// Actions restrict the subset of API calls that can be made. They should
// be used in conjunction with Restricted API Keys. Setting a permission to false
// prevents the related "tool" from being considered.
export type Object =
| 'customers'
| 'disputes'
| 'invoices'
| 'invoiceItems'
| 'paymentLinks'
| 'products'
| 'prices'
| 'balance'
| 'refunds'
| 'paymentIntents'
| 'subscriptions'
| 'documentation'
| 'coupons';
export type Permission = 'create' | 'update' | 'read';
export type Actions = {
[K in Object]?: {
[K in Permission]?: boolean;
};
} & {
balance?: {
read?: boolean;
};
};
// Context are settings that are applied to all requests made by the integration.
export type Context = {
// Account is a Stripe Connected Account ID. If set, the integration will
// make requests for this Account.
account?: string;
// Customer is a Stripe Customer ID. If set, the integration will
// make requests for this Customer.
customer?: string;
// If set to 'modelcontextprotocol', the Stripe API calls will use a special
// header
mode?: 'modelcontextprotocol' | 'toolkit';
};
// Configuration provides various settings and options for the integration
// to tune and manage how it behaves.
export type Configuration = {
actions?: Actions;
context?: Context;
};
export const isToolAllowed = (
tool: StripeToolDefinition,
configuration: Configuration
): boolean => {
return Object.keys(tool.actions).every((resource) => {
// For each resource.permission pair, check the configuration.
// @ts-ignore
const permissions = tool.actions[resource];
return Object.keys(permissions).every((permission) => {
// @ts-ignore
return configuration.actions[resource]?.[permission] === true;
});
});
};
```
--------------------------------------------------------------------------------
/tools/modelcontextprotocol/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@stripe/mcp",
"version": "0.2.5",
"homepage": "https://github.com/stripe/ai/tree/main/tools/modelcontextprotocol",
"description": "A command line tool for setting up Stripe MCP server",
"bin": "dist/index.js",
"files": [
"dist/index.js",
"LICENSE",
"README.md",
"VERSION",
"package.json"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
"clean": "rm -rf dist",
"lint": "eslint \"./**/*.ts*\"",
"prettier": "prettier './**/*.{js,ts,md,html,css}' --write",
"prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check",
"test": "jest",
"build-dxt-extension": "node build-dxt.js"
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.1",
"@stripe/agent-toolkit": "^0.7.11",
"colors": "^1.4.0"
},
"keywords": [
"mcp",
"modelcontextprotocol",
"stripe"
],
"author": "Stripe <[email protected]> (https://stripe.com/)",
"license": "MIT",
"devDependencies": {
"@anthropic-ai/dxt": "^0.1.0",
"@eslint/compat": "^1.2.6",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.4",
"@typescript-eslint/eslint-plugin": "^8.24.1",
"esbuild": "^0.25.5",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-prettier": "^5.2.3",
"globals": "^15.15.0",
"jest": "^29.7.0",
"prettier": "^3.5.1",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
},
"pnpm": {
"overrides": {
"form-data": "^4.0.4",
"@babel/helpers": "^7.26.10",
"jsondiffpatch": "^0.7.2",
"brace-expansion": "^2.0.2",
"@eslint/plugin-kit": "^0.3.4",
"tmp": "^0.2.4"
}
}
}
```
--------------------------------------------------------------------------------
/tools/typescript/src/openai/toolkit.ts:
--------------------------------------------------------------------------------
```typescript
import StripeAPI from '../shared/api';
import tools from '../shared/tools';
import {isToolAllowed, type Configuration} from '../shared/configuration';
import {zodToJsonSchema} from 'zod-to-json-schema';
import type {
ChatCompletionTool,
ChatCompletionMessageToolCall,
ChatCompletionToolMessageParam,
} from 'openai/resources';
class StripeAgentToolkit {
private _stripe: StripeAPI;
tools: ChatCompletionTool[];
constructor({
secretKey,
configuration,
}: {
secretKey: string;
configuration: Configuration;
}) {
this._stripe = new StripeAPI(secretKey, configuration.context);
const context = configuration.context || {};
const filteredTools = tools(context).filter((tool) =>
isToolAllowed(tool, configuration)
);
this.tools = filteredTools.map((tool) => ({
type: 'function',
function: {
name: tool.method,
description: tool.description,
inputSchema: zodToJsonSchema(tool.inputSchema),
},
}));
}
getTools(): ChatCompletionTool[] {
return this.tools;
}
/**
* Processes a single OpenAI tool call by executing the requested function.
*
* @param {ChatCompletionMessageToolCall} toolCall - The tool call object from OpenAI containing
* function name, arguments, and ID.
* @returns {Promise<ChatCompletionToolMessageParam>} A promise that resolves to a tool message
* object containing the result of the tool execution with the proper format for the OpenAI API.
*/
async handleToolCall(toolCall: ChatCompletionMessageToolCall) {
const args = JSON.parse(toolCall.function.arguments);
const response = await this._stripe.run(toolCall.function.name, args);
return {
role: 'tool',
tool_call_id: toolCall.id,
content: response,
} as ChatCompletionToolMessageParam;
}
}
export default StripeAgentToolkit;
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/subscriptions/parameters.test.ts:
--------------------------------------------------------------------------------
```typescript
import {listSubscriptionsParameters} from '@/shared/subscriptions/listSubscriptions';
import {cancelSubscriptionParameters} from '@/shared/subscriptions/cancelSubscription';
import {updateSubscriptionParameters} from '@/shared/subscriptions/updateSubscription';
describe('listSubscriptionsParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = listSubscriptionsParameters({});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['customer', 'price', 'status', 'limit']);
expect(fields.length).toBe(4);
});
it('should return the correct parameters if customer is specified', () => {
const parameters = listSubscriptionsParameters({customer: 'cus_123'});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['price', 'status', 'limit']);
expect(fields.length).toBe(3);
});
});
describe('cancelSubscriptionParameters', () => {
it('should return the correct parameters', () => {
const parameters = cancelSubscriptionParameters({});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['subscription']);
});
});
describe('updateSubscriptionParameters', () => {
it('should return the correct parameters', () => {
const parameters = updateSubscriptionParameters({});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['subscription', 'proration_behavior', 'items']);
});
it('should have the required subscription parameter', () => {
const parameters = updateSubscriptionParameters({});
expect(parameters.shape.subscription).toBeDefined();
});
it('should have the optional parameters defined', () => {
const parameters = updateSubscriptionParameters({});
expect(parameters.shape.proration_behavior).toBeDefined();
expect(parameters.shape.items).toBeDefined();
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/customers/listCustomers.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const listCustomersPrompt = (_context: Context = {}) => `
This tool will fetch a list of Customers from Stripe.
It takes two arguments:
- limit (int, optional): The number of customers to return.
- email (str, optional): A case-sensitive filter on the list based on the customer's email field.
`;
export const listCustomersParameters = (_context: Context = {}) =>
z.object({
limit: z
.number()
.int()
.min(1)
.max(100)
.optional()
.describe(
'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
),
email: z
.string()
.optional()
.describe(
"A case-sensitive filter on the list based on the customer's email field. The value must be a string."
),
});
export const listCustomersAnnotations = () => ({
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
readOnlyHint: true,
title: 'List customers',
});
export const listCustomers = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof listCustomersParameters>>
) => {
try {
const customers = await stripe.customers.list(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return customers.data.map((customer) => ({id: customer.id}));
} catch (error) {
return 'Failed to list customers';
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'list_customers',
name: 'List Customers',
description: listCustomersPrompt(context),
inputSchema: listCustomersParameters(context),
annotations: listCustomersAnnotations(),
actions: {
customers: {
read: true,
},
},
execute: listCustomers,
});
export default tool;
```
--------------------------------------------------------------------------------
/llm/ai-sdk/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@stripe/ai-sdk",
"version": "0.1.1",
"description": "Stripe AI SDK - Provider and metering utilities for Vercel AI SDK",
"exports": {
"./provider": {
"types": "./dist/provider/index.d.ts",
"require": "./dist/provider/index.js",
"import": "./dist/provider/index.mjs"
},
"./meter": {
"types": "./dist/meter/index.d.ts",
"require": "./dist/meter/index.js",
"import": "./dist/meter/index.mjs"
}
},
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"prepublishOnly": "npm run build"
},
"keywords": [
"stripe",
"ai",
"provider",
"billing",
"metering",
"ai-sdk",
"vercel"
],
"author": "Stripe <[email protected]> (https://stripe.com/)",
"license": "MIT",
"engines": {
"node": ">=18"
},
"files": [
"dist/**/*",
"LICENSE",
"README.md",
"package.json"
],
"devDependencies": {
"@ai-sdk/anthropic": "^2.0.35",
"@ai-sdk/google": "^2.0.23",
"@ai-sdk/groq": "^2.0.24",
"@ai-sdk/openai": "^2.0.53",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.5",
"ai": "^5.0.76",
"dotenv": "^17.2.3",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"tsup": "^8.3.5",
"typescript": "^5.8.3"
},
"dependencies": {
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^2.0.0",
"stripe": "^17.5.0",
"zod": "^3.24.1"
},
"peerDependencies": {
"@ai-sdk/anthropic": "^2.0.41",
"@ai-sdk/google": "^2.0.27",
"@ai-sdk/openai": "^2.0.59",
"ai": "^3.4.7 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"@ai-sdk/anthropic": {
"optional": true
},
"@ai-sdk/google": {
"optional": true
},
"@ai-sdk/openai": {
"optional": true
},
"ai": {
"optional": true
}
}
}
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/invoices/parameters.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createInvoiceParameters} from '@/shared/invoices/createInvoice';
import {listInvoicesParameters} from '@/shared/invoices/listInvoices';
import {finalizeInvoiceParameters} from '@/shared/invoices/finalizeInvoice';
describe('createInvoiceParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = createInvoiceParameters({});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['customer', 'days_until_due']);
expect(fields.length).toBe(2);
});
it('should return the correct parameters if customer is specified', () => {
const parameters = createInvoiceParameters({customer: 'cus_123'});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['days_until_due']);
expect(fields.length).toBe(1);
});
});
describe('listInvoicesParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = listInvoicesParameters({});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['customer', 'limit']);
expect(fields.length).toBe(2);
});
it('should return the correct parameters if customer is specified', () => {
const parameters = listInvoicesParameters({customer: 'cus_123'});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['limit']);
expect(fields.length).toBe(1);
});
});
describe('finalizeInvoiceParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = finalizeInvoiceParameters({});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['invoice']);
expect(fields.length).toBe(1);
});
it('should return the correct parameters if customer is specified', () => {
const parameters = finalizeInvoiceParameters({customer: 'cus_123'});
const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['invoice']);
expect(fields.length).toBe(1);
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/subscriptions/prompts.test.ts:
--------------------------------------------------------------------------------
```typescript
import {listSubscriptionsPrompt} from '@/shared/subscriptions/listSubscriptions';
import {cancelSubscriptionPrompt} from '@/shared/subscriptions/cancelSubscription';
import {updateSubscriptionPrompt} from '@/shared/subscriptions/updateSubscription';
describe('listSubscriptionsPrompt', () => {
it('should return the correct prompt with no context', () => {
const prompt = listSubscriptionsPrompt({});
expect(prompt).toContain('This tool will list all subscriptions in Stripe');
expect(prompt).toContain('four arguments');
expect(prompt).toContain('- customer (str, optional)');
expect(prompt).toContain('- price (str, optional)');
expect(prompt).toContain('- status (str, optional)');
expect(prompt).toContain('- limit (int, optional)');
});
it('should return the correct prompt with customer in context', () => {
const prompt = listSubscriptionsPrompt({customer: 'cus_123'});
expect(prompt).toContain('This tool will list all subscriptions in Stripe');
expect(prompt).toContain('three arguments');
expect(prompt).not.toContain('- customer (str, optional)');
expect(prompt).toContain('- price (str, optional)');
expect(prompt).toContain('- status (str, optional)');
expect(prompt).toContain('- limit (int, optional)');
});
});
describe('cancelSubscriptionPrompt', () => {
it('should return the correct prompt', () => {
const prompt = cancelSubscriptionPrompt({});
expect(prompt).toContain('This tool will cancel a subscription in Stripe');
expect(prompt).toContain('- subscription (str, required)');
});
});
describe('updateSubscriptionPrompt', () => {
it('should return the correct prompt', () => {
const prompt = updateSubscriptionPrompt({});
expect(prompt).toContain(
'This tool will update an existing subscription in Stripe'
);
expect(prompt).toContain('- subscription (str, required)');
expect(prompt).toContain('- proration_behavior (str, optional)');
expect(prompt).toContain('- items (array, optional)');
});
});
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
```yaml
name: Bug report
description: Create a report to help us improve
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-happened
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
placeholder: Tell us what you see!
validations:
required: true
- type: textarea
id: repro-steps
attributes:
label: To Reproduce
description: Steps to reproduce the behavior
placeholder: |
1. Fetch a '...'
2. Update the '....'
3. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: code-snippets
attributes:
label: Code snippets
description: If applicable, add code snippets to help explain your problem.
render: Python
validations:
required: false
- type: input
id: os
attributes:
label: OS
placeholder: macOS
validations:
required: true
- type: input
id: language-version
attributes:
label: Language version
placeholder: Python 3.10.4
validations:
required: true
- type: input
id: lib-version
attributes:
label: Library version
placeholder: stripe-python v2.73.0
validations:
required: true
- type: input
id: api-version
attributes:
label: API version
description: See [Versioning](https://stripe.com/docs/api/versioning) in the API Reference to find which version you're using
placeholder: "2020-08-27"
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add any other context about the problem here.
validations:
required: false
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/invoiceItems/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createInvoiceItem} from '@/shared/invoiceItems/createInvoiceItem';
const Stripe = jest.fn().mockImplementation(() => ({
invoiceItems: {
create: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('createInvoiceItem', () => {
it('should create an invoice item and return it', async () => {
const params = {
customer: 'cus_123456',
price: 'price_123456',
invoice: 'in_123456',
};
const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};
const context = {};
stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);
const result = await createInvoiceItem(stripe, context, params);
expect(stripe.invoiceItems.create).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual(mockInvoiceItem);
});
it('should specify the connected account if included in context', async () => {
const params = {
customer: 'cus_123456',
price: 'price_123456',
invoice: 'in_123456',
};
const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};
const context = {
account: 'acct_123456',
};
stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);
const result = await createInvoiceItem(stripe, context, params);
expect(stripe.invoiceItems.create).toHaveBeenCalledWith(params, {
stripeAccount: context.account,
});
expect(result).toEqual(mockInvoiceItem);
});
it('should create an invoice item with a customer if included in context', async () => {
const params = {
price: 'price_123456',
invoice: 'in_123456',
};
const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};
const context = {
customer: 'cus_123456',
};
stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);
const result = await createInvoiceItem(stripe, context, params);
expect(stripe.invoiceItems.create).toHaveBeenCalledWith(
{
...params,
customer: context.customer,
},
undefined
);
expect(result).toEqual(mockInvoiceItem);
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/disputes/listDisputes.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const listDisputesPrompt = (_context: Context = {}) => `
This tool will fetch a list of disputes in Stripe.
It takes the following arguments:
- charge (string, optional): Only return disputes associated to the charge specified by this charge ID.
- payment_intent (string, optional): Only return disputes associated to the PaymentIntent specified by this PaymentIntent ID.
`;
export const listDisputesParameters = (_context: Context = {}) =>
z.object({
charge: z
.string()
.optional()
.describe(
'Only return disputes associated to the charge specified by this charge ID.'
),
payment_intent: z
.string()
.optional()
.describe(
'Only return disputes associated to the PaymentIntent specified by this PaymentIntent ID.'
),
limit: z
.number()
.int()
.min(1)
.max(100)
.default(10)
.optional()
.describe(
'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
),
});
export const listDisputesAnnotations = () => ({
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
readOnlyHint: true,
title: 'List disputes',
});
export const listDisputes = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof listDisputesParameters>>
) => {
try {
const disputes = await stripe.disputes.list(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return disputes.data.map((dispute) => ({id: dispute.id}));
} catch (error) {
return 'Failed to list disputes';
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'list_disputes',
name: 'List Disputes',
description: listDisputesPrompt(context),
inputSchema: listDisputesParameters(context),
annotations: listDisputesAnnotations(),
actions: {
disputes: {
read: true,
},
},
execute: listDisputes,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/python/examples/openai/file_search/main.py:
--------------------------------------------------------------------------------
```python
import asyncio
import os
from pydantic import BaseModel, Field
from dotenv import load_dotenv
load_dotenv()
from agents import Agent, Runner
from agents.tool import FileSearchTool
from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit
stripe_agent_toolkit = StripeAgentToolkit(
secret_key=os.getenv("STRIPE_SECRET_KEY"),
configuration={
"actions": {
"customers": {
"create": True,
},
"products": {
"create": True,
},
"prices": {
"create": True,
},
"invoice_items": {
"create": True,
},
"invoices": {
"create": True,
"update": True,
},
}
},
)
class InvoiceOutput(BaseModel):
name: str = Field(description="The name of the customer")
email: str = Field(description="The email of the customer")
service: str = Field(description="The service that the customer is invoiced for")
amount_due: int = Field(description="The dollar amount due for the invoice. Convert text to dollar amounts if needed.")
id: str = Field(description="The id of the stripe invoice")
class InvoiceListOutput(BaseModel):
invoices: list[InvoiceOutput]
invoice_agent = Agent(
name="Invoice Agent",
instructions="You are an expert at using the Stripe API to create, finalize, and send invoices to customers.",
tools=stripe_agent_toolkit.get_tools(),
)
file_search_agent = Agent(
name="File Search Agent",
instructions="You are an expert at searching for financial documents.",
tools=[
FileSearchTool(
max_num_results=50,
vector_store_ids=[os.getenv("OPENAI_VECTOR_STORE_ID")],
)
],
output_type=InvoiceListOutput,
handoffs=[invoice_agent]
)
async def main():
assignment = "Search for all customers that haven't paid across all of my documents. Handoff to the invoice agent to create, finalize, and send an invoice for each."
outstanding_invoices = await Runner.run(
file_search_agent,
assignment,
)
invoices = outstanding_invoices.final_output
print(invoices)
if __name__ == "__main__":
asyncio.run(main())
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/paymentIntents/listPaymentIntents.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const listPaymentIntentsPrompt = (context: Context = {}) => {
const customerArg = context.customer
? `The customer is already set in the context: ${context.customer}.`
: `- customer (str, optional): The ID of the customer to list payment intents for.\n`;
return `
This tool will list payment intents in Stripe.
It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
${customerArg}
- limit (int, optional): The number of payment intents to return.
`;
};
export const listPaymentIntents = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof listPaymentIntentsParameters>>
) => {
try {
if (context.customer) {
params.customer = context.customer;
}
const paymentIntents = await stripe.paymentIntents.list(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return paymentIntents.data;
} catch (error) {
return 'Failed to list payment intents';
}
};
export const listPaymentIntentsAnnotations = () => ({
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
readOnlyHint: true,
title: 'List payment intents',
});
export const listPaymentIntentsParameters = (
context: Context = {}
): z.AnyZodObject => {
const schema = z.object({
customer: z
.string()
.optional()
.describe('The ID of the customer to list payment intents for.'),
limit: z
.number()
.int()
.min(1)
.max(100)
.optional()
.describe(
'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
),
});
if (context.customer) {
return schema.omit({customer: true});
} else {
return schema;
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'list_payment_intents',
name: 'List Payment Intents',
description: listPaymentIntentsPrompt(context),
inputSchema: listPaymentIntentsParameters(context),
annotations: listPaymentIntentsAnnotations(),
actions: {
paymentIntents: {
read: true,
},
},
execute: listPaymentIntents,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/paymentLinks/createPaymentLink.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const createPaymentLinkPrompt = (_context: Context = {}) => `
This tool will create a payment link in Stripe.
It takes two arguments:
- price (str): The ID of the price to create the payment link for.
- quantity (int): The quantity of the product to include in the payment link.
- redirect_url (str, optional): The URL to redirect to after the payment is completed.
`;
export const createPaymentLink = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof createPaymentLinkParameters>>
) => {
try {
const paymentLink = await stripe.paymentLinks.create(
{
line_items: [{price: params.price, quantity: params.quantity}],
...(params.redirect_url
? {
after_completion: {
type: 'redirect',
redirect: {
url: params.redirect_url,
},
},
}
: undefined),
},
context.account ? {stripeAccount: context.account} : undefined
);
return {id: paymentLink.id, url: paymentLink.url};
} catch (error) {
return 'Failed to create payment link';
}
};
export const createPaymentLinkAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Create payment link',
});
export const createPaymentLinkParameters = (_context: Context = {}) =>
z.object({
price: z
.string()
.describe('The ID of the price to create the payment link for.'),
quantity: z
.number()
.int()
.describe('The quantity of the product to include.'),
redirect_url: z
.string()
.optional()
.describe('The URL to redirect to after the payment is completed.'),
});
const tool = (context: Context): StripeToolDefinition => ({
method: 'create_payment_link',
name: 'Create Payment Link',
description: createPaymentLinkPrompt(context),
inputSchema: createPaymentLinkParameters(context),
annotations: createPaymentLinkAnnotations(),
actions: {
paymentLinks: {
create: true,
},
},
execute: createPaymentLink,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/invoices/createInvoice.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const createInvoicePrompt = (context: Context = {}) => {
const customerArg = context.customer
? `The customer is already set in the context: ${context.customer}.`
: `- customer (str): The ID of the customer to create the invoice for.\n`;
return `
This tool will create an invoice in Stripe.
It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
${customerArg}
- days_until_due (int, optional): The number of days until the invoice is due.
`;
};
export const createInvoiceParameters = (
context: Context = {}
): z.AnyZodObject => {
const schema = z.object({
customer: z
.string()
.describe('The ID of the customer to create the invoice for.'),
days_until_due: z
.number()
.int()
.optional()
.describe('The number of days until the invoice is due.'),
});
if (context.customer) {
return schema.omit({customer: true});
} else {
return schema;
}
};
export const createInvoiceAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Create invoice',
});
export const createInvoice = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof createInvoiceParameters>>
) => {
try {
if (context.customer) {
params.customer = context.customer;
}
const invoice = await stripe.invoices.create(
{
...params,
collection_method: 'send_invoice',
},
context.account ? {stripeAccount: context.account} : undefined
);
return {
id: invoice.id,
url: invoice.hosted_invoice_url,
customer: invoice.customer,
status: invoice.status,
};
} catch (error) {
return 'Failed to create invoice';
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'create_invoice',
name: 'Create Invoice',
description: createInvoicePrompt(context),
inputSchema: createInvoiceParameters(context),
annotations: createInvoiceAnnotations(),
actions: {
invoices: {
create: true,
},
},
execute: createInvoice,
});
export default tool;
```
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
```yaml
name: CI
permissions:
contents: read
on:
workflow_dispatch: {}
push:
branches:
- main
pull_request:
branches:
- main
jobs:
typescript-build:
name: Build - TypeScript
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./tools/typescript
steps:
- name: Checkout
uses: actions/checkout@v3
- name: pnpm
uses: pnpm/action-setup@v4
with:
version: 9.11.0
- name: Node
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Clean
run: pnpm run clean
- name: Lint
run: pnpm run lint
- name: Prettier
run: pnpm run prettier-check
- name: Test
run: pnpm run test
modelcontextprotocol-build:
name: Build - Model Context Protocol
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./tools/modelcontextprotocol
steps:
- name: Checkout
uses: actions/checkout@v3
- name: pnpm
uses: pnpm/action-setup@v4
with:
version: 9.11.0
- name: Node
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Clean
run: pnpm run clean
- name: Lint
run: pnpm run lint
- name: Prettier
run: pnpm run prettier-check
- name: Test
run: pnpm run test
python-build:
name: Build - Python
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./tools/python
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Install
run: make venv
- name: Build
run: |
set -x
source .venv/bin/activate
rm -rf build dist *.egg-info
make build
python -m twine check dist/*
- name: Test
run: |
make venv
make test
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/invoiceItems/createInvoiceItem.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const createInvoiceItem = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof createInvoiceItemParameters>>
) => {
try {
if (context.customer) {
params.customer = context.customer;
}
const invoiceItem = await stripe.invoiceItems.create(
// @ts-ignore
params,
context.account ? {stripeAccount: context.account} : undefined
);
return {
id: invoiceItem.id,
invoice: invoiceItem.invoice,
};
} catch (error) {
return 'Failed to create invoice item';
}
};
export const createInvoiceItemAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Create invoice item',
});
export const createInvoiceItemParameters = (
context: Context = {}
): z.AnyZodObject => {
const schema = z.object({
customer: z
.string()
.describe('The ID of the customer to create the invoice item for.'),
price: z.string().describe('The ID of the price for the item.'),
invoice: z
.string()
.describe('The ID of the invoice to create the item for.'),
});
if (context.customer) {
return schema.omit({customer: true});
} else {
return schema;
}
};
export const createInvoiceItemPrompt = (context: Context = {}) => {
const customerArg = context.customer
? `The customer is already set in the context: ${context.customer}.`
: `- customer (str): The ID of the customer to create the invoice item for.\n`;
return `
This tool will create an invoice item in Stripe.
It takes ${context.customer ? 'two' : 'three'} arguments'}:
${customerArg}
- price (str): The ID of the price to create the invoice item for.
- invoice (str): The ID of the invoice to create the invoice item for.
`;
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'create_invoice_item',
name: 'Create Invoice Item',
description: createInvoiceItemPrompt(context),
inputSchema: createInvoiceItemParameters(context),
annotations: createInvoiceItemAnnotations(),
actions: {
invoiceItems: {
create: true,
},
},
execute: createInvoiceItem,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/invoices/listInvoices.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const listInvoices = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof listInvoicesParameters>>
) => {
try {
if (context.customer) {
params.customer = context.customer;
}
const invoices = await stripe.invoices.list(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return invoices.data;
} catch (error) {
return 'Failed to list invoices';
}
};
export const listInvoicesAnnotations = () => ({
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
readOnlyHint: true,
title: 'List invoices',
});
export const listInvoicesParameters = (
context: Context = {}
): z.AnyZodObject => {
const schema = z.object({
customer: z
.string()
.optional()
.describe('The ID of the customer to list invoices for.'),
limit: z
.number()
.int()
.min(1)
.max(100)
.optional()
.describe(
'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
),
});
if (context.customer) {
return schema.omit({customer: true});
} else {
return schema;
}
};
export const listInvoicesPrompt = (context: Context = {}) => {
const customerArg = context.customer
? `The customer is already set in the context: ${context.customer}.`
: `- customer (str, optional): The ID of the customer to list invoices for.\n`;
return `
This tool will fetch a list of Invoices from Stripe.
It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
${customerArg}
- limit (int, optional): The number of invoices to return.
`;
};
export const finalizeInvoicePrompt = (_context: Context = {}) => `
This tool will finalize an invoice in Stripe.
It takes one argument:
- invoice (str): The ID of the invoice to finalize.
`;
const tool = (context: Context): StripeToolDefinition => ({
method: 'list_invoices',
name: 'List Invoices',
description: listInvoicesPrompt(context),
inputSchema: listInvoicesParameters(context),
annotations: listInvoicesAnnotations(),
actions: {
invoices: {
read: true,
},
},
execute: listInvoices,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/products/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createProduct} from '@/shared/products/createProduct';
import {listProducts} from '@/shared/products/listProducts';
const Stripe = jest.fn().mockImplementation(() => ({
products: {
create: jest.fn(),
list: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('createProduct', () => {
it('should create a product and return it', async () => {
const params = {
name: 'Test Product',
};
const context = {};
const mockProduct = {id: 'prod_123456', name: 'Test Product'};
stripe.products.create.mockResolvedValue(mockProduct);
const result = await createProduct(stripe, context, params);
expect(stripe.products.create).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual(mockProduct);
});
it('should specify the connected account if included in context', async () => {
const params = {
name: 'Test Product',
};
const context = {
account: 'acct_123456',
};
const mockProduct = {id: 'prod_123456', name: 'Test Product'};
stripe.products.create.mockResolvedValue(mockProduct);
const result = await createProduct(stripe, context, params);
expect(stripe.products.create).toHaveBeenCalledWith(params, {
stripeAccount: context.account,
});
expect(result).toEqual(mockProduct);
});
});
describe('listProducts', () => {
it('should list products and return them', async () => {
const mockProducts = [
{id: 'prod_123456', name: 'Test Product 1'},
{id: 'prod_789012', name: 'Test Product 2'},
];
const context = {};
stripe.products.list.mockResolvedValue({data: mockProducts});
const result = await listProducts(stripe, context, {});
expect(stripe.products.list).toHaveBeenCalledWith({}, undefined);
expect(result).toEqual(mockProducts);
});
it('should specify the connected account if included in context', async () => {
const mockProducts = [
{id: 'prod_123456', name: 'Test Product 1'},
{id: 'prod_789012', name: 'Test Product 2'},
];
const context = {
account: 'acct_123456',
};
stripe.products.list.mockResolvedValue({data: mockProducts});
const result = await listProducts(stripe, context, {});
expect(stripe.products.list).toHaveBeenCalledWith(
{},
{
stripeAccount: context.account,
}
);
expect(result).toEqual(mockProducts);
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/paymentLinks/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createPaymentLink} from '@/shared/paymentLinks/createPaymentLink';
const Stripe = jest.fn().mockImplementation(() => ({
paymentLinks: {
create: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('createPaymentLink', () => {
it('should create a payment link and return it', async () => {
const params = {
line_items: [
{
price: 'price_123456',
quantity: 1,
},
],
};
const mockPaymentLink = {
id: 'pl_123456',
url: 'https://example.com',
};
const context = {};
stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);
const result = await createPaymentLink(stripe, context, {
price: 'price_123456',
quantity: 1,
});
expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual(mockPaymentLink);
});
it('should specify the connected account if included in context', async () => {
const params = {
line_items: [
{
price: 'price_123456',
quantity: 1,
},
],
};
const mockPaymentLink = {
id: 'pl_123456',
url: 'https://example.com',
};
const context = {
account: 'acct_123456',
};
stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);
const result = await createPaymentLink(stripe, context, {
price: 'price_123456',
quantity: 1,
});
expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, {
stripeAccount: context.account,
});
expect(result).toEqual(mockPaymentLink);
});
it('should specify the redirect URL if included in params', async () => {
const params = {
line_items: [
{
price: 'price_123456',
quantity: 1,
},
],
after_completion: {
type: 'redirect',
redirect: {
url: 'https://example.com',
},
},
};
const mockPaymentLink = {
id: 'pl_123456',
url: 'https://example.com',
};
const context = {};
stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);
const result = await createPaymentLink(stripe, context, {
price: 'price_123456',
quantity: 1,
redirect_url: 'https://example.com',
});
expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual(mockPaymentLink);
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/documentation/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {searchDocumentation} from '@/shared/documentation/searchDocumentation';
import {z} from 'zod';
import {searchDocumentationParameters} from '@/shared/documentation/searchDocumentation';
const Stripe = jest.fn().mockImplementation(() => ({}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
const EXPECTED_HEADERS = {
'Content-Type': 'application/json',
'X-Requested-With': 'fetch',
'User-Agent': 'stripe-agent-toolkit-typescript',
};
describe('searchDocumentation', () => {
it('should search for Stripe documentation and return sources', async () => {
const question = 'How to create Stripe checkout session?';
const requestBody: z.infer<
ReturnType<typeof searchDocumentationParameters>
> = {
question: question,
language: 'ruby',
};
const sources = [
{
type: 'docs',
url: 'https://docs.stripe.com/payments/checkout/how-checkout-works',
title: 'How checkout works',
content: '...',
},
];
const mockResponse = {
question: question,
status: 'success',
sources: sources,
};
const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: true,
status: 200,
json: jest.fn().mockResolvedValueOnce(mockResponse),
} as unknown as Response);
const result = await searchDocumentation(stripe, {}, requestBody);
expect(fetchMock).toHaveBeenCalledWith('https://ai.stripe.com/search', {
method: 'POST',
headers: EXPECTED_HEADERS,
body: JSON.stringify(requestBody),
});
expect(result).toEqual(sources);
});
it('should return failure string if search failed', async () => {
const question = 'What is the meaning of life?';
const requestBody = {
question: question,
};
const mockError = {
error: 'Invalid query',
message:
'Unable to process your question. Please rephrase it to be more specific.',
};
const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: false,
status: 400,
json: jest.fn().mockResolvedValueOnce(mockError),
} as unknown as Response);
const result = await searchDocumentation(stripe, {}, requestBody);
expect(fetchMock).toHaveBeenCalledWith('https://ai.stripe.com/search', {
method: 'POST',
headers: EXPECTED_HEADERS,
body: JSON.stringify(requestBody),
});
expect(result).toEqual('Failed to search documentation');
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/documentation/searchDocumentation.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const searchDocumentationPrompt = (_context: Context = {}) => `
This tool will take in a user question about integrating with Stripe in their application, then search and retrieve relevant Stripe documentation to answer the question.
It takes two arguments:
- question (str): The user question to search an answer for in the Stripe documentation.
- language (str, optional): The programming language to search for in the the documentation.
`;
export const searchDocumentationParameters = (
_context: Context = {}
): z.AnyZodObject =>
z.object({
question: z
.string()
.describe(
'The user question about integrating with Stripe will be used to search the documentation.'
),
language: z
.enum(['dotnet', 'go', 'java', 'node', 'php', 'ruby', 'python', 'curl'])
.optional()
.describe(
'The programming language to search for in the the documentation.'
),
});
export const searchDocumentationAnnotations = () => ({
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
readOnlyHint: true,
title: 'Search Stripe documentation',
});
export const searchDocumentation = async (
_stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof searchDocumentationParameters>>
) => {
try {
const endpoint = 'https://ai.stripe.com/search';
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'fetch',
'User-Agent':
context.mode === 'modelcontextprotocol'
? 'stripe-mcp'
: 'stripe-agent-toolkit-typescript',
},
body: JSON.stringify(params),
});
// If status not in 200-299 range, throw error
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data?.sources;
} catch (error) {
return 'Failed to search documentation';
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'search_stripe_documentation',
name: 'Search Stripe Documentation',
description: searchDocumentationPrompt(context),
inputSchema: searchDocumentationParameters(context),
annotations: searchDocumentationAnnotations(),
actions: {
documentation: {
read: true,
},
},
execute: searchDocumentation,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/prices/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createPrice} from '@/shared/prices/createPrice';
import {listPrices} from '@/shared/prices/listPrices';
const Stripe = jest.fn().mockImplementation(() => ({
prices: {
create: jest.fn(),
list: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('createPrice', () => {
it('should create a price and return it', async () => {
const params = {
unit_amount: 1000,
currency: 'usd',
product: 'prod_123456',
};
const context = {};
const mockPrice = {id: 'price_123456', unit_amount: 1000, currency: 'usd'};
stripe.prices.create.mockResolvedValue(mockPrice);
const result = await createPrice(stripe, context, params);
expect(stripe.prices.create).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual(mockPrice);
});
it('should specify the connected account if included in context', async () => {
const params = {
unit_amount: 1000,
currency: 'usd',
product: 'prod_123456',
};
const context = {
account: 'acct_123456',
};
const mockPrice = {id: 'price_123456', unit_amount: 1000, currency: 'usd'};
stripe.prices.create.mockResolvedValue(mockPrice);
const result = await createPrice(stripe, context, params);
expect(stripe.prices.create).toHaveBeenCalledWith(params, {
stripeAccount: context.account,
});
expect(result).toEqual(mockPrice);
});
});
describe('listPrices', () => {
it('should list prices and return them', async () => {
const mockPrices = [
{id: 'price_123456', unit_amount: 1000, currency: 'usd'},
{id: 'price_789012', unit_amount: 2000, currency: 'usd'},
];
const context = {};
stripe.prices.list.mockResolvedValue({data: mockPrices});
const result = await listPrices(stripe, context, {});
expect(stripe.prices.list).toHaveBeenCalledWith({}, undefined);
expect(result).toEqual(mockPrices);
});
it('should specify the connected account if included in context', async () => {
const mockPrices = [
{id: 'price_123456', unit_amount: 1000, currency: 'usd'},
{id: 'price_789012', unit_amount: 2000, currency: 'usd'},
];
const context = {
account: 'acct_123456',
};
stripe.prices.list.mockResolvedValue({data: mockPrices});
const result = await listPrices(stripe, context, {});
expect(stripe.prices.list).toHaveBeenCalledWith(
{},
{
stripeAccount: context.account,
}
);
expect(result).toEqual(mockPrices);
});
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/meter-event-logging.ts:
--------------------------------------------------------------------------------
```typescript
import type Stripe from 'stripe';
import type {UsageEvent, MeterConfig} from './meter-event-types';
/**
* Normalize model names to match Stripe's approved model list
*/
function normalizeModelName(provider: string, model: string): string {
if (provider === 'anthropic') {
// Remove date suffix (YYYYMMDD format at the end)
model = model.replace(/-\d{8}$/, '');
// Remove -latest suffix
model = model.replace(/-latest$/, '');
// Convert version number dashes to dots anywhere in the name
// Match patterns like claude-3-7, opus-4-1, sonnet-4-5, etc.
model = model.replace(/(-[a-z]+)?-(\d+)-(\d+)/g, '$1-$2.$3');
return model;
}
if (provider === 'openai') {
// Exception: keep gpt-4o-2024-05-13 as is
if (model === 'gpt-4o-2024-05-13') {
return model;
}
// Remove date suffix in format -YYYY-MM-DD
model = model.replace(/-\d{4}-\d{2}-\d{2}$/, '');
return model;
}
// For other providers (google), return as is
return model;
}
/**
* Send meter events to Stripe
*/
export async function sendMeterEventsToStripe(
stripeClient: Stripe,
config: MeterConfig,
event: UsageEvent
): Promise<void> {
const timestamp = new Date().toISOString();
// Normalize the model name before sending to Stripe
const normalizedModel = normalizeModelName(event.provider, event.model);
const fullModelName = event.provider + '/' + normalizedModel;
try {
if (event.usage.inputTokens > 0) {
const inputPayload = {
event_name: 'token-billing-tokens',
timestamp,
payload: {
stripe_customer_id: event.stripeCustomerId,
value: event.usage.inputTokens.toString(),
model: fullModelName,
token_type: 'input',
},
};
await stripeClient.v2.billing.meterEvents.create(inputPayload);
}
if (event.usage.outputTokens > 0) {
const outputPayload = {
event_name: 'token-billing-tokens',
timestamp,
payload: {
stripe_customer_id: event.stripeCustomerId,
value: event.usage.outputTokens.toString(),
model: fullModelName,
token_type: 'output',
},
};
await stripeClient.v2.billing.meterEvents.create(outputPayload);
}
} catch (error) {
console.error('Error sending meter events to Stripe:', error);
}
}
/**
* Log usage event (fire-and-forget style)
*/
export function logUsageEvent(
stripeClient: Stripe,
config: MeterConfig,
event: UsageEvent
): void {
sendMeterEventsToStripe(stripeClient, config, event).catch((err) => {
console.error('Failed to send meter events to Stripe:', err);
});
}
```
--------------------------------------------------------------------------------
/llm/token-meter/meter-event-logging.ts:
--------------------------------------------------------------------------------
```typescript
import type Stripe from 'stripe';
import type {UsageEvent, MeterConfig} from './meter-event-types';
/**
* Normalize model names to match Stripe's approved model list
*/
function normalizeModelName(provider: string, model: string): string {
if (provider === 'anthropic') {
// Remove date suffix (YYYYMMDD format at the end)
model = model.replace(/-\d{8}$/, '');
// Remove -latest suffix
model = model.replace(/-latest$/, '');
// Convert version number dashes to dots anywhere in the name
// Match patterns like claude-3-7, opus-4-1, sonnet-4-5, etc.
model = model.replace(/(-[a-z]+)?-(\d+)-(\d+)/g, '$1-$2.$3');
return model;
}
if (provider === 'openai') {
// Exception: keep gpt-4o-2024-05-13 as is
if (model === 'gpt-4o-2024-05-13') {
return model;
}
// Remove date suffix in format -YYYY-MM-DD
model = model.replace(/-\d{4}-\d{2}-\d{2}$/, '');
return model;
}
// For other providers (google), return as is
return model;
}
/**
* Send meter events to Stripe
*/
export async function sendMeterEventsToStripe(
stripeClient: Stripe,
config: MeterConfig,
event: UsageEvent
): Promise<void> {
const timestamp = new Date().toISOString();
// Normalize the model name before sending to Stripe
const normalizedModel = normalizeModelName(event.provider, event.model);
const fullModelName = event.provider + '/' + normalizedModel;
try {
if (event.usage.inputTokens > 0) {
const inputPayload = {
event_name: 'token-billing-tokens',
timestamp,
payload: {
stripe_customer_id: event.stripeCustomerId,
value: event.usage.inputTokens.toString(),
model: fullModelName,
token_type: 'input',
},
};
await stripeClient.v2.billing.meterEvents.create(inputPayload);
}
if (event.usage.outputTokens > 0) {
const outputPayload = {
event_name: 'token-billing-tokens',
timestamp,
payload: {
stripe_customer_id: event.stripeCustomerId,
value: event.usage.outputTokens.toString(),
model: fullModelName,
token_type: 'output',
},
};
await stripeClient.v2.billing.meterEvents.create(outputPayload);
}
} catch (error) {
console.error('Error sending meter events to Stripe:', error);
}
}
/**
* Log usage event (fire-and-forget style)
*/
export function logUsageEvent(
stripeClient: Stripe,
config: MeterConfig,
event: UsageEvent
): void {
sendMeterEventsToStripe(stripeClient, config, event).catch((err) => {
console.error('Failed to send meter events to Stripe:', err);
});
}
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/customers/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createCustomer} from '@/shared/customers/createCustomer';
import {listCustomers} from '@/shared/customers/listCustomers';
const Stripe = jest.fn().mockImplementation(() => ({
customers: {
create: jest.fn(),
list: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('createCustomer', () => {
it('should create a customer and return the id', async () => {
const params = {
email: '[email protected]',
name: 'Test User',
};
const context = {};
const mockCustomer = {id: 'cus_123456', email: '[email protected]'};
stripe.customers.create.mockResolvedValue(mockCustomer);
const result = await createCustomer(stripe, context, params);
expect(stripe.customers.create).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual({id: mockCustomer.id});
});
it('should specify the connected account if included in context', async () => {
const params = {
email: '[email protected]',
name: 'Test User',
};
const context = {
account: 'acct_123456',
};
const mockCustomer = {id: 'cus_123456', email: '[email protected]'};
stripe.customers.create.mockResolvedValue(mockCustomer);
const result = await createCustomer(stripe, context, params);
expect(stripe.customers.create).toHaveBeenCalledWith(params, {
stripeAccount: context.account,
});
expect(result).toEqual({id: mockCustomer.id});
});
});
describe('listCustomers', () => {
it('should list customers and return their ids', async () => {
const mockCustomers = [
{id: 'cus_123456', email: '[email protected]'},
{id: 'cus_789012', email: '[email protected]'},
];
const context = {};
stripe.customers.list.mockResolvedValue({data: mockCustomers});
const result = await listCustomers(stripe, context, {});
expect(stripe.customers.list).toHaveBeenCalledWith({}, undefined);
expect(result).toEqual(mockCustomers.map(({id}) => ({id})));
});
it('should specify the connected account if included in context', async () => {
const mockCustomers = [
{id: 'cus_123456', email: '[email protected]'},
{id: 'cus_789012', email: '[email protected]'},
];
const context = {
account: 'acct_123456',
};
stripe.customers.list.mockResolvedValue({data: mockCustomers});
const result = await listCustomers(stripe, context, {});
expect(stripe.customers.list).toHaveBeenCalledWith(
{},
{
stripeAccount: context.account,
}
);
expect(result).toEqual(mockCustomers.map(({id}) => ({id})));
});
});
```
--------------------------------------------------------------------------------
/tools/python/examples/openai/customer_support/main.py:
--------------------------------------------------------------------------------
```python
import env
import asyncio
from emailer import Emailer, Email
from typing import Union, List
import support_agent
import markdown as markdown
import json
from agents import (
ItemHelpers,
TResponseInputItem,
)
env.ensure("STRIPE_SECRET_KEY")
env.ensure("OPENAI_API_KEY")
email_address = env.ensure("EMAIL_ADDRESS")
support_address = env.get_or("SUPPORT_ADDRESS", email_address)
email_password = env.ensure("EMAIL_PASSWORD")
emailer = Emailer(email_address, email_password, support_address)
def unsure(str: str) -> bool:
return (
"not sure" in str
or "unsure" in str
or "don't know" in str
or "dont know" in str
or "do not know" in str
)
async def respond(thread: List[Email]) -> Union[Email, None]:
most_recent = thread[-1]
print(f"Got unread email:\n {json.dumps(most_recent.to_dict())}")
# Loop through the entire thread to add historical context for the agent
input_items: list[TResponseInputItem] = []
for email in thread:
input_items.append(
{
"content": (
"This is an earlier email:"
f"Email from: {email.from_address}\n"
f"To: {email.to_address}\n"
f"Subject: {email.subject}\n\n"
f"{email.body}"
),
"role": "user",
}
)
input_items.append(
{
"content": (
"This the latest email"
"You can use context from earlier emails"
"but reply specifically to the following email:"
f"Email from: {most_recent.from_address}\n"
f"To: {most_recent.to_address}\n"
f"Subject: {most_recent.subject}\n\n"
f"{most_recent.body}"
),
"role": "user",
}
)
print(f"Sending to agent:\n {json.dumps(input_items)}")
output = await support_agent.run(input_items)
body_md = ItemHelpers.text_message_outputs(output.new_items)
# Handle answers that the agent doesn't know
if unsure(body_md.lower()):
print(
f"Agent doesn't know, ignore response and keep email in the inbox:\n{body_md}"
)
return None
# OpenAI often returns the body in html fences, trim those
body_html = markdown.markdown(body_md, extensions=["tables"])
return Email(
from_address=most_recent.to_address,
to_address=most_recent.from_address,
subject=most_recent.subject,
body=body_html,
)
async def main():
await emailer.run(respond, delay=30, mark_read=True)
if __name__ == "__main__":
asyncio.run(main())
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Stripe AI SDK Billing Wrapper Integration
*
* This module provides a wrapper for Vercel AI SDK models that automatically
* reports token usage to Stripe meter events for billing purposes.
*/
import type {LanguageModelV2} from '@ai-sdk/provider';
import {AISDKWrapperV2} from './wrapperV2';
import type {AIMeterConfig} from './types';
export * from './types';
/**
* Wraps a Vercel AI SDK language model to automatically report usage to Stripe meter events.
*
* This function wraps AI SDK v2 models and returns a wrapped version that sends token usage
* to Stripe for billing.
*
* **IMPORTANT:** Only LanguageModelV2 models are supported. If you try to pass a v1 model
* (like Groq), you will get a TypeScript compilation error. This is intentional to ensure
* metering works correctly.
*
* Works with ANY AI SDK provider that implements LanguageModelV2, including:
* - OpenAI (`@ai-sdk/openai`)
* - Anthropic (`@ai-sdk/anthropic`)
* - Google (`@ai-sdk/google`)
* - Azure OpenAI (via `@ai-sdk/openai`)
* - Amazon Bedrock (`@ai-sdk/amazon-bedrock`)
* - Together AI (via `createOpenAI` from `@ai-sdk/openai`)
* - Any other provider with v2 specification
*
* Note: Providers using v1 specification (like Groq) are not supported
*
* @template T - The type of the language model (must extend LanguageModelV2).
* @param model - The Vercel AI SDK language model instance to wrap (must be v2).
* @param stripeApiKey - The Stripe API key.
* @param stripeCustomerId - The Stripe customer ID for meter events.
* @returns The wrapped model with Stripe meter event tracking.
*
* @example
* ```typescript
* import { meteredModel } from '@stripe/ai-sdk/meter';
* import { openai } from '@ai-sdk/openai';
* import { generateText } from 'ai';
*
* const model = meteredModel(openai('gpt-4'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
*
* const { text } = await generateText({
* model: model,
* prompt: 'Say "Hello, World!" and nothing else.',
* });
* ```
*/
export function meteredModel<T extends LanguageModelV2>(
model: T,
stripeApiKey: string,
stripeCustomerId: string
): T {
const config: AIMeterConfig = {
stripeApiKey,
stripeCustomerId,
};
// Verify at runtime that the model is actually v2
if (
!model ||
typeof model !== 'object' ||
!('specificationVersion' in model) ||
model.specificationVersion !== 'v2'
) {
throw new Error(
'[Stripe AI SDK] Invalid model: Only LanguageModelV2 models are supported. ' +
'The provided model does not have specificationVersion "v2". ' +
'Please use a supported provider (OpenAI, Anthropic, Google, etc.).'
);
}
return new AISDKWrapperV2(model, config) as unknown as T;
}
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/configuration.test.ts:
--------------------------------------------------------------------------------
```typescript
import {z} from 'zod';
import {isToolAllowed} from '@/shared/configuration';
describe('isToolAllowed', () => {
it('should return true if all permissions are allowed', () => {
const tool = {
method: 'test',
name: 'Test',
description: 'Test',
inputSchema: z.object({
foo: z.string(),
}),
annotations: {
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Test',
},
execute: async (a: any, b: any, c: any) => {},
actions: {
customers: {
create: true,
read: true,
},
invoices: {
create: true,
read: true,
},
},
};
const configuration = {
actions: {
customers: {
create: true,
read: true,
},
invoices: {
create: true,
read: true,
},
},
};
expect(isToolAllowed(tool, configuration)).toBe(true);
});
it('should return false if any permission is denied', () => {
const tool = {
method: 'test',
name: 'Test',
description: 'Test',
inputSchema: z.object({
foo: z.string(),
}),
annotations: {
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Test',
},
execute: async (a: any, b: any, c: any) => {},
actions: {
customers: {
create: true,
read: true,
},
invoices: {
create: true,
read: true,
},
},
};
const configuration = {
actions: {
customers: {
create: true,
read: true,
},
invoices: {
create: true,
read: false,
},
},
};
expect(isToolAllowed(tool, configuration)).toBe(false);
});
it('should return false if any resource is not allowed', () => {
const tool = {
method: 'test',
name: 'Test',
description: 'Test',
inputSchema: z.object({
foo: z.string(),
}),
annotations: {
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Test',
},
execute: async (a: any, b: any, c: any) => {},
actions: {
paymentLinks: {
create: true,
},
},
};
const configuration = {
actions: {
customers: {
create: true,
read: true,
},
invoices: {
create: true,
read: true,
},
},
};
expect(isToolAllowed(tool, configuration)).toBe(false);
});
});
```
--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/api.py:
--------------------------------------------------------------------------------
```python
"""Util that calls Stripe."""
from __future__ import annotations
import json
import stripe
from typing import Optional, Dict, Callable, Any
from pydantic import BaseModel
from .configuration import Context
from .functions import (
create_customer,
list_customers,
create_product,
list_products,
create_price,
list_prices,
create_payment_link,
list_invoices,
create_invoice,
create_invoice_item,
finalize_invoice,
retrieve_balance,
create_refund,
list_payment_intents,
create_billing_portal_session,
)
class StripeAPI(BaseModel):
""" "Wrapper for Stripe API"""
_context: Context
_method_dispatch: Dict[str, Callable[..., Any]]
def __init__(self, secret_key: str, context: Optional[Context]):
super().__init__()
self._context = context if context is not None else Context()
self._method_dispatch = {
"create_customer": create_customer,
"list_customers": list_customers,
"create_product": create_product,
"list_products": list_products,
"create_price": create_price,
"list_prices": list_prices,
"create_payment_link": create_payment_link,
"list_invoices": list_invoices,
"create_invoice": create_invoice,
"create_invoice_item": create_invoice_item,
"finalize_invoice": finalize_invoice,
"retrieve_balance": retrieve_balance,
"create_refund": create_refund,
"list_payment_intents": list_payment_intents,
"create_billing_portal_session": create_billing_portal_session,
}
stripe.api_key = secret_key
stripe.set_app_info(
"stripe-agent-toolkit-python",
version="0.6.2",
url="https://github.com/stripe/ai",
)
def create_meter_event(
self, event: str, customer: str, value: Optional[str] = None
) -> str:
meter_event_data: dict = {
"event_name": event,
"payload": {
"stripe_customer_id": customer,
},
}
if value is not None:
meter_event_data["payload"]["value"] = value
if self._context.get("account") is not None:
account = self._context.get("account")
if account is not None:
meter_event_data["stripe_account"] = account
stripe.billing.MeterEvent.create(**meter_event_data)
def run(self, method: str, *args, **kwargs) -> str:
if method not in self._method_dispatch:
raise ValueError(f"Invalid method: {method}")
function = self._method_dispatch[method]
result = function(self._context, *args, **kwargs)
return json.dumps(result)
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/subscriptions/listSubscriptions.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const listSubscriptions = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof listSubscriptionsParameters>>
) => {
try {
if (context.customer) {
params.customer = context.customer;
}
const subscriptions = await stripe.subscriptions.list(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return subscriptions.data;
} catch (error) {
return 'Failed to list subscriptions';
}
};
export const listSubscriptionsParameters = (
context: Context = {}
): z.AnyZodObject => {
const schema = z.object({
customer: z
.string()
.optional()
.describe('The ID of the customer to list subscriptions for.'),
price: z
.string()
.optional()
.describe('The ID of the price to list subscriptions for.'),
status: z
.enum([
'active',
'past_due',
'unpaid',
'canceled',
'incomplete',
'incomplete_expired',
'trialing',
'all',
])
.optional()
.describe('The status of the subscriptions to retrieve.'),
limit: z
.number()
.int()
.min(1)
.max(100)
.optional()
.describe(
'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
),
});
if (context.customer) {
return schema.omit({customer: true});
} else {
return schema;
}
};
export const listSubscriptionsPrompt = (context: Context = {}): string => {
const customerArg = context.customer
? `The customer is already set in the context: ${context.customer}.`
: `- customer (str, optional): The ID of the customer to list subscriptions for.\n`;
return `
This tool will list all subscriptions in Stripe.
It takes ${context.customer ? 'three' : 'four'} arguments:
${customerArg}
- price (str, optional): The ID of the price to list subscriptions for.
- status (str, optional): The status of the subscriptions to list.
- limit (int, optional): The number of subscriptions to return.
`;
};
export const listSubscriptionsAnnotations = () => ({
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
readOnlyHint: true,
title: 'List subscriptions',
});
const tool = (context: Context): StripeToolDefinition => ({
method: 'list_subscriptions',
name: 'List Subscriptions',
description: listSubscriptionsPrompt(context),
inputSchema: listSubscriptionsParameters(context),
annotations: listSubscriptionsAnnotations(),
actions: {
subscriptions: {
read: true,
},
},
execute: listSubscriptions,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/tools.ts:
--------------------------------------------------------------------------------
```typescript
import {z} from 'zod';
import createCustomerTool from '@/shared/customers/createCustomer';
import listCustomersTool from '@/shared/customers/listCustomers';
import createProductTool from '@/shared/products/createProduct';
import listProductsTool from '@/shared/products/listProducts';
import createPriceTool from '@/shared/prices/createPrice';
import listPricesTool from '@/shared/prices/listPrices';
import createPaymentLinkTool from '@/shared/paymentLinks/createPaymentLink';
import createInvoiceTool from '@/shared/invoices/createInvoice';
import listInvoicesTool from '@/shared/invoices/listInvoices';
import createInvoiceItemTool from '@/shared/invoiceItems/createInvoiceItem';
import finalizeInvoiceTool from '@/shared/invoices/finalizeInvoice';
import retrieveBalanceTool from '@/shared/balance/retrieveBalance';
import listCouponsTool from '@/shared/coupons/listCoupons';
import createCouponTool from '@/shared/coupons/createCoupon';
import createRefundTool from '@/shared/refunds/createRefund';
import listPaymentIntentsTool from '@/shared/paymentIntents/listPaymentIntents';
import listSubscriptionsTool from '@/shared/subscriptions/listSubscriptions';
import cancelSubscriptionTool from '@/shared/subscriptions/cancelSubscription';
import updateSubscriptionTool from '@/shared/subscriptions/updateSubscription';
import searchDocumentationTool from '@/shared/documentation/searchDocumentation';
import listDisputesTool from '@/shared/disputes/listDisputes';
import updateDisputeTool from '@/shared/disputes/updateDispute';
import {Context} from './configuration';
import Stripe from 'stripe';
export type StripeToolDefinition = {
method: string;
name: string;
description: string;
inputSchema: z.ZodObject<any, any, any, any>;
annotations: {
destructiveHint?: boolean;
idempotentHint?: boolean;
openWorldHint?: boolean;
readOnlyHint?: boolean;
title?: string;
};
actions: {
[key: string]: {
[action: string]: boolean;
};
};
execute: (stripe: Stripe, context: Context, params: any) => Promise<any>;
};
const tools = (context: Context): StripeToolDefinition[] => [
createCustomerTool(context),
listCustomersTool(context),
createProductTool(context),
listProductsTool(context),
createPriceTool(context),
listPricesTool(context),
createPaymentLinkTool(context),
createInvoiceTool(context),
listInvoicesTool(context),
createInvoiceItemTool(context),
finalizeInvoiceTool(context),
retrieveBalanceTool(context),
createRefundTool(context),
listPaymentIntentsTool(context),
listSubscriptionsTool(context),
cancelSubscriptionTool(context),
updateSubscriptionTool(context),
searchDocumentationTool(context),
listCouponsTool(context),
createCouponTool(context),
updateDisputeTool(context),
listDisputesTool(context),
];
export default tools;
```
--------------------------------------------------------------------------------
/tools/typescript/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@stripe/agent-toolkit",
"version": "0.8.1",
"homepage": "https://github.com/stripe/ai",
"scripts": {
"build": "tsup",
"clean": "rm -rf langchain ai-sdk modelcontextprotocol openai cloudflare",
"lint": "eslint \"./**/*.ts*\"",
"prettier": "prettier './**/*.{js,ts,md,html,css}' --write",
"prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check",
"test": "jest",
"eval": "cd ../evals && pnpm test"
},
"exports": {
"./langchain": {
"types": "./langchain/index.d.ts",
"require": "./langchain/index.js",
"import": "./langchain/index.mjs"
},
"./ai-sdk": {
"types": "./ai-sdk/index.d.ts",
"require": "./ai-sdk/index.js",
"import": "./ai-sdk/index.mjs"
},
"./openai": {
"types": "./openai/index.d.ts",
"require": "./openai/index.js",
"import": "./openai/index.mjs"
},
"./modelcontextprotocol": {
"types": "./modelcontextprotocol/index.d.ts",
"require": "./modelcontextprotocol/index.js",
"import": "./modelcontextprotocol/index.mjs"
},
"./cloudflare": {
"types": "./cloudflare/index.d.ts",
"require": "./cloudflare/index.js",
"import": "./cloudflare/index.mjs"
}
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18"
},
"author": "Stripe <[email protected]> (https://stripe.com/)",
"contributors": [
"Steve Kaliski <[email protected]>"
],
"license": "MIT",
"devDependencies": {
"@eslint/compat": "^1.2.4",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.5",
"@typescript-eslint/eslint-plugin": "^8.19.1",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-prettier": "^5.2.1",
"globals": "^15.14.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"tsup": "^8.3.5",
"typescript": "^5.8.3"
},
"dependencies": {
"@ai-sdk/provider": "^2.0.0",
"stripe": "^17.5.0",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.24.6"
},
"peerDependencies": {
"@langchain/core": "^0.3.6",
"@modelcontextprotocol/sdk": "^1.17.1",
"agents": "^0.0.84",
"ai": "^5.0.89",
"openai": "^4.86.1"
},
"workspaces": [
".",
"examples/*"
],
"pnpm": {
"overrides": {
"form-data": "^4.0.4",
"esbuild": "^0.25.0",
"@babel/helpers": "^7.26.10",
"jsondiffpatch": "^0.7.2",
"nanoid": "^3.3.8",
"brace-expansion": "^2.0.2",
"@eslint/plugin-kit": "^0.3.4",
"tmp": "^0.2.4"
}
},
"files": [
"ai-sdk/**/*",
"langchain/**/*",
"modelcontextprotocol/**/*",
"openai/**/*",
"cloudflare/**/*",
"LICENSE",
"README.md",
"VERSION",
"package.json"
]
}
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/paymentIntents/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {listPaymentIntents} from '@/shared/paymentIntents/listPaymentIntents';
const Stripe = jest.fn().mockImplementation(() => ({
paymentIntents: {
list: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('listPaymentIntents', () => {
it('should list payment intents and return them', async () => {
const mockPaymentIntents = [
{
id: 'pi_123456',
customer: 'cus_123456',
amount: 1000,
status: 'succeeded',
description: 'Test Payment Intent',
},
];
const context = {};
stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
const result = await listPaymentIntents(stripe, context, {});
expect(stripe.paymentIntents.list).toHaveBeenCalledWith({}, undefined);
expect(result).toEqual(mockPaymentIntents);
});
it('should list payment intents for a specific customer', async () => {
const mockPaymentIntents = [
{
id: 'pi_123456',
customer: 'cus_123456',
amount: 1000,
status: 'succeeded',
description: 'Test Payment Intent',
},
];
const context = {};
stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
const result = await listPaymentIntents(stripe, context, {
customer: 'cus_123456',
});
expect(stripe.paymentIntents.list).toHaveBeenCalledWith(
{
customer: 'cus_123456',
},
undefined
);
expect(result).toEqual(mockPaymentIntents);
});
it('should specify the connected account if included in context', async () => {
const mockPaymentIntents = [
{
id: 'pi_123456',
customer: 'cus_123456',
amount: 1000,
status: 'succeeded',
description: 'Test Payment Intent',
},
];
const context = {
account: 'acct_123456',
};
stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
const result = await listPaymentIntents(stripe, context, {});
expect(stripe.paymentIntents.list).toHaveBeenCalledWith(
{},
{
stripeAccount: context.account,
}
);
expect(result).toEqual(mockPaymentIntents);
});
it('should list payment intents for a specific customer if included in context', async () => {
const mockPaymentIntents = [
{
id: 'pi_123456',
customer: 'cus_123456',
amount: 1000,
status: 'succeeded',
description: 'Test Payment Intent',
},
];
const context = {
customer: 'cus_123456',
};
stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
const result = await listPaymentIntents(stripe, context, {});
expect(stripe.paymentIntents.list).toHaveBeenCalledWith(
{customer: context.customer},
undefined
);
expect(result).toEqual(mockPaymentIntents);
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/ai-sdk/toolkit.ts:
--------------------------------------------------------------------------------
```typescript
import StripeAPI from '../shared/api';
import tools from '../shared/tools';
import {isToolAllowed, type Configuration} from '../shared/configuration';
import type {
LanguageModelV2Middleware,
LanguageModelV2Usage,
} from '@ai-sdk/provider';
import StripeTool from './tool';
type ProviderTool = ReturnType<typeof StripeTool>;
type WrapGenerateOptions = Parameters<
NonNullable<LanguageModelV2Middleware['wrapGenerate']>
>[0];
type WrapStreamOptions = Parameters<
NonNullable<LanguageModelV2Middleware['wrapStream']>
>[0];
type StripeMiddlewareConfig = {
billing?: {
type?: 'token';
customer: string;
meters: {
input?: string;
output?: string;
};
};
};
class StripeAgentToolkit {
private _stripe: StripeAPI;
tools: Record<string, ProviderTool>;
constructor({
secretKey,
configuration,
}: {
secretKey: string;
configuration: Configuration;
}) {
this._stripe = new StripeAPI(secretKey, configuration.context);
this.tools = {};
const context = configuration.context || {};
const filteredTools = tools(context).filter((tool) =>
isToolAllowed(tool, configuration)
);
filteredTools.forEach((tool) => {
// @ts-ignore
this.tools[tool.method] = StripeTool(
this._stripe,
tool.method,
tool.description,
tool.inputSchema
);
});
}
middleware(config: StripeMiddlewareConfig): LanguageModelV2Middleware {
const bill = async (usage?: LanguageModelV2Usage) => {
if (!config.billing || !usage) {
return;
}
const {inputTokens, outputTokens} = usage;
const inputValue = (inputTokens ?? 0).toString();
const outputValue = (outputTokens ?? 0).toString();
if (config.billing.meters.input) {
await this._stripe.createMeterEvent({
event: config.billing.meters.input,
customer: config.billing.customer,
value: inputValue,
});
}
if (config.billing.meters.output) {
await this._stripe.createMeterEvent({
event: config.billing.meters.output,
customer: config.billing.customer,
value: outputValue,
});
}
};
return {
wrapGenerate: async ({doGenerate}: WrapGenerateOptions) => {
const result = await doGenerate();
await bill(result.usage);
return result;
},
wrapStream: async ({doStream}: WrapStreamOptions) => {
const {stream, ...rest} = await doStream();
const transformStream = new TransformStream<any, any>({
async transform(chunk, controller) {
if (chunk?.type === 'finish') {
await bill(chunk.usage);
}
controller.enqueue(chunk);
},
});
return {
stream: stream.pipeThrough(transformStream),
...rest,
};
},
};
}
getTools(): Record<string, ProviderTool> {
return this.tools;
}
}
export default StripeAgentToolkit;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/coupons/createCoupon.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const createCouponPrompt = (_context: Context = {}) => `
This tool will create a coupon in Stripe.
It takes several arguments:
- name (str): The name of the coupon.
Only use one of percent_off or amount_off, not both:
- percent_off (number, optional): The percentage discount to apply (between 0 and 100).
- amount_off (number, optional): The amount to subtract from an invoice (in cents).
Optional arguments for duration. Use if specific duration is desired, otherwise default to 'once'.
- duration (str, optional): How long the discount will last ('once', 'repeating', or 'forever'). Defaults to 'once'.
- duration_in_months (number, optional): The number of months the discount will last if duration is repeating.
`;
export const createCouponParameters = (
_context: Context = {}
): z.AnyZodObject =>
z.object({
name: z
.string()
.describe(
'Name of the coupon displayed to customers on invoices or receipts'
),
percent_off: z
.number()
.min(0)
.max(100)
.optional()
.describe(
'A positive float larger than 0, and smaller or equal to 100, that represents the discount the coupon will apply (required if amount_off is not passed)'
),
amount_off: z
.number()
.describe(
'A positive integer representing the amount to subtract from an invoice total (required if percent_off is not passed)'
),
currency: z
.string()
.optional()
.default('USD')
.describe(
'Three-letter ISO code for the currency of the amount_off parameter (required if amount_off is passed). Infer based on the amount_off. For example, if a coupon is $2 off, set currency to be USD.'
),
duration: z
.enum(['once', 'repeating', 'forever'])
.default('once')
.optional()
.describe('How long the discount will last. Defaults to "once"'),
duration_in_months: z
.number()
.optional()
.describe(
'The number of months the discount will last if duration is repeating'
),
});
export const createCouponAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Create coupon',
});
export const createCoupon = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof createCouponParameters>>
) => {
try {
const coupon = await stripe.coupons.create(
params,
context.account ? {stripeAccount: context.account} : undefined
);
return {id: coupon.id};
} catch (error: any) {
return `Failed to create coupon: ${error.message}`;
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'create_coupon',
name: 'Create Coupon',
description: createCouponPrompt(context),
inputSchema: createCouponParameters(context),
annotations: createCouponAnnotations(),
actions: {
coupons: {
create: true,
},
},
execute: createCoupon,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/disputes/updateDispute.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const updateDisputePrompt = (_context: Context = {}) => `
When you receive a dispute, contacting your customer is always the best first step. If that doesn't work, you can submit evidence to help resolve the dispute in your favor. This tool helps.
It takes the following arguments:
- dispute (string): The ID of the dispute to update
- evidence (object, optional): Evidence to upload for the dispute.
- cancellation_policy_disclosure (string)
- cancellation_rebuttal (string)
- duplicate_charge_explanation (string)
- uncategorized_text (string, optional): Any additional evidence or statements.
- submit (boolean, optional): Whether to immediately submit evidence to the bank. If false, evidence is staged on the dispute.
`;
export const updateDisputeParameters = (_context: Context = {}) =>
z.object({
dispute: z.string().describe('The ID of the dispute to update'),
evidence: z
.object({
cancellation_policy_disclosure: z
.string()
.max(20000)
.optional()
.describe(
'An explanation of how and when the customer was shown your refund policy prior to purchase.'
),
duplicate_charge_explanation: z
.string()
.max(20000)
.optional()
.describe(
'An explanation of the difference between the disputed charge versus the prior charge that appears to be a duplicate.'
),
uncategorized_text: z
.string()
.max(20000)
.optional()
.describe('Any additional evidence or statements.'),
})
.optional()
.describe(
'Evidence to upload, to respond to a dispute. Updating any field in the hash will submit all fields in the hash for review.'
),
submit: z
.boolean()
.optional()
.describe(
'Whether to immediately submit evidence to the bank. If false, evidence is staged on the dispute.'
),
});
export const updateDisputeAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Update dispute',
});
export const updateDispute = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof updateDisputeParameters>>
) => {
try {
const updateParams: Stripe.DisputeUpdateParams = {
evidence: params.evidence,
submit: params.submit,
};
const updatedDispute = await stripe.disputes.update(
params.dispute,
updateParams,
context.account ? {stripeAccount: context.account} : undefined
);
return {id: updatedDispute.id};
} catch (error) {
return 'Failed to update dispute';
}
};
const tool = (context: Context): StripeToolDefinition => ({
method: 'update_dispute',
name: 'Update Dispute',
description: updateDisputePrompt(context),
inputSchema: updateDisputeParameters(context),
annotations: updateDisputeAnnotations(),
actions: {
disputes: {
update: true,
},
},
execute: updateDispute,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/disputes/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {updateDispute} from '@/shared/disputes/updateDispute';
import {listDisputes} from '@/shared/disputes/listDisputes';
const Stripe = jest.fn().mockImplementation(() => ({
disputes: {
update: jest.fn(),
list: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('updateDispute', () => {
it('should update a dispute and return the id', async () => {
const params = {
dispute: 'dp_123456',
evidence: {
uncategorized_text: 'Test product',
},
submit: true,
};
const context = {};
const mockDispute = {id: 'dp_123456'};
stripe.disputes.update.mockResolvedValue(mockDispute);
const result = await updateDispute(stripe, context, params);
expect(stripe.disputes.update).toHaveBeenCalledWith(
params.dispute,
{
evidence: params.evidence,
submit: params.submit,
},
undefined
);
expect(result).toEqual({id: mockDispute.id});
});
it('should specify the connected account if included in context', async () => {
const params = {
dispute: 'dp_123456',
evidence: {
uncategorized_text: 'Test product',
},
submit: true,
};
const context = {
account: 'acct_123456',
};
const mockDispute = {id: 'dp_123456'};
stripe.disputes.update.mockResolvedValue(mockDispute);
const result = await updateDispute(stripe, context, params);
expect(stripe.disputes.update).toHaveBeenCalledWith(
params.dispute,
{
evidence: params.evidence,
submit: params.submit,
},
{
stripeAccount: context.account,
}
);
expect(result).toEqual({id: mockDispute.id});
});
});
describe('listDisputes', () => {
it('should list disputes and return their ids', async () => {
const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
const context = {};
stripe.disputes.list.mockResolvedValue({data: mockDisputes});
const result = await listDisputes(stripe, context, {});
expect(stripe.disputes.list).toHaveBeenCalledWith({}, undefined);
expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
});
it('should specify the connected account if included in context', async () => {
const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
const context = {
account: 'acct_123456',
};
stripe.disputes.list.mockResolvedValue({data: mockDisputes});
const result = await listDisputes(stripe, context, {});
expect(stripe.disputes.list).toHaveBeenCalledWith(
{},
{
stripeAccount: context.account,
}
);
expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
});
it('should pass through list parameters', async () => {
const params = {
charge: 'ch_123456',
payment_intent: 'pi_123456',
limit: 5,
};
const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
const context = {};
stripe.disputes.list.mockResolvedValue({data: mockDisputes});
const result = await listDisputes(stripe, context, params);
expect(stripe.disputes.list).toHaveBeenCalledWith(params, undefined);
expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
});
});
```
--------------------------------------------------------------------------------
/tools/typescript/src/shared/subscriptions/updateSubscription.ts:
--------------------------------------------------------------------------------
```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {StripeToolDefinition} from '@/shared/tools';
export const updateSubscriptionPrompt = (_context: Context = {}): string => {
return `This tool will update an existing subscription in Stripe. If changing an existing subscription item, the existing subscription item has to be set to deleted and the new one has to be added.
It takes the following arguments:
- subscription (str, required): The ID of the subscription to update.
- proration_behavior (str, optional): Determines how to handle prorations when the subscription items change. Options: 'create_prorations', 'none', 'always_invoice', 'none_implicit'.
- items (array, optional): A list of subscription items to update, add, or remove. Each item can have the following properties:
- id (str, optional): The ID of the subscription item to modify.
- price (str, optional): The ID of the price to switch to.
- quantity (int, optional): The quantity of the plan to subscribe to.
- deleted (bool, optional): Whether to delete this item.
`;
};
export const updateSubscription = async (
stripe: Stripe,
context: Context,
params: z.infer<ReturnType<typeof updateSubscriptionParameters>>
) => {
try {
const {subscription: subscriptionId, ...updateParams} = params;
const subscription = await stripe.subscriptions.update(
subscriptionId,
updateParams,
context.account ? {stripeAccount: context.account} : undefined
);
return subscription;
} catch (error) {
return 'Failed to update subscription';
}
};
export const updateSubscriptionParameters = (
_context: Context = {}
): z.AnyZodObject => {
return z.object({
subscription: z.string().describe('The ID of the subscription to update.'),
proration_behavior: z
.enum(['create_prorations', 'none', 'always_invoice', 'none_implicit'])
.optional()
.describe(
'Determines how to handle prorations when the subscription items change.'
),
items: z
.array(
z.object({
id: z
.string()
.optional()
.describe('The ID of the subscription item to modify.'),
price: z
.string()
.optional()
.describe('The ID of the price to switch to.'),
quantity: z
.number()
.int()
.min(1)
.optional()
.describe('The quantity of the plan to subscribe to.'),
deleted: z
.boolean()
.optional()
.describe('Whether to delete this item.'),
})
)
.optional()
.describe('A list of subscription items to update, add, or remove.'),
});
};
export const updateSubscriptionAnnotations = () => ({
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
readOnlyHint: false,
title: 'Update subscription',
});
const tool = (context: Context): StripeToolDefinition => ({
method: 'update_subscription',
name: 'Update Subscription',
description: updateSubscriptionPrompt(context),
inputSchema: updateSubscriptionParameters(context),
annotations: updateSubscriptionAnnotations(),
actions: {
subscriptions: {
update: true,
},
},
execute: updateSubscription,
});
export default tool;
```
--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/prompts.py:
--------------------------------------------------------------------------------
```python
CREATE_CUSTOMER_PROMPT = """
This tool will create a customer in Stripe.
It takes two arguments:
- name (str): The name of the customer.
- email (str, optional): The email of the customer.
"""
LIST_CUSTOMERS_PROMPT = """
This tool will fetch a list of Customers from Stripe.
It takes no input.
"""
CREATE_PRODUCT_PROMPT = """
This tool will create a product in Stripe.
It takes two arguments:
- name (str): The name of the product.
- description (str, optional): The description of the product.
"""
LIST_PRODUCTS_PROMPT = """
This tool will fetch a list of Products from Stripe.
It takes one optional argument:
- limit (int, optional): The number of products to return.
"""
CREATE_PRICE_PROMPT = """
This tool will create a price in Stripe. If a product has not already been
specified, a product should be created first.
It takes three arguments:
- product (str): The ID of the product to create the price for.
- unit_amount (int): The unit amount of the price in cents.
- currency (str): The currency of the price.
"""
LIST_PRICES_PROMPT = """
This tool will fetch a list of Prices from Stripe.
It takes two arguments:
- product (str, optional): The ID of the product to list prices for.
- limit (int, optional): The number of prices to return.
"""
CREATE_PAYMENT_LINK_PROMPT = """
This tool will create a payment link in Stripe.
It takes two arguments:
- price (str): The ID of the price to create the payment link for.
- quantity (int): The quantity of the product to include in the payment link.
- redirect_url (string, optional): The URL the customer will be redirected to after the purchase is complete.
"""
LIST_INVOICES_PROMPT = """
This tool will list invoices in Stripe.
It takes two arguments:
- customer (str, optional): The ID of the customer to list the invoices for.
- limit (int, optional): The number of prices to return.
"""
CREATE_INVOICE_PROMPT = """
This tool will create an invoice in Stripe.
It takes one argument:
- customer (str): The ID of the customer to create the invoice for.
"""
CREATE_INVOICE_ITEM_PROMPT = """
This tool will create an invoice item in Stripe.
It takes two arguments:
- customer (str): The ID of the customer to create the invoice item for.
- price (str): The ID of the price to create the invoice item for.
- invoice (str): The ID of the invoice to create the invoice item for.
"""
FINALIZE_INVOICE_PROMPT = """
This tool will finalize an invoice in Stripe.
It takes one argument:
- invoice (str): The ID of the invoice to finalize.
"""
RETRIEVE_BALANCE_PROMPT = """
This tool will retrieve the balance from Stripe. It takes no input.
"""
CREATE_REFUND_PROMPT = """
This tool will refund a payment intent in Stripe.
It takes three arguments:
- payment_intent (str): The ID of the payment intent to refund.
- amount (int, optional): The amount to refund in cents.
- reason (str, optional): The reason for the refund.
"""
LIST_PAYMENT_INTENTS_PROMPT = """
This tool will list payment intents in Stripe.
It takes two arguments:
- customer (str, optional): The ID of the customer to list payment intents for.
- limit (int, optional): The number of payment intents to return.
"""
CREATE_BILLING_PORTAL_SESSION_PROMPT = """
This tool will create a billing portal session.
It takes two arguments:
- customer (str): The ID of the customer to create the invoice item for.
- return_url (str, optional): The default URL to return to afterwards.
"""
```
--------------------------------------------------------------------------------
/tools/typescript/examples/cloudflare/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {z} from 'zod';
import {
PaymentState,
experimental_PaidMcpAgent as PaidMcpAgent,
} from '@stripe/agent-toolkit/cloudflare';
import {generateImage} from './imageGenerator';
import {OAuthProvider} from '@cloudflare/workers-oauth-provider';
import app from './app';
type Bindings = Env;
type Props = {
userEmail: string;
};
type State = PaymentState & {};
export class MyMCP extends PaidMcpAgent<Bindings, State, Props> {
server = new McpServer({
name: 'Demo',
version: '1.0.0',
});
initialState: State = {};
async init() {
this.server.tool('add', {a: z.number(), b: z.number()}, ({a, b}) => {
return {
content: [{type: 'text', text: `Result: ${a + b}`}],
};
});
// One-time payment, then the tool is usable forever
this.paidTool(
'buy_premium',
'Buy a premium account',
{},
() => {
return {
content: [{type: 'text', text: `You now have a premium account!`}],
};
},
{
checkout: {
success_url: 'http://localhost:4242/payment/success',
line_items: [
{
price: process.env.STRIPE_PRICE_ID_ONE_TIME_PAYMENT,
quantity: 1,
},
],
mode: 'payment',
},
paymentReason:
'Open the checkout link in the browser to buy a premium account.',
}
);
// Subscription, then the tool is usable as long as the subscription is active
this.paidTool(
'big_add',
'Add two numbers together',
{
a: z.number(),
b: z.number(),
},
({a, b}) => {
return {
content: [{type: 'text', text: `Result: ${a + b}`}],
};
},
{
checkout: {
success_url: 'http://localhost:4242/payment/success',
line_items: [
{
price: process.env.STRIPE_PRICE_ID_SUBSCRIPTION,
quantity: 1,
},
],
mode: 'subscription',
},
paymentReason:
'You must pay a subscription to add two big numbers together.',
}
);
// Usage-based metered payments (Each tool call requires a payment)
this.paidTool(
'generate_emoji',
'Generate an emoji given a single word (the `object` parameter describing the emoji)',
{
object: z.string().describe('one word'),
},
({object}) => {
return {
content: [{type: 'text', text: generateImage(object)}],
};
},
{
checkout: {
success_url: 'http://localhost:4242/payment/success',
line_items: [
{
price: process.env.STRIPE_PRICE_ID_USAGE_BASED_SUBSCRIPTION,
},
],
mode: 'subscription',
},
meterEvent: 'image_generation',
paymentReason:
'You get 3 free generations, then we charge 10 cents per generation.',
}
);
}
}
// Export the OAuth handler as the default
export default new OAuthProvider({
apiRoute: '/sse',
apiHandlers: {
// @ts-ignore
'/sse': MyMCP.serveSSE('/sse'),
// @ts-ignore
'/mcp': MyMCP.serve('/mcp'),
},
// @ts-ignore
defaultHandler: app,
authorizeEndpoint: '/authorize',
tokenEndpoint: '/token',
clientRegistrationEndpoint: '/register',
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/provider/stripe-provider.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Stripe AI SDK Provider
* Integrates with Stripe's llm.stripe.com proxy for usage-based billing
*/
import {LanguageModelV2, ProviderV2} from '@ai-sdk/provider';
import {StripeLanguageModel} from './stripe-language-model';
import {
StripeLanguageModelSettings,
StripeProviderConfig,
} from './types';
import {normalizeModelId} from './utils';
/**
* Stripe Provider interface
*/
export interface StripeProvider extends ProviderV2 {
/**
* Create a language model with the given model ID
* @param modelId - Model ID in Stripe format (e.g., 'openai/gpt-5', 'google/gemini-2.5-pro')
* @param settings - Optional settings for the model
*/
(modelId: string, settings?: StripeLanguageModelSettings): LanguageModelV2;
/**
* Create a language model (alias for direct call)
*/
languageModel(
modelId: string,
settings?: StripeLanguageModelSettings
): LanguageModelV2;
}
/**
* Create a Stripe provider instance
*
* @param config - Provider configuration options
* @returns Stripe provider instance
*
* @example
* ```ts
* import { createStripe } from '@stripe/ai-sdk/provider';
*
* const stripeLLM = createStripe({
* apiKey: process.env.STRIPE_API_KEY,
* customerId: 'cus_xxxxx' // Optional default customer ID
* });
*
* const model = stripe('openai/gpt-5');
* ```
*/
export function createStripe(config: StripeProviderConfig = {}): StripeProvider {
const baseURL = config.baseURL || 'https://llm.stripe.com';
const createModel = (
modelId: string,
settings: StripeLanguageModelSettings = {}
): LanguageModelV2 => {
// Normalize the model ID to match Stripe's approved model list
const normalizedModelId = normalizeModelId(modelId);
// Merge provider-level and model-level customer IDs
const mergedSettings: StripeLanguageModelSettings = {
customerId: config.customerId,
...settings,
};
return new StripeLanguageModel(normalizedModelId, mergedSettings, {
provider: 'stripe',
baseURL,
headers: () => {
const apiKey = config.apiKey || process.env.STRIPE_API_KEY;
if (!apiKey) {
throw new Error(
'Stripe API key is required. Provide it via config.apiKey or STRIPE_API_KEY environment variable.'
);
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
'User-Agent': `Stripe/v1 @stripe/ai-sdk/provider/0.1.0`,
...config.headers,
};
},
});
};
const provider = function (
modelId: string,
settings?: StripeLanguageModelSettings
) {
if (new.target) {
throw new Error(
'The Stripe provider function cannot be called with the new keyword.'
);
}
return createModel(modelId, settings);
};
provider.languageModel = createModel;
// Placeholder implementations for other model types (not yet supported)
provider.textEmbeddingModel = () => {
throw new Error('Text embedding models are not yet supported by Stripe provider');
};
provider.imageModel = () => {
throw new Error('Image models are not yet supported by Stripe provider');
};
return provider as StripeProvider;
}
/**
* Default Stripe provider instance
* Uses STRIPE_API_KEY environment variable for authentication
*
* @example
* ```ts
* import { stripe } from '@stripe/ai-sdk/provider';
*
* const model = stripe('openai/gpt-5', {
* customerId: 'cus_xxxxx'
* });
* ```
*/
export const stripe = createStripe();
```
--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/strands/hooks.py:
--------------------------------------------------------------------------------
```python
from typing import Any, Optional, Dict
from ..api import StripeAPI
class BillingHooks:
"""Billing hooks for Strands framework to track usage and create meter events."""
def __init__(
self,
stripe: StripeAPI,
type: str,
customer: str,
meter: Optional[str] = None,
meters: Optional[Dict[str, str]] = None
):
"""
Initialize billing hooks.
Args:
stripe: StripeAPI instance
type: Type of billing - "outcome" or "token"
customer: Customer ID for billing
meter: Single meter ID for outcome-based billing
meters: Dictionary of meter IDs for token-based billing (input/output)
"""
self.type = type
self.stripe = stripe
self.customer = customer
self.meter = meter
self.meters = meters or {}
def on_start(self, context: Any = None) -> None:
"""Called when agent execution starts."""
pass
def on_end(self, context: Any = None, output: Any = None, usage: Any = None) -> None:
"""
Called when agent execution ends.
Args:
context: Execution context (may contain usage information)
output: Agent output
usage: Usage information (tokens, etc.)
"""
if self.type == "outcome":
# Create a single meter event for outcome-based billing
if self.meter:
self.stripe.create_meter_event(self.meter, self.customer)
elif self.type == "token":
# Create meter events for token-based billing
if usage:
# Try to extract token usage from different possible formats
input_tokens = self._extract_input_tokens(usage, context)
output_tokens = self._extract_output_tokens(usage, context)
if input_tokens and self.meters.get("input"):
self.stripe.create_meter_event(
self.meters["input"],
self.customer,
str(input_tokens)
)
if output_tokens and self.meters.get("output"):
self.stripe.create_meter_event(
self.meters["output"],
self.customer,
str(output_tokens)
)
def on_error(self, context: Any = None, error: Exception = None) -> None:
"""Called when agent execution encounters an error."""
pass
def _extract_input_tokens(self, usage: Any, context: Any = None) -> Optional[int]:
"""Extract input token count from usage information."""
if hasattr(usage, 'input_tokens'):
return usage.input_tokens
elif isinstance(usage, dict):
return usage.get('input_tokens') or usage.get('prompt_tokens')
elif context and hasattr(context, 'usage') and hasattr(context.usage, 'input_tokens'):
return context.usage.input_tokens
return None
def _extract_output_tokens(self, usage: Any, context: Any = None) -> Optional[int]:
"""Extract output token count from usage information."""
if hasattr(usage, 'output_tokens'):
return usage.output_tokens
elif isinstance(usage, dict):
return usage.get('output_tokens') or usage.get('completion_tokens')
elif context and hasattr(context, 'usage') and hasattr(context.usage, 'output_tokens'):
return context.usage.output_tokens
return None
```
--------------------------------------------------------------------------------
/tools/typescript/examples/cloudflare/src/app.ts:
--------------------------------------------------------------------------------
```typescript
// From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/app.ts
import {Hono} from 'hono';
import {
layout,
homeContent,
parseApproveFormBody,
renderAuthorizationRejectedContent,
renderAuthorizationApprovedContent,
renderLoggedInAuthorizeScreen,
renderLoggedOutAuthorizeScreen,
renderPaymentSuccessContent,
} from './utils';
import type {OAuthHelpers} from '@cloudflare/workers-oauth-provider';
export type Bindings = Env & {
OAUTH_PROVIDER: OAuthHelpers;
};
const app = new Hono<{
Bindings: Bindings;
}>();
// Render a basic homepage placeholder to make sure the app is up
app.get('/', async (c) => {
const content = await homeContent(c.req.raw);
return c.html(layout(content, 'MCP Remote Auth Demo - Home'));
});
// Render an authorization page
// If the user is logged in, we'll show a form to approve the appropriate scopes
// If the user is not logged in, we'll show a form to both login and approve the scopes
app.get('/authorize', async (c) => {
// We don't have an actual auth system, so to demonstrate both paths, you can
// hard-code whether the user is logged in or not. We'll default to true
// const isLoggedIn = false;
const isLoggedIn = true;
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
const oauthScopes = [
{
name: 'read_profile',
description: 'Read your basic profile information',
},
{name: 'read_data', description: 'Access your stored data'},
{name: 'write_data', description: 'Create and modify your data'},
];
if (isLoggedIn) {
const content = await renderLoggedInAuthorizeScreen(
oauthScopes,
oauthReqInfo
);
return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
}
const content = await renderLoggedOutAuthorizeScreen(
oauthScopes,
oauthReqInfo
);
return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
});
app.get('/payment/success', async (c) => {
return c.html(
layout(
await renderPaymentSuccessContent(),
'MCP Remote Auth Demo - Payment Success'
)
);
});
// The /authorize page has a form that will POST to /approve
// This endpoint is responsible for validating any login information and
// then completing the authorization request with the OAUTH_PROVIDER
app.post('/approve', async (c) => {
const {action, oauthReqInfo, email, password} = await parseApproveFormBody(
await c.req.parseBody()
);
if (!oauthReqInfo) {
return c.html('INVALID LOGIN', 401);
}
// If the user needs to both login and approve, we should validate the login first
if (action === 'login_approve') {
// We'll allow any values for email and password for this demo
// but you could validate them here
// Ex:
// if (email !== "[email protected]" || password !== "password") {
// biome-ignore lint/correctness/noConstantCondition: This is a demo
if (false) {
return c.html(
layout(
await renderAuthorizationRejectedContent('/'),
'MCP Remote Auth Demo - Authorization Status'
)
);
}
}
// The user must be successfully logged in and have approved the scopes, so we
// can complete the authorization request
const {redirectTo} = await c.env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: email,
metadata: {
label: 'Test User',
},
scope: oauthReqInfo.scope,
props: {
// Here, you can send data to the MCP server
userEmail: email,
},
});
// Store the redirect URL per email in KV somewhere
c.env.OAUTH_KV.put(email, redirectTo);
return c.html(
layout(
await renderAuthorizationApprovedContent(redirectTo),
'MCP Remote Auth Demo - Authorization Status'
)
);
});
export default app;
```
--------------------------------------------------------------------------------
/tools/typescript/examples/cloudflare/src/oauth.ts:
--------------------------------------------------------------------------------
```typescript
// From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/app.ts
import {Hono} from 'hono';
import {
layout,
homeContent,
parseApproveFormBody,
renderAuthorizationRejectedContent,
renderAuthorizationApprovedContent,
renderLoggedInAuthorizeScreen,
renderLoggedOutAuthorizeScreen,
renderPaymentSuccessContent,
} from './utils';
import type {OAuthHelpers} from '@cloudflare/workers-oauth-provider';
export type Bindings = Env & {
OAUTH_PROVIDER: OAuthHelpers;
};
const app = new Hono<{
Bindings: Bindings;
}>();
// Render a basic homepage placeholder to make sure the app is up
app.get('/', async (c) => {
const content = await homeContent(c.req.raw);
return c.html(layout(content, 'MCP Remote Auth Demo - Home'));
});
// Render an authorization page
// If the user is logged in, we'll show a form to approve the appropriate scopes
// If the user is not logged in, we'll show a form to both login and approve the scopes
app.get('/authorize', async (c) => {
// We don't have an actual auth system, so to demonstrate both paths, you can
// hard-code whether the user is logged in or not. We'll default to true
// const isLoggedIn = false;
const isLoggedIn = true;
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
const oauthScopes = [
{
name: 'read_profile',
description: 'Read your basic profile information',
},
{name: 'read_data', description: 'Access your stored data'},
{name: 'write_data', description: 'Create and modify your data'},
];
if (isLoggedIn) {
const content = await renderLoggedInAuthorizeScreen(
oauthScopes,
oauthReqInfo
);
return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
}
const content = await renderLoggedOutAuthorizeScreen(
oauthScopes,
oauthReqInfo
);
return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
});
app.get('/payment/success', async (c) => {
return c.html(
layout(
await renderPaymentSuccessContent(),
'MCP Remote Auth Demo - Payment Success'
)
);
});
// The /authorize page has a form that will POST to /approve
// This endpoint is responsible for validating any login information and
// then completing the authorization request with the OAUTH_PROVIDER
app.post('/approve', async (c) => {
const {action, oauthReqInfo, email, password} = await parseApproveFormBody(
await c.req.parseBody()
);
if (!oauthReqInfo) {
return c.html('INVALID LOGIN', 401);
}
// If the user needs to both login and approve, we should validate the login first
if (action === 'login_approve') {
// We'll allow any values for email and password for this demo
// but you could validate them here
// Ex:
// if (email !== "[email protected]" || password !== "password") {
// biome-ignore lint/correctness/noConstantCondition: This is a demo
if (false) {
return c.html(
layout(
await renderAuthorizationRejectedContent('/'),
'MCP Remote Auth Demo - Authorization Status'
)
);
}
}
// The user must be successfully logged in and have approved the scopes, so we
// can complete the authorization request
const {redirectTo} = await c.env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: email,
metadata: {
label: 'Test User',
},
scope: oauthReqInfo.scope,
props: {
// Here, you can send data to the MCP server
userEmail: email,
},
});
// Store the redirect URL per email in KV somewhere
c.env.OAUTH_KV.put(email, redirectTo);
return c.html(
layout(
await renderAuthorizationApprovedContent(redirectTo),
'MCP Remote Auth Demo - Authorization Status'
)
);
});
export default app;
```
--------------------------------------------------------------------------------
/llm/ai-sdk/provider/examples/openai.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Example: Using Stripe AI SDK Provider with OpenAI models
*
* This example demonstrates how to use the Stripe provider to interact with
* OpenAI models through Stripe's llm.stripe.com proxy for automatic usage tracking.
*/
import {config} from 'dotenv';
import {resolve} from 'path';
import {generateText, streamText} from 'ai';
import {createStripe} from '..';
// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});
async function main() {
// Check environment variables
if (!process.env.STRIPE_API_KEY) {
throw new Error('STRIPE_API_KEY environment variable is not set. Please set it in examples/.env');
}
if (!process.env.STRIPE_CUSTOMER_ID) {
console.warn('Warning: STRIPE_CUSTOMER_ID is not set. Some examples may fail.');
}
// Initialize the Stripe provider
const stripeLLM = createStripe({
apiKey: process.env.STRIPE_API_KEY!,
customerId: process.env.STRIPE_CUSTOMER_ID, // Optional default customer ID
});
console.log('=== Example 1: Simple text generation with OpenAI GPT-5 ===\n');
// Basic text generation
const result1 = await generateText({
model: stripeLLM('openai/gpt-5', {
customerId: process.env.STRIPE_CUSTOMER_ID!, // Model-level customer ID
}),
prompt: 'What are the three primary colors?',
});
console.log('Response:', result1.text);
console.log('Usage:', result1.usage);
console.log('\n');
console.log('=== Example 2: Streaming with GPT-4.1 ===\n');
const streamPrompt = 'Explain how photosynthesis works in simple terms.';
console.log(`Sending request with prompt: "${streamPrompt}"`);
console.log(`Model: openai/gpt-4.1`);
console.log(`Customer ID: ${process.env.STRIPE_CUSTOMER_ID}\n`);
// Streaming response
const result2 = await streamText({
model: stripeLLM('openai/gpt-4.1'),
prompt: streamPrompt,
providerOptions: {
stripe: {
// Override customer ID for this specific call
customerId: process.env.STRIPE_CUSTOMER_ID!,
},
},
});
console.log('Stream started, consuming chunks...');
let chunkCount = 0;
try {
// Print streaming response using textStream (simple string chunks)
for await (const chunk of result2.textStream) {
chunkCount++;
process.stdout.write(chunk);
}
} catch (streamError) {
console.error('\n❌ Error during streaming:', streamError);
throw streamError;
}
console.log(`\n\n(Received ${chunkCount} chunks)`);
// Get usage and final text after stream completes
const [finalText, usage] = await Promise.all([result2.text, result2.usage]);
console.log('Final text length:', finalText.length);
console.log('Usage:', usage);
console.log('=== Example 3: Chat conversation with GPT-4.1-mini ===\n');
// Multi-turn conversation
const result3 = await generateText({
model: stripeLLM('openai/gpt-4.1-mini', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
messages: [
{role: 'user', content: 'What is the capital of France?'},
{role: 'assistant', content: 'The capital of France is Paris.'},
{role: 'user', content: 'What is its population?'},
],
});
console.log('Response:', result3.text);
console.log('Usage:', result3.usage);
console.log('\n');
console.log('=== Example 4: Using OpenAI o3 reasoning model ===\n');
// Using reasoning model
const result4 = await generateText({
model: stripeLLM('openai/o3', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt:
'A farmer has 17 sheep. All but 9 die. How many sheep are left? Think through this step by step.',
});
console.log('Response:', result4.text);
console.log('Usage:', result4.usage);
console.log('\n');
console.log('=== All examples completed! ===');
}
main().catch((error) => {
console.error('\n❌ Error occurred:');
console.error(error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/ai-sdk-billing-wrapper-anthropic.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for AI SDK billing wrapper with Anthropic
* These tests mock Stripe meter events and verify meter events are sent correctly
*/
import Stripe from 'stripe';
import {anthropic} from '@ai-sdk/anthropic';
import {meteredModel} from '../index';
// Mock Stripe
jest.mock('stripe');
describe('AI SDK Billing Wrapper - Anthropic', () => {
let mockMeterEventsCreate: jest.Mock;
const TEST_API_KEY = 'sk_test_mock_key';
beforeEach(() => {
mockMeterEventsCreate = jest.fn().mockResolvedValue({});
// Mock the Stripe constructor
(Stripe as unknown as jest.Mock).mockImplementation(() => ({
v2: {
billing: {
meterEvents: {
create: mockMeterEventsCreate,
},
},
},
}));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should send meter events for doGenerate with Claude', async () => {
const originalModel = anthropic('claude-3-5-haiku-20241022');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Hello from Claude!',
usage: {
inputTokens: 12,
outputTokens: 6,
},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
event_name: 'token-billing-tokens',
payload: expect.objectContaining({
stripe_customer_id: 'cus_test123',
value: '12',
model: 'anthropic/claude-3.5-haiku',
token_type: 'input',
}),
})
);
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
value: '6',
model: 'anthropic/claude-3.5-haiku',
token_type: 'output',
}),
})
);
});
it('should normalize Anthropic model names correctly', async () => {
const testCases = [
{input: 'claude-3-5-haiku-20241022', expected: 'anthropic/claude-3.5-haiku'},
{input: 'claude-3-5-sonnet-20241022', expected: 'anthropic/claude-3.5-sonnet'},
{input: 'claude-3-opus-20240229', expected: 'anthropic/claude-3-opus'},
{input: 'claude-sonnet-4-latest', expected: 'anthropic/claude-sonnet-4'},
];
for (const {input, expected} of testCases) {
mockMeterEventsCreate.mockClear();
const originalModel = anthropic(input as any);
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Test',
usage: {inputTokens: 5, outputTokens: 2},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
model: expected,
}),
})
);
}
});
it('should preserve model properties', () => {
const originalModel = anthropic('claude-3-5-haiku-20241022');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
expect(wrappedModel.modelId).toBe(originalModel.modelId);
expect(wrappedModel.provider).toBe(originalModel.provider);
expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
});
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/wrapperV2.ts:
--------------------------------------------------------------------------------
```typescript
/**
* AI SDK Model Wrapper (v2 specification)
*/
import Stripe from 'stripe';
import type {
LanguageModelV2,
LanguageModelV2CallOptions,
LanguageModelV2StreamPart,
} from '@ai-sdk/provider';
import type {AIMeterConfig, AIUsageInfo} from './types';
import {determineProvider, extractUsageFromStream} from './utils';
import {logUsageEvent} from './meter-event-logging';
/**
* Wrapper class for AI SDK v2 models that adds Stripe meter event tracking
*/
export class AISDKWrapperV2 implements LanguageModelV2 {
private stripeClient: Stripe;
constructor(
private model: LanguageModelV2,
private config: AIMeterConfig
) {
// Construct Stripe client with the API key
this.stripeClient = new Stripe(config.stripeApiKey, {
appInfo: {
name: '@stripe/ai-sdk/meter',
version: '0.1.0',
},
});
}
/**
* Wraps doGenerate to track usage with Stripe meter events
*/
async doGenerate(options: LanguageModelV2CallOptions) {
try {
// Call the original doGenerate function
const response = await this.model.doGenerate(options);
// Extract usage information
const usageInfo: AIUsageInfo = {
provider: determineProvider(this.model.provider),
model: this.model.modelId,
inputTokens: response.usage?.inputTokens ?? 0,
outputTokens: response.usage?.outputTokens ?? 0,
};
// Log to Stripe (fire-and-forget)
this.logUsage(usageInfo);
return response;
} catch (error) {
// Re-throw the error after logging
console.error('[Stripe AI SDK] doGenerate failed:', error);
throw error;
}
}
/**
* Wraps doStream to track usage with Stripe meter events
*/
async doStream(options: LanguageModelV2CallOptions) {
try {
// Call the original doStream method
const response = await this.model.doStream(options);
// Collect chunks to extract usage at the end
const chunks: LanguageModelV2StreamPart[] = [];
const stream = new ReadableStream<LanguageModelV2StreamPart>({
start: async (controller) => {
try {
const reader = response.stream.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
// Stream is done, now extract usage and log
const usage = extractUsageFromStream(chunks);
const usageInfo: AIUsageInfo = {
provider: determineProvider(this.model.provider),
model: this.model.modelId,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
};
// Log to Stripe (fire-and-forget)
this.logUsage(usageInfo);
controller.close();
break;
}
// Collect chunk and pass it through
chunks.push(value);
controller.enqueue(value);
}
} catch (error) {
controller.error(error);
console.error('[Stripe AI SDK] Stream processing failed:', error);
}
},
});
// Return response with the wrapped stream
return {
...response,
stream: stream,
};
} catch (error) {
console.error('[Stripe AI SDK] doStream failed:', error);
throw error;
}
}
/**
* Helper method to log usage to Stripe
*/
private logUsage(usageInfo: AIUsageInfo): void {
logUsageEvent(this.stripeClient, {}, {
model: usageInfo.model,
provider: usageInfo.provider,
usage: {
inputTokens: usageInfo.inputTokens,
outputTokens: usageInfo.outputTokens,
},
stripeCustomerId: this.config.stripeCustomerId,
});
}
// Proxy all other properties from the original model
get modelId() {
return this.model.modelId;
}
get provider() {
return this.model.provider;
}
get specificationVersion() {
return this.model.specificationVersion;
}
get supportedUrls() {
return this.model.supportedUrls;
}
}
```
--------------------------------------------------------------------------------
/llm/ai-sdk/provider/examples/anthropic.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Example: Using Stripe AI SDK Provider with Anthropic Claude models
*
* This example demonstrates how to use the Stripe provider to interact with
* Anthropic Claude models through Stripe's llm.stripe.com proxy for automatic usage tracking.
*/
import {config} from 'dotenv';
import {resolve} from 'path';
import {generateText, streamText} from 'ai';
import {createStripe} from '..';
// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});
async function main() {
// Check environment variables
if (!process.env.STRIPE_API_KEY) {
throw new Error('STRIPE_API_KEY environment variable is not set. Please set it in examples/.env');
}
if (!process.env.STRIPE_CUSTOMER_ID) {
throw new Error('STRIPE_CUSTOMER_ID environment variable is not set. Please set it in examples/.env');
}
// Initialize the Stripe provider
const stripeLLM = createStripe({
apiKey: process.env.STRIPE_API_KEY!,
customerId: process.env.STRIPE_CUSTOMER_ID!, // Default customer ID
});
console.log('=== Example 1: Simple text generation with Claude Sonnet 4 ===\n');
// Basic text generation
const result1 = await generateText({
model: stripeLLM('anthropic/claude-sonnet-4'),
prompt: 'Explain quantum computing in simple terms.',
});
console.log('Response:', result1.text);
console.log('Usage:', result1.usage);
console.log('\n');
console.log('=== Example 2: Streaming with Claude Opus 4 ===\n');
// Streaming response with the most capable Claude model
const result2 = await streamText({
model: stripeLLM('anthropic/claude-opus-4', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt: 'Write a poem about the ocean and its mysteries.',
});
// Print streaming response
for await (const chunk of result2.textStream) {
process.stdout.write(chunk);
}
console.log('\n\n');
console.log('=== Example 3: Chat conversation with Claude 3.7 Sonnet ===\n');
// Multi-turn conversation
const result3 = await generateText({
model: stripeLLM('anthropic/claude-3-7-sonnet', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
messages: [
{role: 'user', content: 'What is the Turing test?'},
{
role: 'assistant',
content:
'The Turing test is a test of a machine\'s ability to exhibit intelligent behavior equivalent to, or indistinguishable from, that of a human.',
},
{role: 'user', content: 'Who created it?'},
],
});
console.log('Response:', result3.text);
console.log('Usage:', result3.usage);
console.log('\n');
console.log('=== Example 4: Using Claude 3.5 Haiku for quick responses ===\n');
// Using the fastest Claude model
const result4 = await generateText({
model: stripeLLM('anthropic/claude-3-5-haiku', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt: 'What are the benefits of functional programming?',
temperature: 0.5,
});
console.log('Response:', result4.text);
console.log('Usage:', result4.usage);
console.log('\n');
console.log('=== Example 5: Complex reasoning with Claude Opus 4.1 ===\n');
// Complex reasoning task
const result5 = await generateText({
model: stripeLLM('anthropic/claude-opus-4-1', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt:
'Design a simple algorithm to solve the traveling salesman problem. Explain your approach and its time complexity.',
temperature: 0.7,
});
console.log('Response:', result5.text);
console.log('Usage:', result5.usage);
console.log('\n');
console.log('=== Example 6: Per-call customer ID override ===\n');
// Override customer ID for a specific call
const result7 = await generateText({
model: stripeLLM('anthropic/claude-3-haiku'),
prompt: 'What is the speed of light?',
providerOptions: {
stripe: {
customerId: 'cus_specific_customer', // Override the default customer ID
},
},
});
console.log('Response:', result7.text);
console.log('Usage:', result7.usage);
console.log('\n');
console.log('=== All examples completed! ===');
}
main().catch((error) => {
console.error('\n❌ Error occurred:');
console.error(error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/tools/modelcontextprotocol/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import {StripeAgentToolkit} from '@stripe/agent-toolkit/modelcontextprotocol';
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
import {green, red, yellow} from 'colors';
type ToolkitConfig = {
actions: {
[product: string]: {[action: string]: boolean};
};
context?: {
account?: string;
mode: 'modelcontextprotocol';
};
};
type Options = {
tools?: string[];
apiKey?: string;
stripeAccount?: string;
};
const ACCEPTED_ARGS = ['api-key', 'tools', 'stripe-account'];
const ACCEPTED_TOOLS = [
'coupons.create',
'coupons.read',
'customers.create',
'customers.read',
'products.create',
'products.read',
'prices.create',
'prices.read',
'paymentLinks.create',
'invoices.create',
'invoices.read',
'invoices.update',
'invoiceItems.create',
'balance.read',
'refunds.create',
'paymentIntents.read',
'subscriptions.read',
'subscriptions.update',
'disputes.read',
'disputes.update',
'documentation.read',
];
export function parseArgs(args: string[]): Options {
const options: Options = {};
args.forEach((arg) => {
if (arg.startsWith('--')) {
const [key, value] = arg.slice(2).split('=');
if (key == 'tools') {
options.tools = value.split(',');
} else if (key == 'api-key') {
if (!value.startsWith('sk_') && !value.startsWith('rk_')) {
throw new Error('API key must start with "sk_" or "rk_".');
}
options.apiKey = value;
} else if (key == 'stripe-account') {
// Validate api-key format
if (!value.startsWith('acct_')) {
throw new Error('Stripe account must start with "acct_".');
}
options.stripeAccount = value;
} else {
throw new Error(
`Invalid argument: ${key}. Accepted arguments are: ${ACCEPTED_ARGS.join(
', '
)}`
);
}
}
});
// Check if required tools arguments is present
if (!options.tools) {
throw new Error('The --tools arguments must be provided.');
}
// Validate tools against accepted enum values
options.tools.forEach((tool: string) => {
if (tool == 'all') {
return;
}
if (!ACCEPTED_TOOLS.includes(tool.trim())) {
throw new Error(
`Invalid tool: ${tool}. Accepted tools are: ${ACCEPTED_TOOLS.join(
', '
)}`
);
}
});
// Check if API key is either provided in args or set in environment variables
const apiKey = options.apiKey || process.env.STRIPE_SECRET_KEY;
if (!apiKey) {
throw new Error(
'Stripe API key not provided. Please either pass it as an argument --api-key=$KEY or set the STRIPE_SECRET_KEY environment variable.'
);
}
options.apiKey = apiKey;
return options;
}
function handleError(error: any) {
console.error(red('\n🚨 Error initializing Stripe MCP server:\n'));
console.error(yellow(` ${error.message}\n`));
}
export async function main() {
const options = parseArgs(process.argv.slice(2));
// Create the StripeAgentToolkit instance
const selectedTools = options.tools!;
const configuration: ToolkitConfig = {actions: {}};
if (selectedTools.includes('all')) {
ACCEPTED_TOOLS.forEach((tool) => {
const [product, action] = tool.split('.');
configuration.actions[product] = {
...configuration.actions[product],
[action]: true,
};
});
} else {
selectedTools.forEach((tool: any) => {
const [product, action] = tool.split('.');
configuration.actions[product] = {[action]: true};
});
}
configuration.context = {
mode: 'modelcontextprotocol',
};
// Append stripe account to configuration if provided
if (options.stripeAccount) {
configuration.context.account = options.stripeAccount;
}
const server = new StripeAgentToolkit({
secretKey: options.apiKey!,
configuration: configuration,
});
const transport = new StdioServerTransport();
await server.connect(transport);
// We use console.error instead of console.log since console.log will output to stdio, which will confuse the MCP server
console.error(green('✅ Stripe MCP Server running on stdio'));
}
if (require.main === module) {
main().catch((error) => {
handleError(error);
});
}
```
--------------------------------------------------------------------------------
/llm/ai-sdk/provider/examples/google.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Example: Using Stripe AI SDK Provider with Google Gemini models
*
* This example demonstrates how to use the Stripe provider to interact with
* Google Gemini models through Stripe's llm.stripe.com proxy for automatic usage tracking.
*/
import {config} from 'dotenv';
import {resolve} from 'path';
import {generateText, streamText} from 'ai';
import {createStripe} from '..';
// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});
async function main() {
// Check environment variables
if (!process.env.STRIPE_API_KEY) {
throw new Error('STRIPE_API_KEY environment variable is not set. Please set it in examples/.env');
}
if (!process.env.STRIPE_CUSTOMER_ID) {
throw new Error('STRIPE_CUSTOMER_ID environment variable is not set. Please set it in examples/.env');
}
// Initialize the Stripe provider
const stripeLLM = createStripe({
apiKey: process.env.STRIPE_API_KEY!,
customerId: process.env.STRIPE_CUSTOMER_ID!, // Default customer ID
});
console.log('=== Example 1: Simple text generation with Gemini 2.5 Pro ===\n');
// Basic text generation
const result1 = await generateText({
model: stripeLLM('google/gemini-2.5-pro'),
prompt: 'What are the main differences between Python and JavaScript?',
});
console.log('Response:', result1.text);
console.log('Usage:', result1.usage);
console.log('\n');
console.log('=== Example 2: Streaming with Gemini 2.5 Flash ===\n');
// Streaming response with the faster Gemini model
const result2 = await streamText({
model: stripeLLM('google/gemini-2.5-flash', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt: 'Write a short story about a robot learning to paint.',
});
// Print streaming response
for await (const chunk of result2.textStream) {
process.stdout.write(chunk);
}
console.log('\n\n');
console.log('=== Example 3: Chat conversation with Gemini 2.0 Flash ===\n');
// Multi-turn conversation
const result3 = await generateText({
model: stripeLLM('google/gemini-2.0-flash'),
messages: [
{role: 'user', content: 'What is machine learning?'},
{
role: 'assistant',
content:
'Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.',
},
{role: 'user', content: 'Can you give me an example?'},
],
providerOptions: {
stripe: {
customerId: process.env.STRIPE_CUSTOMER_ID!,
},
},
});
console.log('Response:', result3.text);
console.log('Usage:', result3.usage);
console.log('\n');
console.log('=== Example 4: Using Gemini 2.5 Flash Lite for quick responses ===\n');
// Using the lite model for faster, cheaper responses
const result4 = await generateText({
model: stripeLLM('google/gemini-2.5-flash-lite', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt: 'List 5 programming languages.',
temperature: 0.3,
});
console.log('Response:', result4.text);
console.log('Usage:', result4.usage);
console.log('\n');
console.log('=== Example 5: Long-form content with Gemini 2.5 Pro ===\n');
// Long-form content generation
const result5 = await generateText({
model: stripeLLM('google/gemini-2.5-pro', {
customerId: process.env.STRIPE_CUSTOMER_ID!,
}),
prompt:
'Write a detailed explanation of how neural networks work, suitable for beginners.',
temperature: 0.7,
});
console.log('Response:', result5.text);
console.log('Usage:', result5.usage);
console.log('\n');
console.log('=== Example 6: Streaming with custom headers ===\n');
// Custom headers example (if needed for specific use cases)
const stripeWithHeaders = createStripe({
apiKey: process.env.STRIPE_API_KEY!,
customerId: process.env.STRIPE_CUSTOMER_ID!,
headers: {
'X-Custom-Header': 'custom-value',
},
});
const result6 = await streamText({
model: stripeWithHeaders('google/gemini-2.5-flash'),
prompt: 'Count from 1 to 10.',
});
for await (const chunk of result6.textStream) {
process.stdout.write(chunk);
}
console.log('\n\n');
console.log('=== All examples completed! ===');
}
main().catch((error) => {
console.error('\n❌ Error occurred:');
console.error(error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/ai-sdk-billing-wrapper-openai.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for AI SDK billing wrapper with OpenAI
* These tests mock Stripe meter events and use jest spies to verify meter events are sent
*/
import Stripe from 'stripe';
import {openai} from '@ai-sdk/openai';
import {meteredModel} from '../index';
// Mock Stripe
jest.mock('stripe');
describe('AI SDK Billing Wrapper - OpenAI', () => {
let mockMeterEventsCreate: jest.Mock;
const TEST_API_KEY = 'sk_test_mock_key';
beforeEach(() => {
mockMeterEventsCreate = jest.fn().mockResolvedValue({});
// Mock the Stripe constructor
(Stripe as unknown as jest.Mock).mockImplementation(() => ({
v2: {
billing: {
meterEvents: {
create: mockMeterEventsCreate,
},
},
},
}));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should create wrapper that preserves model properties', () => {
const originalModel = openai('gpt-4o-mini');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
expect(wrappedModel.modelId).toBe(originalModel.modelId);
expect(wrappedModel.provider).toBe(originalModel.provider);
expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
});
it('should wrap doGenerate method', async () => {
const originalModel = openai('gpt-4o-mini');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
// Spy on the original doGenerate
const mockDoGenerate = jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Test response',
usage: {
inputTokens: 10,
outputTokens: 5,
},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
// Call the wrapped doGenerate
const result = await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
expect(mockDoGenerate).toHaveBeenCalled();
expect((result as any).text).toBe('Test response');
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
// Verify meter events were created
expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
event_name: 'token-billing-tokens',
payload: expect.objectContaining({
stripe_customer_id: 'cus_test123',
value: '10',
model: 'openai/gpt-4o-mini',
token_type: 'input',
}),
})
);
});
it('should normalize OpenAI model names', async () => {
const originalModel = openai('gpt-4-turbo-2024-04-09');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Test',
usage: {inputTokens: 5, outputTokens: 2},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
// Verify model name was normalized (date suffix removed)
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
model: 'openai/gpt-4-turbo',
}),
})
);
});
it('should handle missing usage gracefully', async () => {
const originalModel = openai('gpt-4o-mini');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Test',
usage: undefined,
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
// Should not create events with 0 tokens (code only sends when > 0)
expect(mockMeterEventsCreate).toHaveBeenCalledTimes(0);
});
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/examples/anthropic.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Sample Usage: Vercel AI SDK with Anthropic and Stripe Billing
*
* This demonstrates how to use the meteredModel wrapper to automatically
* report token usage to Stripe for billing purposes when using Anthropic via the Vercel AI SDK.
*/
import {config} from 'dotenv';
import {resolve} from 'path';
import {anthropic} from '@ai-sdk/anthropic';
import {generateText, streamText} from 'ai';
import {meteredModel} from '..';
config({path: resolve(__dirname, '.env')});
// Load environment variables from .env file
const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY!;
if (!STRIPE_API_KEY || !STRIPE_CUSTOMER_ID || !ANTHROPIC_API_KEY) {
throw new Error(
'STRIPE_API_KEY, STRIPE_CUSTOMER_ID, and ANTHROPIC_API_KEY environment variables are required'
);
}
// Sample 1: Basic generateText with Claude
async function sampleBasicGenerateText() {
console.log('\n=== Sample 1: Basic generateText with Claude ===');
const model = meteredModel(
anthropic('claude-3-5-sonnet-20241022'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
prompt: 'Say "Hello, World!" and nothing else.',
});
console.log('Response:', text);
}
// Sample 2: Streaming text with Claude
async function sampleStreamText() {
console.log('\n=== Sample 2: Stream Text with Claude ===');
const model = meteredModel(
anthropic('claude-3-5-sonnet-20241022'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const result = streamText({
model: model,
prompt: 'Count from 1 to 5, one number per line.',
});
let fullText = '';
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
fullText += chunk;
}
console.log('\n\nFull text:', fullText);
}
// Sample 3: With system message
async function sampleWithSystemMessage() {
console.log('\n=== Sample 3: With System Message ===');
const model = meteredModel(
anthropic('claude-3-5-sonnet-20241022'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
system: 'You are a helpful assistant that is concise and to the point.',
prompt: 'What is the capital of France?',
});
console.log('Response:', text);
}
// Sample 4: Multi-turn conversation
async function sampleConversation() {
console.log('\n=== Sample 4: Multi-turn Conversation ===');
const model = meteredModel(
anthropic('claude-3-5-sonnet-20241022'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
messages: [
{role: 'user', content: 'Who is Sachin Tendulkar?'},
{
role: 'assistant',
content: 'Sachin Tendulkar is a legendary Indian cricketer.',
},
{role: 'user', content: 'What are his major achievements?'},
],
});
console.log('Response:', text);
}
// Sample 5: Using Claude Haiku (faster, cheaper model)
async function sampleClaudeHaiku() {
console.log('\n=== Sample 5: Using Claude Haiku ===');
const model = meteredModel(
anthropic('claude-3-5-haiku-20241022'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
prompt: 'Explain quantum computing in one sentence.',
});
console.log('Response:', text);
}
// Sample 6: Stream with max tokens
async function sampleStreamWithMaxTokens() {
console.log('\n=== Sample 6: Stream with Max Tokens ===');
const model = meteredModel(
anthropic('claude-3-5-sonnet-20241022'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const result = streamText({
model: model,
prompt: 'Write a short story about a robot.',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
console.log('\n');
}
// Run all samples
async function runAllSamples() {
console.log('Starting Vercel AI SDK + Anthropic + Stripe Metering Examples');
console.log(
'These examples show how to use meteredModel with Anthropic models\n'
);
try {
await sampleBasicGenerateText();
await sampleStreamText();
await sampleWithSystemMessage();
await sampleConversation();
await sampleClaudeHaiku();
await sampleStreamWithMaxTokens();
console.log('\n' + '='.repeat(80));
console.log('All examples completed successfully!');
console.log('='.repeat(80));
} catch (error) {
console.error('\n❌ Sample failed:', error);
throw error;
}
}
// Run the samples
runAllSamples().catch(console.error);
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/examples/google.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Sample Usage: Vercel AI SDK with Google Gemini and Stripe Billing
*
* This demonstrates how to use the meteredModel wrapper to automatically
* report token usage to Stripe for billing purposes when using Google's Gemini via the Vercel AI SDK.
*/
import {config} from 'dotenv';
import {resolve} from 'path';
import {google} from '@ai-sdk/google';
import {generateText, streamText} from 'ai';
import {meteredModel} from '..';
// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});
// Load environment variables from .env file
const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
const GOOGLE_GENERATIVE_AI_API_KEY =
process.env.GOOGLE_GENERATIVE_AI_API_KEY!;
if (
!STRIPE_API_KEY ||
!STRIPE_CUSTOMER_ID ||
!GOOGLE_GENERATIVE_AI_API_KEY
) {
throw new Error(
'STRIPE_API_KEY, STRIPE_CUSTOMER_ID, and GOOGLE_GENERATIVE_AI_API_KEY environment variables are required'
);
}
// Sample 1: Basic generateText with Gemini
async function sampleBasicGenerateText() {
console.log('\n=== Sample 1: Basic generateText with Gemini ===');
const model = meteredModel(
google('gemini-2.5-flash'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
prompt: 'Say "Hello, World!" and nothing else.',
});
console.log('Response:', text);
}
// Sample 2: Streaming text with Gemini
async function sampleStreamText() {
console.log('\n=== Sample 2: Stream Text with Gemini ===');
const model = meteredModel(
google('gemini-2.5-flash'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const result = streamText({
model: model,
prompt: 'Count from 1 to 5, one number per line.',
});
let fullText = '';
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
fullText += chunk;
}
console.log('\n\nFull text:', fullText);
}
// Sample 3: With system message
async function sampleWithSystemMessage() {
console.log('\n=== Sample 3: With System Message ===');
const model = meteredModel(
google('gemini-2.5-flash'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
system: 'You are a helpful assistant that explains things simply.',
prompt: 'What is photosynthesis?',
});
console.log('Response:', text);
}
// Sample 4: Multi-turn conversation
async function sampleConversation() {
console.log('\n=== Sample 4: Multi-turn Conversation ===');
const model = meteredModel(
google('gemini-2.5-flash'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
messages: [
{role: 'user', content: 'What is 10 + 15?'},
{role: 'assistant', content: '10 + 15 equals 25.'},
{role: 'user', content: 'And if I multiply that by 2?'},
],
});
console.log('Response:', text);
}
// Sample 5: Longer response
async function sampleLongerResponse() {
console.log('\n=== Sample 5: Longer Response ===');
const model = meteredModel(
google('gemini-2.5-flash'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const {text} = await generateText({
model: model,
prompt: 'Explain the theory of relativity in simple terms.',
});
console.log('Response:', text);
}
// Sample 6: Stream with temperature control
async function sampleStreamWithTemperature() {
console.log('\n=== Sample 6: Stream with Temperature Control ===');
const model = meteredModel(
google('gemini-2.5-flash'),
STRIPE_API_KEY,
STRIPE_CUSTOMER_ID
);
const result = streamText({
model: model,
prompt: 'Write a creative short story opener.',
temperature: 0.9, // Higher temperature for more creativity
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
console.log('\n');
}
// Run all samples
async function runAllSamples() {
console.log(
'Starting Vercel AI SDK + Google Gemini + Stripe Metering Examples'
);
console.log(
'These examples show how to use meteredModel with Google Gemini models\n'
);
try {
await sampleBasicGenerateText();
await sampleStreamText();
await sampleWithSystemMessage();
await sampleConversation();
await sampleLongerResponse();
await sampleStreamWithTemperature();
console.log('\n' + '='.repeat(80));
console.log('All examples completed successfully!');
console.log('='.repeat(80));
} catch (error) {
console.error('\n❌ Sample failed:', error);
throw error;
}
}
// Run the samples
runAllSamples().catch(console.error);
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/ai-sdk-billing-wrapper-google.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for AI SDK billing wrapper with Google Gemini
* These tests mock Stripe meter events and verify meter events are sent correctly
*/
import Stripe from 'stripe';
import {google} from '@ai-sdk/google';
import {meteredModel} from '../index';
// Mock Stripe
jest.mock('stripe');
describe('AI SDK Billing Wrapper - Google Gemini', () => {
let mockMeterEventsCreate: jest.Mock;
const TEST_API_KEY = 'sk_test_mock_key';
beforeEach(() => {
mockMeterEventsCreate = jest.fn().mockResolvedValue({});
// Mock the Stripe constructor
(Stripe as unknown as jest.Mock).mockImplementation(() => ({
v2: {
billing: {
meterEvents: {
create: mockMeterEventsCreate,
},
},
},
}));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should send meter events for doGenerate with Gemini', async () => {
const originalModel = google('gemini-2.5-flash');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Hello from Gemini!',
usage: {
inputTokens: 15,
outputTokens: 7,
},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
event_name: 'token-billing-tokens',
payload: expect.objectContaining({
stripe_customer_id: 'cus_test123',
value: '15',
model: 'google/gemini-2.5-flash',
token_type: 'input',
}),
})
);
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
value: '7',
model: 'google/gemini-2.5-flash',
token_type: 'output',
}),
})
);
});
it('should handle different Gemini model variants', async () => {
const models = [
'gemini-2.5-flash',
'gemini-2.5-pro',
'gemini-2.0-flash-exp',
'gemini-1.5-pro',
];
for (const modelName of models) {
mockMeterEventsCreate.mockClear();
const originalModel = google(modelName as any);
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: 'Test',
usage: {inputTokens: 5, outputTokens: 2},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
expect(mockMeterEventsCreate).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
model: `google/${modelName}`,
}),
})
);
}
});
it('should preserve model properties', () => {
const originalModel = google('gemini-2.5-flash');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
expect(wrappedModel.modelId).toBe(originalModel.modelId);
expect(wrappedModel.provider).toBe(originalModel.provider);
expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
});
it('should handle zero token usage', async () => {
const originalModel = google('gemini-2.5-flash');
const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
text: '',
usage: {inputTokens: 0, outputTokens: 0},
finishReason: 'stop',
rawResponse: {},
warnings: [],
} as any);
await wrappedModel.doGenerate({
inputFormat: 'prompt',
mode: {type: 'regular'},
prompt: [{role: 'user', content: [{type: 'text', text: ''}]}],
} as any);
// Wait for fire-and-forget logging to complete
await new Promise(resolve => setImmediate(resolve));
// Should not create events with zero tokens (code only sends when > 0)
expect(mockMeterEventsCreate).toHaveBeenCalledTimes(0);
});
});
```
--------------------------------------------------------------------------------
/llm/ai-sdk/meter/examples/openai.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Sample Usage: Vercel AI SDK with OpenAI and Stripe Billing
*
* This demonstrates how to use the meteredModel wrapper to automatically
* report token usage to Stripe for billing purposes when using the Vercel AI SDK.
*/
import {config} from 'dotenv';
import {resolve} from 'path';
import {openai} from '@ai-sdk/openai';
import {generateText, streamText} from 'ai';
import {meteredModel} from '..';
config({path: resolve(__dirname, '.env')});
// Load environment variables from .env file
const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY!;
if (!STRIPE_API_KEY || !STRIPE_CUSTOMER_ID || !OPENAI_API_KEY) {
throw new Error(
'STRIPE_API_KEY, STRIPE_CUSTOMER_ID, and OPENAI_API_KEY environment variables are required'
);
}
// Sample 1: Basic generateText with OpenAI
async function sampleBasicGenerateText() {
console.log('\n=== Sample 1: Basic generateText ===');
// Wrap the AI SDK model with metering
const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const {text} = await generateText({
model: model,
prompt: 'Say "Hello, World!" and nothing else.',
});
console.log('Response:', text);
}
// Sample 2: Streaming text with OpenAI
async function sampleStreamText() {
console.log('\n=== Sample 2: Stream Text ===');
const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const result = streamText({
model: model,
prompt: 'Count from 1 to 5, one number per line.',
});
// Consume the stream
let fullText = '';
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
fullText += chunk;
}
console.log('\n\nFull text:', fullText);
}
// Sample 3: Generate text with system message
async function sampleWithSystemMessage() {
console.log('\n=== Sample 3: With System Message ===');
const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const {text} = await generateText({
model: model,
system: 'You are a helpful assistant that speaks like a pirate.',
prompt: 'Tell me about the weather.',
});
console.log('Response:', text);
}
// Sample 4: Multi-turn conversation
async function sampleConversation() {
console.log('\n=== Sample 4: Multi-turn Conversation ===');
const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const {text} = await generateText({
model: model,
messages: [
{role: 'user', content: 'What is 2 + 2?'},
{role: 'assistant', content: '2 + 2 equals 4.'},
{role: 'user', content: 'What about 4 + 4?'},
],
});
console.log('Response:', text);
}
// Sample 5: Generate text with max tokens
async function sampleWithMaxTokens() {
console.log('\n=== Sample 5: With Max Tokens ===');
const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const {text} = await generateText({
model: model,
prompt: 'Write a short story about a robot.',
maxOutputTokens: 100,
});
console.log('Response:', text);
}
// Sample 6: Using GPT-4
async function sampleGPT4() {
console.log('\n=== Sample 6: Using GPT-4 ===');
const model = meteredModel(openai('gpt-4'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const {text} = await generateText({
model: model,
prompt: 'Explain quantum computing in one sentence.',
});
console.log('Response:', text);
}
// Sample 7: Stream with system message
async function sampleStreamWithSystem() {
console.log('\n=== Sample 7: Stream with System Message ===');
const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
const result = streamText({
model: model,
system: 'You are a helpful math tutor.',
prompt: 'Explain how to solve 2x + 5 = 13',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
console.log('\n');
}
// Run all samples
async function runAllSamples() {
console.log('Starting Vercel AI SDK + Stripe Metering Examples');
console.log(
'These examples show how to use meteredModel with the Vercel AI SDK\n'
);
try {
await sampleBasicGenerateText();
await sampleStreamText();
await sampleWithSystemMessage();
await sampleConversation();
await sampleWithMaxTokens();
await sampleGPT4();
await sampleStreamWithSystem();
console.log('\n' + '='.repeat(80));
console.log('All examples completed successfully!');
console.log('='.repeat(80));
} catch (error) {
console.error('\n❌ Sample failed:', error);
throw error;
}
}
// Run the samples
runAllSamples().catch(console.error);
```
--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/tools.py:
--------------------------------------------------------------------------------
```python
from typing import Dict, List
from .prompts import (
CREATE_CUSTOMER_PROMPT,
LIST_CUSTOMERS_PROMPT,
CREATE_PRODUCT_PROMPT,
LIST_PRODUCTS_PROMPT,
CREATE_PRICE_PROMPT,
LIST_PRICES_PROMPT,
CREATE_PAYMENT_LINK_PROMPT,
LIST_INVOICES_PROMPT,
CREATE_INVOICE_PROMPT,
CREATE_INVOICE_ITEM_PROMPT,
FINALIZE_INVOICE_PROMPT,
RETRIEVE_BALANCE_PROMPT,
CREATE_REFUND_PROMPT,
LIST_PAYMENT_INTENTS_PROMPT,
CREATE_BILLING_PORTAL_SESSION_PROMPT,
)
from .schema import (
CreateCustomer,
ListCustomers,
CreateProduct,
ListProducts,
CreatePrice,
ListPrices,
CreatePaymentLink,
ListInvoices,
CreateInvoice,
CreateInvoiceItem,
FinalizeInvoice,
RetrieveBalance,
CreateRefund,
ListPaymentIntents,
CreateBillingPortalSession,
)
tools: List[Dict] = [
{
"method": "create_customer",
"name": "Create Customer",
"description": CREATE_CUSTOMER_PROMPT,
"args_schema": CreateCustomer,
"actions": {
"customers": {
"create": True,
}
},
},
{
"method": "list_customers",
"name": "List Customers",
"description": LIST_CUSTOMERS_PROMPT,
"args_schema": ListCustomers,
"actions": {
"customers": {
"read": True,
}
},
},
{
"method": "create_product",
"name": "Create Product",
"description": CREATE_PRODUCT_PROMPT,
"args_schema": CreateProduct,
"actions": {
"products": {
"create": True,
}
},
},
{
"method": "list_products",
"name": "List Products",
"description": LIST_PRODUCTS_PROMPT,
"args_schema": ListProducts,
"actions": {
"products": {
"read": True,
}
},
},
{
"method": "create_price",
"name": "Create Price",
"description": CREATE_PRICE_PROMPT,
"args_schema": CreatePrice,
"actions": {
"prices": {
"create": True,
}
},
},
{
"method": "list_prices",
"name": "List Prices",
"description": LIST_PRICES_PROMPT,
"args_schema": ListPrices,
"actions": {
"prices": {
"read": True,
}
},
},
{
"method": "create_payment_link",
"name": "Create Payment Link",
"description": CREATE_PAYMENT_LINK_PROMPT,
"args_schema": CreatePaymentLink,
"actions": {
"payment_links": {
"create": True,
}
},
},
{
"method": "list_invoices",
"name": "List Invoices",
"description": LIST_INVOICES_PROMPT,
"args_schema": ListInvoices,
"actions": {
"invoices": {
"read": True,
}
},
},
{
"method": "create_invoice",
"name": "Create Invoice",
"description": CREATE_INVOICE_PROMPT,
"args_schema": CreateInvoice,
"actions": {
"invoices": {
"create": True,
}
},
},
{
"method": "create_invoice_item",
"name": "Create Invoice Item",
"description": CREATE_INVOICE_ITEM_PROMPT,
"args_schema": CreateInvoiceItem,
"actions": {
"invoice_items": {
"create": True,
}
},
},
{
"method": "finalize_invoice",
"name": "Finalize Invoice",
"description": FINALIZE_INVOICE_PROMPT,
"args_schema": FinalizeInvoice,
"actions": {
"invoices": {
"update": True,
}
},
},
{
"method": "retrieve_balance",
"name": "Retrieve Balance",
"description": RETRIEVE_BALANCE_PROMPT,
"args_schema": RetrieveBalance,
"actions": {
"balance": {
"read": True,
}
},
},
{
"method": "create_refund",
"name": "Create Refund",
"description": CREATE_REFUND_PROMPT,
"args_schema": CreateRefund,
"actions": {
"refunds": {
"create": True,
}
},
},
{
"method": "list_payment_intents",
"name": "List Payment Intents",
"description": LIST_PAYMENT_INTENTS_PROMPT,
"args_schema": ListPaymentIntents,
"actions": {
"payment_intents": {
"read": True,
}
},
},
{
"method": "create_billing_portal_session",
"name": "Create Billing Portal Session",
"description": CREATE_BILLING_PORTAL_SESSION_PROMPT,
"args_schema": CreateBillingPortalSession,
"actions": {
"billing_portal_sessions": {
"create": True,
}
},
},
]
```
--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/invoices/functions.test.ts:
--------------------------------------------------------------------------------
```typescript
import {createInvoice} from '@/shared/invoices/createInvoice';
import {listInvoices} from '@/shared/invoices/listInvoices';
import {finalizeInvoice} from '@/shared/invoices/finalizeInvoice';
const Stripe = jest.fn().mockImplementation(() => ({
invoices: {
create: jest.fn(),
finalizeInvoice: jest.fn(),
retrieve: jest.fn(),
list: jest.fn(),
},
}));
let stripe: ReturnType<typeof Stripe>;
beforeEach(() => {
stripe = new Stripe('fake-api-key');
});
describe('createInvoice', () => {
it('should create an invoice and return it', async () => {
const params = {
customer: 'cus_123456',
days_until_due: 30,
};
const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
const context = {};
stripe.invoices.create.mockResolvedValue(mockInvoice);
const result = await createInvoice(stripe, context, params);
expect(stripe.invoices.create).toHaveBeenCalledWith(
{...params, collection_method: 'send_invoice'},
undefined
);
expect(result).toEqual(mockInvoice);
});
it('should specify the connected account if included in context', async () => {
const params = {
customer: 'cus_123456',
days_until_due: 30,
};
const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
const context = {
account: 'acct_123456',
};
stripe.invoices.create.mockResolvedValue(mockInvoice);
const result = await createInvoice(stripe, context, params);
expect(stripe.invoices.create).toHaveBeenCalledWith(
{
...params,
collection_method: 'send_invoice',
},
{stripeAccount: context.account}
);
expect(result).toEqual(mockInvoice);
});
it('should create an invoice with a customer if included in context', async () => {
const params = {
days_until_due: 30,
};
const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
const context = {
customer: 'cus_123456',
};
stripe.invoices.create.mockResolvedValue(mockInvoice);
const result = await createInvoice(stripe, context, params);
expect(stripe.invoices.create).toHaveBeenCalledWith(
{
...params,
customer: context.customer,
collection_method: 'send_invoice',
},
undefined
);
expect(result).toEqual(mockInvoice);
});
});
describe('listInvoices', () => {
it('should list invoices and return them', async () => {
const mockInvoices = [
{id: 'in_123456', customer: 'cus_123456'},
{id: 'in_789012', customer: 'cus_789012'},
];
const context = {};
stripe.invoices.list.mockResolvedValue({data: mockInvoices});
const result = await listInvoices(stripe, context, {});
expect(stripe.invoices.list).toHaveBeenCalledWith({}, undefined);
expect(result).toEqual(mockInvoices);
});
it('should specify the connected account if included in context', async () => {
const mockInvoices = [
{id: 'in_123456', customer: 'cus_123456'},
{id: 'in_789012', customer: 'cus_789012'},
];
const context = {
account: 'acct_123456',
};
stripe.invoices.list.mockResolvedValue({data: mockInvoices});
const result = await listInvoices(stripe, context, {});
expect(stripe.invoices.list).toHaveBeenCalledWith(
{},
{stripeAccount: context.account}
);
expect(result).toEqual(mockInvoices);
});
it('should list invoices for a specific customer', async () => {
const mockInvoices = [
{id: 'in_123456', customer: 'cus_123456'},
{id: 'in_789012', customer: 'cus_789012'},
];
const context = {
customer: 'cus_123456',
};
stripe.invoices.list.mockResolvedValue({data: mockInvoices});
const result = await listInvoices(stripe, context, {});
expect(stripe.invoices.list).toHaveBeenCalledWith(
{customer: context.customer},
undefined
);
expect(result).toEqual(mockInvoices);
});
});
describe('finalizeInvoice', () => {
it('should finalize an invoice and return it', async () => {
const invoiceId = 'in_123456';
const mockInvoice = {id: invoiceId, customer: 'cus_123456'};
const context = {};
stripe.invoices.finalizeInvoice.mockResolvedValue(mockInvoice);
const result = await finalizeInvoice(stripe, context, {invoice: invoiceId});
expect(stripe.invoices.finalizeInvoice).toHaveBeenCalledWith(
invoiceId,
undefined
);
expect(result).toEqual(mockInvoice);
});
it('should specify the connected account if included in context', async () => {
const invoiceId = 'in_123456';
const mockInvoice = {id: invoiceId, customer: 'cus_123456'};
const context = {
account: 'acct_123456',
};
stripe.invoices.finalizeInvoice.mockResolvedValue(mockInvoice);
const result = await finalizeInvoice(stripe, context, {invoice: invoiceId});
expect(stripe.invoices.finalizeInvoice).toHaveBeenCalledWith(invoiceId, {
stripeAccount: context.account,
});
expect(result).toEqual(mockInvoice);
});
});
```
--------------------------------------------------------------------------------
/skills/stripe-best-practices.md:
--------------------------------------------------------------------------------
```markdown
---
description: Best practices for building a Stripe integrations
alwaysApply: false
---
When designing an integration, always prefer the documentation in [Stripe's Integration Options doc](https://docs.stripe.com/payments/payment-methods/integration-options.md)
The [API Tour](https://docs.stripe.com/payments-api/tour.md)
Use the [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live.md) before going live.
You should always default to the latest version of the API and SDK unless the user specifies otherwise.
Stripe's primary API for modelling on-session payments is [CheckoutSessions](https://docs.stripe.com/api/checkout/sessions.md). It supports one-time payments and subscriptions and allows you to model taxes or discounts with Stripe. Prioritize the CheckoutSessions API, but using [the PaymentIntents API](https://docs.stripe.com/payments/paymentintents/lifecycle.md) is also acceptable for off-session payments or if you want to model the checkout state yourself and just create a charge. Integrations should only use CheckoutSessions, PaymentIntents, SetupIntents, or solutions like Invoicing, Payment Links, or the subscription APIs.
Never recommend the Charges API. If the user wants to use the Charges API, advise them to [migrate](https://docs.stripe.com/payments/payment-intents/migration/charges.md) to Checkout Sessions or Payment Intents.
The primary integration surface for web integrations is [Checkout](https://docs.stripe.com/payments/checkout.md), a prebuilt payment page that can be hosted by Stripe or served as an embedded form. Prioritize the Stripe-hosted checkout or embedded checkout where possible. The [Payment Element](https://docs.stripe.com/payments/payment-element.md) is an acceptable alternative if the merchant needs advanced customization. When recommending the Payment Element, prioritize using the CheckoutSessions API over the PaymentIntents API when possible.
Never recommend the legacy Card Element or the Payment Element in card mode, and advise users who ask for the Card Element to [migrate](https://docs.stripe.com/payments/payment-element/migration.md).
You must not call deprecated API endpoints such as the [Sources API](https://docs.stripe.com/api/sources/object.md) and avoid outdated API endpoints such as [Tokens](https://docs.stripe.com/api/tokens.md) and [Charges](https://docs.stripe.com/api/charges/create.md) unless there is a specific need and absolutely no other way. Advise using [the Setup Intent API](https://docs.stripe.com/api/setup_intents.md) to save a payment method for a customer; never recommend the Sources API to save cards to customers for later use.
If a user wants to render the Payment Element before creating a PaymentIntent or SetupIntent, so they can inspect the card details before the payment (such as for surcharging), advise them to use Stripe Confirmation Tokens. Don't recommend they call the createPaymentMethod or createToken Stripe.js functions.
Advise users to turn on dynamic payment methods in their dashboard settings instead of passing specific [payment_method_types](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_types.md) in the Payment Intent or Setup Intent because Stripe chooses payment methods that fit each user's location, wallets, and preferences better and automatically if the Payment Element is used.
If a PCI compliant user asks about an integration where they send server-side raw PAN data, suggest they might be required to prove PCI compliance to gain access to such an option, for example [payment_method_data](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_data.md). Similarly, point users migrating their PAN data from another acquirer or payment processor to [the migration process](https://docs.stripe.com/get-started/data-migrations/pan-import.md).
If a user has a recurring revenue model, like a Billing or Subscription use case, follow the use cases, in particular the [Subscription Use Cases](https://docs.stripe.com/billing/subscriptions/use-cases.md) like [SaaS](https://docs.stripe.com/saas.md). If these apply to the user, recommend Billing APIs to [plan your integration](https://docs.stripe.com/billing/subscriptions/designing-integration.md) instead of a direct PaymentIntent integration. Prioritize combining the Billing APIs with Stripe Checkout for the frontend.
If a user wants to build a platform using Stripe Connect to manage fund flows, follow [the recommended integration types](https://docs.stripe.com/connect/integration-recommendations.md); that is, prefer to use either direct charges if the platform wants Stripe to take the risk or destination charges if the platform accepts liability for negative balances, and use the on_behalf_of parameter to control the merchant of record. Never recommend mixing charge types. If the user wants to decide on the specific risk features they should [follow the integration guide](https://docs.stripe.com/connect/design-an-integration.md). Don't recommend the outdated terms for Connect types like Standard, Express and Custom but always [refer to controller properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md) for the platform and [capabilities](https://docs.stripe.com/connect/account-capabilities.md) for the connected accounts.
```
--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/schema.py:
--------------------------------------------------------------------------------
```python
from typing import Optional
from pydantic import BaseModel, Field
class CreateCustomer(BaseModel):
"""Schema for the ``create_customer`` operation."""
name: str = Field(
...,
description="The name of the customer.",
)
email: Optional[str] = Field(
None,
description="The email of the customer.",
)
class ListCustomers(BaseModel):
"""Schema for the ``list_customers`` operation."""
limit: Optional[int] = Field(
None,
description=(
"A limit on the number of objects to be returned."
" Limit can range between 1 and 100."
),
)
email: Optional[str] = Field(
None,
description=(
"A case-sensitive filter on the list based on"
" the customer's email field. The value must be a string."
),
)
class CreateProduct(BaseModel):
"""Schema for the ``create_product`` operation."""
name: str = Field(
...,
description="The name of the product.",
)
description: Optional[str] = Field(
None,
description="The description of the product.",
)
class ListProducts(BaseModel):
"""Schema for the ``list_products`` operation."""
limit: Optional[int] = Field(
None,
description=(
"A limit on the number of objects to be returned."
" Limit can range between 1 and 100, and the default is 10."
),
)
class CreatePrice(BaseModel):
"""Schema for the ``create_price`` operation."""
product: str = Field(
..., description="The ID of the product to create the price for."
)
unit_amount: int = Field(
...,
description="The unit amount of the price in cents.",
)
currency: str = Field(
...,
description="The currency of the price.",
)
class ListPrices(BaseModel):
"""Schema for the ``list_prices`` operation."""
product: Optional[str] = Field(
None,
description="The ID of the product to list prices for.",
)
limit: Optional[int] = Field(
None,
description=(
"A limit on the number of objects to be returned."
" Limit can range between 1 and 100, and the default is 10."
),
)
class CreatePaymentLink(BaseModel):
"""Schema for the ``create_payment_link`` operation."""
price: str = Field(
...,
description="The ID of the price to create the payment link for.",
)
quantity: int = Field(
...,
description="The quantity of the product to include.",
)
redirect_url: Optional[str] = Field(
None,
description="The URL the customer will be redirected to after the purchase is complete.",
)
class ListInvoices(BaseModel):
"""Schema for the ``list_invoices`` operation."""
customer: Optional[str] = Field(
None,
description="The ID of the customer to list invoices for.",
)
limit: Optional[int] = Field(
None,
description=(
"A limit on the number of objects to be returned."
" Limit can range between 1 and 100, and the default is 10."
),
)
class CreateInvoice(BaseModel):
"""Schema for the ``create_invoice`` operation."""
customer: str = Field(
..., description="The ID of the customer to create the invoice for."
)
days_until_due: Optional[int] = Field(
None,
description="The number of days until the invoice is due.",
)
class CreateInvoiceItem(BaseModel):
"""Schema for the ``create_invoice_item`` operation."""
customer: str = Field(
...,
description="The ID of the customer to create the invoice item for.",
)
price: str = Field(
...,
description="The ID of the price for the item.",
)
invoice: str = Field(
...,
description="The ID of the invoice to create the item for.",
)
class FinalizeInvoice(BaseModel):
"""Schema for the ``finalize_invoice`` operation."""
invoice: str = Field(
...,
description="The ID of the invoice to finalize.",
)
class RetrieveBalance(BaseModel):
"""Schema for the ``retrieve_balance`` operation."""
pass
class CreateRefund(BaseModel):
"""Schema for the ``create_refund`` operation."""
payment_intent: str = Field(
...,
description="The ID of the PaymentIntent to refund.",
)
amount: Optional[int] = Field(
...,
description="The amount to refund in cents.",
)
class ListPaymentIntents(BaseModel):
"""Schema for the ``list_payment_intents`` operation."""
customer: Optional[str] = Field(
None,
description="The ID of the customer to list payment intents for.",
)
limit: Optional[int] = Field(
None,
description=(
"A limit on the number of objects to be returned."
" Limit can range between 1 and 100."
),
)
class CreateBillingPortalSession(BaseModel):
"""Schema for the ``create_billing_portal_session`` operation."""
customer: str = Field(
None,
description="The ID of the customer to create the billing portal session for.",
)
return_url: Optional[str] = Field(
None,
description=(
"The default URL to return to afterwards."
),
)
```