Skip to content
Merged
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
98 changes: 98 additions & 0 deletions src/responses/provider-continuation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type {
OcxProviderContinuationOwner,
OcxReasoningReplayIdentity,
} from "../types";

const bounded = (value: unknown, max: number): value is string =>
typeof value === "string" && value.length > 0 && value.length <= max;

const isPlainRecord = (value: unknown): value is Record<string, unknown> => {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
};

const defineOwn = (target: Record<string, unknown>, key: string, value: unknown): void => {
Object.defineProperty(target, key, {
value,
enumerable: true,
configurable: true,
writable: true,
});
};

/** Recursively merge provider-owned plain objects; arrays and scalar values replace prior state. */
export function mergeProviderContinuationPayload(
inherited: Readonly<Record<string, unknown>>,
emitted: Readonly<Record<string, unknown>>,
): Record<string, unknown> {
const merged: Record<string, unknown> = {};
for (const [key, value] of Object.entries(inherited)) defineOwn(merged, key, value);
for (const [key, value] of Object.entries(emitted)) {
const prior = Object.hasOwn(merged, key) ? merged[key] : undefined;
defineOwn(
merged,
key,
isPlainRecord(prior) && isPlainRecord(value)
? mergeProviderContinuationPayload(prior, value)
: value,
);
}
return merged;
}

/** Validate proxy-authored continuation ownership before trusting persisted state. */
export function isValidProviderContinuationOwner(
value: unknown,
): value is OcxProviderContinuationOwner {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const owner = value as Record<string, unknown>;
return owner.version === 1
&& bounded(owner.providerName, 256)
&& typeof owner.providerDestinationIdentity === "string"
&& /^destination:[0-9a-f]{64}$/.test(owner.providerDestinationIdentity)
&& bounded(owner.adapterName, 128)
&& bounded(owner.modelId, 512)
&& typeof owner.credentialIdentity === "string"
&& /^(key|oauth|codex):[0-9a-f]{64}$/.test(owner.credentialIdentity);
}

/** Reuse the exact process-local route identity already established for reasoning replay. */
export function providerContinuationOwnerFromReplayIdentity(
identity: Readonly<OcxReasoningReplayIdentity> | undefined,
): OcxProviderContinuationOwner | undefined {
if (!identity) return undefined;
const owner: OcxProviderContinuationOwner = {
version: 1,
providerName: identity.providerName,
providerDestinationIdentity: identity.providerDestinationIdentity,
adapterName: identity.adapterName,
modelId: identity.modelId,
credentialIdentity: identity.credentialIdentity,
};
return isValidProviderContinuationOwner(owner) ? owner : undefined;
}

export function sameProviderContinuationOwner(
left: OcxProviderContinuationOwner,
right: OcxProviderContinuationOwner,
): boolean {
return left.version === right.version
&& left.providerName === right.providerName
&& left.providerDestinationIdentity === right.providerDestinationIdentity
&& left.adapterName === right.adapterName
&& left.modelId === right.modelId
&& left.credentialIdentity === right.credentialIdentity;
}

/** Cursor hashes this non-secret namespace with the client thread id. */
export function providerContinuationRouteScope(owner: OcxProviderContinuationOwner): string {
return JSON.stringify([
"provider-continuation-v1",
owner.providerName,
owner.providerDestinationIdentity,
owner.adapterName,
owner.modelId,
owner.credentialIdentity,
]);
}
7 changes: 6 additions & 1 deletion src/responses/spill-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { createHash, randomBytes } from "node:crypto";
import { join } from "node:path";
import { getConfigDir } from "../config";
import { forgetEphemeralSecretPath, forgetHardenedSecretPath, hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
import { isValidProviderContinuationOwner } from "./provider-continuation";
import type { OcxProviderContinuationState } from "../types";

export const RESPONSE_SPILL_VERSION = 1;
Expand Down Expand Up @@ -287,7 +288,11 @@ function validPayload(value: unknown, responseId: string): value is ResponseSpil
}
if (payload.providers !== undefined) {
if (!payload.providers || typeof payload.providers !== "object" || Array.isArray(payload.providers)) return false;
for (const providerState of Object.values(payload.providers)) {
const providers = payload.providers as Record<string, unknown>;
if (providers.__ocxOwner !== undefined
&& !isValidProviderContinuationOwner(providers.__ocxOwner)) return false;
for (const [provider, providerState] of Object.entries(providers)) {
if (provider === "__ocxOwner") continue;
if (!providerState || typeof providerState !== "object" || Array.isArray(providerState)) return false;
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/responses/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,17 @@ export function previousResponseReplayPrefixLength(body: unknown): number {
return replayedInputPrefixLengths.get(body) ?? 0;
}

/** Copy proxy-private replay provenance to an internal clone with the same materialized input. */
export function copyPreviousResponseReplayProvenance(source: unknown, target: unknown): void {
if (!source || typeof source !== "object" || Array.isArray(source)) return;
if (!target || typeof target !== "object" || Array.isArray(target)) return;
const prefixLength = replayedInputPrefixLengths.get(source);
if (!prefixLength) return;
const input = (target as { input?: unknown }).input;
if (!Array.isArray(input) || prefixLength > input.length) return;
replayedInputPrefixLengths.set(target, prefixLength);
}

/** True when a stale or foreign previous_response_id was removed from this exact request body. */
export function previousResponseScopeMismatch(body: unknown): boolean {
return !!body && typeof body === "object" && replayScopeMismatches.has(body as object);
Expand Down
Loading
Loading