@segmentstream/app 0.0.0-stage → 0.38.1

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,95 @@
1
+ /** Channel-independent group policy. All names and aliases are supplied by the workspace. */
2
+ export interface SmartPolicyInput {
3
+ readonly model?: string;
4
+ readonly instructions?: string | {
5
+ readonly append?: string;
6
+ readonly replace?: string;
7
+ readonly file?: string;
8
+ };
9
+ readonly criteria?: {
10
+ readonly observe?: string;
11
+ readonly reply?: string;
12
+ };
13
+ readonly context?: {
14
+ readonly messages?: number;
15
+ readonly includeAgentDescription?: boolean;
16
+ /**
17
+ * @legacy agent.config-retired-keys
18
+ * @deprecated Accepted so that configurations written for agent 0.9 and 0.10 still deploy;
19
+ * it has no effect. The router no longer receives the agent's instructions.
20
+ */
21
+ readonly includeAgentInstructions?: boolean | {
22
+ readonly maxChars: number;
23
+ };
24
+ readonly includeChatGuidance?: boolean;
25
+ readonly includeParticipants?: boolean;
26
+ };
27
+ readonly engagement?: {
28
+ readonly policy?: string;
29
+ readonly agentAliases?: ReadonlyArray<string>;
30
+ readonly idleMinutes?: number;
31
+ readonly humanExchangeMinutes?: number;
32
+ readonly humanQuestionMinutes?: number;
33
+ readonly inferredMaxMinutes?: number;
34
+ readonly burst?: {
35
+ readonly quietSeconds?: number;
36
+ readonly maxSeconds?: number;
37
+ readonly gapSeconds?: number;
38
+ };
39
+ readonly followUpMinutes?: number;
40
+ readonly conversationMinutes?: number;
41
+ readonly openQuestionMinutes?: number | "unlimited";
42
+ readonly addresseeGate?: boolean;
43
+ };
44
+ readonly thresholds?: {
45
+ readonly continuation?: number;
46
+ readonly inferredContinuation?: number;
47
+ readonly contested?: number;
48
+ readonly ambient?: number;
49
+ readonly humanExchange?: number;
50
+ readonly engaged?: number;
51
+ readonly neutral?: number;
52
+ readonly humanThread?: number;
53
+ /**
54
+ * Conversation policy: the agent answers when the decision model gives at least this
55
+ * probability that it is addressed and that a reply is needed (0.5..1, default 0.5).
56
+ */
57
+ readonly reply?: number;
58
+ /**
59
+ * Conversation policy: a message below `reply` but at or above `defer` still reaches the
60
+ * agent, which answers or calls `no_reply`; so does a message the decision model could not
61
+ * classify. Defaults to `reply`, which leaves no such band.
62
+ */
63
+ readonly defer?: number;
64
+ };
65
+ readonly timeoutSeconds?: number;
66
+ }
67
+ export type Participants = Readonly<Record<string, {
68
+ readonly aliases: ReadonlyArray<string>;
69
+ }>>;
70
+ export interface ChatOverrideInput {
71
+ readonly mode?: string;
72
+ readonly smart?: Pick<SmartPolicyInput, "instructions" | "criteria" | "engagement" | "thresholds"> & {
73
+ readonly context?: Pick<NonNullable<SmartPolicyInput["context"]>, "messages" | "includeChatGuidance">;
74
+ };
75
+ readonly participants?: Participants;
76
+ }
77
+ export interface GroupPolicyInput {
78
+ readonly mode?: string;
79
+ readonly smart?: SmartPolicyInput;
80
+ /** Aliases of the people in every group chat; a chat's own `participants` add to them. */
81
+ readonly participants?: Participants;
82
+ readonly chats?: Readonly<Record<string, ChatOverrideInput>>;
83
+ readonly observability?: {
84
+ readonly storeRouterPayload?: boolean;
85
+ readonly shadow?: SmartPolicyInput;
86
+ };
87
+ }
88
+ export interface GroupPolicyIssue {
89
+ readonly message: string;
90
+ readonly path: ReadonlyArray<string>;
91
+ }
92
+ /** Strict validation of the `groups` section that `agent/segmentstream.json` authors. */
93
+ export declare function groupPolicyIssues(value: unknown): ReadonlyArray<GroupPolicyIssue>;
94
+ /** Expand authored files before bundling. The caller owns the workspace-relative filesystem boundary. */
95
+ export declare function expandGroupFiles(input: GroupPolicyInput, read: (path: string) => string): GroupPolicyInput;
package/dist/groups.js ADDED
@@ -0,0 +1,280 @@
1
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2
+ /** Strict validation of the `groups` section that `agent/segmentstream.json` authors. */
3
+ export function groupPolicyIssues(value) {
4
+ const issues = [];
5
+ const issue = (path, message) => issues.push({ path, message });
6
+ function section(value, path, keys) {
7
+ if (value === undefined) {
8
+ return {};
9
+ }
10
+ if (!object(value)) {
11
+ issue(path, "must be an object");
12
+ return {};
13
+ }
14
+ for (const key of Object.keys(value)) {
15
+ if (!keys.includes(key)) {
16
+ issue([...path, key], "is not a known setting");
17
+ }
18
+ }
19
+ return value;
20
+ }
21
+ function number(value, path, min, max, integer = false) {
22
+ if (value !== undefined &&
23
+ (typeof value !== "number" ||
24
+ !Number.isFinite(value) ||
25
+ value < min ||
26
+ value > max ||
27
+ (integer && !Number.isInteger(value)))) {
28
+ issue(path, `must be ${integer ? "an integer" : "a number"} between ${min} and ${max}`);
29
+ }
30
+ }
31
+ function bool(value, path) {
32
+ if (value !== undefined && typeof value !== "boolean") {
33
+ issue(path, "must be a boolean");
34
+ }
35
+ }
36
+ function text(value, path, max = 8000) {
37
+ if (value !== undefined && (typeof value !== "string" || value.length > max)) {
38
+ issue(path, `must be text of at most ${max} characters`);
39
+ }
40
+ }
41
+ function smart(value, path, restricted = false) {
42
+ const s = section(value, path, [
43
+ "instructions",
44
+ "criteria",
45
+ "context",
46
+ "engagement",
47
+ "thresholds",
48
+ ...(restricted ? [] : ["model", "timeoutSeconds"]),
49
+ ]);
50
+ if (s.model !== undefined &&
51
+ (typeof s.model !== "string" || !/^[^\s/]+\/[^\s]+$/.test(s.model))) {
52
+ issue([...path, "model"], "must be a Gateway provider/model id");
53
+ }
54
+ number(s.timeoutSeconds, [...path, "timeoutSeconds"], 1, 30);
55
+ if (typeof s.instructions === "string") {
56
+ text(s.instructions, [...path, "instructions"]);
57
+ }
58
+ else if (s.instructions !== undefined) {
59
+ const instructions = section(s.instructions, [...path, "instructions"], ["append", "replace", "file"]);
60
+ if (Object.keys(instructions).length !== 1) {
61
+ issue([...path, "instructions"], "must specify exactly one of append, replace or file");
62
+ }
63
+ for (const [key, value] of Object.entries(instructions)) {
64
+ text(value, [...path, "instructions", key]);
65
+ }
66
+ }
67
+ const criteria = section(s.criteria, [...path, "criteria"], ["observe", "reply"]);
68
+ text(criteria.observe, [...path, "criteria", "observe"]);
69
+ text(criteria.reply, [...path, "criteria", "reply"]);
70
+ const instructionLength = typeof s.instructions === "string"
71
+ ? s.instructions.length
72
+ : object(s.instructions)
73
+ ? String(s.instructions.append ?? s.instructions.replace ?? "").length
74
+ : 0;
75
+ if (instructionLength +
76
+ String(criteria.observe ?? "").length +
77
+ String(criteria.reply ?? "").length >
78
+ 8000) {
79
+ issue(path, "instructions and criteria must fit 8000 characters");
80
+ }
81
+ const c = section(s.context, [...path, "context"], [
82
+ "messages",
83
+ "includeChatGuidance",
84
+ ...(restricted
85
+ ? []
86
+ : ["includeAgentDescription", "includeAgentInstructions", "includeParticipants"]),
87
+ ]);
88
+ number(c.messages, [...path, "context", "messages"], 5, 60, true);
89
+ for (const key of ["includeAgentDescription", "includeChatGuidance", "includeParticipants"]) {
90
+ bool(c[key], [...path, "context", key]);
91
+ }
92
+ if (object(c.includeAgentInstructions)) {
93
+ const a = section(c.includeAgentInstructions, [...path, "context", "includeAgentInstructions"], ["maxChars"]);
94
+ number(a.maxChars, [...path, "context", "includeAgentInstructions", "maxChars"], 1, 6000, true);
95
+ if (a.maxChars === undefined) {
96
+ issue([...path, "context", "includeAgentInstructions"], "maxChars is required");
97
+ }
98
+ }
99
+ else {
100
+ bool(c.includeAgentInstructions, [...path, "context", "includeAgentInstructions"]);
101
+ }
102
+ const e = section(s.engagement, [...path, "engagement"], [
103
+ "policy",
104
+ "followUpMinutes",
105
+ "conversationMinutes",
106
+ "openQuestionMinutes",
107
+ "addresseeGate",
108
+ "agentAliases",
109
+ "idleMinutes",
110
+ "humanExchangeMinutes",
111
+ "humanQuestionMinutes",
112
+ "inferredMaxMinutes",
113
+ "burst",
114
+ ]);
115
+ if (e.policy !== undefined &&
116
+ e.policy !== "legacy" &&
117
+ e.policy !== "engaged" &&
118
+ e.policy !== "conversation") {
119
+ issue([...path, "engagement", "policy"], "must be legacy, engaged or conversation");
120
+ }
121
+ if (e.agentAliases !== undefined &&
122
+ (!Array.isArray(e.agentAliases) ||
123
+ e.agentAliases.length > 20 ||
124
+ e.agentAliases.some((a) => typeof a !== "string" || a.length < 1 || a.length > 128))) {
125
+ issue([...path, "engagement", "agentAliases"], "must contain at most 20 names of 1..128 characters");
126
+ }
127
+ for (const key of [
128
+ "idleMinutes",
129
+ "humanExchangeMinutes",
130
+ "humanQuestionMinutes",
131
+ "inferredMaxMinutes",
132
+ ]) {
133
+ number(e[key], [...path, "engagement", key], 1, 180);
134
+ }
135
+ const burst = section(e.burst, [...path, "engagement", "burst"], ["quietSeconds", "maxSeconds", "gapSeconds"]);
136
+ for (const key of ["quietSeconds", "maxSeconds", "gapSeconds"]) {
137
+ number(burst[key], [...path, "engagement", "burst", key], 1, 60);
138
+ }
139
+ if (burst.quietSeconds !== undefined &&
140
+ burst.maxSeconds !== undefined &&
141
+ Number(burst.quietSeconds) > Number(burst.maxSeconds)) {
142
+ issue([...path, "engagement", "burst"], "quietSeconds must not exceed maxSeconds");
143
+ }
144
+ for (const key of ["followUpMinutes", "conversationMinutes"]) {
145
+ number(e[key], [...path, "engagement", key], 1, 180);
146
+ }
147
+ if (e.openQuestionMinutes !== "unlimited") {
148
+ number(e.openQuestionMinutes, [...path, "engagement", "openQuestionMinutes"], 0, 1440);
149
+ }
150
+ bool(e.addresseeGate, [...path, "engagement", "addresseeGate"]);
151
+ const t = section(s.thresholds, [...path, "thresholds"], [
152
+ "engaged",
153
+ "neutral",
154
+ "humanThread",
155
+ "continuation",
156
+ "inferredContinuation",
157
+ "contested",
158
+ "ambient",
159
+ "humanExchange",
160
+ "reply",
161
+ "defer",
162
+ ]);
163
+ for (const [key, value] of Object.entries(t)) {
164
+ number(value, [...path, "thresholds", key], key === "reply" ? 0.5 : 0, 1);
165
+ }
166
+ // The top level's policy defaults to legacy; a chat or shadow policy may inherit conversation.
167
+ if ((t.reply !== undefined || t.defer !== undefined) &&
168
+ (path.length === 1
169
+ ? e.policy !== "conversation"
170
+ : e.policy !== undefined && e.policy !== "conversation")) {
171
+ issue([...path, "thresholds"], "reply and defer apply only to engagement.policy conversation");
172
+ }
173
+ // A chat or shadow policy may inherit `reply`; the agent checks each chat after inheritance.
174
+ if (path.length === 1
175
+ ? Number(t.defer ?? t.reply ?? 0.5) > Number(t.reply ?? 0.5)
176
+ : t.defer !== undefined && t.reply !== undefined && Number(t.defer) > Number(t.reply)) {
177
+ issue([...path, "thresholds"], "must satisfy defer <= reply");
178
+ }
179
+ const defaults = e.policy === "conversation"
180
+ ? [0.5, 0.9, 0.95]
181
+ : e.policy === "engaged"
182
+ ? [0.5, 0.75, 0.9]
183
+ : [0.5, 0.5, 0.5];
184
+ if ((!restricted &&
185
+ (Number(t.engaged ?? defaults[0]) > Number(t.neutral ?? defaults[1]) ||
186
+ Number(t.neutral ?? defaults[1]) > Number(t.humanThread ?? defaults[2]))) ||
187
+ (restricted &&
188
+ ((t.engaged !== undefined &&
189
+ t.neutral !== undefined &&
190
+ Number(t.engaged) > Number(t.neutral)) ||
191
+ (t.neutral !== undefined &&
192
+ t.humanThread !== undefined &&
193
+ Number(t.neutral) > Number(t.humanThread))))) {
194
+ issue([...path, "thresholds"], "must satisfy engaged <= neutral <= humanThread");
195
+ }
196
+ }
197
+ function override(value, path, isChat) {
198
+ const v = section(value, path, isChat
199
+ ? ["mode", "smart", "participants"]
200
+ : ["mode", "smart", "participants", "chats", "observability"]);
201
+ if (v.mode !== undefined && v.mode !== "smart" && v.mode !== "mention") {
202
+ issue([...path, "mode"], "must be smart or mention");
203
+ }
204
+ smart(v.smart, [...path, "smart"], isChat);
205
+ if (v.participants !== undefined) {
206
+ if (!object(v.participants)) {
207
+ issue([...path, "participants"], "must be an object");
208
+ }
209
+ else {
210
+ for (const [id, participant] of Object.entries(v.participants)) {
211
+ const p = section(participant, [...path, "participants", id], ["aliases"]);
212
+ if (!Array.isArray(p.aliases) ||
213
+ p.aliases.length > 10 ||
214
+ p.aliases.some((a) => typeof a !== "string" || a.length < 1 || a.length > 32 || a.includes("@"))) {
215
+ issue([...path, "participants", id, "aliases"], "must contain at most 10 names of 1..32 characters without @");
216
+ }
217
+ }
218
+ }
219
+ }
220
+ if (!isChat) {
221
+ if (v.chats !== undefined) {
222
+ if (!object(v.chats)) {
223
+ issue([...path, "chats"], "must be an object");
224
+ }
225
+ else {
226
+ for (const [key, entry] of Object.entries(v.chats)) {
227
+ if (!/^[a-z][a-z0-9_-]{0,31}:[^/\s]+(?:\/[^\s]+)?$/.test(key)) {
228
+ issue([...path, "chats", key], "must be provider:externalId[/scope]");
229
+ }
230
+ override(entry, [...path, "chats", key], true);
231
+ }
232
+ }
233
+ }
234
+ const o = section(v.observability, [...path, "observability"], ["storeRouterPayload", "shadow"]);
235
+ bool(o.storeRouterPayload, [...path, "observability", "storeRouterPayload"]);
236
+ smart(o.shadow, [...path, "observability", "shadow"]);
237
+ }
238
+ }
239
+ override(value, [], false);
240
+ return issues;
241
+ }
242
+ /** Expand authored files before bundling. The caller owns the workspace-relative filesystem boundary. */
243
+ export function expandGroupFiles(input, read) {
244
+ if (groupPolicyIssues(input).length > 0) {
245
+ throw new Error("Group policy authoring is invalid.");
246
+ }
247
+ const smart = (value) => {
248
+ const instructions = value?.instructions;
249
+ if (typeof instructions !== "object" || instructions.file === undefined) {
250
+ return value;
251
+ }
252
+ const path = instructions.file;
253
+ if (!path ||
254
+ path.startsWith("/") ||
255
+ path.split(/[\\/]/).some((part) => part === "..") ||
256
+ path.includes("\0")) {
257
+ throw new Error("Group instruction files must stay within agent/.");
258
+ }
259
+ const text = read(path);
260
+ if (text.length > 8000) {
261
+ throw new Error("Group instruction files must fit 8000 characters.");
262
+ }
263
+ return { ...value, instructions: { replace: text } };
264
+ };
265
+ return {
266
+ ...input,
267
+ smart: smart(input.smart),
268
+ ...(input.chats === undefined
269
+ ? {}
270
+ : {
271
+ chats: Object.fromEntries(Object.entries(input.chats).map(([key, value]) => [
272
+ key,
273
+ { ...value, smart: smart(value.smart) },
274
+ ])),
275
+ }),
276
+ ...(input.observability === undefined
277
+ ? {}
278
+ : { observability: { ...input.observability, shadow: smart(input.observability.shadow) } }),
279
+ };
280
+ }
@@ -0,0 +1,10 @@
1
+ import type { NextRequest } from "next/server";
2
+ import type { AppContext } from "./context.js";
3
+ export type RouteHandler = (request: NextRequest) => Promise<Response>;
4
+ export interface SegmentStreamRouteHandlers {
5
+ readonly GET: RouteHandler;
6
+ readonly POST: RouteHandler;
7
+ }
8
+ export declare function createHandlers(context: AppContext, { neon }?: {
9
+ readonly neon?: boolean;
10
+ }): SegmentStreamRouteHandlers;
@@ -0,0 +1,161 @@
1
+ import { NextResponse } from "next/server";
2
+ import { authFlowCookieName, clearCookie, clearSessionCookies, cookiePolicyFor, decodeAuthFlowState, pendingAuthFlowCookies, safeReturnTo, setSessionCookies, } from "./cookies.js";
3
+ import { isSameOrigin } from "./decision-pages.js";
4
+ import { constantTimeEqual } from "./pkce.js";
5
+ import { CALLBACK_PATH, SIGNED_OUT_PATH, SIGN_IN_PATH, SDK_ROUTE_PREFIX, signInRedirect, } from "./sign-in.js";
6
+ export function createHandlers(context, { neon = false } = {}) {
7
+ async function GET(request) {
8
+ switch (request.nextUrl.pathname) {
9
+ case `${SDK_ROUTE_PREFIX}/configuration`:
10
+ return NextResponse.json({ enabled: false, neon, channels: context.config.channels ?? {} }, {
11
+ headers: { "cache-control": "no-store" },
12
+ });
13
+ case CALLBACK_PATH:
14
+ return handleCallback(context, request);
15
+ case SIGN_IN_PATH:
16
+ return signInRedirect({
17
+ config: context.config,
18
+ request,
19
+ returnTo: safeReturnTo(request.nextUrl.searchParams.get("returnTo")),
20
+ clearSession: false,
21
+ });
22
+ case SIGNED_OUT_PATH:
23
+ return htmlResponse(200, "You're signed out", "You have signed out of this app.", "Sign in again");
24
+ default:
25
+ return notFound();
26
+ }
27
+ }
28
+ async function POST(request) {
29
+ if (request.nextUrl.pathname === `${SDK_ROUTE_PREFIX}/sign-out`) {
30
+ return handleSignOut(context, request);
31
+ }
32
+ return notFound();
33
+ }
34
+ return { GET, POST };
35
+ }
36
+ async function handleCallback(context, request) {
37
+ const policy = cookiePolicyFor(request.nextUrl, context.config.workspaceId);
38
+ const code = request.nextUrl.searchParams.get("code");
39
+ const state = request.nextUrl.searchParams.get("state");
40
+ const flowCookie = state === null ? undefined : authFlowCookieName(policy, state);
41
+ const flow = flowCookie === undefined ? null : decodeAuthFlowState(request.cookies.get(flowCookie)?.value);
42
+ if (request.nextUrl.searchParams.get("error") === "access_denied") {
43
+ return callbackFailure(policy, flowCookie, 403, "You don't have access to this app.");
44
+ }
45
+ if (state === null ||
46
+ flowCookie === undefined ||
47
+ flow === null ||
48
+ code === null ||
49
+ code === "" ||
50
+ !constantTimeEqual(state, flow.state)) {
51
+ // A replayed or abandoned sign-in whose browser has signed in since, for example in another tab.
52
+ if (await hasSession(context, request, policy)) {
53
+ return returnToApp(request, policy, flowCookie, "/");
54
+ }
55
+ return callbackFailure(policy, flowCookie, 400, "This sign-in link is invalid or has expired.");
56
+ }
57
+ const exchange = await context.requestToken({
58
+ grantType: "authorization_code",
59
+ code,
60
+ codeVerifier: flow.codeVerifier,
61
+ redirectUri: flow.redirectUri,
62
+ });
63
+ if (exchange.status === "unavailable") {
64
+ return callbackFailure(policy, flowCookie, 503, "Sign-in is temporarily unavailable. Try again in a moment.");
65
+ }
66
+ if (exchange.status === "rejected") {
67
+ const accessRevoked = exchange.httpStatus === 403 || exchange.code === "access_revoked";
68
+ return accessRevoked
69
+ ? callbackFailure(policy, flowCookie, 403, "You don't have access to this app.")
70
+ : callbackFailure(policy, flowCookie, 400, "Sign-in could not be completed. Try again.");
71
+ }
72
+ // A token this app would reject must not become a session, or sign-in would loop.
73
+ const verification = await context.verifyToken(exchange.tokens.accessToken);
74
+ if (verification.status === "unavailable") {
75
+ return callbackFailure(policy, flowCookie, 503, "Sign-in is temporarily unavailable. Try again in a moment.");
76
+ }
77
+ if (verification.status !== "valid") {
78
+ return callbackFailure(policy, flowCookie, 400, "Sign-in returned a session for a different app.");
79
+ }
80
+ const response = returnToApp(request, policy, flowCookie, flow.returnTo);
81
+ setSessionCookies(response, policy, exchange.tokens);
82
+ return response;
83
+ }
84
+ async function hasSession(context, request, policy) {
85
+ const accessToken = request.cookies.get(policy.names.session)?.value;
86
+ if (accessToken === undefined || accessToken === "") {
87
+ return false;
88
+ }
89
+ return (await context.verifyToken(accessToken)).status === "valid";
90
+ }
91
+ function returnToApp(request, policy, flowCookie, returnTo) {
92
+ const response = NextResponse.redirect(new URL(returnTo, request.nextUrl.origin), 303);
93
+ response.headers.set("cache-control", "no-store");
94
+ if (flowCookie !== undefined) {
95
+ clearCookie(response, policy, flowCookie);
96
+ }
97
+ return response;
98
+ }
99
+ async function handleSignOut(context, request) {
100
+ if (!isSameOrigin(request)) {
101
+ return htmlResponse(403, "Sign-out refused", "Sign out from this app's own pages.", "Return to the app");
102
+ }
103
+ const policy = cookiePolicyFor(request.nextUrl, context.config.workspaceId);
104
+ const refreshToken = request.cookies.get(policy.names.refresh)?.value;
105
+ if (refreshToken !== undefined && refreshToken !== "") {
106
+ await context.revokeSession(refreshToken);
107
+ }
108
+ const response = NextResponse.redirect(new URL(SIGNED_OUT_PATH, request.nextUrl.origin), 303);
109
+ const pendingFlows = pendingAuthFlowCookies(policy, request.cookies.getAll().map((cookie) => cookie.name));
110
+ response.headers.set("cache-control", "no-store");
111
+ clearSessionCookies(response, policy);
112
+ for (const name of pendingFlows) {
113
+ clearCookie(response, policy, name);
114
+ }
115
+ return response;
116
+ }
117
+ function callbackFailure(policy, flowCookie, status, message) {
118
+ const response = htmlResponse(status, "Sign-in failed", message, "Try again");
119
+ if (flowCookie !== undefined) {
120
+ clearCookie(response, policy, flowCookie);
121
+ }
122
+ return response;
123
+ }
124
+ function notFound() {
125
+ return NextResponse.json({ error: "not_found" }, { status: 404, headers: { "cache-control": "no-store" } });
126
+ }
127
+ function htmlResponse(status, title, message, linkLabel) {
128
+ const body = [
129
+ "<!doctype html>",
130
+ '<html lang="en">',
131
+ "<head>",
132
+ '<meta charset="utf-8">',
133
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
134
+ '<meta name="robots" content="noindex">',
135
+ `<title>${escapeHtml(title)}</title>`,
136
+ "<style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:15vh auto;padding:0 1rem;}</style>",
137
+ "</head>",
138
+ "<body>",
139
+ `<h1>${escapeHtml(title)}</h1>`,
140
+ `<p>${escapeHtml(message)}</p>`,
141
+ `<p><a href="/">${escapeHtml(linkLabel)}</a></p>`,
142
+ "</body>",
143
+ "</html>",
144
+ ].join("\n");
145
+ return new NextResponse(body, {
146
+ status,
147
+ headers: {
148
+ "content-type": "text/html; charset=utf-8",
149
+ "cache-control": "no-store",
150
+ "x-content-type-options": "nosniff",
151
+ },
152
+ });
153
+ }
154
+ function escapeHtml(value) {
155
+ return value
156
+ .replace(/&/g, "&amp;")
157
+ .replace(/</g, "&lt;")
158
+ .replace(/>/g, "&gt;")
159
+ .replace(/"/g, "&quot;")
160
+ .replace(/'/g, "&#39;");
161
+ }
@@ -0,0 +1,46 @@
1
+ import type { SegmentStreamApp } from "./app.js";
2
+ import type { SegmentStreamAppConfig } from "./config.js";
3
+ /**
4
+ * Creates the SegmentStream integration for a Next.js workspace app. Pass the type of the app's
5
+ * ontology to type `segmentstream.ontology`:
6
+ *
7
+ * ```ts
8
+ * import type ontology from "../../ontology";
9
+ * import link from "../segmentstream.json";
10
+ *
11
+ * export const segmentstream = createSegmentStreamApp<typeof ontology>(link);
12
+ * ```
13
+ *
14
+ * With `NEON_AUTH_BASE_URL` set, users sign in with Neon Auth and their roles and permissions come
15
+ * from the ontology. Without it, members sign in with their SegmentStream account, as in 0.2.
16
+ */
17
+ export declare function createSegmentStreamApp<O = unknown>(config: SegmentStreamAppConfig): SegmentStreamApp<O>;
18
+ export { NOT_INVITED, SIGN_IN_LINK_INVALID, SegmentStreamApiError } from "./errors.js";
19
+ export { DeploymentAdmissionError } from "./deployment.js";
20
+ export type { DeploymentObservation, DeploymentCapabilities, DeploymentAdmissionCode, } from "./deployment.js";
21
+ export { grants, isAllowed } from "./permissions.js";
22
+ export { sql } from "./parameters.js";
23
+ export { withRequestTiming } from "./request-timing.js";
24
+ export type { AuthenticationMethod, SegmentStreamApp } from "./app.js";
25
+ export { CONSENT_PAGE } from "./oauth.js";
26
+ export { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE } from "./agent.js";
27
+ export type { AgentIdentities, AgentIdentity, AgentPairing } from "./agent.js";
28
+ export type { AgentAuthorizationRequest, AgentAuthorizations, AgentScope, DeviceCodeUnavailable, DeviceLookup, } from "./oauth.js";
29
+ export type { ApiIssue, ApiLimit } from "./errors.js";
30
+ export type { JobReadOptions, JobRun, JobRunOptions, JobsClient } from "./jobs.js";
31
+ export type { CurrentUser, ScopedPermission } from "./me.js";
32
+ export type { AuthRouteHandler, AuthRouteHandlers, SignOutOptions, UserOptions } from "./neon.js";
33
+ export type { ActionCall, ActionOptions, ActionResponse, Change, Edit, EditKind, EntityClient, EntityFilesClient, FieldFilter, GeneratedActions, UserGeneratedActions, HistoryEntry, HistoryOptions, HistoryPage, JsonRecord, JsonValue, ManyFilter, MetricQueryClient, OntologyClient, EntityMetricQueryClient, OntologyPage, OntologyQuery, SelectedRecord, OrderBy, RequestOptions, UntypedOntologyClient, ValidationIssue, Where, } from "./ontology.js";
34
+ export type { PermissionHolder } from "./permissions.js";
35
+ export type { EntityMetricQuery, MetricQuery, MetricQueryField, MetricQueryFilter, MetricQueryFilterOperator, MetricQueryGranularity, MetricQueryResult, RelativePeriod, WarehouseClient, WarehouseColumn, WarehouseDryRun, WarehouseQueryOptions, WarehouseReadOptions, WarehouseQueryResult, WarehouseRow, WarehouseValue, } from "./data.js";
36
+ export type { ParameterType, QueryParameterValue, QueryParameters, TypedParameter, } from "./parameters.js";
37
+ /** The earlier names of metric queries and their types, deprecated. */
38
+ export type * from "./legacy.js";
39
+ export type { SegmentStreamAppConfig } from "./config.js";
40
+ export type { SegmentStreamRouteHandlers, RouteHandler } from "./handlers.js";
41
+ export type { ProxyOptions, SegmentStreamProxy } from "./proxy.js";
42
+ export type { AppUser } from "./tokens.js";
43
+ export { createBlobStorageClient, type BlobUploadTicket, type FileRevision, type FileTransfer, type FileDownloadOptions, type FileUploadTarget, type UploadSession, type FileUploadOptions, } from "./storage.js";
44
+ /** Whether a form post came from the app itself: the check the agent connect and consent handlers use. */
45
+ export { isSameOrigin } from "./decision-pages.js";
46
+ export { groupPolicyIssues, type GroupPolicyInput, type SmartPolicyInput, type ChatOverrideInput, type Participants, } from "./groups.js";
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ import { createApp } from "./app.js";
2
+ /**
3
+ * Creates the SegmentStream integration for a Next.js workspace app. Pass the type of the app's
4
+ * ontology to type `segmentstream.ontology`:
5
+ *
6
+ * ```ts
7
+ * import type ontology from "../../ontology";
8
+ * import link from "../segmentstream.json";
9
+ *
10
+ * export const segmentstream = createSegmentStreamApp<typeof ontology>(link);
11
+ * ```
12
+ *
13
+ * With `NEON_AUTH_BASE_URL` set, users sign in with Neon Auth and their roles and permissions come
14
+ * from the ontology. Without it, members sign in with their SegmentStream account, as in 0.2.
15
+ */
16
+ export function createSegmentStreamApp(config) {
17
+ return createApp(config);
18
+ }
19
+ export { NOT_INVITED, SIGN_IN_LINK_INVALID, SegmentStreamApiError } from "./errors.js";
20
+ export { DeploymentAdmissionError } from "./deployment.js";
21
+ export { grants, isAllowed } from "./permissions.js";
22
+ export { sql } from "./parameters.js";
23
+ export { withRequestTiming } from "./request-timing.js";
24
+ export { CONSENT_PAGE } from "./oauth.js";
25
+ export { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE } from "./agent.js";
26
+ export { createBlobStorageClient, } from "./storage.js";
27
+ /** Whether a form post came from the app itself: the check the agent connect and consent handlers use. */
28
+ export { isSameOrigin } from "./decision-pages.js";
29
+ export { groupPolicyIssues, } from "./groups.js";