All files / api/src/middlewares variables.ts

97.29% Statements 72/74
94.44% Branches 17/18
0% Functions 0/1
97.29% Lines 72/74

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    1x 1x         1x   1x   1x         1x 1x   20x   13x       13x 13x 13x       13x 4x 4x 4x     4x 2x 2x 2x 2x 4x 4x 4x 4x   9x 13x 1x 1x 13x       13x 6x 6x 6x 6x 6x 2x   13x 13x 13x 13x 13x 13x 13x 1x   1x 1x 1x 1x 1x 1x 1x 1x 7x   7x   7x 7x 7x 7x 7x 7x 7x 7x 13x     1x 1x 1x     13x 13x 13x 20x 1x  
import type { AppContext } from '@api/types/hono';
import type { HttpMethod } from '@api/types/http';
import { SuperAgentsConfigPreProcessed } from '@shared/types/api/request/headers';
import {
  InvalidRequestBodyError,
  isKnownRoute,
  produceSuperAgentsRequestData,
} from '@shared/utils/sa-request-data';
import { parseAgentSkillPath } from '@shared/utils/url';
import type { Next } from 'hono';
import { createMiddleware } from 'hono/factory';
 
import z from 'zod';
 
/**
 * Middleware to set common variables in the context
 */
export const commonVariablesMiddleware = createMiddleware(
  async (c: AppContext, next: Next) => {
    // Only set variables for  API requests
    if (c.req.url.includes('/v1/')) {
      // Don't set variables for Super Agents API requests
      if (!c.req.url.includes('/v1/super-agents')) {
        // The agent and skill can be named in the path
        // (`/v1/agents/:agent_name/skills/:skill_name/chat/completions`) instead
        // of in the `sa-config` header. When they are, the header is optional.
        const { pathname } = new URL(c.req.url);
        const agentSkillScope = parseAgentSkillPath(pathname);
        const method = c.req.method as HttpMethod;
 
        // Answer an unroutable path before asking for credentials or config, so
        // a mistyped URL reads as a mistyped URL.
        if (!isKnownRoute(method, c.req.url)) {
          return c.json(
            {
              error: `No API route matches ${method} ${pathname}`,
              // The scoped form is easy to mistype, so point at it whenever the
              // path looks like an attempt at it.
              ...(pathname.startsWith('/v1/agents/') && !agentSkillScope
                ? {
                    hint: 'Expected /v1/agents/{agent_name}/skills/{skill_name}/{endpoint}',
                  }
                : {}),
            },
            404,
          );
        }
 
        const configString = c.req.header('sa-config');
        if (!configString && !agentSkillScope) {
          return c.json({ error: 'Missing Super Agents config' }, 422);
        }
        const rawConfig = configString ? JSON.parse(configString) : {};
 
        // The path always wins over the header so that a client pointed at a
        // skill's base URL cannot accidentally target another skill.
        const config = agentSkillScope
          ? {
              ...rawConfig,
              agent_name: agentSkillScope.agent_name,
              skill_name: agentSkillScope.skill_name,
            }
          : rawConfig;
 
        const saConfigPreProcessed = SuperAgentsConfigPreProcessed.safeParse(
          config,
          {
            error: (error) => `Invalid Super Agents config as ${error.message}`,
          },
        );
        if (saConfigPreProcessed.error) {
          const prettyError = z.prettifyError(saConfigPreProcessed.error);
 
          return c.json(
            {
              error: `--Invalid Super Agents config--\n ${prettyError}`,
              details: saConfigPreProcessed.error.message,
            },
            422,
          );
        }
        c.set('sa_config_pre_processed', saConfigPreProcessed.data);
 
        const body = await c.req.json();
 
        try {
          const saRequestData = produceSuperAgentsRequestData(
            method,
            c.req.url,
            c.req.header(),
            body,
          );
          c.set('sa_request_data', saRequestData);
        } catch (err) {
          // `isKnownRoute` ignores the body, so a route can still be ruled out
          // here by the request's stream mode or by its schema.
          if (err instanceof InvalidRequestBodyError) {
            return c.json({ error: err.message }, 422);
          }
          throw err;
        }
      }
    }
    await next();
  },
);