@segmentstream-cloud/ontology 0.13.0 → 0.15.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 +65 -3
- package/dist/agent-chat.d.ts +75 -0
- package/dist/agent-chat.js +176 -0
- package/dist/analyze.d.ts +6 -2
- package/dist/analyze.js +29 -18
- package/dist/authoring.d.ts +6 -3
- package/dist/authoring.js +6 -3
- package/dist/index.d.ts +7 -2
- package/dist/index.js +5 -1
- package/dist/migrate/apply.js +2 -1
- package/dist/migrate/plan.js +21 -9
- package/dist/migrate/storage.d.ts +12 -0
- package/dist/migrate/storage.js +48 -0
- package/dist/names.d.ts +1 -1
- package/dist/names.js +1 -0
- package/dist/permissions.d.ts +11 -2
- package/dist/permissions.js +15 -3
- package/dist/replication/baseline.js +1 -1
- package/dist/replication/model.js +2 -2
- package/dist/replication/schema.js +1 -1
- package/dist/runtime/agents.d.ts +10 -0
- package/dist/runtime/agents.js +305 -0
- package/dist/runtime/generated-actions.d.ts +2 -1
- package/dist/runtime/generated-actions.js +18 -13
- package/dist/runtime/service-binding.js +2 -0
- package/dist/service/agent/channel.d.ts +16 -0
- package/dist/service/agent/channel.js +88 -0
- package/dist/service/agent/protocol.d.ts +61 -0
- package/dist/service/agent/protocol.js +85 -0
- package/dist/service/agent/server.d.ts +28 -0
- package/dist/service/agent/server.js +257 -0
- package/dist/service/aggregate-platform.d.ts +5 -0
- package/dist/service/aggregate-platform.js +1 -1
- package/dist/service/authentication.d.ts +16 -5
- package/dist/service/authentication.js +51 -6
- package/dist/service/config.d.ts +29 -2
- package/dist/service/config.js +52 -2
- package/dist/service/errors.d.ts +6 -0
- package/dist/service/errors.js +16 -0
- package/dist/service/handlers.d.ts +2 -1
- package/dist/service/handlers.js +2 -2
- package/dist/service/mcp/server.d.ts +6 -0
- package/dist/service/mcp/server.js +15 -10
- package/dist/service/operations.d.ts +6 -2
- package/dist/service/operations.js +13 -5
- package/dist/service/runtime-contract.d.ts +94 -0
- package/dist/service/runtime-identity.d.ts +59 -0
- package/dist/service/runtime-identity.js +102 -0
- package/dist/service/service.d.ts +2 -2
- package/dist/service/service.js +24 -6
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import type { Principal } from "../runtime.js";
|
|
|
6
6
|
import type { NeonAuthEnvironment } from "./config.js";
|
|
7
7
|
import { RequestId } from "./errors.js";
|
|
8
8
|
import { OntologyBackend, type SignInIdentity } from "./runtime-contract.js";
|
|
9
|
+
import { type RuntimeCredential, type RuntimeTokenVerifier } from "./runtime-identity.js";
|
|
9
10
|
declare const CurrentPrincipal_base: Context.ServiceClass<CurrentPrincipal, "@segmentstream-cloud/ontology/CurrentPrincipal", Principal>;
|
|
10
11
|
/** The principal the request runs as, provided by {@link Authentication}. */
|
|
11
12
|
export declare class CurrentPrincipal extends CurrentPrincipal_base {
|
|
@@ -23,9 +24,10 @@ declare const Authentication_base: HttpApiMiddleware.ServiceClass<Authentication
|
|
|
23
24
|
readonly bearer: HttpApiSecurity.Http;
|
|
24
25
|
}, CurrentPrincipal, readonly [import("effect/Schema").Top, import("effect/Schema").Top, import("effect/Schema").Top], RequestId>>;
|
|
25
26
|
/**
|
|
26
|
-
* Authenticates every ontology route except `status`: a Neon Auth token, a SegmentStream app token
|
|
27
|
-
*
|
|
28
|
-
* Failures answer the request directly, before its
|
|
27
|
+
* Authenticates every ontology route except `status`: a Neon Auth token, a SegmentStream app token,
|
|
28
|
+
* an API key or the deployment's runtime token acting as a member, all as `Authorization: Bearer …`;
|
|
29
|
+
* without one, the app's Neon Auth session cookie. Failures answer the request directly, before its
|
|
30
|
+
* input is decoded.
|
|
29
31
|
*/
|
|
30
32
|
export declare class Authentication extends Authentication_base {
|
|
31
33
|
}
|
|
@@ -115,6 +117,9 @@ type Authenticated = {
|
|
|
115
117
|
readonly status: "rejected";
|
|
116
118
|
} | {
|
|
117
119
|
readonly status: "not_invited";
|
|
120
|
+
} | {
|
|
121
|
+
readonly status: "forbidden";
|
|
122
|
+
readonly detail: string;
|
|
118
123
|
} | {
|
|
119
124
|
readonly status: "unavailable";
|
|
120
125
|
};
|
|
@@ -125,6 +130,11 @@ export interface CredentialOptions {
|
|
|
125
130
|
* an access token is rejected, as it was issued for the MCP server only.
|
|
126
131
|
*/
|
|
127
132
|
readonly oauthResource?: string;
|
|
133
|
+
/**
|
|
134
|
+
* The deployment's own runtime token may authenticate here, acting as the member it names. Without
|
|
135
|
+
* it a runtime token is just an invalid token.
|
|
136
|
+
*/
|
|
137
|
+
readonly runtime?: RuntimeCredential;
|
|
128
138
|
}
|
|
129
139
|
/** Resolves a bearer credential to a principal. */
|
|
130
140
|
export declare function authenticate(credential: Redacted.Redacted<string>, verifyToken: TokenVerifier, options?: CredentialOptions): Effect.Effect<Authenticated, never, OntologyBackend>;
|
|
@@ -147,7 +157,8 @@ export declare function withPrincipal<E, R>(credential: Redacted.Redacted<string
|
|
|
147
157
|
}): Effect.Effect<HttpServerResponse.HttpServerResponse, E, Exclude<R, CurrentPrincipal> | RequestId>;
|
|
148
158
|
/**
|
|
149
159
|
* The {@link Authentication} middleware, verifying tokens with `verifyToken`. With `exchangeSession`,
|
|
150
|
-
* a request without a bearer token may authenticate with the app's Neon Auth session cookie
|
|
160
|
+
* a request without a bearer token may authenticate with the app's Neon Auth session cookie; with
|
|
161
|
+
* `verifyRuntime`, the deployment's own runtime token may act as the member it names.
|
|
151
162
|
*/
|
|
152
|
-
export declare function authenticationLayer(verifyToken: TokenVerifier, exchangeSession?: SessionExchange): Layer.Layer<Authentication, never, OntologyBackend>;
|
|
163
|
+
export declare function authenticationLayer(verifyToken: TokenVerifier, exchangeSession?: SessionExchange, verifyRuntime?: RuntimeTokenVerifier): Layer.Layer<Authentication, never, OntologyBackend>;
|
|
153
164
|
export {};
|
|
@@ -3,9 +3,10 @@ import { HttpServerRequest } from "effect/unstable/http";
|
|
|
3
3
|
import { HttpApiMiddleware, HttpApiSecurity, OpenApi } from "effect/unstable/httpapi";
|
|
4
4
|
import { createRemoteJWKSet, decodeJwt, errors, jwtVerify, } from "jose";
|
|
5
5
|
import { API_KEY_PREFIX } from "../runtime/principals.js";
|
|
6
|
-
import { RequestId, crossOriginSession, internalError, notInvited, toServerResponse, unauthorized, unavailable, } from "./errors.js";
|
|
6
|
+
import { RequestId, crossOriginSession, forbidden, internalError, notInvited, toServerResponse, unauthorized, unavailable, } from "./errors.js";
|
|
7
7
|
import { ACCESS_TOKEN_PREFIX, principalOfAccessToken } from "./oauth/protocol.js";
|
|
8
8
|
import { OntologyBackend } from "./runtime-contract.js";
|
|
9
|
+
import { ACT_AS_HEADER, logRuntimeRejection, runtimeCredential, } from "./runtime-identity.js";
|
|
9
10
|
import { ErrorSchemas } from "./schemas.js";
|
|
10
11
|
const CLOCK_TOLERANCE_SECONDS = 5;
|
|
11
12
|
const JWKS_TIMEOUT_MS = 5_000;
|
|
@@ -13,9 +14,10 @@ const JWKS_TIMEOUT_MS = 5_000;
|
|
|
13
14
|
export class CurrentPrincipal extends Context.Service()("@segmentstream-cloud/ontology/CurrentPrincipal") {
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
|
-
* Authenticates every ontology route except `status`: a Neon Auth token, a SegmentStream app token
|
|
17
|
-
*
|
|
18
|
-
* Failures answer the request directly, before its
|
|
17
|
+
* Authenticates every ontology route except `status`: a Neon Auth token, a SegmentStream app token,
|
|
18
|
+
* an API key or the deployment's runtime token acting as a member, all as `Authorization: Bearer …`;
|
|
19
|
+
* without one, the app's Neon Auth session cookie. Failures answer the request directly, before its
|
|
20
|
+
* input is decoded.
|
|
19
21
|
*/
|
|
20
22
|
export class Authentication extends HttpApiMiddleware.Service()("@segmentstream-cloud/ontology/Authentication", {
|
|
21
23
|
security: {
|
|
@@ -274,6 +276,29 @@ function sessionMayAuthenticate(request) {
|
|
|
274
276
|
const origin = request.headers.origin;
|
|
275
277
|
return origin !== undefined && origin === requestOrigin(request);
|
|
276
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* The member a verified runtime caller acts for: the member `X-SegmentStream-Act-As` names, with the
|
|
281
|
+
* roles they hold now, while they are active and paired with the agent. Without the header, the
|
|
282
|
+
* runtime may call only the agent's own routes, which authenticate it themselves.
|
|
283
|
+
*/
|
|
284
|
+
function actingMember(actAs) {
|
|
285
|
+
return Effect.gen(function* () {
|
|
286
|
+
const backend = yield* OntologyBackend;
|
|
287
|
+
if (actAs === undefined) {
|
|
288
|
+
return {
|
|
289
|
+
status: "forbidden",
|
|
290
|
+
detail: `The app's agent names the member it acts for in ${ACT_AS_HEADER}.`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const member = backend.agents === undefined ? undefined : yield* backend.agents.actingMember(actAs);
|
|
294
|
+
return member === undefined
|
|
295
|
+
? {
|
|
296
|
+
status: "forbidden",
|
|
297
|
+
detail: "The member is inactive or has not paired with the app's agent.",
|
|
298
|
+
}
|
|
299
|
+
: { status: "authenticated", principal: member };
|
|
300
|
+
});
|
|
301
|
+
}
|
|
277
302
|
/** Resolves a bearer credential to a principal. */
|
|
278
303
|
export function authenticate(credential, verifyToken, options = {}) {
|
|
279
304
|
return Effect.gen(function* () {
|
|
@@ -296,6 +321,21 @@ export function authenticate(credential, verifyToken, options = {}) {
|
|
|
296
321
|
? { status: "rejected" }
|
|
297
322
|
: { status: "authenticated", principal: service };
|
|
298
323
|
}
|
|
324
|
+
if (options.runtime !== undefined) {
|
|
325
|
+
const { verify, actAs } = options.runtime;
|
|
326
|
+
const runtime = yield* Effect.promise(() => verify(token));
|
|
327
|
+
switch (runtime.status) {
|
|
328
|
+
case "valid":
|
|
329
|
+
return yield* actingMember(actAs);
|
|
330
|
+
case "invalid":
|
|
331
|
+
yield* logRuntimeRejection(runtime.reason);
|
|
332
|
+
return { status: "rejected" };
|
|
333
|
+
case "unavailable":
|
|
334
|
+
return { status: "unavailable" };
|
|
335
|
+
case "other":
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
299
339
|
const verification = yield* Effect.promise(() => verifyToken(token));
|
|
300
340
|
if (verification.status !== "valid") {
|
|
301
341
|
return verification.status === "invalid"
|
|
@@ -358,6 +398,8 @@ export function withPrincipal(credential, verifyToken, backend, effect, options
|
|
|
358
398
|
return toServerResponse(unauthorized(requestId, "The bearer token is invalid, expired or revoked.", options.challenge?.(false)));
|
|
359
399
|
case "not_invited":
|
|
360
400
|
return toServerResponse(notInvited(requestId));
|
|
401
|
+
case "forbidden":
|
|
402
|
+
return toServerResponse(forbidden(requestId, outcome.detail));
|
|
361
403
|
case "cross_origin":
|
|
362
404
|
return toServerResponse(crossOriginSession(requestId));
|
|
363
405
|
case "unavailable":
|
|
@@ -369,16 +411,19 @@ export function withPrincipal(credential, verifyToken, backend, effect, options
|
|
|
369
411
|
}
|
|
370
412
|
/**
|
|
371
413
|
* The {@link Authentication} middleware, verifying tokens with `verifyToken`. With `exchangeSession`,
|
|
372
|
-
* a request without a bearer token may authenticate with the app's Neon Auth session cookie
|
|
414
|
+
* a request without a bearer token may authenticate with the app's Neon Auth session cookie; with
|
|
415
|
+
* `verifyRuntime`, the deployment's own runtime token may act as the member it names.
|
|
373
416
|
*/
|
|
374
|
-
export function authenticationLayer(verifyToken, exchangeSession) {
|
|
417
|
+
export function authenticationLayer(verifyToken, exchangeSession, verifyRuntime) {
|
|
375
418
|
return Layer.effect(Authentication, Effect.gen(function* () {
|
|
376
419
|
const backend = yield* OntologyBackend;
|
|
377
420
|
return Authentication.of({
|
|
378
421
|
bearer: (httpEffect, { credential }) => Effect.gen(function* () {
|
|
379
422
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
423
|
+
const runtime = runtimeCredential(verifyRuntime, request);
|
|
380
424
|
return yield* withPrincipal(credential, verifyToken, backend, httpEffect, {
|
|
381
425
|
session: { request, exchange: exchangeSession },
|
|
426
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
382
427
|
});
|
|
383
428
|
}),
|
|
384
429
|
});
|
package/dist/service/config.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { BlobStorageBinding } from "../storage/provider.js";
|
|
|
3
3
|
import type { Principal } from "../runtime.js";
|
|
4
4
|
import { type ConnectionTransportFactory, type LocalConnectionContext } from "./connections.js";
|
|
5
5
|
import type { OntologyServiceRuntime } from "./runtime-contract.js";
|
|
6
|
+
import type { RuntimeIdentityConfig } from "./runtime-identity.js";
|
|
6
7
|
/** The environment of the workspace deployment's ontology service. */
|
|
7
8
|
export declare const ENVIRONMENT_VARIABLES: {
|
|
8
9
|
readonly databaseUrl: "SEGMENTSTREAM_ONTOLOGY_DATABASE_URL";
|
|
@@ -25,6 +26,22 @@ export declare const NEON_AUTH_ENVIRONMENT_VARIABLES: {
|
|
|
25
26
|
readonly baseUrl: "NEON_AUTH_BASE_URL";
|
|
26
27
|
readonly jwksUrl: "NEON_AUTH_JWKS_URL";
|
|
27
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* The deployment's own runtime callers, such as the app's agent: the issuer and the audience of the
|
|
31
|
+
* team's Vercel OIDC tokens, set together by the platform on deployments that have an agent. Without
|
|
32
|
+
* them a runtime token is an invalid token. Tokens must also name the project in Vercel's
|
|
33
|
+
* `VERCEL_PROJECT_ID`, as the agent runs in the same Vercel project as the ontology service.
|
|
34
|
+
*/
|
|
35
|
+
export declare const RUNTIME_IDENTITY_ENVIRONMENT_VARIABLES: {
|
|
36
|
+
readonly issuer: "SEGMENTSTREAM_RUNTIME_OIDC_ISSUER";
|
|
37
|
+
readonly audience: "SEGMENTSTREAM_RUNTIME_OIDC_AUDIENCE";
|
|
38
|
+
readonly projectId: "VERCEL_PROJECT_ID";
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The project's production domain, which Vercel sets: the agent of a deployment with runtime identity
|
|
42
|
+
* takes the messages of `agent_chat.send_message` there.
|
|
43
|
+
*/
|
|
44
|
+
export declare const AGENT_HOST_VARIABLE = "VERCEL_PROJECT_PRODUCTION_URL";
|
|
28
45
|
/** Where the workspace's Neon Auth issues and publishes the keys of its users' tokens. */
|
|
29
46
|
export interface NeonAuthEnvironment {
|
|
30
47
|
/** The Auth URL, such as `https://ep-….neon.tech/ontology/auth`. Its origin is the issuer. */
|
|
@@ -51,6 +68,16 @@ export interface OntologyEnvironment {
|
|
|
51
68
|
readonly appCredential?: Redacted.Redacted<string>;
|
|
52
69
|
/** Present once the workspace has Neon Auth: members then sign in with its tokens. */
|
|
53
70
|
readonly neonAuth?: NeonAuthEnvironment;
|
|
71
|
+
/**
|
|
72
|
+
* Present on deployments with a runtime caller: the deployment's own Vercel OIDC tokens then
|
|
73
|
+
* authenticate, acting as the member `X-SegmentStream-Act-As` names.
|
|
74
|
+
*/
|
|
75
|
+
readonly runtimeIdentity?: RuntimeIdentityConfig;
|
|
76
|
+
/**
|
|
77
|
+
* The agent's origin, `https://<VERCEL_PROJECT_PRODUCTION_URL>`, set with `runtimeIdentity`:
|
|
78
|
+
* `agent_chat.send_message` hands messages to the agent there.
|
|
79
|
+
*/
|
|
80
|
+
readonly agentUrl?: string;
|
|
54
81
|
/**
|
|
55
82
|
* The local grant `segmentstream exec --connection …` passed, in development. Without it, actions
|
|
56
83
|
* call connections through SegmentStream with the deployment's Vercel OIDC token.
|
|
@@ -99,8 +126,8 @@ export declare class OntologyServiceConfigError extends OntologyServiceConfigErr
|
|
|
99
126
|
/**
|
|
100
127
|
* Reads the service's configuration from the environment. It fails closed: a missing or invalid
|
|
101
128
|
* variable throws {@link OntologyServiceConfigError} naming every missing variable, and never the
|
|
102
|
-
* value of the database URL. The Neon Auth variables are optional, but only as
|
|
103
|
-
* credential is optional and kept redacted.
|
|
129
|
+
* value of the database URL. The Neon Auth and runtime identity variables are optional, but only as
|
|
130
|
+
* pairs. The app credential is optional and kept redacted.
|
|
104
131
|
*/
|
|
105
132
|
export declare function configFromEnv(environment?: Readonly<Record<string, string | undefined>>): OntologyEnvironment;
|
|
106
133
|
export {};
|
package/dist/service/config.js
CHANGED
|
@@ -23,6 +23,22 @@ export const NEON_AUTH_ENVIRONMENT_VARIABLES = {
|
|
|
23
23
|
baseUrl: "NEON_AUTH_BASE_URL",
|
|
24
24
|
jwksUrl: "NEON_AUTH_JWKS_URL",
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* The deployment's own runtime callers, such as the app's agent: the issuer and the audience of the
|
|
28
|
+
* team's Vercel OIDC tokens, set together by the platform on deployments that have an agent. Without
|
|
29
|
+
* them a runtime token is an invalid token. Tokens must also name the project in Vercel's
|
|
30
|
+
* `VERCEL_PROJECT_ID`, as the agent runs in the same Vercel project as the ontology service.
|
|
31
|
+
*/
|
|
32
|
+
export const RUNTIME_IDENTITY_ENVIRONMENT_VARIABLES = {
|
|
33
|
+
issuer: "SEGMENTSTREAM_RUNTIME_OIDC_ISSUER",
|
|
34
|
+
audience: "SEGMENTSTREAM_RUNTIME_OIDC_AUDIENCE",
|
|
35
|
+
projectId: "VERCEL_PROJECT_ID",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The project's production domain, which Vercel sets: the agent of a deployment with runtime identity
|
|
39
|
+
* takes the messages of `agent_chat.send_message` there.
|
|
40
|
+
*/
|
|
41
|
+
export const AGENT_HOST_VARIABLE = "VERCEL_PROJECT_PRODUCTION_URL";
|
|
26
42
|
/** The service cannot start: its configuration is incomplete or invalid. */
|
|
27
43
|
export class OntologyServiceConfigError extends Schema.TaggedError()("OntologyServiceConfigError", { message: Schema.String }) {
|
|
28
44
|
}
|
|
@@ -60,6 +76,35 @@ function neonAuthFromEnv(read) {
|
|
|
60
76
|
jwksUrl: httpUrl(NEON_AUTH_ENVIRONMENT_VARIABLES.jwksUrl, read(NEON_AUTH_ENVIRONMENT_VARIABLES.jwksUrl)).href,
|
|
61
77
|
};
|
|
62
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The runtime issuer and audience, both or neither, and the project the tokens must name. A partial
|
|
81
|
+
* configuration is a broken deployment, so the service refuses to start rather than run without the
|
|
82
|
+
* agent's identity, or trust tokens of every project of the team.
|
|
83
|
+
*/
|
|
84
|
+
function runtimeIdentityFromEnv(read) {
|
|
85
|
+
const { issuer, audience, projectId } = RUNTIME_IDENTITY_ENVIRONMENT_VARIABLES;
|
|
86
|
+
const pair = [issuer, audience];
|
|
87
|
+
const missingPair = pair.filter((name) => read(name) === "");
|
|
88
|
+
if (missingPair.length === pair.length) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (missingPair.length > 0) {
|
|
92
|
+
throw new OntologyServiceConfigError({
|
|
93
|
+
message: `The ontology service cannot start: ${pair.join(" and ")} are set together; ` +
|
|
94
|
+
`${missingPair.join(", ")} is not set.`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (read(projectId) === "") {
|
|
98
|
+
throw new OntologyServiceConfigError({
|
|
99
|
+
message: `The ontology service cannot start: ${projectId} is not set, which runtime tokens must name.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
issuer: httpUrl(issuer, read(issuer)).href.replace(/\/+$/, ""),
|
|
104
|
+
audience: read(audience),
|
|
105
|
+
projectId: read(projectId),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
63
108
|
function checkDatabaseUrl(name, value) {
|
|
64
109
|
// The value is a credential: errors name the variable, never the value.
|
|
65
110
|
if (!URL.canParse(value)) {
|
|
@@ -76,8 +121,8 @@ function checkDatabaseUrl(name, value) {
|
|
|
76
121
|
/**
|
|
77
122
|
* Reads the service's configuration from the environment. It fails closed: a missing or invalid
|
|
78
123
|
* variable throws {@link OntologyServiceConfigError} naming every missing variable, and never the
|
|
79
|
-
* value of the database URL. The Neon Auth variables are optional, but only as
|
|
80
|
-
* credential is optional and kept redacted.
|
|
124
|
+
* value of the database URL. The Neon Auth and runtime identity variables are optional, but only as
|
|
125
|
+
* pairs. The app credential is optional and kept redacted.
|
|
81
126
|
*/
|
|
82
127
|
export function configFromEnv(environment = process.env) {
|
|
83
128
|
const read = (name) => environment[name]?.trim() ?? "";
|
|
@@ -90,6 +135,7 @@ export function configFromEnv(environment = process.env) {
|
|
|
90
135
|
});
|
|
91
136
|
}
|
|
92
137
|
const neonAuth = neonAuthFromEnv(read);
|
|
138
|
+
const runtimeIdentity = runtimeIdentityFromEnv(read);
|
|
93
139
|
const localConnections = localConnectionsFromEnv(environment);
|
|
94
140
|
let blobStorage;
|
|
95
141
|
if (read("SEGMENTSTREAM_ONTOLOGY_STORAGE")) {
|
|
@@ -118,6 +164,10 @@ export function configFromEnv(environment = process.env) {
|
|
|
118
164
|
? { appCredential: Redacted.make(read(APP_CREDENTIAL_VARIABLE)) }
|
|
119
165
|
: {}),
|
|
120
166
|
...(neonAuth === undefined ? {} : { neonAuth }),
|
|
167
|
+
...(runtimeIdentity === undefined ? {} : { runtimeIdentity }),
|
|
168
|
+
...(runtimeIdentity === undefined || read(AGENT_HOST_VARIABLE) === ""
|
|
169
|
+
? {}
|
|
170
|
+
: { agentUrl: normalizeUrl(AGENT_HOST_VARIABLE, `https://${read(AGENT_HOST_VARIABLE)}`) }),
|
|
121
171
|
...(localConnections === undefined ? {} : { localConnections }),
|
|
122
172
|
};
|
|
123
173
|
}
|
package/dist/service/errors.d.ts
CHANGED
|
@@ -92,6 +92,12 @@ export declare function notInvited(requestId: string): ErrorResponse;
|
|
|
92
92
|
* The cookie rides along with any request to the app's domain, so only its origin can vouch for it.
|
|
93
93
|
*/
|
|
94
94
|
export declare function crossOriginSession(requestId: string): ErrorResponse;
|
|
95
|
+
/** The caller is authenticated but may not do this; the detail says why. */
|
|
96
|
+
export declare function forbidden(requestId: string, detail: string): ErrorResponse;
|
|
97
|
+
/** A client asked too often; it may ask again after `retryAfterSeconds`. */
|
|
98
|
+
export declare function rateLimited(requestId: string, detail: string, retryAfterSeconds: number): ErrorResponse;
|
|
99
|
+
/** The resource does not exist, or not for this caller; the detail says what to do. */
|
|
100
|
+
export declare function notFound(requestId: string, detail: string): ErrorResponse;
|
|
95
101
|
export declare function unavailable(requestId: string): ErrorResponse;
|
|
96
102
|
export declare function internalError(requestId: string): ErrorResponse;
|
|
97
103
|
export declare function routeNotFound(requestId: string): ErrorResponse;
|
package/dist/service/errors.js
CHANGED
|
@@ -129,6 +129,22 @@ export function crossOriginSession(requestId) {
|
|
|
129
129
|
},
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
|
+
/** The caller is authenticated but may not do this; the detail says why. */
|
|
133
|
+
export function forbidden(requestId, detail) {
|
|
134
|
+
return { status: 403, body: { error: "forbidden", detail, requestId } };
|
|
135
|
+
}
|
|
136
|
+
/** A client asked too often; it may ask again after `retryAfterSeconds`. */
|
|
137
|
+
export function rateLimited(requestId, detail, retryAfterSeconds) {
|
|
138
|
+
return {
|
|
139
|
+
status: 429,
|
|
140
|
+
body: { error: "rate_limited", detail, requestId },
|
|
141
|
+
headers: { "retry-after": String(retryAfterSeconds) },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** The resource does not exist, or not for this caller; the detail says what to do. */
|
|
145
|
+
export function notFound(requestId, detail) {
|
|
146
|
+
return { status: 404, body: { error: "not_found", detail, requestId } };
|
|
147
|
+
}
|
|
132
148
|
export function unavailable(requestId) {
|
|
133
149
|
return {
|
|
134
150
|
status: 503,
|
|
@@ -4,6 +4,7 @@ import type { Aggregates } from "./aggregate.js";
|
|
|
4
4
|
import { type BuiltApi } from "./api.js";
|
|
5
5
|
import type { AfterCommitHook } from "./config.js";
|
|
6
6
|
import type { ReplicationKick } from "./replication.js";
|
|
7
|
+
import type { AgentChannelFactory } from "./agent/channel.js";
|
|
7
8
|
import type { ConnectionTransportFactory } from "./connections.js";
|
|
8
9
|
import { type RateLimit } from "./rate-limit.js";
|
|
9
10
|
import { OntologyBackend } from "./runtime-contract.js";
|
|
@@ -13,4 +14,4 @@ import { OntologyBackend } from "./runtime-contract.js";
|
|
|
13
14
|
*/
|
|
14
15
|
export declare const SIGN_IN_CHECK_LIMIT: RateLimit;
|
|
15
16
|
/** The handlers of every group of the API; they require {@link OntologyBackend}. */
|
|
16
|
-
export declare function handlersLayer(built: BuiltApi, hooks: ReadonlyMap<string, AfterCommitHook>, connections: ConnectionTransportFactory, replicate: ReplicationKick, aggregates: Aggregates): Layer.Layer<HttpApiGroup.Service<"ontology", string>, never, OntologyBackend>;
|
|
17
|
+
export declare function handlersLayer(built: BuiltApi, hooks: ReadonlyMap<string, AfterCommitHook>, connections: ConnectionTransportFactory, replicate: ReplicationKick, aggregates: Aggregates, agentChannel: AgentChannelFactory | undefined): Layer.Layer<HttpApiGroup.Service<"ontology", string>, never, OntologyBackend>;
|
package/dist/service/handlers.js
CHANGED
|
@@ -216,8 +216,8 @@ function storageHandlers(built) {
|
|
|
216
216
|
}));
|
|
217
217
|
}
|
|
218
218
|
/** The handlers of every group of the API; they require {@link OntologyBackend}. */
|
|
219
|
-
export function handlersLayer(built, hooks, connections, replicate, aggregates) {
|
|
220
|
-
const operationsOf = (backend) => makeOperations(backend, built.plan, hooks, connections, replicate);
|
|
219
|
+
export function handlersLayer(built, hooks, connections, replicate, aggregates, agentChannel) {
|
|
220
|
+
const operationsOf = (backend) => makeOperations(backend, built.plan, hooks, connections, replicate, agentChannel);
|
|
221
221
|
const layers = [
|
|
222
222
|
serviceHandlers(built, operationsOf, aggregates.model),
|
|
223
223
|
...(built.plan.ontology.manifest.entities.some((entity) => entity.properties.some((property) => property.type === "blob"))
|
|
@@ -4,10 +4,12 @@ import type { Aggregates } from "../aggregate.js";
|
|
|
4
4
|
import type { BuiltApi } from "../api.js";
|
|
5
5
|
import { type TokenVerifier } from "../authentication.js";
|
|
6
6
|
import type { AfterCommitHook } from "../config.js";
|
|
7
|
+
import type { AgentChannelFactory } from "../agent/channel.js";
|
|
7
8
|
import type { ConnectionTransportFactory } from "../connections.js";
|
|
8
9
|
import { RequestId } from "../errors.js";
|
|
9
10
|
import type { ReplicationKick } from "../replication.js";
|
|
10
11
|
import { OntologyBackend } from "../runtime-contract.js";
|
|
12
|
+
import { type RuntimeTokenVerifier } from "../runtime-identity.js";
|
|
11
13
|
/** The URL path of the ontology's MCP server, outside `/api/ontology`: one per app. */
|
|
12
14
|
export declare const MCP_PATH = "/mcp";
|
|
13
15
|
/** The MCP revisions the server speaks, newest first. */
|
|
@@ -18,8 +20,12 @@ export interface McpServerOptions {
|
|
|
18
20
|
readonly connections: ConnectionTransportFactory;
|
|
19
21
|
readonly replicate: ReplicationKick;
|
|
20
22
|
readonly verifyToken: TokenVerifier;
|
|
23
|
+
/** The deployment's runtime tokens, acting as a member; `undefined` while that is disabled. */
|
|
24
|
+
readonly verifyRuntime?: RuntimeTokenVerifier | undefined;
|
|
21
25
|
/** The facade the aggregate routes run; the aggregate tool runs its model-level aggregate. */
|
|
22
26
|
readonly aggregates: Aggregates;
|
|
27
|
+
/** The workspace's agent, for `agent_chat.send_message`; `undefined` without one. */
|
|
28
|
+
readonly agentChannel?: AgentChannelFactory | undefined;
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
25
31
|
* The ontology's MCP server at `POST /mcp`: Streamable HTTP without sessions, so any instance can
|
|
@@ -12,6 +12,7 @@ import { publicOrigin } from "../oauth/protocol.js";
|
|
|
12
12
|
import { mcpChallenge, mcpResource } from "../oauth/server.js";
|
|
13
13
|
import { identityToken, makeOperations, toQuery } from "../operations.js";
|
|
14
14
|
import { OntologyBackend } from "../runtime-contract.js";
|
|
15
|
+
import { runtimeCredential } from "../runtime-identity.js";
|
|
15
16
|
import { ActionResponse, HistoryPage, HistoryParams, IdParam, Me, } from "../schemas.js";
|
|
16
17
|
import { mcpTools, visibleTools } from "./tools.js";
|
|
17
18
|
/** The URL path of the ontology's MCP server, outside `/api/ontology`: one per app. */
|
|
@@ -346,7 +347,7 @@ function crossOrigin(request) {
|
|
|
346
347
|
return !URL.canParse(origin) || new URL(origin).host !== new URL(publicOrigin(request)).host;
|
|
347
348
|
}
|
|
348
349
|
/** `POST /mcp`: one JSON-RPC message in, one JSON response out, nothing kept between requests. */
|
|
349
|
-
function handlePost(server, verifyToken, backend) {
|
|
350
|
+
function handlePost(server, verifyToken, verifyRuntime, backend) {
|
|
350
351
|
const credential = HttpApiBuilder.securityDecode(HttpApiSecurity.bearer);
|
|
351
352
|
return (request) => Effect.gen(function* () {
|
|
352
353
|
const requestId = yield* RequestId;
|
|
@@ -389,14 +390,18 @@ function handlePost(server, verifyToken, backend) {
|
|
|
389
390
|
}
|
|
390
391
|
});
|
|
391
392
|
const origin = publicOrigin(request);
|
|
393
|
+
const runtime = runtimeCredential(verifyRuntime, request);
|
|
392
394
|
// With the OAuth tables, members' access tokens work here, and a 401 tells the client where
|
|
393
|
-
// the member authorizes it.
|
|
394
|
-
return yield* withPrincipal(yield* Effect.provideService(credential, HttpServerRequest.HttpServerRequest, request), verifyToken, backend, answer,
|
|
395
|
-
? {}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
395
|
+
// the member authorizes it. The app's agent calls as the member it names.
|
|
396
|
+
return yield* withPrincipal(yield* Effect.provideService(credential, HttpServerRequest.HttpServerRequest, request), verifyToken, backend, answer, {
|
|
397
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
398
|
+
...(backend.oauth === undefined
|
|
399
|
+
? {}
|
|
400
|
+
: {
|
|
401
|
+
oauthResource: mcpResource(origin),
|
|
402
|
+
challenge: (missing) => mcpChallenge(origin, missing),
|
|
403
|
+
}),
|
|
404
|
+
});
|
|
400
405
|
});
|
|
401
406
|
}
|
|
402
407
|
/**
|
|
@@ -412,10 +417,10 @@ export function mcpLayer(options) {
|
|
|
412
417
|
const server = {
|
|
413
418
|
built: options.built,
|
|
414
419
|
tools,
|
|
415
|
-
operations: makeOperations(backend, options.built.plan, options.hooks, options.connections, options.replicate),
|
|
420
|
+
operations: makeOperations(backend, options.built.plan, options.hooks, options.connections, options.replicate, options.agentChannel),
|
|
416
421
|
aggregate: options.aggregates.model,
|
|
417
422
|
};
|
|
418
|
-
yield* router.add("POST", MCP_PATH, handlePost(server, options.verifyToken, backend));
|
|
423
|
+
yield* router.add("POST", MCP_PATH, handlePost(server, options.verifyToken, options.verifyRuntime, backend));
|
|
419
424
|
yield* router.add("GET", MCP_PATH, METHOD_NOT_ALLOWED);
|
|
420
425
|
yield* router.add("DELETE", MCP_PATH, METHOD_NOT_ALLOWED);
|
|
421
426
|
}));
|
|
@@ -5,6 +5,7 @@ import type { Query, QueryResult } from "../query.js";
|
|
|
5
5
|
import { CurrentPrincipal } from "./authentication.js";
|
|
6
6
|
import type { AfterCommitHook } from "./config.js";
|
|
7
7
|
import type { ReplicationKick } from "./replication.js";
|
|
8
|
+
import type { AgentChannelFactory } from "./agent/channel.js";
|
|
8
9
|
import { type ConnectionTransportFactory } from "./connections.js";
|
|
9
10
|
import { InvalidActionInput, RequestId } from "./errors.js";
|
|
10
11
|
import type { ApiPlan, PlannedAction, PlannedEntity, Traversal } from "./plan.js";
|
|
@@ -59,8 +60,11 @@ export interface Caller {
|
|
|
59
60
|
readonly permissions: ReadonlyArray<string>;
|
|
60
61
|
}
|
|
61
62
|
type Backend = OntologyBackend["Service"];
|
|
62
|
-
/**
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The operations of a planned ontology, bound to its backend, after-commit hooks, connections and,
|
|
65
|
+
* on deployments with one, the workspace's agent.
|
|
66
|
+
*/
|
|
67
|
+
export declare function makeOperations(backend: Backend, plan: ApiPlan, hooks: ReadonlyMap<string, AfterCommitHook>, connections: ConnectionTransportFactory, replicate: ReplicationKick, agentChannel?: AgentChannelFactory): {
|
|
64
68
|
/** A page of records. Requires `read` on the entity and on every entity the query reaches. */
|
|
65
69
|
find: (planned: PlannedEntity, query: Query) => Effect.Effect<QueryResult<Readonly<Record<string, unknown>>>, PermissionDenied | import("../index.js").InvalidQuery, CurrentPrincipal>;
|
|
66
70
|
/** One record. Requires `read` on the entity. */
|
|
@@ -6,6 +6,7 @@ import { PermissionDenied } from "../errors.js";
|
|
|
6
6
|
import { toCamelCase } from "../names.js";
|
|
7
7
|
import { missingPermission, permissionOf, READ_VERB } from "../permissions.js";
|
|
8
8
|
import { CurrentPrincipal } from "./authentication.js";
|
|
9
|
+
import { AgentChannel } from "../agent-chat.js";
|
|
9
10
|
import { OIDC_TOKEN_HEADER } from "./connections.js";
|
|
10
11
|
import { InvalidActionInput, RequestId } from "./errors.js";
|
|
11
12
|
/**
|
|
@@ -145,12 +146,12 @@ function traverseOf(backend, targets, planned, traversal, id, query) {
|
|
|
145
146
|
return page(yield* backend.find({ entity: traversal.target, query: { ...query, where } }));
|
|
146
147
|
});
|
|
147
148
|
}
|
|
148
|
-
function runActionOf(backend, ontology, hooks, connections, replicate) {
|
|
149
|
+
function runActionOf(backend, ontology, hooks, connections, replicate, agentChannel) {
|
|
149
150
|
return (planned, { name, generated }, call) => Effect.gen(function* () {
|
|
150
151
|
const principal = yield* CurrentPrincipal;
|
|
151
152
|
const requestId = yield* RequestId;
|
|
152
153
|
const hook = hooks.get(`${planned.name}.${name}`);
|
|
153
|
-
const
|
|
154
|
+
const run = backend.runAction({
|
|
154
155
|
entity: planned.name,
|
|
155
156
|
action: name,
|
|
156
157
|
input: call.input,
|
|
@@ -175,6 +176,10 @@ function runActionOf(backend, ontology, hooks, connections, replicate) {
|
|
|
175
176
|
}
|
|
176
177
|
: {}),
|
|
177
178
|
});
|
|
179
|
+
// Built-in actions reach the workspace's agent through it, when the deployment has one.
|
|
180
|
+
const outcome = yield* agentChannel === undefined
|
|
181
|
+
? run
|
|
182
|
+
: run.pipe(Effect.provideService(AgentChannel, agentChannel({ identityToken: call.identityToken, store: backend.agents })));
|
|
178
183
|
const { issues } = outcome.validation;
|
|
179
184
|
if (!outcome.validation.ok && !call.validateOnly) {
|
|
180
185
|
return yield* new InvalidActionInput({ issues });
|
|
@@ -208,8 +213,11 @@ function callerOf(backend) {
|
|
|
208
213
|
};
|
|
209
214
|
});
|
|
210
215
|
}
|
|
211
|
-
/**
|
|
212
|
-
|
|
216
|
+
/**
|
|
217
|
+
* The operations of a planned ontology, bound to its backend, after-commit hooks, connections and,
|
|
218
|
+
* on deployments with one, the workspace's agent.
|
|
219
|
+
*/
|
|
220
|
+
export function makeOperations(backend, plan, hooks, connections, replicate, agentChannel) {
|
|
213
221
|
const targets = relationshipTargets(plan);
|
|
214
222
|
return {
|
|
215
223
|
/** A page of records. Requires `read` on the entity and on every entity the query reaches. */
|
|
@@ -221,7 +229,7 @@ export function makeOperations(backend, plan, hooks, connections, replicate) {
|
|
|
221
229
|
/** The records a relationship reaches from one record, as a page of the target entity. */
|
|
222
230
|
traverse: (planned, traversal, id, query) => traverseOf(backend, targets, planned, traversal, id, query),
|
|
223
231
|
/** Runs an action as the caller, with the action's after-commit hook and connections. */
|
|
224
|
-
runAction: runActionOf(backend, plan.ontology, hooks, connections, replicate),
|
|
232
|
+
runAction: runActionOf(backend, plan.ontology, hooks, connections, replicate, agentChannel),
|
|
225
233
|
/** The caller's user, roles and permissions. It needs no permission. */
|
|
226
234
|
caller: callerOf(backend),
|
|
227
235
|
};
|
|
@@ -153,6 +153,98 @@ export interface OAuthStore {
|
|
|
153
153
|
/** The member a grant acts for, with the roles they hold now; undefined once they may not sign in. */
|
|
154
154
|
readonly memberPrincipal: (userId: string) => Effect.Effect<Principal | undefined>;
|
|
155
155
|
}
|
|
156
|
+
/** A channel account waiting, for ten minutes, for a member to claim it with its pairing code. */
|
|
157
|
+
export interface AgentPairing {
|
|
158
|
+
readonly id: string;
|
|
159
|
+
/** The channel, such as `telegram`. */
|
|
160
|
+
readonly provider: string;
|
|
161
|
+
/** The account's id on the channel. */
|
|
162
|
+
readonly externalId: string;
|
|
163
|
+
/** How the channel names the account, shown on the connect page. */
|
|
164
|
+
readonly display: string | null;
|
|
165
|
+
readonly expiresAt: Date;
|
|
166
|
+
}
|
|
167
|
+
/** A channel account paired with a member: the app's agent acts as the member for its messages. */
|
|
168
|
+
export interface AgentIdentity {
|
|
169
|
+
readonly id: string;
|
|
170
|
+
readonly provider: string;
|
|
171
|
+
readonly externalId: string;
|
|
172
|
+
readonly userId: string;
|
|
173
|
+
readonly display: string | null;
|
|
174
|
+
readonly createdAt: Date;
|
|
175
|
+
/** When the identity stopped working; null while it is live. */
|
|
176
|
+
readonly revokedAt: Date | null;
|
|
177
|
+
}
|
|
178
|
+
/** A member as the agent names them: their user's name and the roles they hold now. */
|
|
179
|
+
export interface AgentMember {
|
|
180
|
+
readonly name: string;
|
|
181
|
+
readonly principal: Principal;
|
|
182
|
+
}
|
|
183
|
+
/** A group chat the agent saw, as `POST /api/ontology/agent/chats/seen` answers. */
|
|
184
|
+
export interface SeenChat {
|
|
185
|
+
readonly id: string;
|
|
186
|
+
readonly title: string;
|
|
187
|
+
readonly purpose: string | null;
|
|
188
|
+
readonly whenToUse: string | null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Where the app's agent keeps pairing codes and the identities they create: the runtime's agent
|
|
192
|
+
* tables in the workspace database. Codes are stored only as SHA-256 hashes. Expiry is checked
|
|
193
|
+
* against the database's clock; failures are defects.
|
|
194
|
+
*/
|
|
195
|
+
export interface AgentStore {
|
|
196
|
+
/** Stores a pairing under its code's hash and forgets expired ones; false when the hash is taken. */
|
|
197
|
+
readonly savePairing: (pairing: AgentPairing & {
|
|
198
|
+
readonly codeHash: string;
|
|
199
|
+
}) => Effect.Effect<boolean>;
|
|
200
|
+
/** A pairing that has neither expired nor been approved. */
|
|
201
|
+
readonly findPairing: (codeHash: string) => Effect.Effect<AgentPairing | undefined>;
|
|
202
|
+
/**
|
|
203
|
+
* Consumes a live pairing, once, and pairs its account with the member. The account's previous
|
|
204
|
+
* identity and the member's previous identity on the same provider are revoked: each has one live
|
|
205
|
+
* identity per provider. `undefined` when the pairing expired or was already approved.
|
|
206
|
+
*/
|
|
207
|
+
readonly approvePairing: (input: {
|
|
208
|
+
readonly codeHash: string;
|
|
209
|
+
readonly userId: string;
|
|
210
|
+
readonly identityId: string;
|
|
211
|
+
}) => Effect.Effect<AgentIdentity | undefined>;
|
|
212
|
+
/** The account's live identity, else its latest revoked one, else `undefined`. */
|
|
213
|
+
readonly findIdentity: (input: {
|
|
214
|
+
readonly provider: string;
|
|
215
|
+
readonly externalId: string;
|
|
216
|
+
}) => Effect.Effect<AgentIdentity | undefined>;
|
|
217
|
+
/** The member's live identities, oldest first. */
|
|
218
|
+
readonly listIdentities: (userId: string) => Effect.Effect<ReadonlyArray<AgentIdentity>>;
|
|
219
|
+
/** Revokes one of the member's live identities; false when they have no such identity. */
|
|
220
|
+
readonly revokeIdentity: (input: {
|
|
221
|
+
readonly id: string;
|
|
222
|
+
readonly userId: string;
|
|
223
|
+
}) => Effect.Effect<boolean>;
|
|
224
|
+
/** An active member by user id; `undefined` for an unknown user, a service or an inactive member. */
|
|
225
|
+
readonly member: (userId: string) => Effect.Effect<AgentMember | undefined>;
|
|
226
|
+
/** The member a runtime caller acts for: active, and with a live identity. */
|
|
227
|
+
readonly actingMember: (userId: string) => Effect.Effect<Principal | undefined>;
|
|
228
|
+
/**
|
|
229
|
+
* Records a message the agent saw in a group chat: creates the `agent_chat` or keeps its title
|
|
230
|
+
* current, records when the chat last had a message (at most every 15 minutes, so that history
|
|
231
|
+
* keeps it without a row per message), links the writer to its members when they are an active
|
|
232
|
+
* member, and unlinks `leftMemberId`, a member who left, when linked. With `agentLeft`, the agent
|
|
233
|
+
* itself left the chat: every member is unlinked, and the chat stays. With `migratedFrom`, the
|
|
234
|
+
* chat had that id before it became a supergroup: its record moves to `externalId`, or, when both
|
|
235
|
+
* ids have one, the new record takes the old one's members, and its purpose and when to use where
|
|
236
|
+
* it has none. Concurrent calls for one chat leave one record.
|
|
237
|
+
*/
|
|
238
|
+
readonly seeChat: (input: {
|
|
239
|
+
readonly provider: string;
|
|
240
|
+
readonly externalId: string;
|
|
241
|
+
readonly title: string;
|
|
242
|
+
readonly memberId?: string;
|
|
243
|
+
readonly leftMemberId?: string;
|
|
244
|
+
readonly agentLeft?: true;
|
|
245
|
+
readonly migratedFrom?: string;
|
|
246
|
+
}) => Effect.Effect<SeenChat>;
|
|
247
|
+
}
|
|
156
248
|
declare const OntologyBackend_base: Context.ServiceClass<OntologyBackend, "@segmentstream-cloud/ontology/OntologyBackend", {
|
|
157
249
|
readonly blobs?: BlobRuntime;
|
|
158
250
|
readonly maintainBlobs?: Effect.Effect<{
|
|
@@ -192,6 +284,8 @@ declare const OntologyBackend_base: Context.ServiceClass<OntologyBackend, "@segm
|
|
|
192
284
|
readonly replicationEnabled?: Effect.Effect<boolean>;
|
|
193
285
|
/** The OAuth tables, for members who connect agents; without it, agents use API keys. */
|
|
194
286
|
readonly oauth?: OAuthStore;
|
|
287
|
+
/** The agent tables, which pair channel accounts with members for the app's own agent. */
|
|
288
|
+
readonly agents?: AgentStore;
|
|
195
289
|
}>;
|
|
196
290
|
/**
|
|
197
291
|
* The runtime as the HTTP service sees it, with its database and ontology already bound. Failures
|