@opengeni/worker-bundle 2.3.1 → 2.3.2-canary.37090756480001
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-turn/claude-usage-observer.d.ts +30 -6
- package/dist/activities/agent-turn/credential-leases.d.ts +5 -32
- package/dist/activities/agent-turn/errors.d.ts +3 -0
- package/dist/activities/agent-turn/finalization-monitor.d.ts +1 -1
- package/dist/activities/agent-turn/finalization.d.ts +2 -0
- package/dist/activities/agent-turn/quiescence.d.ts +3 -4
- package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
- package/dist/activities/agent-turn/turn-context.d.ts +1 -1
- package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
- package/dist/activities/session-state.d.ts +2 -2
- package/dist/activities/types.d.ts +7 -0
- package/dist/{activities-control-3HAH4FMG.js → activities-control-3BY4IJKL.js} +166 -35
- package/dist/activities-control-3BY4IJKL.js.map +1 -0
- package/dist/activities-control.d.ts +6 -4
- package/dist/{activities-turn-KU25J3VQ.js → activities-turn-TTNAVGKN.js} +263 -308
- package/dist/activities-turn-TTNAVGKN.js.map +1 -0
- package/dist/activities.d.ts +6 -4
- package/dist/{chunk-3K3ULWEE.js → chunk-PA7Z55P4.js} +242 -17
- package/dist/chunk-PA7Z55P4.js.map +1 -0
- package/dist/{chunk-YG27NEMX.js → chunk-YOI3Z3GF.js} +9 -1
- package/dist/chunk-YOI3Z3GF.js.map +1 -0
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +82 -16
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +6 -1
- package/dist/sandbox-resume.d.ts +3 -0
- package/dist/session-event-fanout.d.ts +6 -0
- package/dist/worker-service-lifecycle.d.ts +12 -0
- package/dist/workflow-bundle.js +3 -2
- package/package.json +21 -21
- package/src/activities/agent-turn/claim.ts +13 -0
- package/src/activities/agent-turn/claude-usage-observer.ts +99 -8
- package/src/activities/agent-turn/credential-leases.ts +86 -213
- package/src/activities/agent-turn/errors.ts +61 -1
- package/src/activities/agent-turn/failure-settlement.ts +59 -5
- package/src/activities/agent-turn/finalization-monitor.ts +3 -4
- package/src/activities/agent-turn/finalization.ts +8 -3
- package/src/activities/agent-turn/quiescence.ts +3 -4
- package/src/activities/agent-turn/run.ts +24 -20
- package/src/activities/agent-turn/sandbox-establish.ts +2 -0
- package/src/activities/agent-turn/sandbox-runtime.ts +5 -1
- package/src/activities/agent-turn/skill-selection.ts +2 -0
- package/src/activities/agent-turn/subscription-lease.ts +115 -0
- package/src/activities/agent-turn/turn-context.ts +1 -4
- package/src/activities/codex-rotation.ts +2 -12
- package/src/activities/model-call-fact-reconciler.ts +112 -0
- package/src/activities/sandbox-lease.ts +59 -8
- package/src/activities/session-state.ts +29 -4
- package/src/activities/types.ts +7 -0
- package/src/activities-control.ts +2 -0
- package/src/activities-turn.ts +5 -0
- package/src/activities.ts +7 -0
- package/src/http.ts +4 -2
- package/src/index.ts +39 -9
- package/src/observability-metrics.ts +27 -0
- package/src/sandbox-resume.ts +9 -0
- package/src/sandbox-routing.ts +77 -0
- package/src/session-event-fanout.ts +23 -0
- package/src/worker-service-lifecycle.ts +53 -0
- package/src/workflows/file-upload-reaper.ts +2 -0
- package/src/workflows/session.ts +6 -1
- package/dist/activities-control-3HAH4FMG.js.map +0 -1
- package/dist/activities-turn-KU25J3VQ.js.map +0 -1
- package/dist/chunk-3K3ULWEE.js.map +0 -1
- package/dist/chunk-YG27NEMX.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2-canary.37090756480001",
|
|
4
4
|
"description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,26 +54,26 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@llamaindex/liteparse": "2.14.2",
|
|
57
|
-
"@opengeni/agent-proto": "^0.6.1",
|
|
58
|
-
"@opengeni/capabilities": "^0.3.
|
|
59
|
-
"@opengeni/codemode": "^0.
|
|
60
|
-
"@opengeni/codex": "^0.2.
|
|
61
|
-
"@opengeni/config": "^3.
|
|
62
|
-
"@opengeni/contracts": "^5.
|
|
63
|
-
"@opengeni/core": "^5.
|
|
64
|
-
"@opengeni/db": "^6.
|
|
65
|
-
"@opengeni/documents": "^0.8.
|
|
66
|
-
"@opengeni/events": "^0.4.
|
|
67
|
-
"@opengeni/github": "^0.8.
|
|
68
|
-
"@opengeni/interaction": "^0.4.
|
|
69
|
-
"@opengeni/jev": "^0.2.1",
|
|
70
|
-
"@opengeni/network": "^0.3.1",
|
|
71
|
-
"@opengeni/observability": "^0.8.
|
|
72
|
-
"@opengeni/runtime": "^4.
|
|
73
|
-
"@opengeni/sdk": "^7.
|
|
74
|
-
"@opengeni/storage": "^0.2.
|
|
75
|
-
"@opengeni/tool-gateway": "^0.1.
|
|
76
|
-
"@opengeni/xai-subscription": "^0.1.5",
|
|
57
|
+
"@opengeni/agent-proto": "^0.6.1-canary.37090756480001",
|
|
58
|
+
"@opengeni/capabilities": "^0.3.6-canary.37090756480001",
|
|
59
|
+
"@opengeni/codemode": "^0.7.0-canary.37090756480001",
|
|
60
|
+
"@opengeni/codex": "^0.2.32-canary.37090756480001",
|
|
61
|
+
"@opengeni/config": "^3.3.0-canary.37090756480001",
|
|
62
|
+
"@opengeni/contracts": "^5.7.0-canary.37090756480001",
|
|
63
|
+
"@opengeni/core": "^5.2.0-canary.37090756480001",
|
|
64
|
+
"@opengeni/db": "^6.6.0-canary.37090756480001",
|
|
65
|
+
"@opengeni/documents": "^0.8.41-canary.37090756480001",
|
|
66
|
+
"@opengeni/events": "^0.4.39-canary.37090756480001",
|
|
67
|
+
"@opengeni/github": "^0.8.3-canary.37090756480001",
|
|
68
|
+
"@opengeni/interaction": "^0.4.46-canary.37090756480001",
|
|
69
|
+
"@opengeni/jev": "^0.2.1-canary.37090756480001",
|
|
70
|
+
"@opengeni/network": "^0.3.1-canary.37090756480001",
|
|
71
|
+
"@opengeni/observability": "^0.8.38-canary.37090756480001",
|
|
72
|
+
"@opengeni/runtime": "^4.7.0-canary.37090756480001",
|
|
73
|
+
"@opengeni/sdk": "^7.7.0-canary.37090756480001",
|
|
74
|
+
"@opengeni/storage": "^0.2.139-canary.37090756480001",
|
|
75
|
+
"@opengeni/tool-gateway": "^0.1.19-canary.37090756480001",
|
|
76
|
+
"@opengeni/xai-subscription": "^0.1.5-canary.37090756480001",
|
|
77
77
|
"@temporalio/activity": "^1.17.0",
|
|
78
78
|
"@temporalio/client": "^1.17.0",
|
|
79
79
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withDirectModelProviders } from "@opengeni/config";
|
|
2
|
+
import { loadDirectModelProviderConnection } from "@opengeni/db";
|
|
1
3
|
import { FILESYSTEM_DISCONTINUITY_PROTOCOL } from "./recovery-warning";
|
|
2
4
|
import {
|
|
3
5
|
applySessionTurnSettlement,
|
|
@@ -288,6 +290,17 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
288
290
|
workspaceProviderSettings,
|
|
289
291
|
claimedPolicy.kind === "valid" ? claimedPolicy.policy.productModelId : turn.model,
|
|
290
292
|
);
|
|
293
|
+
const selectedDirectConnection = await loadDirectModelProviderConnection(
|
|
294
|
+
db,
|
|
295
|
+
capabilitySettings,
|
|
296
|
+
input.workspaceId,
|
|
297
|
+
claimedPolicy.kind === "valid" ? claimedPolicy.policy.productModelId : (turn.model ?? ""),
|
|
298
|
+
);
|
|
299
|
+
// Execution only needs the selected customer connection. Ordinary turns
|
|
300
|
+
// must not load or install unrelated workspace provider configurations.
|
|
301
|
+
if (selectedDirectConnection) {
|
|
302
|
+
capabilitySettings = withDirectModelProviders(capabilitySettings, [selectedDirectConnection]);
|
|
303
|
+
}
|
|
291
304
|
const codexAppsCredentialId = capabilitySettings.codexConnectedAppsEnabled
|
|
292
305
|
? await resolveCodexAppsCredentialIdForRun(db, input.workspaceId)
|
|
293
306
|
: null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import {
|
|
2
3
|
emptyClaudeUsage,
|
|
3
4
|
mergeClaudeUsage,
|
|
@@ -7,7 +8,22 @@ import {
|
|
|
7
8
|
} from "@opengeni/config";
|
|
8
9
|
|
|
9
10
|
type Scope = "workspace" | "organization";
|
|
11
|
+
type ClaudeRequestCredential = {
|
|
12
|
+
token: string;
|
|
13
|
+
connectionId: string;
|
|
14
|
+
credentialVersion: number;
|
|
15
|
+
};
|
|
16
|
+
export class ClaudeSubscriptionConnectionUnavailable extends Error {
|
|
17
|
+
readonly status = 409;
|
|
18
|
+
readonly code = "claude_subscription_connection_changed";
|
|
19
|
+
constructor() {
|
|
20
|
+
super(
|
|
21
|
+
"Claude connection changed or was disconnected. Start a new turn with the current connection.",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
10
25
|
export type CapturedClaudeUsage = {
|
|
26
|
+
scope: Scope;
|
|
11
27
|
token: string;
|
|
12
28
|
expectedConnectionId: string;
|
|
13
29
|
expectedCredentialVersion: number;
|
|
@@ -18,13 +34,22 @@ export type CapturedClaudeUsage = {
|
|
|
18
34
|
/** Capture the exact credential before requests; replacement fences late responses. */
|
|
19
35
|
export async function createClaudeUsageObserver(
|
|
20
36
|
providers: ReturnType<typeof parseModelProvidersJson>,
|
|
21
|
-
latest: Map<
|
|
37
|
+
latest: Map<string, CapturedClaudeUsage>,
|
|
22
38
|
readCredential: (scope: Scope) => Promise<{
|
|
23
39
|
token: string;
|
|
24
40
|
connectionId: string;
|
|
25
41
|
credentialVersion: number;
|
|
26
42
|
} | null>,
|
|
27
43
|
) {
|
|
44
|
+
const managedProviderIds = new Set(
|
|
45
|
+
providers
|
|
46
|
+
.filter(
|
|
47
|
+
(provider) =>
|
|
48
|
+
provider.kind === "claude-subscription-workspace" ||
|
|
49
|
+
provider.kind === "claude-subscription-organization",
|
|
50
|
+
)
|
|
51
|
+
.map((provider) => provider.id),
|
|
52
|
+
);
|
|
28
53
|
const bindings = await Promise.all(
|
|
29
54
|
providers
|
|
30
55
|
.filter(
|
|
@@ -60,12 +85,27 @@ export async function createClaudeUsageObserver(
|
|
|
60
85
|
}),
|
|
61
86
|
);
|
|
62
87
|
const captured = new Map(bindings.filter((binding) => binding !== null));
|
|
63
|
-
const
|
|
64
|
-
|
|
88
|
+
const observeBinding = (
|
|
89
|
+
binding:
|
|
90
|
+
| Pick<
|
|
91
|
+
CapturedClaudeUsage,
|
|
92
|
+
"scope" | "token" | "expectedConnectionId" | "expectedCredentialVersion"
|
|
93
|
+
>
|
|
94
|
+
| undefined,
|
|
95
|
+
response: Response,
|
|
96
|
+
upstreamModelId?: string,
|
|
97
|
+
requestToken?: string | null,
|
|
98
|
+
) => {
|
|
99
|
+
if (requestToken === null) return;
|
|
65
100
|
if (!binding) return;
|
|
66
|
-
const { scope, ...
|
|
67
|
-
const
|
|
68
|
-
|
|
101
|
+
const { scope, ...capturedIdentity } = binding;
|
|
102
|
+
const identity = { ...capturedIdentity, token: requestToken ?? capturedIdentity.token };
|
|
103
|
+
// Same-generation OAuth renewal can overlap an older request. Never attach
|
|
104
|
+
// its authentication failure to the newly renewed token or merge the two.
|
|
105
|
+
const tokenKey = createHash("sha256").update(identity.token).digest("hex");
|
|
106
|
+
const captureKey = `${scope}:${identity.expectedConnectionId}:${identity.expectedCredentialVersion}:${tokenKey}`;
|
|
107
|
+
const previous = latest.get(captureKey);
|
|
108
|
+
let observation = parseClaudeUsageHeaders(response.headers, new Date(), upstreamModelId);
|
|
69
109
|
if (observation && previous?.observation) {
|
|
70
110
|
const merged = mergeClaudeUsage(
|
|
71
111
|
mergeClaudeUsage(emptyClaudeUsage(binding.expectedCredentialVersion), previous.observation),
|
|
@@ -75,10 +115,13 @@ export async function createClaudeUsageObserver(
|
|
|
75
115
|
windows: merged.windows,
|
|
76
116
|
observedAt: merged.observedAt!,
|
|
77
117
|
source: merged.source!,
|
|
118
|
+
requestStatus: merged.requestStatus ?? null,
|
|
119
|
+
requestRestrictions: merged.requestRestrictions ?? [],
|
|
78
120
|
};
|
|
79
121
|
}
|
|
80
122
|
if (observation || response.status === 401)
|
|
81
|
-
latest.set(
|
|
123
|
+
latest.set(captureKey, {
|
|
124
|
+
scope,
|
|
82
125
|
...identity,
|
|
83
126
|
...(previous?.observation && !observation ? { observation: previous.observation } : {}),
|
|
84
127
|
...(observation ? { observation } : {}),
|
|
@@ -87,9 +130,57 @@ export async function createClaudeUsageObserver(
|
|
|
87
130
|
: {}),
|
|
88
131
|
});
|
|
89
132
|
};
|
|
133
|
+
const observe = (
|
|
134
|
+
providerId: string,
|
|
135
|
+
response: Response,
|
|
136
|
+
upstreamModelId?: string,
|
|
137
|
+
requestToken?: string | null,
|
|
138
|
+
) => observeBinding(captured.get(providerId), response, upstreamModelId, requestToken);
|
|
139
|
+
const prepareRequestWithObserver = async (
|
|
140
|
+
providerId: string,
|
|
141
|
+
headers: Headers,
|
|
142
|
+
resolve: (binding: {
|
|
143
|
+
scope: Scope;
|
|
144
|
+
expectedConnectionId: string;
|
|
145
|
+
expectedCredentialVersion: number;
|
|
146
|
+
}) => Promise<ClaudeRequestCredential | null>,
|
|
147
|
+
) => {
|
|
148
|
+
if (!managedProviderIds.has(providerId)) return { headers, observe };
|
|
149
|
+
const binding = captured.get(providerId);
|
|
150
|
+
if (!binding) throw new ClaudeSubscriptionConnectionUnavailable();
|
|
151
|
+
const credential = await resolve({
|
|
152
|
+
scope: binding.scope,
|
|
153
|
+
expectedConnectionId: binding.expectedConnectionId,
|
|
154
|
+
expectedCredentialVersion: binding.expectedCredentialVersion,
|
|
155
|
+
});
|
|
156
|
+
if (
|
|
157
|
+
!credential ||
|
|
158
|
+
credential.connectionId !== binding.expectedConnectionId ||
|
|
159
|
+
credential.credentialVersion !== binding.expectedCredentialVersion
|
|
160
|
+
)
|
|
161
|
+
throw new ClaudeSubscriptionConnectionUnavailable();
|
|
162
|
+
const dispatched = Object.freeze({ ...binding, token: credential.token });
|
|
163
|
+
captured.set(providerId, dispatched);
|
|
164
|
+
headers.set("authorization", `Bearer ${credential.token}`);
|
|
165
|
+
headers.delete("x-api-key");
|
|
166
|
+
return {
|
|
167
|
+
headers,
|
|
168
|
+
observe: (
|
|
169
|
+
_providerId: string,
|
|
170
|
+
response: Response,
|
|
171
|
+
upstreamModelId?: string,
|
|
172
|
+
requestToken?: string | null,
|
|
173
|
+
) => observeBinding(dispatched, response, upstreamModelId, requestToken),
|
|
174
|
+
};
|
|
175
|
+
};
|
|
90
176
|
return Object.assign(observe, {
|
|
177
|
+
prepareRequestWithObserver,
|
|
178
|
+
async prepareRequest(...args: Parameters<typeof prepareRequestWithObserver>) {
|
|
179
|
+
return (await prepareRequestWithObserver(...args)).headers;
|
|
180
|
+
},
|
|
91
181
|
binding(providerId: string) {
|
|
92
|
-
|
|
182
|
+
const binding = captured.get(providerId);
|
|
183
|
+
return binding ? { ...binding } : undefined;
|
|
93
184
|
},
|
|
94
185
|
renew(
|
|
95
186
|
providerId: string,
|
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
heartbeatXaiCredentialLeaseUntil,
|
|
6
6
|
} from "@opengeni/db";
|
|
7
7
|
import type { SharedActivityServices } from "../types";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
SubscriptionTurnLease,
|
|
10
|
+
type LeaseRenewReason,
|
|
11
|
+
type LeaseLossReason,
|
|
12
|
+
} from "./subscription-lease";
|
|
13
|
+
export type { LeaseRenewReason, LeaseLossReason } from "./subscription-lease";
|
|
9
14
|
import { safeErrorDiagnostic } from "./errors";
|
|
10
15
|
|
|
11
|
-
export type LeaseRenewReason = "timer" | "runtime_event" | "model_usage";
|
|
12
|
-
export type LeaseLossReason = "deadline" | "not_found";
|
|
13
|
-
|
|
14
16
|
export class CodexCredentialLeaseLostError extends Error {
|
|
15
17
|
readonly code = "codex_credential_lease_lost";
|
|
16
18
|
|
|
@@ -34,223 +36,94 @@ export type TurnCredentialLeaseDeps = {
|
|
|
34
36
|
* cross-replica fairness primitive; the heartbeat here only extends its short
|
|
35
37
|
* TTL. A killed worker stops heartbeating and the holder self-expires.
|
|
36
38
|
*/
|
|
37
|
-
export class CodexTurnLease {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.lost = true;
|
|
56
|
-
this.lossReason = reason;
|
|
57
|
-
this.deps.observability.incrementCounter({
|
|
58
|
-
name: "opengeni_codex_lease_renewals_total",
|
|
59
|
-
help: "Codex lease renewal checkpoints by outcome and reason.",
|
|
60
|
-
labels: { workspace_key: this.deps.codexWorkspaceKey, outcome: "lost", reason },
|
|
61
|
-
});
|
|
62
|
-
this.deps.observability.warn("Codex credential lease was lost during an active turn", {
|
|
63
|
-
workspaceId: this.deps.workspaceId,
|
|
64
|
-
turnId: this.deps.getTurnId(),
|
|
65
|
-
reason,
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/** Fail closed at the last worker-confirmed lease deadline before dispatch. */
|
|
70
|
-
assertUsable = (): void => {
|
|
71
|
-
if (this.lost) {
|
|
72
|
-
throw new CodexCredentialLeaseLostError(this.lossReason ?? "not_found");
|
|
73
|
-
}
|
|
74
|
-
if (
|
|
75
|
-
!this.held ||
|
|
76
|
-
this.holderId === null ||
|
|
77
|
-
this.generation === null ||
|
|
78
|
-
codexCredentialLeaseDeadlineExpired(this.confirmedUntilMs)
|
|
79
|
-
) {
|
|
80
|
-
this.markLost("deadline");
|
|
81
|
-
throw new CodexCredentialLeaseLostError("deadline");
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
renew = async (reason: LeaseRenewReason): Promise<void> => {
|
|
86
|
-
const turnId = this.deps.getTurnId();
|
|
87
|
-
if (!turnId || !this.held || !this.holderId || this.generation === null || this.lost) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (codexCredentialLeaseDeadlineExpired(this.confirmedUntilMs)) {
|
|
91
|
-
this.markLost("deadline");
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (this.heartbeatInFlight) return;
|
|
95
|
-
this.heartbeatInFlight = true;
|
|
96
|
-
const priorConfirmedUntilMs = this.confirmedUntilMs;
|
|
97
|
-
const renewalStartedAtMs = performance.now();
|
|
98
|
-
try {
|
|
99
|
-
const renewedUntil = await heartbeatCodexCredentialLeaseUntil(
|
|
100
|
-
this.deps.db,
|
|
101
|
-
this.deps.accountId,
|
|
102
|
-
this.deps.workspaceId,
|
|
103
|
-
turnId,
|
|
104
|
-
this.holderId,
|
|
105
|
-
this.generation,
|
|
106
|
-
CODEX_CREDENTIAL_LEASE_TTL_MS,
|
|
107
|
-
);
|
|
108
|
-
if (!renewedUntil) {
|
|
109
|
-
this.markLost("not_found");
|
|
110
|
-
} else if (
|
|
111
|
-
codexCredentialLeaseDeadlineExpired(priorConfirmedUntilMs) ||
|
|
112
|
-
codexCredentialLeaseDeadlineExpired(renewalStartedAtMs + CODEX_CREDENTIAL_LEASE_TTL_MS)
|
|
113
|
-
) {
|
|
114
|
-
// The database call may have waited on a lock until the prior lease
|
|
115
|
-
// expired. A late successful UPDATE is not proof that this worker
|
|
116
|
-
// owned the credential continuously; do not accept or extend it.
|
|
117
|
-
this.markLost("deadline");
|
|
118
|
-
} else {
|
|
119
|
-
this.confirmedUntilMs = renewalStartedAtMs + CODEX_CREDENTIAL_LEASE_TTL_MS;
|
|
120
|
-
this.deps.observability.incrementCounter({
|
|
39
|
+
export class CodexTurnLease extends SubscriptionTurnLease {
|
|
40
|
+
constructor(deps: TurnCredentialLeaseDeps) {
|
|
41
|
+
super({
|
|
42
|
+
ttlMs: CODEX_CREDENTIAL_LEASE_TTL_MS,
|
|
43
|
+
getTurnId: deps.getTurnId,
|
|
44
|
+
heartbeat: ({ turnId, holderId, generation }) =>
|
|
45
|
+
heartbeatCodexCredentialLeaseUntil(
|
|
46
|
+
deps.db,
|
|
47
|
+
deps.accountId,
|
|
48
|
+
deps.workspaceId,
|
|
49
|
+
turnId,
|
|
50
|
+
holderId,
|
|
51
|
+
generation,
|
|
52
|
+
CODEX_CREDENTIAL_LEASE_TTL_MS,
|
|
53
|
+
),
|
|
54
|
+
lostError: (reason) => new CodexCredentialLeaseLostError(reason),
|
|
55
|
+
onLost: (reason) => {
|
|
56
|
+
deps.observability.incrementCounter({
|
|
121
57
|
name: "opengeni_codex_lease_renewals_total",
|
|
122
58
|
help: "Codex lease renewal checkpoints by outcome and reason.",
|
|
123
|
-
labels: {
|
|
124
|
-
workspace_key: this.deps.codexWorkspaceKey,
|
|
125
|
-
outcome: "completed",
|
|
126
|
-
reason,
|
|
127
|
-
},
|
|
59
|
+
labels: { workspace_key: deps.codexWorkspaceKey, outcome: "lost", reason },
|
|
128
60
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.markLost("deadline");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
this.deps.observability.warn("Codex credential lease heartbeat failed", {
|
|
136
|
-
workspaceId: this.deps.workspaceId,
|
|
137
|
-
turnId,
|
|
138
|
-
reason,
|
|
139
|
-
...safeErrorDiagnostic(error),
|
|
140
|
-
});
|
|
141
|
-
this.deps.observability.incrementCounter({
|
|
142
|
-
name: "opengeni_codex_lease_renewals_total",
|
|
143
|
-
help: "Codex lease renewal checkpoints by outcome and reason.",
|
|
144
|
-
labels: {
|
|
145
|
-
workspace_key: this.deps.codexWorkspaceKey,
|
|
146
|
-
outcome: "error",
|
|
61
|
+
deps.observability.warn("Codex credential lease was lost during an active turn", {
|
|
62
|
+
workspaceId: deps.workspaceId,
|
|
63
|
+
turnId: deps.getTurnId(),
|
|
147
64
|
reason,
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
onRenewed: (reason) =>
|
|
68
|
+
deps.observability.incrementCounter({
|
|
69
|
+
name: "opengeni_codex_lease_renewals_total",
|
|
70
|
+
help: "Codex lease renewal checkpoints by outcome and reason.",
|
|
71
|
+
labels: { workspace_key: deps.codexWorkspaceKey, outcome: "completed", reason },
|
|
72
|
+
}),
|
|
73
|
+
onError: (error, reason) => {
|
|
74
|
+
deps.observability.warn("Codex credential lease heartbeat failed", {
|
|
75
|
+
workspaceId: deps.workspaceId,
|
|
76
|
+
turnId: deps.getTurnId(),
|
|
77
|
+
reason,
|
|
78
|
+
...safeErrorDiagnostic(error),
|
|
79
|
+
});
|
|
80
|
+
deps.observability.incrementCounter({
|
|
81
|
+
name: "opengeni_codex_lease_renewals_total",
|
|
82
|
+
help: "Codex lease renewal checkpoints by outcome and reason.",
|
|
83
|
+
labels: { workspace_key: deps.codexWorkspaceKey, outcome: "error", reason },
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
168
88
|
}
|
|
169
89
|
|
|
170
|
-
/**
|
|
171
|
-
export class XaiTurnLease {
|
|
172
|
-
held = false;
|
|
173
|
-
lost = false;
|
|
90
|
+
/** SuperGrok uses the same deadline and holder fences as Codex. */
|
|
91
|
+
export class XaiTurnLease extends SubscriptionTurnLease {
|
|
174
92
|
subjectId: string | null = null;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
93
|
+
constructor(deps: TurnCredentialLeaseDeps) {
|
|
94
|
+
super({
|
|
95
|
+
ttlMs: XAI_CREDENTIAL_LEASE_TTL_MS,
|
|
96
|
+
getTurnId: deps.getTurnId,
|
|
97
|
+
heartbeat: ({ turnId, holderId, generation }) =>
|
|
98
|
+
this.subjectId
|
|
99
|
+
? heartbeatXaiCredentialLeaseUntil(deps.db, {
|
|
100
|
+
workspaceId: deps.workspaceId,
|
|
101
|
+
subjectId: this.subjectId,
|
|
102
|
+
turnId,
|
|
103
|
+
holderId,
|
|
104
|
+
generation,
|
|
105
|
+
leaseTtlMs: XAI_CREDENTIAL_LEASE_TTL_MS,
|
|
106
|
+
})
|
|
107
|
+
: Promise.resolve(null),
|
|
108
|
+
lostError: (reason) =>
|
|
109
|
+
Object.assign(new Error("SuperGrok credential lease is not usable for provider dispatch"), {
|
|
110
|
+
code: "xai_credential_lease_lost",
|
|
111
|
+
reason,
|
|
112
|
+
}),
|
|
113
|
+
onLost: (reason) =>
|
|
114
|
+
deps.observability.warn("xAI credential lease was lost during an active turn", {
|
|
115
|
+
workspaceId: deps.workspaceId,
|
|
116
|
+
turnId: deps.getTurnId(),
|
|
117
|
+
reason,
|
|
118
|
+
}),
|
|
119
|
+
onError: (error) =>
|
|
120
|
+
deps.observability.warn("xAI credential lease heartbeat failed", {
|
|
121
|
+
workspaceId: deps.workspaceId,
|
|
122
|
+
turnId: deps.getTurnId(),
|
|
123
|
+
...safeErrorDiagnostic(error),
|
|
124
|
+
}),
|
|
190
125
|
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
renew = async (): Promise<void> => {
|
|
194
|
-
const turnId = this.deps.getTurnId();
|
|
195
|
-
if (
|
|
196
|
-
!turnId ||
|
|
197
|
-
!this.held ||
|
|
198
|
-
!this.subjectId ||
|
|
199
|
-
!this.holderId ||
|
|
200
|
-
this.generation === null ||
|
|
201
|
-
this.lost
|
|
202
|
-
) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (codexCredentialLeaseDeadlineExpired(this.confirmedUntilMs)) {
|
|
206
|
-
this.markLost("deadline");
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (this.heartbeatInFlight) return;
|
|
210
|
-
this.heartbeatInFlight = true;
|
|
211
|
-
const renewalStartedAtMs = performance.now();
|
|
212
|
-
try {
|
|
213
|
-
const renewedUntil = await heartbeatXaiCredentialLeaseUntil(this.deps.db, {
|
|
214
|
-
workspaceId: this.deps.workspaceId,
|
|
215
|
-
subjectId: this.subjectId,
|
|
216
|
-
turnId,
|
|
217
|
-
holderId: this.holderId,
|
|
218
|
-
generation: this.generation,
|
|
219
|
-
leaseTtlMs: XAI_CREDENTIAL_LEASE_TTL_MS,
|
|
220
|
-
});
|
|
221
|
-
if (!renewedUntil) {
|
|
222
|
-
this.markLost("not_found");
|
|
223
|
-
} else {
|
|
224
|
-
this.confirmedUntilMs = renewalStartedAtMs + XAI_CREDENTIAL_LEASE_TTL_MS;
|
|
225
|
-
}
|
|
226
|
-
} catch (error) {
|
|
227
|
-
if (codexCredentialLeaseDeadlineExpired(this.confirmedUntilMs)) {
|
|
228
|
-
this.markLost("deadline");
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
this.deps.observability.warn("xAI credential lease heartbeat failed", {
|
|
232
|
-
workspaceId: this.deps.workspaceId,
|
|
233
|
-
turnId,
|
|
234
|
-
...safeErrorDiagnostic(error),
|
|
235
|
-
});
|
|
236
|
-
} finally {
|
|
237
|
-
this.heartbeatInFlight = false;
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
startHeartbeat = (): void => {
|
|
242
|
-
if (!this.deps.getTurnId() || this.heartbeatTimer) return;
|
|
243
|
-
this.heartbeatTimer = setInterval(() => {
|
|
244
|
-
void this.renew();
|
|
245
|
-
}, 60_000);
|
|
246
|
-
this.heartbeatTimer.unref?.();
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
stopHeartbeat = (): void => {
|
|
250
|
-
if (!this.heartbeatTimer) return;
|
|
251
|
-
clearInterval(this.heartbeatTimer);
|
|
252
|
-
this.heartbeatTimer = undefined;
|
|
253
|
-
};
|
|
126
|
+
}
|
|
254
127
|
}
|
|
255
128
|
|
|
256
129
|
/** Both serving-credential leases for one turn attempt plus their composite views. */
|