import { CommandId, MessageId, ProviderThreadId, TurnId, type OrchestrationThreadActivity, type ProviderRuntimeEvent, type ProviderSessionId,} from "@t3tools/contracts";import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Stream } from "effect"; import { ProviderService } from "../../provider/Services/ProviderService.ts";import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts";import { ProviderRuntimeIngestionService, type ProviderRuntimeIngestionShape,} from "../Services/ProviderRuntimeIngestion.ts"; const providerTurnKey = (sessionId: ProviderSessionId, turnId: TurnId) => `${sessionId}:${turnId}`;const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId => CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`);const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000;const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120); function toTurnId(value: string | undefined): TurnId | undefined { return value === undefined ? undefined : TurnId.makeUnsafe(value);} function truncateDetail(value: string, limit = 180): string { return value.length > limit ? `${value.slice(0, limit - 3)}...` : value;} function runtimeEventToActivities( event: ProviderRuntimeEvent,): ReadonlyArray<OrchestrationThreadActivity> { switch (event.type) { case "approval.requested": { return [ { id: event.eventId, createdAt: event.createdAt, tone: "approval", kind: "approval.requested", summary: event.requestKind === "command" ? "Command approval requested" : "File-change approval requested", payload: { requestId: event.requestId, requestKind: event.requestKind, ...(event.detail ? { detail: truncateDetail(event.detail) } : {}), }, turnId: toTurnId(event.turnId) ?? null, }, ]; } case "approval.resolved": { return [ { id: event.eventId, createdAt: event.createdAt, tone: "approval", kind: "approval.resolved", summary: "Approval resolved", payload: { requestId: event.requestId, ...(event.requestKind ? { requestKind: event.requestKind } : {}), ...(event.decision ? { decision: event.decision } : {}), }, turnId: toTurnId(event.turnId) ?? null, }, ]; } case "runtime.error": { return [ { id: event.eventId, createdAt: event.createdAt, tone: "error", kind: "runtime.error", summary: "Runtime error", payload: { message: truncateDetail(event.message), }, turnId: toTurnId(event.turnId) ?? null, }, ]; } case "tool.completed": { return [ { id: event.eventId, createdAt: event.createdAt, tone: "tool", kind: "tool.completed", summary: `${event.title} complete`, payload: { toolKind: event.toolKind, ...(event.detail ? { detail: truncateDetail(event.detail) } : {}), }, turnId: toTurnId(event.turnId) ?? null, }, ]; } case "tool.started": { return [ { id: event.eventId, createdAt: event.createdAt, tone: "tool", kind: "tool.started", summary: `${event.title} started`, payload: { toolKind: event.toolKind, ...(event.detail ? { detail: truncateDetail(event.detail) } : {}), }, turnId: toTurnId(event.turnId) ?? null, }, ]; } default: return []; }} const make = Effect.gen(function* () { const orchestrationEngine = yield* OrchestrationEngineService; const providerService = yield* ProviderService; const turnMessageIdsByTurnKey = yield* Cache.make<string, Set<MessageId>>({ capacity: TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY, timeToLive: TURN_MESSAGE_IDS_BY_TURN_TTL, lookup: () => Effect.succeed(new Set<MessageId>()), }); const rememberAssistantMessageId = ( sessionId: ProviderSessionId, turnId: TurnId, messageId: MessageId, ) => Cache.getOption(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)).pipe( Effect.flatMap((existingIds) => Cache.set( turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId), Option.match(existingIds, { onNone: () => new Set([messageId]), onSome: (ids) => { const nextIds = new Set(ids); nextIds.add(messageId); return nextIds; }, }), ), ), ); const forgetAssistantMessageId = ( sessionId: ProviderSessionId, turnId: TurnId, messageId: MessageId, ) => Cache.getOption(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)).pipe( Effect.flatMap((existingIds) => Option.match(existingIds, { onNone: () => Effect.void, onSome: (ids) => { const nextIds = new Set(ids); nextIds.delete(messageId); if (nextIds.size === 0) { return Cache.invalidate(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)); } return Cache.set(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId), nextIds); }, }), ), ); const getAssistantMessageIdsForTurn = (sessionId: ProviderSessionId, turnId: TurnId) => Cache.getOption(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)).pipe( Effect.map((existingIds) => Option.getOrElse(existingIds, (): Set<MessageId> => new Set<MessageId>()), ), ); const clearAssistantMessageIdsForTurn = (sessionId: ProviderSessionId, turnId: TurnId) => Cache.invalidate(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)); const clearTurnStateForSession = (sessionId: ProviderSessionId) => Effect.gen(function* () { const prefix = `${sessionId}:`; const turnKeys = Array.from(yield* Cache.keys(turnMessageIdsByTurnKey)); yield* Effect.forEach( turnKeys, (key) => key.startsWith(prefix) ? Cache.invalidate(turnMessageIdsByTurnKey, key) : Effect.void, { concurrency: 1 }, ).pipe(Effect.asVoid); }); const processEvent = (event: ProviderRuntimeEvent) => Effect.gen(function* () { const readModel = yield* orchestrationEngine.getReadModel(); const thread = readModel.threads.find( (entry) => entry.session?.providerSessionId === event.sessionId, ); if (!thread) return; const now = event.createdAt; if ( event.type === "session.started" || event.type === "session.exited" || event.type === "thread.started" || event.type === "turn.started" || event.type === "turn.completed" ) { const activeTurnId = event.type === "turn.started" ? (toTurnId(event.turnId) ?? null) : null; const providerThreadIdFromEvent = event.type === "thread.started" ? ProviderThreadId.makeUnsafe(event.threadId) : event.threadId !== undefined ? ProviderThreadId.makeUnsafe(event.threadId) : null; const providerThreadId = providerThreadIdFromEvent ?? thread.session?.providerThreadId ?? null; const status = event.type === "turn.started" ? "running" : event.type === "session.exited" ? "stopped" : event.type === "turn.completed" && event.status === "failed" ? "error" : "ready"; const lastError = event.type === "turn.completed" && event.status === "failed" ? (event.errorMessage ?? thread.session?.lastError ?? "Turn failed") : status === "ready" ? null : (thread.session?.lastError ?? null); yield* orchestrationEngine.dispatch({ type: "thread.session.set", commandId: providerCommandId(event, "thread-session-set"), threadId: thread.id, session: { threadId: thread.id, status, providerName: event.provider, providerSessionId: event.sessionId, providerThreadId, activeTurnId, lastError, updatedAt: now, }, createdAt: now, }); } if (event.type === "message.delta" && event.delta.length > 0) { const assistantMessageId = MessageId.makeUnsafe( `assistant:${event.itemId ?? event.turnId ?? event.sessionId}`, ); const turnId = toTurnId(event.turnId); if (turnId) { yield* rememberAssistantMessageId(event.sessionId, turnId, assistantMessageId); } yield* orchestrationEngine.dispatch({ type: "thread.message.assistant.delta", commandId: providerCommandId(event, "assistant-delta"), threadId: thread.id, messageId: assistantMessageId, delta: event.delta, ...(turnId ? { turnId } : {}), createdAt: now, }); } if (event.type === "message.completed") { const assistantMessageId = MessageId.makeUnsafe(`assistant:${event.itemId}`); const turnId = toTurnId(event.turnId); if (turnId) { yield* rememberAssistantMessageId(event.sessionId, turnId, assistantMessageId); } yield* orchestrationEngine.dispatch({ type: "thread.message.assistant.complete", commandId: providerCommandId(event, "assistant-complete"), threadId: thread.id, messageId: assistantMessageId, ...(turnId ? { turnId } : {}), createdAt: now, }); if (turnId) { yield* forgetAssistantMessageId(event.sessionId, turnId, assistantMessageId); } } if (event.type === "turn.completed") { const turnId = toTurnId(event.turnId); if (turnId) { const assistantMessageIds = yield* getAssistantMessageIdsForTurn(event.sessionId, turnId); yield* Effect.forEach(assistantMessageIds, (assistantMessageId) => orchestrationEngine.dispatch({ type: "thread.message.assistant.complete", commandId: providerCommandId(event, "assistant-complete-finalize"), threadId: thread.id, messageId: assistantMessageId, turnId, createdAt: now, }), ).pipe(Effect.asVoid); yield* clearAssistantMessageIdsForTurn(event.sessionId, turnId); } } if (event.type === "session.exited") { yield* clearTurnStateForSession(event.sessionId); } if (event.type === "runtime.error") { const providerThreadId = event.threadId !== undefined ? ProviderThreadId.makeUnsafe(event.threadId) : (thread.session?.providerThreadId ?? null); yield* orchestrationEngine.dispatch({ type: "thread.session.set", commandId: providerCommandId(event, "runtime-error-session-set"), threadId: thread.id, session: { threadId: thread.id, status: "error", providerName: event.provider, providerSessionId: event.sessionId, providerThreadId, activeTurnId: toTurnId(event.turnId) ?? null, lastError: event.message, updatedAt: now, }, createdAt: now, }); } const activities = runtimeEventToActivities(event); yield* Effect.forEach(activities, (activity) => orchestrationEngine.dispatch({ type: "thread.activity.append", commandId: providerCommandId(event, "thread-activity-append"), threadId: thread.id, activity, createdAt: activity.createdAt, }), ).pipe(Effect.asVoid); }); const processEventSafely = (event: ProviderRuntimeEvent) => processEvent(event).pipe( Effect.catchCause((cause) => { if (Cause.hasInterruptsOnly(cause)) { return Effect.failCause(cause); } return Effect.logWarning("provider runtime ingestion failed to process event", { eventId: event.eventId, eventType: event.type, cause: Cause.pretty(cause), }); }), ); const start: ProviderRuntimeIngestionShape["start"] = Effect.gen(function* () { const providerEventQueue = yield* Queue.unbounded<ProviderRuntimeEvent>(); yield* Effect.addFinalizer(() => Queue.shutdown(providerEventQueue).pipe(Effect.asVoid)); yield* Effect.forkScoped( Effect.forever(Queue.take(providerEventQueue).pipe(Effect.flatMap(processEventSafely))), ); yield* Effect.forkScoped( Stream.runForEach(providerService.streamEvents, (event) => Queue.offer(providerEventQueue, event).pipe(Effect.asVoid), ), ); }); return { start, } satisfies ProviderRuntimeIngestionShape;}); export const ProviderRuntimeIngestionLive = Layer.effect(ProviderRuntimeIngestionService, make);
import {
CommandId,
MessageId,
ProviderThreadId,
TurnId,
type OrchestrationThreadActivity,
type ProviderRuntimeEvent,
type ProviderSessionId,
} from "@t3tools/contracts";
import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Stream } from "effect";
import { ProviderService } from "../../provider/Services/ProviderService.ts";
import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts";
import {
ProviderRuntimeIngestionService,
type ProviderRuntimeIngestionShape,
} from "../Services/ProviderRuntimeIngestion.ts";
const providerTurnKey = (sessionId: ProviderSessionId, turnId: TurnId) => `${sessionId}:${turnId}`;
const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId =>
CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`);
const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000;
const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120);
function toTurnId(value: string | undefined): TurnId | undefined {
return value === undefined ? undefined : TurnId.makeUnsafe(value);
}
function truncateDetail(value: string, limit = 180): string {
return value.length > limit ? `${value.slice(0, limit - 3)}...` : value;
}
function runtimeEventToActivities(
event: ProviderRuntimeEvent,
): ReadonlyArray<OrchestrationThreadActivity> {
switch (event.type) {
case "approval.requested": {
return [
{
id: event.eventId,
createdAt: event.createdAt,
tone: "approval",
kind: "approval.requested",
summary:
event.requestKind === "command"
? "Command approval requested"
: "File-change approval requested",
payload: {
requestId: event.requestId,
requestKind: event.requestKind,
...(event.detail ? { detail: truncateDetail(event.detail) } : {}),
},
turnId: toTurnId(event.turnId) ?? null,
},
];
}
case "approval.resolved": {
return [
{
id: event.eventId,
createdAt: event.createdAt,
tone: "approval",
kind: "approval.resolved",
summary: "Approval resolved",
payload: {
requestId: event.requestId,
...(event.requestKind ? { requestKind: event.requestKind } : {}),
...(event.decision ? { decision: event.decision } : {}),
},
turnId: toTurnId(event.turnId) ?? null,
},
];
}
case "runtime.error": {
return [
{
id: event.eventId,
createdAt: event.createdAt,
tone: "error",
kind: "runtime.error",
summary: "Runtime error",
payload: {
message: truncateDetail(event.message),
},
turnId: toTurnId(event.turnId) ?? null,
},
];
}
case "tool.completed": {
return [
{
id: event.eventId,
createdAt: event.createdAt,
tone: "tool",
kind: "tool.completed",
summary: `${event.title} complete`,
payload: {
toolKind: event.toolKind,
...(event.detail ? { detail: truncateDetail(event.detail) } : {}),
},
turnId: toTurnId(event.turnId) ?? null,
},
];
}
case "tool.started": {
return [
{
id: event.eventId,
createdAt: event.createdAt,
tone: "tool",
kind: "tool.started",
summary: `${event.title} started`,
payload: {
toolKind: event.toolKind,
...(event.detail ? { detail: truncateDetail(event.detail) } : {}),
},
turnId: toTurnId(event.turnId) ?? null,
},
];
}
default:
return [];
}
}
const make = Effect.gen(function* () {
const orchestrationEngine = yield* OrchestrationEngineService;
const providerService = yield* ProviderService;
const turnMessageIdsByTurnKey = yield* Cache.make<string, Set<MessageId>>({
capacity: TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY,
timeToLive: TURN_MESSAGE_IDS_BY_TURN_TTL,
lookup: () => Effect.succeed(new Set<MessageId>()),
});
const rememberAssistantMessageId = (
sessionId: ProviderSessionId,
turnId: TurnId,
messageId: MessageId,
) =>
Cache.getOption(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)).pipe(
Effect.flatMap((existingIds) =>
Cache.set(
turnMessageIdsByTurnKey,
providerTurnKey(sessionId, turnId),
Option.match(existingIds, {
onNone: () => new Set([messageId]),
onSome: (ids) => {
const nextIds = new Set(ids);
nextIds.add(messageId);
return nextIds;
},
}),
),
),
);
const forgetAssistantMessageId = (
sessionId: ProviderSessionId,
turnId: TurnId,
messageId: MessageId,
) =>
Cache.getOption(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)).pipe(
Effect.flatMap((existingIds) =>
Option.match(existingIds, {
onNone: () => Effect.void,
onSome: (ids) => {
const nextIds = new Set(ids);
nextIds.delete(messageId);
if (nextIds.size === 0) {
return Cache.invalidate(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId));
}
return Cache.set(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId), nextIds);
},
}),
),
);
const getAssistantMessageIdsForTurn = (sessionId: ProviderSessionId, turnId: TurnId) =>
Cache.getOption(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)).pipe(
Effect.map((existingIds) =>
Option.getOrElse(existingIds, (): Set<MessageId> => new Set<MessageId>()),
),
);
const clearAssistantMessageIdsForTurn = (sessionId: ProviderSessionId, turnId: TurnId) =>
Cache.invalidate(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId));
const clearTurnStateForSession = (sessionId: ProviderSessionId) =>
Effect.gen(function* () {
const prefix = `${sessionId}:`;
const turnKeys = Array.from(yield* Cache.keys(turnMessageIdsByTurnKey));
yield* Effect.forEach(
turnKeys,
(key) =>
key.startsWith(prefix) ? Cache.invalidate(turnMessageIdsByTurnKey, key) : Effect.void,
{ concurrency: 1 },
).pipe(Effect.asVoid);
});
const processEvent = (event: ProviderRuntimeEvent) =>
Effect.gen(function* () {
const readModel = yield* orchestrationEngine.getReadModel();
const thread = readModel.threads.find(
(entry) => entry.session?.providerSessionId === event.sessionId,
);
if (!thread) return;
const now = event.createdAt;
if (
event.type === "session.started" ||
event.type === "session.exited" ||
event.type === "thread.started" ||
event.type === "turn.started" ||
event.type === "turn.completed"
) {
const activeTurnId =
event.type === "turn.started" ? (toTurnId(event.turnId) ?? null) : null;
const providerThreadIdFromEvent =
event.type === "thread.started"
? ProviderThreadId.makeUnsafe(event.threadId)
: event.threadId !== undefined
? ProviderThreadId.makeUnsafe(event.threadId)
: null;
const providerThreadId =
providerThreadIdFromEvent ?? thread.session?.providerThreadId ?? null;
const status =
event.type === "turn.started"
? "running"
: event.type === "session.exited"
? "stopped"
: event.type === "turn.completed" && event.status === "failed"
? "error"
: "ready";
const lastError =
event.type === "turn.completed" && event.status === "failed"
? (event.errorMessage ?? thread.session?.lastError ?? "Turn failed")
: status === "ready"
? null
: (thread.session?.lastError ?? null);
yield* orchestrationEngine.dispatch({
type: "thread.session.set",
commandId: providerCommandId(event, "thread-session-set"),
threadId: thread.id,
session: {
threadId: thread.id,
status,
providerName: event.provider,
providerSessionId: event.sessionId,
providerThreadId,
activeTurnId,
lastError,
updatedAt: now,
},
createdAt: now,
});
}
if (event.type === "message.delta" && event.delta.length > 0) {
const assistantMessageId = MessageId.makeUnsafe(
`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`,
);
const turnId = toTurnId(event.turnId);
if (turnId) {
yield* rememberAssistantMessageId(event.sessionId, turnId, assistantMessageId);
}
yield* orchestrationEngine.dispatch({
type: "thread.message.assistant.delta",
commandId: providerCommandId(event, "assistant-delta"),
threadId: thread.id,
messageId: assistantMessageId,
delta: event.delta,
...(turnId ? { turnId } : {}),
createdAt: now,
});
}
if (event.type === "message.completed") {
const assistantMessageId = MessageId.makeUnsafe(`assistant:${event.itemId}`);
const turnId = toTurnId(event.turnId);
if (turnId) {
yield* rememberAssistantMessageId(event.sessionId, turnId, assistantMessageId);
}
yield* orchestrationEngine.dispatch({
type: "thread.message.assistant.complete",
commandId: providerCommandId(event, "assistant-complete"),
threadId: thread.id,
messageId: assistantMessageId,
...(turnId ? { turnId } : {}),
createdAt: now,
});
if (turnId) {
yield* forgetAssistantMessageId(event.sessionId, turnId, assistantMessageId);
}
}
if (event.type === "turn.completed") {
const turnId = toTurnId(event.turnId);
if (turnId) {
const assistantMessageIds = yield* getAssistantMessageIdsForTurn(event.sessionId, turnId);
yield* Effect.forEach(assistantMessageIds, (assistantMessageId) =>
orchestrationEngine.dispatch({
type: "thread.message.assistant.complete",
commandId: providerCommandId(event, "assistant-complete-finalize"),
threadId: thread.id,
messageId: assistantMessageId,
turnId,
createdAt: now,
}),
).pipe(Effect.asVoid);
yield* clearAssistantMessageIdsForTurn(event.sessionId, turnId);
}
}
if (event.type === "session.exited") {
yield* clearTurnStateForSession(event.sessionId);
}
if (event.type === "runtime.error") {
const providerThreadId =
event.threadId !== undefined
? ProviderThreadId.makeUnsafe(event.threadId)
: (thread.session?.providerThreadId ?? null);
yield* orchestrationEngine.dispatch({
type: "thread.session.set",
commandId: providerCommandId(event, "runtime-error-session-set"),
threadId: thread.id,
session: {
threadId: thread.id,
status: "error",
providerName: event.provider,
providerSessionId: event.sessionId,
providerThreadId,
activeTurnId: toTurnId(event.turnId) ?? null,
lastError: event.message,
updatedAt: now,
},
createdAt: now,
});
}
const activities = runtimeEventToActivities(event);
yield* Effect.forEach(activities, (activity) =>
orchestrationEngine.dispatch({
type: "thread.activity.append",
commandId: providerCommandId(event, "thread-activity-append"),
threadId: thread.id,
activity,
createdAt: activity.createdAt,
}),
).pipe(Effect.asVoid);
});
const processEventSafely = (event: ProviderRuntimeEvent) =>
processEvent(event).pipe(
Effect.catchCause((cause) => {
if (Cause.hasInterruptsOnly(cause)) {
return Effect.failCause(cause);
}
return Effect.logWarning("provider runtime ingestion failed to process event", {
eventId: event.eventId,
eventType: event.type,
cause: Cause.pretty(cause),
});
}),
);
const start: ProviderRuntimeIngestionShape["start"] = Effect.gen(function* () {
const providerEventQueue = yield* Queue.unbounded<ProviderRuntimeEvent>();
yield* Effect.addFinalizer(() => Queue.shutdown(providerEventQueue).pipe(Effect.asVoid));
yield* Effect.forkScoped(
Effect.forever(Queue.take(providerEventQueue).pipe(Effect.flatMap(processEventSafely))),
);
yield* Effect.forkScoped(
Stream.runForEach(providerService.streamEvents, (event) =>
Queue.offer(providerEventQueue, event).pipe(Effect.asVoid),
),
);
});
return {
start,
} satisfies ProviderRuntimeIngestionShape;
});
export const ProviderRuntimeIngestionLive = Layer.effect(ProviderRuntimeIngestionService, make);