@opengeni/core 0.28.1 → 1.0.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/README.md +1 -1
- package/dist/access/external-actor-authority.d.ts +47 -0
- package/dist/access/index.d.ts +182 -3
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/api-integration-servers.d.ts +20 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +24 -0
- package/dist/application/connect-authority.d.ts +13 -0
- package/dist/application/connect-operation.d.ts +31 -0
- package/dist/application/external-continuation.d.ts +15 -0
- package/dist/application/external-identity-lifecycle.d.ts +20 -0
- package/dist/application/external-link-work-admission.d.ts +12 -0
- package/dist/application/external-workspace-members.d.ts +34 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +39 -2
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +26 -13
- package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
- package/dist/application/session-tenancy.d.ts +19 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/billing/limits.d.ts +11 -0
- package/dist/canonical-human-identities.d.ts +7 -2
- package/dist/canonical-human-identities.js +17 -4
- package/dist/canonical-human-identities.js.map +1 -1
- package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
- package/dist/chunk-2MKMRGSY.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
- package/dist/chunk-GVO3N37G.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
- package/dist/chunk-LCQXNR62.js.map +1 -0
- package/dist/chunk-MM6N7CTO.js +387 -0
- package/dist/chunk-MM6N7CTO.js.map +1 -0
- package/dist/chunk-QO5GVFFO.js +235 -0
- package/dist/chunk-QO5GVFFO.js.map +1 -0
- package/dist/codex-model-availability.d.ts +14 -0
- package/dist/default-session-model.d.ts +139 -0
- package/dist/dependencies.d.ts +87 -4
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +58 -0
- package/dist/domain/capabilities.d.ts +22 -4
- package/dist/domain/company-profile-agent-admin.d.ts +25 -0
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +36 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +4 -3
- package/dist/domain/environments.d.ts +5 -9
- package/dist/domain/external-creation-attribution.d.ts +6 -0
- package/dist/domain/file-owner.d.ts +8 -0
- package/dist/domain/github-action-policies.d.ts +36 -0
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/github-skill-source.d.ts +5 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +28 -1
- package/dist/domain/knowledge-files.d.ts +20 -0
- package/dist/domain/knowledge-messages.d.ts +39 -0
- package/dist/domain/knowledge-messages.test.d.ts +1 -0
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -0
- package/dist/domain/knowledge-search.test.d.ts +1 -0
- package/dist/domain/knowledge.d.ts +9 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/personal-connection-delegations.d.ts +87 -4
- package/dist/domain/pr-review.d.ts +249 -0
- package/dist/domain/resources.d.ts +24 -3
- package/dist/domain/scheduled-task-access.d.ts +247 -0
- package/dist/domain/scheduled-tasks.d.ts +103 -6
- package/dist/domain/session-connection-accounts.d.ts +16 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +93 -4
- package/dist/domain/sessions.d.ts +275 -28
- package/dist/domain/skill-imports.d.ts +1 -0
- package/dist/domain/skill-search.d.ts +30 -0
- package/dist/domain/skills.d.ts +117 -0
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/workspace-members.d.ts +8 -1
- package/dist/editable-artifact-live/application.d.ts +12 -3
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +5 -2
- package/dist/editable-artifact-live/ticket.d.ts +2 -2
- package/dist/editable-artifact-live/types.d.ts +14 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -4
- package/dist/index.d.ts +49 -3
- package/dist/index.js +18812 -6833
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.d.ts +131 -0
- package/dist/managed-auth-session-sets.js +51 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/managed-session.d.ts +98 -11
- package/dist/model-catalog.d.ts +117 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +22 -5
- package/dist/rigs/provider-images.d.ts +1 -1
- package/dist/sandbox/fleet.d.ts +59 -10
- package/dist/sandbox/routing.d.ts +22 -2
- package/dist/sandbox/runtime-settings.d.ts +56 -0
- package/dist/session-authorization.d.ts +38 -1
- package/dist/site-session-origin.d.ts +9 -0
- package/dist/transcription.d.ts +12 -1
- package/dist/turn-surface.d.ts +18 -0
- package/dist/user-presence.d.ts +32 -0
- package/package.json +18 -11
- package/src/access/external-actor-authority.ts +94 -0
- package/src/access/index.ts +1386 -44
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +215 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +77 -0
- package/src/application/connect-authority.ts +77 -0
- package/src/application/connect-operation.ts +80 -0
- package/src/application/external-continuation.ts +129 -0
- package/src/application/external-identity-lifecycle.ts +48 -0
- package/src/application/external-link-work-admission.ts +90 -0
- package/src/application/external-workspace-members.ts +232 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +222 -29
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +430 -73
- package/src/application/session-mcp-credential-rotation.ts +217 -0
- package/src/application/session-tenancy.ts +445 -0
- package/src/application/user-resource-grants.ts +154 -0
- package/src/billing/limits.ts +103 -24
- package/src/canonical-human-identities.ts +22 -3
- package/src/codex-model-availability.ts +136 -0
- package/src/default-session-model.ts +656 -0
- package/src/dependencies.ts +97 -5
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +270 -0
- package/src/domain/capabilities.ts +244 -151
- package/src/domain/company-profile-agent-admin.ts +56 -0
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +371 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +61 -15
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +32 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +25 -45
- package/src/domain/external-creation-attribution.ts +22 -0
- package/src/domain/file-owner.ts +67 -0
- package/src/domain/github-action-policies.ts +169 -0
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/github-skill-source.ts +233 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +270 -169
- package/src/domain/knowledge-files.ts +46 -0
- package/src/domain/knowledge-messages.test.ts +91 -0
- package/src/domain/knowledge-messages.ts +106 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +561 -0
- package/src/domain/knowledge-search.ts +256 -0
- package/src/domain/knowledge.ts +133 -0
- package/src/domain/mcp-account-bindings.ts +171 -0
- package/src/domain/mcp-account-routes.ts +121 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/organization-membership-lifecycle.ts +10 -0
- package/src/domain/personal-connection-delegations.ts +758 -115
- package/src/domain/pr-review.ts +687 -0
- package/src/domain/resources.ts +182 -25
- package/src/domain/scheduled-task-access.ts +1052 -0
- package/src/domain/scheduled-tasks.ts +1952 -215
- package/src/domain/session-connection-accounts.ts +67 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +55 -0
- package/src/domain/session-tool-policy.ts +443 -19
- package/src/domain/sessions.ts +3417 -619
- package/src/domain/skill-imports.ts +137 -17
- package/src/domain/skill-search.ts +221 -0
- package/src/domain/skills.ts +104 -0
- package/src/domain/slack-bot.ts +128 -3
- package/src/domain/workspace-members.ts +34 -2
- package/src/editable-artifact-live/application.ts +29 -4
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +167 -13
- package/src/editable-artifact-live/ticket.ts +87 -6
- package/src/editable-artifact-live/types.ts +16 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +69 -4
- package/src/managed-auth-session-sets.ts +371 -0
- package/src/managed-session.ts +520 -2
- package/src/model-catalog.ts +873 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +179 -63
- package/src/rigs/provider-images.ts +2 -3
- package/src/sandbox/fleet.ts +471 -59
- package/src/sandbox/routing.ts +160 -33
- package/src/sandbox/runtime-settings.ts +281 -0
- package/src/session-authorization.ts +262 -78
- package/src/site-session-origin.ts +22 -0
- package/src/transcription.ts +13 -1
- package/src/turn-surface.ts +45 -0
- package/src/user-presence.ts +134 -0
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-IBOEYG6N.js +0 -34
- package/dist/chunk-IBOEYG6N.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/dist/domain/packs.d.ts +0 -55
- package/src/domain/durable-learning-slack-publication.ts +0 -202
- package/src/domain/packs.ts +0 -556
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { types } from "node:util";
|
|
3
|
+
import type { Settings } from "@opengeni/config";
|
|
4
|
+
import {
|
|
5
|
+
ModalNativeConfigurationCaptureRefV2,
|
|
6
|
+
ModalNativeOriginalScopeV2,
|
|
7
|
+
ModalNativeProofGrantRefV2,
|
|
8
|
+
} from "@opengeni/contracts/modal-native-proof-v2";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
// Private transport only. This module is not an ordinary access-token entry,
|
|
12
|
+
// native acquisition issuer, custody authorizer, public registrar or effect CAS.
|
|
13
|
+
// A successful MAC authenticates this host transport, NOT the named human,
|
|
14
|
+
// live origin, grant, configuration, provider response or physical settlement.
|
|
15
|
+
const PREFIX = "ogmnp2_";
|
|
16
|
+
const SUBKEY_DOMAIN = "opengeni:modal-native-worker-host:transport:subkey:v2";
|
|
17
|
+
const MESSAGE_DOMAIN = "opengeni:modal-native-worker-host:transport:message:v2\0";
|
|
18
|
+
const TRANSPORT_TTL_SECONDS = 60;
|
|
19
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
20
|
+
const MAX_ENVELOPE_BYTES = 16_384;
|
|
21
|
+
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
22
|
+
const nativeByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength")!.get!;
|
|
23
|
+
const nativeByteOffset = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset")!.get!;
|
|
24
|
+
const nativeBuffer = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer")!.get!;
|
|
25
|
+
const uuid = z.string().uuid();
|
|
26
|
+
const revision = z.number().int().positive().safe();
|
|
27
|
+
const base = {
|
|
28
|
+
protocol: z.literal("opengeni-modal-native-worker-host-proof"),
|
|
29
|
+
version: z.literal(2),
|
|
30
|
+
principalKind: z.literal("worker_host"),
|
|
31
|
+
purpose: z.literal("original-native-custody-proof"),
|
|
32
|
+
scope: ModalNativeOriginalScopeV2,
|
|
33
|
+
configurationCaptureRef: ModalNativeConfigurationCaptureRefV2,
|
|
34
|
+
proofGrantRef: ModalNativeProofGrantRefV2,
|
|
35
|
+
requestId: uuid,
|
|
36
|
+
};
|
|
37
|
+
const acquisition = z
|
|
38
|
+
.object({
|
|
39
|
+
claimId: uuid,
|
|
40
|
+
nonce: uuid,
|
|
41
|
+
claimRevision: revision,
|
|
42
|
+
recordRevision: revision,
|
|
43
|
+
captureId: uuid,
|
|
44
|
+
acquisitionId: uuid,
|
|
45
|
+
})
|
|
46
|
+
.strict();
|
|
47
|
+
const intent = z.discriminatedUnion("action", [
|
|
48
|
+
z.object({ ...base, action: z.literal("declare_preparation") }).strict(),
|
|
49
|
+
z
|
|
50
|
+
.object({ ...base, action: z.literal("claim_namespace_read"), recordRevision: revision })
|
|
51
|
+
.strict(),
|
|
52
|
+
z
|
|
53
|
+
.object({ ...base, action: z.literal("claim_original_command_read"), recordRevision: revision })
|
|
54
|
+
.strict(),
|
|
55
|
+
z.object({ ...base, action: z.literal("capture_namespace_binding"), acquisition }).strict(),
|
|
56
|
+
z.object({ ...base, action: z.literal("capture_original_page"), acquisition }).strict(),
|
|
57
|
+
z.object({ ...base, action: z.literal("reconcile_same_capture"), acquisition }).strict(),
|
|
58
|
+
z.object({ ...base, action: z.literal("settle_original_acquisition"), acquisition }).strict(),
|
|
59
|
+
]);
|
|
60
|
+
const timing = z
|
|
61
|
+
.object({
|
|
62
|
+
bodySha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
63
|
+
issuedAt: z.number().int().nonnegative().safe(),
|
|
64
|
+
expiresAt: z.number().int().positive().safe(),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
type Intent = z.infer<typeof intent>;
|
|
68
|
+
export type NativeWorkerHostTransportDescriptionV2 = Intent & z.infer<typeof timing>;
|
|
69
|
+
|
|
70
|
+
declare const transportBrand: unique symbol;
|
|
71
|
+
export type NativeWorkerHostTransport = Readonly<{ [transportBrand]: true }>;
|
|
72
|
+
declare const verifiedBrand: unique symbol;
|
|
73
|
+
export type VerifiedNativeWorkerHostTransport = Readonly<{ [verifiedBrand]: true }>;
|
|
74
|
+
const transports = new WeakMap<NativeWorkerHostTransport, Buffer>();
|
|
75
|
+
const verified = new WeakMap<
|
|
76
|
+
VerifiedNativeWorkerHostTransport,
|
|
77
|
+
{
|
|
78
|
+
transport: NativeWorkerHostTransport;
|
|
79
|
+
description: NativeWorkerHostTransportDescriptionV2;
|
|
80
|
+
}
|
|
81
|
+
>();
|
|
82
|
+
|
|
83
|
+
/** Inspect before schema parsing; getters/Proxies must not execute inside the
|
|
84
|
+
* signing boundary. This is JSON correlation, never an authority constructor. */
|
|
85
|
+
function ownJson(value: unknown, depth = 0): void {
|
|
86
|
+
if (depth > 12) throw new Error("Unsupported native host transport input");
|
|
87
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
88
|
+
if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0)) return;
|
|
89
|
+
if (typeof value !== "object" || types.isProxy(value))
|
|
90
|
+
throw new Error("Unsupported native host transport input");
|
|
91
|
+
const array = Array.isArray(value);
|
|
92
|
+
const prototype = Object.getPrototypeOf(value);
|
|
93
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
|
|
94
|
+
throw new Error("Unsupported native host transport input");
|
|
95
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
96
|
+
const keys = Reflect.ownKeys(descriptors).filter((key) => !(array && key === "length"));
|
|
97
|
+
if (array && keys.length !== (value as unknown[]).length)
|
|
98
|
+
throw new Error("Unsupported native host transport input");
|
|
99
|
+
for (const key of keys) {
|
|
100
|
+
if (typeof key !== "string" || (array && !/^(0|[1-9][0-9]*)$/u.test(key)))
|
|
101
|
+
throw new Error("Unsupported native host transport input");
|
|
102
|
+
const descriptor = descriptors[key]!;
|
|
103
|
+
if (!descriptor.enumerable || !("value" in descriptor))
|
|
104
|
+
throw new Error("Unsupported native host transport input");
|
|
105
|
+
ownJson(descriptor.value, depth + 1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function bodyHash(body: Uint8Array): string {
|
|
110
|
+
if (types.isProxy(body) || !types.isUint8Array(body))
|
|
111
|
+
throw new Error("Unsupported native host transport body");
|
|
112
|
+
// Native getters ignore caller-owned byteLength/buffer/offset overrides. Reject
|
|
113
|
+
// shared backing storage: its bytes could change concurrently during the copy.
|
|
114
|
+
const length = nativeByteLength.call(body) as number;
|
|
115
|
+
const backing = nativeBuffer.call(body) as ArrayBuffer;
|
|
116
|
+
if (length > MAX_BODY_BYTES || !types.isArrayBuffer(backing))
|
|
117
|
+
throw new Error("Unsupported native host transport body");
|
|
118
|
+
const snapshot = new Uint8Array(length);
|
|
119
|
+
try {
|
|
120
|
+
snapshot.set(new Uint8Array(backing, nativeByteOffset.call(body) as number, length));
|
|
121
|
+
return createHash("sha256").update(snapshot).digest("hex");
|
|
122
|
+
} catch {
|
|
123
|
+
throw new Error("Unsupported native host transport body");
|
|
124
|
+
} finally {
|
|
125
|
+
snapshot.fill(0);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function keyFor(transport: NativeWorkerHostTransport): Buffer {
|
|
130
|
+
const key = transports.get(transport);
|
|
131
|
+
if (!key) throw new Error("Native host transport is unavailable");
|
|
132
|
+
return key;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function mac(key: Buffer, encoded: string): Buffer {
|
|
136
|
+
return createHmac("sha256", key).update(MESSAGE_DOMAIN).update(PREFIX).update(encoded).digest();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Host startup only. Deliberately DOES NOT call resolveFirstPartyDelegationSecret:
|
|
140
|
+
* neither configured access-key substitution nor the public local/test fallback
|
|
141
|
+
* can select this new purpose root. Missing explicit deployment secret is OFF.
|
|
142
|
+
* No variable lookup, provider request, config mutation or persistence occurs. */
|
|
143
|
+
export function configuredNativeWorkerHostTransport(
|
|
144
|
+
settings: Pick<Settings, "delegationSecret">,
|
|
145
|
+
): NativeWorkerHostTransport | null {
|
|
146
|
+
if (!settings || typeof settings !== "object" || types.isProxy(settings)) return null;
|
|
147
|
+
const descriptor = Object.getOwnPropertyDescriptor(settings, "delegationSecret");
|
|
148
|
+
if (!descriptor || !("value" in descriptor) || typeof descriptor.value !== "string") return null;
|
|
149
|
+
const secret = descriptor.value.trim();
|
|
150
|
+
if (!secret) return null;
|
|
151
|
+
const transient = Buffer.from(secret, "utf8");
|
|
152
|
+
try {
|
|
153
|
+
const key = createHmac("sha256", transient).update(SUBKEY_DOMAIN).digest();
|
|
154
|
+
const handle = Object.freeze(Object.create(null)) as NativeWorkerHostTransport;
|
|
155
|
+
transports.set(handle, key);
|
|
156
|
+
return handle;
|
|
157
|
+
} finally {
|
|
158
|
+
transient.fill(0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Release only this transport's derived-key copy, not a config/grant/custody
|
|
163
|
+
* lifecycle or observation slot. Outstanding native I/O needs its own join. */
|
|
164
|
+
export function disposeNativeWorkerHostTransport(transport: NativeWorkerHostTransport): void {
|
|
165
|
+
const key = transports.get(transport);
|
|
166
|
+
if (key) key.fill(0);
|
|
167
|
+
transports.delete(transport);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Private worker/core transport serialization. The caller MUST first establish
|
|
171
|
+
* canonical original-host admission. This helper cannot establish that admission
|
|
172
|
+
* and is intentionally not exported from the core package's public entrypoints. */
|
|
173
|
+
export function signNativeWorkerHostTransport(
|
|
174
|
+
transport: NativeWorkerHostTransport,
|
|
175
|
+
input: Intent,
|
|
176
|
+
body: Uint8Array,
|
|
177
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
178
|
+
): string {
|
|
179
|
+
const key = keyFor(transport);
|
|
180
|
+
ownJson(input);
|
|
181
|
+
const parsed = intent.safeParse(input);
|
|
182
|
+
const clock = z.number().int().nonnegative().safe().safeParse(nowSeconds);
|
|
183
|
+
if (!parsed.success || !clock.success) throw new Error("Unsupported native host transport input");
|
|
184
|
+
const timed = timing.safeParse({
|
|
185
|
+
bodySha256: bodyHash(body),
|
|
186
|
+
issuedAt: clock.data,
|
|
187
|
+
expiresAt: clock.data + TRANSPORT_TTL_SECONDS,
|
|
188
|
+
});
|
|
189
|
+
if (!timed.success) throw new Error("Unsupported native host transport input");
|
|
190
|
+
const description: NativeWorkerHostTransportDescriptionV2 = {
|
|
191
|
+
...parsed.data,
|
|
192
|
+
...timed.data,
|
|
193
|
+
};
|
|
194
|
+
const encoded = Buffer.from(JSON.stringify(description), "utf8").toString("base64url");
|
|
195
|
+
if (PREFIX.length + encoded.length + 1 + 43 > MAX_ENVELOPE_BYTES)
|
|
196
|
+
throw new Error("Unsupported native host transport input");
|
|
197
|
+
return `${PREFIX}${encoded}.${mac(key, encoded).toString("base64url")}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Only host transport authenticity is verified. A later narrow authorizer must
|
|
201
|
+
* independently source-join original live/maintenance authority, configuration,
|
|
202
|
+
* grant, claim, acquisition, body fields/cursors and exact action-specific CAS. */
|
|
203
|
+
export function verifyNativeWorkerHostTransport(
|
|
204
|
+
transport: NativeWorkerHostTransport,
|
|
205
|
+
envelope: string,
|
|
206
|
+
body: Uint8Array,
|
|
207
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
208
|
+
): VerifiedNativeWorkerHostTransport | null {
|
|
209
|
+
const key = keyFor(transport);
|
|
210
|
+
if (
|
|
211
|
+
typeof envelope !== "string" ||
|
|
212
|
+
envelope.length > MAX_ENVELOPE_BYTES ||
|
|
213
|
+
!envelope.startsWith(PREFIX)
|
|
214
|
+
)
|
|
215
|
+
return null;
|
|
216
|
+
const pieces = envelope.slice(PREFIX.length).split(".");
|
|
217
|
+
if (pieces.length !== 2) return null;
|
|
218
|
+
const [encoded, signature] = pieces as [string, string];
|
|
219
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(encoded) || !/^[A-Za-z0-9_-]{43}$/u.test(signature)) return null;
|
|
220
|
+
const received = Buffer.from(signature, "base64url");
|
|
221
|
+
if (received.length !== 32 || received.toString("base64url") !== signature) return null;
|
|
222
|
+
if (!timingSafeEqual(received, mac(key, encoded))) return null;
|
|
223
|
+
try {
|
|
224
|
+
const decoded = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as Record<
|
|
225
|
+
string,
|
|
226
|
+
unknown
|
|
227
|
+
>;
|
|
228
|
+
const { bodySha256, issuedAt, expiresAt, ...rawIntent } = decoded;
|
|
229
|
+
const description = {
|
|
230
|
+
...intent.parse(rawIntent),
|
|
231
|
+
...timing.parse({ bodySha256, issuedAt, expiresAt }),
|
|
232
|
+
};
|
|
233
|
+
if (
|
|
234
|
+
!Number.isSafeInteger(nowSeconds) ||
|
|
235
|
+
nowSeconds < 0 ||
|
|
236
|
+
description.issuedAt > nowSeconds ||
|
|
237
|
+
description.expiresAt <= nowSeconds ||
|
|
238
|
+
description.expiresAt - description.issuedAt !== TRANSPORT_TTL_SECONDS ||
|
|
239
|
+
description.bodySha256 !== bodyHash(body) ||
|
|
240
|
+
Buffer.from(JSON.stringify(description), "utf8").toString("base64url") !== encoded
|
|
241
|
+
)
|
|
242
|
+
return null;
|
|
243
|
+
const proof = Object.freeze(Object.create(null)) as VerifiedNativeWorkerHostTransport;
|
|
244
|
+
verified.set(proof, { transport, description });
|
|
245
|
+
return proof;
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Request-local inspection of a previously verified MAC, NOT current admission.
|
|
252
|
+
* The narrow host authorizer must still recheck expiry and current authority/CAS.
|
|
253
|
+
* Copying a description, token or ref cannot mint grant or native-I/O authority. */
|
|
254
|
+
export function nativeWorkerHostTransportDescription(
|
|
255
|
+
transport: NativeWorkerHostTransport,
|
|
256
|
+
proof: VerifiedNativeWorkerHostTransport,
|
|
257
|
+
): NativeWorkerHostTransportDescriptionV2 {
|
|
258
|
+
keyFor(transport);
|
|
259
|
+
const record = verified.get(proof);
|
|
260
|
+
if (!record || record.transport !== transport)
|
|
261
|
+
throw new Error("Native host transport proof is unavailable");
|
|
262
|
+
return structuredClone(record.description);
|
|
263
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fileOwnerContextForAccess } from "../domain/file-owner";
|
|
2
|
+
import { withSessionRlsActorContext } from "@opengeni/db";
|
|
1
3
|
import {
|
|
2
4
|
NewSessionDraft,
|
|
3
5
|
SaveNewSessionDraftRequest,
|
|
@@ -11,9 +13,12 @@ import {
|
|
|
11
13
|
getSandbox,
|
|
12
14
|
getVariableSet,
|
|
13
15
|
NewSessionDraftAccessError,
|
|
16
|
+
newSessionDraftModelProvided,
|
|
17
|
+
newSessionDraftSelectedProjectChannelId,
|
|
14
18
|
newSessionDraftToolsProvided,
|
|
19
|
+
newSessionSelectionHistory,
|
|
15
20
|
publicNewSessionDraftOptions,
|
|
16
|
-
|
|
21
|
+
requireFileForSubject,
|
|
17
22
|
saveNewSessionDraftInTransaction,
|
|
18
23
|
withWorkspaceSubjectRls,
|
|
19
24
|
} from "@opengeni/db";
|
|
@@ -27,8 +32,14 @@ import {
|
|
|
27
32
|
validateGitHubRepositorySelection,
|
|
28
33
|
validateToolRefs,
|
|
29
34
|
} from "../domain/resources";
|
|
30
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
hasPermission,
|
|
37
|
+
hasVerifiedOwningUserAuthorization,
|
|
38
|
+
type AccessGrantAuthorization,
|
|
39
|
+
} from "../access";
|
|
31
40
|
import { assertConfiguredModel, assertWorkspaceModelPolicyAllows } from "../domain/sessions";
|
|
41
|
+
import { resolveDefaultSessionModel } from "../default-session-model";
|
|
42
|
+
import { canonicalizeConfiguredModelId, type Settings } from "@opengeni/config";
|
|
32
43
|
|
|
33
44
|
type NewSessionDraftDependencies = Pick<AppDependencies, "settings" | "db" | "objectStorage">;
|
|
34
45
|
|
|
@@ -40,6 +51,7 @@ function mapNewSessionDraft(
|
|
|
40
51
|
row: Awaited<ReturnType<typeof getNewSessionDraftInTransaction>>,
|
|
41
52
|
): NewSessionDraftValue | null {
|
|
42
53
|
if (!row) return null;
|
|
54
|
+
const selectedProjectChannelId = newSessionDraftSelectedProjectChannelId(row);
|
|
43
55
|
return NewSessionDraft.parse({
|
|
44
56
|
revision: row.revision,
|
|
45
57
|
text: row.text,
|
|
@@ -49,20 +61,81 @@ function mapNewSessionDraft(
|
|
|
49
61
|
model: row.model,
|
|
50
62
|
reasoningEffort: row.reasoningEffort,
|
|
51
63
|
latencyMode: row.latencyMode,
|
|
64
|
+
...(newSessionDraftModelProvided(row) !== undefined
|
|
65
|
+
? { modelProvided: newSessionDraftModelProvided(row) }
|
|
66
|
+
: {}),
|
|
67
|
+
...(selectedProjectChannelId !== undefined ? { selectedProjectChannelId } : {}),
|
|
52
68
|
options: publicNewSessionDraftOptions(row),
|
|
69
|
+
selectionHistory: newSessionSelectionHistory(row),
|
|
53
70
|
updatedAt: row.updatedAt.toISOString(),
|
|
54
71
|
});
|
|
55
72
|
}
|
|
56
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Whether a stored draft's model policy was the person's choice. A row written
|
|
76
|
+
* before the marker existed (or by an older client) counts as a choice unless
|
|
77
|
+
* it is exactly the deployment default policy (model, reasoning, standard
|
|
78
|
+
* speed), which is what an untouched composer used to save.
|
|
79
|
+
*/
|
|
80
|
+
export function draftModelProvided(
|
|
81
|
+
settings: Settings,
|
|
82
|
+
draft: Pick<NewSessionDraftValue, "model" | "reasoningEffort" | "latencyMode" | "modelProvided">,
|
|
83
|
+
): boolean {
|
|
84
|
+
if (draft.modelProvided !== undefined) return draft.modelProvided;
|
|
85
|
+
return !(
|
|
86
|
+
canonicalizeConfiguredModelId(settings, draft.model) ===
|
|
87
|
+
canonicalizeConfiguredModelId(settings, settings.openaiModel) &&
|
|
88
|
+
draft.reasoningEffort === settings.openaiReasoningEffort &&
|
|
89
|
+
draft.latencyMode === "standard"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The resolved new-chat default for this actor and workspace. */
|
|
94
|
+
async function actorDefaultModel(
|
|
95
|
+
deps: Pick<NewSessionDraftDependencies, "db" | "settings">,
|
|
96
|
+
grant: AccessGrant,
|
|
97
|
+
workspaceId: string,
|
|
98
|
+
) {
|
|
99
|
+
return await resolveDefaultSessionModel(deps.db, deps.settings, {
|
|
100
|
+
accountId: grant.accountId,
|
|
101
|
+
workspaceId,
|
|
102
|
+
subjectId: grant.subjectId,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type NewSessionDraftReadOptions = {
|
|
107
|
+
/**
|
|
108
|
+
* Project a draft that follows the default onto today's resolved default.
|
|
109
|
+
* Callers that only reuse a chosen model (Slack defaults) skip the
|
|
110
|
+
* resolution; session creation resolves the default itself.
|
|
111
|
+
*/
|
|
112
|
+
projectDefaultModel?: boolean;
|
|
113
|
+
};
|
|
114
|
+
|
|
57
115
|
async function hydrateNewSessionDraft(
|
|
58
116
|
deps: Pick<NewSessionDraftDependencies, "db" | "settings">,
|
|
59
117
|
grant: AccessGrant,
|
|
60
118
|
workspaceId: string,
|
|
61
119
|
row: Awaited<ReturnType<typeof getNewSessionDraftInTransaction>>,
|
|
120
|
+
readOptions: NewSessionDraftReadOptions = {},
|
|
62
121
|
): Promise<NewSessionDraftValue | null> {
|
|
63
122
|
if (!row) return null;
|
|
64
|
-
const
|
|
65
|
-
if (!
|
|
123
|
+
const stored = mapNewSessionDraft(row);
|
|
124
|
+
if (!stored) return null;
|
|
125
|
+
// A draft that follows the default is projected onto today's default, so a
|
|
126
|
+
// later subscription connect or credit purchase replaces an untouched free
|
|
127
|
+
// model. The stored row is unchanged until the person saves again.
|
|
128
|
+
const modelProvided = draftModelProvided(deps.settings, stored);
|
|
129
|
+
let mapped: NewSessionDraftValue = { ...stored, modelProvided };
|
|
130
|
+
if (!modelProvided && readOptions.projectDefaultModel !== false) {
|
|
131
|
+
const resolved = await actorDefaultModel(deps, grant, workspaceId);
|
|
132
|
+
mapped = {
|
|
133
|
+
...mapped,
|
|
134
|
+
model: resolved.model,
|
|
135
|
+
reasoningEffort: resolved.reasoningEffort,
|
|
136
|
+
latencyMode: resolved.model === stored.model ? stored.latencyMode : "standard",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
66
139
|
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
67
140
|
deps.db,
|
|
68
141
|
workspaceId,
|
|
@@ -88,7 +161,12 @@ async function hydrateNewSessionDraft(
|
|
|
88
161
|
continue;
|
|
89
162
|
}
|
|
90
163
|
try {
|
|
91
|
-
const file = await
|
|
164
|
+
const file = await requireFileForSubject(deps.db, {
|
|
165
|
+
accountId: grant.accountId,
|
|
166
|
+
workspaceId,
|
|
167
|
+
subjectId: grant.subjectId,
|
|
168
|
+
fileId: resource.fileId,
|
|
169
|
+
});
|
|
92
170
|
if (file.status === "ready") resources.push(resource);
|
|
93
171
|
} catch {
|
|
94
172
|
// Missing, foreign, failed, and pending files are stale draft state.
|
|
@@ -96,13 +174,32 @@ async function hydrateNewSessionDraft(
|
|
|
96
174
|
}
|
|
97
175
|
|
|
98
176
|
const options = { ...mapped.options };
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
177
|
+
const variableSetIds = options.variableSetIds ?? [];
|
|
178
|
+
if (variableSetIds.length > 0) {
|
|
179
|
+
const selectionsAuthorized =
|
|
180
|
+
hasPermission(grant.permissions, "variable-sets:attach") &&
|
|
181
|
+
hasPermission(grant.permissions, "variable-sets:use") &&
|
|
182
|
+
(
|
|
183
|
+
await Promise.all(
|
|
184
|
+
variableSetIds.map((variableSetId) =>
|
|
185
|
+
getVariableSet(
|
|
186
|
+
deps.db,
|
|
187
|
+
{ accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
|
|
188
|
+
variableSetId,
|
|
189
|
+
),
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
).every(Boolean);
|
|
193
|
+
if (selectionsAuthorized) {
|
|
194
|
+
options.variableSetIds = variableSetIds;
|
|
195
|
+
options.variableSetId = variableSetIds[variableSetIds.length - 1];
|
|
196
|
+
} else {
|
|
197
|
+
delete options.variableSetIds;
|
|
104
198
|
delete options.variableSetId;
|
|
105
199
|
}
|
|
200
|
+
} else {
|
|
201
|
+
delete options.variableSetIds;
|
|
202
|
+
delete options.variableSetId;
|
|
106
203
|
}
|
|
107
204
|
if (options.rigId) {
|
|
108
205
|
const rig = await getRig(deps.db, workspaceId, options.rigId);
|
|
@@ -151,10 +248,11 @@ async function hydrateNewSessionDraft(
|
|
|
151
248
|
}
|
|
152
249
|
|
|
153
250
|
/** Read the authenticated actor's server-authoritative pre-session composer state. */
|
|
154
|
-
|
|
251
|
+
async function getActorNewSessionDraftInFileScope(
|
|
155
252
|
deps: Pick<NewSessionDraftDependencies, "settings" | "db">,
|
|
156
253
|
grant: AccessGrant,
|
|
157
254
|
workspaceId: string,
|
|
255
|
+
readOptions: NewSessionDraftReadOptions = {},
|
|
158
256
|
): Promise<NewSessionDraftValue> {
|
|
159
257
|
const row = await withWorkspaceSubjectRls(deps.db, workspaceId, grant.subjectId, (scoped) =>
|
|
160
258
|
getNewSessionDraftInTransaction(scoped, {
|
|
@@ -162,20 +260,26 @@ export async function getActorNewSessionDraft(
|
|
|
162
260
|
subjectId: grant.subjectId,
|
|
163
261
|
}),
|
|
164
262
|
);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
263
|
+
const hydrated = await hydrateNewSessionDraft(deps, grant, workspaceId, row, readOptions);
|
|
264
|
+
if (hydrated) return hydrated;
|
|
265
|
+
const resolved =
|
|
266
|
+
readOptions.projectDefaultModel === false
|
|
267
|
+
? { model: deps.settings.openaiModel, reasoningEffort: deps.settings.openaiReasoningEffort }
|
|
268
|
+
: await actorDefaultModel(deps, grant, workspaceId);
|
|
269
|
+
return {
|
|
270
|
+
revision: 0,
|
|
271
|
+
text: "",
|
|
272
|
+
resources: [],
|
|
273
|
+
tools: [],
|
|
274
|
+
toolsProvided: false,
|
|
275
|
+
model: resolved.model,
|
|
276
|
+
reasoningEffort: resolved.reasoningEffort,
|
|
277
|
+
latencyMode: "standard",
|
|
278
|
+
modelProvided: false,
|
|
279
|
+
options: {},
|
|
280
|
+
selectionHistory: { projects: [] },
|
|
281
|
+
updatedAt: null,
|
|
282
|
+
};
|
|
179
283
|
}
|
|
180
284
|
|
|
181
285
|
/**
|
|
@@ -185,11 +289,19 @@ export async function getActorNewSessionDraft(
|
|
|
185
289
|
* draft may represent incomplete options, while no invalid option can become a
|
|
186
290
|
* session without passing that single canonical create boundary.
|
|
187
291
|
*/
|
|
188
|
-
|
|
292
|
+
async function saveActorNewSessionDraftInFileScope(
|
|
189
293
|
deps: NewSessionDraftDependencies,
|
|
190
294
|
grant: AccessGrant,
|
|
191
295
|
workspaceId: string,
|
|
192
296
|
rawInput: unknown,
|
|
297
|
+
/**
|
|
298
|
+
* `AccessGrantAuthorization.canonicalManagedHumanSession` — did this request
|
|
299
|
+
* authenticate as the canonical managed cookie that owns `grant.subjectId`?
|
|
300
|
+
* Defaults to false so every caller that has not proven it fails closed onto
|
|
301
|
+
* the historical bare-membership fence.
|
|
302
|
+
*/
|
|
303
|
+
canonicalManagedHumanSession = false,
|
|
304
|
+
externalAuthorization?: AccessGrantAuthorization,
|
|
193
305
|
): Promise<NewSessionDraftValue> {
|
|
194
306
|
const input = SaveNewSessionDraftRequest.parse(rawInput);
|
|
195
307
|
// The pre-marker client contract required `tools` and had no
|
|
@@ -207,9 +319,20 @@ export async function saveActorNewSessionDraft(
|
|
|
207
319
|
const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
|
|
208
320
|
await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
|
|
209
321
|
if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
|
|
210
|
-
throw new HTTPException(503, {
|
|
322
|
+
throw new HTTPException(503, {
|
|
323
|
+
message: "object storage is not configured",
|
|
324
|
+
});
|
|
211
325
|
}
|
|
212
|
-
await validateFileResources(
|
|
326
|
+
await validateFileResources(
|
|
327
|
+
deps.db,
|
|
328
|
+
grant.accountId,
|
|
329
|
+
workspaceId,
|
|
330
|
+
grant.subjectId,
|
|
331
|
+
resources,
|
|
332
|
+
externalAuthorization
|
|
333
|
+
? await fileOwnerContextForAccess(deps, externalAuthorization, "sessions:create")
|
|
334
|
+
: undefined,
|
|
335
|
+
);
|
|
213
336
|
assertConfiguredModel(deps.settings, input.model);
|
|
214
337
|
await assertWorkspaceModelPolicyAllows(deps.db, deps.settings, workspaceId, input.model);
|
|
215
338
|
|
|
@@ -228,16 +351,31 @@ export async function saveActorNewSessionDraft(
|
|
|
228
351
|
model: input.model,
|
|
229
352
|
reasoningEffort: input.reasoningEffort,
|
|
230
353
|
latencyMode: input.latencyMode,
|
|
354
|
+
...(input.modelProvided !== undefined ? { modelProvided: input.modelProvided } : {}),
|
|
355
|
+
...(input.selectedProjectChannelId !== undefined
|
|
356
|
+
? { selectedProjectChannelId: input.selectedProjectChannelId }
|
|
357
|
+
: {}),
|
|
231
358
|
options: input.options,
|
|
232
359
|
// Only managed people are removed through removeWorkspaceMember().
|
|
233
360
|
// API keys and delegated service actors (for example the first-party
|
|
234
361
|
// worker MCP principal) legitimately have no workspace_memberships
|
|
235
362
|
// row, so they must not be rejected by the human-removal fence.
|
|
236
363
|
requireWorkspaceMembership: grant.subjectId.startsWith("user:"),
|
|
364
|
+
// A managed human's own personal workspace has no membership row at
|
|
365
|
+
// all, so the human-removal fence above must fall back to the
|
|
366
|
+
// organization-membership pointer for them — and only for the
|
|
367
|
+
// verified owning-user authorization for this exact grant.
|
|
368
|
+
personalWorkspaceOwnerException:
|
|
369
|
+
canonicalManagedHumanSession ||
|
|
370
|
+
(externalAuthorization?.grant === grant &&
|
|
371
|
+
hasVerifiedOwningUserAuthorization(externalAuthorization)),
|
|
237
372
|
}),
|
|
238
373
|
),
|
|
239
374
|
);
|
|
240
|
-
|
|
375
|
+
// Report the same model-policy marker a read of this row reports, so the
|
|
376
|
+
// save response and the next GET agree for old and new clients alike.
|
|
377
|
+
const mapped = mapNewSessionDraft(saved)!;
|
|
378
|
+
return { ...mapped, modelProvided: draftModelProvided(deps.settings, mapped) };
|
|
241
379
|
} catch (error) {
|
|
242
380
|
if (error instanceof NewSessionDraftAccessError) {
|
|
243
381
|
throw new HTTPException(403, { message: error.message });
|
|
@@ -245,3 +383,58 @@ export async function saveActorNewSessionDraft(
|
|
|
245
383
|
throw error;
|
|
246
384
|
}
|
|
247
385
|
}
|
|
386
|
+
|
|
387
|
+
export async function getActorNewSessionDraft(
|
|
388
|
+
deps: Parameters<typeof getActorNewSessionDraftInFileScope>[0],
|
|
389
|
+
grant: AccessGrant,
|
|
390
|
+
workspaceId: string,
|
|
391
|
+
authorization?: AccessGrantAuthorization,
|
|
392
|
+
readOptions: NewSessionDraftReadOptions = {},
|
|
393
|
+
): Promise<NewSessionDraftValue> {
|
|
394
|
+
const actor = authorization
|
|
395
|
+
? await fileOwnerContextForAccess(deps, authorization, "sessions:read")
|
|
396
|
+
: { subjectId: grant.subjectId, privateFileOwnerSubjectId: null };
|
|
397
|
+
return withSessionRlsActorContext(actor, () =>
|
|
398
|
+
getActorNewSessionDraftInFileScope(deps, grant, workspaceId, readOptions),
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
export async function saveActorNewSessionDraft(
|
|
402
|
+
...args: Parameters<typeof saveActorNewSessionDraftInFileScope>
|
|
403
|
+
): Promise<NewSessionDraftValue> {
|
|
404
|
+
const [deps, grant, , , , authorization] = args;
|
|
405
|
+
const actor = authorization
|
|
406
|
+
? await fileOwnerContextForAccess(deps, authorization, "sessions:create")
|
|
407
|
+
: { subjectId: grant.subjectId, privateFileOwnerSubjectId: null };
|
|
408
|
+
return withSessionRlsActorContext(actor, () => saveActorNewSessionDraftInFileScope(...args));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* The model policy the person explicitly chose in the website composer, or an
|
|
413
|
+
* empty object when their draft follows the default.
|
|
414
|
+
*
|
|
415
|
+
* Only the model policy is reused. Connectors, repositories, Variable Sets,
|
|
416
|
+
* Sandbox Environment and compute target in the draft are the person's last
|
|
417
|
+
* explicit narrowing of one website chat, so surfaces that start work without
|
|
418
|
+
* the composer (Slack) resolve those from workspace defaults instead of
|
|
419
|
+
* silently inheriting that narrowing.
|
|
420
|
+
*/
|
|
421
|
+
export async function getActorNewSessionModelChoice(
|
|
422
|
+
deps: Parameters<typeof getActorNewSessionDraft>[0],
|
|
423
|
+
grant: AccessGrant,
|
|
424
|
+
workspaceId: string,
|
|
425
|
+
): Promise<
|
|
426
|
+
Pick<NewSessionDraftValue, "model" | "reasoningEffort" | "latencyMode"> | Record<string, never>
|
|
427
|
+
> {
|
|
428
|
+
// A draft that follows the default is not projected here; session creation
|
|
429
|
+
// resolves the default itself, once.
|
|
430
|
+
const draft = await getActorNewSessionDraft(deps, grant, workspaceId, undefined, {
|
|
431
|
+
projectDefaultModel: false,
|
|
432
|
+
});
|
|
433
|
+
return draft.revision > 0 && draft.modelProvided === true
|
|
434
|
+
? {
|
|
435
|
+
model: draft.model,
|
|
436
|
+
reasoningEffort: draft.reasoningEffort,
|
|
437
|
+
latencyMode: draft.latencyMode,
|
|
438
|
+
}
|
|
439
|
+
: {};
|
|
440
|
+
}
|