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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 8 additions & 33 deletions llm/ai-sdk/provider/stripe-language-model-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,44 +231,21 @@ export class StripeLanguageModelV3 implements LanguageModelV3 {

const messages = convertToOpenAIMessagesV3(options.prompt);

// Tool calling is not supported by the Stripe AI SDK Provider.
if (options.tools && options.tools.length > 0) {
throw new Error(
'Tool calling is not supported by the Stripe AI SDK Provider. ' +
'The llm.stripe.com API does not currently support function calling or tool use. ' +
'Please remove the tools parameter from your request.'
'Please remove the tools and toolChoice parameters from your request.'
);
}

const tools =
options.tools && options.tools.length > 0
? options.tools.map((tool) => {
if (tool.type === 'function') {
return {
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
};
}
return tool;
})
: undefined;

let toolChoice:
| string
| {type: string; function?: {name: string}}
| undefined;
if (options.toolChoice) {
if (options.toolChoice.type === 'tool') {
toolChoice = {
type: 'function',
function: {name: options.toolChoice.toolName},
};
} else {
toolChoice = options.toolChoice.type;
}
if (options.toolChoice !== undefined) {
throw new Error(
'Tool calling is not supported by the Stripe AI SDK Provider. ' +
'The llm.stripe.com API does not currently support function calling or tool use. ' +
'Please remove the tools and toolChoice parameters from your request.'
);
}

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options.toolChoice is no longer forwarded (and is silently ignored) when options.tools is empty. Given the explicit “tool calling not supported” stance, consider explicitly throwing when toolChoice is provided as well (and adjusting the message), so callers don’t think the setting is taking effect.

Suggested change
if (options.toolChoice !== undefined) {
throw new Error(
'Tool calling is not supported by the Stripe AI SDK Provider. ' +
'The llm.stripe.com API does not currently support function calling or tool use. ' +
'Please remove the toolChoice parameter (and any tools) from your request.'
);
}

Copilot uses AI. Check for mistakes.
const body: Record<string, any> = {
Expand All @@ -291,8 +268,6 @@ export class StripeLanguageModelV3 implements LanguageModelV3 {
if (options.stopSequences !== undefined)
body.stop = options.stopSequences;
if (options.seed !== undefined) body.seed = options.seed;
if (tools !== undefined) body.tools = tools;
if (toolChoice !== undefined) body.tool_choice = toolChoice;

return {args: body, warnings};
}
Expand Down
54 changes: 15 additions & 39 deletions llm/ai-sdk/provider/stripe-language-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,45 +261,21 @@ export class StripeLanguageModel implements LanguageModelV2 {
// Convert AI SDK prompt to OpenAI-compatible format
const messages = convertToOpenAIMessages(options.prompt);

// Check if tools are provided and throw error (tool calling not supported by Stripe API)
// Tool calling is not supported by the Stripe AI SDK Provider.
if (options.tools && options.tools.length > 0) {
throw new Error(
'Tool calling is not supported by the Stripe AI SDK Provider. ' +
'The llm.stripe.com API does not currently support function calling or tool use. ' +
'Please remove the tools parameter from your request.'
'The llm.stripe.com API does not currently support function calling or tool use. ' +
'Please remove the tools and toolChoice parameters from your request.'
);
}

// Prepare tools if provided
const tools =
options.tools && options.tools.length > 0
? options.tools.map((tool) => {
if (tool.type === 'function') {
return {
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
};
}
// Provider-defined tools
return tool;
})
: undefined;

// Map tool choice
let toolChoice: string | {type: string; function?: {name: string}} | undefined;
if (options.toolChoice) {
if (options.toolChoice.type === 'tool') {
toolChoice = {
type: 'function',
function: {name: options.toolChoice.toolName},
};
} else {
toolChoice = options.toolChoice.type; // 'auto', 'none', 'required'
}
if (options.toolChoice !== undefined) {
throw new Error(
'Tool calling is not supported by the Stripe AI SDK Provider. ' +
'The llm.stripe.com API does not currently support function calling or tool use. ' +
'Please remove the tools and toolChoice parameters from your request.'
);
}
Comment on lines +264 to 279
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After removing the toolChoice mapping, options.toolChoice is now silently ignored when options.tools is empty. Since tool calling is explicitly unsupported, it would be clearer/safer to also reject options.toolChoice (or at least warn) and update the error message to mention toolChoice as unsupported, to avoid a behavior change that’s hard for callers to notice.

Copilot uses AI. Check for mistakes.

// Build request body, only including defined values
Expand All @@ -310,18 +286,18 @@ export class StripeLanguageModel implements LanguageModelV2 {

// Add optional parameters only if they're defined
if (options.temperature !== undefined) body.temperature = options.temperature;

// Handle max_tokens with model-specific defaults for Anthropic
const maxTokens = options.maxOutputTokens ?? this.getDefaultMaxTokens(this.modelId);
if (maxTokens !== undefined) body.max_tokens = maxTokens;

if (options.topP !== undefined) body.top_p = options.topP;
if (options.frequencyPenalty !== undefined) body.frequency_penalty = options.frequencyPenalty;
if (options.presencePenalty !== undefined) body.presence_penalty = options.presencePenalty;
if (options.frequencyPenalty !== undefined)
body.frequency_penalty = options.frequencyPenalty;
if (options.presencePenalty !== undefined)
body.presence_penalty = options.presencePenalty;
if (options.stopSequences !== undefined) body.stop = options.stopSequences;
if (options.seed !== undefined) body.seed = options.seed;
if (tools !== undefined) body.tools = tools;
if (toolChoice !== undefined) body.tool_choice = toolChoice;

return {args: body, warnings};
}
Expand Down
12 changes: 12 additions & 0 deletions llm/ai-sdk/provider/tests/stripe-language-model-v3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ describe('StripeLanguageModelV3', () => {
}).toThrow('Tool calling is not supported by the Stripe AI SDK Provider');
});

it('should throw error when tool choice is provided without tools', () => {
const options: LanguageModelV3CallOptions = {
prompt: [],
toolChoice: {type: 'auto'},
};

expect(() => {
// @ts-expect-error - Accessing private method for testing
model.getArgs(options);
}).toThrow('Tool calling is not supported by the Stripe AI SDK Provider');
});

it('should not throw error when no tools are provided', () => {
const options: LanguageModelV3CallOptions = {
prompt: [
Expand Down
13 changes: 12 additions & 1 deletion llm/ai-sdk/provider/tests/stripe-language-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,18 @@ describe('StripeLanguageModel', () => {
}).toThrow('Tool calling is not supported by the Stripe AI SDK Provider');
});

it('should throw error when tool choice is provided without tools', () => {
const options: LanguageModelV2CallOptions = {
prompt: [],
toolChoice: {type: 'auto'},
};

expect(() => {
// @ts-expect-error - Accessing private method for testing
model.getArgs(options);
}).toThrow('Tool calling is not supported by the Stripe AI SDK Provider');
});

it('should not throw error when no tools are provided', () => {
const options: LanguageModelV2CallOptions = {
prompt: [
Expand Down Expand Up @@ -629,4 +641,3 @@ describe('StripeLanguageModel', () => {
});
});
});

8 changes: 7 additions & 1 deletion tools/typescript/src/shared/mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ export class StripeMcpClient {
const result = await this.client.listTools();
this.tools = result.tools as McpTool[];
} catch (error) {
// Clean up on failure
// Close the active connection before clearing references to avoid leaking
// the underlying transport if connect() succeeded but listTools() failed.
try {
if (this.client) await this.client.close();
} catch {
// Ignore close errors during cleanup
}
this.client = null;
this.transport = null;
Comment on lines +116 to 124
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cleanup closes this.client, but there’s no test coverage that close() is invoked when connect() succeeds and listTools() throws. Adding a Jest test that mocks listTools to reject and asserts client.close is awaited (and that connect() rejects with the wrapped error) would prevent regressions of the transport leak fix.

Copilot uses AI. Check for mistakes.

Expand Down
27 changes: 27 additions & 0 deletions tools/typescript/src/test/shared/mcp-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,33 @@
expect(results).toHaveLength(3);
expect(client.isConnected()).toBe(true);
});

it('should close the client if listTools fails after connect succeeds', async () => {
const { Client } = require('@modelcontextprotocol/sdk/client/index.js');

Check failure on line 121 in tools/typescript/src/test/shared/mcp-client.test.ts

View workflow job for this annotation

GitHub Actions / Build - TypeScript

Replace `·Client·` with `Client`
const connect = jest.fn().mockResolvedValue(undefined);
const listTools = jest
.fn()
.mockRejectedValue(new Error('listTools failed'));
const close = jest.fn().mockResolvedValue(undefined);

Client.mockImplementationOnce(() => ({
connect,
listTools,
callTool: jest.fn(),
close,
}));

const client = new StripeMcpClient({secretKey: 'rk_test_123'});

await expect(client.connect()).rejects.toThrow(
'Failed to connect to Stripe MCP server'
);

expect(connect).toHaveBeenCalledTimes(1);
expect(listTools).toHaveBeenCalledTimes(1);
expect(close).toHaveBeenCalledTimes(1);
expect(client.isConnected()).toBe(false);
});
});

describe('getTools', () => {
Expand Down
Loading