All files / shared/src/utils sa-request-data.ts

92.7% Statements 89/96
92% Branches 23/25
100% Functions 5/5
92.7% Lines 89/96

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 1461x 1x           1x           1x 1x 1x 1x 1x 1x           1x 1x 2x 2x 2x 1x               88x     88x 88x 88x   88x 70x 70x   17x 17x 17x                 1x 19x   19x 19x 19x 19x   1x 69x 69x 69x 69x 69x 69x 69x   69x       68x 69x 4x 4x     69x 629x 629x 629x   629x 67x   67x 67x 67x 67x 2x 2x 2x 2x 65x   65x 67x 2x 2x 2x               2x 2x 2x 2x 2x   65x 65x 65x 65x 65x 65x 65x 65x 65x 65x 65x 65x   65x 65x 65x   65x 65x 629x   1x 1x  
import { functionConfigs } from '@shared/types/api/request';
import {
  type SuperAgentsRequestBody,
  SuperAgentsRequestData,
} from '@shared/types/api/request/body';
import type { SuperAgentsResponseBody } from '@shared/types/api/response';
import type { HttpMethod } from '@shared/types/http';
import { parseAgentSkillPath } from '@shared/utils/url';
 
/**
 * Thrown when no known API route matches the request's method, path and stream
 * mode. Callers should surface this as a 404 rather than a server error.
 */
export class UnknownRouteError extends Error {
  constructor(method: HttpMethod, pathname: string) {
    super(`Unknown method: ${method} for pathname: ${pathname}`);
    this.name = 'UnknownRouteError';
  }
}
 
/**
 * Thrown when the request body does not match the schema of the matched route.
 * Callers should surface this as a 422 rather than a server error.
 */
export class InvalidRequestBodyError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'InvalidRequestBodyError';
  }
}
 
/**
 * Requests may name the agent and skill in the path
 * (`/v1/agents/:agent_name/skills/:skill_name/chat/completions`). Route matching
 * happens on the canonical path so both request styles resolve to the same
 * function.
 */
function canonicalizeUrl(urlString: string): {
  pathname: string;
  url: string;
} {
  const url = new URL(urlString);
  const agentSkillScope = parseAgentSkillPath(url.pathname);
 
  if (!agentSkillScope) {
    return { pathname: url.pathname, url: urlString };
  }
 
  url.pathname = agentSkillScope.pathname;
  return { pathname: url.pathname, url: url.toString() };
}
 
/**
 * Whether any known API route serves this method and path.
 *
 * This ignores the request body, so it can be checked before the body is read.
 * A route that matches here can still be rejected by
 * `produceSuperAgentsRequestData` when the body does not fit its schema.
 */
export function isKnownRoute(method: HttpMethod, urlString: string): boolean {
  const { pathname } = canonicalizeUrl(urlString);
 
  return functionConfigs.some(
    (config) => config.method === method && config.route_pattern.test(pathname),
  );
}
 
export function produceSuperAgentsRequestData(
  method: HttpMethod,
  urlString: string,
  requestHeaders: Record<string, string>,
  rawRequestBody: Record<string, unknown>,
  rawResponseBody?: Record<string, unknown> | null,
): SuperAgentsRequestData {
  const { pathname, url: canonicalUrl } = canonicalizeUrl(urlString);
 
  if (!pathname) {
    throw new Error('No pathname found in URL');
  }
 
  let stream = false;
  if ('stream' in rawRequestBody && rawRequestBody.stream === true) {
    stream = true;
  }
 
  // Find matching route pattern
  for (const config of functionConfigs) {
    const patternMatches = config.route_pattern.test(pathname);
    const methodMatches = config.method === method;
    const streamMatches = (config.stream ?? false) === stream;
 
    if (patternMatches && methodMatches && streamMatches) {
      const functionName = config.functionName;
 
      let requestBody = rawRequestBody;
      const requestSchemaSafeParseResult =
        config.requestSchema.safeParse(rawRequestBody);
      if (!requestSchemaSafeParseResult.success) {
        throw new InvalidRequestBodyError(
          `Invalid request body: ${requestSchemaSafeParseResult.error}`,
        );
      }
      requestBody = requestSchemaSafeParseResult.data as SuperAgentsRequestBody;
 
      let responseBody: SuperAgentsResponseBody | undefined;
      if (rawResponseBody) {
        const responseSchemaSafeParseResult =
          config.responseSchema.safeParse(rawResponseBody);
        if (!responseSchemaSafeParseResult.success) {
          // For logs, the response may have been modified during accumulation
          // Use the raw response without validation instead of throwing
          console.warn(
            `Response body validation failed for ${functionName}, using raw response:`,
            responseSchemaSafeParseResult.error,
          );
          responseBody = rawResponseBody as SuperAgentsResponseBody;
        } else {
          responseBody =
            responseSchemaSafeParseResult.data as SuperAgentsResponseBody;
        }
      }
 
      const rawSuperAgentsRequestData = {
        route_pattern: config.route_pattern,
        method: config.method,
        url: canonicalUrl,
        functionName,
        requestHeaders,
        requestBody,
        responseBody,
        requestSchema: config.requestSchema,
        responseSchema: config.responseSchema,
        stream: config.stream,
      };
 
      const saRequestData = SuperAgentsRequestData.parse(
        rawSuperAgentsRequestData,
      );
 
      return saRequestData;
    }
  }
 
  throw new UnknownRouteError(method, pathname);
}