@opengeni/core 2.6.3-canary.0 → 2.6.4-canary.0
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/billing/limits.d.ts +5 -0
- package/dist/dependencies.d.ts +10 -1
- package/dist/domain/sessions.d.ts +24 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1049 -316
- package/dist/index.js.map +1 -1
- package/dist/model-catalog.d.ts +89 -0
- package/package.json +10 -10
- package/src/billing/limits.ts +52 -22
- package/src/dependencies.ts +15 -1
- package/src/domain/scheduled-tasks.ts +52 -1
- package/src/domain/sessions.ts +790 -344
- package/src/index.ts +1 -0
- package/src/model-catalog.ts +565 -0
package/src/domain/sessions.ts
CHANGED
|
@@ -2,10 +2,11 @@ import { CODEX_MODEL_ID_PREFIX, isCodexBilledModel } from "@opengeni/codex";
|
|
|
2
2
|
import {
|
|
3
3
|
canonicalizeConfiguredModelId,
|
|
4
4
|
configuredAllowedModels,
|
|
5
|
+
WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
|
|
6
|
+
WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
|
|
5
7
|
resolveFirstPartyMcpToolPolicy,
|
|
6
8
|
policyProviderIdForModel,
|
|
7
9
|
resolveTurnExecutionPolicyV1,
|
|
8
|
-
WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
|
|
9
10
|
XAI_SUBSCRIPTION_MODEL_ID_PREFIX,
|
|
10
11
|
type Settings,
|
|
11
12
|
} from "@opengeni/config";
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
evaluateWorkspaceModelPolicy,
|
|
24
25
|
normalizeAutomaticSessionTitle,
|
|
25
26
|
resolveWorkspaceSessionToolDefaults,
|
|
27
|
+
metadataWithTurnExecutionPolicyV1,
|
|
28
|
+
readTurnExecutionPolicyV1,
|
|
26
29
|
stableJson,
|
|
27
30
|
type AccessGrant,
|
|
28
31
|
type ComposerDraft,
|
|
@@ -62,6 +65,7 @@ import {
|
|
|
62
65
|
import {
|
|
63
66
|
createSession,
|
|
64
67
|
createSessionWithIdempotencyKeyResult,
|
|
68
|
+
canonicalSessionCommandHash,
|
|
65
69
|
encryptVariableSetValue,
|
|
66
70
|
getAnySessionInGroup,
|
|
67
71
|
getEnrollment,
|
|
@@ -72,10 +76,10 @@ import {
|
|
|
72
76
|
listDistinctRigVersionIdsInGroup,
|
|
73
77
|
getSandbox,
|
|
74
78
|
getSession,
|
|
79
|
+
getInitializedSessionCreateReplay,
|
|
75
80
|
getSessionAuthorityProjection,
|
|
76
81
|
SessionIdConflictError,
|
|
77
82
|
NewSessionDraftConflictError,
|
|
78
|
-
getSessionSpawnDenialByIdempotencyKey,
|
|
79
83
|
getWorkspaceControlEvent,
|
|
80
84
|
getSessionLineage,
|
|
81
85
|
getSessionTurn,
|
|
@@ -87,7 +91,10 @@ import {
|
|
|
87
91
|
initializeSessionStartAtomically,
|
|
88
92
|
listSessionTurns,
|
|
89
93
|
listSessionMcpServersForChildInheritance,
|
|
94
|
+
lockActiveWorkspaceGatewayCustomModelForAdmission,
|
|
95
|
+
lockActiveWorkspaceOpenRouterCustomModelForAdmission,
|
|
90
96
|
requireSession,
|
|
97
|
+
replaySubmittedHumanPromptFromBoundaryReceipt,
|
|
91
98
|
submitHumanPromptInTransaction,
|
|
92
99
|
appendSessionEventsWithLockedSessionUpdate,
|
|
93
100
|
updateSessionTitleWithEvent,
|
|
@@ -129,6 +136,11 @@ import {
|
|
|
129
136
|
} from "../session-authorization";
|
|
130
137
|
import { swapActiveSandbox, type FleetContext } from "../sandbox/fleet";
|
|
131
138
|
import { managedSessionGroupBackend } from "../sandbox/runtime-settings";
|
|
139
|
+
import {
|
|
140
|
+
isWorkspaceCustomModelId,
|
|
141
|
+
resolveWorkspaceCatalogSettings,
|
|
142
|
+
workspaceCustomModelReference,
|
|
143
|
+
} from "../model-catalog";
|
|
132
144
|
import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
|
|
133
145
|
import { validateSubmittedTimelineAnnotations } from "./timeline-annotations";
|
|
134
146
|
import { requireVariableSetEncryption, validateVariableSetAttachment } from "./environments";
|
|
@@ -656,6 +668,16 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
656
668
|
/** Internal database-only composition seam. The exact session shell and this
|
|
657
669
|
* linkage commit together before its first event/turn can be initialized. */
|
|
658
670
|
beforeCreateCommit?: (tx: Database, sessionId: string) => Promise<void>;
|
|
671
|
+
/** The custom workspace model was frozen by an earlier accepted boundary or
|
|
672
|
+
* inherited from an existing session, so retirement must not invalidate it. */
|
|
673
|
+
retainWorkspaceGatewayModel?: boolean;
|
|
674
|
+
/** Provider-neutral successor to retainWorkspaceGatewayModel. */
|
|
675
|
+
retainWorkspaceCustomModel?: boolean;
|
|
676
|
+
/** The selected workspace Gateway product is backed by a mutable custom row,
|
|
677
|
+
* rather than deployment-curated Gateway membership. */
|
|
678
|
+
workspaceGatewayCustomModel?: boolean;
|
|
679
|
+
/** Provider-neutral successor to workspaceGatewayCustomModel. */
|
|
680
|
+
workspaceCustomModel?: boolean;
|
|
659
681
|
accountId: string;
|
|
660
682
|
workspaceId: string;
|
|
661
683
|
visibility?: "user_private" | "workspace_shared";
|
|
@@ -775,16 +797,56 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
775
797
|
allowNestedAgentDepthIncrease?: boolean;
|
|
776
798
|
subjectId?: string | null;
|
|
777
799
|
}): Promise<CreateSessionOutcome> {
|
|
778
|
-
const sessionMetadata =
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
800
|
+
const sessionMetadata = metadataWithTurnExecutionPolicyV1(
|
|
801
|
+
{
|
|
802
|
+
...input.metadata,
|
|
803
|
+
model: input.model,
|
|
804
|
+
reasoningEffort: input.reasoningEffort,
|
|
805
|
+
...(input.latencyMode !== undefined ? { latencyMode: input.latencyMode } : {}),
|
|
806
|
+
},
|
|
807
|
+
input.turnExecutionPolicy,
|
|
808
|
+
);
|
|
784
809
|
const frozenCreatedByContext = freezeAgentChildAutomaticTitleInCreatorContext(
|
|
785
810
|
input.createdByContext,
|
|
786
811
|
input.initialAutomaticTitle,
|
|
787
812
|
);
|
|
813
|
+
const requiresActiveWorkspaceCustomModel =
|
|
814
|
+
(input.workspaceCustomModel === true || input.workspaceGatewayCustomModel === true) &&
|
|
815
|
+
input.retainWorkspaceCustomModel !== true &&
|
|
816
|
+
input.retainWorkspaceGatewayModel !== true;
|
|
817
|
+
const beforeCreateCommit =
|
|
818
|
+
requiresActiveWorkspaceCustomModel || input.beforeCreateCommit
|
|
819
|
+
? async (tx: Database, sessionId: string, context?: { created: boolean }): Promise<void> => {
|
|
820
|
+
// A committed keyed replay already crossed this fence when its shell
|
|
821
|
+
// was first accepted. Revalidate only the transaction inserting a new
|
|
822
|
+
// session, while still running caller linkage on every replay.
|
|
823
|
+
if (requiresActiveWorkspaceCustomModel && context?.created !== false) {
|
|
824
|
+
const openRouter = input.model.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX);
|
|
825
|
+
const upstreamModelId = input.model.slice(
|
|
826
|
+
openRouter
|
|
827
|
+
? WORKSPACE_OPENROUTER_MODEL_ID_PREFIX.length
|
|
828
|
+
: WORKSPACE_GATEWAY_MODEL_ID_PREFIX.length,
|
|
829
|
+
);
|
|
830
|
+
const active = openRouter
|
|
831
|
+
? await lockActiveWorkspaceOpenRouterCustomModelForAdmission(tx, {
|
|
832
|
+
accountId: input.accountId,
|
|
833
|
+
workspaceId: input.workspaceId,
|
|
834
|
+
upstreamModelId,
|
|
835
|
+
})
|
|
836
|
+
: await lockActiveWorkspaceGatewayCustomModelForAdmission(tx, {
|
|
837
|
+
accountId: input.accountId,
|
|
838
|
+
workspaceId: input.workspaceId,
|
|
839
|
+
upstreamModelId,
|
|
840
|
+
});
|
|
841
|
+
if (!active) {
|
|
842
|
+
throw new HTTPException(422, {
|
|
843
|
+
message: `model is not available: ${input.model}`,
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
await input.beforeCreateCommit?.(tx, sessionId);
|
|
848
|
+
}
|
|
849
|
+
: undefined;
|
|
788
850
|
// Keyed creation is intentionally handled only by the database admission
|
|
789
851
|
// transaction below. Its workspace/key lock replays either the successful
|
|
790
852
|
// session or the committed denial atomically; an application-side lookup
|
|
@@ -834,15 +896,29 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
834
896
|
maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
|
|
835
897
|
allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
|
|
836
898
|
subjectId: input.subjectId ?? null,
|
|
837
|
-
...(
|
|
899
|
+
...(beforeCreateCommit ? { beforeCreateCommit } : {}),
|
|
838
900
|
});
|
|
839
901
|
if (keyedResult.denied) {
|
|
840
902
|
throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(keyedResult.denial));
|
|
841
903
|
}
|
|
842
904
|
const { session: keyed, created } = keyedResult;
|
|
843
905
|
if (!created) {
|
|
906
|
+
const persistedPolicy = readTurnExecutionPolicyV1(keyed.metadata);
|
|
844
907
|
const finished = await finishStartSession(
|
|
845
|
-
keyed.temporalWorkflowId
|
|
908
|
+
keyed.temporalWorkflowId
|
|
909
|
+
? {
|
|
910
|
+
...input,
|
|
911
|
+
seedTargetSandbox: null,
|
|
912
|
+
...(persistedPolicy.kind === "valid"
|
|
913
|
+
? { turnExecutionPolicy: persistedPolicy.policy }
|
|
914
|
+
: {}),
|
|
915
|
+
}
|
|
916
|
+
: {
|
|
917
|
+
...input,
|
|
918
|
+
...(persistedPolicy.kind === "valid"
|
|
919
|
+
? { turnExecutionPolicy: persistedPolicy.policy }
|
|
920
|
+
: {}),
|
|
921
|
+
},
|
|
846
922
|
keyed,
|
|
847
923
|
);
|
|
848
924
|
return {
|
|
@@ -905,7 +981,7 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
905
981
|
maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
|
|
906
982
|
allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
|
|
907
983
|
subjectId: input.subjectId ?? null,
|
|
908
|
-
...(
|
|
984
|
+
...(beforeCreateCommit ? { beforeCreateCommit } : {}),
|
|
909
985
|
});
|
|
910
986
|
} catch (error) {
|
|
911
987
|
if (error instanceof SessionSpawnDeniedDbError) {
|
|
@@ -1137,9 +1213,6 @@ export function canonicalConfiguredModel(
|
|
|
1137
1213
|
) {
|
|
1138
1214
|
return canonicalModel;
|
|
1139
1215
|
}
|
|
1140
|
-
if (canonicalModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
1141
|
-
return canonicalModel;
|
|
1142
|
-
}
|
|
1143
1216
|
throw new HTTPException(422, { message: `model is not available: ${model}` });
|
|
1144
1217
|
}
|
|
1145
1218
|
|
|
@@ -1245,7 +1318,17 @@ export async function requireQueuedTurnForApi(
|
|
|
1245
1318
|
* `session_send_message` tool so the two surfaces cannot drift. Callers own
|
|
1246
1319
|
* resource/tool validation and the per-message usage limit before calling.
|
|
1247
1320
|
*/
|
|
1248
|
-
|
|
1321
|
+
type PostUserMessageTurnResult = {
|
|
1322
|
+
accepted: SessionEvent;
|
|
1323
|
+
turn: SessionTurn;
|
|
1324
|
+
draft: ComposerDraft | null;
|
|
1325
|
+
receipt: SessionCommandReceipt;
|
|
1326
|
+
routing: SessionPromptRouting;
|
|
1327
|
+
interruptionCount: number;
|
|
1328
|
+
replay: boolean;
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
type PostUserMessageTurnInput = {
|
|
1249
1332
|
db: Database;
|
|
1250
1333
|
bus: EventBus;
|
|
1251
1334
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
@@ -1273,28 +1356,143 @@ export async function postUserMessageTurn(input: {
|
|
|
1273
1356
|
commandActor?: SessionCommandActor;
|
|
1274
1357
|
controlEtag?: string | null;
|
|
1275
1358
|
expectedDraftRevision?: number | null;
|
|
1359
|
+
boundaryRequestHash?: string;
|
|
1276
1360
|
reasoningEffortFallback?: Settings["openaiReasoningEffort"];
|
|
1277
1361
|
turnExecutionPolicy: TurnExecutionPolicyV1;
|
|
1278
1362
|
recordAgentRunUsage?: boolean;
|
|
1279
1363
|
schedulePostCommit?: (task: () => Promise<void>) => void;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
function finalizePostUserMessageTurn(
|
|
1367
|
+
input: Pick<
|
|
1368
|
+
PostUserMessageTurnInput,
|
|
1369
|
+
| "db"
|
|
1370
|
+
| "bus"
|
|
1371
|
+
| "workflowClient"
|
|
1372
|
+
| "accountId"
|
|
1373
|
+
| "workspaceId"
|
|
1374
|
+
| "sessionId"
|
|
1375
|
+
| "delivery"
|
|
1376
|
+
| "schedulePostCommit"
|
|
1377
|
+
>,
|
|
1378
|
+
result: Awaited<ReturnType<typeof submitHumanPromptInTransaction>>,
|
|
1379
|
+
): PostUserMessageTurnResult {
|
|
1380
|
+
const { db, bus, workflowClient, accountId, workspaceId, sessionId } = input;
|
|
1381
|
+
const postCommitTask = async () => {
|
|
1382
|
+
await Promise.all([
|
|
1383
|
+
(async () => {
|
|
1384
|
+
try {
|
|
1385
|
+
await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
|
|
1386
|
+
if (result.workspaceControlEventId) {
|
|
1387
|
+
const controlEvent = await getWorkspaceControlEvent(
|
|
1388
|
+
db,
|
|
1389
|
+
workspaceId,
|
|
1390
|
+
result.workspaceControlEventId,
|
|
1391
|
+
);
|
|
1392
|
+
if (!controlEvent) {
|
|
1393
|
+
throw new Error(
|
|
1394
|
+
`Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
|
|
1398
|
+
}
|
|
1399
|
+
} catch {
|
|
1400
|
+
console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
|
|
1401
|
+
errorClass: "PromptEventFanoutOperationError",
|
|
1402
|
+
errorCode: "session_prompt_event_fanout_failed",
|
|
1403
|
+
origin: "core",
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
})(),
|
|
1407
|
+
(async () => {
|
|
1408
|
+
try {
|
|
1409
|
+
await workflowClient.wakeSessionWorkflow({
|
|
1410
|
+
accountId,
|
|
1411
|
+
workspaceId,
|
|
1412
|
+
sessionId,
|
|
1413
|
+
workflowId: result.turn.temporalWorkflowId,
|
|
1414
|
+
wakeRevision: result.wakeRevision,
|
|
1415
|
+
...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
|
|
1416
|
+
? { interruptionRequested: true }
|
|
1417
|
+
: {}),
|
|
1418
|
+
});
|
|
1419
|
+
} catch {
|
|
1420
|
+
console.warn("[sessions] workflow wake failed; durable outbox will retry", {
|
|
1421
|
+
errorClass: "WorkflowWakeOperationError",
|
|
1422
|
+
errorCode: "session_workflow_wake_failed",
|
|
1423
|
+
origin: "core",
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
})(),
|
|
1427
|
+
]);
|
|
1428
|
+
};
|
|
1429
|
+
const schedulePostCommit =
|
|
1430
|
+
input.schedulePostCommit ??
|
|
1431
|
+
((task: () => Promise<void>) => {
|
|
1432
|
+
void task();
|
|
1433
|
+
});
|
|
1434
|
+
try {
|
|
1435
|
+
schedulePostCommit(postCommitTask);
|
|
1436
|
+
} catch {
|
|
1437
|
+
console.warn("[sessions] prompt post-commit scheduling failed; durable recovery remains", {
|
|
1438
|
+
errorClass: "PromptPostCommitScheduleError",
|
|
1439
|
+
errorCode: "session_prompt_post_commit_schedule_failed",
|
|
1440
|
+
origin: "core",
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
accepted: result.accepted,
|
|
1445
|
+
turn: result.turn,
|
|
1446
|
+
receipt: {
|
|
1447
|
+
id: result.receipt.id,
|
|
1448
|
+
action: result.receipt.action,
|
|
1449
|
+
operationKey: result.receipt.operationKey,
|
|
1450
|
+
targetSessionId: result.receipt.targetSessionId,
|
|
1451
|
+
targetTurnId: result.receipt.targetTurnId,
|
|
1452
|
+
appliedControlRevision: result.receipt.appliedControlRevision,
|
|
1453
|
+
appliedQueueVersion: result.receipt.appliedQueueVersion,
|
|
1454
|
+
appliedTurnVersion: result.receipt.appliedTurnVersion,
|
|
1455
|
+
appliedDraftRevision: result.receipt.appliedDraftRevision,
|
|
1456
|
+
createdAt: result.receipt.createdAt.toISOString(),
|
|
1457
|
+
},
|
|
1458
|
+
routing: result.routing,
|
|
1459
|
+
draft: result.draft
|
|
1460
|
+
? {
|
|
1461
|
+
revision: result.draft.revision,
|
|
1462
|
+
text: result.draft.text,
|
|
1463
|
+
annotations: DraftTimelineAnnotations.parse(result.draft.annotations),
|
|
1464
|
+
resources: result.draft.resources as ResourceRef[],
|
|
1465
|
+
model: result.draft.model,
|
|
1466
|
+
reasoningEffort: result.draft.reasoningEffort as ReasoningEffort,
|
|
1467
|
+
latencyMode: result.draft.latencyMode as ComposerDraft["latencyMode"],
|
|
1468
|
+
sourceTurnId: result.draft.sourceTurnId,
|
|
1469
|
+
sourceTurnVersion: result.draft.sourceTurnVersion,
|
|
1470
|
+
updatedAt: result.draft.updatedAt.toISOString(),
|
|
1471
|
+
}
|
|
1472
|
+
: null,
|
|
1473
|
+
interruptionCount: result.interruptionCount,
|
|
1474
|
+
replay: result.replay,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
export async function postUserMessageTurn(
|
|
1479
|
+
input: PostUserMessageTurnInput,
|
|
1480
|
+
): Promise<PostUserMessageTurnResult> {
|
|
1481
|
+
const { db, settings, accountId, workspaceId, sessionId } = input;
|
|
1290
1482
|
const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
|
|
1291
1483
|
const requestedReasoningEffort = input.reasoningEffort ?? null;
|
|
1292
1484
|
// Reject an explicit per-message model the host does not expose; an omitted
|
|
1293
1485
|
// model inherits the session's model downstream (always a configured id).
|
|
1294
1486
|
assertConfiguredModel(settings, requestedModel);
|
|
1295
|
-
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, requestedModel);
|
|
1296
1487
|
const sessionForModelGate = await requireSession(db, workspaceId, sessionId);
|
|
1297
1488
|
const effectiveModelForGate = requestedModel ?? sessionForModelGate.model;
|
|
1489
|
+
const freshWorkspaceCustomModel =
|
|
1490
|
+
requestedModel !== null &&
|
|
1491
|
+
isWorkspaceCustomModelId(settings, requestedModel) &&
|
|
1492
|
+
requestedModel !== sessionForModelGate.model
|
|
1493
|
+
? requestedModel
|
|
1494
|
+
: null;
|
|
1495
|
+
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, effectiveModelForGate);
|
|
1298
1496
|
try {
|
|
1299
1497
|
assertSessionAllowsProductModel(sessionForModelGate, effectiveModelForGate);
|
|
1300
1498
|
} catch (error) {
|
|
@@ -1329,6 +1527,9 @@ export async function postUserMessageTurn(input: {
|
|
|
1329
1527
|
subjectId: input.actor ?? accountId,
|
|
1330
1528
|
},
|
|
1331
1529
|
operationKey,
|
|
1530
|
+
...(input.boundaryRequestHash
|
|
1531
|
+
? { boundaryRequestHash: input.boundaryRequestHash }
|
|
1532
|
+
: {}),
|
|
1332
1533
|
delivery: input.delivery ?? "send",
|
|
1333
1534
|
controlEtag: input.controlEtag ?? null,
|
|
1334
1535
|
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
@@ -1356,6 +1557,36 @@ export async function postUserMessageTurn(input: {
|
|
|
1356
1557
|
}
|
|
1357
1558
|
: {}),
|
|
1358
1559
|
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
1560
|
+
...(freshWorkspaceCustomModel
|
|
1561
|
+
? {
|
|
1562
|
+
beforeFreshPromptCommit: async (tx: Database): Promise<void> => {
|
|
1563
|
+
const reference = workspaceCustomModelReference(
|
|
1564
|
+
settings,
|
|
1565
|
+
freshWorkspaceCustomModel,
|
|
1566
|
+
);
|
|
1567
|
+
if (!reference) {
|
|
1568
|
+
throw new Error("workspace custom model reference disappeared");
|
|
1569
|
+
}
|
|
1570
|
+
const active =
|
|
1571
|
+
reference.providerKind === "openrouter"
|
|
1572
|
+
? await lockActiveWorkspaceOpenRouterCustomModelForAdmission(tx, {
|
|
1573
|
+
accountId,
|
|
1574
|
+
workspaceId,
|
|
1575
|
+
upstreamModelId: reference.upstreamModelId,
|
|
1576
|
+
})
|
|
1577
|
+
: await lockActiveWorkspaceGatewayCustomModelForAdmission(tx, {
|
|
1578
|
+
accountId,
|
|
1579
|
+
workspaceId,
|
|
1580
|
+
upstreamModelId: reference.upstreamModelId,
|
|
1581
|
+
});
|
|
1582
|
+
if (!active) {
|
|
1583
|
+
throw new HTTPException(422, {
|
|
1584
|
+
message: `model is not available: ${freshWorkspaceCustomModel}`,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
}
|
|
1589
|
+
: {}),
|
|
1359
1590
|
controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
|
|
1360
1591
|
}),
|
|
1361
1592
|
),
|
|
@@ -1386,101 +1617,7 @@ export async function postUserMessageTurn(input: {
|
|
|
1386
1617
|
}
|
|
1387
1618
|
throw error;
|
|
1388
1619
|
}
|
|
1389
|
-
|
|
1390
|
-
await Promise.all([
|
|
1391
|
-
(async () => {
|
|
1392
|
-
try {
|
|
1393
|
-
await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
|
|
1394
|
-
if (result.workspaceControlEventId) {
|
|
1395
|
-
const controlEvent = await getWorkspaceControlEvent(
|
|
1396
|
-
db,
|
|
1397
|
-
workspaceId,
|
|
1398
|
-
result.workspaceControlEventId,
|
|
1399
|
-
);
|
|
1400
|
-
if (!controlEvent) {
|
|
1401
|
-
throw new Error(
|
|
1402
|
-
`Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
|
|
1403
|
-
);
|
|
1404
|
-
}
|
|
1405
|
-
await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
|
|
1406
|
-
}
|
|
1407
|
-
} catch {
|
|
1408
|
-
console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
|
|
1409
|
-
errorClass: "PromptEventFanoutOperationError",
|
|
1410
|
-
errorCode: "session_prompt_event_fanout_failed",
|
|
1411
|
-
origin: "core",
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
})(),
|
|
1415
|
-
(async () => {
|
|
1416
|
-
try {
|
|
1417
|
-
await workflowClient.wakeSessionWorkflow({
|
|
1418
|
-
accountId,
|
|
1419
|
-
workspaceId,
|
|
1420
|
-
sessionId,
|
|
1421
|
-
workflowId: result.turn.temporalWorkflowId,
|
|
1422
|
-
wakeRevision: result.wakeRevision,
|
|
1423
|
-
...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
|
|
1424
|
-
? { interruptionRequested: true }
|
|
1425
|
-
: {}),
|
|
1426
|
-
});
|
|
1427
|
-
} catch {
|
|
1428
|
-
console.warn("[sessions] workflow wake failed; durable outbox will retry", {
|
|
1429
|
-
errorClass: "WorkflowWakeOperationError",
|
|
1430
|
-
errorCode: "session_workflow_wake_failed",
|
|
1431
|
-
origin: "core",
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
})(),
|
|
1435
|
-
]);
|
|
1436
|
-
};
|
|
1437
|
-
const schedulePostCommit =
|
|
1438
|
-
input.schedulePostCommit ??
|
|
1439
|
-
((task: () => Promise<void>) => {
|
|
1440
|
-
void task();
|
|
1441
|
-
});
|
|
1442
|
-
try {
|
|
1443
|
-
schedulePostCommit(postCommitTask);
|
|
1444
|
-
} catch {
|
|
1445
|
-
console.warn("[sessions] prompt post-commit scheduling failed; durable recovery remains", {
|
|
1446
|
-
errorClass: "PromptPostCommitScheduleError",
|
|
1447
|
-
errorCode: "session_prompt_post_commit_schedule_failed",
|
|
1448
|
-
origin: "core",
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
return {
|
|
1452
|
-
accepted: result.accepted,
|
|
1453
|
-
turn: result.turn,
|
|
1454
|
-
receipt: {
|
|
1455
|
-
id: result.receipt.id,
|
|
1456
|
-
action: result.receipt.action,
|
|
1457
|
-
operationKey: result.receipt.operationKey,
|
|
1458
|
-
targetSessionId: result.receipt.targetSessionId,
|
|
1459
|
-
targetTurnId: result.receipt.targetTurnId,
|
|
1460
|
-
appliedControlRevision: result.receipt.appliedControlRevision,
|
|
1461
|
-
appliedQueueVersion: result.receipt.appliedQueueVersion,
|
|
1462
|
-
appliedTurnVersion: result.receipt.appliedTurnVersion,
|
|
1463
|
-
appliedDraftRevision: result.receipt.appliedDraftRevision,
|
|
1464
|
-
createdAt: result.receipt.createdAt.toISOString(),
|
|
1465
|
-
},
|
|
1466
|
-
routing: result.routing,
|
|
1467
|
-
draft: result.draft
|
|
1468
|
-
? {
|
|
1469
|
-
revision: result.draft.revision,
|
|
1470
|
-
text: result.draft.text,
|
|
1471
|
-
annotations: DraftTimelineAnnotations.parse(result.draft.annotations),
|
|
1472
|
-
resources: result.draft.resources as ResourceRef[],
|
|
1473
|
-
model: result.draft.model,
|
|
1474
|
-
reasoningEffort: result.draft.reasoningEffort as ReasoningEffort,
|
|
1475
|
-
latencyMode: result.draft.latencyMode as ComposerDraft["latencyMode"],
|
|
1476
|
-
sourceTurnId: result.draft.sourceTurnId,
|
|
1477
|
-
sourceTurnVersion: result.draft.sourceTurnVersion,
|
|
1478
|
-
updatedAt: result.draft.updatedAt.toISOString(),
|
|
1479
|
-
}
|
|
1480
|
-
: null,
|
|
1481
|
-
interruptionCount: result.interruptionCount,
|
|
1482
|
-
replay: result.replay,
|
|
1483
|
-
};
|
|
1620
|
+
return finalizePostUserMessageTurn(input, result);
|
|
1484
1621
|
}
|
|
1485
1622
|
|
|
1486
1623
|
/**
|
|
@@ -1532,16 +1669,92 @@ export function resolveSessionCreateVisibility(input: {
|
|
|
1532
1669
|
return input.requestedVisibility === "private" ? "user_private" : "workspace_shared";
|
|
1533
1670
|
}
|
|
1534
1671
|
|
|
1672
|
+
async function resolveWorkspaceModelBoundarySettings(
|
|
1673
|
+
deps: Pick<ApiRouteDeps, "db" | "settings" | "catalogSourceSettings">,
|
|
1674
|
+
grant: AccessGrant,
|
|
1675
|
+
workspaceId: string,
|
|
1676
|
+
modelIds: readonly (string | null | undefined)[],
|
|
1677
|
+
retainedProductModelId?: string | null,
|
|
1678
|
+
): Promise<Settings> {
|
|
1679
|
+
const retainedWorkspaceModel =
|
|
1680
|
+
retainedProductModelId?.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) === true ||
|
|
1681
|
+
retainedProductModelId?.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX) === true;
|
|
1682
|
+
if (deps.catalogSourceSettings) {
|
|
1683
|
+
// The adapter already resolved one exact workspace catalog snapshot for
|
|
1684
|
+
// this request. Preserve it for fresh selections, but an existing session
|
|
1685
|
+
// may name a retired custom model that the active-only adapter snapshot
|
|
1686
|
+
// intentionally omitted. Re-open only the unoverlaid source for that
|
|
1687
|
+
// retention lookup; never feed the synthetic workspace provider back
|
|
1688
|
+
// through deployment validation.
|
|
1689
|
+
if (!retainedWorkspaceModel) return deps.settings;
|
|
1690
|
+
}
|
|
1691
|
+
const workspaceModelIds = modelIds.filter(
|
|
1692
|
+
(modelId): modelId is string =>
|
|
1693
|
+
typeof modelId === "string" &&
|
|
1694
|
+
(modelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
|
|
1695
|
+
modelId.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX)),
|
|
1696
|
+
);
|
|
1697
|
+
const needsWorkspaceResolution =
|
|
1698
|
+
deps.settings.modelCatalogSource === "database" || workspaceModelIds.length > 0;
|
|
1699
|
+
if (!needsWorkspaceResolution) return deps.settings;
|
|
1700
|
+
return (
|
|
1701
|
+
await resolveWorkspaceCatalogSettings(deps.db, deps.catalogSourceSettings ?? deps.settings, {
|
|
1702
|
+
accountId: grant.accountId,
|
|
1703
|
+
workspaceId,
|
|
1704
|
+
...(retainedProductModelId !== undefined ? { retainedProductModelId } : {}),
|
|
1705
|
+
})
|
|
1706
|
+
).settings;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
async function withSessionCreateUsageRecording(input: {
|
|
1710
|
+
deps: ApiRouteDeps;
|
|
1711
|
+
grant: AccessGrant;
|
|
1712
|
+
workspaceId: string;
|
|
1713
|
+
startMode: "realtime" | undefined;
|
|
1714
|
+
origin: "system" | "user";
|
|
1715
|
+
createOutcome: CreateSessionOutcome;
|
|
1716
|
+
}): Promise<CreateSessionRequestOutcome> {
|
|
1717
|
+
let usageRecording: CreateSessionRequestOutcome["usageRecording"] = "recorded";
|
|
1718
|
+
if (input.startMode !== "realtime") {
|
|
1719
|
+
try {
|
|
1720
|
+
await recordWorkspaceUsage(input.deps, {
|
|
1721
|
+
accountId: input.grant.accountId,
|
|
1722
|
+
workspaceId: input.workspaceId,
|
|
1723
|
+
subjectId: input.grant.subjectId,
|
|
1724
|
+
eventType: "agent_run.created",
|
|
1725
|
+
quantity: 1,
|
|
1726
|
+
unit: "run",
|
|
1727
|
+
sourceResourceType: "session",
|
|
1728
|
+
sourceResourceId: input.createOutcome.session.id,
|
|
1729
|
+
sessionId: input.createOutcome.session.id,
|
|
1730
|
+
initiator: input.createOutcome.session.createdBy,
|
|
1731
|
+
initiatorContext: input.createOutcome.session.createdByContext,
|
|
1732
|
+
origin: input.origin,
|
|
1733
|
+
idempotencyKey: `agent_run.created:${input.workspaceId}:${input.createOutcome.session.id}`,
|
|
1734
|
+
});
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
usageRecording = "failed";
|
|
1737
|
+
reportSessionUsageRecordingFailure(error);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return { ...input.createOutcome, usageRecording };
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1535
1743
|
export async function createSessionForRequestWithOutcome(
|
|
1536
|
-
|
|
1744
|
+
unresolvedDeps: ApiRouteDeps,
|
|
1537
1745
|
grant: AccessGrant,
|
|
1538
1746
|
workspaceId: string,
|
|
1539
1747
|
rawPayload: unknown,
|
|
1540
1748
|
authorization?: AccessGrantAuthorization,
|
|
1541
1749
|
agentChildPresentation?: AgentChildSessionCreatePresentation,
|
|
1542
1750
|
): Promise<CreateSessionRequestOutcome> {
|
|
1543
|
-
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
1544
1751
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
1752
|
+
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
1753
|
+
throw new HTTPException(422, {
|
|
1754
|
+
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY} is reserved for scheduler routing`,
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
const db = unresolvedDeps.db;
|
|
1545
1758
|
const visibilityProvided = hasOwnProperty(rawPayload, "visibility");
|
|
1546
1759
|
if (payload.visibility === "private" && !grant.metadata?.["sessionId"]) {
|
|
1547
1760
|
if (!authorization) {
|
|
@@ -1549,39 +1762,13 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1549
1762
|
message: "managed human session required",
|
|
1550
1763
|
});
|
|
1551
1764
|
}
|
|
1552
|
-
await requireManagedHumanPrivateSessionCreate(
|
|
1765
|
+
await requireManagedHumanPrivateSessionCreate(unresolvedDeps, authorization, workspaceId);
|
|
1553
1766
|
if (payload.sandbox === "shared" || typeof payload.sandbox === "object") {
|
|
1554
1767
|
throw new HTTPException(422, {
|
|
1555
1768
|
message: "Only-me sessions require their own sandbox",
|
|
1556
1769
|
});
|
|
1557
1770
|
}
|
|
1558
1771
|
}
|
|
1559
|
-
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
1560
|
-
throw new HTTPException(422, {
|
|
1561
|
-
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY} is reserved for scheduler routing`,
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
// A committed keyed denial is the idempotent outcome even if mutable
|
|
1565
|
-
// resources, policy, authorization, or budget have changed since the first
|
|
1566
|
-
// attempt. Replay it before any of those checks, just as a keyed successful
|
|
1567
|
-
// session is returned rather than recreated later in createAndStartSession.
|
|
1568
|
-
if (payload.idempotencyKey) {
|
|
1569
|
-
const denial = await getSessionSpawnDenialByIdempotencyKey(
|
|
1570
|
-
db,
|
|
1571
|
-
workspaceId,
|
|
1572
|
-
payload.idempotencyKey,
|
|
1573
|
-
);
|
|
1574
|
-
if (denial) {
|
|
1575
|
-
throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(denial));
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
await requireAtomicPersonalResourceAttachment(
|
|
1579
|
-
deps,
|
|
1580
|
-
authorization,
|
|
1581
|
-
workspaceId,
|
|
1582
|
-
payload.personalResourceAttachment,
|
|
1583
|
-
false,
|
|
1584
|
-
);
|
|
1585
1772
|
// Parent linkage and execution-context inheritance come ONLY from the
|
|
1586
1773
|
// worker-signed sessionId claim. A caller cannot nominate a parent in the
|
|
1587
1774
|
// payload, so inheriting an existing repository/tool/credential snapshot does
|
|
@@ -1592,7 +1779,7 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1592
1779
|
: null;
|
|
1593
1780
|
if (parentSessionId) {
|
|
1594
1781
|
try {
|
|
1595
|
-
await requireSessionAuthorization(
|
|
1782
|
+
await requireSessionAuthorization(unresolvedDeps, grant, {
|
|
1596
1783
|
sessionId: parentSessionId,
|
|
1597
1784
|
operation: "session.child.create",
|
|
1598
1785
|
surface: "core",
|
|
@@ -1650,6 +1837,117 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1650
1837
|
message: "caller attempt does not belong to the parent session",
|
|
1651
1838
|
});
|
|
1652
1839
|
}
|
|
1840
|
+
const replayManagedHumanSubjectId = creationInitiator.actor
|
|
1841
|
+
? (parentCallingTurn?.initiatingHumanSubjectId ??
|
|
1842
|
+
(parentCallingTurn?.initiator.kind === "subject"
|
|
1843
|
+
? parentCallingTurn.initiator.subjectId
|
|
1844
|
+
: null))
|
|
1845
|
+
: authorization?.canonicalManagedHumanSession || grant.principalKind === "human_session"
|
|
1846
|
+
? grant.subjectId
|
|
1847
|
+
: null;
|
|
1848
|
+
let retainedKeyedShellModel: string | null = null;
|
|
1849
|
+
if (
|
|
1850
|
+
payload.idempotencyKey &&
|
|
1851
|
+
(effectiveVisibility !== "user_private" || replayManagedHumanSubjectId !== null)
|
|
1852
|
+
) {
|
|
1853
|
+
try {
|
|
1854
|
+
const initializedReplay = await getInitializedSessionCreateReplay(db, {
|
|
1855
|
+
accountId: grant.accountId,
|
|
1856
|
+
workspaceId,
|
|
1857
|
+
subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
|
|
1858
|
+
...(replayManagedHumanSubjectId
|
|
1859
|
+
? { activeManagedHumanSubjectId: replayManagedHumanSubjectId }
|
|
1860
|
+
: {}),
|
|
1861
|
+
createIdempotencyKey: payload.idempotencyKey,
|
|
1862
|
+
...(payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {}),
|
|
1863
|
+
visibility: effectiveVisibility,
|
|
1864
|
+
variableSetIds: payload.variableSetIds ?? [],
|
|
1865
|
+
initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
|
|
1866
|
+
deferInitialTurn: payload.startMode === "realtime",
|
|
1867
|
+
});
|
|
1868
|
+
if (initializedReplay) {
|
|
1869
|
+
if (initializedReplay.outcome === "denied") {
|
|
1870
|
+
throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(initializedReplay.denial));
|
|
1871
|
+
}
|
|
1872
|
+
if (initializedReplay.outcome === "pending") {
|
|
1873
|
+
retainedKeyedShellModel = initializedReplay.session.model;
|
|
1874
|
+
} else {
|
|
1875
|
+
if (initializedReplay.workflowWakeRevision !== null) {
|
|
1876
|
+
await unresolvedDeps.workflowClient.wakeSessionWorkflow({
|
|
1877
|
+
accountId: grant.accountId,
|
|
1878
|
+
workspaceId,
|
|
1879
|
+
sessionId: initializedReplay.session.id,
|
|
1880
|
+
workflowId: initializedReplay.temporalWorkflowId,
|
|
1881
|
+
wakeRevision: initializedReplay.workflowWakeRevision,
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
return await withSessionCreateUsageRecording({
|
|
1885
|
+
deps: unresolvedDeps,
|
|
1886
|
+
grant,
|
|
1887
|
+
workspaceId,
|
|
1888
|
+
startMode: payload.startMode,
|
|
1889
|
+
origin: creationInitiator.actor ? "system" : "user",
|
|
1890
|
+
createOutcome: {
|
|
1891
|
+
session: initializedReplay.session,
|
|
1892
|
+
outcome: initializedReplay.changed ? "repaired" : "replayed",
|
|
1893
|
+
replay: !initializedReplay.changed,
|
|
1894
|
+
changed: initializedReplay.changed,
|
|
1895
|
+
},
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
if (error instanceof SessionIdConflictError) {
|
|
1901
|
+
throw new HTTPException(409, {
|
|
1902
|
+
message: "requested session id is already in use",
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
if (error instanceof SessionCreateIdempotencyConflictError) {
|
|
1906
|
+
throw new HTTPException(409, { message: error.message, cause: error });
|
|
1907
|
+
}
|
|
1908
|
+
throw error;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
let settings = await resolveWorkspaceModelBoundarySettings(
|
|
1912
|
+
unresolvedDeps,
|
|
1913
|
+
grant,
|
|
1914
|
+
workspaceId,
|
|
1915
|
+
[payload.model],
|
|
1916
|
+
retainedKeyedShellModel,
|
|
1917
|
+
);
|
|
1918
|
+
let deps =
|
|
1919
|
+
settings === unresolvedDeps.settings
|
|
1920
|
+
? unresolvedDeps
|
|
1921
|
+
: {
|
|
1922
|
+
...unresolvedDeps,
|
|
1923
|
+
catalogSourceSettings: unresolvedDeps.catalogSourceSettings ?? unresolvedDeps.settings,
|
|
1924
|
+
settings,
|
|
1925
|
+
};
|
|
1926
|
+
const { bus, workflowClient, objectStorage } = deps;
|
|
1927
|
+
await requireAtomicPersonalResourceAttachment(
|
|
1928
|
+
deps,
|
|
1929
|
+
authorization,
|
|
1930
|
+
workspaceId,
|
|
1931
|
+
payload.personalResourceAttachment,
|
|
1932
|
+
false,
|
|
1933
|
+
);
|
|
1934
|
+
const inheritedModel = parentCallingTurn?.model ?? parentSession?.model ?? settings.openaiModel;
|
|
1935
|
+
const effectiveModelId = payload.model ?? inheritedModel;
|
|
1936
|
+
const effectiveCatalogSettings = await resolveWorkspaceModelBoundarySettings(
|
|
1937
|
+
deps,
|
|
1938
|
+
grant,
|
|
1939
|
+
workspaceId,
|
|
1940
|
+
[effectiveModelId],
|
|
1941
|
+
parentSession ? inheritedModel : null,
|
|
1942
|
+
);
|
|
1943
|
+
if (effectiveCatalogSettings !== settings) {
|
|
1944
|
+
deps = {
|
|
1945
|
+
...deps,
|
|
1946
|
+
catalogSourceSettings: deps.catalogSourceSettings ?? settings,
|
|
1947
|
+
settings: effectiveCatalogSettings,
|
|
1948
|
+
};
|
|
1949
|
+
settings = effectiveCatalogSettings;
|
|
1950
|
+
}
|
|
1653
1951
|
let effectiveGoal = payload.goal;
|
|
1654
1952
|
if (parentSession && payload.goal) {
|
|
1655
1953
|
try {
|
|
@@ -1860,8 +2158,7 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1860
2158
|
// managers: falling back to the deployment model would silently move a child
|
|
1861
2159
|
// onto the OpenGeni-credits billing path. Legacy session-bound grants without
|
|
1862
2160
|
// exact attempt claims fall back to the parent session's persisted defaults.
|
|
1863
|
-
const
|
|
1864
|
-
const model = canonicalConfiguredModel(settings, payload.model ?? inheritedModel);
|
|
2161
|
+
const model = canonicalConfiguredModel(settings, effectiveModelId);
|
|
1865
2162
|
if (model === null || model === undefined) {
|
|
1866
2163
|
throw new Error("effective session model unexpectedly resolved to null");
|
|
1867
2164
|
}
|
|
@@ -2405,6 +2702,8 @@ export async function createSessionForRequestWithOutcome(
|
|
|
2405
2702
|
sessionMcpServers: sessionMcpServers.metadata,
|
|
2406
2703
|
personalConnectionDelegations,
|
|
2407
2704
|
initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
|
|
2705
|
+
workspaceCustomModel: isWorkspaceCustomModelId(settings, model),
|
|
2706
|
+
retainWorkspaceCustomModel: parentSession !== null && model === inheritedModel,
|
|
2408
2707
|
...(xaiProviderAccountAuthoritySnapshot ? { xaiProviderAccountAuthoritySnapshot } : {}),
|
|
2409
2708
|
parentSessionId,
|
|
2410
2709
|
createIdempotencyKey: payload.idempotencyKey ?? null,
|
|
@@ -2474,30 +2773,14 @@ export async function createSessionForRequestWithOutcome(
|
|
|
2474
2773
|
}
|
|
2475
2774
|
throw error;
|
|
2476
2775
|
}
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
quantity: 1,
|
|
2486
|
-
unit: "run",
|
|
2487
|
-
sourceResourceType: "session",
|
|
2488
|
-
sourceResourceId: createOutcome.session.id,
|
|
2489
|
-
sessionId: createOutcome.session.id,
|
|
2490
|
-
initiator: createOutcome.session.createdBy,
|
|
2491
|
-
initiatorContext: createOutcome.session.createdByContext,
|
|
2492
|
-
origin: creationInitiator.actor ? "system" : "user",
|
|
2493
|
-
idempotencyKey: `agent_run.created:${workspaceId}:${createOutcome.session.id}`,
|
|
2494
|
-
});
|
|
2495
|
-
} catch (error) {
|
|
2496
|
-
usageRecording = "failed";
|
|
2497
|
-
reportSessionUsageRecordingFailure(error);
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
return { ...createOutcome, usageRecording };
|
|
2776
|
+
return await withSessionCreateUsageRecording({
|
|
2777
|
+
deps,
|
|
2778
|
+
grant,
|
|
2779
|
+
workspaceId,
|
|
2780
|
+
startMode: payload.startMode,
|
|
2781
|
+
origin: creationInitiator.actor ? "system" : "user",
|
|
2782
|
+
createOutcome,
|
|
2783
|
+
});
|
|
2501
2784
|
}
|
|
2502
2785
|
|
|
2503
2786
|
/** @internal Fixed public projection; the committed session outcome remains authoritative. */
|
|
@@ -2525,6 +2808,53 @@ export async function createSessionForRequest(
|
|
|
2525
2808
|
).session;
|
|
2526
2809
|
}
|
|
2527
2810
|
|
|
2811
|
+
function sessionPromptBoundaryRequestHash(input: {
|
|
2812
|
+
delivery: "send" | "steer";
|
|
2813
|
+
controlEtag: string | null;
|
|
2814
|
+
expectedDraftRevision: number | null;
|
|
2815
|
+
text: string;
|
|
2816
|
+
annotations: SubmittedTimelineAnnotation[];
|
|
2817
|
+
modelContext: string | null;
|
|
2818
|
+
resources: ResourceRef[];
|
|
2819
|
+
composerDraftResources?: ResourceRef[];
|
|
2820
|
+
model: string | null;
|
|
2821
|
+
reasoningEffort: ReasoningEffort | null;
|
|
2822
|
+
latencyMode: "standard" | "priority" | "fast" | null;
|
|
2823
|
+
source: "user" | "api";
|
|
2824
|
+
mcpCredentialUpdates: SessionMcpCredentialUpdateInput[];
|
|
2825
|
+
connectionAuthorities?: McpConnectionAuthoritySelection[];
|
|
2826
|
+
personalResourceAttachment?: PersonalResourceAttachmentIntent;
|
|
2827
|
+
commandActor: SessionCommandActor;
|
|
2828
|
+
}): string {
|
|
2829
|
+
return `prompt-boundary-v1:${canonicalSessionCommandHash({
|
|
2830
|
+
delivery: input.delivery,
|
|
2831
|
+
controlEtag: input.controlEtag,
|
|
2832
|
+
expectedDraftRevision: input.expectedDraftRevision,
|
|
2833
|
+
text: input.text,
|
|
2834
|
+
annotations: input.annotations,
|
|
2835
|
+
modelContext: input.modelContext,
|
|
2836
|
+
resources: input.resources,
|
|
2837
|
+
composerDraftResourcesProvided: input.composerDraftResources !== undefined,
|
|
2838
|
+
composerDraftResources: input.composerDraftResources ?? [],
|
|
2839
|
+
model: input.model,
|
|
2840
|
+
reasoningEffort: input.reasoningEffort,
|
|
2841
|
+
latencyMode: input.latencyMode,
|
|
2842
|
+
source: input.source,
|
|
2843
|
+
mcpCredentialUpdates: input.mcpCredentialUpdates,
|
|
2844
|
+
connectionAuthorities: input.connectionAuthorities ?? [],
|
|
2845
|
+
personalResourceAttachment: input.personalResourceAttachment ?? null,
|
|
2846
|
+
...(input.commandActor.type === "service"
|
|
2847
|
+
? {
|
|
2848
|
+
serviceInitiator: {
|
|
2849
|
+
subjectId: input.commandActor.subjectId,
|
|
2850
|
+
subjectLabel: input.commandActor.subjectLabel ?? null,
|
|
2851
|
+
context: input.commandActor.context ?? {},
|
|
2852
|
+
},
|
|
2853
|
+
}
|
|
2854
|
+
: {}),
|
|
2855
|
+
})}`;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2528
2858
|
/**
|
|
2529
2859
|
* Full accept-user-message flow shared by the `user.message` branch of
|
|
2530
2860
|
* `POST /sessions/:id/events` and the first-party MCP `session_send_message`
|
|
@@ -2566,192 +2896,308 @@ export async function acceptSessionUserMessageWithOutcome(
|
|
|
2566
2896
|
interruptionCount: number;
|
|
2567
2897
|
replay: boolean;
|
|
2568
2898
|
}> {
|
|
2569
|
-
const {
|
|
2899
|
+
const { db, bus, workflowClient, objectStorage } = deps;
|
|
2570
2900
|
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
2901
|
+
const delivery = input.delivery ?? "send";
|
|
2902
|
+
const source = delegatedServiceInitiator || input.origin === "operator" ? "api" : "user";
|
|
2903
|
+
const commandActor: SessionCommandActor = delegatedServiceInitiator
|
|
2904
|
+
? {
|
|
2905
|
+
type: "service",
|
|
2906
|
+
subjectId: delegatedServiceInitiator.initiator.subjectId,
|
|
2907
|
+
...(delegatedServiceInitiator.initiator.label
|
|
2908
|
+
? { subjectLabel: delegatedServiceInitiator.initiator.label }
|
|
2909
|
+
: {}),
|
|
2910
|
+
context: delegatedServiceInitiator.context,
|
|
2911
|
+
}
|
|
2912
|
+
: { type: "human", subjectId: grant.subjectId };
|
|
2571
2913
|
await requireSessionAuthorization(deps, grant, {
|
|
2572
2914
|
sessionId,
|
|
2573
|
-
operation:
|
|
2915
|
+
operation: delivery === "steer" ? "session.steer" : "session.append",
|
|
2574
2916
|
surface: "core",
|
|
2575
2917
|
});
|
|
2576
|
-
await requireAtomicPersonalResourceAttachment(
|
|
2577
|
-
deps,
|
|
2578
|
-
input.authorization,
|
|
2579
|
-
workspaceId,
|
|
2580
|
-
input.personalResourceAttachment,
|
|
2581
|
-
true,
|
|
2582
|
-
);
|
|
2583
|
-
// Hoisted above requireLimit so the codex-billed predicate can resolve the
|
|
2584
|
-
// turn's effective model (a follow-up turn inherits the session's model). A
|
|
2585
|
-
// pure read with no side effects.
|
|
2586
|
-
const existingSession = await requireSession(db, workspaceId, sessionId);
|
|
2587
|
-
const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
|
|
2588
|
-
const effectiveModel =
|
|
2589
|
-
canonicalConfiguredModel(settings, requestedModel ?? existingSession.model) ?? null;
|
|
2590
|
-
if (effectiveModel === null) {
|
|
2591
|
-
throw new Error("effective follow-up model unexpectedly resolved to null");
|
|
2592
|
-
}
|
|
2593
|
-
try {
|
|
2594
|
-
assertSessionAllowsProductModel(existingSession, effectiveModel);
|
|
2595
|
-
} catch (error) {
|
|
2596
|
-
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
2597
|
-
throw new HTTPException(422, { message: error.message, cause: error });
|
|
2598
|
-
}
|
|
2599
|
-
throw error;
|
|
2600
|
-
}
|
|
2601
|
-
const sessionReasoningEffort = existingSession.reasoningEffort;
|
|
2602
|
-
const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
|
|
2603
|
-
const sessionLatencyMode = existingSession.latencyMode;
|
|
2604
|
-
const effectiveLatencyMode = input.latencyMode ?? sessionLatencyMode;
|
|
2605
|
-
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
2606
|
-
modelId: effectiveModel,
|
|
2607
|
-
requestedModelId: input.model ?? null,
|
|
2608
|
-
modelSource: input.model == null ? "session" : "explicit",
|
|
2609
|
-
reasoningEffort: effectiveReasoningEffort,
|
|
2610
|
-
reasoningSource: input.reasoningEffort == null ? "session" : "explicit",
|
|
2611
|
-
latencyMode: effectiveLatencyMode,
|
|
2612
|
-
latencyModeSource: input.latencyMode == null ? "session" : "explicit",
|
|
2613
|
-
});
|
|
2614
2918
|
const requestedResources = normalizeResources(input.resources ?? []);
|
|
2615
2919
|
const composerDraftResources = input.composerDraftResources
|
|
2616
2920
|
? normalizeResources(input.composerDraftResources)
|
|
2617
2921
|
: undefined;
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2922
|
+
const boundaryRequestHash = input.clientEventId
|
|
2923
|
+
? sessionPromptBoundaryRequestHash({
|
|
2924
|
+
delivery,
|
|
2925
|
+
controlEtag: input.controlEtag ?? null,
|
|
2926
|
+
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
2927
|
+
text: input.text,
|
|
2928
|
+
annotations: input.annotations ?? [],
|
|
2929
|
+
modelContext: input.modelContext ?? null,
|
|
2930
|
+
resources: requestedResources,
|
|
2931
|
+
...(composerDraftResources ? { composerDraftResources } : {}),
|
|
2932
|
+
model: input.model ?? null,
|
|
2933
|
+
reasoningEffort: input.reasoningEffort ?? null,
|
|
2934
|
+
latencyMode: input.latencyMode ?? null,
|
|
2935
|
+
source,
|
|
2936
|
+
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
2937
|
+
...(input.connectionAuthorities
|
|
2938
|
+
? { connectionAuthorities: input.connectionAuthorities }
|
|
2939
|
+
: {}),
|
|
2940
|
+
...(input.personalResourceAttachment
|
|
2941
|
+
? { personalResourceAttachment: input.personalResourceAttachment }
|
|
2942
|
+
: {}),
|
|
2943
|
+
commandActor,
|
|
2944
|
+
})
|
|
2945
|
+
: null;
|
|
2946
|
+
if (input.clientEventId && boundaryRequestHash) {
|
|
2947
|
+
const replay = await withWorkspaceSubjectSessionActivityRls(
|
|
2948
|
+
db,
|
|
2949
|
+
workspaceId,
|
|
2950
|
+
grant.subjectId,
|
|
2951
|
+
async (scopedDb) =>
|
|
2952
|
+
await replaySubmittedHumanPromptFromBoundaryReceipt(scopedDb, {
|
|
2953
|
+
workspaceId,
|
|
2954
|
+
sessionId,
|
|
2955
|
+
subjectId: grant.subjectId,
|
|
2956
|
+
actor: commandActor,
|
|
2957
|
+
operationKey: input.clientEventId!,
|
|
2958
|
+
delivery,
|
|
2959
|
+
boundaryRequestHash,
|
|
2960
|
+
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
2961
|
+
}),
|
|
2622
2962
|
);
|
|
2623
|
-
if (
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2963
|
+
if (replay) {
|
|
2964
|
+
return finalizePostUserMessageTurn(
|
|
2965
|
+
{
|
|
2966
|
+
db,
|
|
2967
|
+
bus,
|
|
2968
|
+
workflowClient,
|
|
2969
|
+
accountId: grant.accountId,
|
|
2970
|
+
workspaceId,
|
|
2971
|
+
sessionId,
|
|
2972
|
+
delivery,
|
|
2973
|
+
...(deps.schedulePromptPostCommit
|
|
2974
|
+
? { schedulePostCommit: deps.schedulePromptPostCommit }
|
|
2975
|
+
: {}),
|
|
2976
|
+
},
|
|
2977
|
+
replay,
|
|
2978
|
+
);
|
|
2627
2979
|
}
|
|
2628
2980
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2981
|
+
try {
|
|
2982
|
+
await requireAtomicPersonalResourceAttachment(
|
|
2983
|
+
deps,
|
|
2984
|
+
input.authorization,
|
|
2985
|
+
workspaceId,
|
|
2986
|
+
input.personalResourceAttachment,
|
|
2987
|
+
true,
|
|
2988
|
+
);
|
|
2989
|
+
// Hoisted above requireLimit so the codex-billed predicate can resolve the
|
|
2990
|
+
// turn's effective model (a follow-up turn inherits the session's model). A
|
|
2991
|
+
// pure read with no side effects.
|
|
2992
|
+
const existingSession = await requireSession(db, workspaceId, sessionId);
|
|
2993
|
+
const settings = await resolveWorkspaceModelBoundarySettings(
|
|
2994
|
+
deps,
|
|
2995
|
+
grant,
|
|
2996
|
+
workspaceId,
|
|
2997
|
+
[input.model ?? existingSession.model],
|
|
2998
|
+
existingSession.model,
|
|
2999
|
+
);
|
|
3000
|
+
if (settings !== deps.settings) {
|
|
3001
|
+
deps = {
|
|
3002
|
+
...deps,
|
|
3003
|
+
catalogSourceSettings: deps.catalogSourceSettings ?? deps.settings,
|
|
3004
|
+
settings,
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
|
|
3008
|
+
const effectiveModel =
|
|
3009
|
+
canonicalConfiguredModel(settings, requestedModel ?? existingSession.model) ?? null;
|
|
3010
|
+
if (effectiveModel === null) {
|
|
3011
|
+
throw new Error("effective follow-up model unexpectedly resolved to null");
|
|
3012
|
+
}
|
|
3013
|
+
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, requestedModel);
|
|
3014
|
+
try {
|
|
3015
|
+
assertSessionAllowsProductModel(existingSession, effectiveModel);
|
|
3016
|
+
} catch (error) {
|
|
3017
|
+
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
3018
|
+
throw new HTTPException(422, { message: error.message, cause: error });
|
|
3019
|
+
}
|
|
3020
|
+
throw error;
|
|
3021
|
+
}
|
|
3022
|
+
const sessionReasoningEffort = existingSession.reasoningEffort;
|
|
3023
|
+
const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
|
|
3024
|
+
const sessionLatencyMode = existingSession.latencyMode;
|
|
3025
|
+
const effectiveLatencyMode = input.latencyMode ?? sessionLatencyMode;
|
|
3026
|
+
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
3027
|
+
modelId: effectiveModel,
|
|
3028
|
+
requestedModelId: input.model ?? null,
|
|
3029
|
+
modelSource: input.model == null ? "session" : "explicit",
|
|
3030
|
+
reasoningEffort: effectiveReasoningEffort,
|
|
3031
|
+
reasoningSource: input.reasoningEffort == null ? "session" : "explicit",
|
|
3032
|
+
latencyMode: effectiveLatencyMode,
|
|
3033
|
+
latencyModeSource: input.latencyMode == null ? "session" : "explicit",
|
|
2645
3034
|
});
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
const mcpCredentialUpdates = validateSessionMcpCredentialUpdates({
|
|
2659
|
-
settings,
|
|
2660
|
-
grant,
|
|
2661
|
-
session: existingSession,
|
|
2662
|
-
updates: input.mcpCredentialUpdates ?? [],
|
|
2663
|
-
});
|
|
2664
|
-
const connectionDelegationSource = personalConnectionDelegationSourceForGrant(grant);
|
|
2665
|
-
const inheritedPersonalConnectionDelegations =
|
|
2666
|
-
connectionDelegationSource.kind === "turn"
|
|
2667
|
-
? await getSessionTurnPersonalConnectionDelegations(
|
|
2668
|
-
db,
|
|
2669
|
-
workspaceId,
|
|
2670
|
-
connectionDelegationSource.sessionId,
|
|
2671
|
-
connectionDelegationSource.turnId,
|
|
2672
|
-
)
|
|
2673
|
-
: null;
|
|
2674
|
-
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
2675
|
-
db,
|
|
2676
|
-
workspaceId,
|
|
2677
|
-
settings,
|
|
2678
|
-
inheritedPersonalConnectionDelegations
|
|
2679
|
-
? {
|
|
2680
|
-
personalConnectionDelegations: inheritedPersonalConnectionDelegations,
|
|
2681
|
-
}
|
|
2682
|
-
: { subjectId: grant.subjectId },
|
|
2683
|
-
);
|
|
2684
|
-
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
2685
|
-
db,
|
|
2686
|
-
workspaceId,
|
|
2687
|
-
settings: runtimeSettings,
|
|
2688
|
-
tools: existingSession.tools,
|
|
2689
|
-
resources: [...existingSession.resources, ...requestedResources],
|
|
2690
|
-
source: connectionDelegationSource,
|
|
2691
|
-
targetSessionId: sessionId,
|
|
2692
|
-
googleDrivePublicationEnabled:
|
|
2693
|
-
existingSession.firstPartyMcpTools.includes("editable_artifact_export") &&
|
|
2694
|
-
existingSession.firstPartyMcpTools.includes("editable_artifact_export_status") &&
|
|
2695
|
-
(!existingSession.firstPartyMcpPermissions?.length ||
|
|
2696
|
-
(existingSession.firstPartyMcpPermissions.includes("artifacts:read") &&
|
|
2697
|
-
existingSession.firstPartyMcpPermissions.includes("artifacts:publish"))),
|
|
2698
|
-
atlassianEnabled:
|
|
2699
|
-
existingSession.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) &&
|
|
2700
|
-
(!existingSession.firstPartyMcpPermissions?.length ||
|
|
2701
|
-
existingSession.firstPartyMcpPermissions.includes("connections:read")),
|
|
2702
|
-
...(input.connectionAuthorities ? { authoritySelections: input.connectionAuthorities } : {}),
|
|
2703
|
-
});
|
|
2704
|
-
const { accepted, turn, draft, receipt, routing, interruptionCount, replay } =
|
|
2705
|
-
await postUserMessageTurn({
|
|
3035
|
+
if (composerDraftResources) {
|
|
3036
|
+
const acceptedResources = new Set(requestedResources.map((resource) => stableJson(resource)));
|
|
3037
|
+
const unacceptedDraftResource = composerDraftResources.find(
|
|
3038
|
+
(resource) => !acceptedResources.has(stableJson(resource)),
|
|
3039
|
+
);
|
|
3040
|
+
if (unacceptedDraftResource) {
|
|
3041
|
+
throw new HTTPException(422, {
|
|
3042
|
+
message: "composer draft resources must be included in the accepted resource set",
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
const annotations = await validateSubmittedTimelineAnnotations(
|
|
2706
3047
|
db,
|
|
2707
|
-
bus,
|
|
2708
|
-
workflowClient,
|
|
2709
|
-
settings,
|
|
2710
|
-
accountId: grant.accountId,
|
|
2711
3048
|
workspaceId,
|
|
2712
3049
|
sessionId,
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
3050
|
+
input.annotations ?? [],
|
|
3051
|
+
);
|
|
3052
|
+
await requireLimit(deps, {
|
|
3053
|
+
accountId: grant.accountId,
|
|
3054
|
+
workspaceId,
|
|
3055
|
+
action: "agent_run:create",
|
|
3056
|
+
quantity: 1,
|
|
3057
|
+
model: effectiveModel,
|
|
3058
|
+
});
|
|
3059
|
+
if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
3060
|
+
throw new HTTPException(503, {
|
|
3061
|
+
message: "object storage is not configured",
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
await validateFileResources(
|
|
3065
|
+
db,
|
|
3066
|
+
grant.accountId,
|
|
3067
|
+
workspaceId,
|
|
3068
|
+
grant.subjectId,
|
|
3069
|
+
requestedResources,
|
|
3070
|
+
);
|
|
3071
|
+
await validateGitHubRepositorySelection(db, workspaceId, [
|
|
3072
|
+
...existingSession.resources,
|
|
3073
|
+
...requestedResources,
|
|
3074
|
+
]);
|
|
3075
|
+
const mcpCredentialUpdates = validateSessionMcpCredentialUpdates({
|
|
3076
|
+
settings,
|
|
3077
|
+
grant,
|
|
3078
|
+
session: existingSession,
|
|
3079
|
+
updates: input.mcpCredentialUpdates ?? [],
|
|
3080
|
+
});
|
|
3081
|
+
const connectionDelegationSource = personalConnectionDelegationSourceForGrant(grant);
|
|
3082
|
+
const inheritedPersonalConnectionDelegations =
|
|
3083
|
+
connectionDelegationSource.kind === "turn"
|
|
3084
|
+
? await getSessionTurnPersonalConnectionDelegations(
|
|
3085
|
+
db,
|
|
3086
|
+
workspaceId,
|
|
3087
|
+
connectionDelegationSource.sessionId,
|
|
3088
|
+
connectionDelegationSource.turnId,
|
|
3089
|
+
)
|
|
3090
|
+
: null;
|
|
3091
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
3092
|
+
db,
|
|
3093
|
+
workspaceId,
|
|
3094
|
+
settings,
|
|
3095
|
+
inheritedPersonalConnectionDelegations
|
|
2733
3096
|
? {
|
|
2734
|
-
|
|
2735
|
-
type: "service" as const,
|
|
2736
|
-
subjectId: delegatedServiceInitiator.initiator.subjectId,
|
|
2737
|
-
...(delegatedServiceInitiator.initiator.label
|
|
2738
|
-
? { subjectLabel: delegatedServiceInitiator.initiator.label }
|
|
2739
|
-
: {}),
|
|
2740
|
-
context: delegatedServiceInitiator.context,
|
|
2741
|
-
},
|
|
3097
|
+
personalConnectionDelegations: inheritedPersonalConnectionDelegations,
|
|
2742
3098
|
}
|
|
2743
|
-
: {}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
...
|
|
2751
|
-
|
|
2752
|
-
|
|
3099
|
+
: { subjectId: grant.subjectId },
|
|
3100
|
+
);
|
|
3101
|
+
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
3102
|
+
db,
|
|
3103
|
+
workspaceId,
|
|
3104
|
+
settings: runtimeSettings,
|
|
3105
|
+
tools: existingSession.tools,
|
|
3106
|
+
resources: [...existingSession.resources, ...requestedResources],
|
|
3107
|
+
source: connectionDelegationSource,
|
|
3108
|
+
targetSessionId: sessionId,
|
|
3109
|
+
googleDrivePublicationEnabled:
|
|
3110
|
+
existingSession.firstPartyMcpTools.includes("editable_artifact_export") &&
|
|
3111
|
+
existingSession.firstPartyMcpTools.includes("editable_artifact_export_status") &&
|
|
3112
|
+
(!existingSession.firstPartyMcpPermissions?.length ||
|
|
3113
|
+
(existingSession.firstPartyMcpPermissions.includes("artifacts:read") &&
|
|
3114
|
+
existingSession.firstPartyMcpPermissions.includes("artifacts:publish"))),
|
|
3115
|
+
atlassianEnabled:
|
|
3116
|
+
existingSession.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) &&
|
|
3117
|
+
(!existingSession.firstPartyMcpPermissions?.length ||
|
|
3118
|
+
existingSession.firstPartyMcpPermissions.includes("connections:read")),
|
|
3119
|
+
...(input.connectionAuthorities ? { authoritySelections: input.connectionAuthorities } : {}),
|
|
2753
3120
|
});
|
|
2754
|
-
|
|
3121
|
+
const { accepted, turn, draft, receipt, routing, interruptionCount, replay } =
|
|
3122
|
+
await postUserMessageTurn({
|
|
3123
|
+
db,
|
|
3124
|
+
bus,
|
|
3125
|
+
workflowClient,
|
|
3126
|
+
settings,
|
|
3127
|
+
accountId: grant.accountId,
|
|
3128
|
+
workspaceId,
|
|
3129
|
+
sessionId,
|
|
3130
|
+
text: input.text,
|
|
3131
|
+
annotations,
|
|
3132
|
+
modelContext: input.modelContext ?? null,
|
|
3133
|
+
resources: requestedResources,
|
|
3134
|
+
...(composerDraftResources ? { composerDraftResources } : {}),
|
|
3135
|
+
model: input.model ?? null,
|
|
3136
|
+
reasoningEffort: input.reasoningEffort ?? null,
|
|
3137
|
+
latencyMode: input.latencyMode ?? null,
|
|
3138
|
+
reasoningEffortFallback: sessionReasoningEffort,
|
|
3139
|
+
turnExecutionPolicy,
|
|
3140
|
+
mcpCredentialUpdates,
|
|
3141
|
+
personalConnectionDelegations,
|
|
3142
|
+
...(input.personalResourceAttachment
|
|
3143
|
+
? { personalResourceAttachment: input.personalResourceAttachment }
|
|
3144
|
+
: {}),
|
|
3145
|
+
delivery,
|
|
3146
|
+
origin: source === "api" ? "operator" : "human",
|
|
3147
|
+
actor: grant.subjectId,
|
|
3148
|
+
...(grant.subjectLabel ? { actorLabel: grant.subjectLabel } : {}),
|
|
3149
|
+
commandActor,
|
|
3150
|
+
...(boundaryRequestHash ? { boundaryRequestHash } : {}),
|
|
3151
|
+
...(input.controlEtag !== undefined ? { controlEtag: input.controlEtag } : {}),
|
|
3152
|
+
...(input.expectedDraftRevision !== undefined
|
|
3153
|
+
? { expectedDraftRevision: input.expectedDraftRevision }
|
|
3154
|
+
: {}),
|
|
3155
|
+
...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
|
|
3156
|
+
recordAgentRunUsage: true,
|
|
3157
|
+
...(deps.schedulePromptPostCommit
|
|
3158
|
+
? { schedulePostCommit: deps.schedulePromptPostCommit }
|
|
3159
|
+
: {}),
|
|
3160
|
+
});
|
|
3161
|
+
return { accepted, turn, draft, receipt, routing, interruptionCount, replay };
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
if (input.clientEventId && boundaryRequestHash) {
|
|
3164
|
+
const replay = await withWorkspaceSubjectSessionActivityRls(
|
|
3165
|
+
db,
|
|
3166
|
+
workspaceId,
|
|
3167
|
+
grant.subjectId,
|
|
3168
|
+
async (scopedDb) =>
|
|
3169
|
+
await replaySubmittedHumanPromptFromBoundaryReceipt(scopedDb, {
|
|
3170
|
+
workspaceId,
|
|
3171
|
+
sessionId,
|
|
3172
|
+
subjectId: grant.subjectId,
|
|
3173
|
+
actor: commandActor,
|
|
3174
|
+
operationKey: input.clientEventId!,
|
|
3175
|
+
delivery,
|
|
3176
|
+
boundaryRequestHash,
|
|
3177
|
+
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
3178
|
+
serializeOperation: true,
|
|
3179
|
+
}),
|
|
3180
|
+
);
|
|
3181
|
+
if (replay) {
|
|
3182
|
+
return finalizePostUserMessageTurn(
|
|
3183
|
+
{
|
|
3184
|
+
db,
|
|
3185
|
+
bus,
|
|
3186
|
+
workflowClient,
|
|
3187
|
+
accountId: grant.accountId,
|
|
3188
|
+
workspaceId,
|
|
3189
|
+
sessionId,
|
|
3190
|
+
delivery,
|
|
3191
|
+
...(deps.schedulePromptPostCommit
|
|
3192
|
+
? { schedulePostCommit: deps.schedulePromptPostCommit }
|
|
3193
|
+
: {}),
|
|
3194
|
+
},
|
|
3195
|
+
replay,
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
throw error;
|
|
3200
|
+
}
|
|
2755
3201
|
}
|
|
2756
3202
|
|
|
2757
3203
|
/** Backward-compatible entity-returning path used by existing REST callers. */
|