import {
type OrchestrationV2Notification,
MessageId,
ProviderDriverKind,
ProviderThreadId,
RunId,
ThreadId,
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Queue from "effect/Queue";
export interface ProviderContinuationRequest {
readonly threadId: ThreadId;
readonly providerThreadId: ProviderThreadId;
readonly driver: ProviderDriverKind;
readonly detail: string | null;
readonly notification?: OrchestrationV2Notification;
/**
* Durable ownership for an app-owned delegated-task completion delivery.
* The continuation worker re-reads the cohort before dispatching so a later
* task can join a queued wake and a stopped or acknowledged cohort is dropped.
*/
readonly delegatedCompletion?: {
readonly parentRunId: RunId;
readonly generation: number;
readonly messageId: MessageId;
};
/**
* How the continuation turn gets its content.
*
* `adapter_buffered` (default) is the provider-native wake: the adapter has
* already buffered the CLI's wake output, and the dispatched message only
* triggers ingestion. `ClaudeAdapterV2` deliberately discards the message
* text on that path.
*
* `message_text` is for app-owned work with no buffered provider output, such
* as a delegated child finishing. The text is the entire wake, so it must
* reach the provider as a real prompt.
*/
readonly delivery?: "adapter_buffered" | "message_text";
readonly dispatchIfCurrent?: <A, E, R>(
effect: Effect.Effect<A, E, R>,
) => Effect.Effect<Option.Option<A>, E, R>;
/** Clears a pending offer that the continuation worker intentionally drops. */
readonly clearIfCurrent?: () => Effect.Effect<void>;
}
/**
* Adapters offer a continuation request when provider-native work completes
* outside an active turn (for example a Claude background task wake turn) so
* the orchestrator can start a run that ingests it. The default reference
* drops requests, keeping adapter construction dependency-free in tests; the
* live layer must be shared with the ProviderContinuationService worker that
* drains it.
*/
export class ProviderContinuationRequests extends Context.Reference<{
readonly offer: (request: ProviderContinuationRequest) => Effect.Effect<void>;
readonly take: Effect.Effect<ProviderContinuationRequest>;
}>("@t3tools/provider-core/server/continuationRequests/ProviderContinuationRequests", {
defaultValue: () => ({ offer: () => Effect.void, take: Effect.never }),
}) {}
export const layer = Layer.effect(
ProviderContinuationRequests,
Effect.gen(function* () {
const queue = yield* Queue.unbounded<ProviderContinuationRequest>();
return {
offer: (request: ProviderContinuationRequest) =>
Queue.offer(queue, request).pipe(Effect.asVoid),
take: Queue.take(queue),
};
}),
);