kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: refactor validators to throw on errors; gateway work wip
rodzic
ce2f3afc41
commit
8ae5dec653
|
@ -53,11 +53,6 @@ export async function tryGetDeploymentByIdentifier(
|
|||
deploymentIdentifier,
|
||||
{ strict }
|
||||
)
|
||||
assert(
|
||||
parsedDeploymentIdentifier,
|
||||
400,
|
||||
`Invalid deployment identifier "${deploymentIdentifier}"`
|
||||
)
|
||||
|
||||
const { projectIdentifier, deploymentHash, deploymentVersion } =
|
||||
parsedDeploymentIdentifier
|
||||
|
|
|
@ -43,11 +43,6 @@ export async function tryGetProjectByIdentifier(
|
|||
const parsedProjectIdentifier = parseProjectIdentifier(projectIdentifier, {
|
||||
strict
|
||||
})
|
||||
assert(
|
||||
parsedProjectIdentifier?.projectIdentifier,
|
||||
400,
|
||||
`Invalid project identifier "${projectIdentifier}"`
|
||||
)
|
||||
projectIdentifier = parsedProjectIdentifier.projectIdentifier
|
||||
|
||||
const project = await db.query.projects.findFirst({
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||
import { Validator } from '@agentic/json-schema'
|
||||
import { assert, HttpError } from '@agentic/platform-core'
|
||||
import plur from 'plur'
|
||||
|
@ -27,7 +26,7 @@ export function cfValidateJsonSchema<T = unknown>({
|
|||
coerce?: boolean
|
||||
strictAdditionalProperties?: boolean
|
||||
errorMessage?: string
|
||||
errorStatusCode?: ContentfulStatusCode
|
||||
errorStatusCode?: number
|
||||
}): T {
|
||||
assert(schema, 400, '`schema` is required')
|
||||
const isSchemaObject =
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js'
|
|||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
import { DurableObject } from 'cloudflare:workers'
|
||||
|
||||
import type { RawEnv } from './env'
|
||||
import type { AgenticMcpRequestMetadata } from './types'
|
||||
|
||||
export type DurableMcpClientInfo = {
|
||||
|
@ -17,7 +16,7 @@ export type DurableMcpClientInfo = {
|
|||
// customer<>DurableMcpClientInfo connection?
|
||||
// Currently using `sessionId`
|
||||
|
||||
export class DurableMcpClient extends DurableObject<RawEnv> {
|
||||
export class DurableMcpClient extends DurableObject {
|
||||
protected client?: McpClient
|
||||
protected clientConnectionP?: Promise<void>
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
|
||||
import { DurableObject } from 'cloudflare:workers'
|
||||
|
||||
import type { RawEnv } from './env'
|
||||
import type { AdminConsumer } from './types'
|
||||
|
||||
export type DurableMcpServerInfo = {
|
||||
|
@ -16,7 +15,7 @@ export type DurableMcpServerInfo = {
|
|||
pricingPlan?: PricingPlan
|
||||
}
|
||||
|
||||
export class DurableMcpServer extends DurableObject<RawEnv> {
|
||||
export class DurableMcpServer extends DurableObject {
|
||||
protected server?: McpServer
|
||||
protected serverTransport?: StreamableHTTPServerTransport
|
||||
protected serverConnectionP?: Promise<void>
|
||||
|
@ -50,15 +49,9 @@ export class DurableMcpServer extends DurableObject<RawEnv> {
|
|||
assert(mcpServerInfo, 500, 'DurableMcpServer has not been initialized')
|
||||
const { deployment } = mcpServerInfo
|
||||
|
||||
const parsedDeploymentIdentifier = parseDeploymentIdentifier(
|
||||
const { projectIdentifier } = parseDeploymentIdentifier(
|
||||
deployment.identifier
|
||||
)
|
||||
assert(
|
||||
parsedDeploymentIdentifier,
|
||||
500,
|
||||
`Invalid deployment identifier "${deployment.identifier}"`
|
||||
)
|
||||
const { projectIdentifier } = parsedDeploymentIdentifier
|
||||
|
||||
this.server = new McpServer({
|
||||
name: projectIdentifier,
|
||||
|
|
|
@ -1,25 +1,8 @@
|
|||
import { DurableObject } from 'cloudflare:workers'
|
||||
|
||||
import type { RawEnv } from './env'
|
||||
// TODO: implement
|
||||
|
||||
/** A Durable Object's behavior is defined in an exported Javascript class */
|
||||
export class DurableRateLimiter extends DurableObject<RawEnv> {
|
||||
/**
|
||||
* The constructor is invoked once upon creation of the Durable Object, i.e. the first call to
|
||||
* `DurableObjectStub::get` for a given identifier (no-op constructors can be omitted)
|
||||
*
|
||||
* @param ctx - The interface for interacting with Durable Object state
|
||||
* @param env - The interface to reference bindings declared in wrangler.jsonc
|
||||
*/
|
||||
constructor(ctx: DurableObjectState, env: RawEnv) {
|
||||
super(ctx, env)
|
||||
}
|
||||
|
||||
/**
|
||||
* The Durable Object exposes an RPC method sayHello which will be invoked
|
||||
* when when a Durable Object instance receives a request from a Worker via
|
||||
* the same method invocation on the stub.
|
||||
*/
|
||||
export class DurableRateLimiter extends DurableObject {
|
||||
async sayHello(name: string): Promise<string> {
|
||||
return `Hello, ${name}!`
|
||||
}
|
||||
|
|
|
@ -9,13 +9,9 @@ export async function getAdminDeployment(
|
|||
identifier: string
|
||||
): Promise<AdminDeployment> {
|
||||
const parsedDeploymentIdentifier = parseDeploymentIdentifier(identifier, {
|
||||
strict: false
|
||||
strict: true,
|
||||
errorStatusCode: 404
|
||||
})
|
||||
assert(
|
||||
parsedDeploymentIdentifier,
|
||||
404,
|
||||
`Invalid deployment identifier "${identifier}"`
|
||||
)
|
||||
|
||||
const client = ctx.get('client')
|
||||
const deployment = await client.adminGetDeploymentByIdentifier({
|
||||
|
|
|
@ -16,19 +16,11 @@ export async function resolveMcpEdgeRequest(ctx: GatewayHonoContext): Promise<{
|
|||
const requestedDeploymentIdentifier = pathname
|
||||
.replace(/^\//, '')
|
||||
.replace(/\/$/, '')
|
||||
const parsedDeploymentIdentifier = parseDeploymentIdentifier(
|
||||
const { deploymentIdentifier } = parseDeploymentIdentifier(
|
||||
requestedDeploymentIdentifier
|
||||
)
|
||||
assert(
|
||||
parsedDeploymentIdentifier,
|
||||
404,
|
||||
`Invalid deployment identifier "${requestedDeploymentIdentifier}"`
|
||||
)
|
||||
|
||||
const deployment = await getAdminDeployment(
|
||||
ctx,
|
||||
parsedDeploymentIdentifier.deploymentIdentifier
|
||||
)
|
||||
const deployment = await getAdminDeployment(ctx, deploymentIdentifier)
|
||||
|
||||
const apiKey = ctx.req.query('apiKey')?.trim()
|
||||
let consumer: AdminConsumer | undefined
|
||||
|
|
|
@ -38,18 +38,11 @@ export async function resolveOriginRequest(
|
|||
const requestUrl = new URL(ctx.req.url)
|
||||
const { pathname } = requestUrl
|
||||
const requestedToolIdentifier = pathname.replace(/^\//, '').replace(/\/$/, '')
|
||||
const parsedToolIdentifier = parseToolIdentifier(requestedToolIdentifier)
|
||||
assert(
|
||||
parsedToolIdentifier,
|
||||
404,
|
||||
`Invalid tool identifier "${requestedToolIdentifier}"`
|
||||
const { toolName, deploymentIdentifier } = parseToolIdentifier(
|
||||
requestedToolIdentifier
|
||||
)
|
||||
const { toolName } = parsedToolIdentifier
|
||||
|
||||
const deployment = await getAdminDeployment(
|
||||
ctx,
|
||||
parsedToolIdentifier.deploymentIdentifier
|
||||
)
|
||||
const deployment = await getAdminDeployment(ctx, deploymentIdentifier)
|
||||
|
||||
const tool = getTool({
|
||||
method,
|
||||
|
@ -227,15 +220,10 @@ export async function resolveOriginRequest(
|
|||
version: originAdapter.serverInfo.version
|
||||
})
|
||||
|
||||
const parsedDeploymentIdentifier = parseDeploymentIdentifier(
|
||||
deployment.identifier
|
||||
const { projectIdentifier } = parseDeploymentIdentifier(
|
||||
deployment.identifier,
|
||||
{ errorStatusCode: 500 }
|
||||
)
|
||||
assert(
|
||||
parsedDeploymentIdentifier,
|
||||
500,
|
||||
`Internal error: deployment identifier "${deployment.identifier}" is invalid`
|
||||
)
|
||||
const { projectIdentifier } = parsedDeploymentIdentifier
|
||||
|
||||
originMcpRequestMetadata = {
|
||||
agenticProxySecret: deployment._secret,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
// import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
import { assert, JsonRpcError } from '@agentic/platform-core'
|
||||
import { parseDeploymentIdentifier } from '@agentic/platform-validators'
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import {
|
||||
InitializeRequestSchema,
|
||||
isJSONRPCError,
|
||||
// isJSONRPCError,
|
||||
isJSONRPCNotification,
|
||||
isJSONRPCRequest,
|
||||
isJSONRPCResponse,
|
||||
|
@ -13,6 +15,7 @@ import {
|
|||
|
||||
import type { GatewayHonoContext } from './lib/types'
|
||||
import { resolveMcpEdgeRequest } from './lib/resolve-mcp-edge-request'
|
||||
// import { DurableMcpServer } from './lib/durable-mcp-server'
|
||||
|
||||
// TODO: https://github.com/modelcontextprotocol/servers/blob/8fb7bbdab73eddb42aba72e8eab81102efe1d544/src/everything/sse.ts
|
||||
// TODO: https://github.com/cloudflare/agents
|
||||
|
@ -168,10 +171,11 @@ export async function handleMcpRequest(ctx: GatewayHonoContext) {
|
|||
)
|
||||
ctx.set('sessionId', sessionId)
|
||||
|
||||
// TODO: first version using the McpServer locally instead of a DurableMcpServer
|
||||
// Fetch the durable mcp server for this session
|
||||
const id = ctx.env.DO_MCP_SERVER.idFromName(`streamable-http:${sessionId}`)
|
||||
const durableMcpServer = ctx.env.DO_MCP_SERVER.get(id)
|
||||
const isInitialized = await durableMcpServer.isInitialized()
|
||||
// const id = ctx.env.DO_MCP_SERVER.idFromName(`streamable-http:${sessionId}`)
|
||||
// const durableMcpServer = ctx.env.DO_MCP_SERVER.get(id)
|
||||
// const isInitialized = await durableMcpServer.isInitialized()
|
||||
|
||||
if (!isInitializationRequest && !isInitialized) {
|
||||
// A session id that was never initialized was provided
|
||||
|
@ -183,16 +187,21 @@ export async function handleMcpRequest(ctx: GatewayHonoContext) {
|
|||
})
|
||||
}
|
||||
|
||||
if (isInitializationRequest) {
|
||||
const { deployment, consumer, pricingPlan } =
|
||||
await resolveMcpEdgeRequest(ctx)
|
||||
const { deployment, consumer, pricingPlan } = await resolveMcpEdgeRequest(ctx)
|
||||
const { projectIdentifier } = parseDeploymentIdentifier(deployment.identifier)
|
||||
|
||||
await durableMcpServer.init({
|
||||
deployment,
|
||||
consumer,
|
||||
pricingPlan
|
||||
})
|
||||
}
|
||||
const server = new McpServer({
|
||||
name: projectIdentifier,
|
||||
version: deployment.version ?? '0.0.0'
|
||||
})
|
||||
|
||||
// if (isInitializationRequest) {
|
||||
// await durableMcpServer.init({
|
||||
// deployment,
|
||||
// consumer,
|
||||
// pricingPlan
|
||||
// })
|
||||
// }
|
||||
|
||||
// We've validated and initialized the request! Now it's time to actually
|
||||
// handle the JSON RPC messages in the request and respond with an SSE
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/**
|
||||
* For more details on how to configure Wrangler, refer to:
|
||||
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||
*/
|
||||
{
|
||||
|
|
|
@ -34,12 +34,6 @@ export function registerListDeploymentsCommand({
|
|||
}
|
||||
)
|
||||
|
||||
if (!parsedDeploymentIdentifier) {
|
||||
throw new Error(
|
||||
`Invalid project or deployment identifier "${identifier}"`
|
||||
)
|
||||
}
|
||||
|
||||
query.projectIdentifier = parsedDeploymentIdentifier.projectIdentifier
|
||||
label = `Fetching deployments for project "${query.projectIdentifier}"`
|
||||
|
||||
|
|
|
@ -28,9 +28,6 @@
|
|||
"zod": "catalog:",
|
||||
"zod-validation-error": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"hono": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||
import { fromError } from 'zod-validation-error'
|
||||
|
||||
export class BaseError extends Error {
|
||||
|
@ -16,7 +15,7 @@ export class BaseError extends Error {
|
|||
}
|
||||
|
||||
export class HttpError extends BaseError {
|
||||
readonly statusCode: ContentfulStatusCode
|
||||
readonly statusCode: number
|
||||
|
||||
constructor({
|
||||
message,
|
||||
|
@ -24,7 +23,7 @@ export class HttpError extends BaseError {
|
|||
cause
|
||||
}: {
|
||||
message: string
|
||||
statusCode?: ContentfulStatusCode
|
||||
statusCode?: number
|
||||
cause?: unknown
|
||||
}) {
|
||||
super({ message, cause })
|
||||
|
@ -47,7 +46,7 @@ export class JsonRpcError extends HttpError {
|
|||
message: string
|
||||
jsonRpcErrorCode: number
|
||||
jsonRpcId?: string | number | null
|
||||
statusCode?: ContentfulStatusCode
|
||||
statusCode?: number
|
||||
cause?: unknown
|
||||
}) {
|
||||
super({ message, cause, statusCode })
|
||||
|
@ -63,7 +62,7 @@ export class ZodValidationError extends HttpError {
|
|||
prefix,
|
||||
cause
|
||||
}: {
|
||||
statusCode?: ContentfulStatusCode
|
||||
statusCode?: number
|
||||
prefix?: string
|
||||
cause: unknown
|
||||
}) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||
import type { z, ZodType } from 'zod'
|
||||
import hashObjectImpl, { type Options as HashObjectOptions } from 'hash-object'
|
||||
|
||||
|
@ -51,12 +50,12 @@ export const pick = <
|
|||
export function assert(expr: unknown, message?: string): asserts expr
|
||||
export function assert(
|
||||
expr: unknown,
|
||||
statusCode?: ContentfulStatusCode,
|
||||
statusCode?: number,
|
||||
message?: string
|
||||
): asserts expr
|
||||
export function assert(
|
||||
expr: unknown,
|
||||
statusCodeOrMessage?: ContentfulStatusCode | string,
|
||||
statusCodeOrMessage?: number | string,
|
||||
message = 'Internal assertion failed'
|
||||
): asserts expr {
|
||||
if (expr) {
|
||||
|
@ -86,7 +85,7 @@ export function parseZodSchema<TSchema extends ZodType<any, any, any>>(
|
|||
statusCode = 500
|
||||
}: {
|
||||
error?: string
|
||||
statusCode?: ContentfulStatusCode
|
||||
statusCode?: number
|
||||
} = {}
|
||||
): z.infer<TSchema> {
|
||||
try {
|
||||
|
|
|
@ -25,13 +25,13 @@ export function errorHandler(
|
|||
status = err.status
|
||||
} else if (err instanceof HttpError) {
|
||||
message = err.message
|
||||
status = err.statusCode
|
||||
status = err.statusCode as ContentfulStatusCode
|
||||
} else if (err instanceof HTTPError) {
|
||||
message = err.message
|
||||
status = err.response.status as ContentfulStatusCode
|
||||
} else if (err instanceof JsonRpcError) {
|
||||
message = err.message
|
||||
status = err.statusCode
|
||||
status = err.statusCode as ContentfulStatusCode
|
||||
jsonRpcId = err.jsonRpcId
|
||||
jsonRpcErrorCode = err.jsonRpcErrorCode
|
||||
isJsonRpcRequest = true
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"test:unit": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/platform-core": "workspace:*",
|
||||
"@paralleldrive/cuid2": "catalog:",
|
||||
"email-validator": "catalog:",
|
||||
"type-fest": "catalog:"
|
||||
|
|
|
@ -30,8 +30,7 @@ function success(...args: Parameters<typeof parseDeploymentIdentifier>) {
|
|||
}
|
||||
|
||||
function error(...args: Parameters<typeof parseDeploymentIdentifier>) {
|
||||
const result = parseDeploymentIdentifier(...args)
|
||||
expect(result).toBeUndefined()
|
||||
expect(() => parseDeploymentIdentifier(...args)).throws()
|
||||
}
|
||||
|
||||
describe('parseDeploymentIdentifier', () => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { HttpError } from '@agentic/platform-core'
|
||||
|
||||
import type {
|
||||
ParsedDeploymentIdentifier,
|
||||
ParseIdentifierOptions
|
||||
|
@ -16,15 +18,18 @@ const deploymentIdentifierVersionRe =
|
|||
|
||||
export function parseDeploymentIdentifier(
|
||||
identifier?: string,
|
||||
{ strict = true }: ParseIdentifierOptions = {}
|
||||
): ParsedDeploymentIdentifier | undefined {
|
||||
if (!strict) {
|
||||
const parsedToolIdentifier = parseToolIdentifier(identifier, {
|
||||
strict
|
||||
})
|
||||
{ strict = true, errorStatusCode = 400 }: ParseIdentifierOptions = {}
|
||||
): ParsedDeploymentIdentifier {
|
||||
const inputIdentifier = identifier
|
||||
|
||||
if (parsedToolIdentifier) {
|
||||
return parsedToolIdentifier
|
||||
if (!strict) {
|
||||
try {
|
||||
return parseToolIdentifier(identifier, {
|
||||
strict,
|
||||
errorStatusCode
|
||||
})
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +38,10 @@ export function parseDeploymentIdentifier(
|
|||
}
|
||||
|
||||
if (!identifier?.length) {
|
||||
return
|
||||
throw new HttpError({
|
||||
statusCode: errorStatusCode,
|
||||
message: `Invalid deployment identifier "${inputIdentifier}"`
|
||||
})
|
||||
}
|
||||
|
||||
const iMatch = identifier.match(deploymentIdentifierImplicitRe)
|
||||
|
@ -71,4 +79,9 @@ export function parseDeploymentIdentifier(
|
|||
deploymentVersion: 'latest'
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpError({
|
||||
statusCode: errorStatusCode,
|
||||
message: `Invalid deployment identifier "${inputIdentifier}"`
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@ function success(...args: Parameters<typeof parseProjectIdentifier>) {
|
|||
}
|
||||
|
||||
function error(...args: Parameters<typeof parseProjectIdentifier>) {
|
||||
const result = parseProjectIdentifier(...args)
|
||||
expect(result).toBeUndefined()
|
||||
expect(() => parseProjectIdentifier(...args)).throws()
|
||||
}
|
||||
|
||||
describe('parseProjectIdentifier', () => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { HttpError } from '@agentic/platform-core'
|
||||
|
||||
import type { ParsedProjectIdentifier, ParseIdentifierOptions } from './types'
|
||||
import { parseDeploymentIdentifier } from './parse-deployment-identifier'
|
||||
import { coerceIdentifier } from './utils'
|
||||
|
@ -6,15 +8,18 @@ const projectIdentifierRe = /^@([a-z0-9-]{1,256})\/([a-z0-9-]{1,256})$/
|
|||
|
||||
export function parseProjectIdentifier(
|
||||
identifier?: string,
|
||||
{ strict = true }: ParseIdentifierOptions = {}
|
||||
): ParsedProjectIdentifier | undefined {
|
||||
if (!strict) {
|
||||
const parsedToolIdentifier = parseDeploymentIdentifier(identifier, {
|
||||
strict
|
||||
})
|
||||
{ strict = true, errorStatusCode = 400 }: ParseIdentifierOptions = {}
|
||||
): ParsedProjectIdentifier {
|
||||
const inputIdentifier = identifier
|
||||
|
||||
if (parsedToolIdentifier) {
|
||||
return parsedToolIdentifier
|
||||
if (!strict) {
|
||||
try {
|
||||
return parseDeploymentIdentifier(identifier, {
|
||||
strict,
|
||||
errorStatusCode
|
||||
})
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +28,10 @@ export function parseProjectIdentifier(
|
|||
}
|
||||
|
||||
if (!identifier?.length) {
|
||||
return
|
||||
throw new HttpError({
|
||||
statusCode: errorStatusCode,
|
||||
message: `Invalid project identifier "${inputIdentifier}"`
|
||||
})
|
||||
}
|
||||
|
||||
const match = identifier.match(projectIdentifierRe)
|
||||
|
@ -35,4 +43,9 @@ export function parseProjectIdentifier(
|
|||
projectName: match[2]!
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpError({
|
||||
statusCode: errorStatusCode,
|
||||
message: `Invalid project identifier "${inputIdentifier}"`
|
||||
})
|
||||
}
|
||||
|
|
|
@ -32,8 +32,7 @@ function success(...args: Parameters<typeof parseToolIdentifier>) {
|
|||
}
|
||||
|
||||
function error(...args: Parameters<typeof parseToolIdentifier>) {
|
||||
const result = parseToolIdentifier(...args)
|
||||
expect(result).toBeUndefined()
|
||||
expect(() => parseToolIdentifier(...args)).throws()
|
||||
}
|
||||
|
||||
describe('parseToolIdentifier', () => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { HttpError } from '@agentic/platform-core'
|
||||
|
||||
import type { ParsedToolIdentifier, ParseIdentifierOptions } from './types'
|
||||
import { coerceIdentifier } from './utils'
|
||||
|
||||
|
@ -12,14 +14,19 @@ const toolIdentifierVersionRe =
|
|||
|
||||
export function parseToolIdentifier(
|
||||
identifier?: string,
|
||||
{ strict = true }: ParseIdentifierOptions = {}
|
||||
): ParsedToolIdentifier | undefined {
|
||||
{ strict = true, errorStatusCode = 400 }: ParseIdentifierOptions = {}
|
||||
): ParsedToolIdentifier {
|
||||
const inputIdentifier = identifier
|
||||
|
||||
if (!strict) {
|
||||
identifier = coerceIdentifier(identifier)
|
||||
}
|
||||
|
||||
if (!identifier?.length) {
|
||||
return
|
||||
throw new HttpError({
|
||||
statusCode: errorStatusCode,
|
||||
message: `Invalid tool identifier "${inputIdentifier}"`
|
||||
})
|
||||
}
|
||||
|
||||
const iMatch = identifier.match(toolIdentifierImplicitRe)
|
||||
|
@ -60,4 +67,9 @@ export function parseToolIdentifier(
|
|||
toolName: vMatch[4]!
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpError({
|
||||
statusCode: errorStatusCode,
|
||||
message: `Invalid tool identifier "${inputIdentifier}"`
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { Simplify } from 'type-fest'
|
|||
|
||||
export type ParseIdentifierOptions = {
|
||||
strict?: boolean
|
||||
errorStatusCode?: number
|
||||
}
|
||||
|
||||
export type ParsedProjectIdentifier = {
|
||||
|
|
|
@ -50,14 +50,22 @@ export function isValidProjectIdentifier(
|
|||
value?: string,
|
||||
opts?: ParseIdentifierOptions
|
||||
): boolean {
|
||||
return !!parseProjectIdentifier(value, opts)
|
||||
try {
|
||||
return !!parseProjectIdentifier(value, opts)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidDeploymentIdentifier(
|
||||
value?: string,
|
||||
opts?: ParseIdentifierOptions
|
||||
): boolean {
|
||||
return !!parseDeploymentIdentifier(value, opts)
|
||||
try {
|
||||
return !!parseDeploymentIdentifier(value, opts)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidToolName(value?: string): boolean {
|
||||
|
|
|
@ -546,10 +546,6 @@ importers:
|
|||
zod-validation-error:
|
||||
specifier: 'catalog:'
|
||||
version: 3.4.1(zod@3.25.51)
|
||||
devDependencies:
|
||||
hono:
|
||||
specifier: 'catalog:'
|
||||
version: 4.7.11
|
||||
|
||||
packages/emails:
|
||||
dependencies:
|
||||
|
@ -757,6 +753,9 @@ importers:
|
|||
|
||||
packages/validators:
|
||||
dependencies:
|
||||
'@agentic/platform-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@paralleldrive/cuid2':
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.2
|
||||
|
|
Ładowanie…
Reference in New Issue