kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
a687f41b03
commit
b0e4d07f8f
|
@ -1,21 +1,21 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`MCP => MCP origin basic "add" tool call success > 9.0: @dev/test-basic-mcp/mcp add 1`] = `
|
||||
exports[`MCP => MCP origin basic "echo" tool > 10.0: @dev/test-basic-mcp/mcp echo 1`] = `
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "62",
|
||||
"text": "{"nala":"kitten","num":123,"now":1749678890348}",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`MCP => MCP origin basic "echo" tool > 10.0: @dev/test-basic-mcp/mcp echo 1`] = `
|
||||
exports[`MCP => MCP origin basic "echo" tool with empty body > 13.0: @dev/test-basic-mcp/mcp echo 1`] = `
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "{"nala":"kitten","num":123,"now":1749678633338}",
|
||||
"text": "{}",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
|
|
|
@ -422,7 +422,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [
|
|||
]
|
||||
},
|
||||
{
|
||||
title: 'HTTP => MCP origin basic "add" tool call success',
|
||||
title: 'HTTP => MCP origin basic "add" tool',
|
||||
compareResponseBodies: true,
|
||||
fixtures: [
|
||||
{
|
||||
|
@ -549,7 +549,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [
|
|||
]
|
||||
},
|
||||
{
|
||||
title: 'HTTP => OpenAPI origin everything "disabled_tool"',
|
||||
title: 'HTTP => OpenAPI origin everything "disabled_tool" tool',
|
||||
fixtures: [
|
||||
{
|
||||
path: '@dev/test-everything-openapi/disabled_tool',
|
||||
|
|
|
@ -58,7 +58,9 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) {
|
|||
fixture.response?.snapshot ?? fixtureSuite.snapshot ?? false
|
||||
const expectedStableSnapshot =
|
||||
fixture.response?.stableSnapshot ??
|
||||
fixture.response?.snapshot ??
|
||||
fixtureSuite.stableSnapshot ??
|
||||
fixtureSuite.snapshot ??
|
||||
!isError
|
||||
const debugFixture = !!(
|
||||
fixture.debug ??
|
||||
|
@ -148,10 +150,15 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) {
|
|||
expect(result).toMatchSnapshot()
|
||||
}
|
||||
|
||||
const stableResult = pick(
|
||||
result,
|
||||
'content',
|
||||
'structuredContent',
|
||||
'isError'
|
||||
)
|
||||
|
||||
if (expectedStableSnapshot) {
|
||||
expect(
|
||||
pick(result, 'content', 'structuredContent', 'isError')
|
||||
).toMatchSnapshot()
|
||||
expect(stableResult).toMatchSnapshot()
|
||||
}
|
||||
|
||||
if (validate) {
|
||||
|
@ -160,9 +167,9 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) {
|
|||
|
||||
if (compareResponseBodies && !isError) {
|
||||
if (!fixtureResult) {
|
||||
fixtureResult = result
|
||||
fixtureResult = stableResult
|
||||
} else {
|
||||
expect(result).toEqual(fixtureResult)
|
||||
expect(stableResult).toEqual(fixtureResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { expect } from 'vitest'
|
||||
|
||||
export type MCPE2ETestFixture = {
|
||||
/** @default 60_000 milliseconds */
|
||||
timeout?: number
|
||||
|
@ -16,6 +18,7 @@ export type MCPE2ETestFixture = {
|
|||
|
||||
response?: {
|
||||
result?: any
|
||||
/** @default false */
|
||||
isError?: boolean
|
||||
content?: Array<Record<string, unknown>>
|
||||
structuredContent?: any
|
||||
|
@ -369,6 +372,7 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [
|
|||
{
|
||||
title: 'MCP => MCP origin basic "add" tool call success',
|
||||
path: '@dev/test-basic-mcp/mcp',
|
||||
stableSnapshot: false,
|
||||
fixtures: [
|
||||
{
|
||||
request: {
|
||||
|
@ -411,8 +415,8 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [
|
|||
]
|
||||
},
|
||||
{
|
||||
title: 'MCP => MCP origin basic "pure" tool',
|
||||
path: '@dev/test-basic-mcp/mcp',
|
||||
title: 'MCP => OpenAPI origin everything "pure" tool',
|
||||
path: '@dev/test-everything-openapi/mcp',
|
||||
fixtures: [
|
||||
{
|
||||
request: {
|
||||
|
@ -477,5 +481,84 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'MCP => OpenAPI origin everything "disabled_tool" tool',
|
||||
path: '@dev/test-everything-openapi/mcp',
|
||||
fixtures: [
|
||||
{
|
||||
request: {
|
||||
name: 'disabled_tool',
|
||||
args: {
|
||||
foo: 'bar'
|
||||
}
|
||||
},
|
||||
response: {
|
||||
isError: true,
|
||||
_agenticMeta: {
|
||||
status: 404,
|
||||
toolName: 'disabled_tool'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'MCP => OpenAPI origin everything "echo" tool with empty body',
|
||||
path: '@dev/test-everything-openapi/mcp',
|
||||
fixtures: [
|
||||
{
|
||||
request: {
|
||||
name: 'echo',
|
||||
args: {}
|
||||
},
|
||||
response: {
|
||||
isError: false,
|
||||
content: [{ type: 'text', text: JSON.stringify({}) }]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'MCP => OpenAPI origin everything "unpure_marked_pure" tool',
|
||||
path: '@dev/test-everything-openapi/mcp',
|
||||
compareResponseBodies: true,
|
||||
only: true,
|
||||
fixtures: [
|
||||
{
|
||||
request: {
|
||||
name: 'unpure_marked_pure',
|
||||
args: {
|
||||
nala: 'cat'
|
||||
}
|
||||
},
|
||||
response: {
|
||||
isError: false,
|
||||
validate: (result) => {
|
||||
const body = JSON.parse(result.content[0].text)
|
||||
expect(body?.nala).toEqual('cat')
|
||||
expect(typeof body.now).toBe('number')
|
||||
expect(body.now).toBeGreaterThan(0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// compareResponseBodies should result in the same cached response body,
|
||||
// even though the origin would return a different `now` value if it
|
||||
// weren't marked `pure`.
|
||||
request: {
|
||||
name: 'unpure_marked_pure',
|
||||
args: {
|
||||
nala: 'cat'
|
||||
}
|
||||
},
|
||||
response: {
|
||||
isError: false,
|
||||
_agenticMeta: {
|
||||
cacheStatus: 'HIT'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -48,7 +48,7 @@ export async function createHttpRequestForOpenAPIOperation({
|
|||
)
|
||||
|
||||
const extraArgs =
|
||||
toolConfig?.additionalProperties === false
|
||||
toolConfig?.inputSchemaAdditionalProperties === false
|
||||
? []
|
||||
: // TODO: Make this more efficient...
|
||||
Object.keys(toolCallArgs).filter((key) => {
|
||||
|
|
|
@ -38,7 +38,8 @@ export async function getToolArgsFromRequest(
|
|||
data: incomingRequestArgsRaw,
|
||||
errorPrefix: `Invalid request parameters for tool "${tool.name}"`,
|
||||
coerce: true,
|
||||
strictAdditionalProperties: toolConfig?.additionalProperties === false
|
||||
strictAdditionalProperties:
|
||||
toolConfig?.inputSchemaAdditionalProperties === false
|
||||
})
|
||||
|
||||
return incomingRequestArgs
|
||||
|
|
|
@ -197,7 +197,8 @@ export async function resolveOriginToolCall({
|
|||
schema: tool.inputSchema,
|
||||
data: args,
|
||||
errorPrefix: `Invalid request parameters for tool "${tool.name}"`,
|
||||
strictAdditionalProperties: toolConfig?.additionalProperties === false
|
||||
strictAdditionalProperties:
|
||||
toolConfig?.inputSchemaAdditionalProperties === false
|
||||
})
|
||||
|
||||
const originStartTimeMs = Date.now()
|
||||
|
@ -247,7 +248,8 @@ export async function resolveOriginToolCall({
|
|||
originRequest,
|
||||
originResponse,
|
||||
originTimespanMs: Date.now() - originStartTimeMs,
|
||||
numRequestsCost
|
||||
numRequestsCost,
|
||||
toolConfig
|
||||
}
|
||||
} else if (originAdapter.type === 'mcp') {
|
||||
const { projectIdentifier } = parseDeploymentIdentifier(
|
||||
|
@ -313,7 +315,8 @@ export async function resolveOriginToolCall({
|
|||
toolCallArgs,
|
||||
toolCallResponse: (await response.json()) as McpToolCallResponse,
|
||||
originTimespanMs: Date.now() - originStartTimeMs,
|
||||
numRequestsCost
|
||||
numRequestsCost,
|
||||
toolConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -346,7 +349,8 @@ export async function resolveOriginToolCall({
|
|||
toolCallArgs,
|
||||
toolCallResponse,
|
||||
originTimespanMs: Date.now() - originStartTimeMs,
|
||||
numRequestsCost
|
||||
numRequestsCost,
|
||||
toolConfig
|
||||
}
|
||||
} else {
|
||||
assert(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Tool } from '@agentic/platform-types'
|
||||
import type { Tool, ToolConfig } from '@agentic/platform-types'
|
||||
import { assert, HttpError } from '@agentic/platform-core'
|
||||
import contentType from 'fast-content-type-parse'
|
||||
|
||||
|
@ -9,12 +9,14 @@ export async function transformHttpResponseToMcpToolCallResponse({
|
|||
originRequest,
|
||||
originResponse,
|
||||
tool,
|
||||
toolCallArgs
|
||||
toolCallArgs,
|
||||
toolConfig
|
||||
}: {
|
||||
originRequest: Request
|
||||
originResponse: Response
|
||||
tool: Tool
|
||||
toolCallArgs: ToolCallArgs
|
||||
toolConfig?: ToolConfig
|
||||
}) {
|
||||
const { type: mimeType } = contentType.safeParse(
|
||||
originResponse.headers.get('content-type') || 'application/octet-stream'
|
||||
|
@ -77,8 +79,8 @@ export async function transformHttpResponseToMcpToolCallResponse({
|
|||
data,
|
||||
schema: tool.outputSchema,
|
||||
coerce: false,
|
||||
// TODO: double-check MCP schema on whether additional properties are allowed
|
||||
strictAdditionalProperties: true,
|
||||
strictAdditionalProperties:
|
||||
toolConfig?.outputSchemaAdditionalProperties === false,
|
||||
errorPrefix: `Invalid tool response for tool "${tool.name}"`,
|
||||
errorStatusCode: 502
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import type {
|
|||
} from '@agentic/platform-hono'
|
||||
import type {
|
||||
AdminConsumer as AdminConsumerImpl,
|
||||
ToolConfig,
|
||||
User
|
||||
} from '@agentic/platform-types'
|
||||
import type { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
|
@ -67,6 +68,7 @@ export type ResolvedOriginToolCallResult = {
|
|||
rateLimitResult?: RateLimitResult
|
||||
cacheStatus: CacheStatus
|
||||
reportUsage: boolean
|
||||
toolConfig?: ToolConfig
|
||||
originTimespanMs: number
|
||||
numRequestsCost: number
|
||||
} & (
|
||||
|
|
|
@ -68,7 +68,8 @@ export default defineConfig({
|
|||
},
|
||||
{
|
||||
name: 'strict_additional_properties',
|
||||
additionalProperties: false
|
||||
inputSchemaAdditionalProperties: false,
|
||||
outputSchemaAdditionalProperties: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -12,7 +12,12 @@ import {
|
|||
type PricingPlanListInput,
|
||||
pricingPlanListSchema
|
||||
} from './pricing'
|
||||
import { toolConfigSchema, toolSchema } from './tools'
|
||||
import {
|
||||
type ToolConfig,
|
||||
type ToolConfigInput,
|
||||
toolConfigSchema,
|
||||
toolSchema
|
||||
} from './tools'
|
||||
|
||||
// TODO:
|
||||
// - optional external auth provider config (google, github, twitter, etc)
|
||||
|
@ -159,16 +164,21 @@ To add support for annual pricing plans, for example, you can use: \`['month', '
|
|||
.strip()
|
||||
|
||||
export type AgenticProjectConfigInput = Simplify<
|
||||
Omit<z.input<typeof agenticProjectConfigSchema>, 'pricingPlans'> & {
|
||||
Omit<
|
||||
z.input<typeof agenticProjectConfigSchema>,
|
||||
'pricingPlans' | 'toolConfigs'
|
||||
> & {
|
||||
pricingPlans?: PricingPlanListInput
|
||||
toolConfigs?: ToolConfigInput[]
|
||||
}
|
||||
>
|
||||
export type AgenticProjectConfigRaw = z.output<
|
||||
typeof agenticProjectConfigSchema
|
||||
>
|
||||
export type AgenticProjectConfig = Simplify<
|
||||
Omit<AgenticProjectConfigRaw, 'pricingPlans'> & {
|
||||
Omit<AgenticProjectConfigRaw, 'pricingPlans' | 'toolConfigs'> & {
|
||||
pricingPlans: PricingPlanList
|
||||
toolConfigs: ToolConfig[]
|
||||
}
|
||||
>
|
||||
|
||||
|
@ -178,7 +188,11 @@ export const resolvedAgenticProjectConfigSchema =
|
|||
tools: z.array(toolSchema).default([])
|
||||
})
|
||||
export type ResolvedAgenticProjectConfig = Simplify<
|
||||
Omit<z.output<typeof resolvedAgenticProjectConfigSchema>, 'pricingPlans'> & {
|
||||
Omit<
|
||||
z.output<typeof resolvedAgenticProjectConfigSchema>,
|
||||
'pricingPlans' | 'toolConfigs'
|
||||
> & {
|
||||
pricingPlans: PricingPlanList
|
||||
toolConfigs: ToolConfig[]
|
||||
}
|
||||
>
|
||||
|
|
|
@ -506,7 +506,9 @@ export interface components {
|
|||
reportUsage: boolean;
|
||||
rateLimit?: components["schemas"]["RateLimit"] | null;
|
||||
/** @default true */
|
||||
additionalProperties: boolean;
|
||||
inputSchemaAdditionalProperties: boolean;
|
||||
/** @default true */
|
||||
outputSchemaAdditionalProperties: boolean;
|
||||
/** @description Allows you to override this tool's behavior or disable it entirely for different pricing plans. This is a map of PricingPlan slug to PricingPlanToolOverrides for that plan. */
|
||||
pricingPlanOverridesMap?: {
|
||||
[key: string]: components["schemas"]["PricingPlanToolOverride"];
|
||||
|
|
|
@ -173,9 +173,23 @@ export const toolConfigSchema = z
|
|||
* The default MCP spec allows additional properties. Set this to `false` if
|
||||
* you want your tool to be more strict.
|
||||
*
|
||||
* @note This is only relevant if the tool has defined an `outputSchema`.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
additionalProperties: z.boolean().optional().default(true),
|
||||
inputSchemaAdditionalProperties: z.boolean().optional().default(true),
|
||||
|
||||
/**
|
||||
* Whether to allow additional properties in the tool's output schema.
|
||||
*
|
||||
* The default MCP spec allows additional properties. Set this to `false` if
|
||||
* you want your tool to be more strict.
|
||||
*
|
||||
* @note This is only relevant if the tool has defined an `outputSchema`.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
outputSchemaAdditionalProperties: z.boolean().optional().default(true),
|
||||
|
||||
/**
|
||||
* Allows you to override this tool's behavior or disable it entirely for
|
||||
|
@ -202,6 +216,7 @@ export const toolConfigSchema = z
|
|||
// headers
|
||||
})
|
||||
.openapi('ToolConfig')
|
||||
export type ToolConfigInput = z.input<typeof toolConfigSchema>
|
||||
export type ToolConfig = z.infer<typeof toolConfigSchema>
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* types than what `@hono/zod-openapi` and `zod` v3 provide, but in general
|
||||
* these types are meant to use the backend API as a source of truth.
|
||||
*/
|
||||
import type { PricingPlan } from '@agentic/platform-types'
|
||||
import type { PricingPlan, ToolConfig } from '@agentic/platform-types'
|
||||
import type { Simplify } from 'type-fest'
|
||||
|
||||
import type { components } from './openapi.d.ts'
|
||||
|
@ -16,14 +16,19 @@ export type Team = components['schemas']['Team']
|
|||
export type TeamMember = components['schemas']['TeamMember']
|
||||
|
||||
export type Deployment = Simplify<
|
||||
Omit<components['schemas']['Deployment'], 'pricingPlans'> & {
|
||||
Omit<components['schemas']['Deployment'], 'pricingPlans' | 'toolConfigs'> & {
|
||||
pricingPlans: PricingPlan[]
|
||||
toolConfigs: ToolConfig[]
|
||||
}
|
||||
>
|
||||
|
||||
export type AdminDeployment = Simplify<
|
||||
Omit<components['schemas']['AdminDeployment'], 'pricingPlans'> & {
|
||||
Omit<
|
||||
components['schemas']['AdminDeployment'],
|
||||
'pricingPlans' | 'toolConfigs'
|
||||
> & {
|
||||
pricingPlans: PricingPlan[]
|
||||
toolConfigs: ToolConfig[]
|
||||
}
|
||||
>
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue