Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ dist/
# turbo
.turbo

*.local.*
*.local.*
*.local

# local-only internal material — never commit
# (keep such files in *.local paths so the patterns above catch them)
internal-docs/
internal-docs.local/
.opencode/
1 change: 0 additions & 1 deletion .opencode/skills/baseline-ui

This file was deleted.

1 change: 0 additions & 1 deletion .opencode/skills/fixing-accessibility

This file was deleted.

1 change: 0 additions & 1 deletion .opencode/skills/fixing-metadata

This file was deleted.

1 change: 0 additions & 1 deletion .opencode/skills/react-email

This file was deleted.

1 change: 0 additions & 1 deletion .opencode/skills/trigger-dev-tasks

This file was deleted.

1 change: 0 additions & 1 deletion .opencode/skills/vercel-react-best-practices

This file was deleted.

13 changes: 8 additions & 5 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@
"@calcom/embed-react": "^1.5.3",
"@dnd-kit/sortable": "^10.0.0",
"@hookform/resolvers": "^5.2.2",
"@modelcontextprotocol/sdk": "1.26.0",
"@number-flow/react": "^0.5.10",
"@orpc/client": "1.14.6",
"@orpc/json-schema": "1.14.6",
"@orpc/openapi": "1.14.6",
"@orpc/server": "1.14.6",
"@orpc/tanstack-query": "1.14.6",
"@orpc/zod": "1.14.6",
"@t3-oss/env-nextjs": "catalog:",
"@tanstack/react-query": "catalog:",
"@trpc/client": "catalog:",
"@trpc/server": "catalog:",
"@trpc/tanstack-react-query": "catalog:",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.34.9",
"@vercel/functions": "^3.3.6",
Expand All @@ -52,6 +56,7 @@
"fast-deep-equal": "^3.1.3",
"file-type": "^21.3.0",
"jiti": "^2.6.1",
"mcp-handler": "^1.1.0",
"motion": "^12.26.2",
"next": "^16.1.6",
"posthog-js": "^1.326.0",
Expand All @@ -61,14 +66,12 @@
"resend": "^6.7.0",
"server-only": "^0.0.1",
"shadcn": "^3.8.4",
"superjson": "^2.2.6",
"tw-animate-css": "^1.4.0",
"ua-parser-js": "^2.0.8",
"usehooks-ts": "^3.1.1",
"virtua": "^0.48.3",
"zod": "catalog:",
"zod-error": "catalog:",
"zod-openapi": "catalog:",
"zustand": "^5.0.10"
},
"devDependencies": {
Expand Down
72 changes: 72 additions & 0 deletions apps/web/src/app/api/(internal-api)/rpc/[[...rest]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ApiContext } from "@/server/orpc/base";
import { auth } from "@/lib/auth";
import { toDashboardORPCError } from "@/server/orpc/base";
import { appRouter } from "@/server/orpc/router";
import { onError } from "@orpc/server";
import { RPCHandler } from "@orpc/server/fetch";
import {
BatchHandlerPlugin,
RequestHeadersPlugin,
} from "@orpc/server/plugins";

const handler = new RPCHandler<ApiContext>(appRouter, {
plugins: [
new BatchHandlerPlugin(),
// injects per-item headers as context.reqHeaders AFTER batch splitting,
// so each batched call authenticates with its own x-organization-id
new RequestHeadersPlugin(),
],
clientInterceptors: [
// port of the old timingMiddleware: timing log + AgentsetApiError → typed
// transport error (dashboard mutations surface 4xx instead of 500)
async (options) => {
const start = Date.now();

try {
const result = await options.next();

const end = Date.now();
console.log(
`[RPC] ${options.path.join(".")} took ${end - start}ms to execute`,
);

return result;
} catch (error) {
throw toDashboardORPCError(error);
}
},
],
interceptors: [
onError((error) => {
console.error(error);
}),
],
});

async function handleRequest(request: Request) {
// resolve the session once per HTTP request (parity with createTRPCContext),
// shared across batched procedure calls
const session = await auth.api.getSession({ headers: request.headers });

const context: ApiContext = {
resHeaders: {},
analytics: {},
session,
};

const { response } = await handler.handle(request, {
prefix: "/api/rpc",
context,
});

return response ?? new Response("Not found", { status: 404 });
}

export {
handleRequest as GET,
handleRequest as POST,
handleRequest as PUT,
handleRequest as PATCH,
handleRequest as DELETE,
handleRequest as HEAD,
};
33 changes: 0 additions & 33 deletions apps/web/src/app/api/(internal-api)/trpc/[trpc]/route.ts

This file was deleted.

31 changes: 31 additions & 0 deletions apps/web/src/app/api/(public-api)/[transport]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
MCP_SERVER_INFO,
MCP_SERVER_INSTRUCTIONS,
registerTools,
} from "@/lib/mcp";
import { verifyMcpToken } from "@/lib/mcp/auth";
import { createMcpHandler, withMcpAuth } from "mcp-handler";

export const preferredRegion = "iad1"; // make this closer to the DB
export const maxDuration = 120;

const handler = createMcpHandler(
(server) => {
registerTools(server);
},
{
serverInfo: MCP_SERVER_INFO,
instructions: MCP_SERVER_INSTRUCTIONS,
},
{
basePath: "/api",
disableSse: true,
maxDuration: 120,
verboseLogs: false,
},
);

// authenticated with an org API key: Authorization: Bearer agentset_xxx
const authHandler = withMcpAuth(handler, verifyMcpToken, { required: true });

export { authHandler as GET, authHandler as POST, authHandler as DELETE };
96 changes: 96 additions & 0 deletions apps/web/src/app/api/(public-api)/v1/[...rest]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { ApiContext } from "@/server/orpc/base";
import type { NextRequest } from "next/server";
import {
flushServerEvents,
identifyOrganization,
logServerEvent,
} from "@/lib/analytics-server";
import {
legacyErrorORPCError,
legacyErrorResponseBodyEncoder,
} from "@/server/orpc/base";
import { appRouter } from "@/server/orpc/router";
import { SmartCoercionPlugin } from "@orpc/json-schema";
import { OpenAPIHandler } from "@orpc/openapi/fetch";
import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";

export const preferredRegion = "iad1"; // make this closer to the DB
export const maxDuration = 120;

const handler = new OpenAPIHandler<ApiContext>(appRouter, {
// dashboard-only procedures carry no route metadata — keep them off the
// public REST surface (the hidden PUT aliases have a path, so they stay)
filter: ({ contract }) => !!contract["~orpc"].route.path,
plugins: [
new SmartCoercionPlugin({
schemaConverters: [new ZodToJsonSchemaConverter()],
}),
],
clientInterceptors: [
async (options) => {
try {
return await options.next();
} catch (error) {
console.error(error);
throw legacyErrorORPCError(error);
}
},
],
customErrorResponseBodyEncoder: legacyErrorResponseBodyEncoder,
});

const notFoundResponse = () =>
Response.json(
{
success: false,
error: {
code: "not_found",
message: "The requested endpoint does not exist.",
doc_url: "https://docs.agentset.ai/api-reference",
},
},
{ status: 404 },
);

async function handleRequest(request: NextRequest) {
const context: ApiContext = {
reqHeaders: request.headers,
resHeaders: {},
analytics: {},
};

const { response } = await handler.handle(request, {
prefix: "/api/v1",
context,
});

if (!response) return notFoundResponse();

for (const [key, value] of Object.entries(context.resHeaders)) {
response.headers.set(key, value);
}

// parity with the legacy wrappers: successful requests are logged, errors
// (thrown before the old logging call) are not
const { organization, tenantId, namespaceId, routeName } = context.analytics;
if (organization && routeName && response.status < 400) {
identifyOrganization(organization);
logServerEvent(
"api_request",
{ request, routeName, response, organization },
{ tenantId, ...(namespaceId ? { namespaceId } : {}) },
);
flushServerEvents();
}

return response;
}

export {
handleRequest as GET,
handleRequest as POST,
handleRequest as PUT,
handleRequest as PATCH,
handleRequest as DELETE,
handleRequest as HEAD,
};

This file was deleted.

Loading