@aj-archipelago/cortex
Version:
Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.
220 lines (196 loc) • 8.19 kB
JavaScript
// Check if a value is a JSON Schema object for parameter typing
const isJsonSchemaObject = (value) => {
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
// Basic JSON Schema indicators
return (
typeof value.type === 'string' ||
value.$ref !== undefined ||
value.oneOf !== undefined ||
value.anyOf !== undefined ||
value.allOf !== undefined ||
value.enum !== undefined ||
value.properties !== undefined ||
value.items !== undefined
);
};
// Extract the default value from a JSON Schema object or return the value as-is
const extractValueFromTypeSpec = (value) => {
if (isJsonSchemaObject(value)) {
return value.hasOwnProperty('default') ? value.default : undefined;
}
return value;
};
// Process parameters to convert any type specification objects to their actual values
const processPathwayParameters = (params) => {
if (!params || typeof params !== 'object') {
return params;
}
const processed = {};
for (const [key, value] of Object.entries(params)) {
processed[key] = extractValueFromTypeSpec(value);
}
return processed;
};
const getGraphQlType = (value) => {
// The value might be an object with JSON Schema type specification
if (isJsonSchemaObject(value)) {
const schema = value;
// Map JSON Schema to GraphQL
if (schema.type === 'boolean') {
return { type: 'Boolean', defaultValue: schema.default === undefined ? undefined : schema.default };
}
if (schema.type === 'string') {
return { type: 'String', defaultValue: schema.default === undefined ? undefined : `"${schema.default}"` };
}
if (schema.type === 'integer') {
return { type: 'Int', defaultValue: schema.default };
}
if (schema.type === 'number') {
const def = schema.default;
return { type: 'Float', defaultValue: def };
}
if (schema.type === 'array') {
// Support arrays of primitive types; fall back to JSON string for complex types
const items = schema.items || {};
const def = schema.default;
const defaultArray = Array.isArray(def) ? JSON.stringify(def) : '[]';
// Support explicit object type name (e.g., items: { objType: 'AgentContextInput' })
if (items.objType) {
return { type: `[${items.objType}]`, defaultValue: `"${defaultArray.replace(/"/g, '\\"')}"` };
}
if (items.type === 'string') {
return { type: '[String]', defaultValue: defaultArray };
}
if (items.type === 'integer') {
return { type: '[Int]', defaultValue: defaultArray };
}
if (items.type === 'number') {
return { type: '[Float]', defaultValue: defaultArray };
}
if (items.type === 'boolean') {
return { type: '[Boolean]', defaultValue: defaultArray };
}
// Unknown item type: pass as serialized JSON string argument
return { type: 'String', defaultValue: def === undefined ? '"[]"' : `"${JSON.stringify(def).replace(/"/g, '\\"')}"` };
}
if (schema.type === 'object' || schema.properties) {
// Until explicit input types are defined, accept as stringified JSON
const def = schema.default;
return { type: 'String', defaultValue: def === undefined ? '"{}"' : `"${JSON.stringify(def).replace(/"/g, '\\"')}"` };
}
}
// Otherwise, autodetect the type
switch (typeof value) {
case 'boolean':
return {type: 'Boolean', defaultValue: value};
case 'string':
return {type: 'String', defaultValue: `"${value}"`};
case 'number':
// Check if it's an integer or float
return Number.isInteger(value) ? {type: 'Int', defaultValue: value} : {type: 'Float', defaultValue: value};
case 'object':
// Handle null explicitly (typeof null === 'object' in JavaScript)
if (value === null) {
return {type: 'String', defaultValue: '""'};
}
if (Array.isArray(value)) {
if (value.length > 0 && typeof(value[0]) === 'string') {
return {type: '[String]', defaultValue: JSON.stringify(value)};
}
else {
// Check if it's MultiMessage (content is array) or Message (content is string)
if (Array.isArray(value[0]?.content)) {
return {type: '[MultiMessage]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
}
// Check if it's AgentContextInput (has contextId and default properties)
else if (value[0] && typeof value[0] === 'object' && 'contextId' in value[0] && 'default' in value[0]) {
return {type: '[AgentContextInput]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
}
else {
return {type: '[Message]', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
}
}
} else {
// Check if it has objName property (for custom object types)
if (value && value.objName) {
return {type: `[${value.objName}]`, defaultValue: JSON.stringify(value)};
}
// Otherwise treat as generic object (stringify it)
return {type: 'String', defaultValue: `"${JSON.stringify(value).replace(/"/g, '\\"')}"`};
}
default:
return {type: 'String', defaultValue: `"${value}"`};
}
};
const getMessageTypeDefs = () => {
const messageType = `input Message { role: String, content: String, name: String }`;
const multiMessageType = `input MultiMessage { role: String, content: [String], name: String, tool_calls: [String], tool_call_id: String }`;
const agentContextType = `input AgentContextInput { contextId: String, contextKey: String, default: Boolean }`;
return `${messageType}\n\n${multiMessageType}\n\n${agentContextType}`;
};
const getPathwayTypeDef = (name, returnType) => {
return `type ${name} {
debug: String
result: ${returnType}
resultData: String
previousResult: String
warnings: [String]
errors: [String]
contextId: String
tool: String
}`
};
const buildPathwayTypeDef = (pathway) => {
const { name, objName, defaultInputParameters, inputParameters, format, isMutation = false } = pathway;
const fields = format ? format.match(/\b(\w+)\b/g) : null;
const fieldsStr = !fields ? `` : fields.map((f) => `${f}: String`).join('\n ');
const typeName = fields ? `${objName}Result` : `String`;
const type = fields ? `type ${typeName} {
${fieldsStr}
}` : ``;
const returnType = pathway.list ? `[${typeName}]` : typeName;
const responseType = getPathwayTypeDef(objName, returnType);
const params = { ...defaultInputParameters, ...inputParameters };
const paramsStr = Object.entries(params)
.map(([key, value]) => {
// Handle undefined values - these become optional parameters without defaults
if (value === undefined) {
// For undefined, we can't infer type, so default to String
// Pathways should use JSON Schema objects for better type inference
return `${key}: String`;
}
const { type, defaultValue } = getGraphQlType(value);
// For mutations, never include defaults - make all optional parameters truly optional
// For queries, only omit defaults if defaultValue is undefined (from JSON Schema without defaults)
if (isMutation || defaultValue === undefined) {
return `${key}: ${type}`;
}
return `${key}: ${type} = ${defaultValue}`;
})
.join('\n');
const restDefinition = Object.entries(params).map(([key, value]) => {
return {
name: key,
type: `${getGraphQlType(value).type}`,
};
});
const extendType = isMutation ? 'Mutation' : 'Query';
const gqlDefinition = `${type}\n\n${responseType}\n\nextend type ${extendType} {${name}${paramsStr ? `(${paramsStr})` : ''}: ${objName}}`;
return {
gqlDefinition,
restDefinition,
};
};
const typeDef = (pathway) => {
return buildPathwayTypeDef(pathway);
};
const userPathwayInputParameters = `text: String, promptNames: [String]`;
export {
typeDef,
getMessageTypeDefs,
getPathwayTypeDef,
userPathwayInputParameters,
isJsonSchemaObject,
extractValueFromTypeSpec,
processPathwayParameters,
};