#
tokens: 49642/50000 31/256 files (page 3/7)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 7. Use http://codebase.md/stripe/agent-toolkit?lines=true&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/ai-sdk/toolkit.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import StripeAPI from '../shared/api';
  2 | import tools from '../shared/tools';
  3 | import {isToolAllowed, type Configuration} from '../shared/configuration';
  4 | import type {
  5 |   LanguageModelV2Middleware,
  6 |   LanguageModelV2Usage,
  7 | } from '@ai-sdk/provider';
  8 | import StripeTool from './tool';
  9 | 
 10 | type ProviderTool = ReturnType<typeof StripeTool>;
 11 | type WrapGenerateOptions = Parameters<
 12 |   NonNullable<LanguageModelV2Middleware['wrapGenerate']>
 13 | >[0];
 14 | type WrapStreamOptions = Parameters<
 15 |   NonNullable<LanguageModelV2Middleware['wrapStream']>
 16 | >[0];
 17 | 
 18 | type StripeMiddlewareConfig = {
 19 |   billing?: {
 20 |     type?: 'token';
 21 |     customer: string;
 22 |     meters: {
 23 |       input?: string;
 24 |       output?: string;
 25 |     };
 26 |   };
 27 | };
 28 | 
 29 | class StripeAgentToolkit {
 30 |   private _stripe: StripeAPI;
 31 | 
 32 |   tools: Record<string, ProviderTool>;
 33 | 
 34 |   constructor({
 35 |     secretKey,
 36 |     configuration,
 37 |   }: {
 38 |     secretKey: string;
 39 |     configuration: Configuration;
 40 |   }) {
 41 |     this._stripe = new StripeAPI(secretKey, configuration.context);
 42 |     this.tools = {};
 43 | 
 44 |     const context = configuration.context || {};
 45 |     const filteredTools = tools(context).filter((tool) =>
 46 |       isToolAllowed(tool, configuration)
 47 |     );
 48 | 
 49 |     filteredTools.forEach((tool) => {
 50 |       // @ts-ignore
 51 |       this.tools[tool.method] = StripeTool(
 52 |         this._stripe,
 53 |         tool.method,
 54 |         tool.description,
 55 |         tool.inputSchema
 56 |       );
 57 |     });
 58 |   }
 59 | 
 60 |   middleware(config: StripeMiddlewareConfig): LanguageModelV2Middleware {
 61 |     const bill = async (usage?: LanguageModelV2Usage) => {
 62 |       if (!config.billing || !usage) {
 63 |         return;
 64 |       }
 65 | 
 66 |       const {inputTokens, outputTokens} = usage;
 67 |       const inputValue = (inputTokens ?? 0).toString();
 68 |       const outputValue = (outputTokens ?? 0).toString();
 69 | 
 70 |       if (config.billing.meters.input) {
 71 |         await this._stripe.createMeterEvent({
 72 |           event: config.billing.meters.input,
 73 |           customer: config.billing.customer,
 74 |           value: inputValue,
 75 |         });
 76 |       }
 77 | 
 78 |       if (config.billing.meters.output) {
 79 |         await this._stripe.createMeterEvent({
 80 |           event: config.billing.meters.output,
 81 |           customer: config.billing.customer,
 82 |           value: outputValue,
 83 |         });
 84 |       }
 85 |     };
 86 | 
 87 |     return {
 88 |       wrapGenerate: async ({doGenerate}: WrapGenerateOptions) => {
 89 |         const result = await doGenerate();
 90 | 
 91 |         await bill(result.usage);
 92 | 
 93 |         return result;
 94 |       },
 95 | 
 96 |       wrapStream: async ({doStream}: WrapStreamOptions) => {
 97 |         const {stream, ...rest} = await doStream();
 98 | 
 99 |         const transformStream = new TransformStream<any, any>({
100 |           async transform(chunk, controller) {
101 |             if (chunk?.type === 'finish') {
102 |               await bill(chunk.usage);
103 |             }
104 | 
105 |             controller.enqueue(chunk);
106 |           },
107 |         });
108 | 
109 |         return {
110 |           stream: stream.pipeThrough(transformStream),
111 |           ...rest,
112 |         };
113 |       },
114 |     };
115 |   }
116 | 
117 |   getTools(): Record<string, ProviderTool> {
118 |     return this.tools;
119 |   }
120 | }
121 | 
122 | export default StripeAgentToolkit;
123 | 
```

--------------------------------------------------------------------------------
/tools/typescript/src/shared/coupons/createCoupon.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {StripeToolDefinition} from '@/shared/tools';
  5 | 
  6 | export const createCouponPrompt = (_context: Context = {}) => `
  7 | This tool will create a coupon in Stripe.
  8 | 
  9 | 
 10 | It takes several arguments:
 11 | - name (str): The name of the coupon.
 12 | 
 13 | Only use one of percent_off or amount_off, not both:
 14 | - percent_off (number, optional): The percentage discount to apply (between 0 and 100).
 15 | - amount_off (number, optional): The amount to subtract from an invoice (in cents).
 16 | 
 17 | Optional arguments for duration. Use if specific duration is desired, otherwise default to 'once'.
 18 | - duration (str, optional): How long the discount will last ('once', 'repeating', or 'forever'). Defaults to 'once'.
 19 | - duration_in_months (number, optional): The number of months the discount will last if duration is repeating.
 20 | `;
 21 | 
 22 | export const createCouponParameters = (
 23 |   _context: Context = {}
 24 | ): z.AnyZodObject =>
 25 |   z.object({
 26 |     name: z
 27 |       .string()
 28 |       .describe(
 29 |         'Name of the coupon displayed to customers on invoices or receipts'
 30 |       ),
 31 |     percent_off: z
 32 |       .number()
 33 |       .min(0)
 34 |       .max(100)
 35 |       .optional()
 36 |       .describe(
 37 |         '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)'
 38 |       ),
 39 |     amount_off: z
 40 |       .number()
 41 |       .describe(
 42 |         'A positive integer representing the amount to subtract from an invoice total (required if percent_off is not passed)'
 43 |       ),
 44 |     currency: z
 45 |       .string()
 46 |       .optional()
 47 |       .default('USD')
 48 |       .describe(
 49 |         '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.'
 50 |       ),
 51 |     duration: z
 52 |       .enum(['once', 'repeating', 'forever'])
 53 |       .default('once')
 54 |       .optional()
 55 |       .describe('How long the discount will last. Defaults to "once"'),
 56 |     duration_in_months: z
 57 |       .number()
 58 |       .optional()
 59 |       .describe(
 60 |         'The number of months the discount will last if duration is repeating'
 61 |       ),
 62 |   });
 63 | 
 64 | export const createCouponAnnotations = () => ({
 65 |   destructiveHint: false,
 66 |   idempotentHint: false,
 67 |   openWorldHint: true,
 68 |   readOnlyHint: false,
 69 |   title: 'Create coupon',
 70 | });
 71 | 
 72 | export const createCoupon = async (
 73 |   stripe: Stripe,
 74 |   context: Context,
 75 |   params: z.infer<ReturnType<typeof createCouponParameters>>
 76 | ) => {
 77 |   try {
 78 |     const coupon = await stripe.coupons.create(
 79 |       params,
 80 |       context.account ? {stripeAccount: context.account} : undefined
 81 |     );
 82 | 
 83 |     return {id: coupon.id};
 84 |   } catch (error: any) {
 85 |     return `Failed to create coupon: ${error.message}`;
 86 |   }
 87 | };
 88 | 
 89 | const tool = (context: Context): StripeToolDefinition => ({
 90 |   method: 'create_coupon',
 91 |   name: 'Create Coupon',
 92 |   description: createCouponPrompt(context),
 93 |   inputSchema: createCouponParameters(context),
 94 |   annotations: createCouponAnnotations(),
 95 |   actions: {
 96 |     coupons: {
 97 |       create: true,
 98 |     },
 99 |   },
100 |   execute: createCoupon,
101 | });
102 | 
103 | export default tool;
104 | 
```

--------------------------------------------------------------------------------
/tools/typescript/src/shared/disputes/updateDispute.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {StripeToolDefinition} from '@/shared/tools';
  5 | 
  6 | export const updateDisputePrompt = (_context: Context = {}) => `
  7 | 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.
  8 | 
  9 | It takes the following arguments:
 10 | - dispute (string): The ID of the dispute to update
 11 | - evidence (object, optional): Evidence to upload for the dispute.
 12 |     - cancellation_policy_disclosure (string)
 13 |     - cancellation_rebuttal (string)
 14 |     - duplicate_charge_explanation (string)
 15 |     - uncategorized_text (string, optional): Any additional evidence or statements.
 16 | - submit (boolean, optional): Whether to immediately submit evidence to the bank. If false, evidence is staged on the dispute.
 17 | `;
 18 | 
 19 | export const updateDisputeParameters = (_context: Context = {}) =>
 20 |   z.object({
 21 |     dispute: z.string().describe('The ID of the dispute to update'),
 22 |     evidence: z
 23 |       .object({
 24 |         cancellation_policy_disclosure: z
 25 |           .string()
 26 |           .max(20000)
 27 |           .optional()
 28 |           .describe(
 29 |             'An explanation of how and when the customer was shown your refund policy prior to purchase.'
 30 |           ),
 31 |         duplicate_charge_explanation: z
 32 |           .string()
 33 |           .max(20000)
 34 |           .optional()
 35 |           .describe(
 36 |             'An explanation of the difference between the disputed charge versus the prior charge that appears to be a duplicate.'
 37 |           ),
 38 |         uncategorized_text: z
 39 |           .string()
 40 |           .max(20000)
 41 |           .optional()
 42 |           .describe('Any additional evidence or statements.'),
 43 |       })
 44 |       .optional()
 45 |       .describe(
 46 |         'Evidence to upload, to respond to a dispute. Updating any field in the hash will submit all fields in the hash for review.'
 47 |       ),
 48 |     submit: z
 49 |       .boolean()
 50 |       .optional()
 51 |       .describe(
 52 |         'Whether to immediately submit evidence to the bank. If false, evidence is staged on the dispute.'
 53 |       ),
 54 |   });
 55 | 
 56 | export const updateDisputeAnnotations = () => ({
 57 |   destructiveHint: false,
 58 |   idempotentHint: false,
 59 |   openWorldHint: true,
 60 |   readOnlyHint: false,
 61 |   title: 'Update dispute',
 62 | });
 63 | 
 64 | export const updateDispute = async (
 65 |   stripe: Stripe,
 66 |   context: Context,
 67 |   params: z.infer<ReturnType<typeof updateDisputeParameters>>
 68 | ) => {
 69 |   try {
 70 |     const updateParams: Stripe.DisputeUpdateParams = {
 71 |       evidence: params.evidence,
 72 |       submit: params.submit,
 73 |     };
 74 | 
 75 |     const updatedDispute = await stripe.disputes.update(
 76 |       params.dispute,
 77 |       updateParams,
 78 |       context.account ? {stripeAccount: context.account} : undefined
 79 |     );
 80 | 
 81 |     return {id: updatedDispute.id};
 82 |   } catch (error) {
 83 |     return 'Failed to update dispute';
 84 |   }
 85 | };
 86 | 
 87 | const tool = (context: Context): StripeToolDefinition => ({
 88 |   method: 'update_dispute',
 89 |   name: 'Update Dispute',
 90 |   description: updateDisputePrompt(context),
 91 |   inputSchema: updateDisputeParameters(context),
 92 |   annotations: updateDisputeAnnotations(),
 93 |   actions: {
 94 |     disputes: {
 95 |       update: true,
 96 |     },
 97 |   },
 98 |   execute: updateDispute,
 99 | });
100 | 
101 | export default tool;
102 | 
```

--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/disputes/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {updateDispute} from '@/shared/disputes/updateDispute';
  2 | import {listDisputes} from '@/shared/disputes/listDisputes';
  3 | 
  4 | const Stripe = jest.fn().mockImplementation(() => ({
  5 |   disputes: {
  6 |     update: jest.fn(),
  7 |     list: jest.fn(),
  8 |   },
  9 | }));
 10 | 
 11 | let stripe: ReturnType<typeof Stripe>;
 12 | 
 13 | beforeEach(() => {
 14 |   stripe = new Stripe('fake-api-key');
 15 | });
 16 | 
 17 | describe('updateDispute', () => {
 18 |   it('should update a dispute and return the id', async () => {
 19 |     const params = {
 20 |       dispute: 'dp_123456',
 21 |       evidence: {
 22 |         uncategorized_text: 'Test product',
 23 |       },
 24 |       submit: true,
 25 |     };
 26 | 
 27 |     const context = {};
 28 | 
 29 |     const mockDispute = {id: 'dp_123456'};
 30 |     stripe.disputes.update.mockResolvedValue(mockDispute);
 31 | 
 32 |     const result = await updateDispute(stripe, context, params);
 33 | 
 34 |     expect(stripe.disputes.update).toHaveBeenCalledWith(
 35 |       params.dispute,
 36 |       {
 37 |         evidence: params.evidence,
 38 |         submit: params.submit,
 39 |       },
 40 |       undefined
 41 |     );
 42 |     expect(result).toEqual({id: mockDispute.id});
 43 |   });
 44 | 
 45 |   it('should specify the connected account if included in context', async () => {
 46 |     const params = {
 47 |       dispute: 'dp_123456',
 48 |       evidence: {
 49 |         uncategorized_text: 'Test product',
 50 |       },
 51 |       submit: true,
 52 |     };
 53 | 
 54 |     const context = {
 55 |       account: 'acct_123456',
 56 |     };
 57 | 
 58 |     const mockDispute = {id: 'dp_123456'};
 59 |     stripe.disputes.update.mockResolvedValue(mockDispute);
 60 | 
 61 |     const result = await updateDispute(stripe, context, params);
 62 | 
 63 |     expect(stripe.disputes.update).toHaveBeenCalledWith(
 64 |       params.dispute,
 65 |       {
 66 |         evidence: params.evidence,
 67 |         submit: params.submit,
 68 |       },
 69 |       {
 70 |         stripeAccount: context.account,
 71 |       }
 72 |     );
 73 |     expect(result).toEqual({id: mockDispute.id});
 74 |   });
 75 | });
 76 | 
 77 | describe('listDisputes', () => {
 78 |   it('should list disputes and return their ids', async () => {
 79 |     const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
 80 | 
 81 |     const context = {};
 82 | 
 83 |     stripe.disputes.list.mockResolvedValue({data: mockDisputes});
 84 |     const result = await listDisputes(stripe, context, {});
 85 | 
 86 |     expect(stripe.disputes.list).toHaveBeenCalledWith({}, undefined);
 87 |     expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
 88 |   });
 89 | 
 90 |   it('should specify the connected account if included in context', async () => {
 91 |     const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
 92 | 
 93 |     const context = {
 94 |       account: 'acct_123456',
 95 |     };
 96 | 
 97 |     stripe.disputes.list.mockResolvedValue({data: mockDisputes});
 98 |     const result = await listDisputes(stripe, context, {});
 99 | 
100 |     expect(stripe.disputes.list).toHaveBeenCalledWith(
101 |       {},
102 |       {
103 |         stripeAccount: context.account,
104 |       }
105 |     );
106 |     expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
107 |   });
108 | 
109 |   it('should pass through list parameters', async () => {
110 |     const params = {
111 |       charge: 'ch_123456',
112 |       payment_intent: 'pi_123456',
113 |       limit: 5,
114 |     };
115 | 
116 |     const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
117 | 
118 |     const context = {};
119 | 
120 |     stripe.disputes.list.mockResolvedValue({data: mockDisputes});
121 |     const result = await listDisputes(stripe, context, params);
122 | 
123 |     expect(stripe.disputes.list).toHaveBeenCalledWith(params, undefined);
124 |     expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
125 |   });
126 | });
127 | 
```

--------------------------------------------------------------------------------
/tools/typescript/src/shared/subscriptions/updateSubscription.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {StripeToolDefinition} from '@/shared/tools';
  5 | 
  6 | export const updateSubscriptionPrompt = (_context: Context = {}): string => {
  7 |   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.
  8 |   
  9 |   It takes the following arguments:
 10 |   - subscription (str, required): The ID of the subscription to update.
 11 |   - proration_behavior (str, optional): Determines how to handle prorations when the subscription items change. Options: 'create_prorations', 'none', 'always_invoice', 'none_implicit'.
 12 |   - items (array, optional): A list of subscription items to update, add, or remove. Each item can have the following properties:
 13 |     - id (str, optional): The ID of the subscription item to modify.
 14 |     - price (str, optional): The ID of the price to switch to.
 15 |     - quantity (int, optional): The quantity of the plan to subscribe to.
 16 |     - deleted (bool, optional): Whether to delete this item.
 17 |   `;
 18 | };
 19 | 
 20 | export const updateSubscription = async (
 21 |   stripe: Stripe,
 22 |   context: Context,
 23 |   params: z.infer<ReturnType<typeof updateSubscriptionParameters>>
 24 | ) => {
 25 |   try {
 26 |     const {subscription: subscriptionId, ...updateParams} = params;
 27 | 
 28 |     const subscription = await stripe.subscriptions.update(
 29 |       subscriptionId,
 30 |       updateParams,
 31 |       context.account ? {stripeAccount: context.account} : undefined
 32 |     );
 33 | 
 34 |     return subscription;
 35 |   } catch (error) {
 36 |     return 'Failed to update subscription';
 37 |   }
 38 | };
 39 | 
 40 | export const updateSubscriptionParameters = (
 41 |   _context: Context = {}
 42 | ): z.AnyZodObject => {
 43 |   return z.object({
 44 |     subscription: z.string().describe('The ID of the subscription to update.'),
 45 |     proration_behavior: z
 46 |       .enum(['create_prorations', 'none', 'always_invoice', 'none_implicit'])
 47 |       .optional()
 48 |       .describe(
 49 |         'Determines how to handle prorations when the subscription items change.'
 50 |       ),
 51 |     items: z
 52 |       .array(
 53 |         z.object({
 54 |           id: z
 55 |             .string()
 56 |             .optional()
 57 |             .describe('The ID of the subscription item to modify.'),
 58 |           price: z
 59 |             .string()
 60 |             .optional()
 61 |             .describe('The ID of the price to switch to.'),
 62 |           quantity: z
 63 |             .number()
 64 |             .int()
 65 |             .min(1)
 66 |             .optional()
 67 |             .describe('The quantity of the plan to subscribe to.'),
 68 |           deleted: z
 69 |             .boolean()
 70 |             .optional()
 71 |             .describe('Whether to delete this item.'),
 72 |         })
 73 |       )
 74 |       .optional()
 75 |       .describe('A list of subscription items to update, add, or remove.'),
 76 |   });
 77 | };
 78 | 
 79 | export const updateSubscriptionAnnotations = () => ({
 80 |   destructiveHint: false,
 81 |   idempotentHint: false,
 82 |   openWorldHint: true,
 83 |   readOnlyHint: false,
 84 |   title: 'Update subscription',
 85 | });
 86 | 
 87 | const tool = (context: Context): StripeToolDefinition => ({
 88 |   method: 'update_subscription',
 89 |   name: 'Update Subscription',
 90 |   description: updateSubscriptionPrompt(context),
 91 |   inputSchema: updateSubscriptionParameters(context),
 92 |   annotations: updateSubscriptionAnnotations(),
 93 |   actions: {
 94 |     subscriptions: {
 95 |       update: true,
 96 |     },
 97 |   },
 98 |   execute: updateSubscription,
 99 | });
100 | 
101 | export default tool;
102 | 
```

--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/prompts.py:
--------------------------------------------------------------------------------

```python
  1 | CREATE_CUSTOMER_PROMPT = """
  2 | This tool will create a customer in Stripe.
  3 | 
  4 | It takes two arguments:
  5 | - name (str): The name of the customer.
  6 | - email (str, optional): The email of the customer.
  7 | """
  8 | 
  9 | LIST_CUSTOMERS_PROMPT = """
 10 | This tool will fetch a list of Customers from Stripe.
 11 | 
 12 | It takes no input.
 13 | """
 14 | 
 15 | CREATE_PRODUCT_PROMPT = """
 16 | This tool will create a product in Stripe.
 17 | 
 18 | It takes two arguments:
 19 | - name (str): The name of the product.
 20 | - description (str, optional): The description of the product.
 21 | """
 22 | 
 23 | LIST_PRODUCTS_PROMPT = """
 24 | This tool will fetch a list of Products from Stripe.
 25 | 
 26 | It takes one optional argument:
 27 | - limit (int, optional): The number of products to return.
 28 | """
 29 | 
 30 | CREATE_PRICE_PROMPT = """
 31 | This tool will create a price in Stripe. If a product has not already been
 32 | specified, a product should be created first.
 33 | 
 34 | It takes three arguments:
 35 | - product (str): The ID of the product to create the price for.
 36 | - unit_amount (int): The unit amount of the price in cents.
 37 | - currency (str): The currency of the price.
 38 | """
 39 | 
 40 | LIST_PRICES_PROMPT = """
 41 | This tool will fetch a list of Prices from Stripe.
 42 | 
 43 | It takes two arguments:
 44 | - product (str, optional): The ID of the product to list prices for.
 45 | - limit (int, optional): The number of prices to return.
 46 | """
 47 | 
 48 | CREATE_PAYMENT_LINK_PROMPT = """
 49 | This tool will create a payment link in Stripe.
 50 | 
 51 | It takes two arguments:
 52 | - price (str): The ID of the price to create the payment link for.
 53 | - quantity (int): The quantity of the product to include in the payment link.
 54 | - redirect_url (string, optional): The URL the customer will be redirected to after the purchase is complete.
 55 | """
 56 | 
 57 | LIST_INVOICES_PROMPT = """
 58 | This tool will list invoices in Stripe.
 59 | 
 60 | It takes two arguments:
 61 | - customer (str, optional): The ID of the customer to list the invoices for.
 62 | - limit (int, optional): The number of prices to return.
 63 | """
 64 | 
 65 | CREATE_INVOICE_PROMPT = """
 66 | This tool will create an invoice in Stripe.
 67 | 
 68 | It takes one argument:
 69 | - customer (str): The ID of the customer to create the invoice for.
 70 | """
 71 | 
 72 | CREATE_INVOICE_ITEM_PROMPT = """
 73 | This tool will create an invoice item in Stripe.
 74 | 
 75 | It takes two arguments:
 76 | - customer (str): The ID of the customer to create the invoice item for.
 77 | - price (str): The ID of the price to create the invoice item for.
 78 | - invoice (str): The ID of the invoice to create the invoice item for.
 79 | """
 80 | 
 81 | FINALIZE_INVOICE_PROMPT = """
 82 | This tool will finalize an invoice in Stripe.
 83 | 
 84 | It takes one argument:
 85 | - invoice (str): The ID of the invoice to finalize.
 86 | """
 87 | 
 88 | RETRIEVE_BALANCE_PROMPT = """
 89 | This tool will retrieve the balance from Stripe. It takes no input.
 90 | """
 91 | 
 92 | CREATE_REFUND_PROMPT = """
 93 | This tool will refund a payment intent in Stripe.
 94 | 
 95 | It takes three arguments:
 96 | - payment_intent (str): The ID of the payment intent to refund.
 97 | - amount (int, optional): The amount to refund in cents.
 98 | - reason (str, optional): The reason for the refund.
 99 | """
100 | 
101 | LIST_PAYMENT_INTENTS_PROMPT = """
102 | This tool will list payment intents in Stripe.
103 | 
104 | It takes two arguments:
105 | - customer (str, optional): The ID of the customer to list payment intents for.
106 | - limit (int, optional): The number of payment intents to return.
107 | """
108 | 
109 | CREATE_BILLING_PORTAL_SESSION_PROMPT = """
110 | This tool will create a billing portal session.
111 | 
112 | It takes two arguments:
113 | - customer (str): The ID of the customer to create the invoice item for.
114 | - return_url (str, optional): The default URL to return to afterwards.
115 | """
116 | 
```

--------------------------------------------------------------------------------
/tools/typescript/examples/cloudflare/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import {z} from 'zod';
  3 | import {
  4 |   PaymentState,
  5 |   experimental_PaidMcpAgent as PaidMcpAgent,
  6 | } from '@stripe/agent-toolkit/cloudflare';
  7 | import {generateImage} from './imageGenerator';
  8 | import {OAuthProvider} from '@cloudflare/workers-oauth-provider';
  9 | import app from './app';
 10 | 
 11 | type Bindings = Env;
 12 | 
 13 | type Props = {
 14 |   userEmail: string;
 15 | };
 16 | 
 17 | type State = PaymentState & {};
 18 | 
 19 | export class MyMCP extends PaidMcpAgent<Bindings, State, Props> {
 20 |   server = new McpServer({
 21 |     name: 'Demo',
 22 |     version: '1.0.0',
 23 |   });
 24 | 
 25 |   initialState: State = {};
 26 | 
 27 |   async init() {
 28 |     this.server.tool('add', {a: z.number(), b: z.number()}, ({a, b}) => {
 29 |       return {
 30 |         content: [{type: 'text', text: `Result: ${a + b}`}],
 31 |       };
 32 |     });
 33 | 
 34 |     // One-time payment, then the tool is usable forever
 35 |     this.paidTool(
 36 |       'buy_premium',
 37 |       'Buy a premium account',
 38 |       {},
 39 |       () => {
 40 |         return {
 41 |           content: [{type: 'text', text: `You now have a premium account!`}],
 42 |         };
 43 |       },
 44 |       {
 45 |         checkout: {
 46 |           success_url: 'http://localhost:4242/payment/success',
 47 |           line_items: [
 48 |             {
 49 |               price: process.env.STRIPE_PRICE_ID_ONE_TIME_PAYMENT,
 50 |               quantity: 1,
 51 |             },
 52 |           ],
 53 |           mode: 'payment',
 54 |         },
 55 |         paymentReason:
 56 |           'Open the checkout link in the browser to buy a premium account.',
 57 |       }
 58 |     );
 59 | 
 60 |     // Subscription, then the tool is usable as long as the subscription is active
 61 |     this.paidTool(
 62 |       'big_add',
 63 |       'Add two numbers together',
 64 |       {
 65 |         a: z.number(),
 66 |         b: z.number(),
 67 |       },
 68 |       ({a, b}) => {
 69 |         return {
 70 |           content: [{type: 'text', text: `Result: ${a + b}`}],
 71 |         };
 72 |       },
 73 |       {
 74 |         checkout: {
 75 |           success_url: 'http://localhost:4242/payment/success',
 76 |           line_items: [
 77 |             {
 78 |               price: process.env.STRIPE_PRICE_ID_SUBSCRIPTION,
 79 |               quantity: 1,
 80 |             },
 81 |           ],
 82 |           mode: 'subscription',
 83 |         },
 84 |         paymentReason:
 85 |           'You must pay a subscription to add two big numbers together.',
 86 |       }
 87 |     );
 88 | 
 89 |     // Usage-based metered payments (Each tool call requires a payment)
 90 |     this.paidTool(
 91 |       'generate_emoji',
 92 |       'Generate an emoji given a single word (the `object` parameter describing the emoji)',
 93 |       {
 94 |         object: z.string().describe('one word'),
 95 |       },
 96 |       ({object}) => {
 97 |         return {
 98 |           content: [{type: 'text', text: generateImage(object)}],
 99 |         };
100 |       },
101 |       {
102 |         checkout: {
103 |           success_url: 'http://localhost:4242/payment/success',
104 |           line_items: [
105 |             {
106 |               price: process.env.STRIPE_PRICE_ID_USAGE_BASED_SUBSCRIPTION,
107 |             },
108 |           ],
109 |           mode: 'subscription',
110 |         },
111 |         meterEvent: 'image_generation',
112 |         paymentReason:
113 |           'You get 3 free generations, then we charge 10 cents per generation.',
114 |       }
115 |     );
116 |   }
117 | }
118 | 
119 | // Export the OAuth handler as the default
120 | export default new OAuthProvider({
121 |   apiRoute: '/sse',
122 |   apiHandlers: {
123 |     // @ts-ignore
124 |     '/sse': MyMCP.serveSSE('/sse'),
125 |     // @ts-ignore
126 |     '/mcp': MyMCP.serve('/mcp'),
127 |   },
128 |   // @ts-ignore
129 |   defaultHandler: app,
130 |   authorizeEndpoint: '/authorize',
131 |   tokenEndpoint: '/token',
132 |   clientRegistrationEndpoint: '/register',
133 | });
134 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/provider/stripe-provider.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Stripe AI SDK Provider
  3 |  * Integrates with Stripe's llm.stripe.com proxy for usage-based billing
  4 |  */
  5 | 
  6 | import {LanguageModelV2, ProviderV2} from '@ai-sdk/provider';
  7 | import {StripeLanguageModel} from './stripe-language-model';
  8 | import {
  9 |   StripeLanguageModelSettings,
 10 |   StripeProviderConfig,
 11 | } from './types';
 12 | import {normalizeModelId} from './utils';
 13 | 
 14 | /**
 15 |  * Stripe Provider interface
 16 |  */
 17 | export interface StripeProvider extends ProviderV2 {
 18 |   /**
 19 |    * Create a language model with the given model ID
 20 |    * @param modelId - Model ID in Stripe format (e.g., 'openai/gpt-5', 'google/gemini-2.5-pro')
 21 |    * @param settings - Optional settings for the model
 22 |    */
 23 |   (modelId: string, settings?: StripeLanguageModelSettings): LanguageModelV2;
 24 | 
 25 |   /**
 26 |    * Create a language model (alias for direct call)
 27 |    */
 28 |   languageModel(
 29 |     modelId: string,
 30 |     settings?: StripeLanguageModelSettings
 31 |   ): LanguageModelV2;
 32 | }
 33 | 
 34 | /**
 35 |  * Create a Stripe provider instance
 36 |  *
 37 |  * @param config - Provider configuration options
 38 |  * @returns Stripe provider instance
 39 |  *
 40 |  * @example
 41 |  * ```ts
 42 |  * import { createStripe } from '@stripe/ai-sdk/provider';
 43 |  *
 44 |  * const stripeLLM = createStripe({
 45 |  *   apiKey: process.env.STRIPE_API_KEY,
 46 |  *   customerId: 'cus_xxxxx' // Optional default customer ID
 47 |  * });
 48 |  *
 49 |  * const model = stripe('openai/gpt-5');
 50 |  * ```
 51 |  */
 52 | export function createStripe(config: StripeProviderConfig = {}): StripeProvider {
 53 |   const baseURL = config.baseURL || 'https://llm.stripe.com';
 54 | 
 55 |   const createModel = (
 56 |     modelId: string,
 57 |     settings: StripeLanguageModelSettings = {}
 58 |   ): LanguageModelV2 => {
 59 |     // Normalize the model ID to match Stripe's approved model list
 60 |     const normalizedModelId = normalizeModelId(modelId);
 61 | 
 62 |     // Merge provider-level and model-level customer IDs
 63 |     const mergedSettings: StripeLanguageModelSettings = {
 64 |       customerId: config.customerId,
 65 |       ...settings,
 66 |     };
 67 | 
 68 |     return new StripeLanguageModel(normalizedModelId, mergedSettings, {
 69 |       provider: 'stripe',
 70 |       baseURL,
 71 |       headers: () => {
 72 |         const apiKey = config.apiKey || process.env.STRIPE_API_KEY;
 73 | 
 74 |         if (!apiKey) {
 75 |           throw new Error(
 76 |             'Stripe API key is required. Provide it via config.apiKey or STRIPE_API_KEY environment variable.'
 77 |           );
 78 |         }
 79 | 
 80 |         return {
 81 |           'Content-Type': 'application/json',
 82 |           Authorization: `Bearer ${apiKey}`,
 83 |           'User-Agent': `Stripe/v1 @stripe/ai-sdk/provider/0.1.0`,
 84 |           ...config.headers,
 85 |         };
 86 |       },
 87 |     });
 88 |   };
 89 | 
 90 |   const provider = function (
 91 |     modelId: string,
 92 |     settings?: StripeLanguageModelSettings
 93 |   ) {
 94 |     if (new.target) {
 95 |       throw new Error(
 96 |         'The Stripe provider function cannot be called with the new keyword.'
 97 |       );
 98 |     }
 99 | 
100 |     return createModel(modelId, settings);
101 |   };
102 | 
103 |   provider.languageModel = createModel;
104 | 
105 |   // Placeholder implementations for other model types (not yet supported)
106 |   provider.textEmbeddingModel = () => {
107 |     throw new Error('Text embedding models are not yet supported by Stripe provider');
108 |   };
109 | 
110 |   provider.imageModel = () => {
111 |     throw new Error('Image models are not yet supported by Stripe provider');
112 |   };
113 | 
114 |   return provider as StripeProvider;
115 | }
116 | 
117 | /**
118 |  * Default Stripe provider instance
119 |  * Uses STRIPE_API_KEY environment variable for authentication
120 |  *
121 |  * @example
122 |  * ```ts
123 |  * import { stripe } from '@stripe/ai-sdk/provider';
124 |  *
125 |  * const model = stripe('openai/gpt-5', {
126 |  *   customerId: 'cus_xxxxx'
127 |  * });
128 |  * ```
129 |  */
130 | export const stripe = createStripe();
131 | 
132 | 
```

--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/strands/hooks.py:
--------------------------------------------------------------------------------

```python
 1 | from typing import Any, Optional, Dict
 2 | from ..api import StripeAPI
 3 | 
 4 | 
 5 | class BillingHooks:
 6 |     """Billing hooks for Strands framework to track usage and create meter events."""
 7 | 
 8 |     def __init__(
 9 |         self,
10 |         stripe: StripeAPI,
11 |         type: str,
12 |         customer: str,
13 |         meter: Optional[str] = None,
14 |         meters: Optional[Dict[str, str]] = None
15 |     ):
16 |         """
17 |         Initialize billing hooks.
18 | 
19 |         Args:
20 |             stripe: StripeAPI instance
21 |             type: Type of billing - "outcome" or "token"
22 |             customer: Customer ID for billing
23 |             meter: Single meter ID for outcome-based billing
24 |             meters: Dictionary of meter IDs for token-based billing (input/output)
25 |         """
26 |         self.type = type
27 |         self.stripe = stripe
28 |         self.customer = customer
29 |         self.meter = meter
30 |         self.meters = meters or {}
31 | 
32 |     def on_start(self, context: Any = None) -> None:
33 |         """Called when agent execution starts."""
34 |         pass
35 | 
36 |     def on_end(self, context: Any = None, output: Any = None, usage: Any = None) -> None:
37 |         """
38 |         Called when agent execution ends.
39 | 
40 |         Args:
41 |             context: Execution context (may contain usage information)
42 |             output: Agent output
43 |             usage: Usage information (tokens, etc.)
44 |         """
45 |         if self.type == "outcome":
46 |             # Create a single meter event for outcome-based billing
47 |             if self.meter:
48 |                 self.stripe.create_meter_event(self.meter, self.customer)
49 | 
50 |         elif self.type == "token":
51 |             # Create meter events for token-based billing
52 |             if usage:
53 |                 # Try to extract token usage from different possible formats
54 |                 input_tokens = self._extract_input_tokens(usage, context)
55 |                 output_tokens = self._extract_output_tokens(usage, context)
56 | 
57 |                 if input_tokens and self.meters.get("input"):
58 |                     self.stripe.create_meter_event(
59 |                         self.meters["input"],
60 |                         self.customer,
61 |                         str(input_tokens)
62 |                     )
63 | 
64 |                 if output_tokens and self.meters.get("output"):
65 |                     self.stripe.create_meter_event(
66 |                         self.meters["output"],
67 |                         self.customer,
68 |                         str(output_tokens)
69 |                     )
70 | 
71 |     def on_error(self, context: Any = None, error: Exception = None) -> None:
72 |         """Called when agent execution encounters an error."""
73 |         pass
74 | 
75 |     def _extract_input_tokens(self, usage: Any, context: Any = None) -> Optional[int]:
76 |         """Extract input token count from usage information."""
77 |         if hasattr(usage, 'input_tokens'):
78 |             return usage.input_tokens
79 |         elif isinstance(usage, dict):
80 |             return usage.get('input_tokens') or usage.get('prompt_tokens')
81 |         elif context and hasattr(context, 'usage') and hasattr(context.usage, 'input_tokens'):
82 |             return context.usage.input_tokens
83 |         return None
84 | 
85 |     def _extract_output_tokens(self, usage: Any, context: Any = None) -> Optional[int]:
86 |         """Extract output token count from usage information."""
87 |         if hasattr(usage, 'output_tokens'):
88 |             return usage.output_tokens
89 |         elif isinstance(usage, dict):
90 |             return usage.get('output_tokens') or usage.get('completion_tokens')
91 |         elif context and hasattr(context, 'usage') and hasattr(context.usage, 'output_tokens'):
92 |             return context.usage.output_tokens
93 |         return None
94 | 
```

--------------------------------------------------------------------------------
/tools/typescript/examples/cloudflare/src/app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/app.ts
  2 | 
  3 | import {Hono} from 'hono';
  4 | import {
  5 |   layout,
  6 |   homeContent,
  7 |   parseApproveFormBody,
  8 |   renderAuthorizationRejectedContent,
  9 |   renderAuthorizationApprovedContent,
 10 |   renderLoggedInAuthorizeScreen,
 11 |   renderLoggedOutAuthorizeScreen,
 12 |   renderPaymentSuccessContent,
 13 | } from './utils';
 14 | import type {OAuthHelpers} from '@cloudflare/workers-oauth-provider';
 15 | 
 16 | export type Bindings = Env & {
 17 |   OAUTH_PROVIDER: OAuthHelpers;
 18 | };
 19 | 
 20 | const app = new Hono<{
 21 |   Bindings: Bindings;
 22 | }>();
 23 | 
 24 | // Render a basic homepage placeholder to make sure the app is up
 25 | app.get('/', async (c) => {
 26 |   const content = await homeContent(c.req.raw);
 27 |   return c.html(layout(content, 'MCP Remote Auth Demo - Home'));
 28 | });
 29 | 
 30 | // Render an authorization page
 31 | // If the user is logged in, we'll show a form to approve the appropriate scopes
 32 | // If the user is not logged in, we'll show a form to both login and approve the scopes
 33 | app.get('/authorize', async (c) => {
 34 |   // We don't have an actual auth system, so to demonstrate both paths, you can
 35 |   // hard-code whether the user is logged in or not. We'll default to true
 36 |   // const isLoggedIn = false;
 37 |   const isLoggedIn = true;
 38 | 
 39 |   const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
 40 | 
 41 |   const oauthScopes = [
 42 |     {
 43 |       name: 'read_profile',
 44 |       description: 'Read your basic profile information',
 45 |     },
 46 |     {name: 'read_data', description: 'Access your stored data'},
 47 |     {name: 'write_data', description: 'Create and modify your data'},
 48 |   ];
 49 | 
 50 |   if (isLoggedIn) {
 51 |     const content = await renderLoggedInAuthorizeScreen(
 52 |       oauthScopes,
 53 |       oauthReqInfo
 54 |     );
 55 |     return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 56 |   }
 57 | 
 58 |   const content = await renderLoggedOutAuthorizeScreen(
 59 |     oauthScopes,
 60 |     oauthReqInfo
 61 |   );
 62 |   return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 63 | });
 64 | 
 65 | app.get('/payment/success', async (c) => {
 66 |   return c.html(
 67 |     layout(
 68 |       await renderPaymentSuccessContent(),
 69 |       'MCP Remote Auth Demo - Payment Success'
 70 |     )
 71 |   );
 72 | });
 73 | 
 74 | // The /authorize page has a form that will POST to /approve
 75 | // This endpoint is responsible for validating any login information and
 76 | // then completing the authorization request with the OAUTH_PROVIDER
 77 | app.post('/approve', async (c) => {
 78 |   const {action, oauthReqInfo, email, password} = await parseApproveFormBody(
 79 |     await c.req.parseBody()
 80 |   );
 81 | 
 82 |   if (!oauthReqInfo) {
 83 |     return c.html('INVALID LOGIN', 401);
 84 |   }
 85 | 
 86 |   // If the user needs to both login and approve, we should validate the login first
 87 |   if (action === 'login_approve') {
 88 |     // We'll allow any values for email and password for this demo
 89 |     // but you could validate them here
 90 |     // Ex:
 91 |     // if (email !== "[email protected]" || password !== "password") {
 92 |     // biome-ignore lint/correctness/noConstantCondition: This is a demo
 93 |     if (false) {
 94 |       return c.html(
 95 |         layout(
 96 |           await renderAuthorizationRejectedContent('/'),
 97 |           'MCP Remote Auth Demo - Authorization Status'
 98 |         )
 99 |       );
100 |     }
101 |   }
102 | 
103 |   // The user must be successfully logged in and have approved the scopes, so we
104 |   // can complete the authorization request
105 |   const {redirectTo} = await c.env.OAUTH_PROVIDER.completeAuthorization({
106 |     request: oauthReqInfo,
107 |     userId: email,
108 |     metadata: {
109 |       label: 'Test User',
110 |     },
111 |     scope: oauthReqInfo.scope,
112 |     props: {
113 |       // Here, you can send data to the MCP server
114 |       userEmail: email,
115 |     },
116 |   });
117 | 
118 |   // Store the redirect URL per email in KV somewhere
119 |   c.env.OAUTH_KV.put(email, redirectTo);
120 | 
121 |   return c.html(
122 |     layout(
123 |       await renderAuthorizationApprovedContent(redirectTo),
124 |       'MCP Remote Auth Demo - Authorization Status'
125 |     )
126 |   );
127 | });
128 | 
129 | export default app;
130 | 
```

--------------------------------------------------------------------------------
/tools/typescript/examples/cloudflare/src/oauth.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/app.ts
  2 | 
  3 | import {Hono} from 'hono';
  4 | import {
  5 |   layout,
  6 |   homeContent,
  7 |   parseApproveFormBody,
  8 |   renderAuthorizationRejectedContent,
  9 |   renderAuthorizationApprovedContent,
 10 |   renderLoggedInAuthorizeScreen,
 11 |   renderLoggedOutAuthorizeScreen,
 12 |   renderPaymentSuccessContent,
 13 | } from './utils';
 14 | import type {OAuthHelpers} from '@cloudflare/workers-oauth-provider';
 15 | 
 16 | export type Bindings = Env & {
 17 |   OAUTH_PROVIDER: OAuthHelpers;
 18 | };
 19 | 
 20 | const app = new Hono<{
 21 |   Bindings: Bindings;
 22 | }>();
 23 | 
 24 | // Render a basic homepage placeholder to make sure the app is up
 25 | app.get('/', async (c) => {
 26 |   const content = await homeContent(c.req.raw);
 27 |   return c.html(layout(content, 'MCP Remote Auth Demo - Home'));
 28 | });
 29 | 
 30 | // Render an authorization page
 31 | // If the user is logged in, we'll show a form to approve the appropriate scopes
 32 | // If the user is not logged in, we'll show a form to both login and approve the scopes
 33 | app.get('/authorize', async (c) => {
 34 |   // We don't have an actual auth system, so to demonstrate both paths, you can
 35 |   // hard-code whether the user is logged in or not. We'll default to true
 36 |   // const isLoggedIn = false;
 37 |   const isLoggedIn = true;
 38 | 
 39 |   const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
 40 | 
 41 |   const oauthScopes = [
 42 |     {
 43 |       name: 'read_profile',
 44 |       description: 'Read your basic profile information',
 45 |     },
 46 |     {name: 'read_data', description: 'Access your stored data'},
 47 |     {name: 'write_data', description: 'Create and modify your data'},
 48 |   ];
 49 | 
 50 |   if (isLoggedIn) {
 51 |     const content = await renderLoggedInAuthorizeScreen(
 52 |       oauthScopes,
 53 |       oauthReqInfo
 54 |     );
 55 |     return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 56 |   }
 57 | 
 58 |   const content = await renderLoggedOutAuthorizeScreen(
 59 |     oauthScopes,
 60 |     oauthReqInfo
 61 |   );
 62 |   return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 63 | });
 64 | 
 65 | app.get('/payment/success', async (c) => {
 66 |   return c.html(
 67 |     layout(
 68 |       await renderPaymentSuccessContent(),
 69 |       'MCP Remote Auth Demo - Payment Success'
 70 |     )
 71 |   );
 72 | });
 73 | 
 74 | // The /authorize page has a form that will POST to /approve
 75 | // This endpoint is responsible for validating any login information and
 76 | // then completing the authorization request with the OAUTH_PROVIDER
 77 | app.post('/approve', async (c) => {
 78 |   const {action, oauthReqInfo, email, password} = await parseApproveFormBody(
 79 |     await c.req.parseBody()
 80 |   );
 81 | 
 82 |   if (!oauthReqInfo) {
 83 |     return c.html('INVALID LOGIN', 401);
 84 |   }
 85 | 
 86 |   // If the user needs to both login and approve, we should validate the login first
 87 |   if (action === 'login_approve') {
 88 |     // We'll allow any values for email and password for this demo
 89 |     // but you could validate them here
 90 |     // Ex:
 91 |     // if (email !== "[email protected]" || password !== "password") {
 92 |     // biome-ignore lint/correctness/noConstantCondition: This is a demo
 93 |     if (false) {
 94 |       return c.html(
 95 |         layout(
 96 |           await renderAuthorizationRejectedContent('/'),
 97 |           'MCP Remote Auth Demo - Authorization Status'
 98 |         )
 99 |       );
100 |     }
101 |   }
102 | 
103 |   // The user must be successfully logged in and have approved the scopes, so we
104 |   // can complete the authorization request
105 |   const {redirectTo} = await c.env.OAUTH_PROVIDER.completeAuthorization({
106 |     request: oauthReqInfo,
107 |     userId: email,
108 |     metadata: {
109 |       label: 'Test User',
110 |     },
111 |     scope: oauthReqInfo.scope,
112 |     props: {
113 |       // Here, you can send data to the MCP server
114 |       userEmail: email,
115 |     },
116 |   });
117 | 
118 |   // Store the redirect URL per email in KV somewhere
119 |   c.env.OAUTH_KV.put(email, redirectTo);
120 | 
121 |   return c.html(
122 |     layout(
123 |       await renderAuthorizationApprovedContent(redirectTo),
124 |       'MCP Remote Auth Demo - Authorization Status'
125 |     )
126 |   );
127 | });
128 | 
129 | export default app;
130 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/provider/examples/openai.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Example: Using Stripe AI SDK Provider with OpenAI models
  3 |  *
  4 |  * This example demonstrates how to use the Stripe provider to interact with
  5 |  * OpenAI models through Stripe's llm.stripe.com proxy for automatic usage tracking.
  6 |  */
  7 | 
  8 | import {config} from 'dotenv';
  9 | import {resolve} from 'path';
 10 | import {generateText, streamText} from 'ai';
 11 | import {createStripe} from '..';
 12 | 
 13 | // Load .env from the examples folder
 14 | config({path: resolve(__dirname, '.env')});
 15 | 
 16 | async function main() {
 17 |   // Check environment variables
 18 |   if (!process.env.STRIPE_API_KEY) {
 19 |     throw new Error('STRIPE_API_KEY environment variable is not set. Please set it in examples/.env');
 20 |   }
 21 |   if (!process.env.STRIPE_CUSTOMER_ID) {
 22 |     console.warn('Warning: STRIPE_CUSTOMER_ID is not set. Some examples may fail.');
 23 |   }
 24 | 
 25 | 
 26 |   // Initialize the Stripe provider
 27 |   const stripeLLM = createStripe({
 28 |     apiKey: process.env.STRIPE_API_KEY!,
 29 |     customerId: process.env.STRIPE_CUSTOMER_ID, // Optional default customer ID
 30 |   });
 31 | 
 32 |   console.log('=== Example 1: Simple text generation with OpenAI GPT-5 ===\n');
 33 | 
 34 |   // Basic text generation
 35 |   const result1 = await generateText({
 36 |     model: stripeLLM('openai/gpt-5', {
 37 |       customerId: process.env.STRIPE_CUSTOMER_ID!, // Model-level customer ID
 38 |     }),
 39 |     prompt: 'What are the three primary colors?',
 40 |   });
 41 | 
 42 |   console.log('Response:', result1.text);
 43 |   console.log('Usage:', result1.usage);
 44 |   console.log('\n');
 45 | 
 46 |   console.log('=== Example 2: Streaming with GPT-4.1 ===\n');
 47 | 
 48 |   const streamPrompt = 'Explain how photosynthesis works in simple terms.';
 49 |   console.log(`Sending request with prompt: "${streamPrompt}"`);
 50 |   console.log(`Model: openai/gpt-4.1`);
 51 |   console.log(`Customer ID: ${process.env.STRIPE_CUSTOMER_ID}\n`);
 52 | 
 53 |   // Streaming response
 54 |   const result2 = await streamText({
 55 |     model: stripeLLM('openai/gpt-4.1'),
 56 |     prompt: streamPrompt,
 57 |     providerOptions: {
 58 |       stripe: {
 59 |         // Override customer ID for this specific call
 60 |         customerId: process.env.STRIPE_CUSTOMER_ID!,
 61 |       },
 62 |     },
 63 |   });
 64 | 
 65 |   console.log('Stream started, consuming chunks...');
 66 |   let chunkCount = 0;
 67 |   
 68 |   try {
 69 |     // Print streaming response using textStream (simple string chunks)
 70 |     for await (const chunk of result2.textStream) {
 71 |       chunkCount++;
 72 |       process.stdout.write(chunk);
 73 |     }
 74 |   } catch (streamError) {
 75 |     console.error('\n❌ Error during streaming:', streamError);
 76 |     throw streamError;
 77 |   }
 78 |   
 79 |   console.log(`\n\n(Received ${chunkCount} chunks)`);
 80 |   
 81 |   // Get usage and final text after stream completes
 82 |   const [finalText, usage] = await Promise.all([result2.text, result2.usage]);
 83 |   console.log('Final text length:', finalText.length);
 84 |   console.log('Usage:', usage);
 85 | 
 86 |   console.log('=== Example 3: Chat conversation with GPT-4.1-mini ===\n');
 87 | 
 88 |   // Multi-turn conversation
 89 |   const result3 = await generateText({
 90 |     model: stripeLLM('openai/gpt-4.1-mini', {
 91 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
 92 |     }),
 93 |     messages: [
 94 |       {role: 'user', content: 'What is the capital of France?'},
 95 |       {role: 'assistant', content: 'The capital of France is Paris.'},
 96 |       {role: 'user', content: 'What is its population?'},
 97 |     ],
 98 |   });
 99 | 
100 |   console.log('Response:', result3.text);
101 |   console.log('Usage:', result3.usage);
102 |   console.log('\n');
103 | 
104 |   console.log('=== Example 4: Using OpenAI o3 reasoning model ===\n');
105 | 
106 |   // Using reasoning model
107 |   const result4 = await generateText({
108 |     model: stripeLLM('openai/o3', {
109 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
110 |     }),
111 |     prompt:
112 |       'A farmer has 17 sheep. All but 9 die. How many sheep are left? Think through this step by step.',
113 |   });
114 | 
115 |   console.log('Response:', result4.text);
116 |   console.log('Usage:', result4.usage);
117 |   console.log('\n');
118 | 
119 |   console.log('=== All examples completed! ===');
120 | }
121 | 
122 | main().catch((error) => {
123 |   console.error('\n❌ Error occurred:');
124 |   console.error(error);
125 |   process.exit(1);
126 | });
127 | 
128 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/ai-sdk-billing-wrapper-anthropic.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for AI SDK billing wrapper with Anthropic
  3 |  * These tests mock Stripe meter events and verify meter events are sent correctly
  4 |  */
  5 | 
  6 | import Stripe from 'stripe';
  7 | import {anthropic} from '@ai-sdk/anthropic';
  8 | import {meteredModel} from '../index';
  9 | 
 10 | // Mock Stripe
 11 | jest.mock('stripe');
 12 | 
 13 | describe('AI SDK Billing Wrapper - Anthropic', () => {
 14 |   let mockMeterEventsCreate: jest.Mock;
 15 |   const TEST_API_KEY = 'sk_test_mock_key';
 16 | 
 17 |   beforeEach(() => {
 18 |     mockMeterEventsCreate = jest.fn().mockResolvedValue({});
 19 |     
 20 |     // Mock the Stripe constructor
 21 |     (Stripe as unknown as jest.Mock).mockImplementation(() => ({
 22 |       v2: {
 23 |         billing: {
 24 |           meterEvents: {
 25 |             create: mockMeterEventsCreate,
 26 |           },
 27 |         },
 28 |       },
 29 |     }));
 30 |   });
 31 | 
 32 |   afterEach(() => {
 33 |     jest.clearAllMocks();
 34 |   });
 35 | 
 36 |   it('should send meter events for doGenerate with Claude', async () => {
 37 |     const originalModel = anthropic('claude-3-5-haiku-20241022');
 38 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 39 | 
 40 |     jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
 41 |       text: 'Hello from Claude!',
 42 |       usage: {
 43 |         inputTokens: 12,
 44 |         outputTokens: 6,
 45 |       },
 46 |       finishReason: 'stop',
 47 |       rawResponse: {},
 48 |       warnings: [],
 49 |     } as any);
 50 | 
 51 |     await wrappedModel.doGenerate({
 52 |       inputFormat: 'prompt',
 53 |       mode: {type: 'regular'},
 54 |       prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
 55 |     } as any);
 56 | 
 57 |     // Wait for fire-and-forget logging to complete
 58 |     await new Promise(resolve => setImmediate(resolve));
 59 | 
 60 |     expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
 61 |     
 62 |     expect(mockMeterEventsCreate).toHaveBeenCalledWith(
 63 |       expect.objectContaining({
 64 |         event_name: 'token-billing-tokens',
 65 |         payload: expect.objectContaining({
 66 |           stripe_customer_id: 'cus_test123',
 67 |           value: '12',
 68 |           model: 'anthropic/claude-3.5-haiku',
 69 |           token_type: 'input',
 70 |         }),
 71 |       })
 72 |     );
 73 | 
 74 |     expect(mockMeterEventsCreate).toHaveBeenCalledWith(
 75 |       expect.objectContaining({
 76 |         payload: expect.objectContaining({
 77 |           value: '6',
 78 |           model: 'anthropic/claude-3.5-haiku',
 79 |           token_type: 'output',
 80 |         }),
 81 |       })
 82 |     );
 83 |   });
 84 | 
 85 |   it('should normalize Anthropic model names correctly', async () => {
 86 |     const testCases = [
 87 |       {input: 'claude-3-5-haiku-20241022', expected: 'anthropic/claude-3.5-haiku'},
 88 |       {input: 'claude-3-5-sonnet-20241022', expected: 'anthropic/claude-3.5-sonnet'},
 89 |       {input: 'claude-3-opus-20240229', expected: 'anthropic/claude-3-opus'},
 90 |       {input: 'claude-sonnet-4-latest', expected: 'anthropic/claude-sonnet-4'},
 91 |     ];
 92 | 
 93 |     for (const {input, expected} of testCases) {
 94 |       mockMeterEventsCreate.mockClear();
 95 |       
 96 |       const originalModel = anthropic(input as any);
 97 |       const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 98 | 
 99 |       jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
100 |         text: 'Test',
101 |         usage: {inputTokens: 5, outputTokens: 2},
102 |         finishReason: 'stop',
103 |         rawResponse: {},
104 |         warnings: [],
105 |       } as any);
106 | 
107 |       await wrappedModel.doGenerate({
108 |         inputFormat: 'prompt',
109 |         mode: {type: 'regular'},
110 |         prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
111 |       } as any);
112 | 
113 |       // Wait for fire-and-forget logging to complete
114 |       await new Promise(resolve => setImmediate(resolve));
115 | 
116 |       expect(mockMeterEventsCreate).toHaveBeenCalledWith(
117 |         expect.objectContaining({
118 |           payload: expect.objectContaining({
119 |             model: expected,
120 |           }),
121 |         })
122 |       );
123 |     }
124 |   });
125 | 
126 |   it('should preserve model properties', () => {
127 |     const originalModel = anthropic('claude-3-5-haiku-20241022');
128 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
129 | 
130 |     expect(wrappedModel.modelId).toBe(originalModel.modelId);
131 |     expect(wrappedModel.provider).toBe(originalModel.provider);
132 |     expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
133 |   });
134 | });
135 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/wrapperV2.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * AI SDK Model Wrapper (v2 specification)
  3 |  */
  4 | 
  5 | import Stripe from 'stripe';
  6 | import type {
  7 |   LanguageModelV2,
  8 |   LanguageModelV2CallOptions,
  9 |   LanguageModelV2StreamPart,
 10 | } from '@ai-sdk/provider';
 11 | import type {AIMeterConfig, AIUsageInfo} from './types';
 12 | import {determineProvider, extractUsageFromStream} from './utils';
 13 | import {logUsageEvent} from './meter-event-logging';
 14 | 
 15 | /**
 16 |  * Wrapper class for AI SDK v2 models that adds Stripe meter event tracking
 17 |  */
 18 | export class AISDKWrapperV2 implements LanguageModelV2 {
 19 |   private stripeClient: Stripe;
 20 | 
 21 |   constructor(
 22 |     private model: LanguageModelV2,
 23 |     private config: AIMeterConfig
 24 |   ) {
 25 |     // Construct Stripe client with the API key
 26 |     this.stripeClient = new Stripe(config.stripeApiKey, {
 27 |       appInfo: {
 28 |         name: '@stripe/ai-sdk/meter',
 29 |         version: '0.1.0',
 30 |       },
 31 |     });
 32 |   }
 33 | 
 34 |   /**
 35 |    * Wraps doGenerate to track usage with Stripe meter events
 36 |    */
 37 |   async doGenerate(options: LanguageModelV2CallOptions) {
 38 |     try {
 39 |       // Call the original doGenerate function
 40 |       const response = await this.model.doGenerate(options);
 41 | 
 42 |       // Extract usage information
 43 |       const usageInfo: AIUsageInfo = {
 44 |         provider: determineProvider(this.model.provider),
 45 |         model: this.model.modelId,
 46 |         inputTokens: response.usage?.inputTokens ?? 0,
 47 |         outputTokens: response.usage?.outputTokens ?? 0,
 48 |       };
 49 | 
 50 |       // Log to Stripe (fire-and-forget)
 51 |       this.logUsage(usageInfo);
 52 | 
 53 |       return response;
 54 |     } catch (error) {
 55 |       // Re-throw the error after logging
 56 |       console.error('[Stripe AI SDK] doGenerate failed:', error);
 57 |       throw error;
 58 |     }
 59 |   }
 60 | 
 61 |   /**
 62 |    * Wraps doStream to track usage with Stripe meter events
 63 |    */
 64 |   async doStream(options: LanguageModelV2CallOptions) {
 65 |     try {
 66 |       // Call the original doStream method
 67 |       const response = await this.model.doStream(options);
 68 | 
 69 |       // Collect chunks to extract usage at the end
 70 |       const chunks: LanguageModelV2StreamPart[] = [];
 71 |       const stream = new ReadableStream<LanguageModelV2StreamPart>({
 72 |         start: async (controller) => {
 73 |           try {
 74 |             const reader = response.stream.getReader();
 75 | 
 76 |             while (true) {
 77 |               const {done, value} = await reader.read();
 78 | 
 79 |               if (done) {
 80 |                 // Stream is done, now extract usage and log
 81 |                 const usage = extractUsageFromStream(chunks);
 82 |                 const usageInfo: AIUsageInfo = {
 83 |                   provider: determineProvider(this.model.provider),
 84 |                   model: this.model.modelId,
 85 |                   inputTokens: usage.inputTokens,
 86 |                   outputTokens: usage.outputTokens,
 87 |                 };
 88 | 
 89 |                 // Log to Stripe (fire-and-forget)
 90 |                 this.logUsage(usageInfo);
 91 | 
 92 |                 controller.close();
 93 |                 break;
 94 |               }
 95 | 
 96 |               // Collect chunk and pass it through
 97 |               chunks.push(value);
 98 |               controller.enqueue(value);
 99 |             }
100 |           } catch (error) {
101 |             controller.error(error);
102 |             console.error('[Stripe AI SDK] Stream processing failed:', error);
103 |           }
104 |         },
105 |       });
106 | 
107 |       // Return response with the wrapped stream
108 |       return {
109 |         ...response,
110 |         stream: stream,
111 |       };
112 |     } catch (error) {
113 |       console.error('[Stripe AI SDK] doStream failed:', error);
114 |       throw error;
115 |     }
116 |   }
117 | 
118 |   /**
119 |    * Helper method to log usage to Stripe
120 |    */
121 |   private logUsage(usageInfo: AIUsageInfo): void {
122 |     logUsageEvent(this.stripeClient, {}, {
123 |       model: usageInfo.model,
124 |       provider: usageInfo.provider,
125 |       usage: {
126 |         inputTokens: usageInfo.inputTokens,
127 |         outputTokens: usageInfo.outputTokens,
128 |       },
129 |       stripeCustomerId: this.config.stripeCustomerId,
130 |     });
131 |   }
132 | 
133 |   // Proxy all other properties from the original model
134 |   get modelId() {
135 |     return this.model.modelId;
136 |   }
137 | 
138 |   get provider() {
139 |     return this.model.provider;
140 |   }
141 | 
142 |   get specificationVersion() {
143 |     return this.model.specificationVersion;
144 |   }
145 | 
146 |   get supportedUrls() {
147 |     return this.model.supportedUrls;
148 |   }
149 | }
150 | 
151 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/provider/examples/anthropic.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Example: Using Stripe AI SDK Provider with Anthropic Claude models
  3 |  *
  4 |  * This example demonstrates how to use the Stripe provider to interact with
  5 |  * Anthropic Claude models through Stripe's llm.stripe.com proxy for automatic usage tracking.
  6 |  */
  7 | 
  8 | import {config} from 'dotenv';
  9 | import {resolve} from 'path';
 10 | import {generateText, streamText} from 'ai';
 11 | import {createStripe} from '..';
 12 | 
 13 | // Load .env from the examples folder
 14 | config({path: resolve(__dirname, '.env')});
 15 | 
 16 | async function main() {
 17 |   // Check environment variables
 18 |   if (!process.env.STRIPE_API_KEY) {
 19 |     throw new Error('STRIPE_API_KEY environment variable is not set. Please set it in examples/.env');
 20 |   }
 21 |   if (!process.env.STRIPE_CUSTOMER_ID) {
 22 |     throw new Error('STRIPE_CUSTOMER_ID environment variable is not set. Please set it in examples/.env');
 23 |   }
 24 | 
 25 | 
 26 |   // Initialize the Stripe provider
 27 |   const stripeLLM = createStripe({
 28 |     apiKey: process.env.STRIPE_API_KEY!,
 29 |     customerId: process.env.STRIPE_CUSTOMER_ID!, // Default customer ID
 30 |   });
 31 | 
 32 |   console.log('=== Example 1: Simple text generation with Claude Sonnet 4 ===\n');
 33 | 
 34 |   // Basic text generation
 35 |   const result1 = await generateText({
 36 |     model: stripeLLM('anthropic/claude-sonnet-4'),
 37 |     prompt: 'Explain quantum computing in simple terms.',
 38 |   });
 39 | 
 40 |   console.log('Response:', result1.text);
 41 |   console.log('Usage:', result1.usage);
 42 |   console.log('\n');
 43 | 
 44 |   console.log('=== Example 2: Streaming with Claude Opus 4 ===\n');
 45 | 
 46 |   // Streaming response with the most capable Claude model
 47 |   const result2 = await streamText({
 48 |     model: stripeLLM('anthropic/claude-opus-4', {
 49 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
 50 |     }),
 51 |     prompt: 'Write a poem about the ocean and its mysteries.',
 52 |   });
 53 | 
 54 |   // Print streaming response
 55 |   for await (const chunk of result2.textStream) {
 56 |     process.stdout.write(chunk);
 57 |   }
 58 |   console.log('\n\n');
 59 | 
 60 |   console.log('=== Example 3: Chat conversation with Claude 3.7 Sonnet ===\n');
 61 | 
 62 |   // Multi-turn conversation
 63 |   const result3 = await generateText({
 64 |     model: stripeLLM('anthropic/claude-3-7-sonnet', {
 65 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
 66 |     }),
 67 |     messages: [
 68 |       {role: 'user', content: 'What is the Turing test?'},
 69 |       {
 70 |         role: 'assistant',
 71 |         content:
 72 |           'The Turing test is a test of a machine\'s ability to exhibit intelligent behavior equivalent to, or indistinguishable from, that of a human.',
 73 |       },
 74 |       {role: 'user', content: 'Who created it?'},
 75 |     ],
 76 |   });
 77 | 
 78 |   console.log('Response:', result3.text);
 79 |   console.log('Usage:', result3.usage);
 80 |   console.log('\n');
 81 | 
 82 |   console.log('=== Example 4: Using Claude 3.5 Haiku for quick responses ===\n');
 83 | 
 84 |   // Using the fastest Claude model
 85 |   const result4 = await generateText({
 86 |     model: stripeLLM('anthropic/claude-3-5-haiku', {
 87 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
 88 |     }),
 89 |     prompt: 'What are the benefits of functional programming?',
 90 |     temperature: 0.5,
 91 |   });
 92 | 
 93 |   console.log('Response:', result4.text);
 94 |   console.log('Usage:', result4.usage);
 95 |   console.log('\n');
 96 | 
 97 |   console.log('=== Example 5: Complex reasoning with Claude Opus 4.1 ===\n');
 98 | 
 99 |   // Complex reasoning task
100 |   const result5 = await generateText({
101 |     model: stripeLLM('anthropic/claude-opus-4-1', {
102 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
103 |     }),
104 |     prompt:
105 |       'Design a simple algorithm to solve the traveling salesman problem. Explain your approach and its time complexity.',
106 |     temperature: 0.7,
107 |   });
108 | 
109 |   console.log('Response:', result5.text);
110 |   console.log('Usage:', result5.usage);
111 |   console.log('\n');
112 | 
113 |   console.log('=== Example 6: Per-call customer ID override ===\n');
114 | 
115 |   // Override customer ID for a specific call
116 |   const result7 = await generateText({
117 |     model: stripeLLM('anthropic/claude-3-haiku'),
118 |     prompt: 'What is the speed of light?',
119 |     providerOptions: {
120 |       stripe: {
121 |         customerId: 'cus_specific_customer', // Override the default customer ID
122 |       },
123 |     },
124 |   });
125 | 
126 |   console.log('Response:', result7.text);
127 |   console.log('Usage:', result7.usage);
128 |   console.log('\n');
129 | 
130 |   console.log('=== All examples completed! ===');
131 | }
132 | 
133 | main().catch((error) => {
134 |   console.error('\n❌ Error occurred:');
135 |   console.error(error);
136 |   process.exit(1);
137 | });
138 | 
139 | 
```

--------------------------------------------------------------------------------
/tools/modelcontextprotocol/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import {StripeAgentToolkit} from '@stripe/agent-toolkit/modelcontextprotocol';
  4 | import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
  5 | import {green, red, yellow} from 'colors';
  6 | 
  7 | type ToolkitConfig = {
  8 |   actions: {
  9 |     [product: string]: {[action: string]: boolean};
 10 |   };
 11 |   context?: {
 12 |     account?: string;
 13 |     mode: 'modelcontextprotocol';
 14 |   };
 15 | };
 16 | 
 17 | type Options = {
 18 |   tools?: string[];
 19 |   apiKey?: string;
 20 |   stripeAccount?: string;
 21 | };
 22 | 
 23 | const ACCEPTED_ARGS = ['api-key', 'tools', 'stripe-account'];
 24 | const ACCEPTED_TOOLS = [
 25 |   'coupons.create',
 26 |   'coupons.read',
 27 |   'customers.create',
 28 |   'customers.read',
 29 |   'products.create',
 30 |   'products.read',
 31 |   'prices.create',
 32 |   'prices.read',
 33 |   'paymentLinks.create',
 34 |   'invoices.create',
 35 |   'invoices.read',
 36 |   'invoices.update',
 37 |   'invoiceItems.create',
 38 |   'balance.read',
 39 |   'refunds.create',
 40 |   'paymentIntents.read',
 41 |   'subscriptions.read',
 42 |   'subscriptions.update',
 43 |   'disputes.read',
 44 |   'disputes.update',
 45 |   'documentation.read',
 46 | ];
 47 | 
 48 | export function parseArgs(args: string[]): Options {
 49 |   const options: Options = {};
 50 | 
 51 |   args.forEach((arg) => {
 52 |     if (arg.startsWith('--')) {
 53 |       const [key, value] = arg.slice(2).split('=');
 54 | 
 55 |       if (key == 'tools') {
 56 |         options.tools = value.split(',');
 57 |       } else if (key == 'api-key') {
 58 |         if (!value.startsWith('sk_') && !value.startsWith('rk_')) {
 59 |           throw new Error('API key must start with "sk_" or "rk_".');
 60 |         }
 61 |         options.apiKey = value;
 62 |       } else if (key == 'stripe-account') {
 63 |         // Validate api-key format
 64 |         if (!value.startsWith('acct_')) {
 65 |           throw new Error('Stripe account must start with "acct_".');
 66 |         }
 67 |         options.stripeAccount = value;
 68 |       } else {
 69 |         throw new Error(
 70 |           `Invalid argument: ${key}. Accepted arguments are: ${ACCEPTED_ARGS.join(
 71 |             ', '
 72 |           )}`
 73 |         );
 74 |       }
 75 |     }
 76 |   });
 77 | 
 78 |   // Check if required tools arguments is present
 79 |   if (!options.tools) {
 80 |     throw new Error('The --tools arguments must be provided.');
 81 |   }
 82 | 
 83 |   // Validate tools against accepted enum values
 84 |   options.tools.forEach((tool: string) => {
 85 |     if (tool == 'all') {
 86 |       return;
 87 |     }
 88 |     if (!ACCEPTED_TOOLS.includes(tool.trim())) {
 89 |       throw new Error(
 90 |         `Invalid tool: ${tool}. Accepted tools are: ${ACCEPTED_TOOLS.join(
 91 |           ', '
 92 |         )}`
 93 |       );
 94 |     }
 95 |   });
 96 | 
 97 |   // Check if API key is either provided in args or set in environment variables
 98 |   const apiKey = options.apiKey || process.env.STRIPE_SECRET_KEY;
 99 |   if (!apiKey) {
100 |     throw new Error(
101 |       'Stripe API key not provided. Please either pass it as an argument --api-key=$KEY or set the STRIPE_SECRET_KEY environment variable.'
102 |     );
103 |   }
104 |   options.apiKey = apiKey;
105 | 
106 |   return options;
107 | }
108 | 
109 | function handleError(error: any) {
110 |   console.error(red('\n🚨  Error initializing Stripe MCP server:\n'));
111 |   console.error(yellow(`   ${error.message}\n`));
112 | }
113 | 
114 | export async function main() {
115 |   const options = parseArgs(process.argv.slice(2));
116 | 
117 |   // Create the StripeAgentToolkit instance
118 |   const selectedTools = options.tools!;
119 |   const configuration: ToolkitConfig = {actions: {}};
120 | 
121 |   if (selectedTools.includes('all')) {
122 |     ACCEPTED_TOOLS.forEach((tool) => {
123 |       const [product, action] = tool.split('.');
124 |       configuration.actions[product] = {
125 |         ...configuration.actions[product],
126 |         [action]: true,
127 |       };
128 |     });
129 |   } else {
130 |     selectedTools.forEach((tool: any) => {
131 |       const [product, action] = tool.split('.');
132 |       configuration.actions[product] = {[action]: true};
133 |     });
134 |   }
135 | 
136 |   configuration.context = {
137 |     mode: 'modelcontextprotocol',
138 |   };
139 | 
140 |   // Append stripe account to configuration if provided
141 |   if (options.stripeAccount) {
142 |     configuration.context.account = options.stripeAccount;
143 |   }
144 | 
145 |   const server = new StripeAgentToolkit({
146 |     secretKey: options.apiKey!,
147 |     configuration: configuration,
148 |   });
149 | 
150 |   const transport = new StdioServerTransport();
151 |   await server.connect(transport);
152 |   // We use console.error instead of console.log since console.log will output to stdio, which will confuse the MCP server
153 |   console.error(green('✅ Stripe MCP Server running on stdio'));
154 | }
155 | 
156 | if (require.main === module) {
157 |   main().catch((error) => {
158 |     handleError(error);
159 |   });
160 | }
161 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/provider/examples/google.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Example: Using Stripe AI SDK Provider with Google Gemini models
  3 |  *
  4 |  * This example demonstrates how to use the Stripe provider to interact with
  5 |  * Google Gemini models through Stripe's llm.stripe.com proxy for automatic usage tracking.
  6 |  */
  7 | 
  8 | import {config} from 'dotenv';
  9 | import {resolve} from 'path';
 10 | import {generateText, streamText} from 'ai';
 11 | import {createStripe} from '..';
 12 | 
 13 | // Load .env from the examples folder
 14 | config({path: resolve(__dirname, '.env')});
 15 | 
 16 | async function main() {
 17 |   // Check environment variables
 18 |   if (!process.env.STRIPE_API_KEY) {
 19 |     throw new Error('STRIPE_API_KEY environment variable is not set. Please set it in examples/.env');
 20 |   }
 21 |   if (!process.env.STRIPE_CUSTOMER_ID) {
 22 |     throw new Error('STRIPE_CUSTOMER_ID environment variable is not set. Please set it in examples/.env');
 23 |   }
 24 | 
 25 | 
 26 |   // Initialize the Stripe provider
 27 |   const stripeLLM = createStripe({
 28 |     apiKey: process.env.STRIPE_API_KEY!,
 29 |     customerId: process.env.STRIPE_CUSTOMER_ID!, // Default customer ID
 30 |   });
 31 | 
 32 |   console.log('=== Example 1: Simple text generation with Gemini 2.5 Pro ===\n');
 33 | 
 34 |   // Basic text generation
 35 |   const result1 = await generateText({
 36 |     model: stripeLLM('google/gemini-2.5-pro'),
 37 |     prompt: 'What are the main differences between Python and JavaScript?',
 38 |   });
 39 | 
 40 |   console.log('Response:', result1.text);
 41 |   console.log('Usage:', result1.usage);
 42 |   console.log('\n');
 43 | 
 44 |   console.log('=== Example 2: Streaming with Gemini 2.5 Flash ===\n');
 45 | 
 46 |   // Streaming response with the faster Gemini model
 47 |   const result2 = await streamText({
 48 |     model: stripeLLM('google/gemini-2.5-flash', {
 49 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
 50 |     }),
 51 |     prompt: 'Write a short story about a robot learning to paint.',
 52 |   });
 53 | 
 54 |   // Print streaming response
 55 |   for await (const chunk of result2.textStream) {
 56 |     process.stdout.write(chunk);
 57 |   }
 58 |   console.log('\n\n');
 59 | 
 60 |   console.log('=== Example 3: Chat conversation with Gemini 2.0 Flash ===\n');
 61 | 
 62 |   // Multi-turn conversation
 63 |   const result3 = await generateText({
 64 |     model: stripeLLM('google/gemini-2.0-flash'),
 65 |     messages: [
 66 |       {role: 'user', content: 'What is machine learning?'},
 67 |       {
 68 |         role: 'assistant',
 69 |         content:
 70 |           'Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.',
 71 |       },
 72 |       {role: 'user', content: 'Can you give me an example?'},
 73 |     ],
 74 |     providerOptions: {
 75 |       stripe: {
 76 |         customerId: process.env.STRIPE_CUSTOMER_ID!,
 77 |       },
 78 |     },
 79 |   });
 80 | 
 81 |   console.log('Response:', result3.text);
 82 |   console.log('Usage:', result3.usage);
 83 |   console.log('\n');
 84 | 
 85 |   console.log('=== Example 4: Using Gemini 2.5 Flash Lite for quick responses ===\n');
 86 | 
 87 |   // Using the lite model for faster, cheaper responses
 88 |   const result4 = await generateText({
 89 |     model: stripeLLM('google/gemini-2.5-flash-lite', {
 90 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
 91 |     }),
 92 |     prompt: 'List 5 programming languages.',
 93 |     temperature: 0.3,
 94 |   });
 95 | 
 96 |   console.log('Response:', result4.text);
 97 |   console.log('Usage:', result4.usage);
 98 |   console.log('\n');
 99 | 
100 |   console.log('=== Example 5: Long-form content with Gemini 2.5 Pro ===\n');
101 | 
102 |   // Long-form content generation
103 |   const result5 = await generateText({
104 |     model: stripeLLM('google/gemini-2.5-pro', {
105 |       customerId: process.env.STRIPE_CUSTOMER_ID!,
106 |     }),
107 |     prompt:
108 |       'Write a detailed explanation of how neural networks work, suitable for beginners.',
109 |     temperature: 0.7,
110 |   });
111 | 
112 |   console.log('Response:', result5.text);
113 |   console.log('Usage:', result5.usage);
114 |   console.log('\n');
115 | 
116 |   console.log('=== Example 6: Streaming with custom headers ===\n');
117 | 
118 |   // Custom headers example (if needed for specific use cases)
119 |   const stripeWithHeaders = createStripe({
120 |     apiKey: process.env.STRIPE_API_KEY!,
121 |     customerId: process.env.STRIPE_CUSTOMER_ID!,
122 |     headers: {
123 |       'X-Custom-Header': 'custom-value',
124 |     },
125 |   });
126 | 
127 |   const result6 = await streamText({
128 |     model: stripeWithHeaders('google/gemini-2.5-flash'),
129 |     prompt: 'Count from 1 to 10.',
130 |   });
131 | 
132 |   for await (const chunk of result6.textStream) {
133 |     process.stdout.write(chunk);
134 |   }
135 |   console.log('\n\n');
136 | 
137 |   console.log('=== All examples completed! ===');
138 | }
139 | 
140 | main().catch((error) => {
141 |   console.error('\n❌ Error occurred:');
142 |   console.error(error);
143 |   process.exit(1);
144 | });
145 | 
146 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/ai-sdk-billing-wrapper-openai.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for AI SDK billing wrapper with OpenAI
  3 |  * These tests mock Stripe meter events and use jest spies to verify meter events are sent
  4 |  */
  5 | 
  6 | import Stripe from 'stripe';
  7 | import {openai} from '@ai-sdk/openai';
  8 | import {meteredModel} from '../index';
  9 | 
 10 | // Mock Stripe
 11 | jest.mock('stripe');
 12 | 
 13 | describe('AI SDK Billing Wrapper - OpenAI', () => {
 14 |   let mockMeterEventsCreate: jest.Mock;
 15 |   const TEST_API_KEY = 'sk_test_mock_key';
 16 | 
 17 |   beforeEach(() => {
 18 |     mockMeterEventsCreate = jest.fn().mockResolvedValue({});
 19 |     
 20 |     // Mock the Stripe constructor
 21 |     (Stripe as unknown as jest.Mock).mockImplementation(() => ({
 22 |       v2: {
 23 |         billing: {
 24 |           meterEvents: {
 25 |             create: mockMeterEventsCreate,
 26 |           },
 27 |         },
 28 |       },
 29 |     }));
 30 |   });
 31 | 
 32 |   afterEach(() => {
 33 |     jest.clearAllMocks();
 34 |   });
 35 | 
 36 |   it('should create wrapper that preserves model properties', () => {
 37 |     const originalModel = openai('gpt-4o-mini');
 38 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 39 | 
 40 |     expect(wrappedModel.modelId).toBe(originalModel.modelId);
 41 |     expect(wrappedModel.provider).toBe(originalModel.provider);
 42 |     expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
 43 |   });
 44 | 
 45 |   it('should wrap doGenerate method', async () => {
 46 |     const originalModel = openai('gpt-4o-mini');
 47 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 48 | 
 49 |     // Spy on the original doGenerate
 50 |     const mockDoGenerate = jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
 51 |       text: 'Test response',
 52 |       usage: {
 53 |         inputTokens: 10,
 54 |         outputTokens: 5,
 55 |       },
 56 |       finishReason: 'stop',
 57 |       rawResponse: {},
 58 |       warnings: [],
 59 |     } as any);
 60 | 
 61 |     // Call the wrapped doGenerate
 62 |     const result = await wrappedModel.doGenerate({
 63 |       inputFormat: 'prompt',
 64 |       mode: {type: 'regular'},
 65 |       prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
 66 |     } as any);
 67 | 
 68 |     expect(mockDoGenerate).toHaveBeenCalled();
 69 |     expect((result as any).text).toBe('Test response');
 70 |     
 71 |     // Wait for fire-and-forget logging to complete
 72 |     await new Promise(resolve => setImmediate(resolve));
 73 |     
 74 |     // Verify meter events were created
 75 |     expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
 76 |     expect(mockMeterEventsCreate).toHaveBeenCalledWith(
 77 |       expect.objectContaining({
 78 |         event_name: 'token-billing-tokens',
 79 |         payload: expect.objectContaining({
 80 |           stripe_customer_id: 'cus_test123',
 81 |           value: '10',
 82 |           model: 'openai/gpt-4o-mini',
 83 |           token_type: 'input',
 84 |         }),
 85 |       })
 86 |     );
 87 |   });
 88 | 
 89 |   it('should normalize OpenAI model names', async () => {
 90 |     const originalModel = openai('gpt-4-turbo-2024-04-09');
 91 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 92 | 
 93 |     jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
 94 |       text: 'Test',
 95 |       usage: {inputTokens: 5, outputTokens: 2},
 96 |       finishReason: 'stop',
 97 |       rawResponse: {},
 98 |       warnings: [],
 99 |     } as any);
100 | 
101 |     await wrappedModel.doGenerate({
102 |       inputFormat: 'prompt',
103 |       mode: {type: 'regular'},
104 |       prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
105 |     } as any);
106 | 
107 |     // Wait for fire-and-forget logging to complete
108 |     await new Promise(resolve => setImmediate(resolve));
109 | 
110 |     // Verify model name was normalized (date suffix removed)
111 |     expect(mockMeterEventsCreate).toHaveBeenCalledWith(
112 |       expect.objectContaining({
113 |         payload: expect.objectContaining({
114 |           model: 'openai/gpt-4-turbo',
115 |         }),
116 |       })
117 |     );
118 |   });
119 | 
120 |   it('should handle missing usage gracefully', async () => {
121 |     const originalModel = openai('gpt-4o-mini');
122 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
123 | 
124 |     jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
125 |       text: 'Test',
126 |       usage: undefined,
127 |       finishReason: 'stop',
128 |       rawResponse: {},
129 |       warnings: [],
130 |     } as any);
131 | 
132 |     await wrappedModel.doGenerate({
133 |       inputFormat: 'prompt',
134 |       mode: {type: 'regular'},
135 |       prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
136 |     } as any);
137 | 
138 |     // Wait for fire-and-forget logging to complete
139 |     await new Promise(resolve => setImmediate(resolve));
140 | 
141 |     // Should not create events with 0 tokens (code only sends when > 0)
142 |     expect(mockMeterEventsCreate).toHaveBeenCalledTimes(0);
143 |   });
144 | });
145 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/examples/anthropic.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Sample Usage: Vercel AI SDK with Anthropic and Stripe Billing
  3 |  * 
  4 |  * This demonstrates how to use the meteredModel wrapper to automatically
  5 |  * report token usage to Stripe for billing purposes when using Anthropic via the Vercel AI SDK.
  6 |  */
  7 | 
  8 | import {config} from 'dotenv';
  9 | import {resolve} from 'path';
 10 | import {anthropic} from '@ai-sdk/anthropic';
 11 | import {generateText, streamText} from 'ai';
 12 | import {meteredModel} from '..';
 13 | 
 14 | config({path: resolve(__dirname, '.env')});
 15 | 
 16 | // Load environment variables from .env file
 17 | const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
 18 | const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
 19 | const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY!;
 20 | 
 21 | if (!STRIPE_API_KEY || !STRIPE_CUSTOMER_ID || !ANTHROPIC_API_KEY) {
 22 |   throw new Error(
 23 |     'STRIPE_API_KEY, STRIPE_CUSTOMER_ID, and ANTHROPIC_API_KEY environment variables are required'
 24 |   );
 25 | }
 26 | 
 27 | // Sample 1: Basic generateText with Claude
 28 | async function sampleBasicGenerateText() {
 29 |   console.log('\n=== Sample 1: Basic generateText with Claude ===');
 30 | 
 31 |   const model = meteredModel(
 32 |     anthropic('claude-3-5-sonnet-20241022'),
 33 |     STRIPE_API_KEY,
 34 |     STRIPE_CUSTOMER_ID
 35 |   );
 36 | 
 37 |   const {text} = await generateText({
 38 |     model: model,
 39 |     prompt: 'Say "Hello, World!" and nothing else.',
 40 |   });
 41 | 
 42 |   console.log('Response:', text);
 43 | }
 44 | 
 45 | // Sample 2: Streaming text with Claude
 46 | async function sampleStreamText() {
 47 |   console.log('\n=== Sample 2: Stream Text with Claude ===');
 48 | 
 49 |   const model = meteredModel(
 50 |     anthropic('claude-3-5-sonnet-20241022'),
 51 |     STRIPE_API_KEY,
 52 |     STRIPE_CUSTOMER_ID
 53 |   );
 54 | 
 55 |   const result = streamText({
 56 |     model: model,
 57 |     prompt: 'Count from 1 to 5, one number per line.',
 58 |   });
 59 | 
 60 |   let fullText = '';
 61 |   for await (const chunk of result.textStream) {
 62 |     process.stdout.write(chunk);
 63 |     fullText += chunk;
 64 |   }
 65 | 
 66 |   console.log('\n\nFull text:', fullText);
 67 | }
 68 | 
 69 | // Sample 3: With system message
 70 | async function sampleWithSystemMessage() {
 71 |   console.log('\n=== Sample 3: With System Message ===');
 72 | 
 73 |   const model = meteredModel(
 74 |     anthropic('claude-3-5-sonnet-20241022'),
 75 |     STRIPE_API_KEY,
 76 |     STRIPE_CUSTOMER_ID
 77 |   );
 78 | 
 79 |   const {text} = await generateText({
 80 |     model: model,
 81 |     system: 'You are a helpful assistant that is concise and to the point.',
 82 |     prompt: 'What is the capital of France?',
 83 |   });
 84 | 
 85 |   console.log('Response:', text);
 86 | }
 87 | 
 88 | // Sample 4: Multi-turn conversation
 89 | async function sampleConversation() {
 90 |   console.log('\n=== Sample 4: Multi-turn Conversation ===');
 91 | 
 92 |   const model = meteredModel(
 93 |     anthropic('claude-3-5-sonnet-20241022'),
 94 |     STRIPE_API_KEY,
 95 |     STRIPE_CUSTOMER_ID
 96 |   );
 97 | 
 98 |   const {text} = await generateText({
 99 |     model: model,
100 |     messages: [
101 |       {role: 'user', content: 'Who is Sachin Tendulkar?'},
102 |       {
103 |         role: 'assistant',
104 |         content: 'Sachin Tendulkar is a legendary Indian cricketer.',
105 |       },
106 |       {role: 'user', content: 'What are his major achievements?'},
107 |     ],
108 |   });
109 | 
110 |   console.log('Response:', text);
111 | }
112 | 
113 | // Sample 5: Using Claude Haiku (faster, cheaper model)
114 | async function sampleClaudeHaiku() {
115 |   console.log('\n=== Sample 5: Using Claude Haiku ===');
116 | 
117 |   const model = meteredModel(
118 |     anthropic('claude-3-5-haiku-20241022'),
119 |     STRIPE_API_KEY,
120 |     STRIPE_CUSTOMER_ID
121 |   );
122 | 
123 |   const {text} = await generateText({
124 |     model: model,
125 |     prompt: 'Explain quantum computing in one sentence.',
126 |   });
127 | 
128 |   console.log('Response:', text);
129 | }
130 | 
131 | // Sample 6: Stream with max tokens
132 | async function sampleStreamWithMaxTokens() {
133 |   console.log('\n=== Sample 6: Stream with Max Tokens ===');
134 | 
135 |   const model = meteredModel(
136 |     anthropic('claude-3-5-sonnet-20241022'),
137 |     STRIPE_API_KEY,
138 |     STRIPE_CUSTOMER_ID
139 |   );
140 | 
141 |   const result = streamText({
142 |     model: model,
143 |     prompt: 'Write a short story about a robot.',
144 |   });
145 | 
146 |   for await (const chunk of result.textStream) {
147 |     process.stdout.write(chunk);
148 |   }
149 | 
150 |   console.log('\n');
151 | }
152 | 
153 | // Run all samples
154 | async function runAllSamples() {
155 |   console.log('Starting Vercel AI SDK + Anthropic + Stripe Metering Examples');
156 |   console.log(
157 |     'These examples show how to use meteredModel with Anthropic models\n'
158 |   );
159 | 
160 |   try {
161 |     await sampleBasicGenerateText();
162 |     await sampleStreamText();
163 |     await sampleWithSystemMessage();
164 |     await sampleConversation();
165 |     await sampleClaudeHaiku();
166 |     await sampleStreamWithMaxTokens();
167 | 
168 |     console.log('\n' + '='.repeat(80));
169 |     console.log('All examples completed successfully!');
170 |     console.log('='.repeat(80));
171 |   } catch (error) {
172 |     console.error('\n❌ Sample failed:', error);
173 |     throw error;
174 |   }
175 | }
176 | 
177 | // Run the samples
178 | runAllSamples().catch(console.error);
179 | 
180 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/examples/google.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Sample Usage: Vercel AI SDK with Google Gemini and Stripe Billing
  3 |  * 
  4 |  * This demonstrates how to use the meteredModel wrapper to automatically
  5 |  * report token usage to Stripe for billing purposes when using Google's Gemini via the Vercel AI SDK.
  6 |  */
  7 | 
  8 | import {config} from 'dotenv';
  9 | import {resolve} from 'path';
 10 | import {google} from '@ai-sdk/google';
 11 | import {generateText, streamText} from 'ai';
 12 | import {meteredModel} from '..';
 13 | 
 14 | // Load .env from the examples folder
 15 | config({path: resolve(__dirname, '.env')});
 16 | 
 17 | // Load environment variables from .env file
 18 | const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
 19 | const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
 20 | const GOOGLE_GENERATIVE_AI_API_KEY =
 21 |   process.env.GOOGLE_GENERATIVE_AI_API_KEY!;
 22 | 
 23 | if (
 24 |   !STRIPE_API_KEY ||
 25 |   !STRIPE_CUSTOMER_ID ||
 26 |   !GOOGLE_GENERATIVE_AI_API_KEY
 27 | ) {
 28 |   throw new Error(
 29 |     'STRIPE_API_KEY, STRIPE_CUSTOMER_ID, and GOOGLE_GENERATIVE_AI_API_KEY environment variables are required'
 30 |   );
 31 | }
 32 | 
 33 | // Sample 1: Basic generateText with Gemini
 34 | async function sampleBasicGenerateText() {
 35 |   console.log('\n=== Sample 1: Basic generateText with Gemini ===');
 36 | 
 37 |   const model = meteredModel(
 38 |     google('gemini-2.5-flash'),
 39 |     STRIPE_API_KEY,
 40 |     STRIPE_CUSTOMER_ID
 41 |   );
 42 | 
 43 |   const {text} = await generateText({
 44 |     model: model,
 45 |     prompt: 'Say "Hello, World!" and nothing else.',
 46 |   });
 47 | 
 48 |   console.log('Response:', text);
 49 | }
 50 | 
 51 | // Sample 2: Streaming text with Gemini
 52 | async function sampleStreamText() {
 53 |   console.log('\n=== Sample 2: Stream Text with Gemini ===');
 54 | 
 55 |   const model = meteredModel(
 56 |     google('gemini-2.5-flash'),
 57 |     STRIPE_API_KEY,
 58 |     STRIPE_CUSTOMER_ID
 59 |   );
 60 | 
 61 |   const result = streamText({
 62 |     model: model,
 63 |     prompt: 'Count from 1 to 5, one number per line.',
 64 |   });
 65 | 
 66 |   let fullText = '';
 67 |   for await (const chunk of result.textStream) {
 68 |     process.stdout.write(chunk);
 69 |     fullText += chunk;
 70 |   }
 71 | 
 72 |   console.log('\n\nFull text:', fullText);
 73 | }
 74 | 
 75 | // Sample 3: With system message
 76 | async function sampleWithSystemMessage() {
 77 |   console.log('\n=== Sample 3: With System Message ===');
 78 | 
 79 |   const model = meteredModel(
 80 |     google('gemini-2.5-flash'),
 81 |     STRIPE_API_KEY,
 82 |     STRIPE_CUSTOMER_ID
 83 |   );
 84 | 
 85 |   const {text} = await generateText({
 86 |     model: model,
 87 |     system: 'You are a helpful assistant that explains things simply.',
 88 |     prompt: 'What is photosynthesis?',
 89 |   });
 90 | 
 91 |   console.log('Response:', text);
 92 | }
 93 | 
 94 | // Sample 4: Multi-turn conversation
 95 | async function sampleConversation() {
 96 |   console.log('\n=== Sample 4: Multi-turn Conversation ===');
 97 | 
 98 |   const model = meteredModel(
 99 |     google('gemini-2.5-flash'),
100 |     STRIPE_API_KEY,
101 |     STRIPE_CUSTOMER_ID
102 |   );
103 | 
104 |   const {text} = await generateText({
105 |     model: model,
106 |     messages: [
107 |       {role: 'user', content: 'What is 10 + 15?'},
108 |       {role: 'assistant', content: '10 + 15 equals 25.'},
109 |       {role: 'user', content: 'And if I multiply that by 2?'},
110 |     ],
111 |   });
112 | 
113 |   console.log('Response:', text);
114 | }
115 | 
116 | // Sample 5: Longer response
117 | async function sampleLongerResponse() {
118 |   console.log('\n=== Sample 5: Longer Response ===');
119 | 
120 |   const model = meteredModel(
121 |     google('gemini-2.5-flash'),
122 |     STRIPE_API_KEY,
123 |     STRIPE_CUSTOMER_ID
124 |   );
125 | 
126 |   const {text} = await generateText({
127 |     model: model,
128 |     prompt: 'Explain the theory of relativity in simple terms.',
129 |   });
130 | 
131 |   console.log('Response:', text);
132 | }
133 | 
134 | // Sample 6: Stream with temperature control
135 | async function sampleStreamWithTemperature() {
136 |   console.log('\n=== Sample 6: Stream with Temperature Control ===');
137 | 
138 |   const model = meteredModel(
139 |     google('gemini-2.5-flash'),
140 |     STRIPE_API_KEY,
141 |     STRIPE_CUSTOMER_ID
142 |   );
143 | 
144 |   const result = streamText({
145 |     model: model,
146 |     prompt: 'Write a creative short story opener.',
147 |     temperature: 0.9, // Higher temperature for more creativity
148 |   });
149 | 
150 |   for await (const chunk of result.textStream) {
151 |     process.stdout.write(chunk);
152 |   }
153 | 
154 |   console.log('\n');
155 | }
156 | 
157 | // Run all samples
158 | async function runAllSamples() {
159 |   console.log(
160 |     'Starting Vercel AI SDK + Google Gemini + Stripe Metering Examples'
161 |   );
162 |   console.log(
163 |     'These examples show how to use meteredModel with Google Gemini models\n'
164 |   );
165 | 
166 |   try {
167 |     await sampleBasicGenerateText();
168 |     await sampleStreamText();
169 |     await sampleWithSystemMessage();
170 |     await sampleConversation();
171 |     await sampleLongerResponse();
172 |     await sampleStreamWithTemperature();
173 | 
174 |     console.log('\n' + '='.repeat(80));
175 |     console.log('All examples completed successfully!');
176 |     console.log('='.repeat(80));
177 |   } catch (error) {
178 |     console.error('\n❌ Sample failed:', error);
179 |     throw error;
180 |   }
181 | }
182 | 
183 | // Run the samples
184 | runAllSamples().catch(console.error);
185 | 
186 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/ai-sdk-billing-wrapper-google.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for AI SDK billing wrapper with Google Gemini
  3 |  * These tests mock Stripe meter events and verify meter events are sent correctly
  4 |  */
  5 | 
  6 | import Stripe from 'stripe';
  7 | import {google} from '@ai-sdk/google';
  8 | import {meteredModel} from '../index';
  9 | 
 10 | // Mock Stripe
 11 | jest.mock('stripe');
 12 | 
 13 | describe('AI SDK Billing Wrapper - Google Gemini', () => {
 14 |   let mockMeterEventsCreate: jest.Mock;
 15 |   const TEST_API_KEY = 'sk_test_mock_key';
 16 | 
 17 |   beforeEach(() => {
 18 |     mockMeterEventsCreate = jest.fn().mockResolvedValue({});
 19 |     
 20 |     // Mock the Stripe constructor
 21 |     (Stripe as unknown as jest.Mock).mockImplementation(() => ({
 22 |       v2: {
 23 |         billing: {
 24 |           meterEvents: {
 25 |             create: mockMeterEventsCreate,
 26 |           },
 27 |         },
 28 |       },
 29 |     }));
 30 |   });
 31 | 
 32 |   afterEach(() => {
 33 |     jest.clearAllMocks();
 34 |   });
 35 | 
 36 |   it('should send meter events for doGenerate with Gemini', async () => {
 37 |     const originalModel = google('gemini-2.5-flash');
 38 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 39 | 
 40 |     jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
 41 |       text: 'Hello from Gemini!',
 42 |       usage: {
 43 |         inputTokens: 15,
 44 |         outputTokens: 7,
 45 |       },
 46 |       finishReason: 'stop',
 47 |       rawResponse: {},
 48 |       warnings: [],
 49 |     } as any);
 50 | 
 51 |     await wrappedModel.doGenerate({
 52 |       inputFormat: 'prompt',
 53 |       mode: {type: 'regular'},
 54 |       prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
 55 |     } as any);
 56 | 
 57 |     // Wait for fire-and-forget logging to complete
 58 |     await new Promise(resolve => setImmediate(resolve));
 59 | 
 60 |     expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
 61 |     
 62 |     expect(mockMeterEventsCreate).toHaveBeenCalledWith(
 63 |       expect.objectContaining({
 64 |         event_name: 'token-billing-tokens',
 65 |         payload: expect.objectContaining({
 66 |           stripe_customer_id: 'cus_test123',
 67 |           value: '15',
 68 |           model: 'google/gemini-2.5-flash',
 69 |           token_type: 'input',
 70 |         }),
 71 |       })
 72 |     );
 73 | 
 74 |     expect(mockMeterEventsCreate).toHaveBeenCalledWith(
 75 |       expect.objectContaining({
 76 |         payload: expect.objectContaining({
 77 |           value: '7',
 78 |           model: 'google/gemini-2.5-flash',
 79 |           token_type: 'output',
 80 |         }),
 81 |       })
 82 |     );
 83 |   });
 84 | 
 85 |   it('should handle different Gemini model variants', async () => {
 86 |     const models = [
 87 |       'gemini-2.5-flash',
 88 |       'gemini-2.5-pro',
 89 |       'gemini-2.0-flash-exp',
 90 |       'gemini-1.5-pro',
 91 |     ];
 92 | 
 93 |     for (const modelName of models) {
 94 |       mockMeterEventsCreate.mockClear();
 95 |       
 96 |       const originalModel = google(modelName as any);
 97 |       const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
 98 | 
 99 |       jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
100 |         text: 'Test',
101 |         usage: {inputTokens: 5, outputTokens: 2},
102 |         finishReason: 'stop',
103 |         rawResponse: {},
104 |         warnings: [],
105 |       } as any);
106 | 
107 |       await wrappedModel.doGenerate({
108 |         inputFormat: 'prompt',
109 |         mode: {type: 'regular'},
110 |         prompt: [{role: 'user', content: [{type: 'text', text: 'Test'}]}],
111 |       } as any);
112 | 
113 |       // Wait for fire-and-forget logging to complete
114 |       await new Promise(resolve => setImmediate(resolve));
115 | 
116 |       expect(mockMeterEventsCreate).toHaveBeenCalledWith(
117 |         expect.objectContaining({
118 |           payload: expect.objectContaining({
119 |             model: `google/${modelName}`,
120 |           }),
121 |         })
122 |       );
123 |     }
124 |   });
125 | 
126 |   it('should preserve model properties', () => {
127 |     const originalModel = google('gemini-2.5-flash');
128 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
129 | 
130 |     expect(wrappedModel.modelId).toBe(originalModel.modelId);
131 |     expect(wrappedModel.provider).toBe(originalModel.provider);
132 |     expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
133 |   });
134 | 
135 |   it('should handle zero token usage', async () => {
136 |     const originalModel = google('gemini-2.5-flash');
137 |     const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');
138 | 
139 |     jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
140 |       text: '',
141 |       usage: {inputTokens: 0, outputTokens: 0},
142 |       finishReason: 'stop',
143 |       rawResponse: {},
144 |       warnings: [],
145 |     } as any);
146 | 
147 |     await wrappedModel.doGenerate({
148 |       inputFormat: 'prompt',
149 |       mode: {type: 'regular'},
150 |       prompt: [{role: 'user', content: [{type: 'text', text: ''}]}],
151 |     } as any);
152 | 
153 |     // Wait for fire-and-forget logging to complete
154 |     await new Promise(resolve => setImmediate(resolve));
155 | 
156 |     // Should not create events with zero tokens (code only sends when > 0)
157 |     expect(mockMeterEventsCreate).toHaveBeenCalledTimes(0);
158 |   });
159 | });
160 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/examples/openai.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Sample Usage: Vercel AI SDK with OpenAI and Stripe Billing
  3 |  * 
  4 |  * This demonstrates how to use the meteredModel wrapper to automatically
  5 |  * report token usage to Stripe for billing purposes when using the Vercel AI SDK.
  6 |  */
  7 | 
  8 | import {config} from 'dotenv';
  9 | import {resolve} from 'path';
 10 | import {openai} from '@ai-sdk/openai';
 11 | import {generateText, streamText} from 'ai';
 12 | import {meteredModel} from '..';
 13 | 
 14 | config({path: resolve(__dirname, '.env')});
 15 | 
 16 | // Load environment variables from .env file
 17 | const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
 18 | const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
 19 | const OPENAI_API_KEY = process.env.OPENAI_API_KEY!;
 20 | 
 21 | if (!STRIPE_API_KEY || !STRIPE_CUSTOMER_ID || !OPENAI_API_KEY) {
 22 |   throw new Error(
 23 |     'STRIPE_API_KEY, STRIPE_CUSTOMER_ID, and OPENAI_API_KEY environment variables are required'
 24 |   );
 25 | }
 26 | 
 27 | // Sample 1: Basic generateText with OpenAI
 28 | async function sampleBasicGenerateText() {
 29 |   console.log('\n=== Sample 1: Basic generateText ===');
 30 | 
 31 |   // Wrap the AI SDK model with metering
 32 |   const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
 33 | 
 34 |   const {text} = await generateText({
 35 |     model: model,
 36 |     prompt: 'Say "Hello, World!" and nothing else.',
 37 |   });
 38 | 
 39 |   console.log('Response:', text);
 40 | }
 41 | 
 42 | // Sample 2: Streaming text with OpenAI
 43 | async function sampleStreamText() {
 44 |   console.log('\n=== Sample 2: Stream Text ===');
 45 | 
 46 |   const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
 47 | 
 48 |   const result = streamText({
 49 |     model: model,
 50 |     prompt: 'Count from 1 to 5, one number per line.',
 51 |   });
 52 | 
 53 |   // Consume the stream
 54 |   let fullText = '';
 55 |   for await (const chunk of result.textStream) {
 56 |     process.stdout.write(chunk);
 57 |     fullText += chunk;
 58 |   }
 59 | 
 60 |   console.log('\n\nFull text:', fullText);
 61 | }
 62 | 
 63 | // Sample 3: Generate text with system message
 64 | async function sampleWithSystemMessage() {
 65 |   console.log('\n=== Sample 3: With System Message ===');
 66 | 
 67 |   const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
 68 | 
 69 |   const {text} = await generateText({
 70 |     model: model,
 71 |     system: 'You are a helpful assistant that speaks like a pirate.',
 72 |     prompt: 'Tell me about the weather.',
 73 |   });
 74 | 
 75 |   console.log('Response:', text);
 76 | }
 77 | 
 78 | // Sample 4: Multi-turn conversation
 79 | async function sampleConversation() {
 80 |   console.log('\n=== Sample 4: Multi-turn Conversation ===');
 81 | 
 82 |   const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
 83 | 
 84 |   const {text} = await generateText({
 85 |     model: model,
 86 |     messages: [
 87 |       {role: 'user', content: 'What is 2 + 2?'},
 88 |       {role: 'assistant', content: '2 + 2 equals 4.'},
 89 |       {role: 'user', content: 'What about 4 + 4?'},
 90 |     ],
 91 |   });
 92 | 
 93 |   console.log('Response:', text);
 94 | }
 95 | 
 96 | // Sample 5: Generate text with max tokens
 97 | async function sampleWithMaxTokens() {
 98 |   console.log('\n=== Sample 5: With Max Tokens ===');
 99 | 
100 |   const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
101 | 
102 |   const {text} = await generateText({
103 |     model: model,
104 |     prompt: 'Write a short story about a robot.',
105 |     maxOutputTokens: 100,
106 |   });
107 | 
108 |   console.log('Response:', text);
109 | }
110 | 
111 | // Sample 6: Using GPT-4
112 | async function sampleGPT4() {
113 |   console.log('\n=== Sample 6: Using GPT-4 ===');
114 | 
115 |   const model = meteredModel(openai('gpt-4'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
116 | 
117 |   const {text} = await generateText({
118 |     model: model,
119 |     prompt: 'Explain quantum computing in one sentence.',
120 |   });
121 | 
122 |   console.log('Response:', text);
123 | }
124 | 
125 | // Sample 7: Stream with system message
126 | async function sampleStreamWithSystem() {
127 |   console.log('\n=== Sample 7: Stream with System Message ===');
128 | 
129 |   const model = meteredModel(openai('gpt-4o-mini'), STRIPE_API_KEY, STRIPE_CUSTOMER_ID);
130 | 
131 |   const result = streamText({
132 |     model: model,
133 |     system: 'You are a helpful math tutor.',
134 |     prompt: 'Explain how to solve 2x + 5 = 13',
135 |   });
136 | 
137 |   for await (const chunk of result.textStream) {
138 |     process.stdout.write(chunk);
139 |   }
140 | 
141 |   console.log('\n');
142 | }
143 | 
144 | // Run all samples
145 | async function runAllSamples() {
146 |   console.log('Starting Vercel AI SDK + Stripe Metering Examples');
147 |   console.log(
148 |     'These examples show how to use meteredModel with the Vercel AI SDK\n'
149 |   );
150 | 
151 |   try {
152 |     await sampleBasicGenerateText();
153 |     await sampleStreamText();
154 |     await sampleWithSystemMessage();
155 |     await sampleConversation();
156 |     await sampleWithMaxTokens();
157 |     await sampleGPT4();
158 |     await sampleStreamWithSystem();
159 | 
160 |     console.log('\n' + '='.repeat(80));
161 |     console.log('All examples completed successfully!');
162 |     console.log('='.repeat(80));
163 |   } catch (error) {
164 |     console.error('\n❌ Sample failed:', error);
165 |     throw error;
166 |   }
167 | }
168 | 
169 | // Run the samples
170 | runAllSamples().catch(console.error);
171 | 
172 | 
173 | 
```

--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/tools.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import Dict, List
  2 | 
  3 | from .prompts import (
  4 |     CREATE_CUSTOMER_PROMPT,
  5 |     LIST_CUSTOMERS_PROMPT,
  6 |     CREATE_PRODUCT_PROMPT,
  7 |     LIST_PRODUCTS_PROMPT,
  8 |     CREATE_PRICE_PROMPT,
  9 |     LIST_PRICES_PROMPT,
 10 |     CREATE_PAYMENT_LINK_PROMPT,
 11 |     LIST_INVOICES_PROMPT,
 12 |     CREATE_INVOICE_PROMPT,
 13 |     CREATE_INVOICE_ITEM_PROMPT,
 14 |     FINALIZE_INVOICE_PROMPT,
 15 |     RETRIEVE_BALANCE_PROMPT,
 16 |     CREATE_REFUND_PROMPT,
 17 |     LIST_PAYMENT_INTENTS_PROMPT,
 18 |     CREATE_BILLING_PORTAL_SESSION_PROMPT,
 19 | )
 20 | 
 21 | from .schema import (
 22 |     CreateCustomer,
 23 |     ListCustomers,
 24 |     CreateProduct,
 25 |     ListProducts,
 26 |     CreatePrice,
 27 |     ListPrices,
 28 |     CreatePaymentLink,
 29 |     ListInvoices,
 30 |     CreateInvoice,
 31 |     CreateInvoiceItem,
 32 |     FinalizeInvoice,
 33 |     RetrieveBalance,
 34 |     CreateRefund,
 35 |     ListPaymentIntents,
 36 |     CreateBillingPortalSession,
 37 | )
 38 | 
 39 | tools: List[Dict] = [
 40 |     {
 41 |         "method": "create_customer",
 42 |         "name": "Create Customer",
 43 |         "description": CREATE_CUSTOMER_PROMPT,
 44 |         "args_schema": CreateCustomer,
 45 |         "actions": {
 46 |             "customers": {
 47 |                 "create": True,
 48 |             }
 49 |         },
 50 |     },
 51 |     {
 52 |         "method": "list_customers",
 53 |         "name": "List Customers",
 54 |         "description": LIST_CUSTOMERS_PROMPT,
 55 |         "args_schema": ListCustomers,
 56 |         "actions": {
 57 |             "customers": {
 58 |                 "read": True,
 59 |             }
 60 |         },
 61 |     },
 62 |     {
 63 |         "method": "create_product",
 64 |         "name": "Create Product",
 65 |         "description": CREATE_PRODUCT_PROMPT,
 66 |         "args_schema": CreateProduct,
 67 |         "actions": {
 68 |             "products": {
 69 |                 "create": True,
 70 |             }
 71 |         },
 72 |     },
 73 |     {
 74 |         "method": "list_products",
 75 |         "name": "List Products",
 76 |         "description": LIST_PRODUCTS_PROMPT,
 77 |         "args_schema": ListProducts,
 78 |         "actions": {
 79 |             "products": {
 80 |                 "read": True,
 81 |             }
 82 |         },
 83 |     },
 84 |     {
 85 |         "method": "create_price",
 86 |         "name": "Create Price",
 87 |         "description": CREATE_PRICE_PROMPT,
 88 |         "args_schema": CreatePrice,
 89 |         "actions": {
 90 |             "prices": {
 91 |                 "create": True,
 92 |             }
 93 |         },
 94 |     },
 95 |     {
 96 |         "method": "list_prices",
 97 |         "name": "List Prices",
 98 |         "description": LIST_PRICES_PROMPT,
 99 |         "args_schema": ListPrices,
100 |         "actions": {
101 |             "prices": {
102 |                 "read": True,
103 |             }
104 |         },
105 |     },
106 |     {
107 |         "method": "create_payment_link",
108 |         "name": "Create Payment Link",
109 |         "description": CREATE_PAYMENT_LINK_PROMPT,
110 |         "args_schema": CreatePaymentLink,
111 |         "actions": {
112 |             "payment_links": {
113 |                 "create": True,
114 |             }
115 |         },
116 |     },
117 |     {
118 |         "method": "list_invoices",
119 |         "name": "List Invoices",
120 |         "description": LIST_INVOICES_PROMPT,
121 |         "args_schema": ListInvoices,
122 |         "actions": {
123 |             "invoices": {
124 |                 "read": True,
125 |             }
126 |         },
127 |     },
128 |     {
129 |         "method": "create_invoice",
130 |         "name": "Create Invoice",
131 |         "description": CREATE_INVOICE_PROMPT,
132 |         "args_schema": CreateInvoice,
133 |         "actions": {
134 |             "invoices": {
135 |                 "create": True,
136 |             }
137 |         },
138 |     },
139 |     {
140 |         "method": "create_invoice_item",
141 |         "name": "Create Invoice Item",
142 |         "description": CREATE_INVOICE_ITEM_PROMPT,
143 |         "args_schema": CreateInvoiceItem,
144 |         "actions": {
145 |             "invoice_items": {
146 |                 "create": True,
147 |             }
148 |         },
149 |     },
150 |     {
151 |         "method": "finalize_invoice",
152 |         "name": "Finalize Invoice",
153 |         "description": FINALIZE_INVOICE_PROMPT,
154 |         "args_schema": FinalizeInvoice,
155 |         "actions": {
156 |             "invoices": {
157 |                 "update": True,
158 |             }
159 |         },
160 |     },
161 |     {
162 |         "method": "retrieve_balance",
163 |         "name": "Retrieve Balance",
164 |         "description": RETRIEVE_BALANCE_PROMPT,
165 |         "args_schema": RetrieveBalance,
166 |         "actions": {
167 |             "balance": {
168 |                 "read": True,
169 |             }
170 |         },
171 |     },
172 |     {
173 |         "method": "create_refund",
174 |         "name": "Create Refund",
175 |         "description": CREATE_REFUND_PROMPT,
176 |         "args_schema": CreateRefund,
177 |         "actions": {
178 |             "refunds": {
179 |                 "create": True,
180 |             }
181 |         },
182 |     },
183 |     {
184 |         "method": "list_payment_intents",
185 |         "name": "List Payment Intents",
186 |         "description": LIST_PAYMENT_INTENTS_PROMPT,
187 |         "args_schema": ListPaymentIntents,
188 |         "actions": {
189 |             "payment_intents": {
190 |                 "read": True,
191 |             }
192 |         },
193 |     },
194 |     {
195 |         "method": "create_billing_portal_session",
196 |         "name": "Create Billing Portal Session",
197 |         "description": CREATE_BILLING_PORTAL_SESSION_PROMPT,
198 |         "args_schema": CreateBillingPortalSession,
199 |         "actions": {
200 |             "billing_portal_sessions": {
201 |                 "create": True,
202 |             }
203 |         },
204 |     },
205 | ]
206 | 
```

--------------------------------------------------------------------------------
/tools/typescript/src/test/shared/invoices/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {createInvoice} from '@/shared/invoices/createInvoice';
  2 | import {listInvoices} from '@/shared/invoices/listInvoices';
  3 | import {finalizeInvoice} from '@/shared/invoices/finalizeInvoice';
  4 | 
  5 | const Stripe = jest.fn().mockImplementation(() => ({
  6 |   invoices: {
  7 |     create: jest.fn(),
  8 |     finalizeInvoice: jest.fn(),
  9 |     retrieve: jest.fn(),
 10 |     list: jest.fn(),
 11 |   },
 12 | }));
 13 | 
 14 | let stripe: ReturnType<typeof Stripe>;
 15 | 
 16 | beforeEach(() => {
 17 |   stripe = new Stripe('fake-api-key');
 18 | });
 19 | 
 20 | describe('createInvoice', () => {
 21 |   it('should create an invoice and return it', async () => {
 22 |     const params = {
 23 |       customer: 'cus_123456',
 24 |       days_until_due: 30,
 25 |     };
 26 | 
 27 |     const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
 28 | 
 29 |     const context = {};
 30 | 
 31 |     stripe.invoices.create.mockResolvedValue(mockInvoice);
 32 | 
 33 |     const result = await createInvoice(stripe, context, params);
 34 | 
 35 |     expect(stripe.invoices.create).toHaveBeenCalledWith(
 36 |       {...params, collection_method: 'send_invoice'},
 37 |       undefined
 38 |     );
 39 |     expect(result).toEqual(mockInvoice);
 40 |   });
 41 | 
 42 |   it('should specify the connected account if included in context', async () => {
 43 |     const params = {
 44 |       customer: 'cus_123456',
 45 |       days_until_due: 30,
 46 |     };
 47 | 
 48 |     const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
 49 | 
 50 |     const context = {
 51 |       account: 'acct_123456',
 52 |     };
 53 | 
 54 |     stripe.invoices.create.mockResolvedValue(mockInvoice);
 55 | 
 56 |     const result = await createInvoice(stripe, context, params);
 57 | 
 58 |     expect(stripe.invoices.create).toHaveBeenCalledWith(
 59 |       {
 60 |         ...params,
 61 |         collection_method: 'send_invoice',
 62 |       },
 63 |       {stripeAccount: context.account}
 64 |     );
 65 |     expect(result).toEqual(mockInvoice);
 66 |   });
 67 | 
 68 |   it('should create an invoice with a customer if included in context', async () => {
 69 |     const params = {
 70 |       days_until_due: 30,
 71 |     };
 72 | 
 73 |     const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
 74 | 
 75 |     const context = {
 76 |       customer: 'cus_123456',
 77 |     };
 78 | 
 79 |     stripe.invoices.create.mockResolvedValue(mockInvoice);
 80 | 
 81 |     const result = await createInvoice(stripe, context, params);
 82 | 
 83 |     expect(stripe.invoices.create).toHaveBeenCalledWith(
 84 |       {
 85 |         ...params,
 86 |         customer: context.customer,
 87 |         collection_method: 'send_invoice',
 88 |       },
 89 |       undefined
 90 |     );
 91 |     expect(result).toEqual(mockInvoice);
 92 |   });
 93 | });
 94 | 
 95 | describe('listInvoices', () => {
 96 |   it('should list invoices and return them', async () => {
 97 |     const mockInvoices = [
 98 |       {id: 'in_123456', customer: 'cus_123456'},
 99 |       {id: 'in_789012', customer: 'cus_789012'},
100 |     ];
101 | 
102 |     const context = {};
103 | 
104 |     stripe.invoices.list.mockResolvedValue({data: mockInvoices});
105 | 
106 |     const result = await listInvoices(stripe, context, {});
107 | 
108 |     expect(stripe.invoices.list).toHaveBeenCalledWith({}, undefined);
109 |     expect(result).toEqual(mockInvoices);
110 |   });
111 | 
112 |   it('should specify the connected account if included in context', async () => {
113 |     const mockInvoices = [
114 |       {id: 'in_123456', customer: 'cus_123456'},
115 |       {id: 'in_789012', customer: 'cus_789012'},
116 |     ];
117 | 
118 |     const context = {
119 |       account: 'acct_123456',
120 |     };
121 | 
122 |     stripe.invoices.list.mockResolvedValue({data: mockInvoices});
123 | 
124 |     const result = await listInvoices(stripe, context, {});
125 | 
126 |     expect(stripe.invoices.list).toHaveBeenCalledWith(
127 |       {},
128 |       {stripeAccount: context.account}
129 |     );
130 |     expect(result).toEqual(mockInvoices);
131 |   });
132 | 
133 |   it('should list invoices for a specific customer', async () => {
134 |     const mockInvoices = [
135 |       {id: 'in_123456', customer: 'cus_123456'},
136 |       {id: 'in_789012', customer: 'cus_789012'},
137 |     ];
138 | 
139 |     const context = {
140 |       customer: 'cus_123456',
141 |     };
142 | 
143 |     stripe.invoices.list.mockResolvedValue({data: mockInvoices});
144 | 
145 |     const result = await listInvoices(stripe, context, {});
146 | 
147 |     expect(stripe.invoices.list).toHaveBeenCalledWith(
148 |       {customer: context.customer},
149 |       undefined
150 |     );
151 |     expect(result).toEqual(mockInvoices);
152 |   });
153 | });
154 | 
155 | describe('finalizeInvoice', () => {
156 |   it('should finalize an invoice and return it', async () => {
157 |     const invoiceId = 'in_123456';
158 | 
159 |     const mockInvoice = {id: invoiceId, customer: 'cus_123456'};
160 | 
161 |     const context = {};
162 | 
163 |     stripe.invoices.finalizeInvoice.mockResolvedValue(mockInvoice);
164 | 
165 |     const result = await finalizeInvoice(stripe, context, {invoice: invoiceId});
166 | 
167 |     expect(stripe.invoices.finalizeInvoice).toHaveBeenCalledWith(
168 |       invoiceId,
169 |       undefined
170 |     );
171 |     expect(result).toEqual(mockInvoice);
172 |   });
173 | 
174 |   it('should specify the connected account if included in context', async () => {
175 |     const invoiceId = 'in_123456';
176 | 
177 |     const mockInvoice = {id: invoiceId, customer: 'cus_123456'};
178 | 
179 |     const context = {
180 |       account: 'acct_123456',
181 |     };
182 | 
183 |     stripe.invoices.finalizeInvoice.mockResolvedValue(mockInvoice);
184 | 
185 |     const result = await finalizeInvoice(stripe, context, {invoice: invoiceId});
186 | 
187 |     expect(stripe.invoices.finalizeInvoice).toHaveBeenCalledWith(invoiceId, {
188 |       stripeAccount: context.account,
189 |     });
190 |     expect(result).toEqual(mockInvoice);
191 |   });
192 | });
193 | 
```

--------------------------------------------------------------------------------
/skills/stripe-best-practices.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | description: Best practices for building a Stripe integrations
 3 | alwaysApply: false
 4 | ---
 5 | 
 6 | When designing an integration, always prefer the documentation in [Stripe's Integration Options doc](https://docs.stripe.com/payments/payment-methods/integration-options.md)
 7 | The [API Tour](https://docs.stripe.com/payments-api/tour.md)
 8 | Use the [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live.md) before going live.
 9 | 
10 | You should always default to the latest version of the API and SDK unless the user specifies otherwise. 
11 | 
12 | 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.
13 | 
14 | 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. 
15 | 
16 | 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.
17 | 
18 | 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). 
19 | 
20 | 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.
21 | 
22 | 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.
23 | 
24 | 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.
25 | 
26 | 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).
27 | 
28 | 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.
29 | 
30 | 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.
31 | 
32 | 
```

--------------------------------------------------------------------------------
/tools/python/stripe_agent_toolkit/schema.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import Optional
  2 | from pydantic import BaseModel, Field
  3 | 
  4 | 
  5 | class CreateCustomer(BaseModel):
  6 |     """Schema for the ``create_customer`` operation."""
  7 | 
  8 |     name: str = Field(
  9 |         ...,
 10 |         description="The name of the customer.",
 11 |     )
 12 | 
 13 |     email: Optional[str] = Field(
 14 |         None,
 15 |         description="The email of the customer.",
 16 |     )
 17 | 
 18 | 
 19 | class ListCustomers(BaseModel):
 20 |     """Schema for the ``list_customers`` operation."""
 21 | 
 22 |     limit: Optional[int] = Field(
 23 |         None,
 24 |         description=(
 25 |             "A limit on the number of objects to be returned."
 26 |             " Limit can range between 1 and 100."
 27 |         ),
 28 |     )
 29 | 
 30 |     email: Optional[str] = Field(
 31 |         None,
 32 |         description=(
 33 |             "A case-sensitive filter on the list based on"
 34 |             " the customer's email field. The value must be a string."
 35 |         ),
 36 |     )
 37 | 
 38 | 
 39 | class CreateProduct(BaseModel):
 40 |     """Schema for the ``create_product`` operation."""
 41 | 
 42 |     name: str = Field(
 43 |         ...,
 44 |         description="The name of the product.",
 45 |     )
 46 |     description: Optional[str] = Field(
 47 |         None,
 48 |         description="The description of the product.",
 49 |     )
 50 | 
 51 | 
 52 | class ListProducts(BaseModel):
 53 |     """Schema for the ``list_products`` operation."""
 54 | 
 55 |     limit: Optional[int] = Field(
 56 |         None,
 57 |         description=(
 58 |             "A limit on the number of objects to be returned."
 59 |             " Limit can range between 1 and 100, and the default is 10."
 60 |         ),
 61 |     )
 62 | 
 63 | 
 64 | class CreatePrice(BaseModel):
 65 |     """Schema for the ``create_price`` operation."""
 66 | 
 67 |     product: str = Field(
 68 |         ..., description="The ID of the product to create the price for."
 69 |     )
 70 |     unit_amount: int = Field(
 71 |         ...,
 72 |         description="The unit amount of the price in cents.",
 73 |     )
 74 |     currency: str = Field(
 75 |         ...,
 76 |         description="The currency of the price.",
 77 |     )
 78 | 
 79 | 
 80 | class ListPrices(BaseModel):
 81 |     """Schema for the ``list_prices`` operation."""
 82 | 
 83 |     product: Optional[str] = Field(
 84 |         None,
 85 |         description="The ID of the product to list prices for.",
 86 |     )
 87 |     limit: Optional[int] = Field(
 88 |         None,
 89 |         description=(
 90 |             "A limit on the number of objects to be returned."
 91 |             " Limit can range between 1 and 100, and the default is 10."
 92 |         ),
 93 |     )
 94 | 
 95 | 
 96 | class CreatePaymentLink(BaseModel):
 97 |     """Schema for the ``create_payment_link`` operation."""
 98 | 
 99 |     price: str = Field(
100 |         ...,
101 |         description="The ID of the price to create the payment link for.",
102 |     )
103 |     quantity: int = Field(
104 |         ...,
105 |         description="The quantity of the product to include.",
106 |     )
107 |     redirect_url: Optional[str] = Field(
108 |         None,
109 |         description="The URL the customer will be redirected to after the purchase is complete.",
110 |     )
111 | 
112 | 
113 | class ListInvoices(BaseModel):
114 |     """Schema for the ``list_invoices`` operation."""
115 | 
116 |     customer: Optional[str] = Field(
117 |         None,
118 |         description="The ID of the customer to list invoices for.",
119 |     )
120 |     limit: Optional[int] = Field(
121 |         None,
122 |         description=(
123 |             "A limit on the number of objects to be returned."
124 |             " Limit can range between 1 and 100, and the default is 10."
125 |         ),
126 |     )
127 | 
128 | 
129 | class CreateInvoice(BaseModel):
130 |     """Schema for the ``create_invoice`` operation."""
131 | 
132 |     customer: str = Field(
133 |         ..., description="The ID of the customer to create the invoice for."
134 |     )
135 | 
136 |     days_until_due: Optional[int] = Field(
137 |         None,
138 |         description="The number of days until the invoice is due.",
139 |     )
140 | 
141 | 
142 | class CreateInvoiceItem(BaseModel):
143 |     """Schema for the ``create_invoice_item`` operation."""
144 | 
145 |     customer: str = Field(
146 |         ...,
147 |         description="The ID of the customer to create the invoice item for.",
148 |     )
149 |     price: str = Field(
150 |         ...,
151 |         description="The ID of the price for the item.",
152 |     )
153 |     invoice: str = Field(
154 |         ...,
155 |         description="The ID of the invoice to create the item for.",
156 |     )
157 | 
158 | 
159 | class FinalizeInvoice(BaseModel):
160 |     """Schema for the ``finalize_invoice`` operation."""
161 | 
162 |     invoice: str = Field(
163 |         ...,
164 |         description="The ID of the invoice to finalize.",
165 |     )
166 | 
167 | 
168 | class RetrieveBalance(BaseModel):
169 |     """Schema for the ``retrieve_balance`` operation."""
170 | 
171 |     pass
172 | 
173 | 
174 | class CreateRefund(BaseModel):
175 |     """Schema for the ``create_refund`` operation."""
176 | 
177 |     payment_intent: str = Field(
178 |         ...,
179 |         description="The ID of the PaymentIntent to refund.",
180 |     )
181 |     amount: Optional[int] = Field(
182 |         ...,
183 |         description="The amount to refund in cents.",
184 |     )
185 | 
186 | class ListPaymentIntents(BaseModel):
187 |     """Schema for the ``list_payment_intents`` operation."""
188 | 
189 |     customer: Optional[str] = Field(
190 |         None,
191 |         description="The ID of the customer to list payment intents for.",
192 |     )
193 |     limit: Optional[int] = Field(
194 |         None,
195 |         description=(
196 |             "A limit on the number of objects to be returned."
197 |             " Limit can range between 1 and 100."
198 |         ),
199 |     )
200 | 
201 | class CreateBillingPortalSession(BaseModel):
202 |     """Schema for the ``create_billing_portal_session`` operation."""
203 | 
204 |     customer: str = Field(
205 |         None,
206 |         description="The ID of the customer to create the billing portal session for.",
207 |     )
208 |     return_url: Optional[str] = Field(
209 |         None,
210 |         description=(
211 |             "The default URL to return to afterwards."
212 |         ),
213 |     )
214 | 
```

--------------------------------------------------------------------------------
/llm/token-meter/tests/model-name-normalization.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for model name normalization across all providers
  3 |  * Ensures all model names are properly formatted for meter events
  4 |  */
  5 | 
  6 | import Stripe from 'stripe';
  7 | import {sendMeterEventsToStripe} from '../meter-event-logging';
  8 | import type {UsageEvent, MeterConfig} from '../types';
  9 | 
 10 | // Mock Stripe
 11 | jest.mock('stripe');
 12 | 
 13 | describe('Model Name Normalization - Comprehensive', () => {
 14 |   let mockStripe: jest.Mocked<any>;
 15 | 
 16 |   beforeEach(() => {
 17 |     jest.clearAllMocks();
 18 | 
 19 |     mockStripe = {
 20 |       v2: {
 21 |         billing: {
 22 |           meterEvents: {
 23 |             create: jest.fn().mockResolvedValue({}),
 24 |           },
 25 |         },
 26 |       },
 27 |     };
 28 | 
 29 |     (Stripe as unknown as jest.Mock).mockImplementation(() => mockStripe);
 30 |   });
 31 | 
 32 |   describe('Google/Gemini Models', () => {
 33 |     const testCases = [
 34 |       {model: 'gemini-2.5-pro', expected: 'google/gemini-2.5-pro'},
 35 |       {model: 'gemini-2.5-flash', expected: 'google/gemini-2.5-flash'},
 36 |       {model: 'gemini-2.5-flash-lite', expected: 'google/gemini-2.5-flash-lite'},
 37 |       {model: 'gemini-2.0-flash', expected: 'google/gemini-2.0-flash'},
 38 |       {model: 'gemini-2.0-flash-lite', expected: 'google/gemini-2.0-flash-lite'},
 39 |       // Default fallback model
 40 |       {model: 'gemini-1.5-pro', expected: 'google/gemini-1.5-pro'},
 41 |     ];
 42 | 
 43 |     testCases.forEach(({model, expected}) => {
 44 |       it(`should normalize ${model} to ${expected}`, async () => {
 45 |         const config: MeterConfig = {};
 46 |         const event: UsageEvent = {
 47 |           model,
 48 |           provider: 'google',
 49 |           usage: {inputTokens: 100, outputTokens: 50},
 50 |           stripeCustomerId: 'cus_123',
 51 |         };
 52 | 
 53 |         await sendMeterEventsToStripe(mockStripe, config, event);
 54 | 
 55 |         const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
 56 |         expect(call.payload.model).toBe(expected);
 57 |       });
 58 |     });
 59 |   });
 60 | 
 61 |   describe('OpenAI Models', () => {
 62 |     const testCases = [
 63 |       {model: 'gpt-5', expected: 'openai/gpt-5'},
 64 |       {model: 'gpt-5-mini', expected: 'openai/gpt-5-mini'},
 65 |       {model: 'gpt-5-nano', expected: 'openai/gpt-5-nano'},
 66 |       {model: 'gpt-5-chat-latest', expected: 'openai/gpt-5-chat-latest'},
 67 |       {model: 'gpt-4.1', expected: 'openai/gpt-4.1'},
 68 |       {model: 'gpt-4.1-mini', expected: 'openai/gpt-4.1-mini'},
 69 |       {model: 'gpt-4.1-nano', expected: 'openai/gpt-4.1-nano'},
 70 |       {model: 'gpt-4o', expected: 'openai/gpt-4o'},
 71 |       {model: 'gpt-4o-mini', expected: 'openai/gpt-4o-mini'},
 72 |       {model: 'o4-mini', expected: 'openai/o4-mini'},
 73 |       {model: 'o3', expected: 'openai/o3'},
 74 |       {model: 'o3-mini', expected: 'openai/o3-mini'},
 75 |       {model: 'o3-pro', expected: 'openai/o3-pro'},
 76 |       {model: 'o1', expected: 'openai/o1'},
 77 |       {model: 'o1-mini', expected: 'openai/o1-mini'},
 78 |       {model: 'o1-pro', expected: 'openai/o1-pro'},
 79 |       // With date suffixes that should be removed
 80 |       {model: 'gpt-4o-2024-11-20', expected: 'openai/gpt-4o'},
 81 |       {model: 'gpt-4o-mini-2024-07-18', expected: 'openai/gpt-4o-mini'},
 82 |       {model: 'o1-2024-12-17', expected: 'openai/o1'},
 83 |       {model: 'o1-mini-2024-09-12', expected: 'openai/o1-mini'},
 84 |       // Exception case
 85 |       {model: 'gpt-4o-2024-05-13', expected: 'openai/gpt-4o-2024-05-13'},
 86 |     ];
 87 | 
 88 |     testCases.forEach(({model, expected}) => {
 89 |       it(`should normalize ${model} to ${expected}`, async () => {
 90 |         const config: MeterConfig = {};
 91 |         const event: UsageEvent = {
 92 |           model,
 93 |           provider: 'openai',
 94 |           usage: {inputTokens: 100, outputTokens: 50},
 95 |           stripeCustomerId: 'cus_123',
 96 |         };
 97 | 
 98 |         await sendMeterEventsToStripe(mockStripe, config, event);
 99 | 
100 |         const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
101 |         expect(call.payload.model).toBe(expected);
102 |       });
103 |     });
104 |   });
105 | 
106 |   describe('Anthropic Models', () => {
107 |     const testCases = [
108 |       {model: 'claude-opus-4-1', expected: 'anthropic/claude-opus-4.1'},
109 |       {model: 'claude-opus-4', expected: 'anthropic/claude-opus-4'},
110 |       {model: 'claude-sonnet-4', expected: 'anthropic/claude-sonnet-4'},
111 |       {model: 'claude-3-7-sonnet', expected: 'anthropic/claude-3.7-sonnet'},
112 |       {model: 'claude-3-7-sonnet-latest', expected: 'anthropic/claude-3.7-sonnet'},
113 |       {model: 'claude-3-5-haiku', expected: 'anthropic/claude-3.5-haiku'},
114 |       {model: 'claude-3-5-haiku-latest', expected: 'anthropic/claude-3.5-haiku'},
115 |       {model: 'claude-3-haiku', expected: 'anthropic/claude-3-haiku'},
116 |       // With date suffixes that should be removed
117 |       {model: 'claude-opus-4-1-20241231', expected: 'anthropic/claude-opus-4.1'},
118 |       {model: 'claude-3-5-sonnet-20241022', expected: 'anthropic/claude-3.5-sonnet'},
119 |       {model: 'claude-3-5-haiku-20241022', expected: 'anthropic/claude-3.5-haiku'},
120 |       {model: 'claude-3-haiku-20240307', expected: 'anthropic/claude-3-haiku'},
121 |       // With latest suffixes that should be removed
122 |       {model: 'claude-opus-4-latest', expected: 'anthropic/claude-opus-4'},
123 |       {model: 'claude-sonnet-4-latest', expected: 'anthropic/claude-sonnet-4'},
124 |     ];
125 | 
126 |     testCases.forEach(({model, expected}) => {
127 |       it(`should normalize ${model} to ${expected}`, async () => {
128 |         const config: MeterConfig = {};
129 |         const event: UsageEvent = {
130 |           model,
131 |           provider: 'anthropic',
132 |           usage: {inputTokens: 100, outputTokens: 50},
133 |           stripeCustomerId: 'cus_123',
134 |         };
135 | 
136 |         await sendMeterEventsToStripe(mockStripe, config, event);
137 | 
138 |         const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
139 |         expect(call.payload.model).toBe(expected);
140 |       });
141 |     });
142 |   });
143 | });
144 | 
145 | 
```

--------------------------------------------------------------------------------
/llm/ai-sdk/meter/tests/model-name-normalization.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for model name normalization across all providers
  3 |  * Ensures all model names are properly formatted for meter events
  4 |  */
  5 | 
  6 | import Stripe from 'stripe';
  7 | import {sendMeterEventsToStripe} from '../meter-event-logging';
  8 | import type {UsageEvent, MeterConfig} from '../meter-event-types';
  9 | 
 10 | // Mock Stripe
 11 | jest.mock('stripe');
 12 | 
 13 | describe('Model Name Normalization - Comprehensive', () => {
 14 |   let mockStripe: jest.Mocked<any>;
 15 | 
 16 |   beforeEach(() => {
 17 |     jest.clearAllMocks();
 18 | 
 19 |     mockStripe = {
 20 |       v2: {
 21 |         billing: {
 22 |           meterEvents: {
 23 |             create: jest.fn().mockResolvedValue({}),
 24 |           },
 25 |         },
 26 |       },
 27 |     };
 28 | 
 29 |     (Stripe as unknown as jest.Mock).mockImplementation(() => mockStripe);
 30 |   });
 31 | 
 32 |   describe('Google/Gemini Models', () => {
 33 |     const testCases = [
 34 |       {model: 'gemini-2.5-pro', expected: 'google/gemini-2.5-pro'},
 35 |       {model: 'gemini-2.5-flash', expected: 'google/gemini-2.5-flash'},
 36 |       {model: 'gemini-2.5-flash-lite', expected: 'google/gemini-2.5-flash-lite'},
 37 |       {model: 'gemini-2.0-flash', expected: 'google/gemini-2.0-flash'},
 38 |       {model: 'gemini-2.0-flash-lite', expected: 'google/gemini-2.0-flash-lite'},
 39 |       // Default fallback model
 40 |       {model: 'gemini-1.5-pro', expected: 'google/gemini-1.5-pro'},
 41 |     ];
 42 | 
 43 |     testCases.forEach(({model, expected}) => {
 44 |       it(`should normalize ${model} to ${expected}`, async () => {
 45 |         const config: MeterConfig = {};
 46 |         const event: UsageEvent = {
 47 |           model,
 48 |           provider: 'google',
 49 |           usage: {inputTokens: 100, outputTokens: 50},
 50 |           stripeCustomerId: 'cus_123',
 51 |         };
 52 | 
 53 |         await sendMeterEventsToStripe(mockStripe, config, event);
 54 | 
 55 |         const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
 56 |         expect(call.payload.model).toBe(expected);
 57 |       });
 58 |     });
 59 |   });
 60 | 
 61 |   describe('OpenAI Models', () => {
 62 |     const testCases = [
 63 |       {model: 'gpt-5', expected: 'openai/gpt-5'},
 64 |       {model: 'gpt-5-mini', expected: 'openai/gpt-5-mini'},
 65 |       {model: 'gpt-5-nano', expected: 'openai/gpt-5-nano'},
 66 |       {model: 'gpt-5-chat-latest', expected: 'openai/gpt-5-chat-latest'},
 67 |       {model: 'gpt-4.1', expected: 'openai/gpt-4.1'},
 68 |       {model: 'gpt-4.1-mini', expected: 'openai/gpt-4.1-mini'},
 69 |       {model: 'gpt-4.1-nano', expected: 'openai/gpt-4.1-nano'},
 70 |       {model: 'gpt-4o', expected: 'openai/gpt-4o'},
 71 |       {model: 'gpt-4o-mini', expected: 'openai/gpt-4o-mini'},
 72 |       {model: 'o4-mini', expected: 'openai/o4-mini'},
 73 |       {model: 'o3', expected: 'openai/o3'},
 74 |       {model: 'o3-mini', expected: 'openai/o3-mini'},
 75 |       {model: 'o3-pro', expected: 'openai/o3-pro'},
 76 |       {model: 'o1', expected: 'openai/o1'},
 77 |       {model: 'o1-mini', expected: 'openai/o1-mini'},
 78 |       {model: 'o1-pro', expected: 'openai/o1-pro'},
 79 |       // With date suffixes that should be removed
 80 |       {model: 'gpt-4o-2024-11-20', expected: 'openai/gpt-4o'},
 81 |       {model: 'gpt-4o-mini-2024-07-18', expected: 'openai/gpt-4o-mini'},
 82 |       {model: 'o1-2024-12-17', expected: 'openai/o1'},
 83 |       {model: 'o1-mini-2024-09-12', expected: 'openai/o1-mini'},
 84 |       // Exception case
 85 |       {model: 'gpt-4o-2024-05-13', expected: 'openai/gpt-4o-2024-05-13'},
 86 |     ];
 87 | 
 88 |     testCases.forEach(({model, expected}) => {
 89 |       it(`should normalize ${model} to ${expected}`, async () => {
 90 |         const config: MeterConfig = {};
 91 |         const event: UsageEvent = {
 92 |           model,
 93 |           provider: 'openai',
 94 |           usage: {inputTokens: 100, outputTokens: 50},
 95 |           stripeCustomerId: 'cus_123',
 96 |         };
 97 | 
 98 |         await sendMeterEventsToStripe(mockStripe, config, event);
 99 | 
100 |         const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
101 |         expect(call.payload.model).toBe(expected);
102 |       });
103 |     });
104 |   });
105 | 
106 |   describe('Anthropic Models', () => {
107 |     const testCases = [
108 |       {model: 'claude-opus-4-1', expected: 'anthropic/claude-opus-4.1'},
109 |       {model: 'claude-opus-4', expected: 'anthropic/claude-opus-4'},
110 |       {model: 'claude-sonnet-4', expected: 'anthropic/claude-sonnet-4'},
111 |       {model: 'claude-3-7-sonnet', expected: 'anthropic/claude-3.7-sonnet'},
112 |       {model: 'claude-3-7-sonnet-latest', expected: 'anthropic/claude-3.7-sonnet'},
113 |       {model: 'claude-3-5-haiku', expected: 'anthropic/claude-3.5-haiku'},
114 |       {model: 'claude-3-5-haiku-latest', expected: 'anthropic/claude-3.5-haiku'},
115 |       {model: 'claude-3-haiku', expected: 'anthropic/claude-3-haiku'},
116 |       // With date suffixes that should be removed
117 |       {model: 'claude-opus-4-1-20241231', expected: 'anthropic/claude-opus-4.1'},
118 |       {model: 'claude-3-5-sonnet-20241022', expected: 'anthropic/claude-3.5-sonnet'},
119 |       {model: 'claude-3-5-haiku-20241022', expected: 'anthropic/claude-3.5-haiku'},
120 |       {model: 'claude-3-haiku-20240307', expected: 'anthropic/claude-3-haiku'},
121 |       // With latest suffixes that should be removed
122 |       {model: 'claude-opus-4-latest', expected: 'anthropic/claude-opus-4'},
123 |       {model: 'claude-sonnet-4-latest', expected: 'anthropic/claude-sonnet-4'},
124 |     ];
125 | 
126 |     testCases.forEach(({model, expected}) => {
127 |       it(`should normalize ${model} to ${expected}`, async () => {
128 |         const config: MeterConfig = {};
129 |         const event: UsageEvent = {
130 |           model,
131 |           provider: 'anthropic',
132 |           usage: {inputTokens: 100, outputTokens: 50},
133 |           stripeCustomerId: 'cus_123',
134 |         };
135 | 
136 |         await sendMeterEventsToStripe(mockStripe, config, event);
137 | 
138 |         const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
139 |         expect(call.payload.model).toBe(expected);
140 |       });
141 |     });
142 |   });
143 | });
144 | 
145 | 
```

--------------------------------------------------------------------------------
/llm/token-meter/examples/anthropic.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Sample Usage: Anthropic with Usage Tracking
  3 |  * This demonstrates how to use the generic token meter to automatically report
  4 |  * token usage to Stripe for billing purposes with Anthropic.
  5 |  */
  6 | 
  7 | import {config} from 'dotenv';
  8 | import {resolve} from 'path';
  9 | import Anthropic from '@anthropic-ai/sdk';
 10 | import {createTokenMeter} from '..';
 11 | 
 12 | // Load .env from the examples folder
 13 | config({path: resolve(__dirname, '.env')});
 14 | 
 15 | // Load environment variables from .env file
 16 | const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
 17 | const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
 18 | const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY!;
 19 | 
 20 | // Initialize the standard Anthropic client (no wrapper needed!)
 21 | const anthropic = new Anthropic({
 22 |   apiKey: ANTHROPIC_API_KEY,
 23 | });
 24 | 
 25 | // Create the token meter
 26 | const meter = createTokenMeter(STRIPE_API_KEY);
 27 | 
 28 | // Sample 1: Basic Message Completion (non-streaming)
 29 | async function sampleBasicMessage() {
 30 |   const response = await anthropic.messages.create({
 31 |     model: 'claude-3-5-sonnet-20241022',
 32 |     max_tokens: 20,
 33 |     messages: [
 34 |       {role: 'user', content: 'Say "Hello, World!" and nothing else.'},
 35 |     ],
 36 |   });
 37 | 
 38 |   // Meter the response - auto-detects it's Anthropic!
 39 |   meter.trackUsage(response, STRIPE_CUSTOMER_ID);
 40 | 
 41 |   console.log('Response:', response.content[0]);
 42 |   console.log('Usage:', response.usage);
 43 | }
 44 | 
 45 | // Sample 2: Streaming Message Completion
 46 | async function sampleStreamingMessage() {
 47 |   const stream = await anthropic.messages.create({
 48 |     model: 'claude-3-5-sonnet-20241022',
 49 |     max_tokens: 50,
 50 |     messages: [
 51 |       {role: 'user', content: 'Count from 1 to 5, one number per line.'},
 52 |     ],
 53 |     stream: true,
 54 |   });
 55 | 
 56 |   // Wrap the stream for metering - auto-detects it's Anthropic!
 57 |   const meteredStream = meter.trackUsageStreamAnthropic(stream, STRIPE_CUSTOMER_ID);
 58 | 
 59 |   let fullContent = '';
 60 | 
 61 |   for await (const event of meteredStream) {
 62 |     if (
 63 |       event.type === 'content_block_delta' &&
 64 |       event.delta.type === 'text_delta'
 65 |     ) {
 66 |       const content = event.delta.text;
 67 |       fullContent += content;
 68 |       process.stdout.write(content);
 69 |     }
 70 |   }
 71 | 
 72 |   console.log('\n\nFull content:', fullContent);
 73 | }
 74 | 
 75 | // Sample 3: Message with Tools
 76 | async function sampleMessageWithTools() {
 77 |   const tools: any[] = [
 78 |     {
 79 |       name: 'get_weather',
 80 |       description: 'Get the current weather in a location',
 81 |       input_schema: {
 82 |         type: 'object',
 83 |         properties: {
 84 |           location: {
 85 |             type: 'string',
 86 |             description: 'The city and state, e.g. San Francisco, CA',
 87 |           },
 88 |           unit: {
 89 |             type: 'string',
 90 |             enum: ['celsius', 'fahrenheit'],
 91 |             description: 'The unit of temperature',
 92 |           },
 93 |         },
 94 |         required: ['location'],
 95 |       },
 96 |     },
 97 |   ];
 98 | 
 99 |   const response = await anthropic.messages.create({
100 |     model: 'claude-3-5-sonnet-20241022',
101 |     max_tokens: 100,
102 |     messages: [{role: 'user', content: 'What is the weather in New York?'}],
103 |     tools,
104 |   });
105 | 
106 |   // Meter the response - auto-detects it's Anthropic!
107 |   meter.trackUsage(response, STRIPE_CUSTOMER_ID);
108 | 
109 |   console.log('Response:', JSON.stringify(response.content, null, 2));
110 |   console.log('Usage:', response.usage);
111 | }
112 | 
113 | // Sample 4: Message with System Prompt
114 | async function sampleMessageWithSystem() {
115 |   const response = await anthropic.messages.create({
116 |     model: 'claude-3-5-sonnet-20241022',
117 |     max_tokens: 50,
118 |     system: 'You are a helpful assistant that speaks like a pirate.',
119 |     messages: [{role: 'user', content: 'Tell me about Paris.'}],
120 |   });
121 | 
122 |   // Meter the response - auto-detects it's Anthropic!
123 |   meter.trackUsage(response, STRIPE_CUSTOMER_ID);
124 | 
125 |   console.log('Response:', response.content[0]);
126 |   console.log('Usage:', response.usage);
127 | }
128 | 
129 | // Sample 5: Multi-turn Conversation
130 | async function sampleConversation() {
131 |   const response = await anthropic.messages.create({
132 |     model: 'claude-3-5-sonnet-20241022',
133 |     max_tokens: 50,
134 |     messages: [
135 |       {role: 'user', content: 'What is the capital of France?'},
136 |       {role: 'assistant', content: 'The capital of France is Paris.'},
137 |       {role: 'user', content: 'What is its population?'},
138 |     ],
139 |   });
140 | 
141 |   // Meter the response - auto-detects it's Anthropic!
142 |   meter.trackUsage(response, STRIPE_CUSTOMER_ID);
143 | 
144 |   console.log('Response:', response.content[0]);
145 |   console.log('Usage:', response.usage);
146 | }
147 | 
148 | // Run all samples
149 | async function runAllSamples() {
150 |   console.log('Starting Anthropic Usage Tracking Examples');
151 |   console.log(
152 |     'These examples show how to use the generic meter with Anthropic and Stripe billing\n'
153 |   );
154 | 
155 |   try {
156 |     console.log('\n' + '='.repeat(80));
157 |     console.log('Sample 1: Basic Message');
158 |     console.log('='.repeat(80));
159 |     await sampleBasicMessage();
160 | 
161 |     console.log('\n' + '='.repeat(80));
162 |     console.log('Sample 2: Streaming Message');
163 |     console.log('='.repeat(80));
164 |     await sampleStreamingMessage();
165 | 
166 |     console.log('\n' + '='.repeat(80));
167 |     console.log('Sample 3: Message with Tools');
168 |     console.log('='.repeat(80));
169 |     await sampleMessageWithTools();
170 | 
171 |     console.log('\n' + '='.repeat(80));
172 |     console.log('Sample 4: Message with System Prompt');
173 |     console.log('='.repeat(80));
174 |     await sampleMessageWithSystem();
175 | 
176 |     console.log('\n' + '='.repeat(80));
177 |     console.log('Sample 5: Multi-turn Conversation');
178 |     console.log('='.repeat(80));
179 |     await sampleConversation();
180 | 
181 |     console.log('\n' + '='.repeat(80));
182 |     console.log('All examples completed successfully!');
183 |     console.log('='.repeat(80));
184 |   } catch (error) {
185 |     console.error('\n❌ Sample failed:', error);
186 |     throw error;
187 |   }
188 | }
189 | 
190 | // Run the samples
191 | runAllSamples().catch(console.error);
192 | 
193 | 
```

--------------------------------------------------------------------------------
/tools/typescript/src/modelcontextprotocol/register-paid-tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {z, type ZodRawShape} from 'zod';
  2 | import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
  3 | import {ToolCallback} from '@modelcontextprotocol/sdk/server/mcp.js';
  4 | import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';
  5 | import Stripe from 'stripe';
  6 | 
  7 | /*
  8 |  * This supports one-time payment, subscription, usage-based metered payment.
  9 |  * For usage-based, set a `meterEvent`
 10 |  */
 11 | export type PaidToolOptions = {
 12 |   paymentReason: string;
 13 |   meterEvent?: string;
 14 |   stripeSecretKey: string;
 15 |   userEmail: string;
 16 |   checkout: Stripe.Checkout.SessionCreateParams;
 17 | };
 18 | 
 19 | export async function registerPaidTool<Args extends ZodRawShape>(
 20 |   mcpServer: McpServer,
 21 |   toolName: string,
 22 |   toolDescription: string,
 23 |   paramsSchema: Args,
 24 |   // @ts-ignore: The typescript compiler complains this is an infinitely deep type
 25 |   paidCallback: ToolCallback<Args>,
 26 |   options: PaidToolOptions
 27 | ) {
 28 |   const priceId = options.checkout.line_items?.find((li) => li.price)?.price;
 29 | 
 30 |   if (!priceId) {
 31 |     throw new Error(
 32 |       'Price ID is required for a paid MCP tool. Learn more about prices: https://docs.stripe.com/products-prices/how-products-and-prices-work'
 33 |     );
 34 |   }
 35 | 
 36 |   const stripe = new Stripe(options.stripeSecretKey, {
 37 |     appInfo: {
 38 |       name: 'stripe-agent-toolkit-mcp-payments',
 39 |       version: '0.8.1',
 40 |       url: 'https://github.com/stripe/ai',
 41 |     },
 42 |   });
 43 | 
 44 |   const getCurrentCustomerID = async () => {
 45 |     const customers = await stripe.customers.list({
 46 |       email: options.userEmail,
 47 |     });
 48 |     let customerId: null | string = null;
 49 |     if (customers.data.length > 0) {
 50 |       customerId =
 51 |         customers.data.find((customer) => {
 52 |           return customer.email === options.userEmail;
 53 |         })?.id || null;
 54 |     }
 55 |     if (!customerId) {
 56 |       const customer = await stripe.customers.create({
 57 |         email: options.userEmail,
 58 |       });
 59 |       customerId = customer.id;
 60 |     }
 61 | 
 62 |     return customerId;
 63 |   };
 64 | 
 65 |   const isToolPaidFor = async (toolName: string, customerId: string) => {
 66 |     // Check for paid checkout session for this tool (by metadata)
 67 |     const sessions = await stripe.checkout.sessions.list({
 68 |       customer: customerId,
 69 |       limit: 100,
 70 |     });
 71 |     const paidSession = sessions.data.find(
 72 |       (session) =>
 73 |         session.metadata?.toolName === toolName &&
 74 |         session.payment_status === 'paid'
 75 |     );
 76 | 
 77 |     if (paidSession?.subscription) {
 78 |       // Check for active subscription for the priceId
 79 |       const subs = await stripe.subscriptions.list({
 80 |         customer: customerId || '',
 81 |         status: 'active',
 82 |       });
 83 |       const activeSub = subs.data.find((sub) =>
 84 |         sub.items.data.find((item) => item.price.id === priceId)
 85 |       );
 86 |       if (activeSub) {
 87 |         return true;
 88 |       }
 89 |     }
 90 | 
 91 |     if (paidSession) {
 92 |       return true;
 93 |     }
 94 |     return false;
 95 |   };
 96 | 
 97 |   const createCheckoutSession = async (
 98 |     paymentType: string,
 99 |     customerId: string
100 |   ): Promise<CallToolResult | null> => {
101 |     try {
102 |       const session = await stripe.checkout.sessions.create({
103 |         ...options.checkout,
104 |         metadata: {
105 |           ...options.checkout.metadata,
106 |           toolName,
107 |         },
108 |         customer: customerId || undefined,
109 |       });
110 |       const result = {
111 |         status: 'payment_required',
112 |         data: {
113 |           paymentType,
114 |           checkoutUrl: session.url,
115 |           paymentReason: options.paymentReason,
116 |         },
117 |       };
118 |       return {
119 |         content: [
120 |           {
121 |             type: 'text',
122 |             text: JSON.stringify(result),
123 |           } as {type: 'text'; text: string},
124 |         ],
125 |       };
126 |     } catch (error: unknown) {
127 |       let errMsg = 'Unknown error';
128 |       if (typeof error === 'object' && error !== null) {
129 |         if (
130 |           'raw' in error &&
131 |           typeof (error as {raw?: {message?: string}}).raw?.message === 'string'
132 |         ) {
133 |           errMsg = (error as {raw: {message: string}}).raw.message;
134 |         } else if (
135 |           'message' in error &&
136 |           typeof (error as {message?: string}).message === 'string'
137 |         ) {
138 |           errMsg = (error as {message: string}).message;
139 |         }
140 |       }
141 |       console.error('Error creating stripe checkout session', errMsg);
142 |       return {
143 |         content: [
144 |           {
145 |             type: 'text',
146 |             text: JSON.stringify({
147 |               status: 'error',
148 |               error: errMsg,
149 |             }),
150 |           } as {type: 'text'; text: string},
151 |         ],
152 |         isError: true,
153 |       };
154 |     }
155 |   };
156 | 
157 |   const recordUsage = async (customerId: string) => {
158 |     if (!options.meterEvent) return;
159 |     await stripe.billing.meterEvents.create({
160 |       event_name: options.meterEvent,
161 |       payload: {
162 |         stripe_customer_id: customerId,
163 |         value: '1',
164 |       },
165 |     });
166 |   };
167 | 
168 |   // biome-ignore lint/suspicious/noExplicitAny: <explanation>
169 |   const callback = async (args: any, extra: any): Promise<CallToolResult> => {
170 |     const customerId = await getCurrentCustomerID();
171 |     const paidForTool = await isToolPaidFor(toolName, customerId);
172 |     const paymentType = options.meterEvent
173 |       ? 'usageBased'
174 |       : 'oneTimeSubscription';
175 |     if (!paidForTool) {
176 |       const checkoutResult = await createCheckoutSession(
177 |         paymentType,
178 |         customerId
179 |       );
180 |       if (checkoutResult) return checkoutResult;
181 |     }
182 |     if (paymentType === 'usageBased') {
183 |       await recordUsage(customerId);
184 |     }
185 |     // @ts-ignore: The typescript compiler complains this is an infinitely deep type
186 |     return paidCallback(args, extra);
187 |   };
188 | 
189 |   // @ts-ignore: The typescript compiler complains this is an infinitely deep type
190 |   mcpServer.tool(toolName, toolDescription, paramsSchema, callback as any);
191 | 
192 |   await Promise.resolve();
193 | }
194 | 
```

--------------------------------------------------------------------------------
/llm/token-meter/examples/gemini.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Sample Usage: Gemini with Usage Tracking
  3 |  * This demonstrates how to use the generic token meter to automatically report
  4 |  * token usage to Stripe for billing purposes with Google Gemini.
  5 |  */
  6 | 
  7 | import {config} from 'dotenv';
  8 | import {resolve} from 'path';
  9 | import {GoogleGenerativeAI, SchemaType} from '@google/generative-ai';
 10 | import {createTokenMeter} from '..';
 11 | 
 12 | // Load .env from the examples folder
 13 | config({path: resolve(__dirname, '.env')});
 14 | 
 15 | // Load environment variables from .env file
 16 | const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
 17 | const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
 18 | const GOOGLE_GENERATIVE_AI_API_KEY = process.env.GOOGLE_GENERATIVE_AI_API_KEY!;
 19 | 
 20 | // Initialize the standard GoogleGenerativeAI client (no wrapper needed!)
 21 | const genAI = new GoogleGenerativeAI(GOOGLE_GENERATIVE_AI_API_KEY);
 22 | 
 23 | // Create the token meter
 24 | const meter = createTokenMeter(STRIPE_API_KEY);
 25 | 
 26 | // Sample 1: Basic Text Generation (non-streaming)
 27 | async function sampleBasicGeneration() {
 28 |   const model = genAI.getGenerativeModel({
 29 |     model: 'gemini-2.0-flash-exp',
 30 |   });
 31 | 
 32 |   const result = await model.generateContent(
 33 |     'Say "Hello, World!" and nothing else.'
 34 |   );
 35 | 
 36 |   // Meter the response - auto-detects it's Gemini!
 37 |   meter.trackUsage(result, STRIPE_CUSTOMER_ID);
 38 | 
 39 |   const response = result.response;
 40 |   console.log('Response:', response.text());
 41 |   console.log('Usage:', response.usageMetadata);
 42 | }
 43 | 
 44 | // Sample 2: Streaming Text Generation
 45 | async function sampleStreamingGeneration() {
 46 |   const model = genAI.getGenerativeModel({
 47 |     model: 'gemini-2.0-flash-exp',
 48 |   });
 49 | 
 50 |   const streamResult = await model.generateContentStream(
 51 |     'Count from 1 to 5, one number per line.'
 52 |   );
 53 | 
 54 |   // Wrap the stream for metering - Gemini requires model name
 55 |   const meteredStream = meter.trackUsageStreamGemini(
 56 |     streamResult,
 57 |     STRIPE_CUSTOMER_ID,
 58 |     'gemini-2.0-flash-exp'
 59 |   );
 60 | 
 61 |   let fullText = '';
 62 |   for await (const chunk of meteredStream.stream) {
 63 |     const chunkText = chunk.text();
 64 |     fullText += chunkText;
 65 |     process.stdout.write(chunkText);
 66 |   }
 67 | 
 68 |   console.log('\n\nFull text:', fullText);
 69 |   console.log('Usage:', (await meteredStream.response).usageMetadata);
 70 | }
 71 | 
 72 | // Sample 3: Function Calling
 73 | async function sampleFunctionCalling() {
 74 |   const tools = [
 75 |     {
 76 |       functionDeclarations: [
 77 |         {
 78 |           name: 'get_weather',
 79 |           description: 'Get the current weather in a location',
 80 |           parameters: {
 81 |             type: SchemaType.OBJECT,
 82 |             properties: {
 83 |               location: {
 84 |                 type: SchemaType.STRING,
 85 |                 description: 'The city and state, e.g. San Francisco, CA',
 86 |               } as any,
 87 |               unit: {
 88 |                 type: SchemaType.STRING,
 89 |                 enum: ['celsius', 'fahrenheit'],
 90 |                 description: 'The unit of temperature',
 91 |               } as any,
 92 |             },
 93 |             required: ['location'],
 94 |           },
 95 |         },
 96 |       ],
 97 |     },
 98 |   ];
 99 | 
100 |   const model = genAI.getGenerativeModel({
101 |     model: 'gemini-2.0-flash-exp',
102 |     tools,
103 |   });
104 | 
105 |   const result = await model.generateContent('What is the weather in New York?');
106 | 
107 |   // Meter the response - auto-detects it's Gemini!
108 |   meter.trackUsage(result, STRIPE_CUSTOMER_ID);
109 | 
110 |   const response = result.response;
111 |   console.log(
112 |     'Response:',
113 |     JSON.stringify(response.candidates?.[0]?.content, null, 2)
114 |   );
115 |   console.log('Usage:', response.usageMetadata);
116 | }
117 | 
118 | // Sample 4: System Instructions
119 | async function sampleSystemInstructions() {
120 |   const model = genAI.getGenerativeModel({
121 |     model: 'gemini-2.0-flash-exp',
122 |     systemInstruction: 'You are a helpful assistant that speaks like a pirate.',
123 |   });
124 | 
125 |   const result = await model.generateContent(
126 |     'Tell me about Paris in 2 sentences.'
127 |   );
128 | 
129 |   // Meter the response - auto-detects it's Gemini!
130 |   meter.trackUsage(result, STRIPE_CUSTOMER_ID);
131 | 
132 |   const response = result.response;
133 |   console.log('Response:', response.text());
134 |   console.log('Usage:', response.usageMetadata);
135 | }
136 | 
137 | // Sample 5: Multi-turn Chat
138 | async function sampleMultiTurnChat() {
139 |   const model = genAI.getGenerativeModel({
140 |     model: 'gemini-2.0-flash-exp',
141 |   });
142 | 
143 |   const chat = model.startChat({
144 |     history: [
145 |       {role: 'user', parts: [{text: 'What is the capital of France?'}]},
146 |       {role: 'model', parts: [{text: 'The capital of France is Paris.'}]},
147 |     ],
148 |   });
149 | 
150 |   const result = await chat.sendMessage('What is its population?');
151 | 
152 |   // Meter the response - auto-detects it's Gemini!
153 |   meter.trackUsage(result, STRIPE_CUSTOMER_ID);
154 | 
155 |   console.log('Response:', result.response.text());
156 |   console.log('Usage:', result.response.usageMetadata);
157 | }
158 | 
159 | // Run all samples
160 | async function runAllSamples() {
161 |   console.log('Starting Gemini Usage Tracking Examples');
162 |   console.log(
163 |     'These examples show how to use the generic meter with Gemini and Stripe billing\n'
164 |   );
165 | 
166 |   try {
167 |     console.log('\n' + '='.repeat(80));
168 |     console.log('Sample 1: Basic Text Generation');
169 |     console.log('='.repeat(80));
170 |     await sampleBasicGeneration();
171 | 
172 |     console.log('\n' + '='.repeat(80));
173 |     console.log('Sample 2: Streaming Text Generation');
174 |     console.log('='.repeat(80));
175 |     await sampleStreamingGeneration();
176 | 
177 |     console.log('\n' + '='.repeat(80));
178 |     console.log('Sample 3: Function Calling');
179 |     console.log('='.repeat(80));
180 |     await sampleFunctionCalling();
181 | 
182 |     console.log('\n' + '='.repeat(80));
183 |     console.log('Sample 4: System Instructions');
184 |     console.log('='.repeat(80));
185 |     await sampleSystemInstructions();
186 | 
187 |     console.log('\n' + '='.repeat(80));
188 |     console.log('Sample 5: Multi-turn Chat');
189 |     console.log('='.repeat(80));
190 |     await sampleMultiTurnChat();
191 | 
192 |     console.log('\n' + '='.repeat(80));
193 |     console.log('All examples completed successfully!');
194 |     console.log('='.repeat(80));
195 |   } catch (error) {
196 |     console.error('\n❌ Sample failed:', error);
197 |     throw error;
198 |   }
199 | }
200 | 
201 | // Run the samples
202 | runAllSamples().catch(console.error);
203 | 
204 | 
```
Page 3/7FirstPrevNextLast