Files
openclaw/extensions/codex/src/app-server/thread-context-engine.ts
Peter Steinberger b4b6afadd3 refactor(codex): split app-server thread lifecycle (#108414)
* refactor(codex): split thread lifecycle

* refactor(codex): keep lifecycle types internal
2026-07-15 11:23:47 -07:00

115 lines
3.8 KiB
TypeScript

import {
isActiveHarnessContextEngine,
type EmbeddedRunAttemptParams,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import {
resolveCodexContextEngineProjectionMaxChars,
resolveCodexContextEngineProjectionReserveTokens,
} from "./context-engine-projection.js";
import type { JsonValue } from "./protocol.js";
import type {
CodexAppServerContextEngineBinding,
CodexAppServerContextEngineProjectionBinding,
} from "./session-binding.js";
export type CodexContextEngineThreadBootstrapProjection = {
mode: "thread_bootstrap";
epoch: string;
fingerprint?: string;
};
export function buildContextEngineBinding(
params: EmbeddedRunAttemptParams,
projection?: CodexContextEngineThreadBootstrapProjection,
): CodexAppServerContextEngineBinding | undefined {
const contextEngine = isActiveHarnessContextEngine(params.contextEngine)
? params.contextEngine
: undefined;
const engineId = contextEngine?.info?.id?.trim();
if (!contextEngine || !engineId) {
return undefined;
}
return {
schemaVersion: 1,
engineId,
policyFingerprint: JSON.stringify({
schemaVersion: 1,
engineId,
engineVersion: contextEngine.info.version,
ownsCompaction: contextEngine.info.ownsCompaction === true,
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
citationsMode: resolveContextEngineCitationsMode(params.config),
contextTokenBudget: params.contextTokenBudget,
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
contextTokenBudget: params.contextTokenBudget,
reserveTokens: resolveCodexContextEngineProjectionReserveTokens({
config: params.config,
}),
}),
}),
projection: projection ? buildContextEngineProjectionBinding(projection) : undefined,
};
}
function buildContextEngineProjectionBinding(
projection: CodexContextEngineThreadBootstrapProjection,
): CodexAppServerContextEngineProjectionBinding {
return {
schemaVersion: 1,
mode: "thread_bootstrap",
epoch: projection.epoch,
fingerprint: projection.fingerprint,
};
}
export function isContextEngineBindingCompatible(
previous: CodexAppServerContextEngineBinding | undefined,
next: CodexAppServerContextEngineBinding,
): boolean {
return (
previous?.schemaVersion === next.schemaVersion &&
previous.engineId === next.engineId &&
previous.policyFingerprint === next.policyFingerprint &&
areContextEngineProjectionBindingsCompatible(previous.projection, next.projection)
);
}
function areContextEngineProjectionBindingsCompatible(
previous: CodexAppServerContextEngineProjectionBinding | undefined,
next: CodexAppServerContextEngineProjectionBinding | undefined,
): boolean {
if (!next) {
return previous === undefined;
}
return (
previous?.schemaVersion === next.schemaVersion &&
previous.mode === next.mode &&
previous.epoch === next.epoch &&
previous.fingerprint === next.fingerprint
);
}
function resolveContextEngineCitationsMode(config: unknown): JsonValue | undefined {
const rootConfig = isUnknownRecord(config) ? config : undefined;
const memoryConfig = isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : undefined;
const citations = memoryConfig?.citations;
return isJsonConfigValue(citations) ? citations : undefined;
}
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function isJsonConfigValue(value: unknown): value is JsonValue {
if (value === null || typeof value === "string" || typeof value === "boolean") {
return true;
}
if (typeof value === "number") {
return Number.isFinite(value);
}
if (Array.isArray(value)) {
return value.every(isJsonConfigValue);
}
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
}