@opengeni/core 0.4.7 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +138 -26
- package/dist/index.js +485 -50
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/access/index.ts +4 -0
- package/src/application/session-commands.ts +527 -0
- package/src/dependencies.ts +4 -13
- package/src/domain/sessions.ts +83 -63
- package/src/index.ts +2 -0
- package/src/workflow-wake-contract.ts +6 -0
package/src/domain/sessions.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
createSession,
|
|
22
22
|
createSessionWithIdempotencyKey,
|
|
23
|
-
enqueueSessionMessageAtomically,
|
|
24
23
|
encryptVariableSetValue,
|
|
25
24
|
getAnySessionInGroup,
|
|
26
25
|
getEnrollment,
|
|
@@ -31,20 +30,26 @@ import {
|
|
|
31
30
|
getSandbox,
|
|
32
31
|
getSession,
|
|
33
32
|
getSessionByCreateIdempotencyKey,
|
|
33
|
+
getSessionEvent,
|
|
34
|
+
getWorkspaceControlEvent,
|
|
34
35
|
getSessionLineage,
|
|
35
36
|
getSessionTurn,
|
|
36
37
|
getWorkspaceModelPolicy,
|
|
37
38
|
initializeSessionStartAtomically,
|
|
38
39
|
requireSession,
|
|
40
|
+
submitHumanPromptInTransaction,
|
|
39
41
|
updateSessionTitle as updateSessionTitleRow,
|
|
42
|
+
withWorkspaceSubjectRls,
|
|
40
43
|
type CreateSessionMcpServerInput,
|
|
41
44
|
type Database,
|
|
42
45
|
type UpdateSessionMcpServerCredentialsInput,
|
|
43
|
-
|
|
46
|
+
QueueCommandConflictError,
|
|
47
|
+
SessionControlConflictError,
|
|
44
48
|
} from "@opengeni/db";
|
|
45
49
|
import {
|
|
46
50
|
appendAndPublishEvents,
|
|
47
51
|
publishDurableSessionEvents,
|
|
52
|
+
publishDurableWorkspaceControlEvent,
|
|
48
53
|
type EventBus,
|
|
49
54
|
} from "@opengeni/events";
|
|
50
55
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -596,7 +601,7 @@ export function reasoningEffortForSession(
|
|
|
596
601
|
export async function postUserMessageTurn(input: {
|
|
597
602
|
db: Database;
|
|
598
603
|
bus: EventBus;
|
|
599
|
-
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow"
|
|
604
|
+
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
600
605
|
settings: Settings;
|
|
601
606
|
accountId: string;
|
|
602
607
|
workspaceId: string;
|
|
@@ -608,11 +613,11 @@ export async function postUserMessageTurn(input: {
|
|
|
608
613
|
reasoningEffort?: Settings["openaiReasoningEffort"] | null;
|
|
609
614
|
clientEventId?: string;
|
|
610
615
|
mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
|
|
611
|
-
delivery?: "
|
|
616
|
+
delivery?: "send" | "steer";
|
|
612
617
|
origin?: "human" | "operator";
|
|
613
618
|
actor?: string;
|
|
614
|
-
|
|
615
|
-
|
|
619
|
+
controlEtag?: string | null;
|
|
620
|
+
expectedDraftRevision?: number | null;
|
|
616
621
|
}): Promise<{ accepted: SessionEvent; turn: SessionTurn }> {
|
|
617
622
|
const { db, bus, workflowClient, settings, accountId, workspaceId, sessionId } = input;
|
|
618
623
|
const requestedModel = input.model ?? null;
|
|
@@ -621,34 +626,37 @@ export async function postUserMessageTurn(input: {
|
|
|
621
626
|
// model inherits the session's model downstream (always a configured id).
|
|
622
627
|
assertConfiguredModel(settings, requestedModel);
|
|
623
628
|
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, requestedModel);
|
|
629
|
+
const operationKey = input.clientEventId ?? crypto.randomUUID();
|
|
624
630
|
let result;
|
|
625
631
|
try {
|
|
626
|
-
result = await
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
});
|
|
632
|
+
result = await withWorkspaceSubjectRls(db, workspaceId, input.actor ?? accountId, (scoped) =>
|
|
633
|
+
scoped.transaction((tx) =>
|
|
634
|
+
submitHumanPromptInTransaction(tx as unknown as Database, {
|
|
635
|
+
accountId,
|
|
636
|
+
workspaceId,
|
|
637
|
+
sessionId,
|
|
638
|
+
subjectId: input.actor ?? accountId,
|
|
639
|
+
actor: { type: "human", subjectId: input.actor ?? accountId },
|
|
640
|
+
operationKey,
|
|
641
|
+
delivery: input.delivery ?? "send",
|
|
642
|
+
controlEtag: input.controlEtag ?? null,
|
|
643
|
+
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
644
|
+
text: input.text,
|
|
645
|
+
resources: input.resources,
|
|
646
|
+
tools: input.tools,
|
|
647
|
+
model: requestedModel,
|
|
648
|
+
reasoningEffort: requestedReasoningEffort,
|
|
649
|
+
reasoningEffortFallback: settings.openaiReasoningEffort,
|
|
650
|
+
source: input.origin === "operator" ? "api" : "user",
|
|
651
|
+
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
652
|
+
}),
|
|
653
|
+
),
|
|
654
|
+
);
|
|
650
655
|
} catch (error) {
|
|
651
|
-
if (
|
|
656
|
+
if (
|
|
657
|
+
error instanceof QueueCommandConflictError ||
|
|
658
|
+
error instanceof SessionControlConflictError
|
|
659
|
+
) {
|
|
652
660
|
throw new HTTPException(409, { message: error.message });
|
|
653
661
|
}
|
|
654
662
|
if (error instanceof Error && error.message.includes("cancelled")) {
|
|
@@ -659,35 +667,51 @@ export async function postUserMessageTurn(input: {
|
|
|
659
667
|
}
|
|
660
668
|
throw error;
|
|
661
669
|
}
|
|
662
|
-
await
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
670
|
+
const events = await Promise.all(
|
|
671
|
+
result.eventIds.map((eventId) => getSessionEvent(db, workspaceId, eventId)),
|
|
672
|
+
);
|
|
673
|
+
if (events.some((event) => event === null)) {
|
|
674
|
+
throw new Error("Committed prompt events could not be reloaded");
|
|
675
|
+
}
|
|
676
|
+
const turn = await getSessionTurn(db, workspaceId, result.turnId);
|
|
677
|
+
if (!turn) throw new Error("Committed prompt turn could not be reloaded");
|
|
678
|
+
const accepted = events.find((event) => event?.id === result.acceptedEventId);
|
|
679
|
+
if (!accepted) throw new Error("Committed user.message event could not be reloaded");
|
|
680
|
+
await publishDurableSessionEvents(
|
|
681
|
+
bus,
|
|
682
|
+
workspaceId,
|
|
683
|
+
sessionId,
|
|
684
|
+
events.filter((event): event is SessionEvent => event !== null),
|
|
685
|
+
);
|
|
686
|
+
if (result.workspaceControlEventId) {
|
|
687
|
+
const controlEvent = await getWorkspaceControlEvent(
|
|
688
|
+
db,
|
|
669
689
|
workspaceId,
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (result.workflowWakeRevision === null) {
|
|
677
|
-
throw new Error("Runnable prompt has no workflow wake revision");
|
|
690
|
+
result.workspaceControlEventId,
|
|
691
|
+
);
|
|
692
|
+
if (!controlEvent) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
|
|
695
|
+
);
|
|
678
696
|
}
|
|
697
|
+
await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
679
700
|
await workflowClient.wakeSessionWorkflow({
|
|
680
701
|
accountId,
|
|
681
702
|
workspaceId,
|
|
682
703
|
sessionId,
|
|
683
|
-
workflowId:
|
|
684
|
-
wakeRevision: result.
|
|
704
|
+
workflowId: turn.temporalWorkflowId,
|
|
705
|
+
wakeRevision: result.wakeRevision,
|
|
706
|
+
...(result.interruptionCount > 0 ? { interruptionRequested: true } : {}),
|
|
685
707
|
});
|
|
708
|
+
} catch (error) {
|
|
709
|
+
console.warn(
|
|
710
|
+
`[sessions] workflow wake failed for committed prompt ${workspaceId}/${sessionId}; durable outbox will retry`,
|
|
711
|
+
error,
|
|
712
|
+
);
|
|
686
713
|
}
|
|
687
|
-
return {
|
|
688
|
-
accepted: result.accepted,
|
|
689
|
-
turn: result.turn,
|
|
690
|
-
};
|
|
714
|
+
return { accepted, turn };
|
|
691
715
|
}
|
|
692
716
|
|
|
693
717
|
/**
|
|
@@ -1132,10 +1156,10 @@ export async function acceptSessionUserMessage(
|
|
|
1132
1156
|
reasoningEffort?: ReasoningEffort | null;
|
|
1133
1157
|
clientEventId?: string;
|
|
1134
1158
|
mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
|
|
1135
|
-
delivery?: "
|
|
1159
|
+
delivery?: "send" | "steer";
|
|
1136
1160
|
origin?: "human" | "operator";
|
|
1137
|
-
|
|
1138
|
-
|
|
1161
|
+
controlEtag?: string | null;
|
|
1162
|
+
expectedDraftRevision?: number | null;
|
|
1139
1163
|
},
|
|
1140
1164
|
): Promise<{ accepted: SessionEvent; turn: SessionTurn }> {
|
|
1141
1165
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
@@ -1192,16 +1216,12 @@ export async function acceptSessionUserMessage(
|
|
|
1192
1216
|
model: input.model ?? null,
|
|
1193
1217
|
reasoningEffort: input.reasoningEffort ?? null,
|
|
1194
1218
|
mcpCredentialUpdates,
|
|
1195
|
-
delivery: input.delivery ?? "
|
|
1219
|
+
delivery: input.delivery ?? "send",
|
|
1196
1220
|
origin: input.origin ?? "human",
|
|
1197
1221
|
actor: grant.subjectId,
|
|
1198
|
-
...(input.
|
|
1199
|
-
|
|
1200
|
-
:
|
|
1201
|
-
...(input.expectedWorkspaceInferenceGeneration !== undefined
|
|
1202
|
-
? {
|
|
1203
|
-
expectedWorkspaceInferenceGeneration: input.expectedWorkspaceInferenceGeneration,
|
|
1204
|
-
}
|
|
1222
|
+
...(input.controlEtag !== undefined ? { controlEtag: input.controlEtag } : {}),
|
|
1223
|
+
...(input.expectedDraftRevision !== undefined
|
|
1224
|
+
? { expectedDraftRevision: input.expectedDraftRevision }
|
|
1205
1225
|
: {}),
|
|
1206
1226
|
...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
|
|
1207
1227
|
});
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
// The central dependency type surface (AppDependencies, ApiRouteDeps,
|
|
28
28
|
// SessionWorkflowClient, DocumentIndexClient, ObjectStorageDependency).
|
|
29
29
|
export * from "./dependencies";
|
|
30
|
+
export * from "./workflow-wake-contract";
|
|
30
31
|
|
|
31
32
|
// Boundary type slots referenced by dependencies.ts. The IMPLEMENTATIONS that
|
|
32
33
|
// construct these (the real sandbox client / Better Auth instance) stay in
|
|
@@ -58,3 +59,4 @@ export * from "./domain/resources";
|
|
|
58
59
|
export * from "./domain/scheduled-tasks";
|
|
59
60
|
export * from "./domain/sessions";
|
|
60
61
|
export * from "./domain/workspace-members";
|
|
62
|
+
export * from "./application/session-commands";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** One global Temporal Schedule owns bounded delivery of committed session wakes. */
|
|
2
|
+
export const SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID =
|
|
3
|
+
"opengeni-session-workflow-wake-dispatcher";
|
|
4
|
+
export const SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE =
|
|
5
|
+
"sessionWorkflowWakeDispatcherWorkflow";
|
|
6
|
+
export const SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 10_000;
|