@opengeni/core 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/billing/agent-run-admission.d.ts +17 -0
- package/dist/default-session-model.d.ts +5 -3
- package/dist/dependencies.d.ts +3 -1
- package/dist/domain/knowledge-messages.d.ts +2 -2
- package/dist/goal-admission.d.ts +488 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +287 -52
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/application/session-tenancy.ts +4 -8
- package/src/application/user-resource-grants.ts +0 -13
- package/src/billing/agent-run-admission.ts +107 -0
- package/src/billing/limits.ts +14 -3
- package/src/default-session-model.ts +23 -12
- package/src/dependencies.ts +3 -0
- package/src/domain/knowledge-search.test.ts +1 -1
- package/src/domain/knowledge-search.ts +2 -2
- package/src/domain/session-tool-policy.ts +14 -5
- package/src/domain/sessions.ts +11 -6
- package/src/goal-admission.ts +187 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isModelAvailableForNewSelection,
|
|
3
|
+
policyProviderIdForModel,
|
|
4
|
+
resolveModelProvider,
|
|
5
|
+
runnableLatencyModesForModel,
|
|
6
|
+
withCodexCatalogProvider,
|
|
7
|
+
withXaiSubscriptionCatalogProvider,
|
|
8
|
+
type Settings,
|
|
9
|
+
} from "@opengeni/config";
|
|
10
|
+
import {
|
|
11
|
+
evaluateWorkspaceModelPolicy,
|
|
12
|
+
type GoalAdmissionPausedReason,
|
|
13
|
+
type Session,
|
|
14
|
+
} from "@opengeni/contracts";
|
|
15
|
+
import { isCodexBilledModel } from "@opengeni/codex";
|
|
16
|
+
import { getWorkspaceModelPolicy, type Database } from "@opengeni/db";
|
|
17
|
+
import { resolveWorkspaceCatalogSettings } from "./model-catalog";
|
|
18
|
+
import { agentRunAdmissionDenial } from "./billing/agent-run-admission";
|
|
19
|
+
|
|
20
|
+
export type GoalAdmissionBlock = { pausedReason: GoalAdmissionPausedReason; message: string };
|
|
21
|
+
|
|
22
|
+
/** Catalog membership never grants access to a connection or changes the selected model. */
|
|
23
|
+
export function goalContinuationModelDecision(input: {
|
|
24
|
+
settings: Settings;
|
|
25
|
+
workspaceModelPolicy: Awaited<ReturnType<typeof getWorkspaceModelPolicy>>;
|
|
26
|
+
inheritedModel: string;
|
|
27
|
+
codexCompactionMode?: Session["codexCompactionMode"];
|
|
28
|
+
latencyMode?: Session["latencyMode"];
|
|
29
|
+
}): { model: string; blocked: string | null; pausedReason?: GoalAdmissionPausedReason } {
|
|
30
|
+
const catalogSettings = input.settings.supergrokSubscriptionEnabled
|
|
31
|
+
? withXaiSubscriptionCatalogProvider(
|
|
32
|
+
input.settings.codexSubscriptionEnabled
|
|
33
|
+
? withCodexCatalogProvider(input.settings)
|
|
34
|
+
: input.settings,
|
|
35
|
+
)
|
|
36
|
+
: input.settings.codexSubscriptionEnabled
|
|
37
|
+
? withCodexCatalogProvider(input.settings)
|
|
38
|
+
: input.settings;
|
|
39
|
+
const model = input.inheritedModel;
|
|
40
|
+
if (!resolveModelProvider(catalogSettings, model)) {
|
|
41
|
+
return {
|
|
42
|
+
model,
|
|
43
|
+
blocked: "The selected model is unavailable. Choose an available model before resuming.",
|
|
44
|
+
pausedReason: "model_unavailable",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (!isModelAvailableForNewSelection(catalogSettings, model)) {
|
|
48
|
+
return {
|
|
49
|
+
model,
|
|
50
|
+
blocked: "The selected model has been retired. Choose an available model before resuming.",
|
|
51
|
+
pausedReason: "model_unavailable",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
input.workspaceModelPolicy !== null &&
|
|
56
|
+
!evaluateWorkspaceModelPolicy(input.workspaceModelPolicy, {
|
|
57
|
+
providerId: policyProviderIdForModel(catalogSettings, model),
|
|
58
|
+
modelId: model,
|
|
59
|
+
}).allowed
|
|
60
|
+
) {
|
|
61
|
+
return {
|
|
62
|
+
model,
|
|
63
|
+
blocked:
|
|
64
|
+
"Workspace policy blocks the selected model. Choose an allowed model before resuming.",
|
|
65
|
+
pausedReason: "model_policy",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (input.codexCompactionMode === "remote_v2" && !isCodexBilledModel(model)) {
|
|
69
|
+
return {
|
|
70
|
+
model,
|
|
71
|
+
blocked: "This conversation requires a Codex model. Choose a Codex model before resuming.",
|
|
72
|
+
pausedReason: "model_policy",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
!runnableLatencyModesForModel(catalogSettings, model).includes(input.latencyMode ?? "standard")
|
|
77
|
+
) {
|
|
78
|
+
return {
|
|
79
|
+
model,
|
|
80
|
+
blocked:
|
|
81
|
+
"The selected model does not support this conversation's latency mode. Choose a supported mode or model before resuming.",
|
|
82
|
+
pausedReason: "model_policy",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { model, blocked: null };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The same scoped, secret-free catalog used at ordinary turn admission. */
|
|
89
|
+
export async function resolveGoalModelAdmission(
|
|
90
|
+
db: Database,
|
|
91
|
+
settings: Settings,
|
|
92
|
+
input: {
|
|
93
|
+
accountId: string;
|
|
94
|
+
workspaceId: string;
|
|
95
|
+
model: string;
|
|
96
|
+
codexCompactionMode?: Session["codexCompactionMode"];
|
|
97
|
+
latencyMode?: Session["latencyMode"];
|
|
98
|
+
},
|
|
99
|
+
) {
|
|
100
|
+
const catalog = await resolveWorkspaceCatalogSettings(db, settings, {
|
|
101
|
+
accountId: input.accountId,
|
|
102
|
+
workspaceId: input.workspaceId,
|
|
103
|
+
retainedProductModelId: input.model,
|
|
104
|
+
});
|
|
105
|
+
const workspaceModelPolicy = await getWorkspaceModelPolicy(db, input.workspaceId);
|
|
106
|
+
return {
|
|
107
|
+
settings: catalog.settings,
|
|
108
|
+
...goalContinuationModelDecision({
|
|
109
|
+
settings: catalog.settings,
|
|
110
|
+
workspaceModelPolicy,
|
|
111
|
+
inheritedModel: input.model,
|
|
112
|
+
...(input.codexCompactionMode ? { codexCompactionMode: input.codexCompactionMode } : {}),
|
|
113
|
+
...(input.latencyMode ? { latencyMode: input.latencyMode } : {}),
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Shared with scheduled work; reasons describe the actual admission gate. */
|
|
119
|
+
export async function goalRunBudgetBlocked(
|
|
120
|
+
services: Parameters<typeof agentRunAdmissionDenial>[0],
|
|
121
|
+
input: Omit<Parameters<typeof agentRunAdmissionDenial>[1], "requestedAgentRuns">,
|
|
122
|
+
): Promise<GoalAdmissionBlock | null> {
|
|
123
|
+
const denial = await agentRunAdmissionDenial(services, { ...input, requestedAgentRuns: 1 });
|
|
124
|
+
if (denial === null) return null;
|
|
125
|
+
const blocks: Record<NonNullable<typeof denial>, GoalAdmissionBlock> = {
|
|
126
|
+
insufficient_credits: services.entitlements
|
|
127
|
+
? {
|
|
128
|
+
pausedReason: "usage_policy",
|
|
129
|
+
message: "The application's usage policy blocks another run. Resume when it allows.",
|
|
130
|
+
}
|
|
131
|
+
: {
|
|
132
|
+
pausedReason: "credits",
|
|
133
|
+
message: "Insufficient Opengeni credits. Add credits before resuming.",
|
|
134
|
+
},
|
|
135
|
+
allowance_exhausted: {
|
|
136
|
+
pausedReason: "allowance",
|
|
137
|
+
message: "Opengeni usage allowance exhausted. Resume when your allowance is available.",
|
|
138
|
+
},
|
|
139
|
+
monthly_model_cost_limit: {
|
|
140
|
+
pausedReason: "budget",
|
|
141
|
+
message: "Monthly model spending limit reached. Resume when the spending limit allows.",
|
|
142
|
+
},
|
|
143
|
+
monthly_agent_run_limit: {
|
|
144
|
+
pausedReason: "usage_limit",
|
|
145
|
+
message: "Monthly agent run limit reached. Resume when the run limit allows.",
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
return blocks[denial];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class GoalResumeBlockedError extends Error {
|
|
152
|
+
constructor(
|
|
153
|
+
readonly pausedReason: GoalAdmissionPausedReason,
|
|
154
|
+
message: string,
|
|
155
|
+
) {
|
|
156
|
+
super(message);
|
|
157
|
+
this.name = "GoalResumeBlockedError";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Called under the goal transition's locks, before counters, events or wakes change. */
|
|
162
|
+
export async function assertGoalResumeAllowed(
|
|
163
|
+
services: Parameters<typeof agentRunAdmissionDenial>[0] & { catalogSourceSettings?: Settings },
|
|
164
|
+
session: Pick<Session, "accountId" | "workspaceId" | "model" | "codexCompactionMode"> &
|
|
165
|
+
Partial<Pick<Session, "latencyMode">>,
|
|
166
|
+
causalTurn: { initiatingHumanSubjectId: string | null } | null,
|
|
167
|
+
): Promise<void> {
|
|
168
|
+
const decision = await resolveGoalModelAdmission(
|
|
169
|
+
services.db,
|
|
170
|
+
services.catalogSourceSettings ?? services.settings,
|
|
171
|
+
session,
|
|
172
|
+
);
|
|
173
|
+
if (decision.blocked) {
|
|
174
|
+
throw new GoalResumeBlockedError(decision.pausedReason!, decision.blocked);
|
|
175
|
+
}
|
|
176
|
+
const block = await goalRunBudgetBlocked(
|
|
177
|
+
{ ...services, settings: decision.settings },
|
|
178
|
+
{
|
|
179
|
+
accountId: session.accountId,
|
|
180
|
+
workspaceId: session.workspaceId,
|
|
181
|
+
model: decision.model,
|
|
182
|
+
// Resuming does not lend the clicking user's allowance or credentials.
|
|
183
|
+
initiatingHumanSubjectId: causalTurn?.initiatingHumanSubjectId ?? null,
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
if (block) throw new GoalResumeBlockedError(block.pausedReason, block.message);
|
|
187
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,7 @@ export {
|
|
|
63
63
|
} from "./managed-session";
|
|
64
64
|
export * from "./transcription";
|
|
65
65
|
export * from "./model-catalog";
|
|
66
|
+
export * from "./goal-admission";
|
|
66
67
|
export * from "./codex-model-availability";
|
|
67
68
|
export * from "./default-session-model";
|
|
68
69
|
|
|
@@ -86,6 +87,7 @@ export * from "./session-authorization";
|
|
|
86
87
|
|
|
87
88
|
// Billing / usage-limit admission (checkLimit / requireLimit / recordWorkspaceUsage).
|
|
88
89
|
export * from "./billing/limits";
|
|
90
|
+
export * from "./billing/agent-run-admission";
|
|
89
91
|
|
|
90
92
|
// Domain layer — the off-HTTP V2 surface (createSessionForRequest,
|
|
91
93
|
|