Files
aiturk-hermes-ide/apps/desktop/codex-link-control.ts
T

124 lines
3.5 KiB
TypeScript

import schema from './codex-link-control.schema.json'
import type { CodexLinkControlRequest, CodexLinkControlResponse } from './codex-link-control.types'
import { canonicalJson, CodexLinkValidationError, parseStrictJson } from './codex-link-json'
export const CODEX_LINK_REQUEST_BYTES = 64 * 1024
export const CODEX_LINK_RESPONSE_BYTES = 1024 * 1024
type Rule = Record<string, any>
const definitions: Record<string, Rule> = schema.$defs
function matches(value: any, rule: Rule): boolean {
if (rule.$ref) {
const target = definitions[rule.$ref.replace('#/$defs/', '')]
return !!target && matches(value, target)
}
if (rule.oneOf) {
return rule.oneOf.filter((r: Rule) => matches(value, r)).length === 1
}
if (rule.anyOf) {
return rule.anyOf.some((r: Rule) => matches(value, r))
}
if ('const' in rule && value !== rule.const) {
return false
}
if (rule.enum && !rule.enum.includes(value)) {
return false
}
switch (rule.type) {
case 'object': {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return false
}
const keys = Object.keys(value)
return (
keys.length >= (rule.minProperties ?? 0) &&
rule.required.every((key: string) => Object.hasOwn(value, key)) &&
keys.every(key => Object.hasOwn(rule.properties, key) && matches(value[key], rule.properties[key]))
)
}
case 'array':
return (
Array.isArray(value) &&
value.length >= (rule.minItems ?? 0) &&
(!rule.uniqueItems || new Set(value.map(canonicalJson)).size === value.length) &&
value.every(item => matches(item, rule.items))
)
case 'string':
return (
typeof value === 'string' &&
[...value].length >= (rule.minLength ?? 0) &&
(!rule.pattern || new RegExp(rule.pattern).test(value))
)
case 'integer':
return (
Number.isSafeInteger(value) &&
value >= (rule.minimum ?? -Number.MAX_SAFE_INTEGER) &&
value <= (rule.maximum ?? Number.MAX_SAFE_INTEGER)
)
case 'boolean':
return typeof value === 'boolean'
case 'null':
return value === null
case undefined:
return 'const' in rule || 'enum' in rule
default:
return false
}
}
export function validateControlRequest(value: unknown): asserts value is CodexLinkControlRequest {
const text = canonicalJson(value)
if (
new TextEncoder().encode(text).byteLength > CODEX_LINK_REQUEST_BYTES ||
!matches(value, definitions.control_request)
) {
throw new CodexLinkValidationError('invalid_control_request')
}
}
export function parseControlResponse(
raw: Uint8Array | string,
request: CodexLinkControlRequest
): CodexLinkControlResponse {
const value = parseStrictJson(raw, CODEX_LINK_RESPONSE_BYTES) as CodexLinkControlResponse
if (
!matches(value, definitions.control_response) ||
value.request_id !== request.request_id ||
value.operation_id !== request.operation_id
) {
throw new CodexLinkValidationError('invalid_control_response')
}
const expected = {
settings_get: 'settings_result',
settings_update: 'settings_result',
status_get: 'status_result',
context_views_get: 'context_views_result',
catalog_sync: 'control_accepted',
context_compact_view: 'control_accepted'
}
if (value.kind !== 'control_error' && value.kind !== expected[request.kind]) {
throw new CodexLinkValidationError('unexpected_control_response')
}
return value
}