@segmentstream/app 0.0.0-stage → 0.38.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.
Files changed (125) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/README.md +1236 -2
  4. package/dist/agent.d.ts +56 -0
  5. package/dist/agent.js +203 -0
  6. package/dist/app.d.ts +82 -0
  7. package/dist/app.js +434 -0
  8. package/dist/browser-auth-capability.d.ts +2 -0
  9. package/dist/browser-auth-capability.js +2 -0
  10. package/dist/browser-revision.d.ts +19 -0
  11. package/dist/browser-revision.js +140 -0
  12. package/dist/channel-protocol.d.ts +16 -0
  13. package/dist/channel-protocol.js +17 -0
  14. package/dist/channel-session.d.ts +22 -0
  15. package/dist/channel-session.js +89 -0
  16. package/dist/config.d.ts +53 -0
  17. package/dist/config.js +112 -0
  18. package/dist/context.d.ts +10 -0
  19. package/dist/context.js +1 -0
  20. package/dist/cookies.d.ts +56 -0
  21. package/dist/cookies.js +131 -0
  22. package/dist/data.d.ts +233 -0
  23. package/dist/data.js +206 -0
  24. package/dist/decision-pages.d.ts +14 -0
  25. package/dist/decision-pages.js +87 -0
  26. package/dist/deployment.d.ts +30 -0
  27. package/dist/deployment.js +72 -0
  28. package/dist/errors.d.ts +60 -0
  29. package/dist/errors.js +123 -0
  30. package/dist/federation.d.ts +24 -0
  31. package/dist/federation.js +613 -0
  32. package/dist/groups.d.ts +95 -0
  33. package/dist/groups.js +280 -0
  34. package/dist/handlers.d.ts +10 -0
  35. package/dist/handlers.js +161 -0
  36. package/dist/index.d.ts +46 -0
  37. package/dist/index.js +29 -0
  38. package/dist/jobs.d.ts +49 -0
  39. package/dist/jobs.js +40 -0
  40. package/dist/legacy.d.ts +77 -0
  41. package/dist/legacy.js +57 -0
  42. package/dist/logo.d.ts +1 -0
  43. package/dist/logo.js +25 -0
  44. package/dist/me.d.ts +42 -0
  45. package/dist/me.js +54 -0
  46. package/dist/navigation/build.d.ts +68 -0
  47. package/dist/navigation/build.js +290 -0
  48. package/dist/navigation/cli.d.ts +19 -0
  49. package/dist/navigation/cli.js +37 -0
  50. package/dist/navigation/generate.d.ts +43 -0
  51. package/dist/navigation/generate.js +138 -0
  52. package/dist/navigation/next.d.ts +10 -0
  53. package/dist/navigation/next.js +18 -0
  54. package/dist/navigation-protocol.d.ts +55 -0
  55. package/dist/navigation-protocol.js +298 -0
  56. package/dist/neon.d.ts +101 -0
  57. package/dist/neon.js +539 -0
  58. package/dist/oauth-completion.d.ts +6 -0
  59. package/dist/oauth-completion.js +106 -0
  60. package/dist/oauth.d.ts +59 -0
  61. package/dist/oauth.js +129 -0
  62. package/dist/ontology-names.d.ts +10 -0
  63. package/dist/ontology-names.js +38 -0
  64. package/dist/ontology-transport.d.ts +34 -0
  65. package/dist/ontology-transport.js +130 -0
  66. package/dist/ontology-wire.d.ts +66 -0
  67. package/dist/ontology-wire.js +355 -0
  68. package/dist/ontology.d.ts +381 -0
  69. package/dist/ontology.js +367 -0
  70. package/dist/pages.d.ts +6 -0
  71. package/dist/pages.js +6 -0
  72. package/dist/parameters.d.ts +55 -0
  73. package/dist/parameters.js +118 -0
  74. package/dist/permissions.d.ts +19 -0
  75. package/dist/permissions.js +55 -0
  76. package/dist/pkce.d.ts +8 -0
  77. package/dist/pkce.js +40 -0
  78. package/dist/proxy.d.ts +25 -0
  79. package/dist/proxy.js +135 -0
  80. package/dist/request-timing.d.ts +6 -0
  81. package/dist/request-timing.js +53 -0
  82. package/dist/return-to.d.ts +2 -0
  83. package/dist/return-to.js +24 -0
  84. package/dist/server.d.ts +11 -0
  85. package/dist/server.js +80 -0
  86. package/dist/sessions.d.ts +27 -0
  87. package/dist/sessions.js +72 -0
  88. package/dist/sign-in.d.ts +37 -0
  89. package/dist/sign-in.js +95 -0
  90. package/dist/storage.d.ts +79 -0
  91. package/dist/storage.js +180 -0
  92. package/dist/telegram-launch.d.ts +7 -0
  93. package/dist/telegram-launch.js +134 -0
  94. package/dist/telegram.d.ts +2 -0
  95. package/dist/telegram.js +6 -0
  96. package/dist/tokens.d.ts +51 -0
  97. package/dist/tokens.js +70 -0
  98. package/dist/ui/agent-connect.d.ts +18 -0
  99. package/dist/ui/agent-connect.js +23 -0
  100. package/dist/ui/components.d.ts +27 -0
  101. package/dist/ui/components.js +71 -0
  102. package/dist/ui/hooks.d.ts +19 -0
  103. package/dist/ui/hooks.js +56 -0
  104. package/dist/ui/index.d.ts +22 -0
  105. package/dist/ui/index.js +15 -0
  106. package/dist/ui/navigation.d.ts +5 -0
  107. package/dist/ui/navigation.js +8 -0
  108. package/dist/ui/segmentstream-logo.d.ts +2 -0
  109. package/dist/ui/segmentstream-logo.js +7 -0
  110. package/dist/ui/sign-in-check.d.ts +14 -0
  111. package/dist/ui/sign-in-check.js +41 -0
  112. package/dist/ui/sign-in.d.ts +17 -0
  113. package/dist/ui/sign-in.js +65 -0
  114. package/dist/ui/store.d.ts +80 -0
  115. package/dist/ui/store.js +280 -0
  116. package/dist/ui/styles.css +249 -0
  117. package/dist/ui/telegram-sign-in.d.ts +16 -0
  118. package/dist/ui/telegram-sign-in.js +21 -0
  119. package/dist/ui/use-browser-sign-in.d.ts +52 -0
  120. package/dist/ui/use-browser-sign-in.js +169 -0
  121. package/dist/ui/use-channel-sign-in.d.ts +10 -0
  122. package/dist/ui/use-channel-sign-in.js +118 -0
  123. package/dist/ui/use-sign-in.d.ts +109 -0
  124. package/dist/ui/use-sign-in.js +304 -0
  125. package/package.json +95 -4
@@ -0,0 +1,56 @@
1
+ import type { OntologyTransport } from "./ontology.js";
2
+ import { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE } from "./pages.js";
3
+ export { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE };
4
+ /**
5
+ * A chat account asking to be linked to the signed-in member, as the connect page shows it. The
6
+ * workspace agent answers a sender it does not know with the link `/agent/connect?code=<code>`.
7
+ */
8
+ export interface AgentPairing {
9
+ /** The channel the sender wrote on, such as `telegram`. */
10
+ readonly provider: string;
11
+ /** How the channel names the sender, such as `@ada`; `null` when it gave no name. */
12
+ readonly display: string | null;
13
+ /** When the code stops working: ten minutes after the agent issued it. */
14
+ readonly expiresAt: string;
15
+ }
16
+ /** A chat account linked to a member: the agent acts as that member for its messages. */
17
+ export interface AgentIdentity {
18
+ readonly id: string;
19
+ readonly provider: string;
20
+ /** The account's id on the channel, such as a Telegram user id. */
21
+ readonly externalId: string;
22
+ /** How the channel named the account when it was linked; `null` when unknown. */
23
+ readonly display: string | null;
24
+ readonly createdAt: string;
25
+ /** When the member or an administrator revoked it; `null` while it is live. */
26
+ readonly revokedAt: string | null;
27
+ }
28
+ export interface AgentIdentities {
29
+ /**
30
+ * The pairing the connect page at `/agent/connect?code=<code>` shows, for the signed-in member.
31
+ * Throws `SegmentStreamApiError` with `not_found` or `expired` when the code is unknown, used or
32
+ * too old. Server-only.
33
+ */
34
+ readonly pairing: (code: string) => Promise<AgentPairing>;
35
+ /**
36
+ * Links the pairing's chat account to the signed-in member. The ontology decides: it refuses a
37
+ * used or expired code (`not_found`, `expired`), a member it does not admit (`forbidden`), or an
38
+ * account linked to another member (`identity_in_use`, HTTP 409).
39
+ * Server-only.
40
+ */
41
+ readonly approvePairing: (code: string) => Promise<AgentIdentity>;
42
+ /** The signed-in member's own linked chat accounts. Server-only. */
43
+ readonly identities: () => Promise<ReadonlyArray<AgentIdentity>>;
44
+ /** Revokes one of the signed-in member's linked chat accounts at once. Server-only. */
45
+ readonly revokeIdentity: (id: string) => Promise<void>;
46
+ /**
47
+ * The Route Handler the connect page's form posts to, at `app/src/app/api/agent/connect/route.ts`.
48
+ * It takes `code`, approves the pairing and sends the browser back to the connect page with
49
+ * `connected=<provider>`, or with `expired=1` when the code no longer works.
50
+ */
51
+ readonly connectHandler: () => {
52
+ readonly POST: (request: Request) => Promise<Response>;
53
+ };
54
+ }
55
+ /** Pairing and linked chat accounts, over the ontology service as the signed-in member. */
56
+ export declare function createAgentIdentities(transport: OntologyTransport): AgentIdentities;
package/dist/agent.js ADDED
@@ -0,0 +1,203 @@
1
+ import { NextResponse } from "next/server";
2
+ import { isSameOrigin } from "./decision-pages.js";
3
+ import { SegmentStreamApiError, apiErrorFrom } from "./errors.js";
4
+ import { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE } from "./pages.js";
5
+ export { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE };
6
+ /** The error codes of a code that no longer works: the connect page asks for a new link. */
7
+ const GONE_CODES = new Set(["not_found", "expired"]);
8
+ /** The SDK's error code for a refusal whose body names none. */
9
+ const STATUS_CODES = {
10
+ 403: "forbidden",
11
+ 404: "not_found",
12
+ 409: "identity_in_use",
13
+ 410: "expired",
14
+ };
15
+ function pairingPath(code, approve) {
16
+ const path = `/agent/pairings/${encodeURIComponent(code)}`;
17
+ return approve ? `${path}/approve` : path;
18
+ }
19
+ function identityPath(id) {
20
+ return id === undefined ? "/agent/identities" : `/agent/identities/${encodeURIComponent(id)}`;
21
+ }
22
+ /** The service's error, with the SDK's codes when its body names none. */
23
+ function agentErrorFrom(status, payload, detail) {
24
+ const error = apiErrorFrom(status, payload, detail);
25
+ if (!error.code.startsWith("http_")) {
26
+ return error;
27
+ }
28
+ const code = STATUS_CODES[status];
29
+ if (code === undefined) {
30
+ return error;
31
+ }
32
+ return new SegmentStreamApiError({
33
+ code,
34
+ status,
35
+ detail: error.detail,
36
+ requestId: error.requestId,
37
+ retryable: error.retryable,
38
+ });
39
+ }
40
+ function invalidResponse(detail) {
41
+ return new SegmentStreamApiError({
42
+ code: "ontology_invalid_response",
43
+ status: 502,
44
+ detail,
45
+ retryable: true,
46
+ });
47
+ }
48
+ function unknownCode() {
49
+ return new SegmentStreamApiError({
50
+ code: "not_found",
51
+ status: 404,
52
+ detail: "The link has no pairing code. Ask the agent for a new link.",
53
+ });
54
+ }
55
+ function fields(payload) {
56
+ return typeof payload === "object" && payload !== null && !Array.isArray(payload)
57
+ ? payload
58
+ : null;
59
+ }
60
+ function optionalString(value) {
61
+ return typeof value === "string" && value !== "" ? value : null;
62
+ }
63
+ /** Reads `{ provider, display, expires_at }` tolerantly: unknown fields are ignored. */
64
+ function parsePairing(payload) {
65
+ const body = fields(payload);
66
+ if (body === null || typeof body.provider !== "string" || typeof body.expires_at !== "string") {
67
+ return null;
68
+ }
69
+ return {
70
+ provider: body.provider,
71
+ display: optionalString(body.display),
72
+ expiresAt: body.expires_at,
73
+ };
74
+ }
75
+ /** Reads `{ id, provider, external_id, display?, created_at, revoked_at? }` tolerantly. */
76
+ function parseIdentity(payload) {
77
+ const body = fields(payload);
78
+ if (body === null ||
79
+ typeof body.id !== "string" ||
80
+ typeof body.provider !== "string" ||
81
+ typeof body.external_id !== "string" ||
82
+ typeof body.created_at !== "string") {
83
+ return null;
84
+ }
85
+ return {
86
+ id: body.id,
87
+ provider: body.provider,
88
+ externalId: body.external_id,
89
+ display: optionalString(body.display),
90
+ createdAt: body.created_at,
91
+ revokedAt: optionalString(body.revoked_at),
92
+ };
93
+ }
94
+ /** The identities in `{ identities: [...] }`, `{ items: [...] }` or a bare array. */
95
+ function identityList(payload) {
96
+ if (Array.isArray(payload)) {
97
+ return payload;
98
+ }
99
+ const body = fields(payload);
100
+ const list = body?.identities ?? body?.items;
101
+ return Array.isArray(list) ? list : null;
102
+ }
103
+ /** Pairing and linked chat accounts, over the ontology service as the signed-in member. */
104
+ export function createAgentIdentities(transport) {
105
+ async function send(path, method) {
106
+ const response = await transport.fetch(path, {
107
+ method,
108
+ headers: { accept: "application/json" },
109
+ });
110
+ const text = await response.text();
111
+ let payload = null;
112
+ try {
113
+ // An empty body, such as a 204 on revocation, carries no payload.
114
+ payload = text === "" ? null : JSON.parse(text);
115
+ }
116
+ catch (error) {
117
+ // The parse failure is the cause, so a malformed answer is told apart from a refusal.
118
+ const refused = agentErrorFrom(response.status, null, "The ontology service refused the request.");
119
+ throw new SegmentStreamApiError({
120
+ code: response.ok ? "ontology_invalid_response" : refused.code,
121
+ status: response.ok ? 502 : refused.status,
122
+ detail: response.ok
123
+ ? "The ontology service answered with a body that is not JSON."
124
+ : refused.detail,
125
+ retryable: response.ok ? true : refused.retryable,
126
+ cause: error,
127
+ });
128
+ }
129
+ if (!response.ok) {
130
+ throw agentErrorFrom(response.status, payload, "The ontology service refused the request.");
131
+ }
132
+ return payload;
133
+ }
134
+ async function approvePairing(code) {
135
+ if (code === "") {
136
+ throw unknownCode();
137
+ }
138
+ const payload = await send(pairingPath(code, true), "POST");
139
+ const identity = parseIdentity(fields(payload)?.identity);
140
+ if (identity === null) {
141
+ throw invalidResponse("The ontology service approved the pairing without an { identity }.");
142
+ }
143
+ return identity;
144
+ }
145
+ return {
146
+ async pairing(code) {
147
+ if (code === "") {
148
+ throw unknownCode();
149
+ }
150
+ const payload = await send(pairingPath(code, false), "GET");
151
+ const pairing = parsePairing(payload);
152
+ if (pairing === null) {
153
+ throw invalidResponse("The ontology service answered without { provider, expires_at }.");
154
+ }
155
+ return pairing;
156
+ },
157
+ approvePairing,
158
+ async identities() {
159
+ const payload = await send(identityPath(), "GET");
160
+ const list = identityList(payload);
161
+ if (list === null) {
162
+ throw invalidResponse("The ontology service answered without a list of identities.");
163
+ }
164
+ return list.flatMap((candidate) => {
165
+ const identity = parseIdentity(candidate);
166
+ return identity === null ? [] : [identity];
167
+ });
168
+ },
169
+ async revokeIdentity(id) {
170
+ if (id === "") {
171
+ throw new SegmentStreamApiError({
172
+ code: "not_found",
173
+ status: 404,
174
+ detail: "No identity to revoke.",
175
+ });
176
+ }
177
+ await send(identityPath(id), "DELETE");
178
+ },
179
+ connectHandler: () => ({
180
+ async POST(request) {
181
+ if (!isSameOrigin(request)) {
182
+ return NextResponse.json({ error: "forbidden", detail: "Connect a chat account from the app's own page." }, { status: 403 });
183
+ }
184
+ const form = await request.formData();
185
+ const code = String(form.get("code") ?? "");
186
+ let identity;
187
+ try {
188
+ identity = await approvePairing(code);
189
+ }
190
+ catch (error) {
191
+ if (error instanceof SegmentStreamApiError && GONE_CODES.has(error.code)) {
192
+ return NextResponse.redirect(new URL(`${AGENT_CONNECT_PAGE}?expired=1`, request.url), 303);
193
+ }
194
+ throw error;
195
+ }
196
+ const connected = new URL(AGENT_CONNECT_PAGE, request.url);
197
+ connected.searchParams.set("connected", identity.provider);
198
+ // 303: the browser loads the connect page with a GET, showing the result.
199
+ return NextResponse.redirect(connected, 303);
200
+ },
201
+ }),
202
+ };
203
+ }
package/dist/app.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { type DeploymentObservation } from "./deployment.js";
2
+ import type { Environment, SegmentStreamAppConfig } from "./config.js";
3
+ import type { AgentIdentities } from "./agent.js";
4
+ import type { WarehouseClient } from "./data.js";
5
+ import type { JobsClient } from "./jobs.js";
6
+ import type { SegmentStreamRouteHandlers } from "./handlers.js";
7
+ import type { AgentAuthorizations } from "./oauth.js";
8
+ import type { AuthRouteHandlers, CreateNeonAuth, SignOutOptions, UserOptions } from "./neon.js";
9
+ import type { OntologyClient } from "./ontology.js";
10
+ import type { ProxyOptions, SegmentStreamProxy } from "./proxy.js";
11
+ import type { Fetch } from "./sessions.js";
12
+ import type { AppUser, KeySet } from "./tokens.js";
13
+ /** Primary server integration; declared browser methods take precedence over legacy environment discovery. */
14
+ export type AuthenticationMethod = "neon-auth" | "segmentstream";
15
+ /**
16
+ * The app's SegmentStream integration. `O` is the type of the app's ontology,
17
+ * `typeof import("../../ontology").default`, which types {@link SegmentStreamApp.ontology}.
18
+ */
19
+ export interface SegmentStreamApp<O = unknown> {
20
+ readonly deployment: DeploymentObservation;
21
+ /** `neon-auth` when email is enabled and configured; otherwise `segmentstream`. */
22
+ readonly authentication: AuthenticationMethod;
23
+ /** The Next.js Proxy that keeps every non-public request signed in. */
24
+ readonly proxy: (options?: ProxyOptions) => SegmentStreamProxy;
25
+ /** Route Handlers for `app/src/app/api/auth/[...path]/route.ts`: the Neon Auth API. */
26
+ readonly authHandlers: () => AuthRouteHandlers;
27
+ /** Route Handlers for `app/api/segmentstream/[...path]/route.ts`: the legacy sign-in. */
28
+ readonly handlers: () => SegmentStreamRouteHandlers;
29
+ /**
30
+ * The signed-in user for this request, or `null`. Roles and permissions are cached per session
31
+ * for a minute; `{ fresh: true }` asks the ontology again.
32
+ */
33
+ readonly getUser: (options?: UserOptions) => Promise<AppUser | null>;
34
+ /** The signed-in user; redirects to sign-in when there is none. Takes `{ fresh }` as `getUser`. */
35
+ readonly requireUser: (options?: UserOptions) => Promise<AppUser>;
36
+ /**
37
+ * Whether the signed-in user's permissions cover `permission`, wildcards included. Takes
38
+ * `{ fresh }` as `getUser`.
39
+ */
40
+ readonly can: (permission: string, options?: UserOptions) => Promise<boolean>;
41
+ /**
42
+ * Ends the session and redirects to the sign-in page, or to `redirectTo`. Call it from a Server
43
+ * Function or Route Handler, which may change cookies.
44
+ */
45
+ readonly signOut: (options?: SignOutOptions) => Promise<never>;
46
+ /** The verified legacy app session token for this request, or `null`; always `null` under Neon Auth. */
47
+ readonly getAccessToken: () => Promise<string | null>;
48
+ /**
49
+ * Calls any SegmentStream API path as the signed-in member, with their organization role
50
+ * and permissions. Server-only; the session token never reaches the browser. Legacy sign-in only.
51
+ */
52
+ readonly fetch: (path: string, init?: RequestInit) => Promise<Response>;
53
+ /**
54
+ * Read-only SQL over the workspace warehouse. Server-only. Uses the workspace app credential
55
+ * (`SEGMENTSTREAM_APP_CREDENTIAL`) when it is set, else the signed-in member's session.
56
+ */
57
+ readonly warehouse: WarehouseClient;
58
+ /** Start and read pipeline runs with the workspace credential. Server-only; gate app users. */
59
+ readonly jobs: JobsClient;
60
+ /** The app's ontology service, `/api/ontology`, as the signed-in user. Server-only. */
61
+ readonly ontology: OntologyClient<O>;
62
+ /**
63
+ * Agents connecting to the app's MCP server: the request the consent page shows the signed-in
64
+ * member, and the Route Handler that carries out their decision. Server-only.
65
+ */
66
+ readonly oauth: AgentAuthorizations;
67
+ /**
68
+ * The workspace agent's chat accounts: the pairing the agent connect page shows the signed-in
69
+ * member, its approval, the member's linked accounts and their revocation, and the Route Handler
70
+ * the page's form posts to. Server-only.
71
+ */
72
+ readonly agent: AgentIdentities;
73
+ }
74
+ /** Replaceable capabilities, for tests. */
75
+ export interface AppDependencies {
76
+ readonly fetch: Fetch;
77
+ readonly keySet: KeySet;
78
+ readonly environment: Environment;
79
+ readonly createNeonAuth: CreateNeonAuth;
80
+ readonly now: () => number;
81
+ }
82
+ export declare function createApp<O = unknown>(config: SegmentStreamAppConfig, dependencies?: Partial<AppDependencies>): SegmentStreamApp<O>;