@opengeni/core 2.6.4 → 2.7.2-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 +12 -1
- package/dist/domain/host-mcp-authority-source-admission.d.ts +10 -0
- package/dist/domain/packs.d.ts +1 -0
- package/dist/domain/product-integration-pack.d.ts +50 -0
- package/dist/domain/sessions.d.ts +25 -11
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2226 -694
- package/dist/index.js.map +1 -1
- package/dist/model-catalog.d.ts +105 -0
- package/dist/sandbox/fleet.d.ts +19 -0
- package/package.json +10 -10
- package/src/billing/limits.ts +52 -22
- package/src/dependencies.ts +17 -1
- package/src/domain/capabilities.ts +19 -3
- package/src/domain/host-mcp-authority-source-admission.ts +25 -0
- package/src/domain/insights.ts +32 -0
- package/src/domain/packs.ts +48 -18
- package/src/domain/personal-connection-delegations.ts +14 -6
- package/src/domain/product-integration-pack.ts +494 -0
- package/src/domain/scheduled-tasks.ts +43 -1
- package/src/domain/sessions.ts +964 -386
- package/src/index.ts +3 -0
- package/src/model-catalog.ts +742 -0
- package/src/sandbox/fleet.ts +85 -23
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type ConfiguredModel, type Settings } from "@opengeni/config";
|
|
2
|
+
import { type ModelAvailabilityV1, type ModelCredentialReadinessV1, type WorkspaceModelPolicyContract } from "@opengeni/contracts";
|
|
3
|
+
import { type Database } from "@opengeni/db";
|
|
4
|
+
export type ResolvedCatalogSettings = {
|
|
5
|
+
settings: Settings;
|
|
6
|
+
source: "code" | "database";
|
|
7
|
+
version: number | null;
|
|
8
|
+
modelNotes: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Curated workspace Gateway products and workspace-owned custom slugs share
|
|
12
|
+
* one public prefix. Only the latter have a mutable catalog row whose active
|
|
13
|
+
* generation must be rechecked at a fresh acceptance commit boundary.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isWorkspaceGatewayCustomModelId(settings: Settings, modelId: string): boolean;
|
|
16
|
+
export declare function isWorkspaceOpenRouterCustomModelId(settings: Settings, modelId: string): boolean;
|
|
17
|
+
export type WorkspaceCustomModelReference = {
|
|
18
|
+
scope: "workspace" | "organization";
|
|
19
|
+
providerKind: "vercel_gateway" | "openrouter";
|
|
20
|
+
upstreamModelId: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function workspaceCustomModelReference(settings: Settings, modelId: string): WorkspaceCustomModelReference | null;
|
|
23
|
+
export declare function isWorkspaceCustomModelId(settings: Settings, modelId: string): boolean;
|
|
24
|
+
export declare function lockActiveCustomModelForAdmission(db: Database, input: {
|
|
25
|
+
accountId: string;
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
reference: WorkspaceCustomModelReference;
|
|
28
|
+
}): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the deployment catalog without making synchronous env settings read
|
|
31
|
+
* Postgres. Database mode fails closed when the singleton is absent or invalid;
|
|
32
|
+
* code mode preserves the already-validated env catalog.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveCatalogSettings(db: Database, envSettings: Settings): Promise<ResolvedCatalogSettings>;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the deployment catalog and add only the custom Gateway slugs owned by
|
|
37
|
+
* one workspace. Use this at model-bearing workspace boundaries; public config
|
|
38
|
+
* and deployment-operator surfaces must continue to use `resolveCatalogSettings`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveWorkspaceCatalogSettings(db: Database, envSettings: Settings, input: {
|
|
41
|
+
accountId: string;
|
|
42
|
+
workspaceId: string;
|
|
43
|
+
retainedProductModelId?: string | null;
|
|
44
|
+
retainedProductModelIds?: readonly (string | null | undefined)[];
|
|
45
|
+
}): Promise<ResolvedCatalogSettings>;
|
|
46
|
+
export type ModelAvailabilityObservation = {
|
|
47
|
+
status: "available" | "degraded" | "unavailable";
|
|
48
|
+
reason: "not_entitled" | "provider_unhealthy" | null;
|
|
49
|
+
checkedAt: string;
|
|
50
|
+
};
|
|
51
|
+
export type ModelCredentialReadinessObservation = {
|
|
52
|
+
status: "ready";
|
|
53
|
+
checkedAt: string;
|
|
54
|
+
} | {
|
|
55
|
+
status: "not_ready";
|
|
56
|
+
reason: "prerequisites_missing" | "needs_reauth";
|
|
57
|
+
checkedAt: string;
|
|
58
|
+
} | {
|
|
59
|
+
status: "error";
|
|
60
|
+
reason: "resolver_error";
|
|
61
|
+
checkedAt: string;
|
|
62
|
+
};
|
|
63
|
+
export declare const MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS: number;
|
|
64
|
+
export type WorkspaceModelSelectionInput = {
|
|
65
|
+
settings: Settings;
|
|
66
|
+
policy: WorkspaceModelPolicyContract | null;
|
|
67
|
+
codexSubscriptionActive: boolean;
|
|
68
|
+
xaiSubscriptionActive?: boolean;
|
|
69
|
+
workspaceGatewayConnectionActive?: boolean;
|
|
70
|
+
workspaceOpenRouterConnectionActive?: boolean;
|
|
71
|
+
organizationGatewayConnectionActive?: boolean;
|
|
72
|
+
organizationOpenRouterConnectionActive?: boolean;
|
|
73
|
+
workspaceGatewayCustomModels?: readonly {
|
|
74
|
+
upstreamModelId: string;
|
|
75
|
+
label?: string | null;
|
|
76
|
+
}[];
|
|
77
|
+
workspaceOpenRouterCustomModels?: readonly {
|
|
78
|
+
upstreamModelId: string;
|
|
79
|
+
label?: string | null;
|
|
80
|
+
}[];
|
|
81
|
+
organizationGatewayCustomModels?: readonly {
|
|
82
|
+
upstreamModelId: string;
|
|
83
|
+
label?: string | null;
|
|
84
|
+
}[];
|
|
85
|
+
organizationOpenRouterCustomModels?: readonly {
|
|
86
|
+
upstreamModelId: string;
|
|
87
|
+
label?: string | null;
|
|
88
|
+
}[];
|
|
89
|
+
credentialReadinessObservations?: Readonly<Record<string, ModelCredentialReadinessObservation>> | undefined;
|
|
90
|
+
observations?: Readonly<Record<string, ModelAvailabilityObservation>> | undefined;
|
|
91
|
+
now?: Date | undefined;
|
|
92
|
+
credentialReadinessMaxAgeMs?: number | undefined;
|
|
93
|
+
};
|
|
94
|
+
export type WorkspaceModelSelection = {
|
|
95
|
+
model: ConfiguredModel;
|
|
96
|
+
credentialReadiness: ModelCredentialReadinessV1;
|
|
97
|
+
policyAllowed: boolean;
|
|
98
|
+
availability: ModelAvailabilityV1;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* One shared picker/tool decision. Catalog membership, credential readiness,
|
|
102
|
+
* workspace policy, and optional provider-health observations are evaluated in
|
|
103
|
+
* configured catalog order so every consumer exposes the same selectable set.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveWorkspaceModelSelection(input: WorkspaceModelSelectionInput): WorkspaceModelSelection[];
|
package/dist/sandbox/fleet.d.ts
CHANGED
|
@@ -128,6 +128,24 @@ export type FleetSwapResult = {
|
|
|
128
128
|
* means no compute is attached.
|
|
129
129
|
*/
|
|
130
130
|
export declare function listFleet(services: FleetServices, ctx: FleetContext): Promise<FleetListResult>;
|
|
131
|
+
type FleetResourceContext = Pick<FleetContext, "accountId" | "workspaceId" | "subjectId">;
|
|
132
|
+
export type CreateTimeSandboxTargetPreflight = {
|
|
133
|
+
ok: true;
|
|
134
|
+
targetSandboxId: string;
|
|
135
|
+
workingDir: string | null;
|
|
136
|
+
} | {
|
|
137
|
+
ok: false;
|
|
138
|
+
reason: string;
|
|
139
|
+
code: BackendUnresolvableCode | "invalid_working_directory";
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Validate a named create-time machine target before any session row exists.
|
|
143
|
+
* The caller still commits the pointer with `setActiveSandbox` inside the
|
|
144
|
+
* session-create transaction, which rechecks durable ownership/enrollment
|
|
145
|
+
* authority and closes the remove/revoke race without holding database locks
|
|
146
|
+
* across the liveness probe.
|
|
147
|
+
*/
|
|
148
|
+
export declare function preflightCreateTimeSandboxTarget(services: FleetServices, ctx: FleetResourceContext, target: string, workingDir: string | null): Promise<CreateTimeSandboxTargetPreflight>;
|
|
131
149
|
/**
|
|
132
150
|
* THE SWAP (and attach — identical mechanic). Validate the target's ownership +
|
|
133
151
|
* liveness, then repoint the session via the epoch-fenced CAS `setActiveSandbox`:
|
|
@@ -238,3 +256,4 @@ export declare function provisionSandbox(services: FleetServices, ctx: FleetCont
|
|
|
238
256
|
kind: "selfhosted" | "modal";
|
|
239
257
|
name?: string;
|
|
240
258
|
}): Promise<ProvisionResult>;
|
|
259
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.2-canary.0",
|
|
4
4
|
"description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,15 +54,15 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
57
|
-
"@opengeni/codex": "^0.2.20",
|
|
58
|
-
"@opengeni/config": "^0.
|
|
59
|
-
"@opengeni/contracts": "^2.
|
|
60
|
-
"@opengeni/db": "^3.
|
|
61
|
-
"@opengeni/documents": "^0.8.
|
|
62
|
-
"@opengeni/events": "^0.4.
|
|
63
|
-
"@opengeni/observability": "^0.8.
|
|
64
|
-
"@opengeni/runtime": "^2.0
|
|
65
|
-
"@opengeni/storage": "^0.2.
|
|
57
|
+
"@opengeni/codex": "^0.2.20-canary.2",
|
|
58
|
+
"@opengeni/config": "^0.23.2-canary.0",
|
|
59
|
+
"@opengeni/contracts": "^2.11.1-canary.0",
|
|
60
|
+
"@opengeni/db": "^3.8.2-canary.0",
|
|
61
|
+
"@opengeni/documents": "^0.8.17-canary.0",
|
|
62
|
+
"@opengeni/events": "^0.4.15-canary.0",
|
|
63
|
+
"@opengeni/observability": "^0.8.17-canary.0",
|
|
64
|
+
"@opengeni/runtime": "^2.1.2-canary.0",
|
|
65
|
+
"@opengeni/storage": "^0.2.118-canary.0",
|
|
66
66
|
"hono": "^4.12.18",
|
|
67
67
|
"zod": "^4.2.1"
|
|
68
68
|
},
|
package/src/billing/limits.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
configuredStaticUsageLimits,
|
|
3
|
+
resolveModelProviderForTurn,
|
|
4
|
+
type Settings,
|
|
5
|
+
} from "@opengeni/config";
|
|
2
6
|
import type {
|
|
3
7
|
LimitAction,
|
|
4
8
|
LimitDecision,
|
|
@@ -26,14 +30,35 @@ export type LimitCheckInput = {
|
|
|
26
30
|
workspaceId?: string;
|
|
27
31
|
action: LimitAction;
|
|
28
32
|
quantity?: number;
|
|
29
|
-
// The turn's model id, when the action represents an agent turn.
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// statically ready by definition. Non-model infra actions leave this undefined.
|
|
33
|
+
// The turn's model id, when the action represents an agent turn. The model's
|
|
34
|
+
// deployment cost controls credit/cost gates; upstream metering independently
|
|
35
|
+
// controls the token cap. Connected subscriptions still require live workspace
|
|
36
|
+
// readiness. Non-model infra actions leave this undefined.
|
|
34
37
|
model?: string | null;
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
export function modelFundingForAdmission(
|
|
41
|
+
settings: Settings,
|
|
42
|
+
model: string | null | undefined,
|
|
43
|
+
codexBilled: boolean,
|
|
44
|
+
): { fundedWithoutCredits: boolean; countsTowardTokenCap: boolean } {
|
|
45
|
+
const resolvedModel = model ? resolveModelProviderForTurn(settings, model)?.model : null;
|
|
46
|
+
const codexSubscriptionModel =
|
|
47
|
+
resolvedModel?.credentialSource.kind === "connected_subscription" &&
|
|
48
|
+
resolvedModel.credentialSource.provider === "codex";
|
|
49
|
+
return {
|
|
50
|
+
// A Codex namespace/definition never bypasses credits by itself: the live
|
|
51
|
+
// workspace credential predicate above remains authoritative. SuperGrok's
|
|
52
|
+
// static overlay is likewise secret-free; its worker/provider admission
|
|
53
|
+
// owns live account selection before any upstream request can occur.
|
|
54
|
+
fundedWithoutCredits:
|
|
55
|
+
codexBilled ||
|
|
56
|
+
(resolvedModel != null && !codexSubscriptionModel && resolvedModel.cost !== "credits"),
|
|
57
|
+
countsTowardTokenCap:
|
|
58
|
+
!codexBilled && resolvedModel != null && resolvedModel.billing.upstreamPayer === "deployment",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
37
62
|
export async function requireLimit(deps: LimitDependencies, input: LimitCheckInput): Promise<void> {
|
|
38
63
|
const decision = await checkLimit(deps, input);
|
|
39
64
|
if (decision.allowed) {
|
|
@@ -59,25 +84,22 @@ export async function checkLimit(
|
|
|
59
84
|
model: input.model,
|
|
60
85
|
})
|
|
61
86
|
: false;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
resolved.model.credentialSource.mechanism === "none"
|
|
69
|
-
);
|
|
70
|
-
})()
|
|
71
|
-
: false;
|
|
72
|
-
const externallyBilled = codexBilled || credentialFreeExternal;
|
|
73
|
-
const creditDecision = await checkCreditBalance(deps, input, externallyBilled);
|
|
87
|
+
const { fundedWithoutCredits, countsTowardTokenCap } = modelFundingForAdmission(
|
|
88
|
+
deps.settings,
|
|
89
|
+
input.model,
|
|
90
|
+
codexBilled,
|
|
91
|
+
);
|
|
92
|
+
const creditDecision = await checkCreditBalance(deps, input, fundedWithoutCredits);
|
|
74
93
|
if (!creditDecision.allowed) {
|
|
75
94
|
return creditDecision;
|
|
76
95
|
}
|
|
77
96
|
if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
|
|
78
97
|
return { allowed: true };
|
|
79
98
|
}
|
|
80
|
-
return await checkStaticCaps(deps, input,
|
|
99
|
+
return await checkStaticCaps(deps, input, {
|
|
100
|
+
fundedWithoutCredits,
|
|
101
|
+
countsTowardTokenCap,
|
|
102
|
+
});
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
async function checkCreditBalance(
|
|
@@ -101,10 +123,14 @@ async function checkCreditBalance(
|
|
|
101
123
|
async function checkStaticCaps(
|
|
102
124
|
deps: LimitDependencies,
|
|
103
125
|
input: LimitCheckInput,
|
|
104
|
-
|
|
126
|
+
funding: { fundedWithoutCredits: boolean; countsTowardTokenCap: boolean },
|
|
105
127
|
): Promise<LimitDecision> {
|
|
106
128
|
const limits = configuredStaticUsageLimits(deps.settings);
|
|
107
|
-
if (
|
|
129
|
+
if (
|
|
130
|
+
limits.maxMonthlyCostMicrosPerAccount &&
|
|
131
|
+
isCostlyAction(input.action) &&
|
|
132
|
+
!funding.fundedWithoutCredits
|
|
133
|
+
) {
|
|
108
134
|
const used = await sumUsageQuantity(deps.db, {
|
|
109
135
|
accountId: input.accountId,
|
|
110
136
|
eventType: "model.cost",
|
|
@@ -185,7 +211,11 @@ async function checkStaticCaps(
|
|
|
185
211
|
);
|
|
186
212
|
}
|
|
187
213
|
case "tokens:consume": {
|
|
188
|
-
if (
|
|
214
|
+
if (
|
|
215
|
+
!funding.countsTowardTokenCap ||
|
|
216
|
+
!limits.maxMonthlyTokensPerWorkspace ||
|
|
217
|
+
!input.workspaceId
|
|
218
|
+
) {
|
|
189
219
|
return { allowed: true };
|
|
190
220
|
}
|
|
191
221
|
const used = await sumUsageQuantity(deps.db, {
|
package/src/dependencies.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { ManagedAuthSessionAdapter } from "./managed-auth-session-sets";
|
|
|
19
19
|
import type { ApiSandboxClient, ResumeBoxByIdInput, ResumedSandboxSession } from "./sandbox-types";
|
|
20
20
|
import type { TranscriptionSegmenter, TranscriptionService } from "./transcription";
|
|
21
21
|
import type { EditableArtifactApplicationPort } from "./editable-artifact-live";
|
|
22
|
+
import type { ResolvedCatalogSettings } from "./model-catalog";
|
|
22
23
|
import type {
|
|
23
24
|
EditableArtifactAgentApplication,
|
|
24
25
|
EditableArtifactDurableExportService,
|
|
@@ -129,6 +130,13 @@ export type ManagedEmailTransport = {
|
|
|
129
130
|
|
|
130
131
|
export type AppDependencies = {
|
|
131
132
|
settings: Settings;
|
|
133
|
+
/**
|
|
134
|
+
* Original deployment settings when `settings` is already overlaid with a
|
|
135
|
+
* deployment/workspace catalog snapshot. Model-bearing request adapters set
|
|
136
|
+
* this marker so core admission never feeds a synthetic reviewed provider
|
|
137
|
+
* back through deployment validation.
|
|
138
|
+
*/
|
|
139
|
+
catalogSourceSettings?: Settings;
|
|
132
140
|
db: Database;
|
|
133
141
|
/**
|
|
134
142
|
* Host-composed editable artifact engine. Standalone startup binds the same
|
|
@@ -186,6 +194,8 @@ export type AppDependencies = {
|
|
|
186
194
|
codexFetch?: typeof fetch;
|
|
187
195
|
/** Injectable GitHub transport for deterministic personal-OAuth tests. */
|
|
188
196
|
githubPersonalFetch?: typeof fetch;
|
|
197
|
+
/** Injectable credential-free GitHub transport for public repository verification tests. */
|
|
198
|
+
githubAnonymousFetch?: typeof fetch;
|
|
189
199
|
/** Injectable xAI OAuth/subscription transport for deterministic API/provider tests. */
|
|
190
200
|
xaiFetch?: typeof fetch;
|
|
191
201
|
/** Injectable Slack Web API transport for deterministic bot-connection tests. */
|
|
@@ -225,6 +235,7 @@ export type AppDependencies = {
|
|
|
225
235
|
export type ObjectStorageDependency = ReturnType<typeof createObjectStorage>;
|
|
226
236
|
|
|
227
237
|
export type ApiRouteDeps = AppDependencies & {
|
|
238
|
+
resolveCatalogSettings: () => Promise<ResolvedCatalogSettings>;
|
|
228
239
|
managedEmailTransport: ManagedEmailTransport;
|
|
229
240
|
objectStorage: ObjectStorageDependency;
|
|
230
241
|
githubStateSecret: string;
|
|
@@ -244,7 +255,12 @@ export type ApiRouteDeps = AppDependencies & {
|
|
|
244
255
|
*/
|
|
245
256
|
export type AcceptSessionUserMessageDependencies = Pick<
|
|
246
257
|
AppDependencies,
|
|
247
|
-
|
|
258
|
+
| "settings"
|
|
259
|
+
| "catalogSourceSettings"
|
|
260
|
+
| "db"
|
|
261
|
+
| "bus"
|
|
262
|
+
| "sessionAuthorization"
|
|
263
|
+
| "schedulePromptPostCommit"
|
|
248
264
|
> & {
|
|
249
265
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
250
266
|
objectStorage: ObjectStorageDependency;
|
|
@@ -57,6 +57,7 @@ import { hasPermission } from "../access";
|
|
|
57
57
|
import { isFikenConnection, preferredFikenConnection } from "./fiken";
|
|
58
58
|
import { listSkillLibraryEntries, type SkillLibraryEntry } from "@opengeni/runtime/skill-library";
|
|
59
59
|
import { listCapabilityPacks, listWorkspaceCapabilityPacks } from "./packs";
|
|
60
|
+
import { assertHostMcpAuthoritySourceAdmissionEnabled } from "./host-mcp-authority-source-admission";
|
|
60
61
|
|
|
61
62
|
const officialMcpRegistryUrl = "https://registry.modelcontextprotocol.io";
|
|
62
63
|
const firstPartyMcpServerIds = new Set(["opengeni", "files", "docs"]);
|
|
@@ -403,7 +404,7 @@ function normalizedMcpCredentialHeaders(
|
|
|
403
404
|
}
|
|
404
405
|
|
|
405
406
|
async function validateMcpCapabilityConnectionRef(
|
|
406
|
-
input: { db: Database; grant: AccessGrant; workspaceId: string },
|
|
407
|
+
input: { db: Database; grant: AccessGrant; workspaceId: string; settings: Settings },
|
|
407
408
|
item: CapabilityCatalogItem,
|
|
408
409
|
ref: McpServerConnectionRef,
|
|
409
410
|
): Promise<McpServerConnectionRef> {
|
|
@@ -426,6 +427,7 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
426
427
|
providerDomain: ref.providerDomain.trim(),
|
|
427
428
|
subjectScope,
|
|
428
429
|
...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
|
|
430
|
+
...(ref.authoritySource === "host" ? { authoritySource: "host" as const } : {}),
|
|
429
431
|
...(ref.provider ? { provider: ref.provider.trim() } : {}),
|
|
430
432
|
...(ref.kind ? { kind: ref.kind } : {}),
|
|
431
433
|
...(ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {}),
|
|
@@ -449,6 +451,10 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
449
451
|
"MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef",
|
|
450
452
|
});
|
|
451
453
|
}
|
|
454
|
+
if (normalized.authoritySource === "host") {
|
|
455
|
+
assertHostMcpAuthoritySourceAdmissionEnabled(input.settings, normalized);
|
|
456
|
+
return normalized;
|
|
457
|
+
}
|
|
452
458
|
|
|
453
459
|
let connection = normalized.connectionId
|
|
454
460
|
? await getConnectionMetadata(
|
|
@@ -1680,12 +1686,22 @@ function installationConnectionRef(
|
|
|
1680
1686
|
if (!ref || typeof ref !== "object") {
|
|
1681
1687
|
return null;
|
|
1682
1688
|
}
|
|
1683
|
-
const { connectionId, providerDomain, kind, subjectScope } = ref as Record<
|
|
1689
|
+
const { authoritySource, connectionId, providerDomain, kind, subjectScope } = ref as Record<
|
|
1690
|
+
string,
|
|
1691
|
+
unknown
|
|
1692
|
+
>;
|
|
1684
1693
|
if (typeof providerDomain !== "string" || typeof kind !== "string") {
|
|
1685
1694
|
return null;
|
|
1686
1695
|
}
|
|
1696
|
+
if (authoritySource === "host") {
|
|
1697
|
+
// The internal installation/runtime ref retains the exact host binding.
|
|
1698
|
+
// Public capability catalogs use the existing null representation for an
|
|
1699
|
+
// enabled capability without a native OpenGeni connection, so indefinitely
|
|
1700
|
+
// open old browser bundles cannot treat a host UUID as native OAuth state.
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1687
1703
|
if (subjectScope === "subject") {
|
|
1688
|
-
// Never project a personal connection UUID through workspace-visible
|
|
1704
|
+
// Never project a native personal connection UUID through workspace-visible
|
|
1689
1705
|
// capability configuration, including legacy rows that still contain one.
|
|
1690
1706
|
return { providerDomain, kind, subjectScope: "subject" };
|
|
1691
1707
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import type { McpServerConnectionRef } from "@opengeni/contracts";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Explicit host-owned MCP refs change which credential authority executes an
|
|
7
|
+
* opaque connection id. Admit new external refs only after the operator has
|
|
8
|
+
* completed the two-phase fleet rollout. Readers and inheritance remain
|
|
9
|
+
* tolerant regardless of this switch, and markerless legacy refs retain their
|
|
10
|
+
* bounded non-UUID compatibility path.
|
|
11
|
+
*/
|
|
12
|
+
export function assertHostMcpAuthoritySourceAdmissionEnabled(
|
|
13
|
+
settings: Pick<Settings, "hostMcpAuthoritySourceAdmissionEnabled">,
|
|
14
|
+
connectionRef: McpServerConnectionRef | null | undefined,
|
|
15
|
+
): void {
|
|
16
|
+
if (
|
|
17
|
+
connectionRef?.authoritySource === "host" &&
|
|
18
|
+
!settings.hostMcpAuthoritySourceAdmissionEnabled
|
|
19
|
+
) {
|
|
20
|
+
throw new HTTPException(422, {
|
|
21
|
+
message:
|
|
22
|
+
"new host-owned MCP connection refs are not admitted; upgrade the complete API/worker/web fleet, then set OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED=true",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/domain/insights.ts
CHANGED
|
@@ -305,6 +305,8 @@ export async function getWorkspaceInsights(
|
|
|
305
305
|
creditUsd: microsToUsd(row.pricedCostMicros),
|
|
306
306
|
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
307
307
|
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
308
|
+
equivalentCreditUsd: microsToUsd(row.equivalentCreditCostMicros),
|
|
309
|
+
equivalentCreditCostKnownCalls: row.equivalentCreditCostKnownCalls,
|
|
308
310
|
}))
|
|
309
311
|
.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
310
312
|
|
|
@@ -326,6 +328,22 @@ export async function getWorkspaceInsights(
|
|
|
326
328
|
(sum, row) => sum + row.estimatedProviderCostKnownCalls,
|
|
327
329
|
0,
|
|
328
330
|
);
|
|
331
|
+
const equivalentCreditCostMicros = modelRows.reduce(
|
|
332
|
+
(sum, row) => sum + row.equivalentCreditCostMicros,
|
|
333
|
+
0,
|
|
334
|
+
);
|
|
335
|
+
const priorEquivalentCreditCostMicros = priorModelRows.reduce(
|
|
336
|
+
(sum, row) => sum + row.equivalentCreditCostMicros,
|
|
337
|
+
0,
|
|
338
|
+
);
|
|
339
|
+
const equivalentCreditCostKnownCalls = modelRows.reduce(
|
|
340
|
+
(sum, row) => sum + row.equivalentCreditCostKnownCalls,
|
|
341
|
+
0,
|
|
342
|
+
);
|
|
343
|
+
const priorEquivalentCreditCostKnownCalls = priorModelRows.reduce(
|
|
344
|
+
(sum, row) => sum + row.equivalentCreditCostKnownCalls,
|
|
345
|
+
0,
|
|
346
|
+
);
|
|
329
347
|
const modelCalls = modelRows.reduce((sum, row) => sum + row.calls, 0);
|
|
330
348
|
const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
331
349
|
const priorTotalTokens = priorModelRows.reduce((sum, row) => sum + row.totalTokens, 0);
|
|
@@ -342,6 +360,8 @@ export async function getWorkspaceInsights(
|
|
|
342
360
|
costMicros: 0,
|
|
343
361
|
estimatedProviderCostMicros: 0,
|
|
344
362
|
estimatedProviderCostKnownCalls: 0,
|
|
363
|
+
equivalentCreditCostMicros: 0,
|
|
364
|
+
equivalentCreditCostKnownCalls: 0,
|
|
345
365
|
inputTokens: 0,
|
|
346
366
|
outputTokens: 0,
|
|
347
367
|
cachedTokens: 0,
|
|
@@ -361,6 +381,8 @@ export async function getWorkspaceInsights(
|
|
|
361
381
|
modelCostUsd: microsToUsd(modelCostMicros),
|
|
362
382
|
estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
|
|
363
383
|
estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
|
|
384
|
+
equivalentCreditUsd: microsToUsd(facts.equivalentCreditCostMicros),
|
|
385
|
+
equivalentCreditCostKnownCalls: facts.equivalentCreditCostKnownCalls,
|
|
364
386
|
warmSeconds: usageBuckets.get(bucket)?.warmSeconds ?? 0,
|
|
365
387
|
inputTokens: facts.inputTokens,
|
|
366
388
|
outputTokens: facts.outputTokens,
|
|
@@ -393,6 +415,8 @@ export async function getWorkspaceInsights(
|
|
|
393
415
|
creditUsd,
|
|
394
416
|
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
395
417
|
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
418
|
+
equivalentCreditUsd: microsToUsd(row.equivalentCreditCostMicros),
|
|
419
|
+
equivalentCreditCostKnownCalls: row.equivalentCreditCostKnownCalls,
|
|
396
420
|
tokens: row.totalTokens,
|
|
397
421
|
cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens),
|
|
398
422
|
pctOfCreditUsd:
|
|
@@ -414,6 +438,8 @@ export async function getWorkspaceInsights(
|
|
|
414
438
|
creditUsd: fact ? microsToUsd(fact.pricedCostMicros) : null,
|
|
415
439
|
estimatedProviderUsd: fact ? microsToUsd(fact.estimatedProviderCostMicros) : null,
|
|
416
440
|
estimatedProviderCostKnownCalls: fact ? fact.estimatedProviderCostKnownCalls : null,
|
|
441
|
+
equivalentCreditUsd: fact ? microsToUsd(fact.equivalentCreditCostMicros) : null,
|
|
442
|
+
equivalentCreditCostKnownCalls: fact ? fact.equivalentCreditCostKnownCalls : null,
|
|
417
443
|
tokens: fact ? fact.totalTokens : null,
|
|
418
444
|
cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.cacheInputTokens) : null,
|
|
419
445
|
billing: fact ? billingPathOf(fact.billingPath) : null,
|
|
@@ -489,6 +515,8 @@ export async function getWorkspaceInsights(
|
|
|
489
515
|
row.estimatedProviderCostMicros == null
|
|
490
516
|
? null
|
|
491
517
|
: microsToUsd(row.estimatedProviderCostMicros),
|
|
518
|
+
equivalentCreditUsd:
|
|
519
|
+
row.equivalentCreditCostMicros == null ? null : microsToUsd(row.equivalentCreditCostMicros),
|
|
492
520
|
pricingSource: pricingSourceOf(row.pricingSource),
|
|
493
521
|
})),
|
|
494
522
|
promptContributions,
|
|
@@ -522,6 +550,10 @@ export async function getWorkspaceInsights(
|
|
|
522
550
|
priorEstimatedProviderUsd: microsToUsd(priorEstimatedProviderCostMicros),
|
|
523
551
|
estimatedProviderCostKnownCalls,
|
|
524
552
|
priorEstimatedProviderCostKnownCalls,
|
|
553
|
+
equivalentCreditUsd: microsToUsd(equivalentCreditCostMicros),
|
|
554
|
+
priorEquivalentCreditUsd: microsToUsd(priorEquivalentCreditCostMicros),
|
|
555
|
+
equivalentCreditCostKnownCalls,
|
|
556
|
+
priorEquivalentCreditCostKnownCalls,
|
|
525
557
|
modelCalls,
|
|
526
558
|
priorInputTokens,
|
|
527
559
|
priorTotalTokens,
|
package/src/domain/packs.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
PR_REVIEW_AUTOMATION_ADAPTER_ID,
|
|
32
32
|
PR_REVIEW_AUTOMATION_TEMPLATE_ID,
|
|
33
33
|
} from "./pr-review";
|
|
34
|
+
import { OPENGENI_PRODUCT_INTEGRATION_PACK } from "./product-integration-pack";
|
|
34
35
|
|
|
35
36
|
export const MARKETING_SOCIAL_PACK_ID = "marketing-social-daily-analysis";
|
|
36
37
|
|
|
@@ -277,7 +278,11 @@ const openGeniPrReviewPack: CapabilityPack = {
|
|
|
277
278
|
},
|
|
278
279
|
};
|
|
279
280
|
|
|
280
|
-
const packs = [
|
|
281
|
+
const packs = [
|
|
282
|
+
marketingSocialPack,
|
|
283
|
+
openGeniPrReviewPack,
|
|
284
|
+
OPENGENI_PRODUCT_INTEGRATION_PACK,
|
|
285
|
+
] satisfies CapabilityPack[];
|
|
281
286
|
|
|
282
287
|
export function listCapabilityPacks(): CapabilityPack[] {
|
|
283
288
|
return packs;
|
|
@@ -355,6 +360,7 @@ export type InlinePackSkillInstall = {
|
|
|
355
360
|
sourcePath: string;
|
|
356
361
|
name: string;
|
|
357
362
|
description: string;
|
|
363
|
+
activationMode: "workspace_managed" | "session_selected";
|
|
358
364
|
contentSha256: string;
|
|
359
365
|
totalBytes: number;
|
|
360
366
|
files: Array<{ path: string; content: string; byteSize: number; contentSha256: string }>;
|
|
@@ -372,19 +378,22 @@ export function inlinePackSkillInstall(
|
|
|
372
378
|
});
|
|
373
379
|
}
|
|
374
380
|
const normalizedName = skill.name.toLowerCase();
|
|
381
|
+
const activationMode = skill.activationMode ?? "workspace_managed";
|
|
382
|
+
const activationIdentity = activationMode === "session_selected" ? "session-selected/" : "";
|
|
375
383
|
const encodedSkill = encodeURIComponent(normalizedName);
|
|
376
|
-
const sourceUrl = `https://opengeni.invalid/pack-inline-skills/${encodedSkill}/${artifact.contentSha256}`;
|
|
377
|
-
const capabilityId = `skill:pack-inline/${normalizedName}@${artifact.contentSha256}`;
|
|
384
|
+
const sourceUrl = `https://opengeni.invalid/pack-inline-skills/${activationIdentity}${encodedSkill}/${artifact.contentSha256}`;
|
|
385
|
+
const capabilityId = `skill:pack-inline/${activationIdentity}${normalizedName}@${artifact.contentSha256}`;
|
|
378
386
|
return {
|
|
379
387
|
componentKey: `inline-skill/${normalizedName}`,
|
|
380
388
|
capabilityId,
|
|
381
|
-
pluginKey: `pack-skill/${normalizedName}/${artifact.contentSha256}`,
|
|
389
|
+
pluginKey: `pack-skill/${activationIdentity}${normalizedName}/${artifact.contentSha256}`,
|
|
382
390
|
sourceUrl,
|
|
383
391
|
repositoryUrl: "https://opengeni.invalid/pack-inline-skills",
|
|
384
392
|
sourceCommit: artifact.contentSha256,
|
|
385
393
|
sourcePath: normalizedName,
|
|
386
394
|
name: artifact.name,
|
|
387
395
|
description: artifact.description,
|
|
396
|
+
activationMode,
|
|
388
397
|
contentSha256: artifact.contentSha256,
|
|
389
398
|
totalBytes: artifact.totalBytes,
|
|
390
399
|
files: artifact.files.map((file) => ({
|
|
@@ -405,21 +414,42 @@ export async function previewCapabilityPackInstallation(
|
|
|
405
414
|
const { workspaceId } = access;
|
|
406
415
|
const installation = await getPackInstallation(db, workspaceId, pack.id);
|
|
407
416
|
const inlineInstalls = pack.skills.map((skill) => inlinePackSkillInstall(pack, skill));
|
|
408
|
-
const [referencedComponents, inlineComponents] = await Promise.all([
|
|
409
|
-
resolvePackComponentReferences(db, workspaceId, pack.components),
|
|
410
|
-
resolvePackInlineSkillReferences(
|
|
411
|
-
db,
|
|
412
|
-
workspaceId,
|
|
413
|
-
inlineInstalls.map((inline) => ({
|
|
414
|
-
key: inline.componentKey,
|
|
415
|
-
capabilityId: inline.capabilityId,
|
|
416
|
-
name: inline.name,
|
|
417
|
-
contentSha256: inline.contentSha256,
|
|
418
|
-
})),
|
|
419
|
-
installation?.id,
|
|
420
|
-
),
|
|
421
|
-
]);
|
|
422
417
|
const manifestDigest = capabilityPackManifestDigest(pack);
|
|
418
|
+
const inlineRequirements = inlineInstalls.map((inline) => ({
|
|
419
|
+
key: inline.componentKey,
|
|
420
|
+
capabilityId: inline.capabilityId,
|
|
421
|
+
name: inline.name,
|
|
422
|
+
activationMode: inline.activationMode,
|
|
423
|
+
contentSha256: inline.contentSha256,
|
|
424
|
+
}));
|
|
425
|
+
const [referencedComponents, plannedInlineComponents, installedSessionSelectedComponents] =
|
|
426
|
+
await Promise.all([
|
|
427
|
+
resolvePackComponentReferences(db, workspaceId, pack.components),
|
|
428
|
+
resolvePackInlineSkillReferences(db, workspaceId, inlineRequirements, installation?.id),
|
|
429
|
+
installation?.status === "active" && installation.manifestDigest === manifestDigest
|
|
430
|
+
? resolvePackInlineSkillReferences(
|
|
431
|
+
db,
|
|
432
|
+
workspaceId,
|
|
433
|
+
inlineRequirements.filter(
|
|
434
|
+
(requirement) => requirement.activationMode === "session_selected",
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
: Promise.resolve([]),
|
|
438
|
+
]);
|
|
439
|
+
const installedSessionSelectedByKey = new Map(
|
|
440
|
+
installedSessionSelectedComponents.map((component) => [component.key, component]),
|
|
441
|
+
);
|
|
442
|
+
// Installation planning excludes the Pack's own current ownership so an
|
|
443
|
+
// update can replace old inline content. For launch affordances, preserve an
|
|
444
|
+
// independently resolved active facet-installation id on an exact installed
|
|
445
|
+
// manifest. A missing facet retains the future capability id and therefore
|
|
446
|
+
// cannot be mistaken for something a new session can select right now.
|
|
447
|
+
const inlineComponents = plannedInlineComponents.map((component) => {
|
|
448
|
+
const installed = installedSessionSelectedByKey.get(component.key);
|
|
449
|
+
return installed?.status === "ready" && installed.resolvedId
|
|
450
|
+
? { ...component, resolvedId: installed.resolvedId }
|
|
451
|
+
: component;
|
|
452
|
+
});
|
|
423
453
|
const components: PackComponentResolution[] = [...referencedComponents, ...inlineComponents];
|
|
424
454
|
const blockers = components
|
|
425
455
|
.filter((component) => component.required && component.status !== "ready")
|