kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
8aca815ccf
commit
6a985e5ccb
|
@ -1,10 +1,10 @@
|
||||||
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||||
import { Validator } from '@agentic/json-schema'
|
import { Validator } from '@agentic/json-schema'
|
||||||
import { HttpError } from '@agentic/platform-core'
|
import { assert, HttpError } from '@agentic/platform-core'
|
||||||
import plur from 'plur'
|
import plur from 'plur'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates `data` against the provided JSON schema object.
|
* Validates `data` against the provided JSON schema.
|
||||||
*
|
*
|
||||||
* This method uses a fork of `@cfworker/json-schema`. It does not use `ajv`
|
* This method uses a fork of `@cfworker/json-schema`. It does not use `ajv`
|
||||||
* because `ajv` is not supported on CF workers due to its dynamic code
|
* because `ajv` is not supported on CF workers due to its dynamic code
|
||||||
|
@ -14,9 +14,7 @@ import plur from 'plur'
|
||||||
* not running on CF workers, consider using `validateJsonSchemaObject` from
|
* not running on CF workers, consider using `validateJsonSchemaObject` from
|
||||||
* `@agentic/platform-openapi-utils`.
|
* `@agentic/platform-openapi-utils`.
|
||||||
*/
|
*/
|
||||||
export function cfValidateJsonSchemaObject<
|
export function cfValidateJsonSchema<T = unknown>({
|
||||||
T extends Record<string, any> = Record<string, any>
|
|
||||||
>({
|
|
||||||
schema,
|
schema,
|
||||||
data,
|
data,
|
||||||
coerce = false,
|
coerce = false,
|
||||||
|
@ -25,16 +23,29 @@ export function cfValidateJsonSchemaObject<
|
||||||
errorStatusCode = 400
|
errorStatusCode = 400
|
||||||
}: {
|
}: {
|
||||||
schema: any
|
schema: any
|
||||||
data: Record<string, unknown>
|
data: unknown
|
||||||
coerce?: boolean
|
coerce?: boolean
|
||||||
strictAdditionalProperties?: boolean
|
strictAdditionalProperties?: boolean
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
errorStatusCode?: ContentfulStatusCode
|
errorStatusCode?: ContentfulStatusCode
|
||||||
}): T {
|
}): T {
|
||||||
// Special-case check for required fields to give better error messages.
|
assert(schema, 400, '`schema` is required')
|
||||||
if (schema.required && Array.isArray(schema.required)) {
|
const isSchemaObject =
|
||||||
|
typeof schema === 'object' &&
|
||||||
|
!Array.isArray(schema) &&
|
||||||
|
schema.type === 'object'
|
||||||
|
const isDataObject = typeof data === 'object' && !Array.isArray(data)
|
||||||
|
if (isSchemaObject && !isDataObject) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 400,
|
||||||
|
message: `${errorMessage ? errorMessage + ': ' : ''}Data must be an object according to its schema.`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special-case check for required fields to give better error messages
|
||||||
|
if (isSchemaObject && Array.isArray(schema.required)) {
|
||||||
const missingRequiredFields: string[] = schema.required.filter(
|
const missingRequiredFields: string[] = schema.required.filter(
|
||||||
(field: string) => (data as T)[field] === undefined
|
(field: string) => (data as Record<string, unknown>)[field] === undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
if (missingRequiredFields.length > 0) {
|
if (missingRequiredFields.length > 0) {
|
||||||
|
@ -48,11 +59,12 @@ export function cfValidateJsonSchemaObject<
|
||||||
// Special-case check for additional top-level fields to give better error
|
// Special-case check for additional top-level fields to give better error
|
||||||
// messages.
|
// messages.
|
||||||
if (
|
if (
|
||||||
|
isSchemaObject &&
|
||||||
schema.properties &&
|
schema.properties &&
|
||||||
(schema.additionalProperties === false ||
|
(schema.additionalProperties === false ||
|
||||||
(schema.additionalProperties === undefined && strictAdditionalProperties))
|
(schema.additionalProperties === undefined && strictAdditionalProperties))
|
||||||
) {
|
) {
|
||||||
const extraProperties = Object.keys(data).filter(
|
const extraProperties = Object.keys(data as Record<string, unknown>).filter(
|
||||||
(key) => !schema.properties[key]
|
(key) => !schema.properties[key]
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { AdminDeployment, Tool } from '@agentic/platform-types'
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert } from '@agentic/platform-core'
|
||||||
|
|
||||||
import type { GatewayHonoContext, McpToolCallResponse } from './types'
|
import type { GatewayHonoContext, McpToolCallResponse } from './types'
|
||||||
import { cfValidateJsonSchemaObject } from './cf-validate-json-schema-object'
|
import { cfValidateJsonSchema } from './cf-validate-json-schema'
|
||||||
|
|
||||||
export async function createHttpResponseFromMcpToolCallResponse(
|
export async function createHttpResponseFromMcpToolCallResponse(
|
||||||
_ctx: GatewayHonoContext,
|
_ctx: GatewayHonoContext,
|
||||||
|
@ -35,7 +35,7 @@ export async function createHttpResponseFromMcpToolCallResponse(
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate tool response against the tool's output schema.
|
// Validate tool response against the tool's output schema.
|
||||||
const toolCallResponseContent = cfValidateJsonSchemaObject({
|
const toolCallResponseContent = cfValidateJsonSchema({
|
||||||
schema: tool.outputSchema,
|
schema: tool.outputSchema,
|
||||||
data: toolCallResponse.structuredContent as Record<string, unknown>,
|
data: toolCallResponse.structuredContent as Record<string, unknown>,
|
||||||
coerce: false,
|
coerce: false,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { AdminDeployment, Tool } from '@agentic/platform-types'
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert } from '@agentic/platform-core'
|
||||||
|
|
||||||
import type { GatewayHonoContext } from './types'
|
import type { GatewayHonoContext } from './types'
|
||||||
import { cfValidateJsonSchemaObject } from './cf-validate-json-schema-object'
|
import { cfValidateJsonSchema } from './cf-validate-json-schema'
|
||||||
|
|
||||||
export async function getToolArgsFromRequest(
|
export async function getToolArgsFromRequest(
|
||||||
ctx: GatewayHonoContext,
|
ctx: GatewayHonoContext,
|
||||||
|
@ -48,7 +48,7 @@ export async function getToolArgsFromRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate incoming request params against the tool's input schema.
|
// Validate incoming request params against the tool's input schema.
|
||||||
const incomingRequestArgs = cfValidateJsonSchemaObject({
|
const incomingRequestArgs = cfValidateJsonSchema<Record<string, any>>({
|
||||||
schema: tool.inputSchema,
|
schema: tool.inputSchema,
|
||||||
data: incomingRequestArgsRaw,
|
data: incomingRequestArgsRaw,
|
||||||
errorMessage: `Invalid request parameters for tool "${tool.name}"`,
|
errorMessage: `Invalid request parameters for tool "${tool.name}"`,
|
||||||
|
|
|
@ -22,6 +22,18 @@ server.addTool({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
server.addTool({
|
||||||
|
name: 'add2',
|
||||||
|
description: 'TODO',
|
||||||
|
parameters: z.object({
|
||||||
|
a: z.number(),
|
||||||
|
b: z.number()
|
||||||
|
}),
|
||||||
|
execute: async (args) => {
|
||||||
|
return String(args.a + args.b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
await server.start({
|
await server.start({
|
||||||
transportType: 'httpStream',
|
transportType: 'httpStream',
|
||||||
httpStream: {
|
httpStream: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { hashObject, HttpError } from '@agentic/platform-core'
|
import { assert, hashObject, HttpError } from '@agentic/platform-core'
|
||||||
import { betterAjvErrors } from '@apideck/better-ajv-errors'
|
import { betterAjvErrors } from '@apideck/better-ajv-errors'
|
||||||
import Ajv, { type ValidateFunction } from 'ajv'
|
import Ajv, { type ValidateFunction } from 'ajv'
|
||||||
import addFormats from 'ajv-formats'
|
import addFormats from 'ajv-formats'
|
||||||
|
@ -19,7 +19,7 @@ const globalAjv = new Ajv({
|
||||||
addFormats(globalAjv)
|
addFormats(globalAjv)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates `data` against the provided JSON schema object.
|
* Validates `data` against the provided JSON schema.
|
||||||
*
|
*
|
||||||
* This method uses `ajv` and is therefore not compatible with CF workers due
|
* This method uses `ajv` and is therefore not compatible with CF workers due
|
||||||
* to its use of code generation and evaluation.
|
* to its use of code generation and evaluation.
|
||||||
|
@ -29,9 +29,7 @@ addFormats(globalAjv)
|
||||||
*
|
*
|
||||||
* @see https://github.com/ajv-validator/ajv/issues/2318
|
* @see https://github.com/ajv-validator/ajv/issues/2318
|
||||||
*/
|
*/
|
||||||
export function validateJsonSchemaObject<
|
export function validateJsonSchema<T = unknown>({
|
||||||
T extends Record<string, unknown> = Record<string, unknown>
|
|
||||||
>({
|
|
||||||
schema,
|
schema,
|
||||||
data,
|
data,
|
||||||
ajv = globalAjv,
|
ajv = globalAjv,
|
||||||
|
@ -42,10 +40,23 @@ export function validateJsonSchemaObject<
|
||||||
ajv?: Ajv
|
ajv?: Ajv
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
}): T {
|
}): T {
|
||||||
|
assert(schema, 400, '`schema` is required')
|
||||||
|
const isSchemaObject =
|
||||||
|
typeof schema === 'object' &&
|
||||||
|
!Array.isArray(schema) &&
|
||||||
|
schema.type === 'object'
|
||||||
|
const isDataObject = typeof data === 'object' && !Array.isArray(data)
|
||||||
|
if (isSchemaObject && !isDataObject) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 400,
|
||||||
|
message: `${errorMessage ? errorMessage + ': ' : ''}Data must be an object according to its schema.`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Special-case check for required fields to give better error messages
|
// Special-case check for required fields to give better error messages
|
||||||
if (Array.isArray(schema.required)) {
|
if (isSchemaObject && Array.isArray(schema.required)) {
|
||||||
const missingRequiredFields: string[] = schema.required.filter(
|
const missingRequiredFields: string[] = schema.required.filter(
|
||||||
(field: string) => (data as T)[field] === undefined
|
(field: string) => (data as Record<string, unknown>)[field] === undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
if (missingRequiredFields.length > 0) {
|
if (missingRequiredFields.length > 0) {
|
||||||
|
|
Ładowanie…
Reference in New Issue