#
tokens: 48182/50000 22/256 files (page 3/5)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 5. Use http://codebase.md/stripe/agent-toolkit?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   └── workflows
│       ├── main.yml
│       ├── npm_release_shared.yml
│       ├── pypi_release.yml
│       └── sync-skills.yml
├── .gitignore
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── gemini-extension.json
├── LICENSE
├── llm
│   ├── ai-sdk
│   │   ├── jest.config.ts
│   │   ├── LICENSE
│   │   ├── meter
│   │   │   ├── examples
│   │   │   │   ├── .env.example
│   │   │   │   ├── .gitignore
│   │   │   │   ├── anthropic.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── openai.ts
│   │   │   │   ├── README.md
│   │   │   │   └── tsconfig.json
│   │   │   ├── index.ts
│   │   │   ├── meter-event-logging.ts
│   │   │   ├── meter-event-types.ts
│   │   │   ├── README.md
│   │   │   ├── tests
│   │   │   │   ├── ai-sdk-billing-wrapper-anthropic.test.ts
│   │   │   │   ├── ai-sdk-billing-wrapper-general.test.ts
│   │   │   │   ├── ai-sdk-billing-wrapper-google.test.ts
│   │   │   │   ├── ai-sdk-billing-wrapper-openai.test.ts
│   │   │   │   ├── ai-sdk-billing-wrapper-other-providers.test.ts
│   │   │   │   ├── meter-event-logging.test.ts
│   │   │   │   └── model-name-normalization.test.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── types.ts
│   │   │   ├── utils.ts
│   │   │   └── wrapperV2.ts
│   │   ├── package.json
│   │   ├── pnpm-lock.yaml
│   │   ├── provider
│   │   │   ├── examples
│   │   │   │   ├── .env.example
│   │   │   │   ├── .gitignore
│   │   │   │   ├── anthropic.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── openai.ts
│   │   │   │   ├── README.md
│   │   │   │   └── tsconfig.json
│   │   │   ├── index.ts
│   │   │   ├── README.md
│   │   │   ├── stripe-language-model.ts
│   │   │   ├── stripe-provider.ts
│   │   │   ├── tests
│   │   │   │   ├── stripe-language-model.test.ts
│   │   │   │   ├── stripe-provider.test.ts
│   │   │   │   └── utils.test.ts
│   │   │   ├── tsconfig.build.json
│   │   │   ├── tsconfig.json
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   ├── README.md
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   ├── README.md
│   └── token-meter
│       ├── examples
│       │   ├── anthropic.ts
│       │   ├── gemini.ts
│       │   └── openai.ts
│       ├── index.ts
│       ├── jest.config.ts
│       ├── LICENSE
│       ├── meter-event-logging.ts
│       ├── meter-event-types.ts
│       ├── package.json
│       ├── pnpm-lock.yaml
│       ├── README.md
│       ├── tests
│       │   ├── meter-event-logging.test.ts
│       │   ├── model-name-normalization.test.ts
│       │   ├── token-meter-anthropic.test.ts
│       │   ├── token-meter-gemini.test.ts
│       │   ├── token-meter-general.test.ts
│       │   ├── token-meter-openai.test.ts
│       │   └── type-detection.test.ts
│       ├── token-meter.ts
│       ├── tsconfig.build.json
│       ├── tsconfig.json
│       ├── types.ts
│       └── utils
│           └── type-detection.ts
├── README.md
├── SECURITY.md
├── skills
│   ├── get-started-kiro.md
│   ├── README.md
│   ├── stripe-best-practices.md
│   └── sync.js
└── tools
    ├── modelcontextprotocol
    │   ├── .dxtignore
    │   ├── .gitignore
    │   ├── .node-version
    │   ├── .prettierrc
    │   ├── build-dxt.js
    │   ├── Dockerfile
    │   ├── eslint.config.mjs
    │   ├── jest.config.ts
    │   ├── LICENSE
    │   ├── manifest.json
    │   ├── package.json
    │   ├── pnpm-lock.yaml
    │   ├── README.md
    │   ├── server.json
    │   ├── src
    │   │   ├── index.ts
    │   │   └── test
    │   │       └── index.test.ts
    │   ├── stripe_icon.png
    │   └── tsconfig.json
    ├── python
    │   ├── .editorconfig
    │   ├── .flake8
    │   ├── examples
    │   │   ├── crewai
    │   │   │   ├── .env.template
    │   │   │   ├── main.py
    │   │   │   └── README.md
    │   │   ├── langchain
    │   │   │   ├── __init__.py
    │   │   │   ├── .env.template
    │   │   │   ├── main.py
    │   │   │   └── README.md
    │   │   ├── openai
    │   │   │   ├── .env.template
    │   │   │   ├── customer_support
    │   │   │   │   ├── .env.template
    │   │   │   │   ├── emailer.py
    │   │   │   │   ├── env.py
    │   │   │   │   ├── main.py
    │   │   │   │   ├── pyproject.toml
    │   │   │   │   ├── README.md
    │   │   │   │   ├── repl.py
    │   │   │   │   └── support_agent.py
    │   │   │   ├── file_search
    │   │   │   │   ├── main.py
    │   │   │   │   └── README.md
    │   │   │   └── web_search
    │   │   │       ├── .env.template
    │   │   │       ├── main.py
    │   │   │       └── README.md
    │   │   └── strands
    │   │       └── main.py
    │   ├── Makefile
    │   ├── pyproject.toml
    │   ├── README.md
    │   ├── requirements.txt
    │   ├── stripe_agent_toolkit
    │   │   ├── __init__.py
    │   │   ├── api.py
    │   │   ├── configuration.py
    │   │   ├── crewai
    │   │   │   ├── tool.py
    │   │   │   └── toolkit.py
    │   │   ├── functions.py
    │   │   ├── langchain
    │   │   │   ├── tool.py
    │   │   │   └── toolkit.py
    │   │   ├── openai
    │   │   │   ├── hooks.py
    │   │   │   ├── tool.py
    │   │   │   └── toolkit.py
    │   │   ├── prompts.py
    │   │   ├── schema.py
    │   │   ├── strands
    │   │   │   ├── __init__.py
    │   │   │   ├── hooks.py
    │   │   │   ├── tool.py
    │   │   │   └── toolkit.py
    │   │   └── tools.py
    │   └── tests
    │       ├── __init__.py
    │       ├── test_configuration.py
    │       └── test_functions.py
    ├── README.md
    └── typescript
        ├── .gitignore
        ├── .prettierrc
        ├── eslint.config.mjs
        ├── examples
        │   ├── ai-sdk
        │   │   ├── .env.template
        │   │   ├── index.ts
        │   │   ├── package.json
        │   │   ├── README.md
        │   │   └── tsconfig.json
        │   ├── cloudflare
        │   │   ├── .dev.vars.example
        │   │   ├── .gitignore
        │   │   ├── biome.json
        │   │   ├── package.json
        │   │   ├── README.md
        │   │   ├── src
        │   │   │   ├── app.ts
        │   │   │   ├── imageGenerator.ts
        │   │   │   ├── index.ts
        │   │   │   ├── oauth.ts
        │   │   │   └── utils.ts
        │   │   ├── tsconfig.json
        │   │   ├── worker-configuration.d.ts
        │   │   └── wrangler.jsonc
        │   ├── langchain
        │   │   ├── .env.template
        │   │   ├── index.ts
        │   │   ├── package.json
        │   │   ├── README.md
        │   │   └── tsconfig.json
        │   └── openai
        │       ├── .env.template
        │       ├── index.ts
        │       ├── package.json
        │       ├── README.md
        │       └── tsconfig.json
        ├── jest.config.ts
        ├── LICENSE
        ├── package.json
        ├── pnpm-lock.yaml
        ├── pnpm-workspace.yaml
        ├── README.md
        ├── src
        │   ├── ai-sdk
        │   │   ├── index.ts
        │   │   ├── tool.ts
        │   │   └── toolkit.ts
        │   ├── cloudflare
        │   │   ├── index.ts
        │   │   └── README.md
        │   ├── langchain
        │   │   ├── index.ts
        │   │   ├── tool.ts
        │   │   └── toolkit.ts
        │   ├── modelcontextprotocol
        │   │   ├── index.ts
        │   │   ├── README.md
        │   │   ├── register-paid-tool.ts
        │   │   └── toolkit.ts
        │   ├── openai
        │   │   ├── index.ts
        │   │   └── toolkit.ts
        │   ├── shared
        │   │   ├── api.ts
        │   │   ├── balance
        │   │   │   └── retrieveBalance.ts
        │   │   ├── configuration.ts
        │   │   ├── coupons
        │   │   │   ├── createCoupon.ts
        │   │   │   └── listCoupons.ts
        │   │   ├── customers
        │   │   │   ├── createCustomer.ts
        │   │   │   └── listCustomers.ts
        │   │   ├── disputes
        │   │   │   ├── listDisputes.ts
        │   │   │   └── updateDispute.ts
        │   │   ├── documentation
        │   │   │   └── searchDocumentation.ts
        │   │   ├── invoiceItems
        │   │   │   └── createInvoiceItem.ts
        │   │   ├── invoices
        │   │   │   ├── createInvoice.ts
        │   │   │   ├── finalizeInvoice.ts
        │   │   │   └── listInvoices.ts
        │   │   ├── paymentIntents
        │   │   │   └── listPaymentIntents.ts
        │   │   ├── paymentLinks
        │   │   │   └── createPaymentLink.ts
        │   │   ├── prices
        │   │   │   ├── createPrice.ts
        │   │   │   └── listPrices.ts
        │   │   ├── products
        │   │   │   ├── createProduct.ts
        │   │   │   └── listProducts.ts
        │   │   ├── refunds
        │   │   │   └── createRefund.ts
        │   │   ├── subscriptions
        │   │   │   ├── cancelSubscription.ts
        │   │   │   ├── listSubscriptions.ts
        │   │   │   └── updateSubscription.ts
        │   │   └── tools.ts
        │   └── test
        │       ├── modelcontextprotocol
        │       │   └── register-paid-tool.test.ts
        │       └── shared
        │           ├── balance
        │           │   ├── functions.test.ts
        │           │   └── parameters.test.ts
        │           ├── configuration.test.ts
        │           ├── customers
        │           │   ├── functions.test.ts
        │           │   └── parameters.test.ts
        │           ├── disputes
        │           │   └── functions.test.ts
        │           ├── documentation
        │           │   ├── functions.test.ts
        │           │   └── parameters.test.ts
        │           ├── invoiceItems
        │           │   ├── functions.test.ts
        │           │   ├── parameters.test.ts
        │           │   └── prompts.test.ts
        │           ├── invoices
        │           │   ├── functions.test.ts
        │           │   ├── parameters.test.ts
        │           │   └── prompts.test.ts
        │           ├── paymentIntents
        │           │   ├── functions.test.ts
        │           │   ├── parameters.test.ts
        │           │   └── prompts.test.ts
        │           ├── paymentLinks
        │           │   ├── functions.test.ts
        │           │   ├── parameters.test.ts
        │           │   └── prompts.test.ts
        │           ├── prices
        │           │   ├── functions.test.ts
        │           │   └── parameters.test.ts
        │           ├── products
        │           │   ├── functions.test.ts
        │           │   └── parameters.test.ts
        │           ├── refunds
        │           │   ├── functions.test.ts
        │           │   └── parameters.test.ts
        │           └── subscriptions
        │               ├── functions.test.ts
        │               ├── parameters.test.ts
        │               └── prompts.test.ts
        ├── tsconfig.json
        └── tsup.config.ts
```

# Files

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

```typescript
/**
 * Tests for model name normalization across all providers
 * Ensures all model names are properly formatted for meter events
 */

import Stripe from 'stripe';
import {sendMeterEventsToStripe} from '../meter-event-logging';
import type {UsageEvent, MeterConfig} from '../types';

// Mock Stripe
jest.mock('stripe');

describe('Model Name Normalization - Comprehensive', () => {
  let mockStripe: jest.Mocked<any>;

  beforeEach(() => {
    jest.clearAllMocks();

    mockStripe = {
      v2: {
        billing: {
          meterEvents: {
            create: jest.fn().mockResolvedValue({}),
          },
        },
      },
    };

    (Stripe as unknown as jest.Mock).mockImplementation(() => mockStripe);
  });

  describe('Google/Gemini Models', () => {
    const testCases = [
      {model: 'gemini-2.5-pro', expected: 'google/gemini-2.5-pro'},
      {model: 'gemini-2.5-flash', expected: 'google/gemini-2.5-flash'},
      {model: 'gemini-2.5-flash-lite', expected: 'google/gemini-2.5-flash-lite'},
      {model: 'gemini-2.0-flash', expected: 'google/gemini-2.0-flash'},
      {model: 'gemini-2.0-flash-lite', expected: 'google/gemini-2.0-flash-lite'},
      // Default fallback model
      {model: 'gemini-1.5-pro', expected: 'google/gemini-1.5-pro'},
    ];

    testCases.forEach(({model, expected}) => {
      it(`should normalize ${model} to ${expected}`, async () => {
        const config: MeterConfig = {};
        const event: UsageEvent = {
          model,
          provider: 'google',
          usage: {inputTokens: 100, outputTokens: 50},
          stripeCustomerId: 'cus_123',
        };

        await sendMeterEventsToStripe(mockStripe, config, event);

        const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
        expect(call.payload.model).toBe(expected);
      });
    });
  });

  describe('OpenAI Models', () => {
    const testCases = [
      {model: 'gpt-5', expected: 'openai/gpt-5'},
      {model: 'gpt-5-mini', expected: 'openai/gpt-5-mini'},
      {model: 'gpt-5-nano', expected: 'openai/gpt-5-nano'},
      {model: 'gpt-5-chat-latest', expected: 'openai/gpt-5-chat-latest'},
      {model: 'gpt-4.1', expected: 'openai/gpt-4.1'},
      {model: 'gpt-4.1-mini', expected: 'openai/gpt-4.1-mini'},
      {model: 'gpt-4.1-nano', expected: 'openai/gpt-4.1-nano'},
      {model: 'gpt-4o', expected: 'openai/gpt-4o'},
      {model: 'gpt-4o-mini', expected: 'openai/gpt-4o-mini'},
      {model: 'o4-mini', expected: 'openai/o4-mini'},
      {model: 'o3', expected: 'openai/o3'},
      {model: 'o3-mini', expected: 'openai/o3-mini'},
      {model: 'o3-pro', expected: 'openai/o3-pro'},
      {model: 'o1', expected: 'openai/o1'},
      {model: 'o1-mini', expected: 'openai/o1-mini'},
      {model: 'o1-pro', expected: 'openai/o1-pro'},
      // With date suffixes that should be removed
      {model: 'gpt-4o-2024-11-20', expected: 'openai/gpt-4o'},
      {model: 'gpt-4o-mini-2024-07-18', expected: 'openai/gpt-4o-mini'},
      {model: 'o1-2024-12-17', expected: 'openai/o1'},
      {model: 'o1-mini-2024-09-12', expected: 'openai/o1-mini'},
      // Exception case
      {model: 'gpt-4o-2024-05-13', expected: 'openai/gpt-4o-2024-05-13'},
    ];

    testCases.forEach(({model, expected}) => {
      it(`should normalize ${model} to ${expected}`, async () => {
        const config: MeterConfig = {};
        const event: UsageEvent = {
          model,
          provider: 'openai',
          usage: {inputTokens: 100, outputTokens: 50},
          stripeCustomerId: 'cus_123',
        };

        await sendMeterEventsToStripe(mockStripe, config, event);

        const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
        expect(call.payload.model).toBe(expected);
      });
    });
  });

  describe('Anthropic Models', () => {
    const testCases = [
      {model: 'claude-opus-4-1', expected: 'anthropic/claude-opus-4.1'},
      {model: 'claude-opus-4', expected: 'anthropic/claude-opus-4'},
      {model: 'claude-sonnet-4', expected: 'anthropic/claude-sonnet-4'},
      {model: 'claude-3-7-sonnet', expected: 'anthropic/claude-3.7-sonnet'},
      {model: 'claude-3-7-sonnet-latest', expected: 'anthropic/claude-3.7-sonnet'},
      {model: 'claude-3-5-haiku', expected: 'anthropic/claude-3.5-haiku'},
      {model: 'claude-3-5-haiku-latest', expected: 'anthropic/claude-3.5-haiku'},
      {model: 'claude-3-haiku', expected: 'anthropic/claude-3-haiku'},
      // With date suffixes that should be removed
      {model: 'claude-opus-4-1-20241231', expected: 'anthropic/claude-opus-4.1'},
      {model: 'claude-3-5-sonnet-20241022', expected: 'anthropic/claude-3.5-sonnet'},
      {model: 'claude-3-5-haiku-20241022', expected: 'anthropic/claude-3.5-haiku'},
      {model: 'claude-3-haiku-20240307', expected: 'anthropic/claude-3-haiku'},
      // With latest suffixes that should be removed
      {model: 'claude-opus-4-latest', expected: 'anthropic/claude-opus-4'},
      {model: 'claude-sonnet-4-latest', expected: 'anthropic/claude-sonnet-4'},
    ];

    testCases.forEach(({model, expected}) => {
      it(`should normalize ${model} to ${expected}`, async () => {
        const config: MeterConfig = {};
        const event: UsageEvent = {
          model,
          provider: 'anthropic',
          usage: {inputTokens: 100, outputTokens: 50},
          stripeCustomerId: 'cus_123',
        };

        await sendMeterEventsToStripe(mockStripe, config, event);

        const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
        expect(call.payload.model).toBe(expected);
      });
    });
  });
});


```

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

```typescript
/**
 * Tests for model name normalization across all providers
 * Ensures all model names are properly formatted for meter events
 */

import Stripe from 'stripe';
import {sendMeterEventsToStripe} from '../meter-event-logging';
import type {UsageEvent, MeterConfig} from '../meter-event-types';

// Mock Stripe
jest.mock('stripe');

describe('Model Name Normalization - Comprehensive', () => {
  let mockStripe: jest.Mocked<any>;

  beforeEach(() => {
    jest.clearAllMocks();

    mockStripe = {
      v2: {
        billing: {
          meterEvents: {
            create: jest.fn().mockResolvedValue({}),
          },
        },
      },
    };

    (Stripe as unknown as jest.Mock).mockImplementation(() => mockStripe);
  });

  describe('Google/Gemini Models', () => {
    const testCases = [
      {model: 'gemini-2.5-pro', expected: 'google/gemini-2.5-pro'},
      {model: 'gemini-2.5-flash', expected: 'google/gemini-2.5-flash'},
      {model: 'gemini-2.5-flash-lite', expected: 'google/gemini-2.5-flash-lite'},
      {model: 'gemini-2.0-flash', expected: 'google/gemini-2.0-flash'},
      {model: 'gemini-2.0-flash-lite', expected: 'google/gemini-2.0-flash-lite'},
      // Default fallback model
      {model: 'gemini-1.5-pro', expected: 'google/gemini-1.5-pro'},
    ];

    testCases.forEach(({model, expected}) => {
      it(`should normalize ${model} to ${expected}`, async () => {
        const config: MeterConfig = {};
        const event: UsageEvent = {
          model,
          provider: 'google',
          usage: {inputTokens: 100, outputTokens: 50},
          stripeCustomerId: 'cus_123',
        };

        await sendMeterEventsToStripe(mockStripe, config, event);

        const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
        expect(call.payload.model).toBe(expected);
      });
    });
  });

  describe('OpenAI Models', () => {
    const testCases = [
      {model: 'gpt-5', expected: 'openai/gpt-5'},
      {model: 'gpt-5-mini', expected: 'openai/gpt-5-mini'},
      {model: 'gpt-5-nano', expected: 'openai/gpt-5-nano'},
      {model: 'gpt-5-chat-latest', expected: 'openai/gpt-5-chat-latest'},
      {model: 'gpt-4.1', expected: 'openai/gpt-4.1'},
      {model: 'gpt-4.1-mini', expected: 'openai/gpt-4.1-mini'},
      {model: 'gpt-4.1-nano', expected: 'openai/gpt-4.1-nano'},
      {model: 'gpt-4o', expected: 'openai/gpt-4o'},
      {model: 'gpt-4o-mini', expected: 'openai/gpt-4o-mini'},
      {model: 'o4-mini', expected: 'openai/o4-mini'},
      {model: 'o3', expected: 'openai/o3'},
      {model: 'o3-mini', expected: 'openai/o3-mini'},
      {model: 'o3-pro', expected: 'openai/o3-pro'},
      {model: 'o1', expected: 'openai/o1'},
      {model: 'o1-mini', expected: 'openai/o1-mini'},
      {model: 'o1-pro', expected: 'openai/o1-pro'},
      // With date suffixes that should be removed
      {model: 'gpt-4o-2024-11-20', expected: 'openai/gpt-4o'},
      {model: 'gpt-4o-mini-2024-07-18', expected: 'openai/gpt-4o-mini'},
      {model: 'o1-2024-12-17', expected: 'openai/o1'},
      {model: 'o1-mini-2024-09-12', expected: 'openai/o1-mini'},
      // Exception case
      {model: 'gpt-4o-2024-05-13', expected: 'openai/gpt-4o-2024-05-13'},
    ];

    testCases.forEach(({model, expected}) => {
      it(`should normalize ${model} to ${expected}`, async () => {
        const config: MeterConfig = {};
        const event: UsageEvent = {
          model,
          provider: 'openai',
          usage: {inputTokens: 100, outputTokens: 50},
          stripeCustomerId: 'cus_123',
        };

        await sendMeterEventsToStripe(mockStripe, config, event);

        const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
        expect(call.payload.model).toBe(expected);
      });
    });
  });

  describe('Anthropic Models', () => {
    const testCases = [
      {model: 'claude-opus-4-1', expected: 'anthropic/claude-opus-4.1'},
      {model: 'claude-opus-4', expected: 'anthropic/claude-opus-4'},
      {model: 'claude-sonnet-4', expected: 'anthropic/claude-sonnet-4'},
      {model: 'claude-3-7-sonnet', expected: 'anthropic/claude-3.7-sonnet'},
      {model: 'claude-3-7-sonnet-latest', expected: 'anthropic/claude-3.7-sonnet'},
      {model: 'claude-3-5-haiku', expected: 'anthropic/claude-3.5-haiku'},
      {model: 'claude-3-5-haiku-latest', expected: 'anthropic/claude-3.5-haiku'},
      {model: 'claude-3-haiku', expected: 'anthropic/claude-3-haiku'},
      // With date suffixes that should be removed
      {model: 'claude-opus-4-1-20241231', expected: 'anthropic/claude-opus-4.1'},
      {model: 'claude-3-5-sonnet-20241022', expected: 'anthropic/claude-3.5-sonnet'},
      {model: 'claude-3-5-haiku-20241022', expected: 'anthropic/claude-3.5-haiku'},
      {model: 'claude-3-haiku-20240307', expected: 'anthropic/claude-3-haiku'},
      // With latest suffixes that should be removed
      {model: 'claude-opus-4-latest', expected: 'anthropic/claude-opus-4'},
      {model: 'claude-sonnet-4-latest', expected: 'anthropic/claude-sonnet-4'},
    ];

    testCases.forEach(({model, expected}) => {
      it(`should normalize ${model} to ${expected}`, async () => {
        const config: MeterConfig = {};
        const event: UsageEvent = {
          model,
          provider: 'anthropic',
          usage: {inputTokens: 100, outputTokens: 50},
          stripeCustomerId: 'cus_123',
        };

        await sendMeterEventsToStripe(mockStripe, config, event);

        const call = mockStripe.v2.billing.meterEvents.create.mock.calls[0][0];
        expect(call.payload.model).toBe(expected);
      });
    });
  });
});


```

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

```typescript
/**
 * Sample Usage: Anthropic with Usage Tracking
 * This demonstrates how to use the generic token meter to automatically report
 * token usage to Stripe for billing purposes with Anthropic.
 */

import {config} from 'dotenv';
import {resolve} from 'path';
import Anthropic from '@anthropic-ai/sdk';
import {createTokenMeter} from '..';

// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});

// Load environment variables from .env file
const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY!;

// Initialize the standard Anthropic client (no wrapper needed!)
const anthropic = new Anthropic({
  apiKey: ANTHROPIC_API_KEY,
});

// Create the token meter
const meter = createTokenMeter(STRIPE_API_KEY);

// Sample 1: Basic Message Completion (non-streaming)
async function sampleBasicMessage() {
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 20,
    messages: [
      {role: 'user', content: 'Say "Hello, World!" and nothing else.'},
    ],
  });

  // Meter the response - auto-detects it's Anthropic!
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', response.content[0]);
  console.log('Usage:', response.usage);
}

// Sample 2: Streaming Message Completion
async function sampleStreamingMessage() {
  const stream = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 50,
    messages: [
      {role: 'user', content: 'Count from 1 to 5, one number per line.'},
    ],
    stream: true,
  });

  // Wrap the stream for metering - auto-detects it's Anthropic!
  const meteredStream = meter.trackUsageStreamAnthropic(stream, STRIPE_CUSTOMER_ID);

  let fullContent = '';

  for await (const event of meteredStream) {
    if (
      event.type === 'content_block_delta' &&
      event.delta.type === 'text_delta'
    ) {
      const content = event.delta.text;
      fullContent += content;
      process.stdout.write(content);
    }
  }

  console.log('\n\nFull content:', fullContent);
}

// Sample 3: Message with Tools
async function sampleMessageWithTools() {
  const tools: any[] = [
    {
      name: 'get_weather',
      description: 'Get the current weather in a location',
      input_schema: {
        type: 'object',
        properties: {
          location: {
            type: 'string',
            description: 'The city and state, e.g. San Francisco, CA',
          },
          unit: {
            type: 'string',
            enum: ['celsius', 'fahrenheit'],
            description: 'The unit of temperature',
          },
        },
        required: ['location'],
      },
    },
  ];

  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 100,
    messages: [{role: 'user', content: 'What is the weather in New York?'}],
    tools,
  });

  // Meter the response - auto-detects it's Anthropic!
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', JSON.stringify(response.content, null, 2));
  console.log('Usage:', response.usage);
}

// Sample 4: Message with System Prompt
async function sampleMessageWithSystem() {
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 50,
    system: 'You are a helpful assistant that speaks like a pirate.',
    messages: [{role: 'user', content: 'Tell me about Paris.'}],
  });

  // Meter the response - auto-detects it's Anthropic!
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', response.content[0]);
  console.log('Usage:', response.usage);
}

// Sample 5: Multi-turn Conversation
async function sampleConversation() {
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 50,
    messages: [
      {role: 'user', content: 'What is the capital of France?'},
      {role: 'assistant', content: 'The capital of France is Paris.'},
      {role: 'user', content: 'What is its population?'},
    ],
  });

  // Meter the response - auto-detects it's Anthropic!
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', response.content[0]);
  console.log('Usage:', response.usage);
}

// Run all samples
async function runAllSamples() {
  console.log('Starting Anthropic Usage Tracking Examples');
  console.log(
    'These examples show how to use the generic meter with Anthropic and Stripe billing\n'
  );

  try {
    console.log('\n' + '='.repeat(80));
    console.log('Sample 1: Basic Message');
    console.log('='.repeat(80));
    await sampleBasicMessage();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 2: Streaming Message');
    console.log('='.repeat(80));
    await sampleStreamingMessage();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 3: Message with Tools');
    console.log('='.repeat(80));
    await sampleMessageWithTools();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 4: Message with System Prompt');
    console.log('='.repeat(80));
    await sampleMessageWithSystem();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 5: Multi-turn Conversation');
    console.log('='.repeat(80));
    await sampleConversation();

    console.log('\n' + '='.repeat(80));
    console.log('All examples completed successfully!');
    console.log('='.repeat(80));
  } catch (error) {
    console.error('\n❌ Sample failed:', error);
    throw error;
  }
}

// Run the samples
runAllSamples().catch(console.error);


```

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

```typescript
import {z, type ZodRawShape} from 'zod';
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {ToolCallback} from '@modelcontextprotocol/sdk/server/mcp.js';
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';
import Stripe from 'stripe';

/*
 * This supports one-time payment, subscription, usage-based metered payment.
 * For usage-based, set a `meterEvent`
 */
export type PaidToolOptions = {
  paymentReason: string;
  meterEvent?: string;
  stripeSecretKey: string;
  userEmail: string;
  checkout: Stripe.Checkout.SessionCreateParams;
};

export async function registerPaidTool<Args extends ZodRawShape>(
  mcpServer: McpServer,
  toolName: string,
  toolDescription: string,
  paramsSchema: Args,
  // @ts-ignore: The typescript compiler complains this is an infinitely deep type
  paidCallback: ToolCallback<Args>,
  options: PaidToolOptions
) {
  const priceId = options.checkout.line_items?.find((li) => li.price)?.price;

  if (!priceId) {
    throw new Error(
      'Price ID is required for a paid MCP tool. Learn more about prices: https://docs.stripe.com/products-prices/how-products-and-prices-work'
    );
  }

  const stripe = new Stripe(options.stripeSecretKey, {
    appInfo: {
      name: 'stripe-agent-toolkit-mcp-payments',
      version: '0.8.1',
      url: 'https://github.com/stripe/ai',
    },
  });

  const getCurrentCustomerID = async () => {
    const customers = await stripe.customers.list({
      email: options.userEmail,
    });
    let customerId: null | string = null;
    if (customers.data.length > 0) {
      customerId =
        customers.data.find((customer) => {
          return customer.email === options.userEmail;
        })?.id || null;
    }
    if (!customerId) {
      const customer = await stripe.customers.create({
        email: options.userEmail,
      });
      customerId = customer.id;
    }

    return customerId;
  };

  const isToolPaidFor = async (toolName: string, customerId: string) => {
    // Check for paid checkout session for this tool (by metadata)
    const sessions = await stripe.checkout.sessions.list({
      customer: customerId,
      limit: 100,
    });
    const paidSession = sessions.data.find(
      (session) =>
        session.metadata?.toolName === toolName &&
        session.payment_status === 'paid'
    );

    if (paidSession?.subscription) {
      // Check for active subscription for the priceId
      const subs = await stripe.subscriptions.list({
        customer: customerId || '',
        status: 'active',
      });
      const activeSub = subs.data.find((sub) =>
        sub.items.data.find((item) => item.price.id === priceId)
      );
      if (activeSub) {
        return true;
      }
    }

    if (paidSession) {
      return true;
    }
    return false;
  };

  const createCheckoutSession = async (
    paymentType: string,
    customerId: string
  ): Promise<CallToolResult | null> => {
    try {
      const session = await stripe.checkout.sessions.create({
        ...options.checkout,
        metadata: {
          ...options.checkout.metadata,
          toolName,
        },
        customer: customerId || undefined,
      });
      const result = {
        status: 'payment_required',
        data: {
          paymentType,
          checkoutUrl: session.url,
          paymentReason: options.paymentReason,
        },
      };
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result),
          } as {type: 'text'; text: string},
        ],
      };
    } catch (error: unknown) {
      let errMsg = 'Unknown error';
      if (typeof error === 'object' && error !== null) {
        if (
          'raw' in error &&
          typeof (error as {raw?: {message?: string}}).raw?.message === 'string'
        ) {
          errMsg = (error as {raw: {message: string}}).raw.message;
        } else if (
          'message' in error &&
          typeof (error as {message?: string}).message === 'string'
        ) {
          errMsg = (error as {message: string}).message;
        }
      }
      console.error('Error creating stripe checkout session', errMsg);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              status: 'error',
              error: errMsg,
            }),
          } as {type: 'text'; text: string},
        ],
        isError: true,
      };
    }
  };

  const recordUsage = async (customerId: string) => {
    if (!options.meterEvent) return;
    await stripe.billing.meterEvents.create({
      event_name: options.meterEvent,
      payload: {
        stripe_customer_id: customerId,
        value: '1',
      },
    });
  };

  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
  const callback = async (args: any, extra: any): Promise<CallToolResult> => {
    const customerId = await getCurrentCustomerID();
    const paidForTool = await isToolPaidFor(toolName, customerId);
    const paymentType = options.meterEvent
      ? 'usageBased'
      : 'oneTimeSubscription';
    if (!paidForTool) {
      const checkoutResult = await createCheckoutSession(
        paymentType,
        customerId
      );
      if (checkoutResult) return checkoutResult;
    }
    if (paymentType === 'usageBased') {
      await recordUsage(customerId);
    }
    // @ts-ignore: The typescript compiler complains this is an infinitely deep type
    return paidCallback(args, extra);
  };

  // @ts-ignore: The typescript compiler complains this is an infinitely deep type
  mcpServer.tool(toolName, toolDescription, paramsSchema, callback as any);

  await Promise.resolve();
}

```

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

```typescript
/**
 * Sample Usage: Gemini with Usage Tracking
 * This demonstrates how to use the generic token meter to automatically report
 * token usage to Stripe for billing purposes with Google Gemini.
 */

import {config} from 'dotenv';
import {resolve} from 'path';
import {GoogleGenerativeAI, SchemaType} from '@google/generative-ai';
import {createTokenMeter} from '..';

// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});

// Load environment variables from .env file
const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
const GOOGLE_GENERATIVE_AI_API_KEY = process.env.GOOGLE_GENERATIVE_AI_API_KEY!;

// Initialize the standard GoogleGenerativeAI client (no wrapper needed!)
const genAI = new GoogleGenerativeAI(GOOGLE_GENERATIVE_AI_API_KEY);

// Create the token meter
const meter = createTokenMeter(STRIPE_API_KEY);

// Sample 1: Basic Text Generation (non-streaming)
async function sampleBasicGeneration() {
  const model = genAI.getGenerativeModel({
    model: 'gemini-2.0-flash-exp',
  });

  const result = await model.generateContent(
    'Say "Hello, World!" and nothing else.'
  );

  // Meter the response - auto-detects it's Gemini!
  meter.trackUsage(result, STRIPE_CUSTOMER_ID);

  const response = result.response;
  console.log('Response:', response.text());
  console.log('Usage:', response.usageMetadata);
}

// Sample 2: Streaming Text Generation
async function sampleStreamingGeneration() {
  const model = genAI.getGenerativeModel({
    model: 'gemini-2.0-flash-exp',
  });

  const streamResult = await model.generateContentStream(
    'Count from 1 to 5, one number per line.'
  );

  // Wrap the stream for metering - Gemini requires model name
  const meteredStream = meter.trackUsageStreamGemini(
    streamResult,
    STRIPE_CUSTOMER_ID,
    'gemini-2.0-flash-exp'
  );

  let fullText = '';
  for await (const chunk of meteredStream.stream) {
    const chunkText = chunk.text();
    fullText += chunkText;
    process.stdout.write(chunkText);
  }

  console.log('\n\nFull text:', fullText);
  console.log('Usage:', (await meteredStream.response).usageMetadata);
}

// Sample 3: Function Calling
async function sampleFunctionCalling() {
  const tools = [
    {
      functionDeclarations: [
        {
          name: 'get_weather',
          description: 'Get the current weather in a location',
          parameters: {
            type: SchemaType.OBJECT,
            properties: {
              location: {
                type: SchemaType.STRING,
                description: 'The city and state, e.g. San Francisco, CA',
              } as any,
              unit: {
                type: SchemaType.STRING,
                enum: ['celsius', 'fahrenheit'],
                description: 'The unit of temperature',
              } as any,
            },
            required: ['location'],
          },
        },
      ],
    },
  ];

  const model = genAI.getGenerativeModel({
    model: 'gemini-2.0-flash-exp',
    tools,
  });

  const result = await model.generateContent('What is the weather in New York?');

  // Meter the response - auto-detects it's Gemini!
  meter.trackUsage(result, STRIPE_CUSTOMER_ID);

  const response = result.response;
  console.log(
    'Response:',
    JSON.stringify(response.candidates?.[0]?.content, null, 2)
  );
  console.log('Usage:', response.usageMetadata);
}

// Sample 4: System Instructions
async function sampleSystemInstructions() {
  const model = genAI.getGenerativeModel({
    model: 'gemini-2.0-flash-exp',
    systemInstruction: 'You are a helpful assistant that speaks like a pirate.',
  });

  const result = await model.generateContent(
    'Tell me about Paris in 2 sentences.'
  );

  // Meter the response - auto-detects it's Gemini!
  meter.trackUsage(result, STRIPE_CUSTOMER_ID);

  const response = result.response;
  console.log('Response:', response.text());
  console.log('Usage:', response.usageMetadata);
}

// Sample 5: Multi-turn Chat
async function sampleMultiTurnChat() {
  const model = genAI.getGenerativeModel({
    model: 'gemini-2.0-flash-exp',
  });

  const chat = model.startChat({
    history: [
      {role: 'user', parts: [{text: 'What is the capital of France?'}]},
      {role: 'model', parts: [{text: 'The capital of France is Paris.'}]},
    ],
  });

  const result = await chat.sendMessage('What is its population?');

  // Meter the response - auto-detects it's Gemini!
  meter.trackUsage(result, STRIPE_CUSTOMER_ID);

  console.log('Response:', result.response.text());
  console.log('Usage:', result.response.usageMetadata);
}

// Run all samples
async function runAllSamples() {
  console.log('Starting Gemini Usage Tracking Examples');
  console.log(
    'These examples show how to use the generic meter with Gemini and Stripe billing\n'
  );

  try {
    console.log('\n' + '='.repeat(80));
    console.log('Sample 1: Basic Text Generation');
    console.log('='.repeat(80));
    await sampleBasicGeneration();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 2: Streaming Text Generation');
    console.log('='.repeat(80));
    await sampleStreamingGeneration();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 3: Function Calling');
    console.log('='.repeat(80));
    await sampleFunctionCalling();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 4: System Instructions');
    console.log('='.repeat(80));
    await sampleSystemInstructions();

    console.log('\n' + '='.repeat(80));
    console.log('Sample 5: Multi-turn Chat');
    console.log('='.repeat(80));
    await sampleMultiTurnChat();

    console.log('\n' + '='.repeat(80));
    console.log('All examples completed successfully!');
    console.log('='.repeat(80));
  } catch (error) {
    console.error('\n❌ Sample failed:', error);
    throw error;
  }
}

// Run the samples
runAllSamples().catch(console.error);


```

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

```typescript
/**
 * General tests for AI SDK billing wrapper functionality
 * Tests provider detection, model wrapping, and error handling with mocks
 */

import Stripe from 'stripe';
import {openai} from '@ai-sdk/openai';
import {anthropic} from '@ai-sdk/anthropic';
import {google} from '@ai-sdk/google';
import {meteredModel} from '../index';
import {determineProvider} from '../utils';

// Mock Stripe
jest.mock('stripe');

describe('AI SDK Billing Wrapper - General', () => {
  let mockMeterEventsCreate: jest.Mock;
  const TEST_API_KEY = 'sk_test_mock_key';

  beforeEach(() => {
    mockMeterEventsCreate = jest.fn().mockResolvedValue({});
    
    // Mock the Stripe constructor
    (Stripe as unknown as jest.Mock).mockImplementation(() => ({
      v2: {
        billing: {
          meterEvents: {
            create: mockMeterEventsCreate,
          },
        },
      },
    }));
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('Provider Detection', () => {
    it('should correctly detect OpenAI provider', () => {
      expect(determineProvider('openai')).toBe('openai');
      expect(determineProvider('openai.chat')).toBe('openai');
    });

    it('should correctly detect Anthropic provider', () => {
      expect(determineProvider('anthropic')).toBe('anthropic');
      expect(determineProvider('anthropic.messages')).toBe('anthropic');
    });

    it('should correctly detect Google provider', () => {
      expect(determineProvider('google')).toBe('google');
      expect(determineProvider('google-generative-ai')).toBe('google');
      expect(determineProvider('gemini')).toBe('google');
    });

    it('should correctly detect Azure provider', () => {
      expect(determineProvider('azure')).toBe('azure');
      expect(determineProvider('azure-openai')).toBe('azure');
    });

    it('should correctly detect Bedrock provider', () => {
      expect(determineProvider('bedrock')).toBe('bedrock');
      expect(determineProvider('amazon-bedrock')).toBe('bedrock');
    });

    it('should correctly detect other providers', () => {
      expect(determineProvider('groq')).toBe('groq');
      expect(determineProvider('huggingface')).toBe('huggingface');
      expect(determineProvider('together')).toBe('together');
    });

    it('should return lowercased provider name for unknown providers', () => {
      expect(determineProvider('unknown-provider')).toBe('unknown-provider');
      expect(determineProvider('Custom-Provider')).toBe('custom-provider');
      expect(determineProvider('MY-NEW-AI')).toBe('my-new-ai');
    });
  });

  describe('Model Wrapping', () => {
    it('should return wrapped model with same specification version', () => {
      const originalModel = openai('gpt-4o-mini');
      const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');

      expect(wrappedModel.specificationVersion).toBe(originalModel.specificationVersion);
    });

    it('should preserve model ID', () => {
      const originalModel = openai('gpt-4o-mini');
      const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');

      expect(wrappedModel.modelId).toBe(originalModel.modelId);
    });

    it('should preserve provider', () => {
      const originalModel = openai('gpt-4o-mini');
      const wrappedModel = meteredModel(originalModel, TEST_API_KEY, 'cus_test123');

      expect(wrappedModel.provider).toBe(originalModel.provider);
    });
  });

  describe('Error Handling', () => {
    it('should throw error for model without specification version', () => {
      const mockModel = {
        modelId: 'test-model',
        provider: 'test-provider',
      } as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow('Only LanguageModelV2 models are supported');
    });

    it('should throw error for unsupported specification version', () => {
      const mockModel = {
        modelId: 'test-model',
        provider: 'test-provider',
        specificationVersion: 'v99',
      } as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow('Only LanguageModelV2 models are supported');
    });

    it('should provide clear error messages', () => {
      const mockModel = {
        modelId: 'test-model',
        provider: 'test-provider',
        specificationVersion: 'v1',
      } as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow(/specificationVersion "v2"/);
      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow(/OpenAI, Anthropic, Google/);
    });
  });

  describe('Multi-Provider Integration', () => {
    it('should work with different providers', () => {
      const openaiModel = meteredModel(openai('gpt-4o-mini'), TEST_API_KEY, 'cus_test');
      const anthropicModel = meteredModel(anthropic('claude-3-5-haiku-20241022'), TEST_API_KEY, 'cus_test');
      const googleModel = meteredModel(google('gemini-2.5-flash'), TEST_API_KEY, 'cus_test');

      // Verify all models are wrapped correctly
      expect(openaiModel).toBeDefined();
      expect(anthropicModel).toBeDefined();
      expect(googleModel).toBeDefined();
      
      // Verify model IDs are preserved
      expect(openaiModel.modelId).toBe('gpt-4o-mini');
      expect(anthropicModel.modelId).toBe('claude-3-5-haiku-20241022');
      expect(googleModel.modelId).toBe('gemini-2.5-flash');
    });

    it('should support custom v2 provider', () => {
      const customModel = {
        modelId: 'custom-123',
        provider: 'custom-ai',
        specificationVersion: 'v2',
        doGenerate: jest.fn(),
        doStream: jest.fn(),
      } as any;

      const wrapped = meteredModel(customModel, TEST_API_KEY, 'cus_test');
      
      expect(wrapped).toBeDefined();
      expect(wrapped.provider).toBe('custom-ai');
      expect(wrapped.modelId).toBe('custom-123');
    });
  });
});

```

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

```typescript
import {main} from '../index';
import {parseArgs} from '../index';
import {StripeAgentToolkit} from '@stripe/agent-toolkit/modelcontextprotocol';
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
describe('parseArgs function', () => {
  describe('success cases', () => {
    it('should parse api-key, tools and stripe-header arguments correctly', () => {
      const args = [
        '--api-key=sk_test_123',
        '--tools=all',
        '--stripe-account=acct_123',
      ];
      const options = parseArgs(args);
      expect(options.apiKey).toBe('sk_test_123');
      expect(options.tools).toEqual(['all']);
      expect(options.stripeAccount).toBe('acct_123');
    });

    it('should parse api-key and tools arguments correctly', () => {
      const args = ['--api-key=sk_test_123', '--tools=all'];
      const options = parseArgs(args);
      expect(options.apiKey).toBe('sk_test_123');
      expect(options.tools).toEqual(['all']);
    });

    it('should parse restricted api key correctly', () => {
      const args = ['--api-key=rk_test_123', '--tools=all'];
      const options = parseArgs(args);
      expect(options.apiKey).toBe('rk_test_123');
      expect(options.tools).toEqual(['all']);
    });

    it('if api key set in env variable, should parse tools argument correctly', () => {
      process.env.STRIPE_SECRET_KEY = 'sk_test_123';
      const args = ['--tools=all'];
      const options = parseArgs(args);
      expect(options.apiKey).toBe('sk_test_123');
      expect(options.tools).toEqual(['all']);
    });

    it('if api key set in env variable but also passed into args, should prefer args key', () => {
      process.env.STRIPE_SECRET_KEY = 'sk_test_123';
      const args = ['--api-key=sk_test_456', '--tools=all'];
      const options = parseArgs(args);
      expect(options.apiKey).toBe('sk_test_456');
      expect(options.tools).toEqual(['all']);
      delete process.env.STRIPE_SECRET_KEY;
    });

    it('should parse tools argument correctly if a list of tools is provided', () => {
      const args = [
        '--api-key=sk_test_123',
        '--tools=customers.create,products.read,documentation.read',
      ];
      const options = parseArgs(args);
      expect(options.tools).toEqual([
        'customers.create',
        'products.read',
        'documentation.read',
      ]);
      expect(options.apiKey).toBe('sk_test_123');
    });

    it('ignore all arguments not prefixed with --', () => {
      const args = [
        '--api-key=sk_test_123',
        '--tools=all',
        'stripe-account=acct_123',
      ];
      const options = parseArgs(args);
      expect(options.apiKey).toBe('sk_test_123');
      expect(options.tools).toEqual(['all']);
      expect(options.stripeAccount).toBeUndefined();
    });
  });

  describe('error cases', () => {
    it("should throw an error if api-key does not start with 'sk_' or 'rk_'", () => {
      const args = ['--api-key=test_123', '--tools=all'];
      expect(() => parseArgs(args)).toThrow(
        'API key must start with "sk_" or "rk_".'
      );
    });

    it('should throw an error if api-key is not provided', () => {
      const args = ['--tools=all'];
      expect(() => parseArgs(args)).toThrow(
        'Stripe API key not provided. Please either pass it as an argument --api-key=$KEY or set the STRIPE_SECRET_KEY environment variable.'
      );
    });

    it("should throw an error if stripe-account does not start with 'acct_'", () => {
      const args = [
        '--api-key=sk_test_123',
        '--tools=all',
        '--stripe-account=test_123',
      ];
      expect(() => parseArgs(args)).toThrow(
        'Stripe account must start with "acct_".'
      );
    });

    it('should throw an error if tools argument is not provided', () => {
      const args = ['--api-key=sk_test_123'];
      expect(() => parseArgs(args)).toThrow(
        'The --tools arguments must be provided.'
      );
    });

    it('should throw an error if an invalid argument is provided', () => {
      const args = [
        '--invalid-arg=value',
        '--api-key=sk_test_123',
        '--tools=all',
      ];
      expect(() => parseArgs(args)).toThrow(
        'Invalid argument: invalid-arg. Accepted arguments are: api-key, tools, stripe-account'
      );
    });

    it('should throw an error if tools is not in accepted tool list', () => {
      const args = [
        '--api-key=sk_test_123',
        '--tools=customers.create,products.read,fake.tool',
      ];
      expect(() => parseArgs(args)).toThrow(
        /Invalid tool: fake\.tool\. Accepted tools are: .*$/
      );
    });
  });
});

jest.mock('@stripe/agent-toolkit/modelcontextprotocol');
jest.mock('@modelcontextprotocol/sdk/server/stdio.js');

describe('main function', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should initialize the server with tools=all correctly', async () => {
    process.argv = ['node', 'index.js', '--api-key=sk_test_123', '--tools=all'];

    await main();

    expect(StripeAgentToolkit).toHaveBeenCalledWith({
      secretKey: 'sk_test_123',
      configuration: {
        actions: ALL_ACTIONS,
        context: {mode: 'modelcontextprotocol'},
      },
    });

    expect(StdioServerTransport).toHaveBeenCalled();
  });

  it('should initialize the server with specific list of tools correctly', async () => {
    process.argv = [
      'node',
      'index.js',
      '--api-key=sk_test_123',
      '--tools=customers.create,products.read,documentation.read',
    ];

    await main();

    expect(StripeAgentToolkit).toHaveBeenCalledWith({
      secretKey: 'sk_test_123',
      configuration: {
        actions: {
          customers: {
            create: true,
          },
          products: {
            read: true,
          },
          documentation: {
            read: true,
          },
        },
        context: {
          mode: 'modelcontextprotocol',
        },
      },
    });

    expect(StdioServerTransport).toHaveBeenCalled();
  });

  it('should initialize the server with stripe header', async () => {
    process.argv = [
      'node',
      'index.js',
      '--api-key=sk_test_123',
      '--tools=all',
      '--stripe-account=acct_123',
    ];

    await main();

    expect(StripeAgentToolkit).toHaveBeenCalledWith({
      secretKey: 'sk_test_123',
      configuration: {
        actions: ALL_ACTIONS,
        context: {account: 'acct_123', mode: 'modelcontextprotocol'},
      },
    });

    expect(StdioServerTransport).toHaveBeenCalled();
  });
});

const ALL_ACTIONS = {
  customers: {
    create: true,
    read: true,
  },
  coupons: {
    create: true,
    read: true,
  },
  invoices: {
    create: true,
    update: true,
    read: true,
  },
  invoiceItems: {
    create: true,
  },
  paymentLinks: {
    create: true,
  },
  products: {
    create: true,
    read: true,
  },
  prices: {
    create: true,
    read: true,
  },
  balance: {
    read: true,
  },
  refunds: {
    create: true,
  },
  subscriptions: {
    read: true,
    update: true,
  },
  paymentIntents: {
    read: true,
  },
  disputes: {
    read: true,
    update: true,
  },
  documentation: {
    read: true,
  },
};

```

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

```typescript
import {listSubscriptions} from '@/shared/subscriptions/listSubscriptions';
import {cancelSubscription} from '@/shared/subscriptions/cancelSubscription';
import {updateSubscription} from '@/shared/subscriptions/updateSubscription';

const Stripe = jest.fn().mockImplementation(() => ({
  subscriptions: {
    list: jest.fn(),
    cancel: jest.fn(),
    update: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('listSubscriptions', () => {
  it('should list subscriptions and return data', async () => {
    const mockSubscriptions = [
      {
        id: 'sub_123456',
        customer: 'cus_123456',
        status: 'active',
        current_period_start: 1609459200, // 2021-01-01
        current_period_end: 1612137600, // 2021-02-01
        items: {
          data: [
            {
              id: 'si_123',
              price: 'price_123',
              quantity: 1,
            },
          ],
        },
      },
      {
        id: 'sub_789012',
        customer: 'cus_123456',
        status: 'canceled',
        current_period_start: 1609459200, // 2021-01-01
        current_period_end: 1612137600, // 2021-02-01
        items: {
          data: [
            {
              id: 'si_456',
              price: 'price_456',
              quantity: 2,
            },
          ],
        },
      },
    ];

    const context = {};
    const params = {};

    stripe.subscriptions.list.mockResolvedValue({data: mockSubscriptions});
    const result = await listSubscriptions(stripe, context, params);

    expect(stripe.subscriptions.list).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockSubscriptions);
  });

  it('should add customer from context if provided', async () => {
    const mockSubscriptions = [
      {
        id: 'sub_123456',
        customer: 'cus_123456',
        status: 'active',
        current_period_start: 1609459200,
        current_period_end: 1612137600,
        items: {
          data: [
            {
              id: 'si_123',
              price: 'price_123',
              quantity: 1,
            },
          ],
        },
      },
    ];

    const context = {
      customer: 'cus_123456',
    };
    const params = {};

    stripe.subscriptions.list.mockResolvedValue({data: mockSubscriptions});
    const result = await listSubscriptions(stripe, context, params);

    expect(stripe.subscriptions.list).toHaveBeenCalledWith(
      {customer: 'cus_123456'},
      undefined
    );
    expect(result).toEqual(mockSubscriptions);
  });

  it('should specify the connected account if included in context', async () => {
    const mockSubscriptions = [
      {
        id: 'sub_123456',
        customer: 'cus_123456',
        status: 'active',
        current_period_start: 1609459200,
        current_period_end: 1612137600,
        items: {
          data: [
            {
              id: 'si_123',
              price: 'price_123',
              quantity: 1,
            },
          ],
        },
      },
    ];

    const context = {
      account: 'acct_123456',
    };
    const params = {};

    stripe.subscriptions.list.mockResolvedValue({data: mockSubscriptions});
    const result = await listSubscriptions(stripe, context, params);

    expect(stripe.subscriptions.list).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual(mockSubscriptions);
  });

  it('should handle errors gracefully', async () => {
    const context = {};
    const params = {};

    stripe.subscriptions.list.mockRejectedValue(new Error('API Error'));
    const result = await listSubscriptions(stripe, context, params);

    expect(result).toBe('Failed to list subscriptions');
  });
});

describe('cancelSubscription', () => {
  it('should cancel a subscription and return the result', async () => {
    const mockSubscription = {
      id: 'sub_123456',
      customer: 'cus_123456',
      status: 'active',
      current_period_start: 1609459200,
      current_period_end: 1612137600,
      items: {
        data: [
          {
            id: 'si_123',
            price: 'price_123',
            quantity: 1,
          },
        ],
      },
    };

    const context = {};
    const params = {
      subscription: 'sub_123456',
    };

    stripe.subscriptions.cancel.mockResolvedValue(mockSubscription);
    const result = await cancelSubscription(stripe, context, params);

    expect(stripe.subscriptions.cancel).toHaveBeenCalledWith(
      'sub_123456',
      {},
      undefined
    );
    expect(result).toEqual(mockSubscription);
  });

  it('should handle errors gracefully', async () => {
    const context = {};
    const params = {
      subscription: 'sub_123456',
    };

    stripe.subscriptions.cancel.mockRejectedValue(new Error('API Error'));
    const result = await cancelSubscription(stripe, context, params);

    expect(result).toBe('Failed to cancel subscription');
  });
});

describe('updateSubscription', () => {
  it('should update a subscription and return the result', async () => {
    const mockSubscription = {
      id: 'sub_123456',
      customer: 'cus_123456',
      status: 'active',
      current_period_start: 1609459200,
      current_period_end: 1612137600,
      items: {
        data: [
          {
            id: 'si_123',
            price: 'price_123',
            quantity: 1,
          },
        ],
      },
    };

    const context = {};
    const params = {
      subscription: 'sub_123456',
      items: [
        {
          id: 'si_123',
          quantity: 2,
        },
      ],
    };

    stripe.subscriptions.update.mockResolvedValue(mockSubscription);
    const result = await updateSubscription(stripe, context, params);

    expect(stripe.subscriptions.update).toHaveBeenCalledWith(
      'sub_123456',
      {
        items: [
          {
            id: 'si_123',
            quantity: 2,
          },
        ],
      },
      undefined
    );
    expect(result).toEqual(mockSubscription);
  });

  it('should handle errors gracefully', async () => {
    const context = {};
    const params = {
      subscription: 'sub_123456',
      items: [
        {
          id: 'si_123',
          quantity: 2,
        },
      ],
    };

    stripe.subscriptions.update.mockRejectedValue(new Error('API Error'));
    const result = await updateSubscription(stripe, context, params);

    expect(result).toBe('Failed to update subscription');
  });

  it('should specify the connected account if included in context', async () => {
    const mockSubscription = {
      id: 'sub_123456',
      customer: 'cus_123456',
      status: 'active',
      current_period_start: 1609459200,
      current_period_end: 1612137600,
      items: {
        data: [
          {
            id: 'si_123',
            price: 'price_123',
            quantity: 1,
          },
        ],
      },
    };

    const context = {
      account: 'acct_123456',
    };
    const params = {
      subscription: 'sub_123456',
      cancel_at_period_end: true,
    };

    stripe.subscriptions.update.mockResolvedValue(mockSubscription);
    const result = await updateSubscription(stripe, context, params);

    expect(stripe.subscriptions.update).toHaveBeenCalledWith(
      'sub_123456',
      {
        cancel_at_period_end: true,
      },
      {
        stripeAccount: context.account,
      }
    );
    expect(result).toEqual(mockSubscription);
  });
});

```

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

```typescript
/**
 * Generic token metering implementation
 */

import Stripe from 'stripe';
import type OpenAI from 'openai';
import type {Stream} from 'openai/streaming';
import type Anthropic from '@anthropic-ai/sdk';
import type {Stream as AnthropicStream} from '@anthropic-ai/sdk/streaming';
import type {
  GenerateContentResult,
  GenerateContentStreamResult,
} from '@google/generative-ai';
import type {MeterConfig} from './types';
import {logUsageEvent} from './meter-event-logging';
import {
  detectResponse,
  isGeminiStream,
  extractUsageFromChatStream,
  extractUsageFromResponseStream,
  extractUsageFromAnthropicStream,
  type DetectedResponse,
} from './utils/type-detection';

/**
 * Supported response types from all AI providers
 */
export type SupportedResponse =
  | OpenAI.ChatCompletion
  | OpenAI.Responses.Response
  | OpenAI.CreateEmbeddingResponse
  | Anthropic.Messages.Message
  | GenerateContentResult;

/**
 * Supported stream types from all AI providers
 */
export type SupportedStream =
  | Stream<OpenAI.ChatCompletionChunk>
  | Stream<OpenAI.Responses.ResponseStreamEvent>
  | AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>
  | GenerateContentStreamResult;

/**
 * Generic token meter interface
 */
export interface TokenMeter {
  /**
   * Track usage from any supported response type (fire-and-forget)
   * Automatically detects provider and response type
   */
  trackUsage(response: SupportedResponse, stripeCustomerId: string): void;

  /**
   * Track usage from OpenAI streaming response
   * Model name is automatically extracted from the stream
   * Returns the wrapped stream for consumption
   */
  trackUsageStreamOpenAI<
    T extends
      | Stream<OpenAI.ChatCompletionChunk>
      | Stream<OpenAI.Responses.ResponseStreamEvent>
  >(
    stream: T,
    stripeCustomerId: string
  ): T;

  /**
   * Track usage from Anthropic streaming response
   * Model name is automatically extracted from the stream
   * Returns the wrapped stream for consumption
   */
  trackUsageStreamAnthropic(
    stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>,
    stripeCustomerId: string
  ): AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>;

  /**
   * Track usage from Gemini/Google streaming response
   * Model name must be provided as Gemini streams don't include it
   * Returns the wrapped stream for consumption
   */
  trackUsageStreamGemini(
    stream: GenerateContentStreamResult,
    stripeCustomerId: string,
    modelName: string
  ): GenerateContentStreamResult;
}

/**
 * Create a generic token meter that works with any supported AI provider
 *
 * @param stripeApiKey - Your Stripe API key
 * @param config - Optional configuration for the meter
 * @returns TokenMeter instance for tracking usage
 */
export function createTokenMeter(
  stripeApiKey: string,
  config: MeterConfig = {}
): TokenMeter {
  // Construct Stripe client with the API key
  const stripeClient = new Stripe(stripeApiKey, {
    appInfo: {
      name: '@stripe/token-meter',
      version: '0.1.0',
    },
  });
  return {
    trackUsage(response: SupportedResponse, stripeCustomerId: string): void {
      const detected = detectResponse(response);

      if (!detected) {
        console.warn(
          'Unable to detect response type. Supported types: OpenAI ChatCompletion, Responses API, Embeddings'
        );
        return;
      }

      // Fire-and-forget logging
      logUsageEvent(stripeClient, config, {
        model: detected.model,
        provider: detected.provider,
        usage: {
          inputTokens: detected.inputTokens,
          outputTokens: detected.outputTokens,
        },
        stripeCustomerId,
      });
    },

    trackUsageStreamGemini(
      stream: GenerateContentStreamResult,
      stripeCustomerId: string,
      modelName: string
    ): GenerateContentStreamResult {
      const originalStream = stream.stream;
        
        const wrappedStream = (async function* () {
          let lastUsageMetadata: any = null;

          for await (const chunk of originalStream) {
            if (chunk.usageMetadata) {
              lastUsageMetadata = chunk.usageMetadata;
            }
            yield chunk;
          }

          // Log usage after stream completes
          if (lastUsageMetadata) {
            const baseOutputTokens = lastUsageMetadata?.candidatesTokenCount ?? 0;
            // thoughtsTokenCount is for extended thinking models, may not always be present
            const reasoningTokens = (lastUsageMetadata as any)?.thoughtsTokenCount ?? 0;

            logUsageEvent(stripeClient, config, {
              model: modelName,
              provider: 'google',
              usage: {
                inputTokens: lastUsageMetadata?.promptTokenCount ?? 0,
                outputTokens: baseOutputTokens + reasoningTokens,
              },
              stripeCustomerId,
            });
          }
        })();

        // Return the wrapped structure
        return {
          stream: wrappedStream,
          response: stream.response,
        };
    },

    trackUsageStreamOpenAI<
      T extends
        | Stream<OpenAI.ChatCompletionChunk>
        | Stream<OpenAI.Responses.ResponseStreamEvent>
    >(stream: T, stripeCustomerId: string): T {
      const [peekStream, stream2] = stream.tee();

      (async () => {
        // Peek at the first chunk to determine stream type
        const [stream1, meterStream] = peekStream.tee();
        const reader = stream1[Symbol.asyncIterator]();
        const firstChunk = await reader.next();
        
        let detected: DetectedResponse | null = null;
        
        if (!firstChunk.done && firstChunk.value) {
          const chunk = firstChunk.value as any;
          
          // Check if it's an OpenAI Chat stream (has choices array)
          if ('choices' in chunk && Array.isArray(chunk.choices)) {
            detected = await extractUsageFromChatStream(meterStream as any);
          }
          // Check if it's an OpenAI Response API stream (has type starting with 'response.')
          else if (chunk.type && typeof chunk.type === 'string' && chunk.type.startsWith('response.')) {
            detected = await extractUsageFromResponseStream(meterStream as any);
          }
          else {
            console.warn('Unable to detect OpenAI stream type from first chunk:', chunk);
          }
        }

        if (detected) {
          logUsageEvent(stripeClient, config, {
            model: detected.model,
            provider: detected.provider,
            usage: {
              inputTokens: detected.inputTokens,
              outputTokens: detected.outputTokens,
            },
            stripeCustomerId,
          });
        } else {
          console.warn('Unable to extract usage from OpenAI stream');
        }
      })();

      return stream2 as T;
    },

    trackUsageStreamAnthropic(
      stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>,
      stripeCustomerId: string
    ): AnthropicStream<Anthropic.Messages.RawMessageStreamEvent> {
      const [peekStream, stream2] = stream.tee();

      (async () => {
        const detected = await extractUsageFromAnthropicStream(peekStream);

        if (detected) {
          logUsageEvent(stripeClient, config, {
            model: detected.model,
            provider: detected.provider,
            usage: {
              inputTokens: detected.inputTokens,
              outputTokens: detected.outputTokens,
            },
            stripeCustomerId,
          });
        } else {
          console.warn('Unable to extract usage from Anthropic stream');
        }
      })();

      return stream2;
    },
  };
}


```

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

```typescript
import {z} from 'zod';
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {registerPaidTool} from '../../modelcontextprotocol/register-paid-tool';
import Stripe from 'stripe';
import type {
  ServerNotification,
  ServerRequest,
} from '@modelcontextprotocol/sdk/types.js';
import type {RequestHandlerExtra} from '@modelcontextprotocol/sdk/shared/protocol.js';

// Mock Stripe
jest.mock('stripe');
const mockSecretKey = 'sk_test_123';

describe('registerPaidTool', () => {
  let mockMcpServer: jest.Mocked<McpServer>;
  let mockStripe: jest.Mocked<any>;
  let mockExtra: RequestHandlerExtra<ServerRequest, ServerNotification>;

  beforeEach(() => {
    // Reset all mocks
    jest.clearAllMocks();

    // Mock McpServer
    mockMcpServer = {
      tool: jest.fn(),
    } as any;

    // Mock Stripe instance and methods
    mockStripe = {
      customers: {
        list: jest.fn(),
        create: jest.fn(),
      },
      checkout: {
        sessions: {
          create: jest.fn(),
          retrieve: jest.fn(),
          list: jest.fn(),
        },
      },
      subscriptions: {
        list: jest.fn(),
      },
      billing: {
        meterEvents: {
          create: jest.fn(),
        },
      },
    };

    (Stripe as unknown as jest.Mock).mockImplementation(() => mockStripe);

    // Mock request handler extra
    mockExtra = {
      signal: new AbortController().signal,
      sendNotification: jest.fn(),
      sendRequest: jest.fn(),
      requestId: '123',
    };
  });

  it('should register a tool with the McpServer', async () => {
    const toolName = 'testTool';
    const toolDescription = 'Test tool description';
    const paramsSchema = {
      testParam: z.string(),
    };
    const callback = jest.fn();

    // @ts-ignore: https://github.com/modelcontextprotocol/typescript-sdk/issues/494
    await registerPaidTool(
      mockMcpServer,
      toolName,
      toolDescription,
      paramsSchema,
      callback,
      {
        paymentReason: 'Test payment',
        stripeSecretKey: mockSecretKey,
        userEmail: '[email protected]',
        checkout: {
          success_url: 'https://example.com/success',
          line_items: [{price: 'price_123', quantity: 1}],
          mode: 'subscription',
        },
      }
    );

    expect(mockMcpServer.tool).toHaveBeenCalledWith(
      toolName,
      toolDescription,
      paramsSchema,
      expect.any(Function)
    );
  });

  it('should create a new customer if one does not exist', async () => {
    mockStripe.customers.list.mockResolvedValue({data: []});
    mockStripe.customers.create.mockResolvedValue({id: 'cus_123'});
    mockStripe.subscriptions.list.mockResolvedValue({
      data: [
        {
          items: {
            data: [
              {
                price: {
                  id: 'price_123',
                },
              },
            ],
          },
        },
      ],
    });
    mockStripe.checkout.sessions.list.mockResolvedValue({data: []});
    mockStripe.checkout.sessions.create.mockResolvedValue({
      id: 'cs_123',
      url: 'https://checkout.stripe.com/123',
    });

    const toolName = 'testTool';
    const callback = jest.fn();

    await registerPaidTool(
      mockMcpServer,
      toolName,
      'Test description',
      {testParam: z.string()},
      callback,
      {
        paymentReason: 'Test payment',
        stripeSecretKey: mockSecretKey,
        userEmail: '[email protected]',
        checkout: {
          success_url: 'https://example.com/success',
          line_items: [{price: 'price_123', quantity: 1}],
          mode: 'subscription',
        },
      }
    );

    const registeredCallback = mockMcpServer.tool.mock.calls[0]?.[3];
    // @ts-ignore: TypeScript can't disambiguate between params schema and annotations
    await registeredCallback({testParam: 'test'}, mockExtra);

    expect(mockStripe.customers.list).toHaveBeenCalledWith({
      email: '[email protected]',
    });
    expect(mockStripe.customers.create).toHaveBeenCalledWith({
      email: '[email protected]',
    });
  });

  it('should create a checkout session for unpaid tools', async () => {
    mockStripe.customers.list.mockResolvedValue({
      data: [{id: 'cus_123', email: '[email protected]'}],
    });
    mockStripe.checkout.sessions.create.mockResolvedValue({
      id: 'cs_123',
      url: 'https://checkout.stripe.com/123',
    });
    mockStripe.subscriptions.list.mockResolvedValue({
      data: [], // No active subscriptions
    });
    mockStripe.checkout.sessions.list.mockResolvedValue({
      data: [], // No paid sessions
    });

    const toolName = 'testTool';
    const callback = jest.fn();

    await registerPaidTool(
      mockMcpServer,
      toolName,
      'Test description',
      {testParam: z.string()},
      callback,
      {
        paymentReason: 'Test payment',
        stripeSecretKey: mockSecretKey,
        userEmail: '[email protected]',
        checkout: {
          success_url: 'https://example.com/success',
          line_items: [{price: 'price_123', quantity: 1}],
          mode: 'subscription',
        },
      }
    );

    const registeredCallback = mockMcpServer.tool.mock.calls[0]?.[3];
    // @ts-ignore: TypeScript can't disambiguate between params schema and annotations
    const result = await registeredCallback({testParam: 'test'}, mockExtra);

    expect(mockStripe.checkout.sessions.create).toHaveBeenCalledWith({
      success_url: 'https://example.com/success',
      line_items: [
        {
          price: 'price_123',
          quantity: 1,
        },
      ],
      mode: 'subscription',
      customer: 'cus_123',
      metadata: {toolName},
    });
    expect(result).toEqual({
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            status: 'payment_required',
            data: {
              paymentType: 'oneTimeSubscription',
              checkoutUrl: 'https://checkout.stripe.com/123',
              paymentReason: 'Test payment',
            },
          }),
        },
      ],
    });
    expect(callback).not.toHaveBeenCalled();
  });

  it('should handle usage-based billing when meterEvent is provided', async () => {
    const toolName = 'testTool';
    mockStripe.customers.list.mockResolvedValue({
      data: [{id: 'cus_123', email: '[email protected]'}],
    });
    mockStripe.checkout.sessions.list.mockResolvedValue({
      data: [
        {
          id: 'cs_123',
          metadata: {toolName},
          payment_status: 'paid',
          subscription: 'sub_123',
        },
      ],
    });
    mockStripe.subscriptions.list.mockResolvedValue({
      data: [
        {
          items: {
            data: [
              {
                price: {
                  id: 'price_123',
                },
              },
            ],
          },
        },
      ],
    });
    const callback = jest.fn().mockResolvedValue({
      content: [{type: 'text', text: 'Success'}],
    });

    await registerPaidTool(
      mockMcpServer,
      toolName,
      'Test description',
      {testParam: z.string()},
      callback,
      {
        paymentReason: 'Test payment',
        meterEvent: 'test.event',
        stripeSecretKey: mockSecretKey,
        userEmail: '[email protected]',
        checkout: {
          success_url: 'https://example.com/success',
          line_items: [{price: 'price_123'}],
          mode: 'subscription',
        },
      }
    );

    const registeredCallback = mockMcpServer.tool.mock.calls[0]?.[3];
    // @ts-ignore: TypeScript can't disambiguate between params schema and annotations
    await registeredCallback({testParam: 'test'}, mockExtra);

    expect(mockStripe.billing.meterEvents.create).toHaveBeenCalledWith({
      event_name: 'test.event',
      payload: {
        stripe_customer_id: 'cus_123',
        value: '1',
      },
    });
  });
});

```

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

```typescript
/**
 * Utility functions for Stripe AI SDK Provider
 */

import {
  LanguageModelV2Prompt,
  LanguageModelV2FinishReason,
} from '@ai-sdk/provider';

type AssistantMessage = Extract<
  LanguageModelV2Prompt[number],
  {role: 'assistant'}
>;
type UserMessage = Extract<LanguageModelV2Prompt[number], {role: 'user'}>;

type AssistantContentPart = AssistantMessage['content'][number];
type UserContentPart = UserMessage['content'][number];

/**
 * Type guards for content parts
 */
function isTextPart(
  part: AssistantContentPart
): part is Extract<AssistantContentPart, {type: 'text'}> {
  return part.type === 'text';
}

function isToolCallPart(
  part: AssistantContentPart
): part is Extract<AssistantContentPart, {type: 'tool-call'}> {
  return part.type === 'tool-call';
}

function isUserTextPart(
  part: UserContentPart
): part is Extract<UserContentPart, {type: 'text'}> {
  return part.type === 'text';
}

function isUserFilePart(
  part: UserContentPart
): part is Extract<UserContentPart, {type: 'file'}> {
  return part.type === 'file';
}

/**
 * Converts AI SDK V2 prompt to OpenAI-compatible messages format
 */
export function convertToOpenAIMessages(
  prompt: LanguageModelV2Prompt
): Array<{
  role: string;
  content:
    | string
    | Array<{
        type: string;
        text?: string;
        image_url?: {url: string};
      }>;
  tool_calls?: Array<{
    id: string;
    type: string;
    function: {name: string; arguments: string};
  }>;
  tool_call_id?: string;
  name?: string;
}> {
  return prompt.map((message) => {
    switch (message.role) {
      case 'system':
        return [{
          role: 'system',
          content: message.content,
        }];

      case 'user': {
        const contentParts = message.content.map((part) => {
          if (isUserTextPart(part)) {
            return {
              type: 'text',
              text: part.text,
            };
          } else if (isUserFilePart(part)) {
            // Convert file data to data URL if needed
            let fileUrl: string;
            if (typeof part.data === 'string') {
              // Could be a URL or base64 data
              if (part.data.startsWith('http://') || part.data.startsWith('https://')) {
                fileUrl = part.data;
              } else {
                // Assume it's base64
                fileUrl = `data:${part.mediaType};base64,${part.data}`;
              }
            } else if (part.data instanceof URL) {
              fileUrl = part.data.toString();
            } else {
              // Convert Uint8Array to base64 data URL
              const base64 = btoa(
                String.fromCharCode(...Array.from(part.data))
              );
              fileUrl = `data:${part.mediaType};base64,${base64}`;
            }
            
            // For images, use image_url format
            if (part.mediaType.startsWith('image/')) {
              return {
                type: 'image_url',
                image_url: {url: fileUrl},
              };
            }
            
            // For other file types, use text representation
            // (OpenAI format doesn't support arbitrary file types well)
            return {
              type: 'text',
              text: `[File: ${part.filename || 'unknown'}, type: ${part.mediaType}]`,
            };
          } else {
            // TypeScript should prevent this, but handle it for runtime safety
            const _exhaustiveCheck: never = part;
            throw new Error(`Unsupported user message part type`);
          }
        });

        // If there's only one text part, send as a simple string
        // This is more compatible with Anthropic's requirements
        const content = 
          contentParts.length === 1 && contentParts[0].type === 'text'
            ? contentParts[0].text!
            : contentParts;

        return [{
          role: 'user',
          content,
        }];
      }

      case 'assistant': {
        // Extract text content
        const textParts = message.content.filter(isTextPart);
        const textContent = textParts.length > 0
          ? textParts.map((part) => part.text).join('')
          : '';

        // Extract tool calls
        const toolCallParts = message.content.filter(isToolCallPart);
        const toolCalls = toolCallParts.length > 0
          ? toolCallParts.map((part) => ({
              id: part.toolCallId,
              type: 'function' as const,
              function: {
                name: part.toolName,
                arguments:
                  typeof part.input === 'string'
                    ? part.input
                    : JSON.stringify(part.input),
              },
            }))
          : undefined;

        return [{
          role: 'assistant',
          content: textContent || (toolCalls ? '' : ''),
          tool_calls: toolCalls,
        }];
      }

      case 'tool':
        // In OpenAI format, each tool result is a separate message
        // Note: This returns an array, so we need to flatten it later
        return message.content.map((part) => {
          let content: string;
          
          // Handle different output types
          if (part.output.type === 'text') {
            content = part.output.value;
          } else if (part.output.type === 'json') {
            content = JSON.stringify(part.output.value);
          } else if (part.output.type === 'error-text') {
            content = `Error: ${part.output.value}`;
          } else if (part.output.type === 'error-json') {
            content = `Error: ${JSON.stringify(part.output.value)}`;
          } else if (part.output.type === 'content') {
            // Convert content array to string
            content = part.output.value
              .map((item) => {
                if (item.type === 'text') {
                  return item.text;
                } else if (item.type === 'media') {
                  return `[Media: ${item.mediaType}]`;
                }
                return '';
              })
              .join('\n');
          } else {
            content = String(part.output);
          }

          return {
            role: 'tool',
            tool_call_id: part.toolCallId,
            name: part.toolName,
            content,
          };
        });

      default:
        // TypeScript should ensure we never get here, but just in case
        const exhaustiveCheck: never = message;
        throw new Error(`Unsupported message role: ${JSON.stringify(exhaustiveCheck)}`);
    }
  }).flat();
}

/**
 * Maps OpenAI finish reasons to AI SDK V2 finish reasons
 */
export function mapOpenAIFinishReason(
  finishReason: string | null | undefined
): LanguageModelV2FinishReason {
  switch (finishReason) {
    case 'stop':
      return 'stop';
    case 'length':
      return 'length';
    case 'content_filter':
      return 'content-filter';
    case 'tool_calls':
    case 'function_call':
      return 'tool-calls';
    default:
      return 'unknown';
  }
}

/**
 * Normalize model names to match Stripe's approved model list
 * This function handles provider-specific normalization rules:
 * - Anthropic: Removes date suffixes, -latest suffix, converts version dashes to dots
 * - OpenAI: Removes date suffixes (with exceptions)
 * - Google: Returns as-is
 * 
 * @param modelId - Model ID in provider/model format (e.g., 'anthropic/claude-3-5-sonnet-20241022')
 * @returns Normalized model ID in the same format
 */
export function normalizeModelId(modelId: string): string {
  // Split the model ID into provider and model parts
  const parts = modelId.split('/');
  if (parts.length !== 2) {
    // If format is not provider/model, return as-is
    return modelId;
  }

  const [provider, model] = parts;
  const normalizedProvider = provider.toLowerCase();
  let normalizedModel = model;

  if (normalizedProvider === 'anthropic') {
    // Remove date suffix (YYYYMMDD format at the end)
    normalizedModel = normalizedModel.replace(/-\d{8}$/, '');

    // Remove -latest suffix
    normalizedModel = normalizedModel.replace(/-latest$/, '');

    // Convert version number dashes to dots anywhere in the name
    // Match patterns like claude-3-7, opus-4-1, sonnet-4-5, etc.
    normalizedModel = normalizedModel.replace(/(-[a-z]+)?-(\d+)-(\d+)/g, '$1-$2.$3');
  } else if (normalizedProvider === 'openai') {
    // Exception: keep gpt-4o-2024-05-13 as is
    if (normalizedModel === 'gpt-4o-2024-05-13') {
      return modelId;
    }

    // Remove date suffix in format -YYYY-MM-DD
    normalizedModel = normalizedModel.replace(/-\d{4}-\d{2}-\d{2}$/, '');
  }
  // For other providers (google/gemini), return as is

  return `${provider}/${normalizedModel}`;
}

```

--------------------------------------------------------------------------------
/llm/token-meter/tests/token-meter-general.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for TokenMeter - General Functionality
 * Tests for cross-provider features, unknown types, and edge cases
 */

import Stripe from 'stripe';
import {createTokenMeter} from '../token-meter';
import type {MeterConfig} from '../types';

// Mock Stripe
jest.mock('stripe');

describe('TokenMeter - General Functionality', () => {
  let mockMeterEventsCreate: jest.Mock;
  let config: MeterConfig;
  let consoleWarnSpy: jest.SpyInstance;
  const TEST_API_KEY = 'sk_test_mock_key';

  beforeEach(() => {
    jest.clearAllMocks();
    mockMeterEventsCreate = jest.fn().mockResolvedValue({});
    
    // Mock the Stripe constructor
    (Stripe as unknown as jest.Mock).mockImplementation(() => ({
      v2: {
        billing: {
          meterEvents: {
            create: mockMeterEventsCreate,
          },
        },
      },
    }));
    
    config = {};
    consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
  });

  afterEach(() => {
    consoleWarnSpy.mockRestore();
  });

  describe('Unknown Response Types', () => {
    it('should warn and not log for unknown response formats', async () => {
      const meter = createTokenMeter(TEST_API_KEY, config);

      const unknownResponse = {
        some: 'unknown',
        response: 'format',
        that: 'does not match any provider',
      };

      meter.trackUsage(unknownResponse as any, 'cus_123');

      expect(consoleWarnSpy).toHaveBeenCalledWith(
        'Unable to detect response type. Supported types: OpenAI ChatCompletion, Responses API, Embeddings'
      );
      await new Promise(resolve => setImmediate(resolve));
      expect(mockMeterEventsCreate).not.toHaveBeenCalled();
    });

    it('should handle null response', async () => {
      const meter = createTokenMeter(TEST_API_KEY, config);

      meter.trackUsage(null as any, 'cus_123');

      expect(consoleWarnSpy).toHaveBeenCalled();
      await new Promise(resolve => setImmediate(resolve));
      expect(mockMeterEventsCreate).not.toHaveBeenCalled();
    });

    it('should handle undefined response', async () => {
      const meter = createTokenMeter(TEST_API_KEY, config);

      meter.trackUsage(undefined as any, 'cus_123');

      expect(consoleWarnSpy).toHaveBeenCalled();
      await new Promise(resolve => setImmediate(resolve));
      expect(mockMeterEventsCreate).not.toHaveBeenCalled();
    });

    it('should handle empty object response', async () => {
      const meter = createTokenMeter(TEST_API_KEY, config);

      meter.trackUsage({} as any, 'cus_123');

      expect(consoleWarnSpy).toHaveBeenCalled();
      await new Promise(resolve => setImmediate(resolve));
      expect(mockMeterEventsCreate).not.toHaveBeenCalled();
    });
  });

  describe('Customer ID Handling', () => {
    it('should pass customer ID correctly for different providers', async () => {
      const meter = createTokenMeter(TEST_API_KEY, config);
      const customerId = 'cus_special_id_123';

      // OpenAI
      const openaiResponse = {
        id: 'chatcmpl-123',
        object: 'chat.completion',
        created: Date.now(),
        model: 'gpt-4',
        choices: [{index: 0, message: {role: 'assistant', content: 'Hi'}, finish_reason: 'stop'}],
        usage: {prompt_tokens: 5, completion_tokens: 2, total_tokens: 7},
      };

      meter.trackUsage(openaiResponse as any, customerId);

      await new Promise(resolve => setImmediate(resolve));

      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({
            stripe_customer_id: customerId,
          }),
        })
      );

      mockMeterEventsCreate.mockClear();

      // Anthropic
      const anthropicResponse = {
        id: 'msg_123',
        type: 'message',
        role: 'assistant',
        content: [{type: 'text', text: 'Hi'}],
        model: 'claude-3-5-sonnet-20241022',
        stop_reason: 'end_turn',
        stop_sequence: null,
        usage: {input_tokens: 5, output_tokens: 2},
      };

      meter.trackUsage(anthropicResponse as any, customerId);

      await new Promise(resolve => setImmediate(resolve));

      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({
            stripe_customer_id: customerId,
          }),
        })
      );

      mockMeterEventsCreate.mockClear();

      // Gemini
      const geminiResponse = {
        response: {
          text: () => 'Hi',
          usageMetadata: {promptTokenCount: 5, candidatesTokenCount: 2, totalTokenCount: 7},
          modelVersion: 'gemini-1.5-pro',
        },
      };

      meter.trackUsage(geminiResponse as any, customerId);

      await new Promise(resolve => setImmediate(resolve));

      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({
            stripe_customer_id: customerId,
          }),
        })
      );
    });
  });

  describe('Multiple Meter Instances', () => {
    it('should allow multiple independent meter instances', async () => {
      const config1: MeterConfig = {};
      const config2: MeterConfig = {};

      const meter1 = createTokenMeter(TEST_API_KEY, config1);
      const meter2 = createTokenMeter(TEST_API_KEY, config2);

      const response = {
        id: 'chatcmpl-123',
        object: 'chat.completion',
        created: Date.now(),
        model: 'gpt-4',
        choices: [{index: 0, message: {role: 'assistant', content: 'Hi'}, finish_reason: 'stop'}],
        usage: {prompt_tokens: 5, completion_tokens: 2, total_tokens: 7},
      };

      meter1.trackUsage(response as any, 'cus_123');
      meter2.trackUsage(response as any, 'cus_456');

      await new Promise(resolve => setImmediate(resolve));

      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({stripe_customer_id: 'cus_123'}),
        })
      );
      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({stripe_customer_id: 'cus_456'}),
        })
      );
    });
  });

  describe('Provider Detection Accuracy', () => {
    it('should correctly identify provider from response shape alone', async () => {
      const meter = createTokenMeter(TEST_API_KEY, config);

      // Test that provider is detected purely from response structure
      const responses = [
        {
          response: {
            id: 'chatcmpl-123',
            object: 'chat.completion',
            model: 'gpt-4',
            choices: [{message: {content: 'test'}}],
            usage: {prompt_tokens: 1, completion_tokens: 1},
          },
          expectedProvider: 'openai',
        },
        {
          response: {
            id: 'msg_123',
            type: 'message',
            role: 'assistant',
            content: [{type: 'text', text: 'test'}],
            model: 'claude-3-opus',
            usage: {input_tokens: 1, output_tokens: 1},
          },
          expectedProvider: 'anthropic',
        },
        {
          response: {
            response: {
              text: () => 'test',
              usageMetadata: {promptTokenCount: 1, candidatesTokenCount: 1},
              modelVersion: 'gemini-2.0-flash-exp',
            },
          },
          expectedProvider: 'google',
        },
      ];

      for (const {response, expectedProvider} of responses) {
        mockMeterEventsCreate.mockClear();
        meter.trackUsage(response as any, 'cus_123');

        await new Promise(resolve => setImmediate(resolve));

        expect(mockMeterEventsCreate).toHaveBeenCalledWith(
          expect.objectContaining({
            payload: expect.objectContaining({
              model: expect.stringContaining(expectedProvider + '/'),
            }),
          })
        );
      }
    });
  });
});

// Helper function to create mock streams with tee()
function createMockStreamWithTee(chunks: any[]) {
  return {
    tee() {
      const stream1 = {
        async *[Symbol.asyncIterator]() {
          for (const chunk of chunks) {
            yield chunk;
          }
        },
        tee() {
          const s1 = {
            async *[Symbol.asyncIterator]() {
              for (const chunk of chunks) {
                yield chunk;
              }
            },
          };
          const s2 = {
            async *[Symbol.asyncIterator]() {
              for (const chunk of chunks) {
                yield chunk;
              }
            },
          };
          return [s1, s2];
        },
      };
      const stream2 = {
        async *[Symbol.asyncIterator]() {
          for (const chunk of chunks) {
            yield chunk;
          }
        },
      };
      return [stream1, stream2];
    },
    async *[Symbol.asyncIterator]() {
      for (const chunk of chunks) {
        yield chunk;
      }
    },
  };
}


```

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

```typescript
/**
 * Tests for AI SDK billing wrapper with other providers and unsupported models
 * Tests error handling and edge cases with mocks
 */

import Stripe from 'stripe';
import {createOpenAI} from '@ai-sdk/openai';
import {meteredModel} from '../index';

// Mock Stripe
jest.mock('stripe');

describe('AI SDK Billing Wrapper - Other Providers', () => {
  let mockMeterEventsCreate: jest.Mock;
  const TEST_API_KEY = 'sk_test_mock_key';

  beforeEach(() => {
    mockMeterEventsCreate = jest.fn().mockResolvedValue({});
    
    // Mock the Stripe constructor
    (Stripe as unknown as jest.Mock).mockImplementation(() => ({
      v2: {
        billing: {
          meterEvents: {
            create: mockMeterEventsCreate,
          },
        },
      },
    }));
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('OpenAI-Compatible Providers', () => {
    it('should work with Together AI (OpenAI-compatible)', async () => {
      const together = createOpenAI({
        apiKey: 'mock-key',
        baseURL: 'https://api.together.xyz/v1',
      });

      const model = meteredModel(
        together('meta-llama/Llama-3-70b-chat-hf'),
        TEST_API_KEY,
        'cus_test123'
      );

      const originalModel = together('meta-llama/Llama-3-70b-chat-hf');
      jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
        text: 'Together AI response',
        usage: {
          inputTokens: 12,
          outputTokens: 5,
        },
        finishReason: 'stop',
        rawResponse: {},
        warnings: [],
      } as any);

      // Copy the mock to our wrapped model's internal model
      (model as any).model.doGenerate = originalModel.doGenerate;

      await model.doGenerate({
        inputFormat: 'prompt',
        mode: {type: 'regular'},
        prompt: [],
      } as any);

      // Wait for fire-and-forget logging to complete
      await new Promise(resolve => setImmediate(resolve));

      expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
    });

    it('should work with custom OpenAI-compatible providers', async () => {
      const customProvider = createOpenAI({
        apiKey: 'mock-key',
        baseURL: 'https://custom-ai.example.com/v1',
      });

      const model = meteredModel(
        customProvider('custom-model-v1'),
        TEST_API_KEY,
        'cus_test123'
      );

      const originalModel = customProvider('custom-model-v1');
      jest.spyOn(originalModel, 'doGenerate').mockResolvedValue({
        text: 'Custom response',
        usage: {
          inputTokens: 8,
          outputTokens: 3,
        },
        finishReason: 'stop',
        rawResponse: {},
        warnings: [],
      } as any);

      (model as any).model.doGenerate = originalModel.doGenerate;

      await model.doGenerate({
        inputFormat: 'prompt',
        mode: {type: 'regular'},
        prompt: [],
      } as any);

      // Wait for fire-and-forget logging to complete
      await new Promise(resolve => setImmediate(resolve));

      expect(mockMeterEventsCreate).toHaveBeenCalledTimes(2);
      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({
            value: '8',
            token_type: 'input',
          }),
        })
      );
    });
  });

  describe('Unsupported Models', () => {
    it('should throw error for model with v1 specification', () => {
      const mockModel = {
        modelId: 'mock-model-v1',
        provider: 'mock-provider',
        specificationVersion: 'v1',
        doGenerate: jest.fn(),
        doStream: jest.fn(),
      } as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow('Only LanguageModelV2 models are supported');
    });

    it('should throw error for model without specification version', () => {
      const mockModel = {
        modelId: 'mock-model-unknown',
        provider: 'unknown-provider',
        doGenerate: jest.fn(),
        doStream: jest.fn(),
      } as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow('Only LanguageModelV2 models are supported');
    });

    it('should throw error for model with unknown specification version', () => {
      const mockModel = {
        modelId: 'mock-model-v99',
        provider: 'future-provider',
        specificationVersion: 'v99',
        doGenerate: jest.fn(),
        doStream: jest.fn(),
      } as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow('Only LanguageModelV2 models are supported');
    });

    it('should provide clear error message', () => {
      const mockModel = {specificationVersion: 'v1'} as any;

      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow(/Only LanguageModelV2 models are supported/);
      expect(() => {
        meteredModel(mockModel, TEST_API_KEY, 'cus_test123');
      }).toThrow(/Please use a supported provider/);
    });
  });

  describe('Provider Support', () => {
    it('should support any v2 provider name', async () => {
      const customProviderModel = {
        modelId: 'custom-model-123',
        provider: 'my-custom-provider',
        specificationVersion: 'v2',
        doGenerate: jest.fn().mockResolvedValue({
          text: 'Custom response',
          usage: {
            inputTokens: 10,
            outputTokens: 5,
          },
          finishReason: 'stop',
          rawResponse: {},
          warnings: [],
        }),
        doStream: jest.fn(),
      } as any;

      const wrapped = meteredModel(
        customProviderModel,
        TEST_API_KEY,
        'cus_test123'
      );

      await wrapped.doGenerate({
        inputFormat: 'prompt',
        mode: {type: 'regular'},
        prompt: [],
      } as any);

      // Wait for fire-and-forget logging to complete
      await new Promise(resolve => setImmediate(resolve));

      expect(wrapped).toBeDefined();
      expect(wrapped.provider).toBe('my-custom-provider');
      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({
            model: 'my-custom-provider/custom-model-123',
          }),
        })
      );
    });

    it('should provide helpful error message for unsupported models', () => {
      const unsupportedModel = {
        modelId: 'test',
        provider: 'test-provider',
        specificationVersion: 'v1',
      } as any;

      try {
        meteredModel(unsupportedModel, TEST_API_KEY, 'cus_test123');
        fail('Should have thrown an error');
      } catch (error: any) {
        expect(error.message).toContain('Only LanguageModelV2 models are supported');
        expect(error.message).toContain('specificationVersion "v2"');
        expect(error.message).toContain('OpenAI, Anthropic, Google');
      }
    });
  });

  describe('Edge Cases', () => {
    it('should handle model with missing usage data', async () => {
      const model = {
        modelId: 'test-model',
        provider: 'test-provider',
        specificationVersion: 'v2',
        doGenerate: jest.fn().mockResolvedValue({
          text: 'Response',
          usage: undefined,
          finishReason: 'stop',
          rawResponse: {},
          warnings: [],
        }),
        doStream: jest.fn(),
      } as any;

      const wrapped = meteredModel(model, TEST_API_KEY, 'cus_test123');

      await wrapped.doGenerate({
        inputFormat: 'prompt',
        mode: {type: 'regular'},
        prompt: [],
      } as any);

      // Wait for fire-and-forget logging to complete
      await new Promise(resolve => setImmediate(resolve));

      // Should not create meter events with 0 tokens (code only sends when > 0)
      expect(mockMeterEventsCreate).toHaveBeenCalledTimes(0);
    });

    it('should handle model with partial usage data', async () => {
      const model = {
        modelId: 'test-model',
        provider: 'test-provider',
        specificationVersion: 'v2',
        doGenerate: jest.fn().mockResolvedValue({
          text: 'Response',
          usage: {
            inputTokens: 10,
            outputTokens: undefined,
          },
          finishReason: 'stop',
          rawResponse: {},
          warnings: [],
        }),
        doStream: jest.fn(),
      } as any;

      const wrapped = meteredModel(model, TEST_API_KEY, 'cus_test123');

      await wrapped.doGenerate({
        inputFormat: 'prompt',
        mode: {type: 'regular'},
        prompt: [],
      } as any);

      // Wait for fire-and-forget logging to complete
      await new Promise(resolve => setImmediate(resolve));

      // Should handle partial data gracefully - only sends event for input tokens
      // Output tokens with value 0 are not sent (code only sends when > 0)
      expect(mockMeterEventsCreate).toHaveBeenCalledTimes(1);
      expect(mockMeterEventsCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          payload: expect.objectContaining({
            value: '10',
            token_type: 'input',
          }),
        })
      );
    });
  });
});

```

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

```typescript
/**
 * Sample Usage: OpenAI with Usage Tracking
 * This demonstrates how to use the generic token meter to automatically report
 * token usage to Stripe for billing purposes.
 */

import {config} from 'dotenv';
import {resolve} from 'path';
import OpenAI from 'openai';
import {createTokenMeter} from '..';

// Load .env from the examples folder
config({path: resolve(__dirname, '.env')});

// Load environment variables from .env file
const STRIPE_API_KEY = process.env.STRIPE_API_KEY!;
const STRIPE_CUSTOMER_ID = process.env.STRIPE_CUSTOMER_ID!;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY!;

// Initialize the OpenAI client (standard SDK)
const openai = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

// Create the token meter
const meter = createTokenMeter(STRIPE_API_KEY);

// Sample 1: Basic Chat Completion (non-streaming)
async function sampleBasicChatCompletion() {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {role: 'user', content: 'Say "Hello, World!" and nothing else.'},
    ],
    max_tokens: 20,
  });

  // Track usage with the meter
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', response.choices[0]?.message?.content);
  console.log('Usage:', response.usage);
}

// Sample 2: Streaming Chat Completion
async function sampleStreamingChatCompletion() {
  const stream = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {role: 'user', content: 'Count from 1 to 5, one number per line.'},
    ],
    stream: true,
    stream_options: {include_usage: true}, // Important for metering
    max_tokens: 50,
  });

  // Wrap the stream for metering
  const meteredStream = meter.trackUsageStreamOpenAI(stream, STRIPE_CUSTOMER_ID);

  let fullContent = '';
  for await (const chunk of meteredStream) {
    const content = chunk.choices[0]?.delta?.content || '';
    fullContent += content;
    process.stdout.write(content);
  }

  console.log('\n\nFull content:', fullContent);
}

// Sample 3: Chat Completion with Tools
async function sampleChatCompletionWithTools() {
  const tools: any[] = [
    {
      type: 'function',
      function: {
        name: 'get_weather',
        description: 'Get the current weather in a location',
        parameters: {
          type: 'object',
          properties: {
            location: {
              type: 'string',
              description: 'The city and state, e.g. San Francisco, CA',
            },
            unit: {
              type: 'string',
              enum: ['celsius', 'fahrenheit'],
            },
          },
          required: ['location'],
        },
      },
    },
  ];

  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{role: 'user', content: 'What is the weather in New York?'}],
    tools,
    max_tokens: 100,
  });

  // Track usage with the meter
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log(
    'Response:',
    JSON.stringify(response.choices[0]?.message, null, 2)
  );
  console.log('Usage:', response.usage);
}

// Sample 4: Streaming Chat Completion with Tools
async function sampleStreamingChatCompletionWithTools() {
  const tools: any[] = [
    {
      type: 'function',
      function: {
        name: 'calculate',
        description: 'Calculate a mathematical expression',
        parameters: {
          type: 'object',
          properties: {
            expression: {
              type: 'string',
              description: 'The mathematical expression to calculate',
            },
          },
          required: ['expression'],
        },
      },
    },
  ];

  const stream = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{role: 'user', content: 'What is 25 * 4?'}],
    tools,
    stream: true,
    stream_options: {include_usage: true}, // Important for metering
    max_tokens: 100,
  });

  // Wrap the stream for metering
  const meteredStream = meter.trackUsageStreamOpenAI(stream, STRIPE_CUSTOMER_ID);

  const toolCalls = new Map<
    number,
    {id: string; name: string; arguments: string}
  >();

  for await (const chunk of meteredStream) {
    const choice = chunk.choices[0];

    // Handle tool calls
    if (choice?.delta?.tool_calls) {
      for (const toolCall of choice.delta.tool_calls) {
        const index = toolCall.index;
        if (index === undefined) continue;

        if (!toolCalls.has(index)) {
          toolCalls.set(index, {id: '', name: '', arguments: ''});
        }
        const tc = toolCalls.get(index)!;
        if (toolCall.id) tc.id = toolCall.id;
        if (toolCall.function?.name) tc.name = toolCall.function.name;
        if (toolCall.function?.arguments)
          tc.arguments += toolCall.function.arguments;
      }
    }

    // Print usage if available
    if (chunk.usage) {
      console.log('\nUsage in stream:', chunk.usage);
    }
  }

  console.log('\nTool calls:', Array.from(toolCalls.values()));
}

// Sample 5: Responses API (non-streaming)
async function sampleResponsesAPIBasic() {
  const response = await openai.responses.create({
    model: 'gpt-4o-mini',
    input: 'What is 2+2?',
    instructions: 'You are a helpful math assistant.',
    max_output_tokens: 50,
  });

  // Track usage with the meter
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', JSON.stringify(response.output, null, 2));
  console.log('Usage:', response.usage);
}

// Sample 6: Responses API (streaming)
async function sampleResponsesAPIStreaming() {
  const stream = await openai.responses.create({
    model: 'gpt-4o-mini',
    input: 'Tell me a fun fact about cats.',
    instructions: 'You are a helpful assistant.',
    stream: true,
    max_output_tokens: 100,
  });

  // Wrap the stream for metering
  const meteredStream = meter.trackUsageStreamOpenAI(stream, STRIPE_CUSTOMER_ID);

  let finalOutput: any = null;
  let finalUsage: any = null;

  for await (const event of meteredStream) {
    if (event.type === 'response.completed' && 'response' in event) {
      finalOutput = event.response.output;
      finalUsage = event.response.usage;
    }
  }

  console.log('Final output:', JSON.stringify(finalOutput, null, 2));
  console.log('Usage:', finalUsage);
}

// Sample 7: Responses API with parse (structured outputs)
async function sampleResponsesAPIParse() {
  // Note: The schema is defined inline in the API call
  // Zod is not needed for this example
  const response = await openai.responses.parse({
    model: 'gpt-4o-mini',
    input: 'What is 15 multiplied by 7?',
    instructions:
      'You are a helpful math assistant. Provide the answer and a brief explanation.',
    text: {
      format: {
        type: 'json_schema',
        name: 'math_response',
        strict: true,
        schema: {
          type: 'object',
          properties: {
            answer: {type: 'number'},
            explanation: {type: 'string'},
          },
          required: ['answer', 'explanation'],
          additionalProperties: false,
        },
      },
    },
    max_output_tokens: 100,
  });

  // Track usage with the meter
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Parsed response:', response.output[0]);
  console.log('Usage:', response.usage);
}

// Sample 8: Embeddings
async function sampleEmbeddings() {
  const response = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: 'The quick brown fox jumps over the lazy dog',
  });

  // Track usage with the meter
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Embedding dimensions:', response.data[0]?.embedding.length);
  console.log('First 5 values:', response.data[0]?.embedding.slice(0, 5));
  console.log('Usage:', response.usage);
}

// Sample 9: Multiple messages conversation
async function sampleConversation() {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {role: 'system', content: 'You are a helpful assistant.'},
      {role: 'user', content: 'What is the capital of France?'},
      {role: 'assistant', content: 'The capital of France is Paris.'},
      {role: 'user', content: 'What is its population?'},
    ],
    max_tokens: 50,
  });

  // Track usage with the meter
  meter.trackUsage(response, STRIPE_CUSTOMER_ID);

  console.log('Response:', response.choices[0]?.message?.content);
  console.log('Usage:', response.usage);
}

// Run all samples
async function runAllSamples() {
  console.log('Starting OpenAI Usage Tracking Examples');
  console.log(
    'These examples show how to use the generic token meter with OpenAI\n'
  );

  try {
    await sampleBasicChatCompletion();
    await sampleStreamingChatCompletion();
    await sampleChatCompletionWithTools();
    await sampleStreamingChatCompletionWithTools();
    await sampleResponsesAPIBasic();
    await sampleResponsesAPIStreaming();
    await sampleResponsesAPIParse();
    await sampleEmbeddings();
    await sampleConversation();

    console.log('\n' + '='.repeat(80));
    console.log('All examples completed successfully!');
    console.log('='.repeat(80));
  } catch (error) {
    console.error('\n❌ Sample failed:', error);
    throw error;
  }
}

// Run the samples
runAllSamples().catch(console.error);


```

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

```typescript
/**
 * Tests for Stripe AI SDK Provider
 */

import {createStripe, stripe} from '../index';
import {StripeLanguageModel} from '../stripe-language-model';

describe('Stripe Provider', () => {
  describe('createStripe', () => {
    it('should create a provider instance', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_123',
      });

      expect(provider).toBeDefined();
      expect(typeof provider).toBe('function');
      expect(provider.languageModel).toBeDefined();
    });

    it('should create a language model with provider function', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_123',
      });

      const model = provider('openai/gpt-5');

      expect(model).toBeInstanceOf(StripeLanguageModel);
      expect(model.modelId).toBe('openai/gpt-5');
      expect(model.provider).toBe('stripe');
      expect(model.specificationVersion).toBe('v2');
    });

    it('should create a language model with languageModel method', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_123',
      });

      const model = provider.languageModel('google/gemini-2.5-pro');

      expect(model).toBeInstanceOf(StripeLanguageModel);
      expect(model.modelId).toBe('google/gemini-2.5-pro');
    });

    it('should throw error when called with new keyword', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
      });

      expect(() => {
        // @ts-expect-error - Testing error case
        new provider('openai/gpt-5');
      }).toThrow(
        'The Stripe provider function cannot be called with the new keyword.'
      );
    });

    it('should use default baseURL when not provided', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_123',
      });

      const model = provider('openai/gpt-5') as any;
      expect(model.config.baseURL).toBe('https://llm.stripe.com');
    });

    it('should use custom baseURL when provided', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_123',
        baseURL: 'https://custom.stripe.com',
      });

      const model = provider('openai/gpt-5') as any;
      expect(model.config.baseURL).toBe('https://custom.stripe.com');
    });

    it('should merge customer IDs from provider and model settings', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_provider',
      });

      const model1 = provider('openai/gpt-5') as any;
      expect(model1.settings.customerId).toBe('cus_provider');

      const model2 = provider('openai/gpt-5', {
        customerId: 'cus_model',
      }) as any;
      expect(model2.settings.customerId).toBe('cus_model');
    });

    it('should throw error for text embedding models', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
      });

      expect(() => {
        provider.textEmbeddingModel('openai/text-embedding-3-small');
      }).toThrow('Text embedding models are not yet supported by Stripe provider');
    });

    it('should throw error for image models', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
      });

      expect(() => {
        provider.imageModel('openai/dall-e-3');
      }).toThrow('Image models are not yet supported by Stripe provider');
    });
  });

  describe('default stripe provider', () => {
    beforeEach(() => {
      // Clear environment variable
      delete process.env.STRIPE_API_KEY;
    });

    it('should be defined', () => {
      expect(stripe).toBeDefined();
      expect(typeof stripe).toBe('function');
    });

    it('should use STRIPE_API_KEY from environment', () => {
      process.env.STRIPE_API_KEY = 'sk_test_env';

      // This would throw if API key is not found
      const model = stripe('openai/gpt-5', {customerId: 'cus_123'});
      expect(model).toBeDefined();
    });
  });

  describe('API key handling', () => {
    it('should throw error when API key is not provided', () => {
      delete process.env.STRIPE_API_KEY;

      const provider = createStripe({
        customerId: 'cus_123',
      });

      expect(() => {
        const model = provider('openai/gpt-5') as any;
        model.config.headers();
      }).toThrow(
        'Stripe API key is required. Provide it via config.apiKey or STRIPE_API_KEY environment variable.'
      );
    });

    it('should use API key from config', () => {
      const provider = createStripe({
        apiKey: 'sk_test_config',
        customerId: 'cus_123',
      });

      const model = provider('openai/gpt-5') as any;
      const headers = model.config.headers();

      expect(headers.Authorization).toBe('Bearer sk_test_config');
    });

    it('should prefer config API key over environment', () => {
      process.env.STRIPE_API_KEY = 'sk_test_env';

      const provider = createStripe({
        apiKey: 'sk_test_config',
        customerId: 'cus_123',
      });

      const model = provider('openai/gpt-5') as any;
      const headers = model.config.headers();

      expect(headers.Authorization).toBe('Bearer sk_test_config');
    });
  });

  describe('headers handling', () => {
    it('should include custom headers from provider config', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
        customerId: 'cus_123',
        headers: {
          'X-Custom': 'provider-value',
        },
      });

      const model = provider('openai/gpt-5') as any;
      const headers = model.config.headers();

      expect(headers['X-Custom']).toBe('provider-value');
    });

    it('should include custom headers from model settings', () => {
      const provider = createStripe({
        apiKey: 'sk_test_123',
      });

      const model = provider('openai/gpt-5', {
        customerId: 'cus_123',
        headers: {
          'X-Model': 'model-value',
        },
      }) as any;

      expect(model.settings.headers['X-Model']).toBe('model-value');
    });
  });

  describe('supported models', () => {
    const provider = createStripe({
      apiKey: 'sk_test_123',
      customerId: 'cus_123',
    });

    const testModels = [
      'openai/gpt-5',
      'openai/gpt-4.1',
      'openai/o3',
      'google/gemini-2.5-pro',
      'google/gemini-2.0-flash',
      'anthropic/claude-sonnet-4',
      'anthropic/claude-opus-4',
    ];

    testModels.forEach((modelId) => {
      it(`should create model for ${modelId}`, () => {
        const model = provider(modelId);
        expect(model.modelId).toBe(modelId);
      });
    });
  });

  describe('model name normalization', () => {
    const provider = createStripe({
      apiKey: 'sk_test_123',
      customerId: 'cus_123',
    });

    describe('Anthropic models', () => {
      it('should normalize model with date suffix (YYYYMMDD)', () => {
        const model = provider('anthropic/claude-3-5-sonnet-20241022');
        expect(model.modelId).toBe('anthropic/claude-3.5-sonnet');
      });

      it('should normalize model with -latest suffix', () => {
        const model = provider('anthropic/claude-sonnet-4-latest');
        expect(model.modelId).toBe('anthropic/claude-sonnet-4');
      });

      it('should normalize version dashes to dots', () => {
        const model = provider('anthropic/claude-3-5-sonnet');
        expect(model.modelId).toBe('anthropic/claude-3.5-sonnet');
      });

      it('should handle combined normalization (date + version)', () => {
        const model = provider('anthropic/claude-3-7-sonnet-20250115');
        expect(model.modelId).toBe('anthropic/claude-3.7-sonnet');
      });

      it('should normalize sonnet-4-5 to sonnet-4.5', () => {
        const model = provider('anthropic/sonnet-4-5');
        expect(model.modelId).toBe('anthropic/sonnet-4.5');
      });

      it('should normalize opus-4-1 to opus-4.1', () => {
        const model = provider('anthropic/opus-4-1');
        expect(model.modelId).toBe('anthropic/opus-4.1');
      });
    });

    describe('OpenAI models', () => {
      it('should normalize model with date suffix (YYYY-MM-DD)', () => {
        const model = provider('openai/gpt-4-turbo-2024-04-09');
        expect(model.modelId).toBe('openai/gpt-4-turbo');
      });

      it('should keep gpt-4o-2024-05-13 as exception', () => {
        const model = provider('openai/gpt-4o-2024-05-13');
        expect(model.modelId).toBe('openai/gpt-4o-2024-05-13');
      });

      it('should not normalize YYYYMMDD format (only YYYY-MM-DD)', () => {
        const model = provider('openai/gpt-4-20241231');
        expect(model.modelId).toBe('openai/gpt-4-20241231');
      });
    });

    describe('Google models', () => {
      it('should keep Google models unchanged', () => {
        const model = provider('google/gemini-2.5-pro');
        expect(model.modelId).toBe('google/gemini-2.5-pro');
      });

      it('should not remove any suffixes from Google models', () => {
        const model = provider('google/gemini-2.5-pro-20250101');
        expect(model.modelId).toBe('google/gemini-2.5-pro-20250101');
      });
    });

    describe('Other providers', () => {
      it('should keep unknown provider models unchanged', () => {
        const model = provider('custom/my-model-1-2-3');
        expect(model.modelId).toBe('custom/my-model-1-2-3');
      });
    });
  });
});


```

--------------------------------------------------------------------------------
/tools/python/examples/openai/customer_support/emailer.py:
--------------------------------------------------------------------------------

```python
# pyright: strict

import imaplib
import email
import smtplib
from email.mime.text import MIMEText
from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr
from typing import List, Tuple, Callable, Union, Awaitable
import asyncio
import json
import re
from datetime import datetime
from email.utils import parsedate_to_datetime


class Email:
    def __init__(
        self,
        from_address: str,
        to_address: str,
        subject: str,
        body: str,
        id: str = "",
        date: datetime = datetime.now(),
    ):
        self.id = id
        self.to_address = to_address
        self.from_address = from_address
        self.subject = subject
        self.body = body
        self.date = date

    def to_message(self, reply_id: str, reply_to: str) -> MIMEMultipart:
        msg = MIMEMultipart()
        msg["From"] = self.from_address
        msg["To"] = self.to_address
        msg["Subject"] = self.subject
        msg["In-Reply-To"] = reply_id
        msg["References"] = reply_id
        msg["Reply-To"] = reply_to
        msg.attach(MIMEText(f"<html><body>{self.body}</body></html>", "html"))
        return msg

    def to_dict(self):
        return {
            "id": self.id,
            "to": self.to_address,
            "from": self.from_address,
            "subject": self.subject,
            "body": self.body,
            "date": self.date.strftime("%a, %d %b %Y %H:%M:%S %z"),
        }


class Emailer:
    """
    Emailer is an IMAP/SMTP client that can be used to fetch and respond to emails.
    It was mostly vibe-coded so please make improvements!
    TODO: add agent replies to the context
    """

    def __init__(
        self,
        email_address: str,
        email_password: str,
        support_address: str = "",
        imap_server: str = "imap.gmail.com",
        imap_port: int = 993,
        smtp_server: str = "smtp.gmail.com",
        smtp_port: int = 587,
    ):
        # Email configuration
        self.email_address = email_address
        self.support_address = support_address if support_address else email_address
        self.email_password = email_password
        self.imap_server = imap_server
        self.imap_port = imap_port
        self.smtp_server = smtp_server
        self.smtp_port = smtp_port

    def _connect_to_email(self) -> Tuple[imaplib.IMAP4_SSL, smtplib.SMTP]:
        """Establish connections to email servers."""
        # Connect to IMAP server
        imap_conn = imaplib.IMAP4_SSL(self.imap_server, self.imap_port)
        imap_conn.login(self.email_address, self.email_password)

        # Connect to SMTP server
        smtp_conn = smtplib.SMTP(self.smtp_server, self.smtp_port)
        smtp_conn.starttls()
        smtp_conn.login(self.email_address, self.email_password)

        return imap_conn, smtp_conn

    def _get_body(self, email_message: Message) -> str:
        body: str = ""
        if email_message.is_multipart():
            for part in email_message.walk():
                if part.get_content_type() == "text/plain":
                    payload = part.get_payload(decode=True)
                    if isinstance(payload, bytes):
                        body = payload.decode()
                    break
        else:
            payload = email_message.get_payload(decode=True)
            if isinstance(payload, bytes):
                body = payload.decode()
            else:
                body = str(payload)
        return self._strip_replies(body)

    def _strip_replies(self, raw_body: str) -> str:
        lines = raw_body.split("\n")
        pruned: List[str] = []
        for line in lines:
            # Stop if we see a typical reply indicator
            if line.strip().startswith("On ") and " wrote:" in line:
                break
            pruned.append(line)
        return "\n".join(pruned).strip()

    def _parse_email(
        self, imap_conn: imaplib.IMAP4_SSL, email_id: bytes
    ) -> Union[Email, None]:
        _, msg_data = imap_conn.fetch(email_id.decode(), "(BODY.PEEK[])")
        if not msg_data or not msg_data[0]:
            return None
        msg_resp = msg_data[0]
        if isinstance(msg_resp, tuple) and len(msg_resp) == 2:
            email_body = msg_resp[1]
        else:
            return None

        email_message = email.message_from_bytes(email_body)
        subject = email_message["subject"] or ""
        from_address = parseaddr(email_message.get("From", ""))[1]
        to_address = parseaddr(email_message.get("To", ""))[1]
        date_str = email_message.get("Date", "")
        date = datetime.now()
        if date_str:
            try:
                date = parsedate_to_datetime(date_str)
            except Exception:
                pass

        body = self._get_body(email_message)
        return Email(
            id=email_id.decode(),
            from_address=from_address,
            to_address=to_address,
            subject=subject,
            body=body,
            date=date,
        )

    def _get_email_thread(
        self, imap_conn: imaplib.IMAP4_SSL, email_id_bytes: bytes
    ) -> List[Email]:
        email = self._parse_email(imap_conn, email_id_bytes)
        if not email:
            return []

        thread = [email]

        # Try thread via X-GM-THRID (Gmail extension)
        _, thrid_data = imap_conn.fetch(email.id, "(X-GM-THRID)")
        match = None
        if thrid_data and thrid_data[0]:
            data = thrid_data[0]
            if isinstance(data, bytes):
                match = re.search(r"X-GM-THRID\s+(\d+)", data.decode())
            else:
                match = re.search(r"X-GM-THRID\s+(\d+)", str(data))
        if match:
            thread_id = match.group(1)
            _, thread_ids = imap_conn.search(None, f"X-GM-THRID {thread_id}")
            if thread_ids and thread_ids[0]:
                thread = [
                    self._parse_email(imap_conn, mid) for mid in thread_ids[0].split()
                ]
                thread = [e for e in thread if e]
                thread.sort(key=lambda e: e.date)
                return thread

        # Fallback: use REFERENCES header
        _, ref_data = imap_conn.fetch(
            email.id, "(BODY.PEEK[HEADER.FIELDS (REFERENCES)])"
        )
        if ref_data and ref_data[0]:
            ref_line = (
                ref_data[0][1].decode() if isinstance(ref_data[0][1], bytes) else ""
            )
            refs = re.findall(r"<([^>]+)>", ref_line)
            for ref in refs:
                _, ref_ids = imap_conn.search(None, f'(HEADER Message-ID "<{ref}>")')
                if ref_ids and ref_ids[0]:
                    for ref_id in ref_ids[0].split():
                        ref_email = self._parse_email(imap_conn, ref_id)
                        if ref_email and ref_email.id not in [e.id for e in thread]:
                            thread.append(ref_email)

            # Sort emails in the thread by date (ascending order)
            thread.sort(key=lambda e: e.date)
            return thread

        return thread

    def _get_unread_emails(self, imap_conn: imaplib.IMAP4_SSL) -> List[List[Email]]:
        imap_conn.select("INBOX")
        _, msg_nums = imap_conn.search(None, f'(UNSEEN TO "{self.support_address}")')
        emails: List[List[Email]] = []

        for email_id in msg_nums[0].split():
            thread = self._get_email_thread(imap_conn, email_id)
            emails.append(thread)

        return emails

    def mark_as_read(self, imap_conn: imaplib.IMAP4_SSL, message_id: str):
        imap_conn.store(message_id, "+FLAGS", "\\Seen")

    def get_email_thread(self, email_id: str) -> List[Email]:
        # Connect to email servers
        imap_conn, smtp_conn = self._connect_to_email()
        imap_conn.select("INBOX")

        # Get the thread
        thread = self._get_email_thread(
            imap_conn=imap_conn, email_id_bytes=email_id.encode()
        )

        # Close connections
        imap_conn.logout()
        smtp_conn.quit()

        return thread

    async def process(
        self,
        respond: Callable[[List[Email]], Awaitable[Union[Email, None]]],
        mark_read: bool = True,
    ):
        # Connect to email servers
        imap_conn, smtp_conn = self._connect_to_email()

        # Get unread emails
        print("Fetching unread emails...")
        unread_emails = self._get_unread_emails(imap_conn)
        for email_thread in unread_emails:
            # Get the most recent email in the thread
            most_recent = email_thread[-1]

            # Generate the response
            response = await respond(email_thread)

            # If there is no response, skip this email and keep as unread
            # in the inbox
            if response is None:
                continue

            # Send the response
            # Get the most recent email in the thread to reply to
            print(
                f"Replying to '{response.to_address}' with:\n  {json.dumps(response.body)}"
            )
            smtp_conn.send_message(
                response.to_message(most_recent.id, self.support_address)
            )

            # Mark the original email as read
            if mark_read:
                self.mark_as_read(imap_conn, most_recent.id)

        # Close connections
        imap_conn.logout()
        smtp_conn.quit()

    async def run(
        self,
        respond: Callable[[List[Email]], Awaitable[Union[Email, None]]],
        mark_read: bool = True,
        delay: int = 60,
    ):
        while True:
            # Process emails
            await self.process(respond, mark_read)
            # Wait before next check
            print(f"Sleeping for {delay}s...")
            await asyncio.sleep(delay)

```

--------------------------------------------------------------------------------
/tools/modelcontextprotocol/eslint.config.mjs:
--------------------------------------------------------------------------------

```
import prettier from "eslint-plugin-prettier";
import _import from "eslint-plugin-import";
import { fixupPluginRules } from "@eslint/compat";
import globals from "globals";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
    baseDirectory: __dirname,
    recommendedConfig: js.configs.recommended,
    allConfig: js.configs.all
});

export default [...compat.extends("plugin:prettier/recommended"), {
    plugins: {
        prettier,
        import: fixupPluginRules(_import),
    },

    languageOptions: {
        globals: {
            ...globals.node,
        },

        ecmaVersion: 2018,
        sourceType: "commonjs",
    },

    rules: {
        "accessor-pairs": "error",
        "array-bracket-spacing": ["error", "never"],
        "array-callback-return": "off",
        "arrow-parens": "error",
        "arrow-spacing": "error",
        "block-scoped-var": "off",
        "block-spacing": "off",

        "brace-style": ["error", "1tbs", {
            allowSingleLine: true,
        }],

        "capitalized-comments": "off",
        "class-methods-use-this": "off",
        "comma-dangle": "off",
        "comma-spacing": "off",
        "comma-style": ["error", "last"],
        complexity: "error",
        "computed-property-spacing": ["error", "never"],
        "consistent-return": "off",
        "consistent-this": "off",
        curly: "error",
        "default-case": "off",
        "dot-location": ["error", "property"],
        "dot-notation": "error",
        "eol-last": "error",
        eqeqeq: "off",
        "func-call-spacing": "error",
        "func-name-matching": "error",
        "func-names": "off",

        "func-style": ["error", "declaration", {
            allowArrowFunctions: true,
        }],

        "generator-star-spacing": "error",
        "global-require": "off",
        "guard-for-in": "error",
        "handle-callback-err": "off",
        "id-blacklist": "error",
        "id-length": "off",
        "id-match": "error",
        "import/extensions": "off",
        "init-declarations": "off",
        "jsx-quotes": "error",
        "key-spacing": "error",

        "keyword-spacing": ["error", {
            after: true,
            before: true,
        }],

        "line-comment-position": "off",
        "linebreak-style": ["error", "unix"],
        "lines-around-directive": "error",
        "max-depth": "error",
        "max-len": "off",
        "max-lines": "off",
        "max-nested-callbacks": "error",
        "max-params": "off",
        "max-statements": "off",
        "max-statements-per-line": "off",
        "multiline-ternary": "off",
        "new-cap": "off",
        "new-parens": "error",
        "newline-after-var": "off",
        "newline-before-return": "off",
        "newline-per-chained-call": "off",
        "no-alert": "error",
        "no-array-constructor": "error",
        "no-await-in-loop": "error",
        "no-bitwise": "off",
        "no-caller": "error",
        "no-catch-shadow": "off",
        "no-compare-neg-zero": "error",
        "no-confusing-arrow": "error",
        "no-continue": "off",
        "no-div-regex": "error",
        "no-duplicate-imports": "off",
        "no-else-return": "off",
        "no-empty-function": "off",
        "no-eq-null": "off",
        "no-eval": "error",
        "no-extend-native": "error",
        "no-extra-bind": "error",
        "no-extra-label": "error",
        "no-extra-parens": "off",
        "no-floating-decimal": "error",
        "no-implicit-globals": "error",
        "no-implied-eval": "error",
        "no-inline-comments": "off",
        "no-inner-declarations": ["error", "functions"],
        "no-invalid-this": "off",
        "no-iterator": "error",
        "no-label-var": "error",
        "no-labels": "error",
        "no-lone-blocks": "error",
        "no-lonely-if": "error",
        "no-loop-func": "error",
        "no-magic-numbers": "off",
        "no-mixed-requires": "error",
        "no-multi-assign": "off",
        "no-multi-spaces": "error",
        "no-multi-str": "error",
        "no-multiple-empty-lines": "error",
        "no-native-reassign": "error",
        "no-negated-condition": "off",
        "no-negated-in-lhs": "error",
        "no-nested-ternary": "error",
        "no-new": "error",
        "no-new-func": "error",
        "no-new-object": "error",
        "no-new-require": "error",
        "no-new-wrappers": "error",
        "no-octal-escape": "error",
        "no-param-reassign": "off",
        "no-path-concat": "error",

        "no-plusplus": ["error", {
            allowForLoopAfterthoughts: true,
        }],

        "no-process-env": "off",
        "no-process-exit": "error",
        "no-proto": "error",
        "no-prototype-builtins": "off",
        "no-restricted-globals": "error",
        "no-restricted-imports": "error",
        "no-restricted-modules": "error",
        "no-restricted-properties": "error",
        "no-restricted-syntax": "error",
        "no-return-assign": "error",
        "no-return-await": "error",
        "no-script-url": "error",
        "no-self-compare": "error",
        "no-sequences": "error",
        "no-shadow": "off",
        "no-shadow-restricted-names": "error",
        "no-spaced-func": "error",
        "no-sync": "error",
        "no-tabs": "error",
        "no-template-curly-in-string": "error",
        "no-ternary": "off",
        "no-throw-literal": "error",
        "no-trailing-spaces": "error",
        "no-undef-init": "error",
        "no-undefined": "off",
        "no-underscore-dangle": "off",
        "no-unmodified-loop-condition": "error",
        "no-unneeded-ternary": "error",
        "no-unused-expressions": "error",

        "no-unused-vars": ["error", {
            args: "none",
        }],

        "no-use-before-define": "off",
        "no-useless-call": "error",
        "no-useless-computed-key": "error",
        "no-useless-concat": "error",
        "no-useless-constructor": "error",
        "no-useless-escape": "off",
        "no-useless-rename": "error",
        "no-useless-return": "error",
        "no-var": "off",
        "no-void": "error",
        "no-warning-comments": "error",
        "no-whitespace-before-property": "error",
        "no-with": "error",
        "nonblock-statement-body-position": "error",
        "object-curly-newline": "off",
        "object-curly-spacing": ["error", "never"],
        "object-property-newline": "off",
        "object-shorthand": "off",
        "one-var": "off",
        "one-var-declaration-per-line": "error",
        "operator-assignment": ["error", "always"],
        "operator-linebreak": "off",
        "padded-blocks": "off",
        "prefer-arrow-callback": "off",
        "prefer-const": "error",

        "prefer-destructuring": ["error", {
            array: false,
            object: false,
        }],

        "prefer-numeric-literals": "error",
        "prefer-promise-reject-errors": "error",
        "prefer-reflect": "off",
        "prefer-rest-params": "off",
        "prefer-spread": "off",
        "prefer-template": "off",
        "quote-props": "off",

        quotes: ["error", "single", {
            avoidEscape: true,
        }],

        radix: "error",
        "require-await": "error",
        "require-jsdoc": "off",
        "rest-spread-spacing": "error",
        semi: "off",

        "semi-spacing": ["error", {
            after: true,
            before: false,
        }],

        "sort-imports": "off",
        "sort-keys": "off",
        "sort-vars": "error",
        "space-before-blocks": "error",
        "space-before-function-paren": "off",
        "space-in-parens": ["error", "never"],
        "space-infix-ops": "error",
        "space-unary-ops": "error",
        "spaced-comment": ["error", "always"],
        strict: "off",
        "symbol-description": "error",
        "template-curly-spacing": "error",
        "template-tag-spacing": "error",
        "unicode-bom": ["error", "never"],
        "valid-jsdoc": "off",
        "vars-on-top": "off",
        "wrap-regex": "off",
        "yield-star-spacing": "error",
        yoda: ["error", "never"],
    },
}, ...compat.extends(
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
).map(config => ({
    ...config,
    files: ["**/*.ts"],
})), {
    files: ["**/*.ts"],

    plugins: {
        "@typescript-eslint": typescriptEslint,
        prettier,
    },

    rules: {
        "@typescript-eslint/no-use-before-define": 0,
        "@typescript-eslint/no-empty-interface": 0,
        "@typescript-eslint/no-unused-vars": 0,
        "@typescript-eslint/triple-slash-reference": 0,
        "@typescript-eslint/ban-ts-comment": "off",
        "@typescript-eslint/no-empty-function": 0,
        "@typescript-eslint/no-require-imports": 0,

        "@typescript-eslint/naming-convention": ["error", {
            selector: "default",
            format: ["camelCase", "UPPER_CASE", "PascalCase"],
            leadingUnderscore: "allow",
        }, {
            selector: "property",
            format: null,
        }],

        "@typescript-eslint/no-explicit-any": 0,
        "@typescript-eslint/explicit-function-return-type": "off",
        "@typescript-eslint/no-this-alias": "off",
        "@typescript-eslint/no-var-requires": 0,
        "prefer-rest-params": "off",
    },
}, {
    files: ["test/**/*.ts"],

    rules: {
        "@typescript-eslint/explicit-function-return-type": "off",
    },
}];

```

--------------------------------------------------------------------------------
/tools/typescript/eslint.config.mjs:
--------------------------------------------------------------------------------

```
import prettier from "eslint-plugin-prettier";
import _import from "eslint-plugin-import";
import { fixupPluginRules } from "@eslint/compat";
import globals from "globals";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
    baseDirectory: __dirname,
    recommendedConfig: js.configs.recommended,
    allConfig: js.configs.all
});

export default [...compat.extends("plugin:prettier/recommended"), {
    plugins: {
        prettier,
        import: fixupPluginRules(_import),
    },

    languageOptions: {
        globals: {
            ...globals.node,
        },

        ecmaVersion: 2018,
        sourceType: "commonjs",
    },

    rules: {
        "accessor-pairs": "error",
        "array-bracket-spacing": ["error", "never"],
        "array-callback-return": "off",
        "arrow-parens": "error",
        "arrow-spacing": "error",
        "block-scoped-var": "off",
        "block-spacing": "off",

        "brace-style": ["error", "1tbs", {
            allowSingleLine: true,
        }],

        "capitalized-comments": "off",
        "class-methods-use-this": "off",
        "comma-dangle": "off",
        "comma-spacing": "off",
        "comma-style": ["error", "last"],
        complexity: "error",
        "computed-property-spacing": ["error", "never"],
        "consistent-return": "off",
        "consistent-this": "off",
        curly: "error",
        "default-case": "off",
        "dot-location": ["error", "property"],
        "dot-notation": "error",
        "eol-last": "error",
        eqeqeq: "off",
        "func-call-spacing": "error",
        "func-name-matching": "error",
        "func-names": "off",

        "func-style": ["error", "declaration", {
            allowArrowFunctions: true,
        }],

        "generator-star-spacing": "error",
        "global-require": "off",
        "guard-for-in": "error",
        "handle-callback-err": "off",
        "id-blacklist": "error",
        "id-length": "off",
        "id-match": "error",
        "import/extensions": "off",
        "init-declarations": "off",
        "jsx-quotes": "error",
        "key-spacing": "error",

        "keyword-spacing": ["error", {
            after: true,
            before: true,
        }],

        "line-comment-position": "off",
        "linebreak-style": ["error", "unix"],
        "lines-around-directive": "error",
        "max-depth": "error",
        "max-len": "off",
        "max-lines": "off",
        "max-nested-callbacks": "error",
        "max-params": "off",
        "max-statements": "off",
        "max-statements-per-line": "off",
        "multiline-ternary": "off",
        "new-cap": "off",
        "new-parens": "error",
        "newline-after-var": "off",
        "newline-before-return": "off",
        "newline-per-chained-call": "off",
        "no-alert": "error",
        "no-array-constructor": "error",
        "no-await-in-loop": "error",
        "no-bitwise": "off",
        "no-caller": "error",
        "no-catch-shadow": "off",
        "no-compare-neg-zero": "error",
        "no-confusing-arrow": "error",
        "no-continue": "off",
        "no-div-regex": "error",
        "no-duplicate-imports": "off",
        "no-else-return": "off",
        "no-empty-function": "off",
        "no-eq-null": "off",
        "no-eval": "error",
        "no-extend-native": "error",
        "no-extra-bind": "error",
        "no-extra-label": "error",
        "no-extra-parens": "off",
        "no-floating-decimal": "error",
        "no-implicit-globals": "error",
        "no-implied-eval": "error",
        "no-inline-comments": "off",
        "no-inner-declarations": ["error", "functions"],
        "no-invalid-this": "off",
        "no-iterator": "error",
        "no-label-var": "error",
        "no-labels": "error",
        "no-lone-blocks": "error",
        "no-lonely-if": "error",
        "no-loop-func": "error",
        "no-magic-numbers": "off",
        "no-mixed-requires": "error",
        "no-multi-assign": "off",
        "no-multi-spaces": "error",
        "no-multi-str": "error",
        "no-multiple-empty-lines": "error",
        "no-native-reassign": "error",
        "no-negated-condition": "off",
        "no-negated-in-lhs": "error",
        "no-nested-ternary": "error",
        "no-new": "error",
        "no-new-func": "error",
        "no-new-object": "error",
        "no-new-require": "error",
        "no-new-wrappers": "error",
        "no-octal-escape": "error",
        "no-param-reassign": "off",
        "no-path-concat": "error",

        "no-plusplus": ["error", {
            allowForLoopAfterthoughts: true,
        }],

        "no-process-env": "off",
        "no-process-exit": "error",
        "no-proto": "error",
        "no-prototype-builtins": "off",
        "no-restricted-globals": "error",
        "no-restricted-imports": "error",
        "no-restricted-modules": "error",
        "no-restricted-properties": "error",
        "no-restricted-syntax": "error",
        "no-return-assign": "error",
        "no-return-await": "error",
        "no-script-url": "error",
        "no-self-compare": "error",
        "no-sequences": "error",
        "no-shadow": "off",
        "no-shadow-restricted-names": "error",
        "no-spaced-func": "error",
        "no-sync": "error",
        "no-tabs": "error",
        "no-template-curly-in-string": "error",
        "no-ternary": "off",
        "no-throw-literal": "error",
        "no-trailing-spaces": "error",
        "no-undef-init": "error",
        "no-undefined": "off",
        "no-underscore-dangle": "off",
        "no-unmodified-loop-condition": "error",
        "no-unneeded-ternary": "error",
        "no-unused-expressions": "error",

        "no-unused-vars": ["error", {
            args: "none",
        }],

        "no-use-before-define": "off",
        "no-useless-call": "error",
        "no-useless-computed-key": "error",
        "no-useless-concat": "error",
        "no-useless-constructor": "error",
        "no-useless-escape": "off",
        "no-useless-rename": "error",
        "no-useless-return": "error",
        "no-var": "off",
        "no-void": "error",
        "no-warning-comments": "error",
        "no-whitespace-before-property": "error",
        "no-with": "error",
        "nonblock-statement-body-position": "error",
        "object-curly-newline": "off",
        "object-curly-spacing": ["error", "never"],
        "object-property-newline": "off",
        "object-shorthand": "off",
        "one-var": "off",
        "one-var-declaration-per-line": "error",
        "operator-assignment": ["error", "always"],
        "operator-linebreak": "off",
        "padded-blocks": "off",
        "prefer-arrow-callback": "off",
        "prefer-const": "error",

        "prefer-destructuring": ["error", {
            array: false,
            object: false,
        }],

        "prefer-numeric-literals": "error",
        "prefer-promise-reject-errors": "error",
        "prefer-reflect": "off",
        "prefer-rest-params": "off",
        "prefer-spread": "off",
        "prefer-template": "off",
        "quote-props": "off",

        quotes: ["error", "single", {
            avoidEscape: true,
        }],

        radix: "error",
        "require-await": "error",
        "require-jsdoc": "off",
        "rest-spread-spacing": "error",
        semi: "off",

        "semi-spacing": ["error", {
            after: true,
            before: false,
        }],

        "sort-imports": "off",
        "sort-keys": "off",
        "sort-vars": "error",
        "space-before-blocks": "error",
        "space-before-function-paren": "off",
        "space-in-parens": ["error", "never"],
        "space-infix-ops": "error",
        "space-unary-ops": "error",
        "spaced-comment": ["error", "always"],
        strict: "off",
        "symbol-description": "error",
        "template-curly-spacing": "error",
        "template-tag-spacing": "error",
        "unicode-bom": ["error", "never"],
        "valid-jsdoc": "off",
        "vars-on-top": "off",
        "wrap-regex": "off",
        "yield-star-spacing": "error",
        yoda: ["error", "never"],
    },
}, ...compat.extends(
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
).map(config => ({
    ...config,
    files: ["**/*.ts"],
})), {
    files: ["**/*.ts"],

    plugins: {
        "@typescript-eslint": typescriptEslint,
        prettier,
    },

    rules: {
        "@typescript-eslint/no-use-before-define": 0,
        "@typescript-eslint/no-empty-interface": 0,
        "@typescript-eslint/no-unused-vars": 0,
        "@typescript-eslint/triple-slash-reference": 0,
        "@typescript-eslint/ban-ts-comment": "off",
        "@typescript-eslint/no-empty-function": 0,
        "@typescript-eslint/no-require-imports": 0,

        "@typescript-eslint/naming-convention": ["error", {
            selector: "default",
            format: ["camelCase", "UPPER_CASE", "PascalCase"],
            leadingUnderscore: "allow",
        }, {
            selector: "property",
            format: null,
        }],

        "@typescript-eslint/no-explicit-any": 0,
        "@typescript-eslint/explicit-function-return-type": "off",
        "@typescript-eslint/no-this-alias": "off",
        "@typescript-eslint/no-var-requires": 0,
        "prefer-rest-params": "off",
    },
}, {
    files: ["test/**/*.ts"],

    rules: {
        "@typescript-eslint/explicit-function-return-type": "off",
    },
}, {
    files: ["examples/cloudflare/**/*.ts", "examples/cloudflare/**/*.js", "examples/cloudflare/**/*.mjs"],
    ignores: [],
    rules: {
        // Disable all rules for cloudflare examples
        ...Object.fromEntries(
            Object.keys(typescriptEslint.rules).map(rule => [`@typescript-eslint/${rule}`, "off"])
        ),
        // Disable all base rules
        "no-unused-vars": "off",
        "no-undef": "off",
        "no-console": "off",
        "require-await": "off",
        "prettier/prettier": "off",
        "func-style": "off",
        "no-warning-comments": "off",
        "no-constant-condition": "off",
        // Add any other rules you want to disable
    }
}];

```

--------------------------------------------------------------------------------
/llm/token-meter/utils/type-detection.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utilities for detecting response types from different AI providers
 */

import type OpenAI from 'openai';
import type {Stream} from 'openai/streaming';
import type Anthropic from '@anthropic-ai/sdk';
import type {Stream as AnthropicStream} from '@anthropic-ai/sdk/streaming';
import type {GenerateContentResult} from '@google/generative-ai';

/**
 * Provider types
 */
export type Provider = 'openai' | 'anthropic' | 'google' | 'unknown';

/**
 * Response type categories
 */
export type ResponseType =
  | 'chat_completion'
  | 'response_api'
  | 'embedding'
  | 'unknown';

/**
 * Detected response information
 */
export interface DetectedResponse {
  provider: Provider;
  type: ResponseType;
  model: string;
  inputTokens: number;
  outputTokens: number;
}

/**
 * Check if response is an OpenAI ChatCompletion
 */
function isOpenAIChatCompletion(response: any): response is OpenAI.ChatCompletion {
  return (
    response &&
    typeof response === 'object' &&
    'choices' in response &&
    'model' in response &&
    response.choices?.[0]?.message !== undefined
  );
}

/**
 * Check if response is an OpenAI Responses API response
 */
function isOpenAIResponse(response: any): response is OpenAI.Responses.Response {
  return (
    response &&
    typeof response === 'object' &&
    'output' in response &&
    'model' in response &&
    'usage' in response &&
    response.usage?.input_tokens !== undefined
  );
}

/**
 * Check if response is an OpenAI Embedding response
 */
function isOpenAIEmbedding(
  response: any
): response is OpenAI.CreateEmbeddingResponse {
  return (
    response &&
    typeof response === 'object' &&
    'data' in response &&
    'model' in response &&
    Array.isArray(response.data) &&
    response.data?.[0]?.embedding !== undefined
  );
}

/**
 * Check if response is an Anthropic Message
 */
function isAnthropicMessage(
  response: any
): response is Anthropic.Messages.Message {
  return (
    response &&
    typeof response === 'object' &&
    'content' in response &&
    'model' in response &&
    'usage' in response &&
    response.usage?.input_tokens !== undefined &&
    response.usage?.output_tokens !== undefined &&
    response.type === 'message'
  );
}

/**
 * Check if response is a Gemini GenerateContentResult
 */
function isGeminiResponse(response: any): response is GenerateContentResult {
  return (
    response &&
    typeof response === 'object' &&
    'response' in response &&
    response.response?.usageMetadata !== undefined &&
    response.response?.usageMetadata?.promptTokenCount !== undefined
  );
}

/**
 * Detect and extract usage information from a response
 */
export function detectResponse(response: any): DetectedResponse | null {
  // OpenAI Chat Completion
  if (isOpenAIChatCompletion(response)) {
    return {
      provider: 'openai',
      type: 'chat_completion',
      model: response.model,
      inputTokens: response.usage?.prompt_tokens ?? 0,
      outputTokens: response.usage?.completion_tokens ?? 0,
    };
  }

  // OpenAI Responses API
  if (isOpenAIResponse(response)) {
    return {
      provider: 'openai',
      type: 'response_api',
      model: response.model,
      inputTokens: response.usage?.input_tokens ?? 0,
      outputTokens: response.usage?.output_tokens ?? 0,
    };
  }

  // OpenAI Embeddings
  if (isOpenAIEmbedding(response)) {
    return {
      provider: 'openai',
      type: 'embedding',
      model: response.model,
      inputTokens: response.usage?.prompt_tokens ?? 0,
      outputTokens: 0, // Embeddings don't have output tokens
    };
  }

  // Anthropic Message
  if (isAnthropicMessage(response)) {
    return {
      provider: 'anthropic',
      type: 'chat_completion',
      model: response.model,
      inputTokens: response.usage?.input_tokens ?? 0,
      outputTokens: response.usage?.output_tokens ?? 0,
    };
  }

  // Gemini GenerateContentResult
  if (isGeminiResponse(response)) {
    const usageMetadata = response.response.usageMetadata;
    const baseOutputTokens = usageMetadata?.candidatesTokenCount ?? 0;
    // thoughtsTokenCount is for extended thinking models, may not always be present
    const reasoningTokens = (usageMetadata as any)?.thoughtsTokenCount ?? 0;
    
    // Extract model name from response if available
    const model = (response.response as any)?.modelVersion || 'gemini';

    return {
      provider: 'google',
      type: 'chat_completion',
      model,
      inputTokens: usageMetadata?.promptTokenCount ?? 0,
      outputTokens: baseOutputTokens + reasoningTokens, // Include reasoning tokens
    };
  }

  // Unknown response type
  return null;
}

/**
 * Stream type detection
 */
export type StreamType = 'chat_completion' | 'response_api' | 'unknown';

/**
 * Detect stream type by checking if it has OpenAI stream methods
 * OpenAI streams have 'toReadableStream' method, Anthropic streams don't
 */
export function isOpenAIStream(stream: any): stream is Stream<any> {
  return (
    stream &&
    typeof stream === 'object' &&
    'tee' in stream &&
    'toReadableStream' in stream
  );
}

/**
 * Extract usage from OpenAI chat completion stream chunks
 */
export async function extractUsageFromChatStream(
  stream: Stream<OpenAI.ChatCompletionChunk>
): Promise<DetectedResponse | null> {
  let usage: any = {
    prompt_tokens: 0,
    completion_tokens: 0,
  };
  let model = '';

  try {
    for await (const chunk of stream) {
      if (chunk.model) {
        model = chunk.model;
      }
      if (chunk.usage) {
        usage = chunk.usage;
      }
    }

    if (model) {
      return {
        provider: 'openai',
        type: 'chat_completion',
        model,
        inputTokens: usage.prompt_tokens ?? 0,
        outputTokens: usage.completion_tokens ?? 0,
      };
    }
  } catch (error) {
    console.error('Error extracting usage from chat stream:', error);
  }

  return null;
}

/**
 * Extract usage from OpenAI Responses API stream events
 */
export async function extractUsageFromResponseStream(
  stream: Stream<OpenAI.Responses.ResponseStreamEvent>
): Promise<DetectedResponse | null> {
  let usage: any = {
    input_tokens: 0,
    output_tokens: 0,
  };
  let model = '';

  try {
    for await (const chunk of stream) {
      if ('response' in chunk && chunk.response) {
        if (chunk.response.model) {
          model = chunk.response.model;
        }
        if (chunk.response.usage) {
          usage = chunk.response.usage;
        }
      }
    }

    if (model) {
      return {
        provider: 'openai',
        type: 'response_api',
        model,
        inputTokens: usage.input_tokens ?? 0,
        outputTokens: usage.output_tokens ?? 0,
      };
    }
  } catch (error) {
    console.error('Error extracting usage from response stream:', error);
  }

  return null;
}

/**
 * Check if stream is an Anthropic stream
 * Anthropic streams have 'controller' but NOT 'toReadableStream'
 */
export function isAnthropicStream(
  stream: any
): stream is AnthropicStream<Anthropic.Messages.RawMessageStreamEvent> {
  return (
    stream &&
    typeof stream === 'object' &&
    'tee' in stream &&
    'controller' in stream &&
    !('toReadableStream' in stream)
  );
}

/**
 * Extract usage from Anthropic message stream events
 */
export async function extractUsageFromAnthropicStream(
  stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>
): Promise<DetectedResponse | null> {
  const usage: {
    input_tokens: number;
    output_tokens: number;
  } = {
    input_tokens: 0,
    output_tokens: 0,
  };
  let model = '';

  try {
    for await (const chunk of stream) {
      // Capture usage from message_start event (input tokens)
      if (chunk.type === 'message_start') {
        usage.input_tokens = chunk.message.usage.input_tokens ?? 0;
        model = chunk.message.model;
      }
      // Capture usage from message_delta event (output tokens)
      if (chunk.type === 'message_delta' && 'usage' in chunk) {
        usage.output_tokens = chunk.usage.output_tokens ?? 0;
      }
    }

    if (model) {
      return {
        provider: 'anthropic',
        type: 'chat_completion',
        model,
        inputTokens: usage.input_tokens,
        outputTokens: usage.output_tokens,
      };
    }
  } catch (error) {
    console.error('Error extracting usage from Anthropic stream:', error);
  }

  return null;
}

/**
 * Check if stream is a Gemini stream
 * Gemini returns an object with {stream, response}, not just a stream
 */
export function isGeminiStream(stream: any): boolean {
  // Gemini returns {stream: AsyncGenerator, response: Promise}
  return (
    stream &&
    typeof stream === 'object' &&
    'stream' in stream &&
    'response' in stream &&
    typeof stream.stream?.[Symbol.asyncIterator] === 'function' &&
    !('tee' in stream)
  );
}

/**
 * Extract usage from Gemini stream
 * Gemini provides an object with both stream and response promise
 */
export async function extractUsageFromGeminiStream(
  streamResult: any
): Promise<DetectedResponse | null> {
  try {
    // Gemini returns {stream, response}
    // We need to consume the stream to get usage
    let lastUsageMetadata: any = null;

    for await (const chunk of streamResult.stream) {
      if (chunk.usageMetadata) {
        lastUsageMetadata = chunk.usageMetadata;
      }
    }

    if (lastUsageMetadata) {
      const baseOutputTokens = lastUsageMetadata?.candidatesTokenCount ?? 0;
      // thoughtsTokenCount is for extended thinking models, may not always be present
      const reasoningTokens = (lastUsageMetadata as any)?.thoughtsTokenCount ?? 0;
      
      // Get model from the response - this field is always present in real Gemini responses
      const response = await streamResult.response;
      const model = (response as any)?.modelVersion;
      
      if (!model) {
        throw new Error('Gemini response is missing modelVersion field. This should never happen with real Gemini API responses.');
      }

      return {
        provider: 'google',
        type: 'chat_completion',
        model,
        inputTokens: lastUsageMetadata?.promptTokenCount ?? 0,
        outputTokens: baseOutputTokens + reasoningTokens,
      };
    }
  } catch (error) {
    console.error('Error extracting usage from Gemini stream:', error);
  }

  return null;
}

/**
 * Detect stream type by examining first chunk (without consuming the stream)
 * This is a heuristic approach - for now we'll try chat completion first,
 * then fall back to response API
 */
export function detectStreamType(_stream: any): StreamType {
  // For now, we'll assume chat completion by default
  // In the future, we could peek at the first chunk to determine type
  return 'chat_completion';
}


```

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

```python
import stripe
from typing import Optional
from .configuration import Context


def create_customer(context: Context, name: str, email: Optional[str] = None):
    """
    Create a customer.

    Parameters:
        name (str): The name of the customer.
        email (str, optional): The email address of the customer.

    Returns:
        stripe.Customer: The created customer.
    """
    customer_data: dict = {"name": name}
    if email:
        customer_data["email"] = email
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            customer_data["stripe_account"] = account

    customer = stripe.Customer.create(**customer_data)
    return {"id": customer.id}


def list_customers(
    context: Context,
    email: Optional[str] = None,
    limit: Optional[int] = None,
):
    """
    List Customers.

    Parameters:
        email (str, optional): The email address of the customer.
        limit (int, optional): The number of customers to return.

    Returns:
        stripe.ListObject: A list of customers.
    """
    customer_data: dict = {}
    if email:
        customer_data["email"] = email
    if limit:
        customer_data["limit"] = limit
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            customer_data["stripe_account"] = account

    customers = stripe.Customer.list(**customer_data)
    return [{"id": customer.id} for customer in customers.data]


def create_product(
    context: Context, name: str, description: Optional[str] = None
):
    """
    Create a product.

    Parameters:
        name (str): The name of the product.
        description (str, optional): The description of the product.

    Returns:
        stripe.Product: The created product.
    """
    product_data: dict = {"name": name}
    if description:
        product_data["description"] = description
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            product_data["stripe_account"] = account

    return stripe.Product.create(**product_data)


def list_products(context: Context, limit: Optional[int] = None):
    """
    List Products.
    Parameters:
        limit (int, optional): The number of products to return.

    Returns:
        stripe.ListObject: A list of products.
    """
    product_data: dict = {}
    if limit:
        product_data["limit"] = limit
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            product_data["stripe_account"] = account

    return stripe.Product.list(**product_data).data


def create_price(
    context: Context, product: str, currency: str, unit_amount: int
):
    """
    Create a price.

    Parameters:
        product (str): The ID of the product.
        currency (str): The currency of the price.
        unit_amount (int): The unit amount of the price.

    Returns:
        stripe.Price: The created price.
    """
    price_data: dict = {
        "product": product,
        "currency": currency,
        "unit_amount": unit_amount,
    }
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            price_data["stripe_account"] = account

    return stripe.Price.create(**price_data)


def list_prices(
    context: Context,
    product: Optional[str] = None,
    limit: Optional[int] = None,
):
    """
    List Prices.

    Parameters:
        product (str, optional): The ID of the product to list prices for.
        limit (int, optional): The number of prices to return.

    Returns:
        stripe.ListObject: A list of prices.
    """
    prices_data: dict = {}
    if product:
        prices_data["product"] = product
    if limit:
        prices_data["limit"] = limit
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            prices_data["stripe_account"] = account

    return stripe.Price.list(**prices_data).data


def create_payment_link(context: Context, price: str, quantity: int, redirect_url: Optional[str] = None):
    """
    Create a payment link.

    Parameters:
        price (str): The ID of the price.
        quantity (int): The quantity of the product.
        redirect_url (string, optional): The URL the customer will be redirected to after the purchase is complete.

    Returns:
        stripe.PaymentLink: The created payment link.
    """
    payment_link_data: dict = {
        "line_items": [{"price": price, "quantity": quantity}],
    }
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            payment_link_data["stripe_account"] = account

    if redirect_url:
        payment_link_data["after_completion"] = {"type": "redirect", "redirect": {"url": redirect_url}}

    payment_link = stripe.PaymentLink.create(**payment_link_data)

    return {"id": payment_link.id, "url": payment_link.url}


def list_invoices(
    context: Context,
    customer: Optional[str] = None,
    limit: Optional[int] = None,
):
    """
    List invoices.

    Parameters:
        customer (str, optional): The ID of the customer.
        limit (int, optional): The number of invoices to return.

    Returns:
        stripe.ListObject: A list of invoices.
    """
    invoice_data: dict = {}
    if customer:
        invoice_data["customer"] = customer
    if limit:
        invoice_data["limit"] = limit
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            invoice_data["stripe_account"] = account

    return stripe.Invoice.list(**invoice_data).data


def create_invoice(context: Context, customer: str, days_until_due: int = 30):
    """
    Create an invoice.

    Parameters:
        customer (str): The ID of the customer.
        days_until_due (int, optional): The number of days until the
        invoice is due.

    Returns:
        stripe.Invoice: The created invoice.
    """
    invoice_data: dict = {
        "customer": customer,
        "collection_method": "send_invoice",
        "days_until_due": days_until_due,
    }
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            invoice_data["stripe_account"] = account

    invoice = stripe.Invoice.create(**invoice_data)

    return {
        "id": invoice.id,
        "hosted_invoice_url": invoice.hosted_invoice_url,
        "customer": invoice.customer,
        "status": invoice.status,
    }


def create_invoice_item(
    context: Context, customer: str, price: str, invoice: str
):
    """
    Create an invoice item.

    Parameters:
        customer (str): The ID of the customer.
        price (str): The ID of the price.
        invoice (str): The ID of the invoice.

    Returns:
        stripe.InvoiceItem: The created invoice item.
    """
    invoice_item_data: dict = {
        "customer": customer,
        "price": price,
        "invoice": invoice,
    }
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            invoice_item_data["stripe_account"] = account

    invoice_item = stripe.InvoiceItem.create(**invoice_item_data)

    return {"id": invoice_item.id, "invoice": invoice_item.invoice}


def finalize_invoice(context: Context, invoice: str):
    """
    Finalize an invoice.

    Parameters:
        invoice (str): The ID of the invoice.

    Returns:
        stripe.Invoice: The finalized invoice.
    """
    invoice_data: dict = {"invoice": invoice}
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            invoice_data["stripe_account"] = account

    invoice_object = stripe.Invoice.finalize_invoice(**invoice_data)

    return {
        "id": invoice_object.id,
        "hosted_invoice_url": invoice_object.hosted_invoice_url,
        "customer": invoice_object.customer,
        "status": invoice_object.status,
    }


def retrieve_balance(
    context: Context,
):
    """
    Retrieve the balance.

    Returns:
        stripe.Balance: The balance.
    """
    balance_data: dict = {}
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            balance_data["stripe_account"] = account

    return stripe.Balance.retrieve(**balance_data)


def create_refund(
    context: Context, payment_intent: str, amount: Optional[int] = None
):
    """
    Create a refund.

    Parameters:
        payment_intent (str): The ID of the payment intent.
        amount (int, optional): The amount to refund in cents.

    Returns:
        stripe.Refund: The created refund.
    """
    refund_data: dict = {
        "payment_intent": payment_intent,
    }
    if amount:
        refund_data["amount"] = amount
        if context.get("account") is not None:
            account = context.get("account")
            if account is not None:
                refund_data["stripe_account"] = account

    return stripe.Refund.create(**refund_data)

def list_payment_intents(context: Context, customer: Optional[str] = None, limit: Optional[int] = None):
    """
    List payment intents.

    Parameters:
        customer (str, optional): The ID of the customer to list payment intents for.
        limit (int, optional): The number of payment intents to return.

    Returns:
        stripe.ListObject: A list of payment intents.
    """
    payment_intent_data: dict = {}
    if customer:
        payment_intent_data["customer"] = customer
    if limit:
        payment_intent_data["limit"] = limit
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            payment_intent_data["stripe_account"] = account

    return stripe.PaymentIntent.list(**payment_intent_data).data

def create_billing_portal_session(context: Context, customer: str, return_url: Optional[str] = None):
    """
    Creates a session of the customer portal.

    Parameters:
        customer (str): The ID of the customer to list payment intents for.
        return_url (str, optional): The URL to return to after the session is complete.

    Returns:
        stripe.BillingPortalSession: The created billing portal session.
    """
    billing_portal_session_data: dict = {
        "customer": customer,
    }
    if return_url:
        billing_portal_session_data["return_url"] = return_url
    if context.get("account") is not None:
        account = context.get("account")
        if account is not None:
            billing_portal_session_data["stripe_account"] = account

    session_object = stripe.billing_portal.Session.create(**billing_portal_session_data)

    return {
        "id": session_object.id,
        "customer": session_object.customer,
        "url": session_object.url,
    }

```

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

```typescript
// From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/utils.ts

// Helper to generate the layout
import {html, raw} from 'hono/html';
import type {HtmlEscapedString} from 'hono/utils/html';
import type {AuthRequest} from '@cloudflare/workers-oauth-provider';

// This file mainly exists as a dumping ground for uninteresting html and CSS
// to remove clutter and noise from the auth logic. You likely do not need
// anything from this file.

export const layout = (
  content: HtmlEscapedString | string,
  title: string
) => html`
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>${title}</title>
      <script src="https://cdn.tailwindcss.com"></script>
      <script>
        tailwind.config = {
          theme: {
            extend: {
              colors: {
                primary: '#3498db',
                secondary: '#2ecc71',
                accent: '#f39c12',
              },
              fontFamily: {
                sans: ['Inter', 'system-ui', 'sans-serif'],
                heading: ['Roboto', 'system-ui', 'sans-serif'],
              },
            },
          },
        };
      </script>
      <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap');

        /* Custom styling for markdown content */
        .markdown h1 {
          font-size: 2.25rem;
          font-weight: 700;
          font-family: 'Roboto', system-ui, sans-serif;
          color: #1a202c;
          margin-bottom: 1rem;
          line-height: 1.2;
        }

        .markdown h2 {
          font-size: 1.5rem;
          font-weight: 600;
          font-family: 'Roboto', system-ui, sans-serif;
          color: #2d3748;
          margin-top: 1.5rem;
          margin-bottom: 0.75rem;
          line-height: 1.3;
        }

        .markdown h3 {
          font-size: 1.25rem;
          font-weight: 600;
          font-family: 'Roboto', system-ui, sans-serif;
          color: #2d3748;
          margin-top: 1.25rem;
          margin-bottom: 0.5rem;
        }

        .markdown p {
          font-size: 1.125rem;
          color: #4a5568;
          margin-bottom: 1rem;
          line-height: 1.6;
        }

        .markdown a {
          color: #3498db;
          font-weight: 500;
          text-decoration: none;
        }

        .markdown a:hover {
          text-decoration: underline;
        }

        .markdown blockquote {
          border-left: 4px solid #f39c12;
          padding-left: 1rem;
          padding-top: 0.75rem;
          padding-bottom: 0.75rem;
          margin-top: 1.5rem;
          margin-bottom: 1.5rem;
          background-color: #fffbeb;
          font-style: italic;
        }

        .markdown blockquote p {
          margin-bottom: 0.25rem;
        }

        .markdown ul,
        .markdown ol {
          margin-top: 1rem;
          margin-bottom: 1rem;
          margin-left: 1.5rem;
          font-size: 1.125rem;
          color: #4a5568;
        }

        .markdown li {
          margin-bottom: 0.5rem;
        }

        .markdown ul li {
          list-style-type: disc;
        }

        .markdown ol li {
          list-style-type: decimal;
        }

        .markdown pre {
          background-color: #f7fafc;
          padding: 1rem;
          border-radius: 0.375rem;
          margin-top: 1rem;
          margin-bottom: 1rem;
          overflow-x: auto;
        }

        .markdown code {
          font-family: monospace;
          font-size: 0.875rem;
          background-color: #f7fafc;
          padding: 0.125rem 0.25rem;
          border-radius: 0.25rem;
        }

        .markdown pre code {
          background-color: transparent;
          padding: 0;
        }
      </style>
    </head>
    <body
      class="bg-gray-50 text-gray-800 font-sans leading-relaxed flex flex-col min-h-screen"
    >
      <header class="bg-white shadow-sm mb-8">
        <div
          class="container mx-auto px-4 py-4 flex justify-between items-center"
        >
          <a
            href="/"
            class="text-xl font-heading font-bold text-primary hover:text-primary/80 transition-colors"
            >MCP Remote Auth Demo</a
          >
        </div>
      </header>
      <main class="container mx-auto px-4 pb-12 flex-grow">${content}</main>
      <footer class="bg-gray-100 py-6 mt-12">
        <div class="container mx-auto px-4 text-center text-gray-600">
          <p>
            &copy; ${new Date().getFullYear()} MCP Remote Auth Demo. All rights
            reserved.
          </p>
        </div>
      </footer>
    </body>
  </html>
`;

export const homeContent = async (req: Request): Promise<HtmlEscapedString> => {
  return html`
    <div class="max-w-4xl mx-auto markdown">Example Paid MCP Server</div>
  `;
};

export const renderLoggedInAuthorizeScreen = async (
  oauthScopes: {name: string; description: string}[],
  oauthReqInfo: AuthRequest
) => {
  return html`
    <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md">
      <h1 class="text-2xl font-heading font-bold mb-6 text-gray-900">
        Authorization Request
      </h1>

      <div class="mb-8">
        <h2 class="text-lg font-semibold mb-3 text-gray-800">
          MCP Remote Auth Demo would like permission to:
        </h2>
        <ul class="space-y-2">
          ${oauthScopes.map(
            (scope) => html`
              <li class="flex items-start">
                <span class="inline-block mr-2 mt-1 text-secondary">✓</span>
                <div>
                  <p class="font-medium">${scope.name}</p>
                  <p class="text-gray-600 text-sm">${scope.description}</p>
                </div>
              </li>
            `
          )}
        </ul>
      </div>
      <form action="/approve" method="POST" class="space-y-4">
        <input
          type="hidden"
          name="oauthReqInfo"
          value="${JSON.stringify(oauthReqInfo)}"
        />
        <input
          name="email"
          value="[email protected]"
          required
          placeholder="Enter email"
          class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
        />
        <button
          type="submit"
          name="action"
          value="approve"
          class="w-full py-3 px-4 bg-secondary text-white rounded-md font-medium hover:bg-secondary/90 transition-colors"
        >
          Approve
        </button>
        <button
          type="submit"
          name="action"
          value="reject"
          class="w-full py-3 px-4 border border-gray-300 text-gray-700 rounded-md font-medium hover:bg-gray-50 transition-colors"
        >
          Reject
        </button>
      </form>
    </div>
  `;
};

export const renderLoggedOutAuthorizeScreen = async (
  oauthScopes: {name: string; description: string}[],
  oauthReqInfo: AuthRequest
) => {
  return html`
    <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md">
      <h1 class="text-2xl font-heading font-bold mb-6 text-gray-900">
        Authorization Request
      </h1>

      <div class="mb-8">
        <h2 class="text-lg font-semibold mb-3 text-gray-800">
          MCP Remote Auth Demo would like permission to:
        </h2>
        <ul class="space-y-2">
          ${oauthScopes.map(
            (scope) => html`
              <li class="flex items-start">
                <span class="inline-block mr-2 mt-1 text-secondary">✓</span>
                <div>
                  <p class="font-medium">${scope.name}</p>
                  <p class="text-gray-600 text-sm">${scope.description}</p>
                </div>
              </li>
            `
          )}
        </ul>
      </div>
      <form action="/approve" method="POST" class="space-y-4">
        <input
          type="hidden"
          name="oauthReqInfo"
          value="${JSON.stringify(oauthReqInfo)}"
        />
        <div class="space-y-4">
          <div>
            <label
              for="email"
              class="block text-sm font-medium text-gray-700 mb-1"
              >Email</label
            >
            <input
              type="email"
              id="email"
              name="email"
              required
              class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
            />
          </div>
          <div>
            <label
              for="password"
              class="block text-sm font-medium text-gray-700 mb-1"
              >Password</label
            >
            <input
              type="password"
              id="password"
              name="password"
              required
              class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
            />
          </div>
        </div>
        <button
          type="submit"
          name="action"
          value="login_approve"
          class="w-full py-3 px-4 bg-primary text-white rounded-md font-medium hover:bg-primary/90 transition-colors"
        >
          Log in and Approve
        </button>
        <button
          type="submit"
          name="action"
          value="reject"
          class="w-full py-3 px-4 border border-gray-300 text-gray-700 rounded-md font-medium hover:bg-gray-50 transition-colors"
        >
          Reject
        </button>
      </form>
    </div>
  `;
};

export const renderApproveContent = async (
  message: string,
  status: string,
  redirectUrl: string
) => {
  return html`
    <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md text-center">
      <div class="mb-4">
        <span
          class="inline-block p-3 ${status === 'success'
            ? 'bg-green-100 text-green-800'
            : 'bg-red-100 text-red-800'} rounded-full"
        >
          ${status === 'success' ? '✓' : '✗'}
        </span>
      </div>
      <h1 class="text-2xl font-heading font-bold mb-4 text-gray-900">
        ${message}
      </h1>
      <p class="mb-8 text-gray-600">
        You will be redirected back to the application shortly.
      </p>
      ${raw(`
				<script>
					setTimeout(() => {
						window.location.href = "${redirectUrl}";
					}, 1000);
				</script>
			`)}
    </div>
  `;
};

export const renderAuthorizationApprovedContent = async (
  redirectUrl: string
) => {
  return renderApproveContent(
    'Authorization approved!',
    'success',
    redirectUrl
  );
};

export const renderAuthorizationRejectedContent = async (
  redirectUrl: string
) => {
  return renderApproveContent('Authorization rejected.', 'error', redirectUrl);
};

export const parseApproveFormBody = async (body: {
  [x: string]: string | File;
}) => {
  const action = body.action as string;
  const email = body.email as string;
  const password = body.password as string;
  let oauthReqInfo: AuthRequest | null = null;
  try {
    oauthReqInfo = JSON.parse(body.oauthReqInfo as string) as AuthRequest;
  } catch (e) {
    oauthReqInfo = null;
  }

  return {action, oauthReqInfo, email, password};
};

export const renderPaymentSuccessContent =
  async (): Promise<HtmlEscapedString> => {
    return html`
      <div
        class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md text-center"
      >
        <h1 class="text-2xl font-heading font-bold mb-4 text-gray-900">
          Payment Successful!
        </h1>
        <p class="mb-8 text-gray-600">
          You can return to the MCP client now and rerun the tool.
        </p>
      </div>
    `;
  };

```

--------------------------------------------------------------------------------
/skills/get-started-kiro.md:
--------------------------------------------------------------------------------

```markdown
---
name: "stripe"
displayName: "Stripe Payments"
description: "Build payment integrations with Stripe - accept payments, manage subscriptions, handle billing, and process refunds"
keywords:
  [
    "stripe",
    "payments",
    "checkout",
    "subscriptions",
    "billing",
    "invoices",
    "refunds",
    "payment-intents",
  ]
author: "Stripe"
---

# Stripe Payments Power

## Overview

Build payment integrations with Stripe's comprehensive payment platform. Accept one-time payments and subscriptions, manage customer billing, process refunds, and handle complex payment flows. This power provides access to Stripe's APIs through an MCP server, enabling you to build production-ready payment systems.

Use Stripe Checkout for hosted payment pages, Payment Intents for custom payment flows, or Billing APIs for subscription management. The platform handles PCI compliance, fraud detection, and supports 135+ currencies and payment methods worldwide.

**Key capabilities:**

- **Checkout Sessions**: Hosted payment pages for one-time payments and subscriptions
- **Payment Intents**: Custom payment flows with full control over the checkout experience
- **Subscriptions**: Recurring billing with flexible pricing models
- **Customers**: Manage customer data and saved payment methods
- **Invoices**: Generate and send invoices with automatic payment collection
- **Refunds**: Process full or partial refunds
- **Payment Methods**: Save and reuse payment methods for future charges

**Authentication**: Requires Stripe secret API key for server-side operations. Never expose in client code. Requires Stripe publishable key (only for client-side operations like Elements or Checkout); safe to include in browser code.

## Available MCP Servers

### stripe

**Connection:** HTTPS API endpoint at `https://mcp.stripe.com`
**Authorization:** Use OAuth to connect to the Stripe MCP server

## Best Practices

### Integration Approach

**Always prefer Checkout Sessions** for standard payment flows:

- One-time payments
- Subscription sign-ups
- Hosted (preferred) or embedded checkout forms

**Use Payment Intents** only when:

- Building custom checkout UI
- Handling off-session payments
- Need full control over payment state

**Never use the deprecated Charges API** - migrate to Checkout Sessions or Payment Intents.
**Use Payment Links** when:

- User wants a _No code_ Stripe integration
- Quickly create shareable payment pages
- Selling products or collecting donations with minimal setup

### Payment Methods

**Enable dynamic payment methods** in Dashboard settings instead of hardcoding `payment_method_types`. Stripe automatically shows optimal payment methods based on:

- Customer location
- Available wallets
- User preferences
- Transaction context

### Subscriptions

**For recurring revenue models**, use Billing APIs:

- Follow [Subscription Use Cases](https://docs.stripe.com/billing/subscriptions/use-cases)
- Use [SaaS integration patterns](https://docs.stripe.com/saas)
- Combine with Checkout for frontend
- [Plan your integration](https://docs.stripe.com/billing/subscriptions/designing-integration)
- [Usage-based billing to charge customers based on their usage of your product or service](https://docs.stripe.com/billing/subscriptions/usage-based)

### Stripe Connect

**For platforms managing fund flows**:

- Use **direct charges** if platform accepts risk (Stripe handles liability)
- Use **destination charges** if platform manages risk (platform handles negative balances)
- Use `on_behalf_of` parameter to control merchant of record
- Never mix charge types
- Refer to [controller properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md) not legacy Standard/Express/Custom terms
- Follow [integration recommendations](https://docs.stripe.com/connect/integration-recommendations.md)

### Saving Payment Methods

**Use Setup Intents API** to save payment methods for future use:

- Never use deprecated Sources API
- For pre-authorization before payment, use Confirmation Tokens
- Don't call `createPaymentMethod` or `createToken` directly

### PCI Compliance

**For server-side raw PAN data**:

- Requires PCI compliance proof
- Use `payment_method_data` parameter
- For migrations, follow [PAN import process](https://docs.stripe.com/get-started/data-migrations/pan-import)

### Before Going Live

Review the [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live):

- Test with sandbox keys
- Handle webhooks for async events
- Implement error handling
- Set up proper logging
- Configure tax and compliance settings

## Common Workflows

### Workflow 1: Accept One-Time Payment

```javascript
// Step 1: Create Checkout Session
const session = createCheckoutSession({
  mode: "payment",
  line_items: [
    {
      price_data: {
        currency: "usd",
        product_data: { name: "Premium Plan" },
        unit_amount: 2999,
      },
      quantity: 1,
    },
  ],
  success_url: "https://example.com/success",
  cancel_url: "https://example.com/cancel",
});

// Step 2: Redirect customer to session.url
// Step 3: Handle webhook for payment_intent.succeeded
```

### Workflow 2: Create Subscription

```javascript
// Step 1: Create or retrieve customer
const customer = createCustomer({
  email: "[email protected]",
  name: "Jane Doe",
});

// Step 2: Create Checkout Session for subscription
const session = createCheckoutSession({
  mode: "subscription",
  customer: customer.id,
  line_items: [
    {
      price: "price_monthly_premium",
      quantity: 1,
    },
  ],
  success_url: "https://example.com/success",
  cancel_url: "https://example.com/cancel",
});

// Step 3: Handle webhook for customer.subscription.created
```

### Workflow 3: Process Refund

```javascript
// Step 1: Retrieve payment intent or charge
const paymentIntent = retrievePaymentIntent("pi_xxx");

// Step 2: Create refund
const refund = createRefund({
  payment_intent: paymentIntent.id,
  amount: 1000, // Partial refund in cents, omit for full refund
});

// Step 3: Handle webhook for charge.refunded
```

### Workflow 4: Save Payment Method for Future Use

```javascript
// Step 1: Create Setup Intent
const setupIntent = createSetupIntent({
  customer: "cus_xxx",
  payment_method_types: ["card"],
});

// Step 2: Collect payment method on frontend with Setup Intent
// Step 3: Handle webhook for setup_intent.succeeded
// Step 4: Use saved payment method for future charges
const paymentIntent = createPaymentIntent({
  amount: 2999,
  currency: "usd",
  customer: "cus_xxx",
  payment_method: "pm_xxx",
  off_session: true,
  confirm: true,
});
```

## Best Practices Summary

### ✅ Do:

- **Use Checkout Sessions** for standard payment flows
- **Enable dynamic payment methods** in Dashboard settings
- **Use Billing APIs** for subscription models
- **Use Setup Intents** to save payment methods
- **Handle webhooks** for all async events
- **Test thoroughly** in sandbox before going live
- **Follow the Go Live Checklist** before production
- **Do not include API version** in code snippets. Read https://docs.stripe.com/api/versioning.md for more information on versions
- **Implement idempotency keys** for safe retries
- **Log all API interactions** for debugging

### ❌ Don't:

- **Use Charges API** - it's deprecated, migrate to Payment Intents
- **Use Sources API** - deprecated for saving cards
- **Use Card Element** - migrate to Payment Element
- **Hardcode payment_method_types** - use dynamic payment methods
- **Mix Connect charge types** - choose one approach
- **Skip webhook handling** - critical for payment confirmation
- **Use production keys in development** - always use test keys
- **Ignore errors** - implement proper error handling
- **Skip PCI compliance** - required for handling card data
- **Forget to test edge cases** - declined cards, network failures, etc.
- **Expose API secret keys** - never include secret keys in client-side code, mobile apps, or public repositories

## Configuration

**Authentication Required**: Stripe secret key

**Setup Steps:**

1. Create Stripe account at https://stripe.com
2. Navigate to Developers → API keys
3. Copy your secret key (starts with `sk_test_` for [sandboxes](https://docs.stripe.com/sandboxes/dashboard/manage))
4. (Optional) Copy your publishable key (starts with `pk_test_` for sandboxes). Only needed for Stripe client-side code.
5. For production, use live mode key (starts with `sk_live_` and `pk_live_`)
6. Configure key in Kiro Powers UI when installing this power

**Permissions**: Secret key has full API access - keep secure and never expose client-side. Publishable keys (pk\_...) are intended and acceptable to embed in client-side code.

**MCP Configuration:**

```json
{
  "mcpServers": {
    "stripe": {
      "url": "https://mcp.stripe.com"
    }
  }
}
```

## Troubleshooting

### Error: "Invalid API key"

**Cause:** Incorrect or missing API key
**Solution:**

1. Verify key starts with `sk_test_` or `sk_live_`
2. Check key hasn't been deleted in Dashboard
3. Ensure using secret key, not publishable key
4. Regenerate key if compromised

### Error: "Payment method not available"

**Cause:** Payment method not enabled or not supported in region
**Solution:**

1. Enable payment methods in Dashboard → Settings → Payment methods
2. Check customer location supports the payment method
3. Use dynamic payment methods instead of hardcoding types
4. Verify currency is supported by payment method

### Error: "Customer not found"

**Cause:** Invalid customer ID or customer deleted
**Solution:**

1. Verify customer ID format (starts with `cus_`)
2. Check customer exists in Dashboard
3. Ensure using correct API mode (test vs live)
4. Create customer if doesn't exist

### Error: "Subscription creation failed"

**Cause:** Missing required parameters or invalid price ID
**Solution:**

1. Verify price ID exists (starts with `price_`)
2. Ensure price is active in Dashboard
3. Check customer has valid payment method
4. Review subscription parameters match price configuration

### Webhook not received

**Cause:** Webhook endpoint not configured or failing
**Solution:**

1. Configure webhook endpoint in Dashboard → Developers → Webhooks
2. Verify endpoint is publicly accessible
3. Check endpoint returns 200 status
4. Review webhook logs in Dashboard
5. Test with Stripe CLI: `stripe listen --forward-to localhost:3000/webhook`

### Payment declined

**Cause:** Card declined by issuer or failed fraud check
**Solution:**

1. Use test cards from [Stripe testing docs](https://docs.stripe.com/testing)
2. Check decline code in error response
3. Implement proper error messaging for customer
4. For production, customer should contact their bank
5. Review Radar rules if fraud detection triggered

## Tips

1. **Start with Checkout** - Fastest way to accept payments with minimal code
2. **Use sandbox extensively** - Test all scenarios before going live
3. **Implement webhooks early** - Critical for handling async events
4. **Use Stripe CLI** - Test webhooks locally during development
5. **Follow integration guides** - Use [API Tour](https://docs.stripe.com/payments-api/tour) and [Integration Options](https://docs.stripe.com/payments/payment-methods/integration-options)
6. **Monitor Dashboard** - Review payments, disputes, and logs regularly
7. **Handle errors gracefully** - Show clear messages to customers
8. **Use idempotency keys** - Prevent duplicate charges on retries
9. **Keep keys secure** - Never commit to version control
10. **Stay updated** - Review API changelog for new features and deprecations

## Resources

- [Integration Options](https://docs.stripe.com/payments/payment-methods/integration-options)
- [API Tour](https://docs.stripe.com/payments-api/tour)
- [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live)
- [Checkout Sessions](https://docs.stripe.com/api/checkout/sessions)
- [Payment Intents](https://docs.stripe.com/payments/paymentintents/lifecycle)
- [Subscription Use Cases](https://docs.stripe.com/billing/subscriptions/use-cases)
- [Connect Integration](https://docs.stripe.com/connect/design-an-integration)
- [Testing](https://docs.stripe.com/testing)

---

**License:** Proprietary

```
Page 3/5FirstPrevNextLast