1
This commit is contained in:
@@ -10,12 +10,14 @@ import { responseEnvelopeMiddleware } from './middleware/responseEnvelope.js';
|
||||
import { createCharacterAssetRoutes } from './modules/assets/characterAssetRoutes.js';
|
||||
import { createEditorRoutes } from './modules/editor/editorRoutes.js';
|
||||
import { createAuthRoutes } from './routes/authRoutes.js';
|
||||
import { createBigFishProxyRoutes } from './routes/bigFishProxyRoutes.js';
|
||||
import { createCustomWorldAgentRoutes } from './routes/customWorldAgent.js';
|
||||
import { createPuzzleProxyRoutes } from './routes/puzzleProxyRoutes.js';
|
||||
import { createRpgEntrySaveRoutes } from './routes/rpg-entry/rpgEntrySaveRoutes.js';
|
||||
import { createRpgWorldLibraryRoutes } from './routes/rpg-entry/rpgWorldLibraryRoutes.js';
|
||||
import { createRpgProfileRoutes } from './routes/rpg-profile/rpgProfileRoutes.js';
|
||||
import { createRpgRuntimeAiAssistRoutes } from './routes/rpg-runtime/rpgRuntimeAiAssistRoutes.js';
|
||||
import { createRpgRuntimeStoryRoutes } from './routes/rpg-runtime/rpgRuntimeStoryRoutes.js';
|
||||
import { createCustomWorldAgentRoutes } from './routes/customWorldAgent.js';
|
||||
|
||||
function matchesRoutePrefix(
|
||||
request: express.Request,
|
||||
@@ -212,6 +214,16 @@ export function createApp(context: AppContext) {
|
||||
withRouteMeta({ routeVersion: '2026-04-21', operation: 'rpg.creation.agent.api' }),
|
||||
createCustomWorldAgentRoutes(context),
|
||||
);
|
||||
app.use(
|
||||
'/api/runtime/big-fish',
|
||||
withRouteMeta({ routeVersion: '2026-04-22', operation: 'bigFish.runtime.proxy.api' }),
|
||||
createBigFishProxyRoutes(context),
|
||||
);
|
||||
app.use(
|
||||
'/api/runtime/puzzle',
|
||||
withRouteMeta({ routeVersion: '2026-04-22', operation: 'puzzle.runtime.proxy.api' }),
|
||||
createPuzzleProxyRoutes(context),
|
||||
);
|
||||
app.use(
|
||||
express.static(context.config.publicDir, {
|
||||
fallthrough: true,
|
||||
|
||||
329
server-node/src/routes/bigFishProxyRoutes.ts
Normal file
329
server-node/src/routes/bigFishProxyRoutes.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
import { type Request, type Response,Router } from 'express';
|
||||
|
||||
import type { AppContext } from '../context.js';
|
||||
import { badRequest, upstreamError } from '../errors.js';
|
||||
import {
|
||||
API_RESPONSE_ENVELOPE_HEADER,
|
||||
API_VERSION_HEADER,
|
||||
asyncHandler,
|
||||
prepareApiResponse,
|
||||
RESPONSE_TIME_HEADER,
|
||||
ROUTE_VERSION_HEADER,
|
||||
} from '../http.js';
|
||||
import { requireJwtAuth } from '../middleware/auth.js';
|
||||
import { routeMeta } from '../middleware/routeMeta.js';
|
||||
|
||||
const BIG_FISH_ROUTE_VERSION = '2026-04-22';
|
||||
const DEFAULT_RUST_API_TARGET = 'http://127.0.0.1:3100';
|
||||
const DEFAULT_INTERNAL_API_SECRET = 'genarrative-dev-internal-bridge';
|
||||
const INTERNAL_USER_HEADER = 'x-genarrative-authenticated-user-id';
|
||||
const INTERNAL_SECRET_HEADER = 'x-genarrative-internal-api-secret';
|
||||
|
||||
function resolveRustApiTarget(context: AppContext) {
|
||||
const configured =
|
||||
context.config.rawEnv.GENARRATIVE_API_TARGET?.trim() ||
|
||||
context.config.rawEnv.RUST_API_SERVER_TARGET?.trim() ||
|
||||
'';
|
||||
return configured || DEFAULT_RUST_API_TARGET;
|
||||
}
|
||||
|
||||
function resolveInternalApiSecret(context: AppContext) {
|
||||
return (
|
||||
context.config.rawEnv.GENARRATIVE_INTERNAL_API_SECRET?.trim() ||
|
||||
DEFAULT_INTERNAL_API_SECRET
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeRouteSuffix(path: string) {
|
||||
const normalized = path.startsWith('/') ? path : `/${path}`;
|
||||
return normalized.replace(/\/+$/u, '');
|
||||
}
|
||||
|
||||
function buildUpstreamUrl(context: AppContext, pathSuffix: string) {
|
||||
const baseUrl = resolveRustApiTarget(context).replace(/\/+$/u, '');
|
||||
return `${baseUrl}${normalizeRouteSuffix(pathSuffix)}`;
|
||||
}
|
||||
|
||||
function pickForwardHeaders(
|
||||
request: Request,
|
||||
context: AppContext,
|
||||
userId: string,
|
||||
) {
|
||||
const forwardedHeaders = new Headers();
|
||||
|
||||
const contentType = request.header('content-type')?.trim();
|
||||
if (contentType) {
|
||||
forwardedHeaders.set('content-type', contentType);
|
||||
}
|
||||
|
||||
const accept = request.header('accept')?.trim();
|
||||
if (accept) {
|
||||
forwardedHeaders.set('accept', accept);
|
||||
}
|
||||
|
||||
const requestId = request.requestId?.trim();
|
||||
if (requestId) {
|
||||
forwardedHeaders.set('x-request-id', requestId);
|
||||
}
|
||||
|
||||
const envelope = request.header(API_RESPONSE_ENVELOPE_HEADER)?.trim();
|
||||
if (envelope) {
|
||||
forwardedHeaders.set(API_RESPONSE_ENVELOPE_HEADER, envelope);
|
||||
}
|
||||
|
||||
forwardedHeaders.set(INTERNAL_USER_HEADER, userId);
|
||||
const internalSecret = resolveInternalApiSecret(context);
|
||||
if (internalSecret) {
|
||||
forwardedHeaders.set(INTERNAL_SECRET_HEADER, internalSecret);
|
||||
}
|
||||
return forwardedHeaders;
|
||||
}
|
||||
|
||||
function readBodyAllowed(method: string) {
|
||||
return !['GET', 'HEAD'].includes(method.toUpperCase());
|
||||
}
|
||||
|
||||
async function proxyBigFishRequest(params: {
|
||||
context: AppContext;
|
||||
request: Request;
|
||||
response: Response;
|
||||
pathSuffix: string;
|
||||
streamBody?: boolean;
|
||||
}) {
|
||||
const { context, request, response, pathSuffix, streamBody = false } = params;
|
||||
const userId = request.userId?.trim();
|
||||
if (!userId) {
|
||||
throw badRequest('缺少已认证用户上下文');
|
||||
}
|
||||
|
||||
const upstreamUrl = buildUpstreamUrl(context, pathSuffix);
|
||||
const method = request.method.toUpperCase();
|
||||
const body =
|
||||
readBodyAllowed(method) && request.body !== undefined
|
||||
? JSON.stringify(request.body)
|
||||
: undefined;
|
||||
|
||||
let upstreamResponse: globalThis.Response;
|
||||
try {
|
||||
upstreamResponse = await fetch(upstreamUrl, {
|
||||
method,
|
||||
// 这里显式转发“已通过 Node 校验的用户身份”,让 Big Fish 继续由 Rust 真相后端处理。
|
||||
headers: pickForwardHeaders(request, context, userId),
|
||||
body,
|
||||
});
|
||||
} catch (error) {
|
||||
request.log?.error(
|
||||
{
|
||||
err: error,
|
||||
user_id: userId,
|
||||
upstream_url: upstreamUrl,
|
||||
},
|
||||
'big fish upstream request failed',
|
||||
);
|
||||
throw upstreamError('大鱼吃小鱼后端暂时不可用');
|
||||
}
|
||||
|
||||
prepareApiResponse(request, response, {
|
||||
statusCode: upstreamResponse.status,
|
||||
headers: {
|
||||
'Content-Type':
|
||||
upstreamResponse.headers.get('content-type') ||
|
||||
'application/json; charset=utf-8',
|
||||
'Cache-Control':
|
||||
upstreamResponse.headers.get('cache-control') || 'no-cache',
|
||||
},
|
||||
routeMeta: {
|
||||
routeVersion: BIG_FISH_ROUTE_VERSION,
|
||||
},
|
||||
});
|
||||
|
||||
const upstreamRequestId = upstreamResponse.headers.get('x-request-id');
|
||||
if (upstreamRequestId) {
|
||||
response.setHeader('x-upstream-request-id', upstreamRequestId);
|
||||
}
|
||||
|
||||
const upstreamRouteVersion = upstreamResponse.headers.get(ROUTE_VERSION_HEADER);
|
||||
if (upstreamRouteVersion) {
|
||||
response.setHeader('x-upstream-route-version', upstreamRouteVersion);
|
||||
}
|
||||
|
||||
const upstreamApiVersion = upstreamResponse.headers.get(API_VERSION_HEADER);
|
||||
if (upstreamApiVersion) {
|
||||
response.setHeader('x-upstream-api-version', upstreamApiVersion);
|
||||
}
|
||||
|
||||
const upstreamLatency = upstreamResponse.headers.get(RESPONSE_TIME_HEADER);
|
||||
if (upstreamLatency) {
|
||||
response.setHeader('x-upstream-response-time-ms', upstreamLatency);
|
||||
}
|
||||
|
||||
if (streamBody) {
|
||||
if (!upstreamResponse.body) {
|
||||
throw upstreamError('大鱼吃小鱼流式响应不可用');
|
||||
}
|
||||
|
||||
response.flushHeaders?.();
|
||||
await Readable.fromWeb(upstreamResponse.body as never).pipe(response);
|
||||
return;
|
||||
}
|
||||
|
||||
response.end(await upstreamResponse.text());
|
||||
}
|
||||
|
||||
function readParam(value: string | string[] | undefined) {
|
||||
return Array.isArray(value) ? value[0]?.trim() || '' : value?.trim() || '';
|
||||
}
|
||||
|
||||
export function createBigFishProxyRoutes(context: AppContext) {
|
||||
const router = Router();
|
||||
const requireAuth = requireJwtAuth(context.config, context.userRepository);
|
||||
|
||||
router.use(requireAuth);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions',
|
||||
routeMeta({ operation: 'runtime.bigFish.createSession', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: '/api/runtime/big-fish/agent/sessions',
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/agent/sessions/:sessionId',
|
||||
routeMeta({ operation: 'runtime.bigFish.getSession', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/agent/sessions/${encodeURIComponent(sessionId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions/:sessionId/messages',
|
||||
routeMeta({ operation: 'runtime.bigFish.sendMessage', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/agent/sessions/${encodeURIComponent(sessionId)}/messages`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions/:sessionId/messages/stream',
|
||||
routeMeta({
|
||||
operation: 'runtime.bigFish.streamMessage',
|
||||
routeVersion: BIG_FISH_ROUTE_VERSION,
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/agent/sessions/${encodeURIComponent(sessionId)}/messages/stream`,
|
||||
streamBody: true,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions/:sessionId/actions',
|
||||
routeMeta({ operation: 'runtime.bigFish.executeAction', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/agent/sessions/${encodeURIComponent(sessionId)}/actions`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/sessions/:sessionId/runs',
|
||||
routeMeta({ operation: 'runtime.bigFish.startRun', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/sessions/${encodeURIComponent(sessionId)}/runs`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/runs/:runId',
|
||||
routeMeta({ operation: 'runtime.bigFish.getRun', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const runId = readParam(request.params.runId);
|
||||
if (!runId) {
|
||||
throw badRequest('runId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/runs/${encodeURIComponent(runId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runs/:runId/input',
|
||||
routeMeta({ operation: 'runtime.bigFish.submitInput', routeVersion: BIG_FISH_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const runId = readParam(request.params.runId);
|
||||
if (!runId) {
|
||||
throw badRequest('runId is required');
|
||||
}
|
||||
|
||||
await proxyBigFishRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/big-fish/runs/${encodeURIComponent(runId)}/input`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
439
server-node/src/routes/puzzleProxyRoutes.ts
Normal file
439
server-node/src/routes/puzzleProxyRoutes.ts
Normal file
@@ -0,0 +1,439 @@
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
import { type Request, type Response, Router } from 'express';
|
||||
|
||||
import type { AppContext } from '../context.js';
|
||||
import { badRequest, upstreamError } from '../errors.js';
|
||||
import {
|
||||
API_RESPONSE_ENVELOPE_HEADER,
|
||||
API_VERSION_HEADER,
|
||||
asyncHandler,
|
||||
prepareApiResponse,
|
||||
RESPONSE_TIME_HEADER,
|
||||
ROUTE_VERSION_HEADER,
|
||||
} from '../http.js';
|
||||
import { requireJwtAuth } from '../middleware/auth.js';
|
||||
import { routeMeta } from '../middleware/routeMeta.js';
|
||||
|
||||
const PUZZLE_ROUTE_VERSION = '2026-04-22';
|
||||
const DEFAULT_RUST_API_TARGET = 'http://127.0.0.1:3100';
|
||||
const DEFAULT_INTERNAL_API_SECRET = 'genarrative-dev-internal-bridge';
|
||||
const INTERNAL_USER_HEADER = 'x-genarrative-authenticated-user-id';
|
||||
const INTERNAL_SECRET_HEADER = 'x-genarrative-internal-api-secret';
|
||||
|
||||
function resolveRustApiTarget(context: AppContext) {
|
||||
const configured =
|
||||
context.config.rawEnv.GENARRATIVE_API_TARGET?.trim() ||
|
||||
context.config.rawEnv.RUST_API_SERVER_TARGET?.trim() ||
|
||||
'';
|
||||
return configured || DEFAULT_RUST_API_TARGET;
|
||||
}
|
||||
|
||||
function resolveInternalApiSecret(context: AppContext) {
|
||||
return (
|
||||
context.config.rawEnv.GENARRATIVE_INTERNAL_API_SECRET?.trim() ||
|
||||
DEFAULT_INTERNAL_API_SECRET
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeRouteSuffix(path: string) {
|
||||
const normalized = path.startsWith('/') ? path : `/${path}`;
|
||||
return normalized.replace(/\/+$/u, '');
|
||||
}
|
||||
|
||||
function buildUpstreamUrl(context: AppContext, pathSuffix: string) {
|
||||
const baseUrl = resolveRustApiTarget(context).replace(/\/+$/u, '');
|
||||
return `${baseUrl}${normalizeRouteSuffix(pathSuffix)}`;
|
||||
}
|
||||
|
||||
function pickForwardHeaders(
|
||||
request: Request,
|
||||
context: AppContext,
|
||||
userId: string,
|
||||
) {
|
||||
const forwardedHeaders = new Headers();
|
||||
|
||||
const contentType = request.header('content-type')?.trim();
|
||||
if (contentType) {
|
||||
forwardedHeaders.set('content-type', contentType);
|
||||
}
|
||||
|
||||
const accept = request.header('accept')?.trim();
|
||||
if (accept) {
|
||||
forwardedHeaders.set('accept', accept);
|
||||
}
|
||||
|
||||
const requestId = request.requestId?.trim();
|
||||
if (requestId) {
|
||||
forwardedHeaders.set('x-request-id', requestId);
|
||||
}
|
||||
|
||||
const envelope = request.header(API_RESPONSE_ENVELOPE_HEADER)?.trim();
|
||||
if (envelope) {
|
||||
forwardedHeaders.set(API_RESPONSE_ENVELOPE_HEADER, envelope);
|
||||
}
|
||||
|
||||
forwardedHeaders.set(INTERNAL_USER_HEADER, userId);
|
||||
const internalSecret = resolveInternalApiSecret(context);
|
||||
if (internalSecret) {
|
||||
forwardedHeaders.set(INTERNAL_SECRET_HEADER, internalSecret);
|
||||
}
|
||||
return forwardedHeaders;
|
||||
}
|
||||
|
||||
function readBodyAllowed(method: string) {
|
||||
return !['GET', 'HEAD'].includes(method.toUpperCase());
|
||||
}
|
||||
|
||||
async function proxyPuzzleRequest(params: {
|
||||
context: AppContext;
|
||||
request: Request;
|
||||
response: Response;
|
||||
pathSuffix: string;
|
||||
streamBody?: boolean;
|
||||
}) {
|
||||
const { context, request, response, pathSuffix, streamBody = false } = params;
|
||||
const userId = request.userId?.trim();
|
||||
if (!userId) {
|
||||
throw badRequest('缺少已认证用户上下文');
|
||||
}
|
||||
|
||||
const upstreamUrl = buildUpstreamUrl(context, pathSuffix);
|
||||
const method = request.method.toUpperCase();
|
||||
const body =
|
||||
readBodyAllowed(method) && request.body !== undefined
|
||||
? JSON.stringify(request.body)
|
||||
: undefined;
|
||||
|
||||
let upstreamResponse: globalThis.Response;
|
||||
try {
|
||||
upstreamResponse = await fetch(upstreamUrl, {
|
||||
method,
|
||||
headers: pickForwardHeaders(request, context, userId),
|
||||
body,
|
||||
});
|
||||
} catch (error) {
|
||||
request.log?.error(
|
||||
{
|
||||
err: error,
|
||||
user_id: userId,
|
||||
upstream_url: upstreamUrl,
|
||||
},
|
||||
'puzzle upstream request failed',
|
||||
);
|
||||
throw upstreamError('拼图后端暂时不可用');
|
||||
}
|
||||
|
||||
prepareApiResponse(request, response, {
|
||||
statusCode: upstreamResponse.status,
|
||||
headers: {
|
||||
'Content-Type':
|
||||
upstreamResponse.headers.get('content-type') ||
|
||||
'application/json; charset=utf-8',
|
||||
'Cache-Control':
|
||||
upstreamResponse.headers.get('cache-control') || 'no-cache',
|
||||
},
|
||||
routeMeta: {
|
||||
routeVersion: PUZZLE_ROUTE_VERSION,
|
||||
},
|
||||
});
|
||||
|
||||
const upstreamRequestId = upstreamResponse.headers.get('x-request-id');
|
||||
if (upstreamRequestId) {
|
||||
response.setHeader('x-upstream-request-id', upstreamRequestId);
|
||||
}
|
||||
|
||||
const upstreamRouteVersion = upstreamResponse.headers.get(ROUTE_VERSION_HEADER);
|
||||
if (upstreamRouteVersion) {
|
||||
response.setHeader('x-upstream-route-version', upstreamRouteVersion);
|
||||
}
|
||||
|
||||
const upstreamApiVersion = upstreamResponse.headers.get(API_VERSION_HEADER);
|
||||
if (upstreamApiVersion) {
|
||||
response.setHeader('x-upstream-api-version', upstreamApiVersion);
|
||||
}
|
||||
|
||||
const upstreamLatency = upstreamResponse.headers.get(RESPONSE_TIME_HEADER);
|
||||
if (upstreamLatency) {
|
||||
response.setHeader('x-upstream-response-time-ms', upstreamLatency);
|
||||
}
|
||||
|
||||
if (streamBody) {
|
||||
if (!upstreamResponse.body) {
|
||||
throw upstreamError('拼图流式响应不可用');
|
||||
}
|
||||
|
||||
response.flushHeaders?.();
|
||||
await Readable.fromWeb(upstreamResponse.body as never).pipe(response);
|
||||
return;
|
||||
}
|
||||
|
||||
response.end(await upstreamResponse.text());
|
||||
}
|
||||
|
||||
function readParam(value: string | string[] | undefined) {
|
||||
return Array.isArray(value) ? value[0]?.trim() || '' : value?.trim() || '';
|
||||
}
|
||||
|
||||
export function createPuzzleProxyRoutes(context: AppContext) {
|
||||
const router = Router();
|
||||
const requireAuth = requireJwtAuth(context.config, context.userRepository);
|
||||
|
||||
router.use(requireAuth);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions',
|
||||
routeMeta({ operation: 'runtime.puzzle.createSession', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: '/api/runtime/puzzle/agent/sessions',
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/agent/sessions/:sessionId',
|
||||
routeMeta({ operation: 'runtime.puzzle.getSession', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/agent/sessions/${encodeURIComponent(sessionId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions/:sessionId/messages',
|
||||
routeMeta({ operation: 'runtime.puzzle.sendMessage', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/agent/sessions/${encodeURIComponent(sessionId)}/messages`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions/:sessionId/messages/stream',
|
||||
routeMeta({
|
||||
operation: 'runtime.puzzle.streamMessage',
|
||||
routeVersion: PUZZLE_ROUTE_VERSION,
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/agent/sessions/${encodeURIComponent(sessionId)}/messages/stream`,
|
||||
streamBody: true,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/agent/sessions/:sessionId/actions',
|
||||
routeMeta({ operation: 'runtime.puzzle.executeAction', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const sessionId = readParam(request.params.sessionId);
|
||||
if (!sessionId) {
|
||||
throw badRequest('sessionId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/agent/sessions/${encodeURIComponent(sessionId)}/actions`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/works',
|
||||
routeMeta({ operation: 'runtime.puzzle.listWorks', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: '/api/runtime/puzzle/works',
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/works/:profileId',
|
||||
routeMeta({ operation: 'runtime.puzzle.getWorkDetail', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const profileId = readParam(request.params.profileId);
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/works/${encodeURIComponent(profileId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/works/:profileId',
|
||||
routeMeta({ operation: 'runtime.puzzle.updateWork', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const profileId = readParam(request.params.profileId);
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/works/${encodeURIComponent(profileId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/gallery',
|
||||
routeMeta({ operation: 'runtime.puzzle.listGallery', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: '/api/runtime/puzzle/gallery',
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/gallery/:profileId',
|
||||
routeMeta({ operation: 'runtime.puzzle.getGalleryDetail', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const profileId = readParam(request.params.profileId);
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/gallery/${encodeURIComponent(profileId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runs',
|
||||
routeMeta({ operation: 'runtime.puzzle.startRun', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: '/api/runtime/puzzle/runs',
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/runs/:runId',
|
||||
routeMeta({ operation: 'runtime.puzzle.getRun', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const runId = readParam(request.params.runId);
|
||||
if (!runId) {
|
||||
throw badRequest('runId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/runs/${encodeURIComponent(runId)}`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runs/:runId/swap',
|
||||
routeMeta({ operation: 'runtime.puzzle.swapPieces', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const runId = readParam(request.params.runId);
|
||||
if (!runId) {
|
||||
throw badRequest('runId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/runs/${encodeURIComponent(runId)}/swap`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runs/:runId/drag',
|
||||
routeMeta({ operation: 'runtime.puzzle.dragPiece', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const runId = readParam(request.params.runId);
|
||||
if (!runId) {
|
||||
throw badRequest('runId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/runs/${encodeURIComponent(runId)}/drag`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runs/:runId/next-level',
|
||||
routeMeta({ operation: 'runtime.puzzle.nextLevel', routeVersion: PUZZLE_ROUTE_VERSION }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const runId = readParam(request.params.runId);
|
||||
if (!runId) {
|
||||
throw badRequest('runId is required');
|
||||
}
|
||||
|
||||
await proxyPuzzleRequest({
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
pathSuffix: `/api/runtime/puzzle/runs/${encodeURIComponent(runId)}/next-level`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
Reference in New Issue
Block a user