@opengeni/worker-bundle 1.0.2 → 1.2.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/activities/agent-run-admission.d.ts +1 -14
- package/dist/activities/agent-turn/admission.d.ts +3 -2
- package/dist/activities/agent-turn/compaction-prep.d.ts +1 -0
- package/dist/activities/agent-turn/errors.d.ts +3 -0
- package/dist/activities/agent-turn/finalization-monitor.d.ts +6 -1
- package/dist/activities/agent-turn/model-usage.d.ts +10 -0
- package/dist/activities/agent-turn/provider-recovery-metrics.d.ts +18 -0
- package/dist/activities/agent-turn/turn-context.d.ts +5 -0
- package/dist/activities/goals.d.ts +1 -17
- package/dist/activities/types.d.ts +1 -1
- package/dist/{activities-control-BNPTRTOE.js → activities-control-QSDPXLLD.js} +26 -10
- package/dist/activities-control-QSDPXLLD.js.map +1 -0
- package/dist/{activities-turn-5LGZOSVU.js → activities-turn-BMY3CDKL.js} +191 -35
- package/dist/activities-turn-BMY3CDKL.js.map +1 -0
- package/dist/{chunk-LJX7Z2KP.js → chunk-JKMG5ZHH.js} +55 -167
- package/dist/chunk-JKMG5ZHH.js.map +1 -0
- package/dist/{chunk-3ZTRWDMN.js → chunk-XONQEVAD.js} +1 -1
- package/dist/chunk-XONQEVAD.js.map +1 -0
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +1 -1
- package/dist/workflow-bundle.js +1 -1
- package/package.json +21 -21
- package/src/activities/agent-run-admission.ts +1 -98
- package/src/activities/agent-turn/admission.ts +11 -5
- package/src/activities/agent-turn/claim.ts +12 -0
- package/src/activities/agent-turn/compaction-prep.ts +35 -1
- package/src/activities/agent-turn/errors.ts +51 -19
- package/src/activities/agent-turn/failure-settlement.ts +31 -1
- package/src/activities/agent-turn/finalization-monitor.ts +14 -1
- package/src/activities/agent-turn/finalization.ts +5 -0
- package/src/activities/agent-turn/model-usage.ts +38 -0
- package/src/activities/agent-turn/provider-recovery-metrics.ts +64 -0
- package/src/activities/agent-turn/run.ts +1 -0
- package/src/activities/agent-turn/stream-attempt.ts +32 -16
- package/src/activities/agent-turn/tool-environment.ts +9 -2
- package/src/activities/agent-turn/turn-context.ts +4 -0
- package/src/activities/goals.ts +17 -113
- package/src/activities/knowledge-indexing.ts +2 -2
- package/src/activities/scheduled-tasks.ts +18 -3
- package/src/activities/types.ts +1 -0
- package/src/index.ts +0 -2
- package/src/observability-metrics.ts +1 -0
- package/dist/activities-control-BNPTRTOE.js.map +0 -1
- package/dist/activities-turn-5LGZOSVU.js.map +0 -1
- package/dist/chunk-3ZTRWDMN.js.map +0 -1
- package/dist/chunk-LJX7Z2KP.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalizeConfiguredModelId } from "@opengeni/config";
|
|
1
2
|
import {
|
|
2
3
|
applyCreditDebitUpToBalance,
|
|
3
4
|
recordUsageEvent,
|
|
@@ -55,6 +56,27 @@ export function modelUsageSourceKey(input: {
|
|
|
55
56
|
return input.dispatchId ? `${input.dispatchId}:${input.positionalKey}` : input.positionalKey;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/** Legacy aggregate usage may only debit a policy shared by its completed calls. */
|
|
60
|
+
export function aggregateCreditPolicyRevision(input: {
|
|
61
|
+
responseRevisions: ReadonlySet<number | undefined>;
|
|
62
|
+
lastAdmittedRevision: number | undefined;
|
|
63
|
+
chargesOpenGeniCredits: boolean;
|
|
64
|
+
totalTokens: number | null;
|
|
65
|
+
}): number | undefined {
|
|
66
|
+
if (
|
|
67
|
+
input.chargesOpenGeniCredits &&
|
|
68
|
+
input.responseRevisions.size > 1 &&
|
|
69
|
+
(input.totalTokens ?? 0) > 0
|
|
70
|
+
) {
|
|
71
|
+
throw new Error("Aggregate model usage spans different credit policy revisions");
|
|
72
|
+
}
|
|
73
|
+
// Bind the completed response, even if later preparation changed admission.
|
|
74
|
+
// Older runtimes without response callbacks retain their admitted snapshot.
|
|
75
|
+
return input.responseRevisions.size === 1
|
|
76
|
+
? input.responseRevisions.values().next().value
|
|
77
|
+
: input.lastAdmittedRevision;
|
|
78
|
+
}
|
|
79
|
+
|
|
58
80
|
export function providerContextTokens(
|
|
59
81
|
usage:
|
|
60
82
|
| {
|
|
@@ -209,6 +231,7 @@ export async function processModelResponseTerminalEvent(input: {
|
|
|
209
231
|
externallyBilled: boolean;
|
|
210
232
|
chargesOpenGeniCredits?: boolean;
|
|
211
233
|
countsTowardTokenCap?: boolean;
|
|
234
|
+
creditPolicyRevision?: number | undefined;
|
|
212
235
|
servingCredentialId: string | null;
|
|
213
236
|
priorSessionCredentialId: string | null;
|
|
214
237
|
emittedSourceKeys: Set<string>;
|
|
@@ -231,6 +254,15 @@ export async function processModelResponseTerminalEvent(input: {
|
|
|
231
254
|
if (!terminal) {
|
|
232
255
|
return { status: "not_response" };
|
|
233
256
|
}
|
|
257
|
+
// Some providers mirror a terminal response before normalized usage arrives.
|
|
258
|
+
// Claiming that empty raw mirror would discard the SDK's billable response.
|
|
259
|
+
if (
|
|
260
|
+
!terminal.usage &&
|
|
261
|
+
input.event.type === "raw_model_stream_event" &&
|
|
262
|
+
input.event.data.type !== "response_done"
|
|
263
|
+
) {
|
|
264
|
+
return { status: "not_response" };
|
|
265
|
+
}
|
|
234
266
|
|
|
235
267
|
const responseOrdinal = input.state.responseCount + 1;
|
|
236
268
|
const sourceKey = modelUsageSourceKey({
|
|
@@ -268,6 +300,7 @@ export async function processModelResponseTerminalEvent(input: {
|
|
|
268
300
|
turnId: input.turnId,
|
|
269
301
|
turnAttemptId: input.turnAttemptId,
|
|
270
302
|
model: input.model,
|
|
303
|
+
creditPolicyRevision: input.creditPolicyRevision,
|
|
271
304
|
externallyBilled: input.externallyBilled,
|
|
272
305
|
...(input.chargesOpenGeniCredits !== undefined
|
|
273
306
|
? { chargesOpenGeniCredits: input.chargesOpenGeniCredits }
|
|
@@ -374,6 +407,7 @@ export async function processCompactionModelUsageEvent(input: {
|
|
|
374
407
|
externallyBilled: boolean;
|
|
375
408
|
chargesOpenGeniCredits?: boolean;
|
|
376
409
|
countsTowardTokenCap?: boolean;
|
|
410
|
+
creditPolicyRevision?: number | undefined;
|
|
377
411
|
servingCredentialId: string | null;
|
|
378
412
|
priorSessionCredentialId: string | null;
|
|
379
413
|
emittedSourceKeys: Set<string>;
|
|
@@ -416,6 +450,7 @@ export async function processCompactionModelUsageEvent(input: {
|
|
|
416
450
|
turnId: input.turnId,
|
|
417
451
|
turnAttemptId: input.turnAttemptId,
|
|
418
452
|
model: input.model,
|
|
453
|
+
creditPolicyRevision: input.creditPolicyRevision,
|
|
419
454
|
externallyBilled: input.externallyBilled,
|
|
420
455
|
...(input.chargesOpenGeniCredits !== undefined
|
|
421
456
|
? { chargesOpenGeniCredits: input.chargesOpenGeniCredits }
|
|
@@ -651,6 +686,7 @@ export async function recordModelUsageAndDebitCredits(
|
|
|
651
686
|
externallyBilled: boolean;
|
|
652
687
|
chargesOpenGeniCredits?: boolean;
|
|
653
688
|
countsTowardTokenCap?: boolean;
|
|
689
|
+
creditPolicyRevision?: number | undefined;
|
|
654
690
|
gatewayBilling?: ModelResponseUsage["gatewayBilling"];
|
|
655
691
|
usage?: ModelUsageInput | ModelCallUsageInput | null;
|
|
656
692
|
normalizedUsage?: ModelCallUsageNormalization;
|
|
@@ -842,6 +878,8 @@ export async function recordModelUsageAndDebitCredits(
|
|
|
842
878
|
workspaceId: input.workspaceId,
|
|
843
879
|
type: "model_usage_debit",
|
|
844
880
|
requestedAmountMicros: costMicros,
|
|
881
|
+
modelId: canonicalizeConfiguredModelId(settings, input.model),
|
|
882
|
+
creditPolicyRevision: input.creditPolicyRevision,
|
|
845
883
|
sourceType: "model_response",
|
|
846
884
|
sourceId: `${input.turnId}:${input.sourceKey}`,
|
|
847
885
|
idempotencyKey: `credit:model_usage_debit:${input.turnId}:${input.sourceKey}`,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Observability } from "@opengeni/observability";
|
|
2
|
+
|
|
3
|
+
export type ProviderRecoveryObservation = {
|
|
4
|
+
startedAt: number;
|
|
5
|
+
cause: "rate_limited" | "unavailable" | "connectivity";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function providerRecoveryCause(code: unknown): ProviderRecoveryObservation["cause"] | null {
|
|
9
|
+
if (code === "provider_rate_limited") return "rate_limited";
|
|
10
|
+
if (code === "provider_unavailable") return "unavailable";
|
|
11
|
+
if (code === "upstream_connectivity_unavailable") return "connectivity";
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readProviderRecoveryObservation(
|
|
16
|
+
metadata: Record<string, unknown>,
|
|
17
|
+
): ProviderRecoveryObservation | undefined {
|
|
18
|
+
const cause = providerRecoveryCause(metadata.providerRecoveryReason);
|
|
19
|
+
const startedAt =
|
|
20
|
+
typeof metadata.providerRecoveryStartedAt === "string"
|
|
21
|
+
? Date.parse(metadata.providerRecoveryStartedAt)
|
|
22
|
+
: Number.NaN;
|
|
23
|
+
return cause && Number.isFinite(startedAt) ? { cause, startedAt } : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Operational observations only; never retry authority, billing or diagnostics. */
|
|
27
|
+
export function recordProviderRecoveryOutcome(
|
|
28
|
+
observability: Observability,
|
|
29
|
+
input: {
|
|
30
|
+
route?: { provider: string; model: string } | undefined;
|
|
31
|
+
cause: ProviderRecoveryObservation["cause"];
|
|
32
|
+
outcome: "scheduled" | "recovered" | "exhausted";
|
|
33
|
+
delayMs?: number;
|
|
34
|
+
elapsedMs?: number;
|
|
35
|
+
},
|
|
36
|
+
): void {
|
|
37
|
+
if (!input.route) return;
|
|
38
|
+
const labels = { ...input.route, cause: input.cause, outcome: input.outcome };
|
|
39
|
+
try {
|
|
40
|
+
observability.incrementCounter({
|
|
41
|
+
name: "opengeni_model_recovery_total",
|
|
42
|
+
help: "Observed model recovery decisions and successful resumptions.",
|
|
43
|
+
labels,
|
|
44
|
+
});
|
|
45
|
+
if (input.delayMs !== undefined) {
|
|
46
|
+
observability.observeHistogram({
|
|
47
|
+
name: "opengeni_model_recovery_delay_seconds",
|
|
48
|
+
help: "Scheduled model recovery delay, including provider hints and jitter.",
|
|
49
|
+
labels: { ...input.route, cause: input.cause },
|
|
50
|
+
value: Math.max(0, input.delayMs) / 1_000,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (input.elapsedMs !== undefined) {
|
|
54
|
+
observability.observeHistogram({
|
|
55
|
+
name: "opengeni_model_recovery_duration_seconds",
|
|
56
|
+
help: "Elapsed recovery episode including backoff, preparation and model requests.",
|
|
57
|
+
labels,
|
|
58
|
+
value: Math.max(0, input.elapsedMs) / 1_000,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Metrics cannot interrupt settlement or model progress.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
updateSessionTitleWithEvent,
|
|
19
19
|
} from "@opengeni/db";
|
|
20
20
|
import { publishDurableSessionEvents } from "@opengeni/events";
|
|
21
|
+
import { recordProviderRecoveryOutcome } from "./provider-recovery-metrics";
|
|
21
22
|
import {
|
|
22
23
|
AssistantMessagePhaseTracker,
|
|
23
24
|
normalizeModelCallUsage,
|
|
@@ -121,6 +122,7 @@ import {
|
|
|
121
122
|
completedToolCallFromSdkEvent,
|
|
122
123
|
} from "./history";
|
|
123
124
|
import {
|
|
125
|
+
aggregateCreditPolicyRevision,
|
|
124
126
|
modelUsageSourceKey,
|
|
125
127
|
recordCompletedModelCallBeforeOwnershipFences,
|
|
126
128
|
TurnEventPublisher,
|
|
@@ -383,6 +385,8 @@ export async function runTurnStreamAttempt(
|
|
|
383
385
|
);
|
|
384
386
|
let parallelSessionTitle: ReturnType<typeof startParallelSessionTitleGeneration> | null = null;
|
|
385
387
|
let parallelSessionTitleFinished = false;
|
|
388
|
+
let creditPolicyRevision: number | undefined;
|
|
389
|
+
let titleCreditPolicyRevision: number | undefined;
|
|
386
390
|
const finishParallelSessionTitle = async (): Promise<void> => {
|
|
387
391
|
if (parallelSessionTitleFinished) return;
|
|
388
392
|
parallelSessionTitleFinished = true;
|
|
@@ -392,6 +396,7 @@ export async function runTurnStreamAttempt(
|
|
|
392
396
|
if (generated.usage) {
|
|
393
397
|
await processSessionTitleModelUsageEvent({
|
|
394
398
|
usage: generated.usage,
|
|
399
|
+
creditPolicyRevision: titleCreditPolicyRevision,
|
|
395
400
|
state: sessionTitleUsageState,
|
|
396
401
|
dispatchId: modelUsageDispatchId,
|
|
397
402
|
settings,
|
|
@@ -594,7 +599,8 @@ export async function runTurnStreamAttempt(
|
|
|
594
599
|
let finalReplyNudged = false;
|
|
595
600
|
const revalidateModelCallAdmission = async () => {
|
|
596
601
|
await historySink.reconcileConversationTruth({ requireDurable: true });
|
|
597
|
-
await ensureRunAllowedBetweenModelCalls({
|
|
602
|
+
creditPolicyRevision = await ensureRunAllowedBetweenModelCalls({
|
|
603
|
+
modelId: resolvedModel?.configured.id ?? turn.model,
|
|
598
604
|
settings,
|
|
599
605
|
db,
|
|
600
606
|
accountId: input.accountId,
|
|
@@ -624,6 +630,7 @@ export async function runTurnStreamAttempt(
|
|
|
624
630
|
signal: runtimeCancellationSignal,
|
|
625
631
|
admit: revalidateModelCallAdmission,
|
|
626
632
|
});
|
|
633
|
+
const responseCreditPolicyRevisions = new Set<number | undefined>();
|
|
627
634
|
const responseCountBeforeStream = modelResponseState.responseCount;
|
|
628
635
|
eventing.batcher = null;
|
|
629
636
|
// The SDK emits every processed call item for one model response before
|
|
@@ -841,7 +848,10 @@ export async function runTurnStreamAttempt(
|
|
|
841
848
|
attempt.modelRequestStarted = true;
|
|
842
849
|
return await runtime.runStream(agent, runInput!, eventing.modelRunSettings, {
|
|
843
850
|
beforeModelRequest: modelCallAdmission.beforeModelRequest,
|
|
844
|
-
onModelResponse:
|
|
851
|
+
onModelResponse: (event) => {
|
|
852
|
+
responseCreditPolicyRevisions.add(creditPolicyRevision);
|
|
853
|
+
return modelCallAdmission.onModelResponse(event);
|
|
854
|
+
},
|
|
845
855
|
signal: runtimeCancellationSignal,
|
|
846
856
|
sandboxEnvironment,
|
|
847
857
|
onModelVisibleContext: async (snapshot) => {
|
|
@@ -1067,6 +1077,7 @@ export async function runTurnStreamAttempt(
|
|
|
1067
1077
|
? await media.retainNativeGeneratedImage(generatedImage)
|
|
1068
1078
|
: null;
|
|
1069
1079
|
const responseResult = await processModelResponseTerminalEvent({
|
|
1080
|
+
creditPolicyRevision,
|
|
1070
1081
|
event: next.value,
|
|
1071
1082
|
state: modelResponseState,
|
|
1072
1083
|
dispatchId: modelUsageDispatchId,
|
|
@@ -1132,6 +1143,15 @@ export async function runTurnStreamAttempt(
|
|
|
1132
1143
|
]);
|
|
1133
1144
|
attempt.providerRecoveryCount = 0;
|
|
1134
1145
|
}
|
|
1146
|
+
if (attempt.providerRecoveryObservation) {
|
|
1147
|
+
recordProviderRecoveryOutcome(observability, {
|
|
1148
|
+
route: attempt.modelMetricRoute,
|
|
1149
|
+
cause: attempt.providerRecoveryObservation.cause,
|
|
1150
|
+
outcome: "recovered",
|
|
1151
|
+
elapsedMs: Date.now() - attempt.providerRecoveryObservation.startedAt,
|
|
1152
|
+
});
|
|
1153
|
+
attempt.providerRecoveryObservation = undefined;
|
|
1154
|
+
}
|
|
1135
1155
|
const rawStreamHistory = (eventing.stream.state as { history?: unknown[] }).history;
|
|
1136
1156
|
if (Array.isArray(rawStreamHistory)) {
|
|
1137
1157
|
// The completed image item is normally retained from its own
|
|
@@ -1148,21 +1168,9 @@ export async function runTurnStreamAttempt(
|
|
|
1148
1168
|
await historySink.reconcileConversationTruth();
|
|
1149
1169
|
turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
|
|
1150
1170
|
modelCheckpointMemoryCollector.schedule(observability);
|
|
1151
|
-
await ensureRunAllowedBetweenModelCalls({
|
|
1152
|
-
settings,
|
|
1153
|
-
db,
|
|
1154
|
-
accountId: input.accountId,
|
|
1155
|
-
workspaceId: input.workspaceId,
|
|
1156
|
-
isExternallyBilledTurn: billingState.isExternallyBilledTurn,
|
|
1157
|
-
entitlements,
|
|
1158
|
-
chargesOpenGeniCredits: billingState.chargesOpenGeniCredits,
|
|
1159
|
-
countsTowardTokenCap: billingState.countsTowardTokenCap,
|
|
1160
|
-
initiatingHumanSubjectId: turn.initiatingHumanSubjectId,
|
|
1161
|
-
serializedRunState: () =>
|
|
1162
|
-
media.compactMediaRunState(String(eventing.stream!.state.toString())),
|
|
1163
|
-
});
|
|
1164
1171
|
}
|
|
1165
|
-
// Release
|
|
1172
|
+
// Release after settlement. The producer checks fresh admission before
|
|
1173
|
+
// its next request; a final response keeps the revision it ran under.
|
|
1166
1174
|
modelCallAdmission.settle(next.value);
|
|
1167
1175
|
const durableSdkEvent = generatedImageReceipt
|
|
1168
1176
|
? compactGeneratedImageSdkEvent(next.value, generatedImageReceipt)
|
|
@@ -1551,6 +1559,12 @@ export async function runTurnStreamAttempt(
|
|
|
1551
1559
|
if (!streamSawPerResponseUsage) {
|
|
1552
1560
|
const aggregateUsage = eventing.stream.state.usage;
|
|
1553
1561
|
const normalizedAggregateUsage = normalizeModelCallUsage(aggregateUsage);
|
|
1562
|
+
const aggregatePolicyRevision = aggregateCreditPolicyRevision({
|
|
1563
|
+
responseRevisions: responseCreditPolicyRevisions,
|
|
1564
|
+
lastAdmittedRevision: creditPolicyRevision,
|
|
1565
|
+
chargesOpenGeniCredits: billingState.chargesOpenGeniCredits,
|
|
1566
|
+
totalTokens: normalizedAggregateUsage.totalTokens,
|
|
1567
|
+
});
|
|
1554
1568
|
const aggregateInput = normalizedAggregateUsage.telemetry.inputTokens;
|
|
1555
1569
|
const aggregateSourceKey = modelUsageSourceKey({
|
|
1556
1570
|
responseId: null,
|
|
@@ -1573,6 +1587,7 @@ export async function runTurnStreamAttempt(
|
|
|
1573
1587
|
leaseLostMessage: "Provider credential lease expired during the active turn",
|
|
1574
1588
|
recordUsage: async () => {
|
|
1575
1589
|
const billing = await recordModelUsageAndDebitCredits(settings, db, {
|
|
1590
|
+
creditPolicyRevision: aggregatePolicyRevision,
|
|
1576
1591
|
accountId: input.accountId,
|
|
1577
1592
|
workspaceId: input.workspaceId,
|
|
1578
1593
|
sessionId: input.sessionId,
|
|
@@ -1965,6 +1980,7 @@ export async function runTurnStreamAttempt(
|
|
|
1965
1980
|
turnExecutionPolicy.providerId,
|
|
1966
1981
|
turnExecutionPolicy.latencyMode,
|
|
1967
1982
|
);
|
|
1983
|
+
titleCreditPolicyRevision = creditPolicyRevision;
|
|
1968
1984
|
parallelSessionTitle = startParallelSessionTitleGeneration({
|
|
1969
1985
|
signal: runtimeCancellationSignal,
|
|
1970
1986
|
generate: async (signal) =>
|
|
@@ -82,6 +82,7 @@ import { createTurnMediaArtifacts } from "./media-artifacts";
|
|
|
82
82
|
import { SandboxChannelAService } from "@opengeni/runtime/sandbox";
|
|
83
83
|
import { sandboxRunAs } from "@opengeni/runtime";
|
|
84
84
|
import {
|
|
85
|
+
bundledSkillSelectionForAgentConfig,
|
|
85
86
|
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
86
87
|
resolveAgentToolFamilies,
|
|
87
88
|
type ResourceRef,
|
|
@@ -596,7 +597,11 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
596
597
|
session.firstPartyMcpPermissions,
|
|
597
598
|
linkedAuthority,
|
|
598
599
|
);
|
|
599
|
-
|
|
600
|
+
// Background-command tools need compute: the effective route of this turn,
|
|
601
|
+
// a managed sandbox or an attached Connected Machine, not the durable home.
|
|
602
|
+
const toolFamilies = resolveAgentToolFamilies(session.agent, {
|
|
603
|
+
sandboxAttached: (activeSandboxBackend ?? groupBoxBackend) !== "none",
|
|
604
|
+
});
|
|
600
605
|
const selectedFirstPartyMcpTools = toolFamilies.firstPartyTools(
|
|
601
606
|
allowedFirstPartyMcpToolsForSession(runSettings, session.firstPartyMcpTools),
|
|
602
607
|
);
|
|
@@ -669,7 +674,9 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
669
674
|
}),
|
|
670
675
|
]);
|
|
671
676
|
const bundledSkills = loadConfiguredBundledSkills({
|
|
672
|
-
|
|
677
|
+
// Rows that could not freeze the "none" default at create (scheduled
|
|
678
|
+
// generated sessions, pre-existing rows) get the same rule here.
|
|
679
|
+
bundledSkillIds: bundledSkillSelectionForAgentConfig(session.bundledSkillIds, session.agent),
|
|
673
680
|
firstPartyTools: selectedFirstPartyMcpTools,
|
|
674
681
|
videoGenerationEnabled:
|
|
675
682
|
skillConfiguration.defaultModelId !== null && skillConfiguration.enabledModelIds.length > 0,
|
|
@@ -68,6 +68,10 @@ export type AttemptIdentityState = {
|
|
|
68
68
|
triggerEventId: string | undefined;
|
|
69
69
|
executionGeneration: number;
|
|
70
70
|
providerRecoveryCount: number;
|
|
71
|
+
providerRecoveryObservation?:
|
|
72
|
+
| import("./provider-recovery-metrics").ProviderRecoveryObservation
|
|
73
|
+
| undefined;
|
|
74
|
+
modelMetricRoute?: { provider: string; model: string };
|
|
71
75
|
claudeAuthRecovery?: { credentialId: string; credentialVersion: number } | undefined;
|
|
72
76
|
modelRequestStarted: boolean;
|
|
73
77
|
redispatchesAtDispatch: number;
|
package/src/activities/goals.ts
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allowedFirstPartyMcpToolsForSession,
|
|
3
|
-
configuredStaticUsageLimits,
|
|
4
|
-
isModelAvailableForNewSelection,
|
|
5
|
-
policyProviderIdForModel,
|
|
6
|
-
resolveModelProvider,
|
|
7
3
|
resolveTurnExecutionPolicyV1,
|
|
8
|
-
withCodexCatalogProvider,
|
|
9
|
-
withXaiSubscriptionCatalogProvider,
|
|
10
|
-
WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
|
|
11
|
-
WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
|
|
12
4
|
type Settings,
|
|
13
5
|
} from "@opengeni/config";
|
|
14
6
|
import {
|
|
15
|
-
evaluateWorkspaceModelPolicy,
|
|
16
7
|
mergeToolRefs,
|
|
17
8
|
readTurnExecutionPolicyV1,
|
|
18
9
|
type SessionGoal,
|
|
19
10
|
type ToolRef,
|
|
20
11
|
} from "@opengeni/contracts";
|
|
21
|
-
import { isCodexBilledModel } from "@opengeni/codex";
|
|
22
12
|
import {
|
|
23
13
|
enqueueSessionWorkflowWakeIfRunnable,
|
|
24
|
-
getWorkspaceModelPolicy,
|
|
25
14
|
getSessionGoal,
|
|
26
15
|
getSessionTurn,
|
|
27
16
|
materializeGoalContinuation,
|
|
@@ -34,10 +23,10 @@ import type {
|
|
|
34
23
|
} from "./types";
|
|
35
24
|
import {
|
|
36
25
|
modelFundingForAdmission,
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
goalRunBudgetBlocked,
|
|
27
|
+
resolveGoalModelAdmission,
|
|
39
28
|
} from "@opengeni/core";
|
|
40
|
-
|
|
29
|
+
export { goalContinuationModelDecision, goalRunBudgetBlocked } from "@opengeni/core";
|
|
41
30
|
import { turnCredentialRestriction } from "./agent-turn/credential-restriction";
|
|
42
31
|
|
|
43
32
|
export function createGoalActivities(services: () => Promise<ControlActivityServices>) {
|
|
@@ -80,42 +69,18 @@ export function createGoalActivities(services: () => Promise<ControlActivityServ
|
|
|
80
69
|
if (session.status === "failed" || session.status === "cancelled") {
|
|
81
70
|
return { action: "none" };
|
|
82
71
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
const modelDecision = await resolveGoalModelAdmission(db, catalogSourceSettings, {
|
|
73
|
+
accountId: input.accountId,
|
|
74
|
+
workspaceId: input.workspaceId,
|
|
75
|
+
model: session.model,
|
|
76
|
+
codexCompactionMode: session.codexCompactionMode,
|
|
77
|
+
latencyMode: session.latencyMode,
|
|
78
|
+
});
|
|
79
|
+
const settings = modelDecision.settings;
|
|
80
|
+
const continuationModel = modelDecision.model;
|
|
86
81
|
const continuationReasoningEffort = session.reasoningEffort;
|
|
87
82
|
const continuationLatencyMode = session.latencyMode;
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
inheritedContinuationModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
|
|
91
|
-
inheritedContinuationModel.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX) ||
|
|
92
|
-
session.model.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
|
|
93
|
-
session.model.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX)
|
|
94
|
-
) {
|
|
95
|
-
settings = (
|
|
96
|
-
await resolveWorkspaceCatalogSettings(db, catalogSourceSettings, {
|
|
97
|
-
accountId: input.accountId,
|
|
98
|
-
workspaceId: input.workspaceId,
|
|
99
|
-
retainedProductModelIds: [inheritedContinuationModel, session.model],
|
|
100
|
-
})
|
|
101
|
-
).settings;
|
|
102
|
-
}
|
|
103
|
-
const modelDecision = goalContinuationModelDecision({
|
|
104
|
-
settings,
|
|
105
|
-
workspaceModelPolicy,
|
|
106
|
-
inheritedModel: inheritedContinuationModel,
|
|
107
|
-
});
|
|
108
|
-
continuationModel = modelDecision.model;
|
|
109
|
-
let modelPolicyBlocked = modelDecision.blocked;
|
|
110
|
-
// remote_v2 sessions may only continue on Codex models — refuse synthesis
|
|
111
|
-
// that would leave the portable/non-Codex path (and mixed history shapes).
|
|
112
|
-
if (
|
|
113
|
-
!modelPolicyBlocked &&
|
|
114
|
-
session.codexCompactionMode === "remote_v2" &&
|
|
115
|
-
!isCodexBilledModel(continuationModel)
|
|
116
|
-
) {
|
|
117
|
-
modelPolicyBlocked = `session is locked to Codex remote compaction v2; model "${continuationModel}" is not a Codex subscription model`;
|
|
118
|
-
}
|
|
83
|
+
const modelPolicyBlocked = modelDecision.blocked;
|
|
119
84
|
const turnExecutionPolicy = modelPolicyBlocked
|
|
120
85
|
? undefined
|
|
121
86
|
: resolveTurnExecutionPolicyV1(settings, {
|
|
@@ -183,11 +148,12 @@ export function createGoalActivities(services: () => Promise<ControlActivityServ
|
|
|
183
148
|
initiatingHumanSubjectId: causalTurn?.initiatingHumanSubjectId ?? null,
|
|
184
149
|
},
|
|
185
150
|
);
|
|
151
|
+
const pausedReason = modelPolicyBlocked
|
|
152
|
+
? modelDecision.pausedReason
|
|
153
|
+
: budgetBlocked?.pausedReason;
|
|
186
154
|
return {
|
|
187
155
|
budgetBlocked: modelPolicyBlocked ?? budgetBlocked?.message ?? null,
|
|
188
|
-
budgetPausedReason:
|
|
189
|
-
? "limits"
|
|
190
|
-
: (budgetBlocked?.pausedReason ?? "limits"),
|
|
156
|
+
...(pausedReason ? { budgetPausedReason: pausedReason } : {}),
|
|
191
157
|
};
|
|
192
158
|
},
|
|
193
159
|
policy: continuationPolicy,
|
|
@@ -220,47 +186,6 @@ export function createGoalActivities(services: () => Promise<ControlActivityServ
|
|
|
220
186
|
};
|
|
221
187
|
}
|
|
222
188
|
|
|
223
|
-
export function goalContinuationModelDecision(input: {
|
|
224
|
-
settings: Settings;
|
|
225
|
-
workspaceModelPolicy: Awaited<ReturnType<typeof getWorkspaceModelPolicy>>;
|
|
226
|
-
inheritedModel: string;
|
|
227
|
-
}): { model: string; blocked: string | null } {
|
|
228
|
-
const catalogSettings = input.settings.supergrokSubscriptionEnabled
|
|
229
|
-
? withXaiSubscriptionCatalogProvider(
|
|
230
|
-
input.settings.codexSubscriptionEnabled
|
|
231
|
-
? withCodexCatalogProvider(input.settings)
|
|
232
|
-
: input.settings,
|
|
233
|
-
)
|
|
234
|
-
: input.settings.codexSubscriptionEnabled
|
|
235
|
-
? withCodexCatalogProvider(input.settings)
|
|
236
|
-
: input.settings;
|
|
237
|
-
const policyBlocks = (modelId: string): boolean =>
|
|
238
|
-
input.workspaceModelPolicy !== null &&
|
|
239
|
-
!evaluateWorkspaceModelPolicy(input.workspaceModelPolicy, {
|
|
240
|
-
providerId: policyProviderIdForModel(catalogSettings, modelId),
|
|
241
|
-
modelId,
|
|
242
|
-
}).allowed;
|
|
243
|
-
if (!resolveModelProvider(catalogSettings, input.inheritedModel)) {
|
|
244
|
-
return {
|
|
245
|
-
model: input.inheritedModel,
|
|
246
|
-
blocked: `model "${input.inheritedModel}" is no longer in the deployment or workspace catalog; choose an available model before resuming the goal`,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
if (!isModelAvailableForNewSelection(catalogSettings, input.inheritedModel)) {
|
|
250
|
-
return {
|
|
251
|
-
model: input.inheritedModel,
|
|
252
|
-
blocked: `model "${input.inheritedModel}" is retired from new selection; choose an available model before resuming the goal`,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
if (!policyBlocks(input.inheritedModel)) {
|
|
256
|
-
return { model: input.inheritedModel, blocked: null };
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
model: input.inheritedModel,
|
|
260
|
-
blocked: `workspace model policy blocks model "${input.inheritedModel}"; pick an allowed model or change the workspace model policy`,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
189
|
export function goalContinuationFundedWithoutCredits(
|
|
265
190
|
settings: Settings,
|
|
266
191
|
model: string,
|
|
@@ -364,24 +289,3 @@ export function withFirstPartyTools(settings: Settings, tools: ToolRef[]): ToolR
|
|
|
364
289
|
}
|
|
365
290
|
return mergeToolRefs(tools, [{ kind: "mcp", id: "opengeni" }]);
|
|
366
291
|
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Goals share scheduled admission and pause visibly without synthesizing work.
|
|
370
|
-
*/
|
|
371
|
-
export async function goalRunBudgetBlocked(
|
|
372
|
-
services: Parameters<typeof agentRunAdmissionDenial>[0],
|
|
373
|
-
input: Omit<Parameters<typeof agentRunAdmissionDenial>[1], "requestedAgentRuns">,
|
|
374
|
-
): Promise<{ pausedReason: "limits" | "allowance"; message: string } | null> {
|
|
375
|
-
const denial = await agentRunAdmissionDenial(services, { ...input, requestedAgentRuns: 1 });
|
|
376
|
-
if (denial === null) return null;
|
|
377
|
-
if (denial === "allowance_exhausted") {
|
|
378
|
-
return { pausedReason: "allowance", message: "Opengeni usage allowance exhausted" };
|
|
379
|
-
}
|
|
380
|
-
const limits = configuredStaticUsageLimits(services.settings);
|
|
381
|
-
const messages = {
|
|
382
|
-
insufficient_credits: "insufficient Opengeni credits",
|
|
383
|
-
monthly_model_cost_limit: `monthly model cost limit reached (${limits.maxMonthlyCostMicrosPerAccount} micros)`,
|
|
384
|
-
monthly_agent_run_limit: `monthly agent run limit reached (${limits.maxMonthlyAgentRunsPerWorkspace})`,
|
|
385
|
-
};
|
|
386
|
-
return { pausedReason: "limits", message: messages[denial] };
|
|
387
|
-
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
creditDebitAttributionMetadata,
|
|
11
11
|
deferKnowledgeIndexJob,
|
|
12
12
|
freezeKnowledgeIndexBillingMode,
|
|
13
|
-
|
|
13
|
+
getSpendableCreditBalance,
|
|
14
14
|
guardPaidKnowledgeIndexPublication,
|
|
15
15
|
knowledgeIndexBillingActivationTime,
|
|
16
16
|
readKnowledgeIndexSource,
|
|
@@ -211,7 +211,7 @@ export function createKnowledgeIndexingActivities(
|
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
if (paid && current.nextIndex === 0) {
|
|
214
|
-
const balance = await
|
|
214
|
+
const balance = await getSpendableCreditBalance(lockedDb, claim.accountId);
|
|
215
215
|
if (balance.balanceMicros <= 0) {
|
|
216
216
|
await waitKnowledgeIndexForFunding(lockedDb, claim);
|
|
217
217
|
result.deferred++;
|
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
allowedFirstPartyMcpToolsForSession,
|
|
99
99
|
resolveFirstPartyMcpToolPolicy,
|
|
100
100
|
resolveTurnExecutionPolicyV1,
|
|
101
|
+
TurnExecutionPolicyModelUnavailableError,
|
|
101
102
|
} from "@opengeni/config";
|
|
102
103
|
import { Context } from "@temporalio/activity";
|
|
103
104
|
import { createHash } from "node:crypto";
|
|
@@ -1082,8 +1083,9 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
|
|
|
1082
1083
|
}
|
|
1083
1084
|
}
|
|
1084
1085
|
: undefined;
|
|
1085
|
-
|
|
1086
|
-
|
|
1086
|
+
let acceptedTurnExecutionPolicy: ReturnType<typeof resolveTurnExecutionPolicyV1>;
|
|
1087
|
+
try {
|
|
1088
|
+
acceptedTurnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
1087
1089
|
modelId: acceptedModel,
|
|
1088
1090
|
requestedModelId:
|
|
1089
1091
|
generatedTarget && task.agentConfig.model ? task.agentConfig.model : null,
|
|
@@ -1100,7 +1102,20 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
|
|
|
1100
1102
|
: "session",
|
|
1101
1103
|
latencyMode: acceptedLatencyMode,
|
|
1102
1104
|
latencyModeSource: generatedTarget ? "deployment" : "session",
|
|
1103
|
-
})
|
|
1105
|
+
});
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
// A retired or removed model refuses every occurrence until the task
|
|
1108
|
+
// (or its target session) names an available model: record that as a
|
|
1109
|
+
// visible terminal run instead of exhausting activity retries.
|
|
1110
|
+
if (!(error instanceof TurnExecutionPolicyModelUnavailableError)) throw error;
|
|
1111
|
+
return await refuseAdmission(
|
|
1112
|
+
"scheduled_model_unavailable",
|
|
1113
|
+
false,
|
|
1114
|
+
`${error.message}: ${acceptedModel}`,
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
const turnExecutionPolicy = scheduledTaskRunExecutionPolicy(
|
|
1118
|
+
acceptedTurnExecutionPolicy,
|
|
1104
1119
|
input,
|
|
1105
1120
|
creatorPolicy?.credentialRestriction,
|
|
1106
1121
|
);
|
package/src/activities/types.ts
CHANGED
|
@@ -500,6 +500,7 @@ export type DispatchScheduledTaskRunResult =
|
|
|
500
500
|
| "machine_enrollment_inactive"
|
|
501
501
|
| "variable_set_unavailable"
|
|
502
502
|
| "rig_version_unavailable"
|
|
503
|
+
| "scheduled_model_unavailable"
|
|
503
504
|
| "knowledge_source_paused"
|
|
504
505
|
| "legacy_source_schedule_requires_migration"
|
|
505
506
|
| "atlassian_native_retired"
|
package/src/index.ts
CHANGED
|
@@ -1214,8 +1214,6 @@ export async function startWorker() {
|
|
|
1214
1214
|
rlsStrategy: settings.rlsStrategy,
|
|
1215
1215
|
expectedRole: settings.runtimeDatabaseRole,
|
|
1216
1216
|
targetSchema: settings.dbSchema.trim() || "public",
|
|
1217
|
-
organizationTenancyCanonicalActivationEnabled:
|
|
1218
|
-
settings.organizationTenancyCanonicalActivationEnabled,
|
|
1219
1217
|
} as const;
|
|
1220
1218
|
const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);
|
|
1221
1219
|
let bus: Awaited<ReturnType<typeof createNatsEventBus>> | undefined;
|
|
@@ -1308,6 +1308,7 @@ export function recordSandboxDeadlineRotationsRequested(
|
|
|
1308
1308
|
export const SANDBOX_COMMAND_CONTAINMENT_OUTCOMES = [
|
|
1309
1309
|
"idle_enrolled",
|
|
1310
1310
|
"deadline_enrolled",
|
|
1311
|
+
"quiescence_enrolled",
|
|
1311
1312
|
"resumed_enrolled",
|
|
1312
1313
|
"not_eligible",
|
|
1313
1314
|
"inspection_failed",
|