@copilotkit/runtime
Version:
<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />
204 lines (202 loc) • 8.62 kB
JavaScript
import "reflect-metadata";
import { isIntelligenceRuntime } from "../../core/runtime.mjs";
import { PlatformRequestError } from "../../intelligence-platform/client.mjs";
import { errorResponse, isHandlerResponse } from "../shared/json-response.mjs";
import { resolveIntelligenceUser } from "../shared/resolve-intelligence-user.mjs";
import { logger } from "@copilotkit/shared";
//#region src/v2/runtime/handlers/intelligence/memories.ts
const MISSING_INTELLIGENCE_MESSAGE = "Missing CopilotKitIntelligence configuration. Memory operations require a CopilotKitIntelligence instance to be provided in CopilotRuntime options.";
/** Allowed `kind` vocabulary the platform's memory endpoints accept. */
const MEMORY_KINDS = new Set([
"topical",
"episodic",
"operational"
]);
/** Allowed `scope` vocabulary the platform's memory endpoints accept. */
const MEMORY_SCOPES = new Set(["user", "project"]);
/**
* Maps a thrown error to a `Response`.
*
* For a {@link PlatformRequestError}, forward only client-actionable **4xx**
* statuses verbatim (e.g. 404 missing/wrong-scope memory, 409 conflict, 422
* unprocessable) so a `useMemories` consumer can branch on them — a flat 500
* would erase that distinction. A platform **5xx** (or any non-4xx / malformed
* status) means the runtime is healthy but its dependency failed, so it surfaces
* as `502 Bad Gateway` rather than echoing the upstream status as if the runtime
* itself broke — and this also avoids a `new Response(..., { status })`
* `RangeError` on an out-of-range status. Non-platform throws stay 500.
*/
function memoryErrorResponse(error, message) {
if (error instanceof PlatformRequestError) {
const { status } = error;
if (Number.isInteger(status) && status >= 400 && status <= 499) return errorResponse(message, status);
return errorResponse(message, 502);
}
return errorResponse(message, 500);
}
async function parseJsonBody(request) {
try {
return await request.json();
} catch (error) {
logger.error({ err: error }, "Malformed JSON in memory request body");
return errorResponse("Invalid request body", 400);
}
}
/**
* Extracts and validates the create/supersede body fields the platform's
* memory endpoints require. Returns a `Response` (400) on invalid input.
*/
function parseMemoryBody(body) {
const { content, kind, scope, sourceThreadIds } = body;
if (typeof content !== "string" || typeof kind !== "string") return errorResponse("Memory requires string `content` and `kind`", 400);
if (!MEMORY_KINDS.has(kind)) return errorResponse("Memory `kind` must be one of: topical, episodic, operational", 400);
if (scope !== void 0 && typeof scope !== "string") return errorResponse("Memory `scope` must be a string when provided", 400);
if (typeof scope === "string" && !MEMORY_SCOPES.has(scope)) return errorResponse("Memory `scope` must be one of: user, project", 400);
if (sourceThreadIds !== void 0 && (!Array.isArray(sourceThreadIds) || !sourceThreadIds.every((id) => typeof id === "string"))) return errorResponse("Memory `sourceThreadIds` must be an array of strings when provided", 400);
return {
content,
kind,
...typeof scope === "string" ? { scope } : {},
...Array.isArray(sourceThreadIds) ? { sourceThreadIds } : {}
};
}
/**
* Lists the resolved user's long-term memories via the Intelligence platform.
*
* Mirrors {@link handleListThreads}: requires a `CopilotKitIntelligence`
* runtime, resolves the user with `identifyUser` (never trusting a
* client-supplied id), and proxies to the platform's `GET /api/memories`
* with the project API key + resolved user. The `?includeInvalidated=true`
* query is forwarded so callers can opt into retired rows. The response is
* the platform's `{ memories }` envelope, which the client memory store
* consumes directly.
*/
async function handleListMemories({ runtime, request }) {
if (isIntelligenceRuntime(runtime)) try {
const includeInvalidated = new URL(request.url).searchParams.get("includeInvalidated") === "true";
const user = await resolveIntelligenceUser({
runtime,
request
});
if (isHandlerResponse(user)) return user;
const data = await runtime.intelligence.listMemories({
userId: user.id,
...includeInvalidated ? { includeInvalidated: true } : {}
});
if (data == null || typeof data !== "object" || !Array.isArray(data.memories)) {
logger.error({ data }, "listMemories: platform returned a response without a `memories` array");
return errorResponse("Memory platform returned an invalid list response", 502);
}
return Response.json(data);
} catch (error) {
logger.error({ err: error }, "Error listing memories");
return memoryErrorResponse(error, "Failed to list memories");
}
return errorResponse(MISSING_INTELLIGENCE_MESSAGE, 422);
}
/**
* Mints memory-realtime join credentials (platform `POST
* /api/memories/subscribe`). Mirrors {@link handleSubscribeToThreads}: requires
* a `CopilotKitIntelligence` runtime and resolves the user with `identifyUser`
* (never a client-supplied id). Returns `{ joinToken, joinCode }` — memory needs
* the `joinCode` here (unlike threads, where it rides the thread-list response)
* because the client builds the `user_meta:memories:<joinCode>` channel topic
* from it.
*/
async function handleSubscribeToMemories({ runtime, request }) {
if (isIntelligenceRuntime(runtime)) try {
const user = await resolveIntelligenceUser({
runtime,
request
});
if (isHandlerResponse(user)) return user;
const credentials = await runtime.intelligence.ɵsubscribeToMemories({ userId: user.id });
return Response.json({
joinToken: credentials.joinToken,
joinCode: credentials.joinCode
});
} catch (error) {
logger.error({ err: error }, "Error subscribing to memories");
return memoryErrorResponse(error, "Failed to subscribe to memories");
}
return errorResponse(MISSING_INTELLIGENCE_MESSAGE, 422);
}
/**
* Creates a memory for the resolved user (platform `POST /api/memories`).
* Identity comes from `identifyUser`, never the request body. Returns 201
* with the stored memory (the client store applies it server-authoritatively).
*/
async function handleCreateMemory({ runtime, request }) {
if (!isIntelligenceRuntime(runtime)) return errorResponse(MISSING_INTELLIGENCE_MESSAGE, 422);
try {
const body = await parseJsonBody(request);
if (isHandlerResponse(body)) return body;
const fields = parseMemoryBody(body);
if (isHandlerResponse(fields)) return fields;
const user = await resolveIntelligenceUser({
runtime,
request
});
if (isHandlerResponse(user)) return user;
const data = await runtime.intelligence.createMemory({
userId: user.id,
...fields
});
return Response.json(data, { status: 201 });
} catch (error) {
logger.error({ err: error }, "Error creating memory");
return memoryErrorResponse(error, "Failed to create memory");
}
}
/**
* Supersedes a memory (platform `PATCH /api/memories/:id`): retires `:id` and
* inserts the new content atomically; the response carries `retiredId`.
*/
async function handleUpdateMemory({ runtime, request, memoryId }) {
if (!isIntelligenceRuntime(runtime)) return errorResponse(MISSING_INTELLIGENCE_MESSAGE, 422);
try {
const body = await parseJsonBody(request);
if (isHandlerResponse(body)) return body;
const fields = parseMemoryBody(body);
if (isHandlerResponse(fields)) return fields;
const user = await resolveIntelligenceUser({
runtime,
request
});
if (isHandlerResponse(user)) return user;
const data = await runtime.intelligence.updateMemory({
userId: user.id,
id: memoryId,
...fields
});
return Response.json(data);
} catch (error) {
logger.error({ err: error }, "Error updating memory");
return memoryErrorResponse(error, "Failed to update memory");
}
}
/**
* Retires (forgets) a memory (platform `DELETE /api/memories/:id`). Non-lossy
* on the platform side; returns 204.
*/
async function handleRemoveMemory({ runtime, request, memoryId }) {
if (!isIntelligenceRuntime(runtime)) return errorResponse(MISSING_INTELLIGENCE_MESSAGE, 422);
try {
const user = await resolveIntelligenceUser({
runtime,
request
});
if (isHandlerResponse(user)) return user;
await runtime.intelligence.removeMemory({
userId: user.id,
id: memoryId
});
return new Response(null, { status: 204 });
} catch (error) {
logger.error({ err: error }, "Error removing memory");
return memoryErrorResponse(error, "Failed to remove memory");
}
}
//#endregion
export { handleCreateMemory, handleListMemories, handleRemoveMemory, handleSubscribeToMemories, handleUpdateMemory };
//# sourceMappingURL=memories.mjs.map